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: |
ff942066d5bb2ce61ee7981895f3b6e0 |
| 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
Changes to VERSION.
|
| | | 1 | 2.27.1 |
Changes to src/http.c.
| ︙ | ︙ | |||
117 118 119 120 121 122 123 |
}
fossil_free(g.url.passwd);
g.url.passwd = fossil_strdup(zPw);
}
blob_append(&pw, zPw, -1);
sha1sum_blob(&pw, &sig);
| | > | 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 |
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 */
| > > > > > > > > > > > > > > > > > > | > | 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 |
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();
| | | 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 |
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
| | | 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 |
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 */
| > | > > > > > > > > < < < < | 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;
|
| ︙ | ︙ |