Check-in [ff942066d5]
Not logged in

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

Overview
Comment:For testing purposes only, unconditionally use the X-Fossil-Xfer-Login HTTP header for sync requests, rather than add it to the payload (which seems to work okay). This is primarily so that apples-to-apples comparisons can be made in libfossil's testing, and will be reverted (or applied conditionally) once the libfossil side is working.
Timelines: family | ancestors | descendants | both | xfer-login-card
Files: files | file ages | folders
SHA3-256: ff942066d5bb2ce61ee7981895f3b6e0bb4e212ab6eff532e28d18ce34819a2e
User & Date: stephan 2025-07-22 02:16:43.517
Context
2025-07-22
02:32
The previous checkin left me unable to push because (of course) the remote trunk doesn't know how to use the login card header. This checkin disables, via a macro toggle, the use of that header on outbound sync requests. check-in: cb42278d84 user: stephan tags: xfer-login-card
02:16
For testing purposes only, unconditionally use the X-Fossil-Xfer-Login HTTP header for sync requests, rather than add it to the payload (which seems to work okay). This is primarily so that apples-to-apples comparisons can be made in libfossil's testing, and will be reverted (or applied conditionally) once the libfossil side is working. check-in: ff942066d5 user: stephan tags: xfer-login-card
2025-07-21
23:45
Move the X-Fossil-Xfer-Login header check to the correct end of the connection. It is receiving these from libfossil tests but is failing to validate them, but that may well be a bug in that brand new downstream code. check-in: b49c9b3685 user: stephan tags: xfer-login-card
Changes
Unified Diff Ignore Whitespace Patch
Changes to VERSION.
1
2.27
|
1
2.27.1
Changes to src/http.c.
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
    }
    fossil_free(g.url.passwd);
    g.url.passwd = fossil_strdup(zPw);
  }

  blob_append(&pw, zPw, -1);
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig);
  blob_reset(&pw);
  blob_reset(&sig);
  blob_reset(&nonce);
}

/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card) already compressed.
*/
static void http_build_header(

  Blob *pPayload,              /* the payload that will be sent */
  Blob *pHdr,                  /* construct the header here */
  const char *zAltMimetype     /* Alternative mimetype */
){
  int nPayload = pPayload ? blob_size(pPayload) : 0;

  blob_zero(pHdr);







|











>







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
    }
    fossil_free(g.url.passwd);
    g.url.passwd = fossil_strdup(zPw);
  }

  blob_append(&pw, zPw, -1);
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
  blob_reset(&pw);
  blob_reset(&sig);
  blob_reset(&nonce);
}

/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card) already compressed.
*/
static void http_build_header(
  Blob *pLogin,                /* Login card or NULL */
  Blob *pPayload,              /* the payload that will be sent */
  Blob *pHdr,                  /* construct the header here */
  const char *zAltMimetype     /* Alternative mimetype */
){
  int nPayload = pPayload ? blob_size(pPayload) : 0;

  blob_zero(pHdr);
151
152
153
154
155
156
157



158
159
160
161
162
163
164
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");



  if( nPayload ){
    if( zAltMimetype ){
      blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
    }else 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");







>
>
>







152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( pLogin ){
    blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin);
  }
  if( nPayload ){
    if( zAltMimetype ){
      blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
    }else 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");
458
459
460
461
462
463
464

465
466
467
468

469











470
471
472



473
474
475

476
477
478

479

480
481
482
483
484
485
486
  if( transport_open(&g.url) ){
    fossil_warning("%s", transport_errmsg(&g.url));
    return 1;
  }

  /* Construct the login card and prepare the complete payload */
  if( blob_size(pSend)==0 ){

    blob_zero(&payload);
  }else{
    blob_zero(&login);
    if( mHttpFlags & HTTP_USE_LOGIN ) http_build_login_card(pSend, &login);

    if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){











      payload = login;
      blob_append(&payload, blob_buffer(pSend), blob_size(pSend));
    }else{



      blob_compress2(&login, pSend, &payload);
      blob_reset(&login);
    }

  }

  /* Construct the HTTP request header */

  http_build_header(&payload, &hdr, zAltMimetype);


  /* When tracing, write the transmitted HTTP message both to standard
  ** output and into a file.  The file can then be used to drive the
  ** server-side like this:
  **
  **      ./fossil test-http <http-request-1.txt
  */







>




>

>
>
>
>
>
>
>
>
>
>
>



>
>
>



>



>
|
>







462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
  if( transport_open(&g.url) ){
    fossil_warning("%s", transport_errmsg(&g.url));
    return 1;
  }

  /* Construct the login card and prepare the complete payload */
  if( blob_size(pSend)==0 ){
    blob_zero(&login);
    blob_zero(&payload);
  }else{
    blob_zero(&login);
    if( mHttpFlags & HTTP_USE_LOGIN ) http_build_login_card(pSend, &login);
#if RELEASE_VERSION_NUMBER >= 22701
    if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){
      /*blob_append(&payload, blob_buffer(pSend), blob_size(pSend));*/
      blob_zero(&payload);
      blob_swap(pSend, &payload);
    }else{
      blob_compress(pSend, &payload);
    }
#else
    if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){
      if( blob_size(&login) ){
        blob_append_char(&login, '\n');
      }
      payload = login;
      blob_append(&payload, blob_buffer(pSend), blob_size(pSend));
    }else{
      if( blob_size(&login) ){
        blob_append_char(&login, '\n');
      }
      blob_compress2(&login, pSend, &payload);
      blob_reset(&login);
    }
#endif
  }

  /* Construct the HTTP request header */
  http_build_header(blob_size(&login) ? &login : 0,
                    &payload, &hdr, zAltMimetype);
  blob_reset(&login);

  /* When tracing, write the transmitted HTTP message both to standard
  ** output and into a file.  The file can then be used to drive the
  ** server-side like this:
  **
  **      ./fossil test-http <http-request-1.txt
  */
Changes to src/user.c.
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
      fossil_print("password unchanged\n");
    }else{
      char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
      db_unprotect(PROTECT_USER);
      db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
                    zSecret, uid);
      db_protect_pop();
      free(zSecret);
    }
  }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
    int uid;
    if( g.argc!=4 && g.argc!=5 ){
      usage("capabilities USERNAME ?PERMISSIONS?");
    }
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);







|







463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
      fossil_print("password unchanged\n");
    }else{
      char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
      db_unprotect(PROTECT_USER);
      db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
                    zSecret, uid);
      db_protect_pop();
      fossil_free(zSecret);
    }
  }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
    int uid;
    if( g.argc!=4 && g.argc!=5 ){
      usage("capabilities USERNAME ?PERMISSIONS?");
    }
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
Changes to src/xfer.c.
788
789
790
791
792
793
794





795
796
797
798
799
800
801
** the length of the input hash in pHash.
*/
static int check_tail_hash(Blob *pHash, Blob *pMsg){
  Blob tail;
  int rc;
  blob_tail(pMsg, &tail);
  rc = hname_verify_hash(&tail, blob_buffer(pHash), blob_size(pHash));





  blob_reset(&tail);
  return rc==HNAME_ERROR;
}

/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:







>
>
>
>
>







788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
** the length of the input hash in pHash.
*/
static int check_tail_hash(Blob *pHash, Blob *pMsg){
  Blob tail;
  int rc;
  blob_tail(pMsg, &tail);
  rc = hname_verify_hash(&tail, blob_buffer(pHash), blob_size(pHash));
#if 0
  fprintf(stderr, "check tail=%d hash=[%.*s]\ntail=<<%.*s>>\n", rc,
          blob_size(pHash), blob_str(pHash),
          blob_size(&tail), blob_str(&tail));
#endif
  blob_reset(&tail);
  return rc==HNAME_ERROR;
}

/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
static int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
  Stmt q;
  int rc = -1;
  char *zLogin = blob_terminate(pLogin);
  defossilize(zLogin);

  if( fossil_strcmp(zLogin, "nobody")==0
   || fossil_strcmp(zLogin,"anonymous")==0
  ){
    return 0;   /* Anybody is allowed to sync as "nobody" or "anonymous" */
  }
  if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0
      && db_get_boolean("remote_user_ok",0) ){
    return 0;   /* Accept Basic Authorization */
  }







|







830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
static int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
  Stmt q;
  int rc = -1;
  char *zLogin = blob_terminate(pLogin);
  defossilize(zLogin);

  if( fossil_strcmp(zLogin, "nobody")==0
   || fossil_strcmp(zLogin, "anonymous")==0
  ){
    return 0;   /* Anybody is allowed to sync as "nobody" or "anonymous" */
  }
  if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0
      && db_get_boolean("remote_user_ok",0) ){
    return 0;   /* Accept Basic Authorization */
  }
852
853
854
855
856
857
858









859
860
861
862
863
864
865
    szPw = blob_size(&pw);
    blob_zero(&combined);
    blob_copy(&combined, pNonce);
    blob_append(&combined, blob_buffer(&pw), szPw);
    sha1sum_blob(&combined, &hash);
    assert( blob_size(&hash)==40 );
    rc = blob_constant_time_cmp(&hash, pSig);









    blob_reset(&hash);
    blob_reset(&combined);
    if( rc!=0 && szPw!=40 ){
      /* If this server stores cleartext passwords and the password did not
      ** match, then perhaps the client is sending SHA1 passwords.  Try
      ** again with the SHA1 password.
      */







>
>
>
>
>
>
>
>
>







857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
    szPw = blob_size(&pw);
    blob_zero(&combined);
    blob_copy(&combined, pNonce);
    blob_append(&combined, blob_buffer(&pw), szPw);
    sha1sum_blob(&combined, &hash);
    assert( blob_size(&hash)==40 );
    rc = blob_constant_time_cmp(&hash, pSig);
#if 0
  fprintf(stderr,
          "check login rc=%d nonce=[%.*s] pSig=[%.*s] .hash=[%.*s]\n",
          rc,
          blob_size(pNonce), blob_str(pNonce),
          blob_size(pSig), blob_str(pSig),
          blob_size(&hash), blob_str(&hash));

#endif
    blob_reset(&hash);
    blob_reset(&combined);
    if( rc!=0 && szPw!=40 ){
      /* If this server stores cleartext passwords and the password did not
      ** match, then perhaps the client is sending SHA1 passwords.  Try
      ** again with the SHA1 password.
      */
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
  zScript = xfer_push_code();
  if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
    pzUuidList = &zUuidList;
    pnUuidList = &nUuidList;
  }
  if( g.zLoginCard ){
    /* Login card received via HTTP header X-Fossil-Xfer-Login */

    blob_init(&xfer.line, g.zLoginCard, -1);
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
                                count(xfer.aToken));








    if( xfer.nToken==4
        && blob_eq(&xfer.aToken[0], "login") ){
      /*fprintf(stderr,"g.zLoginCard=%s nToken=%d\n", g.zLoginCard,
        xfer.nToken);*/
      goto handle_login_card;
    }
    fossil_free( g.zLoginCard );
    g.zLoginCard = 0;
  }
  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));

    /*   file HASH SIZE \n CONTENT







>
|


>
>
>
>
>
>
>
>


<
<


<
<







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
  zScript = xfer_push_code();
  if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
    pzUuidList = &zUuidList;
    pnUuidList = &nUuidList;
  }
  if( g.zLoginCard ){
    /* Login card received via HTTP header X-Fossil-Xfer-Login */
    blob_zero(&xfer.line);
    blob_append(&xfer.line, g.zLoginCard, -1);
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
                                count(xfer.aToken));
#if 0
    fprintf(stderr,"%s:%d: g.zLoginCard=[%s]\nnToken=%d tok[0]=%s line=%s\n",
            __FILE__, __LINE__, g.zLoginCard,
            xfer.nToken, xfer.nToken ? blob_str(&xfer.aToken[0]) : "<NULL>",
            blob_str(&xfer.line));
#endif
    fossil_free( g.zLoginCard );
    g.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));

    /*   file HASH SIZE \n CONTENT
1582
1583
1584
1585
1586
1587
1588





1589
1590
1591
1592
1593
1594
1595
        g.perm.Read = g.perm.Write = g.perm.Private = g.perm.Admin = 1;
      }else if( nLogin > 1 ){
        cgi_reset_content();
        @ error multiple\slogin\cards
        nErr++;
        break;
      }else{





        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
          break;







>
>
>
>
>







1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
        g.perm.Read = g.perm.Write = g.perm.Private = g.perm.Admin = 1;
      }else if( nLogin > 1 ){
        cgi_reset_content();
        @ error multiple\slogin\cards
        nErr++;
        break;
      }else{
#if 0
        fprintf(stderr, "handle_login_card: aToken[2]=[%.*s]\n",
                blob_size(&xfer.aToken[2]),
                blob_str(&xfer.aToken[2]));
#endif
        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
          break;