]> granicus.if.org Git - git/commitdiff
http: use credential API to handle proxy authentication
authorKnut Franke <k.franke@science-computing.de>
Tue, 26 Jan 2016 13:02:48 +0000 (13:02 +0000)
committerJunio C Hamano <gitster@pobox.com>
Tue, 26 Jan 2016 18:53:25 +0000 (10:53 -0800)
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>
Documentation/config.txt
http.c
http.h

index a6c3d0fa50e53c328f021002a6b9b779389d51ab..8b969497ba3f92385211d2aa599947578fdd3c72 100644 (file)
@@ -1596,9 +1596,13 @@ help.htmlPath::
 
 http.proxy::
        Override the HTTP proxy, normally configured using the 'http_proxy',
-       'https_proxy', and 'all_proxy' environment variables (see
-       `curl(1)`).  This can be overridden on a per-remote basis; see
-       remote.<name>.proxy
+       'https_proxy', and 'all_proxy' environment variables (see `curl(1)`). In
+       addition to the syntax understood by curl, it is possible to specify a
+       proxy string with a user name but no password, in which case git will
+       attempt to acquire one in the same way it does for other credentials. See
+       linkgit:gitcredentials[7] for more information. The syntax thus is
+       '[protocol://][user[:password]@]proxyhost[:port]'. This can be overridden
+       on a per-remote basis; see remote.<name>.proxy
 
 http.proxyAuthMethod::
        Set the method with which to authenticate against the HTTP proxy. This
diff --git a/http.c b/http.c
index f46bfc43f9e5e8073563be853744262a1bb4c5d6..dfc53c1e2554e76126459d6cb1f098facac28593 100644 (file)
--- a/http.c
+++ b/http.c
@@ -80,6 +80,8 @@ static struct {
         * here, too
         */
 };
+static struct credential proxy_auth = CREDENTIAL_INIT;
+static const char *curl_proxyuserpwd;
 static const char *curl_cookie_file;
 static int curl_save_cookies;
 struct credential http_auth = CREDENTIAL_INIT;
@@ -177,6 +179,9 @@ static void finish_active_slot(struct active_request_slot *slot)
 #else
                slot->results->auth_avail = 0;
 #endif
+
+               curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE,
+                       &slot->results->http_connectcode);
        }
 
        /* Run callback if appropriate */
@@ -334,8 +339,32 @@ static void var_override(const char **var, char *value)
        }
 }
 
+static void set_proxyauth_name_password(CURL *result)
+{
+#if LIBCURL_VERSION_NUM >= 0x071301
+               curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
+                       proxy_auth.username);
+               curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
+                       proxy_auth.password);
+#else
+               struct strbuf s = STRBUF_INIT;
+
+               strbuf_addstr_urlencode(&s, proxy_auth.username, 1);
+               strbuf_addch(&s, ':');
+               strbuf_addstr_urlencode(&s, proxy_auth.password, 1);
+               curl_proxyuserpwd = strbuf_detach(&s, NULL);
+               curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd);
+#endif
+}
+
 static void init_curl_proxy_auth(CURL *result)
 {
+       if (proxy_auth.username) {
+               if (!proxy_auth.password)
+                       credential_fill(&proxy_auth);
+               set_proxyauth_name_password(result);
+       }
+
        var_override(&http_proxy_authmethod, getenv("GIT_HTTP_PROXY_AUTHMETHOD"));
 
 #if LIBCURL_VERSION_NUM >= 0x070a07 /* CURLOPT_PROXYAUTH and CURLAUTH_ANY */
@@ -517,6 +546,31 @@ static CURL *get_curl_handle(void)
                curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY);
 #endif
 
+       /*
+        * CURL also examines these variables as a fallback; but we need to query
+        * them here in order to decide whether to prompt for missing password (cf.
+        * init_curl_proxy_auth()).
+        *
+        * Unlike many other common environment variables, these are historically
+        * lowercase only. It appears that CURL did not know this and implemented
+        * only uppercase variants, which was later corrected to take both - with
+        * the exception of http_proxy, which is lowercase only also in CURL. As
+        * the lowercase versions are the historical quasi-standard, they take
+        * precedence here, as in CURL.
+        */
+       if (!curl_http_proxy) {
+               if (!strcmp(http_auth.protocol, "https")) {
+                       var_override(&curl_http_proxy, getenv("HTTPS_PROXY"));
+                       var_override(&curl_http_proxy, getenv("https_proxy"));
+               } else {
+                       var_override(&curl_http_proxy, getenv("http_proxy"));
+               }
+               if (!curl_http_proxy) {
+                       var_override(&curl_http_proxy, getenv("ALL_PROXY"));
+                       var_override(&curl_http_proxy, getenv("all_proxy"));
+               }
+       }
+
        if (curl_http_proxy) {
                curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
 #if LIBCURL_VERSION_NUM >= 0x071800
@@ -530,6 +584,16 @@ static CURL *get_curl_handle(void)
                        curl_easy_setopt(result,
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
 #endif
+               if (strstr(curl_http_proxy, "://"))
+                       credential_from_url(&proxy_auth, curl_http_proxy);
+               else {
+                       struct strbuf url = STRBUF_INIT;
+                       strbuf_addf(&url, "http://%s", curl_http_proxy);
+                       credential_from_url(&proxy_auth, url.buf);
+                       strbuf_release(&url);
+               }
+
+               curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
        }
        init_curl_proxy_auth(result);
 
@@ -673,6 +737,15 @@ void http_cleanup(void)
                curl_http_proxy = NULL;
        }
 
+       if (proxy_auth.password) {
+               memset(proxy_auth.password, 0, strlen(proxy_auth.password));
+               free(proxy_auth.password);
+               proxy_auth.password = NULL;
+       }
+
+       free((void *)curl_proxyuserpwd);
+       curl_proxyuserpwd = NULL;
+
        free((void *)http_proxy_authmethod);
        http_proxy_authmethod = NULL;
 
@@ -1005,6 +1078,8 @@ static int handle_curl_result(struct slot_results *results)
 
        if (results->curl_result == CURLE_OK) {
                credential_approve(&http_auth);
+               if (proxy_auth.password)
+                       credential_approve(&proxy_auth);
                return HTTP_OK;
        } else if (missing_target(results))
                return HTTP_MISSING_TARGET;
@@ -1019,6 +1094,8 @@ static int handle_curl_result(struct slot_results *results)
                        return HTTP_REAUTH;
                }
        } else {
+               if (results->http_connectcode == 407)
+                       credential_reject(&proxy_auth);
 #if LIBCURL_VERSION_NUM >= 0x070c00
                if (!curl_errorstr[0])
                        strlcpy(curl_errorstr,
diff --git a/http.h b/http.h
index 4f97b60b5c8abdf5ab0610382a6d6fa289df2605..f83cfa686823728587b2a803c3e84a8cd4669220 100644 (file)
--- a/http.h
+++ b/http.h
@@ -54,6 +54,7 @@ struct slot_results {
        CURLcode curl_result;
        long http_code;
        long auth_avail;
+       long http_connectcode;
 };
 
 struct active_request_slot {