Fossil

Check-in [6c900645ea]
Login

Check-in [6c900645ea]

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

Overview
Comment:Add the x-fossil-xfer-login header check in one additional place. With the help of the included debug output, the login problem seems to be caused by CGI (only) instances not reading the inbound HTTP headers. My attempts to make it do have, so far, only triggered HTTP 500 responses. (Edit: i'd forgotten that CGIs don't get headers. The headers are necessarily consumed by the web server to find the CGI script and populate its environment.)
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | xfer-login-card
Files: files | file ages | folders
SHA3-256: 6c900645ea5765e26067f0032a312ed6941f1b843d324edf2eae54d04cff8052
User & Date: stephan 2025-07-23 17:39:48.989
Original Comment: Add the x-fossil-xfer-login header check in one additional place. With the help of the included debug output, the login problem seems to be caused by CGI (only) instances not reading the inbound HTTP headers. My attempts to make it do have, so far, only triggered HTTP 500 responses.
Context
2025-07-23
20:56
Account for CGI-hosted fossil instances by sending the xfer login card as a URL argument. This is somewhat inelegant but works around their inability to read HTTP headers. This version is still more verbose than it needs to be, and requires more testing for compatibility with trunk fossil versions. ... (check-in: 439af9348b user: stephan tags: xfer-login-card)
17:39
Add the x-fossil-xfer-login header check in one additional place. With the help of the included debug output, the login problem seems to be caused by CGI (only) instances not reading the inbound HTTP headers. My attempts to make it do have, so far, only triggered HTTP 500 responses. (Edit: i'd forgotten that CGIs don't get headers. The headers are necessarily consumed by the web server to find the CGI script and populate its environment.) ... (check-in: 6c900645ea user: stephan tags: xfer-login-card)
2025-07-22
22:53
Add some debugging 'message' cards to help trace how the remote is handling the login. ... (check-in: 21be2978af user: stephan tags: xfer-login-card)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
2123
2124
2125
2126
2127
2128
2129


2130
2131
2132
2133
2134
2135
2136
void cgi_handle_http_request(const char *zIpAddr){
  char *z, *zToken;
  int i;
  const char *zScheme = "http";
  char zLine[2000];     /* A single line of input. */
  g.fullHttpReply = 1;
  g.zReqType = "HTTP";


  if( cgi_fgets(zLine, sizeof(zLine))==0 ){
    malformed_request("missing header");
  }
  blob_append(&g.httpHeader, zLine, -1);
  cgi_trace(zLine);
  zToken = extract_token(zLine, &z);
  if( zToken==0 ){







>
>







2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
void cgi_handle_http_request(const char *zIpAddr){
  char *z, *zToken;
  int i;
  const char *zScheme = "http";
  char zLine[2000];     /* A single line of input. */
  g.fullHttpReply = 1;
  g.zReqType = "HTTP";

  /*cgi_setenv("JUST_TESTING1", "cgi_handle_http_request()");*/
  if( cgi_fgets(zLine, sizeof(zLine))==0 ){
    malformed_request("missing header");
  }
  blob_append(&g.httpHeader, zLine, -1);
  cgi_trace(zLine);
  zToken = extract_token(zLine, &z);
  if( zToken==0 ){
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
  if( zIpAddr==0 ){
    zIpAddr = cgi_remote_ip(fossil_fileno(g.httpIn));
  }
  if( zIpAddr ){
    cgi_setenv("REMOTE_ADDR", zIpAddr);
    g.zIpAddr = fossil_strdup(zIpAddr);
  }


  /* Get all the optional fields that follow the first line.
  */
  while( cgi_fgets(zLine,sizeof(zLine)) ){
    char *zFieldName;
    char *zVal;








<







2160
2161
2162
2163
2164
2165
2166

2167
2168
2169
2170
2171
2172
2173
  if( zIpAddr==0 ){
    zIpAddr = cgi_remote_ip(fossil_fileno(g.httpIn));
  }
  if( zIpAddr ){
    cgi_setenv("REMOTE_ADDR", zIpAddr);
    g.zIpAddr = fossil_strdup(zIpAddr);
  }


  /* Get all the optional fields that follow the first line.
  */
  while( cgi_fgets(zLine,sizeof(zLine)) ){
    char *zFieldName;
    char *zVal;

2220
2221
2222
2223
2224
2225
2226

2227
2228
2229
2230
2231
2232
2233
      int x1 = 0;
      int x2 = 0;
      if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
        rangeStart = x1;
        rangeEnd = x2+1;
      }
    }else if( fossil_strcmp(zFieldName, "x-fossil-xfer-login:")==0 ){

      g.syncInfo.zLoginCard = fossil_strdup(zVal);
      g.syncInfo.bLoginCardHeader = 1;
    }
  }
  cgi_setenv("REQUEST_SCHEME",zScheme);
  cgi_init();
  cgi_trace(0);







>







2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
      int x1 = 0;
      int x2 = 0;
      if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
        rangeStart = x1;
        rangeEnd = x2+1;
      }
    }else if( fossil_strcmp(zFieldName, "x-fossil-xfer-login:")==0 ){
      /*cgi_setenv("FOSSIL_LCH_cgi_handle_http_request", zVal);*/
      g.syncInfo.zLoginCard = fossil_strdup(zVal);
      g.syncInfo.bLoginCardHeader = 1;
    }
  }
  cgi_setenv("REQUEST_SCHEME",zScheme);
  cgi_init();
  cgi_trace(0);
Changes to src/http.c.
658
659
660
661
662
663
664




665
666
667
668
669
670
671
      }else{
        if( mHttpFlags & HTTP_GENERIC ){
          if( mHttpFlags & HTTP_NOCOMPRESS ) isCompressed = 0;
        }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){
          isError = 1;
        }
      }




    }
  }
  if( iHttpVersion<0 ){
    /* We got nothing back from the server.  If using the ssh: protocol,
    ** this might mean we need to add or remove the PATH=... argument
    ** to the SSH command being sent.  If that is the case, retry the
    ** request after adding or removing the PATH= argument.







>
>
>
>







658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
      }else{
        if( mHttpFlags & HTTP_GENERIC ){
          if( mHttpFlags & HTTP_NOCOMPRESS ) isCompressed = 0;
        }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){
          isError = 1;
        }
      }
    }else if( fossil_strnicmp(zLine, "x-fossil-xfer-login: ", 21)==0 ){
      /*cgi_setenv("FOSSIL_LCH_http_exchange", &zLine[21]);*/
      g.syncInfo.zLoginCard = fossil_strdup(&zLine[21]);
      g.syncInfo.bLoginCardHeader = 1;
    }
  }
  if( iHttpVersion<0 ){
    /* We got nothing back from the server.  If using the ssh: protocol,
    ** this might mean we need to add or remove the PATH=... argument
    ** to the SSH command being sent.  If that is the case, retry the
    ** request after adding or removing the PATH= argument.
Changes to src/main.c.
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
        @ <!-- Looking for repository named "%h(zRepo)" -->
        fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
      }


      /* Restrictions on the URI for security:
      **
      **    1.  Reject characters that are not ASCII alphanumerics, 
      **        "-", "_", ".", "/", or unicode (above ASCII).
      **        In other words:  No ASCII punctuation or control characters
      **        other than "-", "_", "." and "/".
      **    2.  Exception to rule 1: Allow /X:/ where X is any ASCII 
      **        alphabetic character at the beginning of the name on windows.
      **    3.  "-" may not occur immediately after "/"
      **    4.  "." may not be adjacent to another "." or to "/"
      **
      ** Any character does not satisfy these constraints a Not Found
      ** error is returned.
      */  
      szFile = 0;
      for(j=nBase+1, k=0; zRepo[j] && k<i-1; j++, k++){
        char c = zRepo[j];
        if( c>='a' && c<='z' ) continue;
        if( c>='A' && c<='Z' ) continue;
        if( c>='0' && c<='9' ) continue;
        if( (c&0x80)==0x80 ) continue;







|



|






|







1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
        @ <!-- Looking for repository named "%h(zRepo)" -->
        fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
      }


      /* Restrictions on the URI for security:
      **
      **    1.  Reject characters that are not ASCII alphanumerics,
      **        "-", "_", ".", "/", or unicode (above ASCII).
      **        In other words:  No ASCII punctuation or control characters
      **        other than "-", "_", "." and "/".
      **    2.  Exception to rule 1: Allow /X:/ where X is any ASCII
      **        alphabetic character at the beginning of the name on windows.
      **    3.  "-" may not occur immediately after "/"
      **    4.  "." may not be adjacent to another "." or to "/"
      **
      ** Any character does not satisfy these constraints a Not Found
      ** error is returned.
      */
      szFile = 0;
      for(j=nBase+1, k=0; zRepo[j] && k<i-1; j++, k++){
        char c = zRepo[j];
        if( c>='a' && c<='z' ) continue;
        if( c>='A' && c<='Z' ) continue;
        if( c>='0' && c<='9' ) continue;
        if( (c&0x80)==0x80 ) continue;
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
  if( zRemote ){
    /* If a USER@HOST:REPO argument is supplied, then use SSH to run
    ** "fossil ui --nobrowser" on the remote system and to set up a
    ** tunnel from the local machine to the remote. */
    FILE *sshIn;
    Blob ssh;
    int bRunning = 0;    /* True when fossil starts up on the remote */
    int isRetry;         /* True if on the second attempt */        
    char zLine[1000];

    blob_init(&ssh, 0, 0);
    for(isRetry=0; isRetry<2 && !bRunning; isRetry++){
      blob_reset(&ssh);
      transport_ssh_command(&ssh);
      blob_appendf(&ssh,







|







3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
  if( zRemote ){
    /* If a USER@HOST:REPO argument is supplied, then use SSH to run
    ** "fossil ui --nobrowser" on the remote system and to set up a
    ** tunnel from the local machine to the remote. */
    FILE *sshIn;
    Blob ssh;
    int bRunning = 0;    /* True when fossil starts up on the remote */
    int isRetry;         /* True if on the second attempt */
    char zLine[1000];

    blob_init(&ssh, 0, 0);
    for(isRetry=0; isRetry<2 && !bRunning; isRetry++){
      blob_reset(&ssh);
      transport_ssh_command(&ssh);
      blob_appendf(&ssh,
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
      if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
      if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
      if( fCreate ) blob_appendf(&ssh, " --create");
      blob_appendf(&ssh, " %$", g.argv[2]);
      if( isRetry ){
        fossil_print("First attempt to run \"fossil\" on %s failed\n"
                     "Retry: ", zRemote);
      } 
      fossil_print("%s\n", blob_str(&ssh));
      sshIn = popen(blob_str(&ssh), "r");
      if( sshIn==0 ){
        fossil_fatal("unable to %s", blob_str(&ssh));
      }
      while( fgets(zLine, sizeof(zLine), sshIn) ){
        fputs(zLine, stdout);







|







3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
      if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
      if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
      if( fCreate ) blob_appendf(&ssh, " --create");
      blob_appendf(&ssh, " %$", g.argv[2]);
      if( isRetry ){
        fossil_print("First attempt to run \"fossil\" on %s failed\n"
                     "Retry: ", zRemote);
      }
      fossil_print("%s\n", blob_str(&ssh));
      sshIn = popen(blob_str(&ssh), "r");
      if( sshIn==0 ){
        fossil_fatal("unable to %s", blob_str(&ssh));
      }
      while( fgets(zLine, sizeof(zLine), sshIn) ){
        fputs(zLine, stdout);
Changes to src/xfer.c.
877
878
879
880
881
882
883
884
885
886
887
888

889
890
891
892
893
894
895
    if( rc==0 ){
      const char *zCap;
      zCap = db_column_text(&q, 1);
      login_set_capabilities(zCap, 0);
      g.userUid = db_column_int(&q, 2);
      g.zLogin = mprintf("%b", pLogin);
      g.zNonce = mprintf("%b", pNonce);
      if( g.perm.Debug ){
        @ message g.zLogin=%F(g.zLogin)\szCap=%F(zCap)
      }
    }
  }

  db_finalize(&q);
  return rc;
}

/*
** Send the content of all files in the unsent table.
**







<
<
|
|
<
>







877
878
879
880
881
882
883


884
885

886
887
888
889
890
891
892
893
    if( rc==0 ){
      const char *zCap;
      zCap = db_column_text(&q, 1);
      login_set_capabilities(zCap, 0);
      g.userUid = db_column_int(&q, 2);
      g.zLogin = mprintf("%b", pLogin);
      g.zNonce = mprintf("%b", pNonce);


    }
  }

  @ message login\src=%d(rc)\sas\s%F(g.zLogin)
  db_finalize(&q);
  return rc;
}

/*
** Send the content of all files in the unsent table.
**
1319
1320
1321
1322
1323
1324
1325


1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
  zScript = xfer_push_code();
  if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
    pzUuidList = &zUuidList;
    pnUuidList = &nUuidList;
  }
  if( g.syncInfo.zLoginCard ){
    /* Login card received via HTTP header X-Fossil-Xfer-Login */


    blob_zero(&xfer.line);
    blob_append(&xfer.line, g.syncInfo.zLoginCard, -1);
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
                                count(xfer.aToken));
    fossil_free( g.syncInfo.zLoginCard );
    g.syncInfo.zLoginCard = 0;
    if( xfer.nToken==4
        && blob_eq(&xfer.aToken[0], "login") ){
      @ message got\slogin\scard\sheader
      goto handle_login_card;
    }
  }
  while( blob_line(xfer.pIn, &xfer.line) ){
    if( blob_buffer(&xfer.line)[0]=='#' ) continue;
    if( blob_size(&xfer.line)==0 ) continue;
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));







>
>








<







1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333

1334
1335
1336
1337
1338
1339
1340
  zScript = xfer_push_code();
  if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
    pzUuidList = &zUuidList;
    pnUuidList = &nUuidList;
  }
  if( g.syncInfo.zLoginCard ){
    /* Login card received via HTTP header X-Fossil-Xfer-Login */
    assert( g.syncInfo.bLoginCardHeader && "Set via HTTP header parser" );
    @ message got\slogin\scard\sheader:\s%F(g.syncInfo.zLoginCard)
    blob_zero(&xfer.line);
    blob_append(&xfer.line, g.syncInfo.zLoginCard, -1);
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
                                count(xfer.aToken));
    fossil_free( g.syncInfo.zLoginCard );
    g.syncInfo.zLoginCard = 0;
    if( xfer.nToken==4
        && blob_eq(&xfer.aToken[0], "login") ){

      goto handle_login_card;
    }
  }
  while( blob_line(xfer.pIn, &xfer.line) ){
    if( blob_buffer(&xfer.line)[0]=='#' ) continue;
    if( blob_size(&xfer.line)==0 ) continue;
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
    if( blob_eq(&xfer.aToken[0], "private") ){
      if( !g.perm.Private ){
        server_private_xfer_not_authorized();
      }else{
        xfer.nextIsPrivate = 1;
      }
    }else


    /*    pragma NAME VALUE...
    **
    ** The client issues pragmas to try to influence the behavior of the
    ** server.  These are requests only.  Unknown pragmas are silently
    ** ignored.
    */







<







1680
1681
1682
1683
1684
1685
1686

1687
1688
1689
1690
1691
1692
1693
    if( blob_eq(&xfer.aToken[0], "private") ){
      if( !g.perm.Private ){
        server_private_xfer_not_authorized();
      }else{
        xfer.nextIsPrivate = 1;
      }
    }else


    /*    pragma NAME VALUE...
    **
    ** The client issues pragmas to try to influence the behavior of the
    ** server.  These are requests only.  Unknown pragmas are silently
    ** ignored.
    */
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
    }
    /* Append randomness to the end of the uplink message.  This makes all
    ** 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)!=0
     && (syncFlags & SYNC_XVERBOSE)==0
    ){
      fossil_print("waiting for server...");
    }
    fflush(stdout);







|







2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
    }
    /* Append randomness to the end of the uplink message.  This makes all
    ** 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);
    fossil_free(zRandomness);

    if( (syncFlags & SYNC_VERBOSE)!=0
     && (syncFlags & SYNC_XVERBOSE)==0
    ){
      fossil_print("waiting for server...");
    }
    fflush(stdout);
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
        }
      }else

      /*   message MESSAGE
      **
      ** A message is received from the server.  Print it.
      ** Similar to "error" but does not stop processing.
      **
      ** If the "login failed" message is seen, clear the sync password prior
      ** to the next cycle.
      */
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        if( (syncFlags & SYNC_PUSH) && zMsg
            && sqlite3_strglob("pull only *", zMsg)==0 ){
          syncFlags &= ~SYNC_PUSH;







<
<
<







2755
2756
2757
2758
2759
2760
2761



2762
2763
2764
2765
2766
2767
2768
        }
      }else

      /*   message MESSAGE
      **
      ** A message is received from the server.  Print it.
      ** Similar to "error" but does not stop processing.



      */
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        if( (syncFlags & SYNC_PUSH) && zMsg
            && sqlite3_strglob("pull only *", zMsg)==0 ){
          syncFlags &= ~SYNC_PUSH;
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
    if( go ){
      manifest_crosslink_end(MC_PERMIT_HOOKS);
    }else{
      manifest_crosslink_end(MC_PERMIT_HOOKS);
      content_enable_dephantomize(1);
    }
    db_end_transaction(0);
  };
  transport_stats(&nSent, &nRcvd, 1);
  if( pnRcvd ) *pnRcvd = nArtifactRcvd;
  if( (rSkew*24.0*3600.0) > 10.0 ){
     fossil_warning("*** time skew *** server is fast by %s",
                    db_timespan_name(rSkew));
     g.clockSkewSeen = 1;
  }else if( rSkew*24.0*3600.0 < -10.0 ){







|







2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
    if( go ){
      manifest_crosslink_end(MC_PERMIT_HOOKS);
    }else{
      manifest_crosslink_end(MC_PERMIT_HOOKS);
      content_enable_dephantomize(1);
    }
    db_end_transaction(0);
  }; /* while(go) */
  transport_stats(&nSent, &nRcvd, 1);
  if( pnRcvd ) *pnRcvd = nArtifactRcvd;
  if( (rSkew*24.0*3600.0) > 10.0 ){
     fossil_warning("*** time skew *** server is fast by %s",
                    db_timespan_name(rSkew));
     g.clockSkewSeen = 1;
  }else if( rSkew*24.0*3600.0 < -10.0 ){