Index: src/clone.c ================================================================== --- src/clone.c +++ src/clone.c @@ -109,22 +109,25 @@ ** --admin-user|-A USERNAME Make USERNAME the administrator ** --once Don't save url. ** --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 +** --httpauth|-B 'user:pass' Add HTTP Basic Authorization to requests ** ** See also: init */ void clone_cmd(void){ char *zPassword; const char *zDefaultUser; /* Optional name of the default user */ + const char *zHttpAuth; /* HTTP Authorization user:pass information */ int nErr = 0; int bPrivate = 0; /* Also clone private branches */ int urlFlags = URL_PROMPT_PW | URL_REMEMBER; if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE; if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER; + zHttpAuth = find_option("httpauth","B",1); zDefaultUser = find_option("admin-user","A",1); clone_ssh_find_options(); url_proxy_options(); if( g.argc < 4 ){ usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); @@ -159,10 +162,11 @@ db_initial_setup(0, 0, zDefaultUser, 0); user_select(); db_set("content-schema", CONTENT_SCHEMA, 0); db_set("aux-schema", AUX_SCHEMA, 0); db_set("rebuilt", get_version(), 0); + remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, g.argv[2]); url_remember(); if( g.zSSLIdentity!=0 ){ /* If the --ssl-identity option was specified, store it as a setting */ Blob fn; blob_zero(&fn); @@ -195,10 +199,53 @@ 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); } + +/* +** If user chooses to use HTTP Authentication over unencrypted HTTP, +** remember decision. Otherwise, if the URL is being changed and no preference +** has been indicated, err on the safe side and revert the decision. +** Set the global preference if the URL is not being changed. +*/ +void remember_or_get_http_auth(const char *zHttpAuth, int fRemember, const char *zUrl){ + char *zKey = mprintf("http-auth:%s", g.urlCanonical); + if( zHttpAuth && zHttpAuth[0] ){ + g.zHttpAuth = mprintf("%s", zHttpAuth); + } + if( fRemember ){ + if( g.zHttpAuth && g.zHttpAuth[0] ){ + set_httpauth(g.zHttpAuth); + }else if( zUrl && zUrl[0] ){ + db_unset(zKey, 0); + }else{ + g.zHttpAuth = get_httpauth(); + } + }else if( g.zHttpAuth==0 && zUrl==0 ){ + g.zHttpAuth = get_httpauth(); + } + free(zKey); +} + +/* +** Get the HTTP Authorization preference from db. +*/ +char *get_httpauth(void){ + char *zKey = mprintf("http-auth:%s", g.urlCanonical); + return unobscure(db_get(zKey, 0)); + free(zKey); +} + +/* +** Set the HTTP Authorization preference in db. +*/ +void set_httpauth(const char *zHttpAuth){ + char *zKey = mprintf("http-auth:%s", g.urlCanonical); + db_set(zKey, obscure(zHttpAuth), 0); + free(zKey); +} /* ** Look for SSH clone command line options and setup in globals. */ void clone_ssh_find_options(void){ Index: src/http.c ================================================================== --- src/http.c +++ src/http.c @@ -19,10 +19,26 @@ */ #include "config.h" #include "http.h" #include +#ifdef _WIN32 +#include +#ifndef isatty +#define isatty(d) _isatty(d) +#endif +#ifndef fileno +#define fileno(s) _fileno(s) +#endif +#endif + +/* Maximum number of HTTP Authorization attempts */ +#define MAX_HTTP_AUTH 2 + +/* Keep track of HTTP Basic Authorization failures */ +static int fSeenHttpAuth = 0; + /* ** Construct the "login" card with the client credentials. ** ** login LOGIN NONCE SIGNATURE ** @@ -62,16 +78,10 @@ /* Password failure while doing a sync from the command-line interface */ url_prompt_for_password(); zPw = g.urlPasswd; } - /* If the first character of the password is "#", then that character is - ** not really part of the password - it is an indicator that we should - ** use Basic Authentication. So skip that character. - */ - if( zPw && zPw[0]=='#' ) zPw++; - /* The login card wants the SHA1 hash of the password, so convert the ** password to its SHA1 hash it it isn't already a SHA1 hash. */ /* fossil_print("\nzPw=[%s]\n", zPw); // TESTING ONLY */ if( zPw && zPw[0] ) zPw = sha1_shared_secret(zPw, zLogin, 0); @@ -102,16 +112,15 @@ } blob_appendf(pHdr, "POST %s%sxfer/xfer HTTP/1.0\r\n", g.urlPath, zSep); if( g.urlProxyAuth ){ blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.urlProxyAuth); } - if( g.urlPasswd && g.urlUser && g.urlPasswd[0]=='#' ){ - char *zCredentials = mprintf("%s:%s", g.urlUser, &g.urlPasswd[1]); + if( g.zHttpAuth && g.zHttpAuth[0] ){ + const char *zCredentials = g.zHttpAuth; char *zEncoded = encode64(zCredentials, -1); 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: %s\r\n", get_user_agent()); if( g.urlIsSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n"); if( g.fHttpTrace ){ @@ -119,10 +128,71 @@ }else{ blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n"); } blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload)); } + +/* +** Use Fossil credentials for HTTP Basic Authorization prompt +*/ +static int use_fossil_creds_for_httpauth_prompt(void){ + Blob x; + char c; + prompt_user("Use Fossil username and password (y/N)? ", &x); + c = blob_str(&x)[0]; + blob_reset(&x); + return ( c=='y' || c=='Y' ); +} + +/* +** Prompt to save HTTP Basic Authorization information +*/ +static int save_httpauth_prompt(void){ + Blob x; + char c; + if( (g.urlFlags & URL_REMEMBER)==0 ) return; + prompt_user("Remember Basic Authorization credentials (Y/n)? ", &x); + c = blob_str(&x)[0]; + blob_reset(&x); + return ( c!='n' && c!='N' ); +} + +/* +** Get the HTTP Basic Authorization credentials from the user +** when 401 is received. +*/ +char *prompt_for_httpauth_creds(void){ + Blob x; + char *zUser; + char *zPw; + char *zPrompt; + char *zHttpAuth = 0; + if( !isatty(fileno(stdin)) ) return 0; + zPrompt = mprintf("\n%s authorization required by\n%s\n", + g.urlIsHttps==1 ? "Encrypted HTTPS" : "Unencrypted HTTP", g.urlCanonical); + fossil_print(zPrompt); + free(zPrompt); + if ( g.urlUser && g.urlPasswd && use_fossil_creds_for_httpauth_prompt() ){ + zHttpAuth = mprintf("%s:%s", g.urlUser, g.urlPasswd); + }else{ + prompt_user("Basic Authorization user: ", &x); + zUser = mprintf("%b", &x); + zPrompt = mprintf("HTTP password for %b: ", &x); + blob_reset(&x); + prompt_for_password(zPrompt, &x, 1); + zPw = mprintf("%b", &x); + zHttpAuth = mprintf("%s:%s", zUser, zPw); + free(zUser); + free(zPw); + free(zPrompt); + blob_reset(&x); + } + if( save_httpauth_prompt() ){ + set_httpauth(zHttpAuth); + } + return zHttpAuth; +} /* ** Sign the content in pSend, compress it, and send it to the server ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply ** in pRecv. pRecv is assumed to be uninitialized when @@ -205,10 +275,20 @@ iLength = -1; while( (zLine = transport_receive_line(GLOBAL_URL()))!=0 && zLine[0]!=0 ){ /* printf("[%s]\n", zLine); fflush(stdout); */ if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){ if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err; + if( rc==401 ){ + if( fSeenHttpAuth++ < MAX_HTTP_AUTH ){ + if( g.zHttpAuth ){ + if( g.zHttpAuth ) free(g.zHttpAuth); + } + g.zHttpAuth = prompt_for_httpauth_creds(); + transport_close(GLOBAL_URL()); + return http_exchange(pSend, pReply, useLogin, maxRedirect); + } + } 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]); @@ -254,10 +334,13 @@ j -= 4; zLine[j] = 0; } fossil_print("redirect to %s\n", &zLine[i]); url_parse(&zLine[i], 0); + fSeenHttpAuth = 0; + if( g.zHttpAuth ) free(g.zHttpAuth); + g.zHttpAuth = get_httpauth(); transport_close(GLOBAL_URL()); return http_exchange(pSend, pReply, useLogin, maxRedirect); }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){ if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){ isCompressed = 0; Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -140,10 +140,11 @@ 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 */ + char *zHttpAuth; /* HTTP Authorization user:pass information */ 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 */ @@ -651,10 +652,11 @@ 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.zHttpAuth = 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; if( find_option("localtime",0,0) ) g.fTimeFormat = 2; Index: src/sync.c ================================================================== --- src/sync.c +++ src/sync.c @@ -54,10 +54,11 @@ 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(); } + g.zHttpAuth = get_httpauth(); url_remember(); #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. @@ -83,17 +84,19 @@ ** of a server to sync against. If no argument is given, use the ** most recently synced URL. Remember the current URL for next time. */ static void process_sync_args(unsigned *pConfigFlags, unsigned *pSyncFlags){ const char *zUrl = 0; + const char *zHttpAuth = 0; unsigned configSync = 0; unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW; int urlOptional = 0; if( find_option("autourl",0,0)!=0 ){ urlOptional = 1; urlFlags = 0; } + zHttpAuth = find_option("httpauth","B",1); if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER; if( find_option("private",0,0)!=0 ){ *pSyncFlags |= SYNC_PRIVATE; } if( find_option("verbose","v",0)!=0 ){ @@ -116,10 +119,11 @@ } if( urlFlags & URL_REMEMBER ){ clone_ssh_db_set_options(); } url_parse(zUrl, urlFlags); + remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, zUrl); url_remember(); if( g.urlProtocol==0 ){ if( urlOptional ) fossil_exit(0); usage("URL"); } @@ -263,10 +267,11 @@ usage("remote-url ?URL|off?"); } if( g.argc==3 ){ db_unset("last-sync-url", 0); db_unset("last-sync-pw", 0); + db_unset("http-auth", 0); if( is_false(g.argv[2]) ) return; url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW|URL_ASK_REMEMBER_PW); } url_remember(); zUrl = db_get("last-sync-url", 0); Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -578,11 +578,12 @@ 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 ){ + if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0 + && db_get_boolean("remote_user_ok",0) ){ return 0; /* Accept Basic Authorization */ } db_prepare(&q, "SELECT pw, cap, uid FROM user" " WHERE login=%Q"