http: use credential API to handle proxy authentication

Currently, the only way to pass proxy credentials to curl is by including them in the proxy URL. Usually, this means they will end up on disk unencrypted, one way or another (by inclusion in ~/.gitconfig, shell profile or history). Since proxy authentication often uses a domain user, credentials can be security sensitive; therefore, a safer way of passing credentials is desirable. If the configured proxy contains a username but not a password, query the credential API for one. Also, make sure we approve/reject proxy credentials properly. For consistency reasons, add parsing of http_proxy/https_proxy/all_proxy environment variables, which would otherwise be evaluated as a fallback by curl. Without this, we would have different semantics for git configuration and environment variables. Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Elia Pinto <gitter.spiros@gmail.com> Signed-off-by: Knut Franke <k.franke@science-computing.de> Signed-off-by: Elia Pinto <gitter.spiros@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Knut Franke committed Jan 26, 2016 at 13:02 UTC 372370f1675c2b935fb703665358dd5567641107
3 files changed +85 -3
Documentation/config.txt
+7 -3
@@ -1596,9 +1596,13 @@ help.htmlPath::
1596
1597 http.proxy::
1598 Override the HTTP proxy, normally configured using the 'http_proxy',
1599 - 'https_proxy', and 'all_proxy' environment variables (see
1600 - `curl(1)`). This can be overridden on a per-remote basis; see
1601 - remote.<name>.proxy
1599 + 'https_proxy', and 'all_proxy' environment variables (see `curl(1)`). In
1600 + addition to the syntax understood by curl, it is possible to specify a
1601 + proxy string with a user name but no password, in which case git will
1602 + attempt to acquire one in the same way it does for other credentials. See
1603 + linkgit:gitcredentials[7] for more information. The syntax thus is
1604 + '[protocol://][user[:password]@]proxyhost[:port]'. This can be overridden
1605 + on a per-remote basis; see remote.<name>.proxy
1606
1607 http.proxyAuthMethod::
1608 Set the method with which to authenticate against the HTTP proxy. This
http.c
+77
@@ -80,6 +80,8 @@ static struct {
80 * here, too
81 */
82 };
83 +static struct credential proxy_auth = CREDENTIAL_INIT;
84 +static const char *curl_proxyuserpwd;
85 static const char *curl_cookie_file;
86 static int curl_save_cookies;
87 struct credential http_auth = CREDENTIAL_INIT;
@@ -177,6 +179,9 @@ static void finish_active_slot(struct active_request_slot *slot)
179 #else
180 slot->results->auth_avail = 0;
181 #endif
182 +
183 + curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE,
184 + &slot->results->http_connectcode);
185 }
186
187 /* Run callback if appropriate */
@@ -334,8 +339,32 @@ static void var_override(const char **var, char *value)
339 }
340 }
341
342 +static void set_proxyauth_name_password(CURL *result)
343 +{
344 +#if LIBCURL_VERSION_NUM >= 0x071301
345 + curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
346 + proxy_auth.username);
347 + curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
348 + proxy_auth.password);
349 +#else
350 + struct strbuf s = STRBUF_INIT;
351 +
352 + strbuf_addstr_urlencode(&s, proxy_auth.username, 1);
353 + strbuf_addch(&s, ':');
354 + strbuf_addstr_urlencode(&s, proxy_auth.password, 1);
355 + curl_proxyuserpwd = strbuf_detach(&s, NULL);
356 + curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd);
357 +#endif
358 +}
359 +
360 static void init_curl_proxy_auth(CURL *result)
361 {
362 + if (proxy_auth.username) {
363 + if (!proxy_auth.password)
364 + credential_fill(&proxy_auth);
365 + set_proxyauth_name_password(result);
366 + }
367 +
368 var_override(&http_proxy_authmethod, getenv("GIT_HTTP_PROXY_AUTHMETHOD"));
369
370 #if LIBCURL_VERSION_NUM >= 0x070a07 /* CURLOPT_PROXYAUTH and CURLAUTH_ANY */
@@ -517,6 +546,31 @@ static CURL *get_curl_handle(void)
546 curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY);
547 #endif
548
549 + /*
550 + * CURL also examines these variables as a fallback; but we need to query
551 + * them here in order to decide whether to prompt for missing password (cf.
552 + * init_curl_proxy_auth()).
553 + *
554 + * Unlike many other common environment variables, these are historically
555 + * lowercase only. It appears that CURL did not know this and implemented
556 + * only uppercase variants, which was later corrected to take both - with
557 + * the exception of http_proxy, which is lowercase only also in CURL. As
558 + * the lowercase versions are the historical quasi-standard, they take
559 + * precedence here, as in CURL.
560 + */
561 + if (!curl_http_proxy) {
562 + if (!strcmp(http_auth.protocol, "https")) {
563 + var_override(&curl_http_proxy, getenv("HTTPS_PROXY"));
564 + var_override(&curl_http_proxy, getenv("https_proxy"));
565 + } else {
566 + var_override(&curl_http_proxy, getenv("http_proxy"));
567 + }
568 + if (!curl_http_proxy) {
569 + var_override(&curl_http_proxy, getenv("ALL_PROXY"));
570 + var_override(&curl_http_proxy, getenv("all_proxy"));
571 + }
572 + }
573 +
574 if (curl_http_proxy) {
575 curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
576 #if LIBCURL_VERSION_NUM >= 0x071800
@@ -530,6 +584,16 @@ static CURL *get_curl_handle(void)
584 curl_easy_setopt(result,
585 CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
586 #endif
587 + if (strstr(curl_http_proxy, "://"))
588 + credential_from_url(&proxy_auth, curl_http_proxy);
589 + else {
590 + struct strbuf url = STRBUF_INIT;
591 + strbuf_addf(&url, "http://%s", curl_http_proxy);
592 + credential_from_url(&proxy_auth, url.buf);
593 + strbuf_release(&url);
594 + }
595 +
596 + curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
597 }
598 init_curl_proxy_auth(result);
599
@@ -673,6 +737,15 @@ void http_cleanup(void)
737 curl_http_proxy = NULL;
738 }
739
740 + if (proxy_auth.password) {
741 + memset(proxy_auth.password, 0, strlen(proxy_auth.password));
742 + free(proxy_auth.password);
743 + proxy_auth.password = NULL;
744 + }
745 +
746 + free((void *)curl_proxyuserpwd);
747 + curl_proxyuserpwd = NULL;
748 +
749 free((void *)http_proxy_authmethod);
750 http_proxy_authmethod = NULL;
751
@@ -1005,6 +1078,8 @@ static int handle_curl_result(struct slot_results *results)
1078
1079 if (results->curl_result == CURLE_OK) {
1080 credential_approve(&http_auth);
1081 + if (proxy_auth.password)
1082 + credential_approve(&proxy_auth);
1083 return HTTP_OK;
1084 } else if (missing_target(results))
1085 return HTTP_MISSING_TARGET;
@@ -1019,6 +1094,8 @@ static int handle_curl_result(struct slot_results *results)
1094 return HTTP_REAUTH;
1095 }
1096 } else {
1097 + if (results->http_connectcode == 407)
1098 + credential_reject(&proxy_auth);
1099 #if LIBCURL_VERSION_NUM >= 0x070c00
1100 if (!curl_errorstr[0])
1101 strlcpy(curl_errorstr,
http.h
+1
@@ -54,6 +54,7 @@ struct slot_results {
54 CURLcode curl_result;
55 long http_code;
56 long auth_avail;
57 + long http_connectcode;
58 };
59
60 struct active_request_slot {