Fossil

Check-in [dbb5e2d32a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge the enhanced SSH transport changes into trunk. The ssh command now runs a single instance of "fossil" directly on the remote side, obviating the need for a remote shell.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: dbb5e2d32a8c482e6fccfff89cda87081cc2d594
User & Date: drh 2013-10-09 00:09:10.628
Context
2013-10-09
02:44
Mention the --dontstop option in the "fossil all" on-line help. ... (check-in: f89ba310bb user: drh tags: trunk)
00:09
Merge the enhanced SSH transport changes into trunk. The ssh command now runs a single instance of "fossil" directly on the remote side, obviating the need for a remote shell. ... (check-in: dbb5e2d32a user: drh tags: trunk)
2013-10-08
15:21
double-quote and question mark are a problem as well. ... (check-in: 760f80cd8f user: jan.nijtmans tags: trunk)
2013-10-02
04:22
Merge in latest from trunk to keep up-to-date. ... (Closed-Leaf check-in: c6500ac985 user: andybradford tags: ssh-transport-changes)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
58
59
60
61
62
63
64







65
66
67
68
69
70
71

/*
** Destinations for output text.
*/
#define CGI_HEADER   0
#define CGI_BODY     1








#endif /* INTERFACE */

/*
** The HTTP reply is generated in two pieces: the header and the body.
** These pieces are generated separately because they are not necessary
** produced in order.  Parts of the header might be built after all or
** part of the body.  The header and body are accumulated in separate







>
>
>
>
>
>
>







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

/*
** Destinations for output text.
*/
#define CGI_HEADER   0
#define CGI_BODY     1

/*
** Flags for SSH HTTP clients
*/
#define CGI_SSH_CLIENT           0x0001     /* Client is SSH */
#define CGI_SSH_COMPAT           0x0002     /* Compat for old SSH transport */
#define CGI_SSH_FOSSIL           0x0004     /* Use new Fossil SSH transport */

#endif /* INTERFACE */

/*
** The HTTP reply is generated in two pieces: the header and the body.
** These pieces are generated separately because they are not necessary
** produced in order.  Parts of the header might be built after all or
** part of the body.  The header and body are accumulated in separate
1300
1301
1302
1303
1304
1305
1306
































































































































































































































1307
1308
1309
1310
1311
1312
1313
        cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
      }
    }
  }
  cgi_init();
  cgi_trace(0);
}

































































































































































































































/*
** This routine handles a single SCGI request which is coming in on
** g.httpIn and which replies on g.httpOut
**
** The SCGI request is read from g.httpIn and is used to initialize
** entries in the cgi_parameter() hash, as if those entries were







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
        cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
      }
    }
  }
  cgi_init();
  cgi_trace(0);
}

/*
** This routine handles a single HTTP request from an SSH client which is
** coming in on g.httpIn and which replies on g.httpOut
**
** Once all the setup is finished, this procedure returns
** and subsequent code handles the actual generation of the webpage.
**
** It is called in a loop so some variables will need to be replaced
*/
void cgi_handle_ssh_http_request(const char *zIpAddr){
  static int nCycles = 0;
  static char *zCmd = 0;
  char *z, *zToken;
  const char *zType;
  int i, content_length;
  char zLine[2000];     /* A single line of input. */

  if( zIpAddr ){
    if( nCycles==0 ){   
      cgi_setenv("REMOTE_ADDR", zIpAddr);
      g.zIpAddr = mprintf("%s", zIpAddr);
    }
  }else{
    fossil_panic("missing SSH IP address");
  }
  if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
    malformed_request("missing HTTP header");
  }
  cgi_trace(zLine);
  zToken = extract_token(zLine, &z);
  if( zToken==0 ){
    malformed_request("malformed HTTP header");
  }

  if( fossil_strcmp(zToken, "echo")==0 ){
    /* start looking for probes to complete transport_open */
    zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken);
    if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
      malformed_request("missing HTTP header");
    }
    cgi_trace(zLine);
    zToken = extract_token(zLine, &z);
    if( zToken==0 ){
      malformed_request("malformed HTTP header");
    }
  }else if( zToken && strlen(zToken)==0 && zCmd ){
    /* transport_flip request and continued transport_open */
    cgi_handle_ssh_transport(zCmd);
    if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
      malformed_request("missing HTTP header");
    }
    cgi_trace(zLine);
    zToken = extract_token(zLine, &z);
    if( zToken==0 ){
      malformed_request("malformed HTTP header");
    }
  }

  if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
      && fossil_strcmp(zToken,"HEAD")!=0 ){
    malformed_request("unsupported HTTP method");
  }

  if( nCycles==0 ){
    cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
    cgi_setenv("REQUEST_METHOD",zToken);
  }

  zToken = extract_token(z, &z);
  if( zToken==0 ){
    malformed_request("malformed URL in HTTP header");
  }
  if( nCycles==0 ){
    cgi_setenv("REQUEST_URI", zToken);
    cgi_setenv("SCRIPT_NAME", "");
  }

  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  if( nCycles==0 ){
    cgi_setenv("PATH_INFO", zToken);
  }else{
    cgi_replace_parameter("PATH_INFO", mprintf("%s",zToken));
  }
 
  /* Get all the optional fields that follow the first line.
  */
  while( fgets(zLine,sizeof(zLine),g.httpIn) ){
    char *zFieldName;
    char *zVal;

    cgi_trace(zLine);
    zFieldName = extract_token(zLine,&zVal);
    if( zFieldName==0 || *zFieldName==0 ) break;
    while( fossil_isspace(*zVal) ){ zVal++; }
    i = strlen(zVal);
    while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
    zVal[i] = 0;
    for(i=0; zFieldName[i]; i++){
      zFieldName[i] = fossil_tolower(zFieldName[i]);
    }
    if( fossil_strcmp(zFieldName,"content-length:")==0 ){
      content_length = atoi(zVal);
    }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){
      g.zContentType = zType = mprintf("%s", zVal);
    }else if( fossil_strcmp(zFieldName,"host:")==0 ){
      if( nCycles==0 ){
        cgi_setenv("HTTP_HOST", zVal);
      }
    }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
      if( nCycles==0 ){
        cgi_setenv("HTTP_USER_AGENT", zVal);
      }
    }else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){
      if( fossil_strnicmp(zVal, "ssh", 3)==0 ){
        if( nCycles==0 ){
          g.fSshClient |= CGI_SSH_FOSSIL;
          g.fullHttpReply = 0;
        }
      }
    }
  }

  if( nCycles==0 ){
    if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){
      /* did not find new fossil ssh transport */
      g.fSshClient &= ~CGI_SSH_CLIENT;
      g.fullHttpReply = 1;
      cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1");
    }
  }

  cgi_reset_content();
  cgi_destination(CGI_BODY);

  if( content_length>0 && zType ){
    blob_zero(&g.cgiIn);
    if( fossil_strcmp(zType, "application/x-fossil")==0 ){
      blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
      blob_uncompress(&g.cgiIn, &g.cgiIn);
    }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
      blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
    }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
      blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
    }
  }
  cgi_trace(0);
  nCycles++;
}

/*
** This routine handles the old fossil SSH probes
*/
char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){
  /* Start looking for probes */
  while( fossil_strcmp(zToken, "echo")==0 ){
    zToken = extract_token(z, &z);
    if( zToken==0 ){
      malformed_request("malformed probe");
    }
    if( fossil_strncmp(zToken, "test", 4)==0 ||
        fossil_strncmp(zToken, "probe-", 6)==0 ){
      fprintf(g.httpOut, "%s\n", zToken);
      fflush(g.httpOut);
    }else{
      malformed_request("malformed probe");
    }
    if( fgets(zLine, zSize, g.httpIn)==0 ){
      malformed_request("malformed probe");
    }
    cgi_trace(zLine);
    zToken = extract_token(zLine, &z);
    if( zToken==0 ){
      malformed_request("malformed probe");
    }
  }

  /* Got all probes now first transport_open is completed
  ** so return the command that was requested
  */
  g.fSshClient |= CGI_SSH_COMPAT;
  return mprintf("%s", zToken);
}

/*
** This routine handles the old fossil SSH transport_flip
** and transport_open communications if detected.
*/
void cgi_handle_ssh_transport(const char *zCmd){
  char *z, *zToken;
  char zLine[2000];     /* A single line of input. */

  /* look for second newline of transport_flip */
  if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
    malformed_request("incorrect transport_flip");
  }
  cgi_trace(zLine);
  zToken = extract_token(zLine, &z);
  if( zToken && strlen(zToken)==0 ){
    /* look for path to fossil */
    if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
      if ( zCmd==0 ){
        malformed_request("missing fossil command");
      }else{
        /* no new command so exit */
        fossil_exit(0);
      }
    }
    cgi_trace(zLine);
    zToken = extract_token(zLine, &z);
    if( zToken==0 ){
      malformed_request("malformed fossil command");
    }
    /* see if we've seen the command */
    if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){
      return;
    }else{
      malformed_request("transport_open failed");
    }
  }else{
    malformed_request("transport_flip failed");
  }
}

/*
** This routine handles a single SCGI request which is coming in on
** g.httpIn and which replies on g.httpOut
**
** The SCGI request is read from g.httpIn and is used to initialize
** entries in the cgi_parameter() hash, as if those entries were
1598
1599
1600
1601
1602
1603
1604


















  if( zIf==0 ) return;
  if( objectTime > cgi_rfc822_parsedate(zIf) ) return;
  cgi_set_status(304,"Not Modified");
  cgi_reset_content();
  cgi_reply();
  fossil_exit(0);
}

























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
  if( zIf==0 ) return;
  if( objectTime > cgi_rfc822_parsedate(zIf) ) return;
  cgi_set_status(304,"Not Modified");
  cgi_reset_content();
  cgi_reply();
  fossil_exit(0);
}

/*
** Check to see if the remote client is SSH and return
** its IP or return default
*/
const char *cgi_ssh_remote_addr(const char *zDefault){
  char *zIndex;
  const char *zSshConn = fossil_getenv("SSH_CONNECTION");

  if( zSshConn && zSshConn[0] ){
    char *zSshClient = mprintf("%s",zSshConn);
    if( (zIndex = strchr(zSshClient,' '))!=0 ){
      zSshClient[zIndex-zSshClient] = '\0';
      return zSshClient;
    }
  }
  return zDefault;
}
Changes to src/clone.c.
105
106
107
108
109
110
111

112
113
114
115
116
117
118
119
120
121

122
123
124
125
126
127
128
** admin user. This can be overridden using the -A|--admin-user
** parameter.
**
** Options:
**    --admin-user|-A USERNAME   Make USERNAME the administrator
**    --private                  Also clone private branches 
**    --ssl-identity=filename    Use the SSL identity if requested by the server

**
** See also: init
*/
void clone_cmd(void){
  char *zPassword;
  const char *zDefaultUser;   /* Optional name of the default user */
  int nErr = 0;
  int bPrivate = 0;           /* Also clone private branches */

  if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;

  url_proxy_options();
  if( g.argc < 4 ){
    usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config(0);
  if( file_size(g.argv[3])>0 ){
    fossil_fatal("file already exists: %s", g.argv[3]);







>










>







105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
** admin user. This can be overridden using the -A|--admin-user
** parameter.
**
** Options:
**    --admin-user|-A USERNAME   Make USERNAME the administrator
**    --private                  Also clone private branches 
**    --ssl-identity=filename    Use the SSL identity if requested by the server
**    --ssh-command|-c 'command' Use this SSH command
**
** See also: init
*/
void clone_cmd(void){
  char *zPassword;
  const char *zDefaultUser;   /* Optional name of the default user */
  int nErr = 0;
  int bPrivate = 0;           /* Also clone private branches */

  if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;
  clone_ssh_find_options();
  url_proxy_options();
  if( g.argc < 4 ){
    usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config(0);
  if( file_size(g.argv[3])>0 ){
    fossil_fatal("file already exists: %s", g.argv[3]);
165
166
167
168
169
170
171

172
173
174
175
176
177
178
      blob_reset(&fn);
    }
    db_multi_exec(
      "REPLACE INTO config(name,value,mtime)"
      " VALUES('server-code', lower(hex(randomblob(20))), now());"
    );
    url_enable_proxy(0);

    url_get_password_if_needed();
    g.xlinkClusterOnly = 1;
    nErr = client_sync(SYNC_CLONE | bPrivate,CONFIGSET_ALL,0);
    g.xlinkClusterOnly = 0;
    verify_cancel();
    db_end_transaction(0);
    db_close(1);







>







167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
      blob_reset(&fn);
    }
    db_multi_exec(
      "REPLACE INTO config(name,value,mtime)"
      " VALUES('server-code', lower(hex(randomblob(20))), now());"
    );
    url_enable_proxy(0);
    clone_ssh_db_set_options();
    url_get_password_if_needed();
    g.xlinkClusterOnly = 1;
    nErr = client_sync(SYNC_CLONE | bPrivate,CONFIGSET_ALL,0);
    g.xlinkClusterOnly = 0;
    verify_cancel();
    db_end_transaction(0);
    db_close(1);
186
187
188
189
190
191
192






















  fossil_print("Rebuilding repository meta-data...\n");
  rebuild_db(0, 1, 0);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
  db_end_transaction(0);
}





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
  fossil_print("Rebuilding repository meta-data...\n");
  rebuild_db(0, 1, 0);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
  db_end_transaction(0);
}

/*
** Look for SSH clone command line options and setup in globals.
*/
void clone_ssh_find_options(void){
  const char *zSshCmd;        /* SSH command string */

  zSshCmd = find_option("ssh-command","c",1);
  if( zSshCmd && zSshCmd[0] ){
    g.zSshCmd = mprintf("%s", zSshCmd);
  }
}

/*
** Set SSH options discovered in global variables (set from command line 
** options).
*/
void clone_ssh_db_set_options(void){
  if( g.zSshCmd && g.zSshCmd[0] ){
    db_set("ssh-command", g.zSshCmd, 0);
  }
}
Changes to src/http.c.
110
111
112
113
114
115
116

117
118
119
120
121
122
123
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
    fossil_free(zCredentials);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
  blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION 
                     " (" MANIFEST_DATE " " MANIFEST_VERSION ")\r\n");

  if( g.fHttpTrace ){
    blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
  }else{
    blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
  }
  blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload));
}







>







110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
    fossil_free(zCredentials);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
  blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION 
                     " (" MANIFEST_DATE " " MANIFEST_VERSION ")\r\n");
  if( g.urlIsSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( g.fHttpTrace ){
    blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
  }else{
    blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
  }
  blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload));
}
215
216
217
218
219
220
221










222
223
224
225
226
227
228
        goto write_err;
      }
      if( iHttpVersion==0 ){
        closeConnection = 1;
      }else{
        closeConnection = 0;
      }










    }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){
      for(i=15; fossil_isspace(zLine[i]); i++){}
      iLength = atoi(&zLine[i]);
    }else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){
      char c;
      for(i=11; fossil_isspace(zLine[i]); i++){}
      c = zLine[i];







>
>
>
>
>
>
>
>
>
>







216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        goto write_err;
      }
      if( iHttpVersion==0 ){
        closeConnection = 1;
      }else{
        closeConnection = 0;
      }
    }else if( g.urlIsSsh && fossil_strnicmp(zLine, "status:", 7)==0 ){
      if( sscanf(zLine, "Status: %d", &rc)!=1 ) goto write_err;
      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_warning("server says: %s", &zLine[ii]);
        goto write_err;
      }
      closeConnection = 0;
    }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){
      for(i=15; fossil_isspace(zLine[i]); i++){}
      iLength = atoi(&zLine[i]);
    }else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){
      char c;
      for(i=11; fossil_isspace(zLine[i]); i++){}
      c = zLine[i];
293
294
295
296
297
298
299


300
301
302
303
304
305
306
307
308

  /*
  ** Close the connection to the server if appropriate.
  **
  ** FIXME:  There is some bug in the lower layers that prevents the
  ** connection from remaining open.  The easiest fix for now is to
  ** simply close and restart the connection for each round-trip.


  */
  closeConnection = 1; /* FIX ME */
  if( closeConnection ){
    transport_close();
  }else{
    transport_rewind();
  }
  return 0;








>
>

|







304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321

  /*
  ** Close the connection to the server if appropriate.
  **
  ** FIXME:  There is some bug in the lower layers that prevents the
  ** connection from remaining open.  The easiest fix for now is to
  ** simply close and restart the connection for each round-trip.
  **
  ** For SSH we will leave the connection open.
  */
  if( ! g.urlIsSsh ) closeConnection = 1; /* FIX ME */
  if( closeConnection ){
    transport_close();
  }else{
    transport_rewind();
  }
  return 0;

Changes to src/http_socket.c.
207
208
209
210
211
212
213

















    if( got<=0 ) break;
    total += (size_t)got;
    N -= (size_t)got;
    pContent = (void*)&((char*)pContent)[got];
  }
  return total;
}
























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    if( got<=0 ) break;
    total += (size_t)got;
    N -= (size_t)got;
    pContent = (void*)&((char*)pContent)[got];
  }
  return total;
}

/*
** Attempt to resolve g.urlName to IP and setup g.zIpAddr so rcvfrom gets
** populated. For hostnames with more than one IP (or if overridden in
** ~/.ssh/config) the rcvfrom may not match the host to which we connect.
*/
void socket_ssh_resolve_addr(void){
  struct hostent *pHost;        /* Used to make best effort for rcvfrom */
  struct sockaddr_in addr;

  memset(&addr, 0, sizeof(addr));
  pHost = gethostbyname(g.urlName);
  if( pHost!=0 ){
    memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
    g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
  }
}
Changes to src/http_transport.c.
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

190
191
192
193
194
195
196
197
198

199
200

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243

244

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301

302
303
304
305
306
307
308
  if( pnRcvd ) *pnRcvd = transport.nRcvd;
  if( resetFlag ){
    transport.nSent = 0;
    transport.nRcvd = 0;
  }
}

/*
** Read text from sshIn.  Zero-terminate and remove trailing
** whitespace.
*/
static void sshin_read(char *zBuf, int szBuf){
  int got;
  zBuf[0] = 0;
  got = read(sshIn, zBuf, szBuf-1);
  while( got>=0 ){
    zBuf[got] = 0;
    if( got==0 || !fossil_isspace(zBuf[got-1]) ) break;
    got--;
  }
}

/*
** Default SSH command
*/
#ifdef __MINGW32__
static char zDefaultSshCmd[] = "ssh -T";
#else
static char zDefaultSshCmd[] = "ssh -e none -T";
#endif

/*
** Generate a random SSH link problem keyword
*/
static int random_probe(char *zProbe, int nProbe){
  unsigned r[4];
  sqlite3_randomness(sizeof(r), r);
  sqlite3_snprintf(nProbe, zProbe, "probe-%08x%08x%08x%08x",
                   r[0], r[1], r[2], r[3]);
  return (int)strlen(zProbe);
}

/*
** Bring up an SSH link.  This involves sending some "echo" commands and
** get back appropriate responses.  The point is to move past the MOTD and
** verify that the link is working.
*/
static void transport_ssh_startup(void){
  char *zIn;                       /* An input line received back from remote */
  int nWait;                       /* Number of times waiting for the MOTD */
  char zProbe[40];                 /* Text of the random probe */
  int nProbe;                      /* Size of probe message */
  int nIn;                         /* Size of input */
  static const int nBuf = 10000;   /* Size of input buffer */

  zIn = fossil_malloc(nBuf);
  nProbe = random_probe(zProbe, sizeof(zProbe));
  fprintf(sshOut, "echo %s\n", zProbe);
  fflush(sshOut);
  if( g.fSshTrace ){
    printf("Sent: [echo %s]\n", zProbe);
    fflush(stdout);
  }
  memset(zIn, '*', nProbe);
  for(nWait=1; nWait<=10; nWait++){
    sshin_read(zIn+nProbe, nBuf-nProbe);
    if( g.fSshTrace ){
      printf("Got back-----------------------------------------------\n"
             "%s\n"
             "-------------------------------------------------------\n",
             zIn+nProbe);
    }
    if( strstr(zIn, zProbe) ) break;
    sqlite3_sleep(100*nWait);
    nIn = (int)strlen(zIn);
    memcpy(zIn, zIn+(nIn-nProbe), nProbe);
    if( g.fSshTrace ){
      printf("Fetching more text.  Looking for [%s]...\n", zProbe);
      fflush(stdout);
    }
  }
  nProbe = random_probe(zProbe, sizeof(zProbe));
  fprintf(sshOut, "echo %s\n", zProbe);
  fflush(sshOut);
  if( g.fSshTrace ){
    printf("Sent: [echo %s]\n", zProbe);
    fflush(stdout);
  }
  sshin_read(zIn, nBuf);
  if( zIn[0]==0 ){
    sqlite3_sleep(250);
    sshin_read(zIn, nBuf);
  }
  if( g.fSshTrace ){
    printf("Got back-----------------------------------------------\n"
           "%s\n"
           "-------------------------------------------------------\n", zIn);
  }
  if( memcmp(zIn, zProbe, nProbe)!=0 ){
    pclose2(sshIn, sshOut, sshPid);
    fossil_fatal("ssh connection failed: [%s]", zIn);
  }
  fossil_free(zIn);
}

/*
** Global initialization of the transport layer
*/
void transport_global_startup(void){
  if( g.urlIsSsh ){
    /* Only SSH requires a global initialization.  For SSH we need to create
    ** and run an SSH command to talk to the remote machine.
    */
    const char *zSsh;  /* The base SSH command */
    Blob zCmd;         /* The SSH command */
    char *zHost;       /* The host name to contact */
    int n;             /* Size of prefix string */


    zSsh = db_get("ssh-command", zDefaultSshCmd);
    blob_init(&zCmd, zSsh, -1);
    if( g.urlPort!=g.urlDfltPort ){
#ifdef __MINGW32__
      blob_appendf(&zCmd, " -P %d", g.urlPort);
#else
      blob_appendf(&zCmd, " -p %d", g.urlPort);
#endif
    }

    fossil_force_newline();
    fossil_print("%s", blob_str(&zCmd));  /* Show the base of the SSH command */

    if( g.urlUser && g.urlUser[0] ){
      zHost = mprintf("%s@%s", g.urlUser, g.urlName);
#ifdef __MINGW32__
      /* Only win32 (and specifically PLINK.EXE) support the -pw option */
      if( g.urlPasswd && g.urlPasswd[0] ){
        Blob pw;
        blob_zero(&pw);
        if( g.urlPasswd[0]=='*' ){
          char *zPrompt;
          zPrompt = mprintf("Password for [%s]: ", zHost);
          prompt_for_password(zPrompt, &pw, 0);
          free(zPrompt);
        }else{
          blob_init(&pw, g.urlPasswd, -1);
        }
        blob_append(&zCmd, " -pw ", -1);
        shell_escape(&zCmd, blob_str(&pw));
        blob_reset(&pw);
        fossil_print(" -pw ********");  /* Do not show the password text */
      }
#endif
    }else{
      zHost = mprintf("%s", g.urlName);
    }
    n = blob_size(&zCmd);
    blob_append(&zCmd, " ", 1);
    shell_escape(&zCmd, zHost);



    if( g.urlShell ){
      blob_appendf(&zCmd, " %s", g.urlShell);
    }else{
#if defined(FOSSIL_ENABLE_SSH_FAR_SIDE)
      /* The following works.  But only if the fossil on the remote side
      ** is recent enough to support the test-ssh-far-side command.  That
      ** command was added on 2013-02-06.  We will leave this turned off
      ** until most fossil servers have upgraded to that version or a later
      ** version.  The sync will still work as long as the shell on the far
      ** side is bash and not tcsh.  And if the default far side shell is
      ** tcsh, then the shell=/bin/bash query parameter can be used as a
      ** work-around.  Enable this code after about a year...
      */
      blob_appendf(&zCmd, " exec %s test-ssh-far-side", g.urlFossil);
#endif
    }

    fossil_print("%s\n", blob_str(&zCmd)+n);  /* Show tail of SSH command */

    free(zHost);
    popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
    if( sshPid==0 ){
      fossil_fatal("cannot start ssh tunnel using [%b]", &zCmd);
    }
    blob_reset(&zCmd);
    transport_ssh_startup();
  }
}

/*
** COMMAND: test-ssh-far-side
**
** Read lines of input text, one by one, and evaluate each line using
** system().  The ssh: sync protocol uses this on the far side of the
** SSH link.
*/
void test_ssh_far_side_cmd(void){
  int i = 0;
  int got;
  char zLine[5000];
  while( i<sizeof(zLine) ){
    got = read(0, zLine+i, 1);
    if( got==0 ) return;
    if( zLine[i]=='\n' ){
      zLine[i] = 0;
      system(zLine);
      i = 0;
    }else{
      i++;
    }
  }
}

/*
** Open a connection to the server.  The server is defined by the following
** global variables:
**
**   g.urlName        Name of the server.  Ex: www.fossil-scm.org
**   g.urlPort        TCP/IP port.  Ex: 80
**   g.urlIsHttps     Use TLS for the connection
**
** Return the number of errors.
*/
int transport_open(void){
  int rc = 0;
  if( transport.isOpen==0 ){
    if( g.urlIsSsh ){
      Blob cmd;
      blob_zero(&cmd);
      shell_escape(&cmd, g.urlFossil);
      blob_append(&cmd, " test-http ", -1);
      shell_escape(&cmd, g.urlPath);
      fprintf(sshOut, "%s || true\n", blob_str(&cmd));
      fflush(sshOut);
      if( g.fSshTrace ) printf("Sent: [%s]\n", blob_str(&cmd));
      blob_reset(&cmd);

    }else if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      rc = ssl_open();
      if( rc==0 ) transport.isOpen = 1;
      #else
      socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
      rc = 1;







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|

|
<
|
|
|
|
|
|
|

>
|
|
|

|

|

|
>


>
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|
|
|
>
>
>
|
|
<
<
<
<
<
<
<
<
<
<
<
|
<
|
>

>
|
|
|
|
|
|
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















<
<
<
|
<
<
<
<
<
>







72
73
74
75
76
77
78















79
80
81
82
83
84
85
86
87
88










































































89
90
91

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115



















116
117
118
119
120
121
122
123
124
125
126











127

128
129
130
131
132
133
134
135
136
137



138






















139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154



155





156
157
158
159
160
161
162
163
  if( pnRcvd ) *pnRcvd = transport.nRcvd;
  if( resetFlag ){
    transport.nSent = 0;
    transport.nRcvd = 0;
  }
}
















/*
** Default SSH command
*/
#ifdef __MINGW32__
static char zDefaultSshCmd[] = "ssh -T";
#else
static char zDefaultSshCmd[] = "ssh -e none -T";
#endif

/*










































































** SSH initialization of the transport layer
*/
int transport_ssh_open(void){

  /* For SSH we need to create and run SSH fossil http 
  ** to talk to the remote machine.
  */
  const char *zSsh;  /* The base SSH command */
  Blob zCmd;         /* The SSH command */
  char *zHost;       /* The host name to contact */
  int n;             /* Size of prefix string */

  socket_ssh_resolve_addr();
  zSsh = db_get("ssh-command", zDefaultSshCmd);
  blob_init(&zCmd, zSsh, -1);
  if( g.urlPort!=g.urlDfltPort && g.urlPort ){
#ifdef __MINGW32__
    blob_appendf(&zCmd, " -P %d", g.urlPort);
#else
    blob_appendf(&zCmd, " -p %d", g.urlPort);
#endif
  }
  if( g.fSshTrace ){
    fossil_force_newline();
    fossil_print("%s", blob_str(&zCmd));  /* Show the base of the SSH command */
  }
  if( g.urlUser && g.urlUser[0] ){
    zHost = mprintf("%s@%s", g.urlUser, g.urlName);



















  }else{
    zHost = mprintf("%s", g.urlName);
  }
  n = blob_size(&zCmd);
  blob_append(&zCmd, " ", 1);
  shell_escape(&zCmd, zHost);
  blob_append(&zCmd, " ", 1);
  shell_escape(&zCmd, mprintf("%s", g.urlFossil));
  blob_append(&zCmd, " test-http", 10);
  if( g.urlPath && g.urlPath[0] ){
    blob_append(&zCmd, " ", 1);











    shell_escape(&zCmd, mprintf("%s", g.urlPath));

  }
  if( g.fSshTrace ){
    fossil_print("%s\n", blob_str(&zCmd)+n);  /* Show tail of SSH command */
  }
  free(zHost);
  popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
  if( sshPid==0 ){
    socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
  }
  blob_reset(&zCmd);



  return sshPid==0;






















}

/*
** Open a connection to the server.  The server is defined by the following
** global variables:
**
**   g.urlName        Name of the server.  Ex: www.fossil-scm.org
**   g.urlPort        TCP/IP port.  Ex: 80
**   g.urlIsHttps     Use TLS for the connection
**
** Return the number of errors.
*/
int transport_open(void){
  int rc = 0;
  if( transport.isOpen==0 ){
    if( g.urlIsSsh ){



      rc = transport_ssh_open();





      if( rc==0 ) transport.isOpen = 1;
    }else if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      rc = ssl_open();
      if( rc==0 ) transport.isOpen = 1;
      #else
      socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
      rc = 1;
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
    transport.nUsed = 0;
    transport.iCursor = 0;
    if( transport.pLog ){
      fclose(transport.pLog);
      transport.pLog = 0;
    }
    if( g.urlIsSsh ){
      /* No-op */
    }else if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      ssl_close();
      #endif
    }else if( g.urlIsFile ){
      if( transport.pFile ){
        fclose(transport.pFile);







|







193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
    transport.nUsed = 0;
    transport.iCursor = 0;
    if( transport.pLog ){
      fclose(transport.pLog);
      transport.pLog = 0;
    }
    if( g.urlIsSsh ){
      transport_ssh_close();
    }else if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      ssl_close();
      #endif
    }else if( g.urlIsFile ){
      if( transport.pFile ){
        fclose(transport.pFile);
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
}

/*
** This routine is called when the outbound message is complete and
** it is time to being receiving a reply.
*/
void transport_flip(void){
  if( g.urlIsSsh ){
    fprintf(sshOut, "\n\n");
  }else if( g.urlIsFile ){
    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
       g.nameOfExe, g.urlName, transport.zOutFile, transport.zInFile
    );
    fossil_system(zCmd);
    free(zCmd);







|
<
<







252
253
254
255
256
257
258
259


260
261
262
263
264
265
266
}

/*
** This routine is called when the outbound message is complete and
** it is time to being receiving a reply.
*/
void transport_flip(void){
  if( g.urlIsFile ){


    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
       g.nameOfExe, g.urlName, transport.zOutFile, transport.zInFile
    );
    fossil_system(zCmd);
    free(zCmd);
579
580
581
582
583
584
585



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600












    }
    i++;
  }
  if( g.fSshTrace ) printf("Got line: [%s]\n", &transport.pBuf[iStart]);
  return &transport.pBuf[iStart];
}




void transport_global_shutdown(void){
  if( g.urlIsSsh && sshPid ){
    /*printf("Closing SSH tunnel: ");*/
    fflush(stdout);
    pclose2(sshIn, sshOut, sshPid);
    sshPid = 0;
  }
  if( g.urlIsHttps ){
    #ifdef FOSSIL_ENABLE_SSL
    ssl_global_shutdown();
    #endif
  }else{
    socket_global_shutdown();
  }
}



















>
>
>

|
<
<
|
<









>
>
>
>
>
>
>
>
>
>
>
>
432
433
434
435
436
437
438
439
440
441
442
443


444

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
    }
    i++;
  }
  if( g.fSshTrace ) printf("Got line: [%s]\n", &transport.pBuf[iStart]);
  return &transport.pBuf[iStart];
}

/*
** Global transport shutdown
*/
void transport_global_shutdown(void){
  if( g.urlIsSsh ){


    transport_ssh_close();

  }
  if( g.urlIsHttps ){
    #ifdef FOSSIL_ENABLE_SSL
    ssl_global_shutdown();
    #endif
  }else{
    socket_global_shutdown();
  }
}

/*
** Close SSH transport.
*/
void transport_ssh_close(void){
  if( sshPid ){
    /*printf("Closing SSH tunnel: ");*/
    fflush(stdout);
    pclose2(sshIn, sshOut, sshPid);
    sshPid = 0;
  }
}
Changes to src/login.c.
791
792
793
794
795
796
797
798

799
800
801
802
803
804
805
  ** local login is disabled and if we are using HTTP and not HTTPS, 
  ** then there is no need to check user credentials.
  **
  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
  if( fossil_strcmp(zIpAddr, "127.0.0.1")==0

   && g.useLocalauth
   && db_get_int("localauth",0)==0
   && P("HTTPS")==0
  ){
    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "sx";







|
>







791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
  ** local login is disabled and if we are using HTTP and not HTTPS, 
  ** then there is no need to check user credentials.
  **
  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
  if( ( fossil_strcmp(zIpAddr, "127.0.0.1")==0 ||
        g.fSshClient & CGI_SSH_CLIENT )
   && g.useLocalauth
   && db_get_int("localauth",0)==0
   && P("HTTPS")==0
  ){
    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "sx";
Changes to src/main.c.
136
137
138
139
140
141
142


143
144
145
146
147
148
149
  int fSqlTrace;          /* True if --sqltrace flag is present */
  int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
  int fSqlPrint;          /* True if -sqlprint flag is present */
  int fQuiet;             /* True if -quiet flag is present */
  int fHttpTrace;         /* Trace outbound HTTP requests */
  int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
  int fSshTrace;          /* Trace the SSH setup traffic */


  int fNoSync;            /* Do not do an autosync ever.  --nosync */
  char *zPath;            /* Name of webpage being served */
  char *zExtra;           /* Extra path information past the webpage name */
  char *zBaseURL;         /* Full text of the URL being served */
  char *zTop;             /* Parent directory of zPath */
  const char *zContentType;  /* The content type of the input HTTP request */
  int iErrPriority;       /* Priority of current error message */







>
>







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  int fSqlTrace;          /* True if --sqltrace flag is present */
  int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
  int fSqlPrint;          /* True if -sqlprint flag is present */
  int fQuiet;             /* True if -quiet flag is present */
  int fHttpTrace;         /* Trace outbound HTTP requests */
  int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
  int fSshTrace;          /* Trace the SSH setup traffic */
  int fSshClient;         /* HTTP client flags for SSH client */
  char *zSshCmd;          /* SSH command string */
  int fNoSync;            /* Do not do an autosync ever.  --nosync */
  char *zPath;            /* Name of webpage being served */
  char *zExtra;           /* Extra path information past the webpage name */
  char *zBaseURL;         /* Full text of the URL being served */
  char *zTop;             /* Parent directory of zPath */
  const char *zContentType;  /* The content type of the input HTTP request */
  int iErrPriority;       /* Priority of current error message */
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  int urlDfltPort;        /* The default port for the given protocol */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */
  char *urlFossil;        /* The fossil query parameter on ssh: */
  char *urlShell;         /* The shell query parameter on ssh: */
  unsigned urlFlags;      /* Boolean flags controlling URL processing */

  const char *zLogin;     /* Login name.  "" if not logged in. */
  const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of
                             ** SSL client identity */
  int useLocalauth;       /* No login required if from 127.0.0.1 */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */







<







179
180
181
182
183
184
185

186
187
188
189
190
191
192
  int urlDfltPort;        /* The default port for the given protocol */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */
  char *urlFossil;        /* The fossil query parameter on ssh: */

  unsigned urlFlags;      /* Boolean flags controlling URL processing */

  const char *zLogin;     /* Login name.  "" if not logged in. */
  const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of
                             ** SSL client identity */
  int useLocalauth;       /* No login required if from 127.0.0.1 */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
601
602
603
604
605
606
607


608
609
610
611
612
613
614
    const char *zChdir = find_option("chdir",0,1);
    g.isHTTP = 0;
    g.fQuiet = find_option("quiet", 0, 0)!=0;
    g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
    g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;


    if( g.fSqlTrace ) g.fSqlStats = 1;
    g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
    g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
    g.zLogin = find_option("user", "U", 1);
    g.zSSLIdentity = find_option("ssl-identity", 0, 1);
    g.zErrlog = find_option("errorlog", 0, 1);
    if( find_option("utc",0,0) ) g.fTimeFormat = 1;







>
>







602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
    const char *zChdir = find_option("chdir",0,1);
    g.isHTTP = 0;
    g.fQuiet = find_option("quiet", 0, 0)!=0;
    g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
    g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
    g.fSshClient = 0;
    g.zSshCmd = 0;
    if( g.fSqlTrace ) g.fSqlStats = 1;
    g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
    g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
    g.zLogin = find_option("user", "U", 1);
    g.zSSLIdentity = find_option("ssl-identity", 0, 1);
    g.zErrlog = find_option("errorlog", 0, 1);
    if( find_option("utc",0,0) ) g.fTimeFormat = 1;
1312
1313
1314
1315
1316
1317
1318

1319
1320
1321
1322
1323
1324
1325
1326
          zRepo, zPathInfo, zNewScript);
    }
  }

  /* Find the page that the user has requested, construct and deliver that
  ** page.
  */

  if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){
    zPathInfo = "/xfer";
  }
  set_base_url(0);
  if( zPathInfo==0 || zPathInfo[0]==0
      || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
#ifdef FOSSIL_ENABLE_JSON
    if(g.json.isJsonMode){







>
|







1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
          zRepo, zPathInfo, zNewScript);
    }
  }

  /* Find the page that the user has requested, construct and deliver that
  ** page.
  */
  if( g.zContentType &&
      strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
    zPathInfo = "/xfer";
  }
  set_base_url(0);
  if( zPathInfo==0 || zPathInfo[0]==0
      || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
#ifdef FOSSIL_ENABLE_JSON
    if(g.json.isJsonMode){
1729
1730
1731
1732
1733
1734
1735






1736
1737
1738
1739
1740


1741
1742
1743
1744
1745












1746
1747
1748
1749
1750
1751

1752
1753


1754
1755
1756
1757
1758
1759
1760
1761
1762






1763
1764

1765
1766
1767
1768
1769
1770
1771
    g.httpIn = fossil_fopen(g.argv[3], "rb");
    g.httpOut = fossil_fopen(g.argv[4], "wb");
    zIpAddr = g.argv[5];
  }else{
    g.httpIn = stdin;
    g.httpOut = stdout;
    zIpAddr = 0;






  }
  find_server_repository(0);
  g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
  if( useSCGI ){
    cgi_handle_scgi_request();


  }else{
    cgi_handle_http_request(zIpAddr);
  }
  process_one_web_page(zNotFound, glob_create(zFileGlob));
}













/*
** Note that the following command is used by ssh:// processing.
**
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.

*/
void cmd_test_http(void){


  Th_InitTraceLog();
  login_set_capabilities("sx", 0);
  g.useLocalauth = 1;
  cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
  g.httpIn = stdin;
  g.httpOut = stdout;
  find_server_repository(0);
  g.cgiOutput = 1;
  g.fullHttpReply = 1;






  cgi_handle_http_request(0);
  process_one_web_page(0, 0);

}

#if !defined(_WIN32)
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.







>
>
>
>
>
>





>
>





>
>
>
>
>
>
>
>
>
>
>
>






>


>
>



<





>
>
>
>
>
>
|
|
>







1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783

1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
    g.httpIn = fossil_fopen(g.argv[3], "rb");
    g.httpOut = fossil_fopen(g.argv[4], "wb");
    zIpAddr = g.argv[5];
  }else{
    g.httpIn = stdin;
    g.httpOut = stdout;
    zIpAddr = 0;
  }
  if( zIpAddr==0 ){
    zIpAddr = cgi_ssh_remote_addr(0);
    if( zIpAddr && zIpAddr[0] ){
      g.fSshClient |= CGI_SSH_CLIENT;
    }
  }
  find_server_repository(0);
  g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
  if( useSCGI ){
    cgi_handle_scgi_request();
  }else if( g.fSshClient & CGI_SSH_CLIENT ){
    ssh_request_loop(zIpAddr, glob_create(zFileGlob));
  }else{
    cgi_handle_http_request(zIpAddr);
  }
  process_one_web_page(zNotFound, glob_create(zFileGlob));
}

/*
** Process all requests in a single SSH connection if possible.
*/
void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
  do{
    cgi_handle_ssh_http_request(zIpAddr);
    process_one_web_page(0, FileGlob);
    blob_reset(&g.cgiIn);
  } while ( g.fSshClient & CGI_SSH_FOSSIL ||
          g.fSshClient & CGI_SSH_COMPAT );
}

/*
** Note that the following command is used by ssh:// processing.
**
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
**
*/
void cmd_test_http(void){
  const char *zIpAddr;    /* IP address of remote client */

  Th_InitTraceLog();
  login_set_capabilities("sx", 0);
  g.useLocalauth = 1;

  g.httpIn = stdin;
  g.httpOut = stdout;
  find_server_repository(0);
  g.cgiOutput = 1;
  g.fullHttpReply = 1;
  zIpAddr = cgi_ssh_remote_addr(0);
  if( zIpAddr && zIpAddr[0] ){
    g.fSshClient |= CGI_SSH_CLIENT;
    ssh_request_loop(zIpAddr, 0);
  }else{
    cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
    cgi_handle_http_request(0);
    process_one_web_page(0, 0);
  }
}

#if !defined(_WIN32)
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.
Changes to src/popen.c.
25
26
27
28
29
30
31



32
33
34
35
36
37
38
#include <fcntl.h>
/*
** Print a fatal error and quit.
*/
static void win32_fatal_error(const char *zMsg){
  fossil_fatal("%s", zMsg);
}



#endif

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.







>
>
>







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <fcntl.h>
/*
** Print a fatal error and quit.
*/
static void win32_fatal_error(const char *zMsg){
  fossil_fatal("%s", zMsg);
}
#else
#include <signal.h>
#include <sys/wait.h>
#endif

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
169
170
171
172
173
174
175

176
177
178
179
180
181
182
    close(pin[0]);
    close(pin[1]);
    close(pout[0]);
    close(pout[1]);
    *pChildPid = 0;
    return 1;
  }

  if( *pChildPid==0 ){
    int fd;
    int nErr = 0;
    /* This is the child process */
    close(0);
    fd = dup(pout[0]);
    if( fd!=0 ) nErr++;







>







172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    close(pin[0]);
    close(pin[1]);
    close(pout[0]);
    close(pout[1]);
    *pChildPid = 0;
    return 1;
  }
  signal(SIGPIPE,SIG_IGN);
  if( *pChildPid==0 ){
    int fd;
    int nErr = 0;
    /* This is the child process */
    close(0);
    fd = dup(pout[0]);
    if( fd!=0 ) nErr++;
209
210
211
212
213
214
215

216
217
  /* Not implemented, yet */
  close(fdIn);
  fclose(pOut);
#else
  close(fdIn);
  fclose(pOut);
  kill(childPid, SIGINT);

#endif
}







>


213
214
215
216
217
218
219
220
221
222
  /* Not implemented, yet */
  close(fdIn);
  fclose(pOut);
#else
  close(fdIn);
  fclose(pOut);
  kill(childPid, SIGINT);
  while( waitpid(0, 0, WNOHANG)>0 ) {}
#endif
}
Changes to src/sync.c.
49
50
51
52
53
54
55


56
57
58
59
60
61
62
  }else{
    /* Autosync defaults on.  To make it default off, "return" here. */
  }
  url_parse(0, URL_REMEMBER);
  if( g.urlProtocol==0 ) return 0;  
  if( g.urlUser!=0 && g.urlPasswd==0 ){
    g.urlPasswd = unobscure(db_get("last-sync-pw", 0));


  }
#if 0 /* Disabled for now */
  if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
    /* When doing an automatic pull, also automatically pull shuns from
    ** the server if pull_shuns is enabled.
    **
    ** TODO:  What happens if the shun list gets really big? 







>
>







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
  }else{
    /* Autosync defaults on.  To make it default off, "return" here. */
  }
  url_parse(0, URL_REMEMBER);
  if( g.urlProtocol==0 ) return 0;  
  if( g.urlUser!=0 && g.urlPasswd==0 ){
    g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
    g.urlFlags |= URL_PROMPT_PW;
    url_prompt_for_password();
  }
#if 0 /* Disabled for now */
  if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
    /* When doing an automatic pull, also automatically pull shuns from
    ** the server if pull_shuns is enabled.
    **
    ** TODO:  What happens if the shun list gets really big? 
99
100
101
102
103
104
105

106
107
108
109
110
111



112
113
114
115
116
117
118
  /* The --verily option to sync, push, and pull forces extra igot cards
  ** to be exchanged.  This can overcome malfunctions in the sync protocol.
  */
  if( find_option("verily",0,0)!=0 ){
    *pSyncFlags |= SYNC_RESYNC;
  }
  url_proxy_options();

  db_find_and_open_repository(0, 0);
  db_open_config(0);
  if( g.argc==2 ){
    if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN;
  }else if( g.argc==3 ){
    zUrl = g.argv[2];



  }
  url_parse(zUrl, urlFlags);
  if( g.urlProtocol==0 ){
    if( urlOptional ) fossil_exit(0);
    usage("URL");
  }
  user_select();







>






>
>
>







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
  /* The --verily option to sync, push, and pull forces extra igot cards
  ** to be exchanged.  This can overcome malfunctions in the sync protocol.
  */
  if( find_option("verily",0,0)!=0 ){
    *pSyncFlags |= SYNC_RESYNC;
  }
  url_proxy_options();
  clone_ssh_find_options();
  db_find_and_open_repository(0, 0);
  db_open_config(0);
  if( g.argc==2 ){
    if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN;
  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( urlFlags & URL_REMEMBER ){
    clone_ssh_db_set_options();
  }
  url_parse(zUrl, urlFlags);
  if( g.urlProtocol==0 ){
    if( urlOptional ) fossil_exit(0);
    usage("URL");
  }
  user_select();
Changes to src/url.c.
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
      g.urlDfltPort = 443;
      iStart = 8;
    }else if( zUrl[0]=='s' ){
      g.urlIsSsh = 1;
      g.urlProtocol = "ssh";
      g.urlDfltPort = 22;
      g.urlFossil = "fossil";
      g.urlShell = 0;
      iStart = 6;
    }else{
      g.urlIsHttps = 0;
      g.urlProtocol = "http";
      g.urlDfltPort = 80;
      iStart = 7;
    }







<







98
99
100
101
102
103
104

105
106
107
108
109
110
111
      g.urlDfltPort = 443;
      iStart = 8;
    }else if( zUrl[0]=='s' ){
      g.urlIsSsh = 1;
      g.urlProtocol = "ssh";
      g.urlDfltPort = 22;
      g.urlFossil = "fossil";

      iStart = 6;
    }else{
      g.urlIsHttps = 0;
      g.urlProtocol = "http";
      g.urlDfltPort = 80;
      iStart = 7;
    }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
      }
      if( fossil_strcmp(zName,"fossil")==0 ){
        g.urlFossil = zValue;
        dehttpize(g.urlFossil);
        zExe = mprintf("%cfossil=%T", cQuerySep, g.urlFossil);
        cQuerySep = '&';
      }
      if( fossil_strcmp(zName,"shell")==0 ){
        g.urlShell = zValue;
        dehttpize(g.urlShell);
        zExe = mprintf("%cshell=%T", cQuerySep, g.urlFossil);
        cQuerySep = '&';
      }
    }

    dehttpize(g.urlPath);
    if( g.urlDfltPort==g.urlPort ){
      g.urlCanonical = mprintf(
        "%s://%s%T%T%s", 
        g.urlProtocol, zLogin, g.urlName, g.urlPath, zExe







<
<
<
<
<
<







170
171
172
173
174
175
176






177
178
179
180
181
182
183
      }
      if( fossil_strcmp(zName,"fossil")==0 ){
        g.urlFossil = zValue;
        dehttpize(g.urlFossil);
        zExe = mprintf("%cfossil=%T", cQuerySep, g.urlFossil);
        cQuerySep = '&';
      }






    }

    dehttpize(g.urlPath);
    if( g.urlDfltPort==g.urlPort ){
      g.urlCanonical = mprintf(
        "%s://%s%T%T%s", 
        g.urlProtocol, zLogin, g.urlName, g.urlPath, zExe
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456

457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
*/
void url_prompt_for_password(void){
  if( g.urlIsSsh || g.urlIsFile ) return;
  if( isatty(fileno(stdin))
   && (g.urlFlags & URL_PROMPT_PW)!=0
   && (g.urlFlags & URL_PROMPTED)==0
  ){
    char *zPrompt = mprintf("\rpassword for %s: ", g.urlUser);
    Blob x;
    fossil_force_newline();
    prompt_for_password(zPrompt, &x, 0);
    free(zPrompt);
    g.urlPasswd = mprintf("%b", &x);
    blob_reset(&x);
    g.urlFlags |= URL_PROMPTED;

    if( g.urlPasswd[0]
     && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
    ){
      char c;
      prompt_user("remember password (Y/n)? ", &x);
      c = blob_str(&x)[0];
      blob_reset(&x);
      if( c!='n' && c!='N' ){
        g.urlFlags |= URL_REMEMBER_PW;
        if( g.urlFlags & URL_REMEMBER ){
          db_set("last-sync-pw", obscure(g.urlPasswd), 0);
        }
      }
    }
  }else{







<
<
<
<
<
<
<

>



<
|
<
<
<







435
436
437
438
439
440
441







442
443
444
445
446

447



448
449
450
451
452
453
454
*/
void url_prompt_for_password(void){
  if( g.urlIsSsh || g.urlIsFile ) return;
  if( isatty(fileno(stdin))
   && (g.urlFlags & URL_PROMPT_PW)!=0
   && (g.urlFlags & URL_PROMPTED)==0
  ){







    g.urlFlags |= URL_PROMPTED;
    g.urlPasswd = prompt_for_user_password(g.urlUser);
    if( g.urlPasswd[0]
     && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
    ){

      if( save_password_prompt() ){



        g.urlFlags |= URL_REMEMBER_PW;
        if( g.urlFlags & URL_REMEMBER ){
          db_set("last-sync-pw", obscure(g.urlPasswd), 0);
        }
      }
    }
  }else{
488
489
490
491
492
493
494
495
496
497
498
499
/* Preemptively prompt for a password if a username is given in the
** URL but no password.
*/
void url_get_password_if_needed(void){
  if( (g.urlUser && g.urlUser[0])
   && (g.urlPasswd==0 || g.urlPasswd[0]==0)
   && isatty(fileno(stdin)) 
   && g.urlIsSsh==0
  ){
    url_prompt_for_password();
  }
}







<




471
472
473
474
475
476
477

478
479
480
481
/* Preemptively prompt for a password if a username is given in the
** URL but no password.
*/
void url_get_password_if_needed(void){
  if( (g.urlUser && g.urlUser[0])
   && (g.urlPasswd==0 || g.urlPasswd[0]==0)
   && isatty(fileno(stdin)) 

  ){
    url_prompt_for_password();
  }
}
Changes to src/user.c.
126
127
128
129
130
131
132



























133
134
135
136
137
138
139
      fossil_print("Passphrases do not match.  Try again...\n");
    }else{
      break;
    }
  }
  blob_reset(&secondTry);
}




























/*
** Prompt the user to enter a single line of text.
*/
void prompt_user(const char *zPrompt, Blob *pIn){
  char *z;
  char zLine[1000];







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
      fossil_print("Passphrases do not match.  Try again...\n");
    }else{
      break;
    }
  }
  blob_reset(&secondTry);
}

/*
** Prompt to save Fossil user password
*/
int save_password_prompt(){
  Blob x;
  char c;
  prompt_user("remember password (Y/n)? ", &x);
  c = blob_str(&x)[0];
  blob_reset(&x);
  return ( c!='n' && c!='N' );
}

/*
** Prompt for Fossil user password
*/
char *prompt_for_user_password(const char *zUser){
  char *zPrompt = mprintf("\rpassword for %s: ", zUser);
  char *zPw;
  Blob x;
  fossil_force_newline();
  prompt_for_password(zPrompt, &x, 0);
  free(zPrompt);
  zPw = mprintf("%b", &x);
  blob_reset(&x);
  return zPw;
}

/*
** Prompt the user to enter a single line of text.
*/
void prompt_user(const char *zPrompt, Blob *pIn){
  char *z;
  char zLine[1000];
Changes to src/xfer.c.
1226
1227
1228
1229
1230
1231
1232

1233
1234
1235
1236
1237
1238
1239
    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))
    }
    blobarray_reset(xfer.aToken, xfer.nToken);

  }
  if( isPush ){
    if( run_push_script()==TH_ERROR ){
      cgi_reset_content();
      @ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
      nErr++;
    }







>







1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))
    }
    blobarray_reset(xfer.aToken, xfer.nToken);
    blob_reset(&xfer.line);
  }
  if( isPush ){
    if( run_push_script()==TH_ERROR ){
      cgi_reset_content();
      @ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
      nErr++;
    }
1253
1254
1255
1256
1257
1258
1259

1260
1261
1262
1263
1264
1265
1266
    create_cluster();
    send_unclustered(&xfer);
    if( xfer.syncPrivate ) send_private(&xfer);
  }
  if( recvConfig ){
    configure_finalize_receive();
  }

  manifest_crosslink_end();

  /* Send the server timestamp last, in case prior processing happened
  ** to use up a significant fraction of our time window.
  */
  zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')");
  @ # timestamp %s(zNow)







>







1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
    create_cluster();
    send_unclustered(&xfer);
    if( xfer.syncPrivate ) send_private(&xfer);
  }
  if( recvConfig ){
    configure_finalize_receive();
  }
  db_multi_exec("DROP TABLE onremote");
  manifest_crosslink_end();

  /* Send the server timestamp last, in case prior processing happened
  ** to use up a significant fraction of our time window.
  */
  zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')");
  @ # timestamp %s(zNow)
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
  if( syncFlags & SYNC_PUSH ){
    blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
    nCardSent++;
    if( (syncFlags & SYNC_PULL)==0 ) zOpType = "Push";
    if( (syncFlags & SYNC_RESYNC)!=0 ) xfer.resync = 0x7fffffff;
  }
  manifest_crosslink_begin();
  transport_global_startup();
  if( syncFlags & SYNC_VERBOSE ){
    fossil_print(zLabelFormat, "", "Bytes", "Cards", "Artifacts", "Deltas");
  }

  while( go ){
    int newPhantom = 0;
    char *zRandomness;







<







1425
1426
1427
1428
1429
1430
1431

1432
1433
1434
1435
1436
1437
1438
  if( syncFlags & SYNC_PUSH ){
    blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
    nCardSent++;
    if( (syncFlags & SYNC_PULL)==0 ) zOpType = "Push";
    if( (syncFlags & SYNC_RESYNC)!=0 ) xfer.resync = 0x7fffffff;
  }
  manifest_crosslink_begin();

  if( syncFlags & SYNC_VERBOSE ){
    fossil_print(zLabelFormat, "", "Bytes", "Cards", "Artifacts", "Deltas");
  }

  while( go ){
    int newPhantom = 0;
    char *zRandomness;
1502
1503
1504
1505
1506
1507
1508




1509







1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
    ** messages unique so that that the login-card nonce will always
    ** be unique.
    */
    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);
    free(zRandomness);





    /* Exchange messages with the server */







    if( syncFlags & SYNC_VERBOSE ){
      fossil_print(zValueFormat, "Sent:",
                   blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
                   xfer.nFileSent, xfer.nDeltaSent);
    }else{
      nRoundtrip++;
      nArtifactSent += xfer.nFileSent + xfer.nDeltaSent;
      fossil_print(zBriefFormat, nRoundtrip, nArtifactSent, nArtifactRcvd);
    }
    nCardSent = 0;
    nCardRcvd = 0;
    xfer.nFileSent = 0;
    xfer.nDeltaSent = 0;
    xfer.nGimmeSent = 0;
    xfer.nIGotSent = 0;
    if( syncFlags & SYNC_VERBOSE ){
      fossil_print("waiting for server...");
    }
    fflush(stdout);
    if( http_exchange(&send, &recv, (syncFlags & SYNC_CLONE)==0 || nCycle>0,
        MAX_REDIRECTS) ){
      nErr++;
      break;
    }
    lastPctDone = -1;
    blob_reset(&send);
    rArrivalTime = db_double(0.0, "SELECT julianday('now')");

    /* Send the send-private pragma if we are trying to sync private data */
    if( syncFlags & SYNC_PRIVATE ){
      blob_append(&send, "pragma send-private\n", -1);







>
>
>
>

>
>
>
>
>
>
>















<
<
|
<
<
<
<
<
<







1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536


1537






1538
1539
1540
1541
1542
1543
1544
    ** messages unique so that that the login-card nonce will always
    ** be unique.
    */
    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);
    free(zRandomness);

    if( syncFlags & SYNC_VERBOSE ){
      fossil_print("waiting for server...");
    }
    fflush(stdout);
    /* Exchange messages with the server */
    if( http_exchange(&send, &recv, (syncFlags & SYNC_CLONE)==0 || nCycle>0,
        MAX_REDIRECTS) ){
      nErr++;
      break;
    }

    /* Output current stats */
    if( syncFlags & SYNC_VERBOSE ){
      fossil_print(zValueFormat, "Sent:",
                   blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
                   xfer.nFileSent, xfer.nDeltaSent);
    }else{
      nRoundtrip++;
      nArtifactSent += xfer.nFileSent + xfer.nDeltaSent;
      fossil_print(zBriefFormat, nRoundtrip, nArtifactSent, nArtifactRcvd);
    }
    nCardSent = 0;
    nCardRcvd = 0;
    xfer.nFileSent = 0;
    xfer.nDeltaSent = 0;
    xfer.nGimmeSent = 0;
    xfer.nIGotSent = 0;









    lastPctDone = -1;
    blob_reset(&send);
    rArrivalTime = db_double(0.0, "SELECT julianday('now')");

    /* Send the send-private pragma if we are trying to sync private data */
    if( syncFlags & SYNC_PRIVATE ){
      blob_append(&send, "pragma send-private\n", -1);
1773
1774
1775
1776
1777
1778
1779
1780



1781
1782
1783
1784
1785
1786
1787
          defossilize(zMsg);
          fossil_force_newline();
          fossil_print("Error: %s\n", zMsg);
          if( fossil_strcmp(zMsg, "login failed")==0 ){
            if( nCycle<2 ){
              g.urlPasswd = 0;
              go = 1;
              if( g.cgiOutput==0 ) url_prompt_for_password();



            }
          }else{
            blob_appendf(&xfer.err, "server says: %s\n", zMsg);
            nErr++;
          }
          break;
        }







|
>
>
>







1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
          defossilize(zMsg);
          fossil_force_newline();
          fossil_print("Error: %s\n", zMsg);
          if( fossil_strcmp(zMsg, "login failed")==0 ){
            if( nCycle<2 ){
              g.urlPasswd = 0;
              go = 1;
              if( g.cgiOutput==0 ){
                g.urlFlags |= URL_PROMPT_PW;
                url_prompt_for_password();
              }
            }
          }else{
            blob_appendf(&xfer.err, "server says: %s\n", zMsg);
            nErr++;
          }
          break;
        }