Merge branch 'kf/http-proxy-auth-methods'

New http.proxyAuthMethod configuration variable can be used to
specify what authentication method to use, as a way to work around
proxies that do not give error response expected by libcurl when
CURLAUTH_ANY is used.  Also, the codepath for proxy authentication
has been taught to use credential API to store the authentication
material in user's keyrings.

* kf/http-proxy-auth-methods:
  http: use credential API to handle proxy authentication
  http: allow selection of proxy authentication method
maint
Junio C Hamano 2016-02-03 14:16:08 -08:00
commit 30f302f7e7
6 changed files with 181 additions and 6 deletions

View File

@ -1610,9 +1610,34 @@ help.htmlPath::


http.proxy:: http.proxy::
Override the HTTP proxy, normally configured using the 'http_proxy', Override the HTTP proxy, normally configured using the 'http_proxy',
'https_proxy', and 'all_proxy' environment variables (see 'https_proxy', and 'all_proxy' environment variables (see `curl(1)`). In
`curl(1)`). This can be overridden on a per-remote basis; see addition to the syntax understood by curl, it is possible to specify a
remote.<name>.proxy 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
only takes effect if the configured proxy string contains a user name part
(i.e. is of the form 'user@host' or 'user@host:port'). This can be
overridden on a per-remote basis; see `remote.<name>.proxyAuthMethod`.
Both can be overridden by the 'GIT_HTTP_PROXY_AUTHMETHOD' environment
variable. Possible values are:
+
--
* `anyauth` - Automatically pick a suitable authentication method. It is
assumed that the proxy answers an unauthenticated request with a 407
status code and one or more Proxy-authenticate headers with supported
authentication methods. This is the default.
* `basic` - HTTP Basic authentication
* `digest` - HTTP Digest authentication; this prevents the password from being
transmitted to the proxy in clear text
* `negotiate` - GSS-Negotiate authentication (compare the --negotiate option
of `curl(1)`)
* `ntlm` - NTLM authentication (compare the --ntlm option of `curl(1)`)
--


http.cookieFile:: http.cookieFile::
File containing previously stored cookie lines which should be used File containing previously stored cookie lines which should be used
@ -2423,6 +2448,11 @@ remote.<name>.proxy::
the proxy to use for that remote. Set to the empty string to the proxy to use for that remote. Set to the empty string to
disable proxying for that remote. disable proxying for that remote.


remote.<name>.proxyAuthMethod::
For remotes that require curl (http, https and ftp), the method to use for
authenticating against the proxy in use (probably set in
`remote.<name>.proxy`). See `http.proxyAuthMethod`.

remote.<name>.fetch:: remote.<name>.fetch::
The default set of "refspec" for linkgit:git-fetch[1]. See The default set of "refspec" for linkgit:git-fetch[1]. See
linkgit:git-fetch[1]. linkgit:git-fetch[1].

View File

@ -51,6 +51,10 @@ struct remote


The proxy to use for curl (http, https, ftp, etc.) URLs. The proxy to use for curl (http, https, ftp, etc.) URLs.


`http_proxy_authmethod`::

The method used for authenticating against `http_proxy`.

struct remotes can be found by name with remote_get(), and iterated struct remotes can be found by name with remote_get(), and iterated
through with for_each_remote(). remote_get(NULL) will return the through with for_each_remote(). remote_get(NULL) will return the
default remote, given the current branch and configuration. default remote, given the current branch and configuration.

142
http.c
View File

@ -62,6 +62,26 @@ static long curl_low_speed_limit = -1;
static long curl_low_speed_time = -1; static long curl_low_speed_time = -1;
static int curl_ftp_no_epsv; static int curl_ftp_no_epsv;
static const char *curl_http_proxy; static const char *curl_http_proxy;
static const char *http_proxy_authmethod;
static struct {
const char *name;
long curlauth_param;
} proxy_authmethods[] = {
{ "basic", CURLAUTH_BASIC },
{ "digest", CURLAUTH_DIGEST },
{ "negotiate", CURLAUTH_GSSNEGOTIATE },
{ "ntlm", CURLAUTH_NTLM },
#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
{ "anyauth", CURLAUTH_ANY },
#endif
/*
* CURLAUTH_DIGEST_IE has no corresponding command-line option in
* curl(1) and is not included in CURLAUTH_ANY, so we leave it out
* here, too
*/
};
static struct credential proxy_auth = CREDENTIAL_INIT;
static const char *curl_proxyuserpwd;
static const char *curl_cookie_file; static const char *curl_cookie_file;
static int curl_save_cookies; static int curl_save_cookies;
struct credential http_auth = CREDENTIAL_INIT; struct credential http_auth = CREDENTIAL_INIT;
@ -159,6 +179,9 @@ static void finish_active_slot(struct active_request_slot *slot)
#else #else
slot->results->auth_avail = 0; slot->results->auth_avail = 0;
#endif #endif

curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE,
&slot->results->http_connectcode);
} }


/* Run callback if appropriate */ /* Run callback if appropriate */
@ -256,6 +279,9 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.proxy", var)) if (!strcmp("http.proxy", var))
return git_config_string(&curl_http_proxy, var, value); return git_config_string(&curl_http_proxy, var, value);


if (!strcmp("http.proxyauthmethod", var))
return git_config_string(&http_proxy_authmethod, var, value);

if (!strcmp("http.cookiefile", var)) if (!strcmp("http.cookiefile", var))
return git_config_string(&curl_cookie_file, var, value); return git_config_string(&curl_cookie_file, var, value);
if (!strcmp("http.savecookies", var)) { if (!strcmp("http.savecookies", var)) {
@ -304,6 +330,64 @@ static void init_curl_http_auth(CURL *result)
#endif #endif
} }


/* *var must be free-able */
static void var_override(const char **var, char *value)
{
if (value) {
free((void *)*var);
*var = xstrdup(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 */
if (http_proxy_authmethod) {
int i;
for (i = 0; i < ARRAY_SIZE(proxy_authmethods); i++) {
if (!strcmp(http_proxy_authmethod, proxy_authmethods[i].name)) {
curl_easy_setopt(result, CURLOPT_PROXYAUTH,
proxy_authmethods[i].curlauth_param);
break;
}
}
if (i == ARRAY_SIZE(proxy_authmethods)) {
warning("unsupported proxy authentication method %s: using anyauth",
http_proxy_authmethod);
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
}
}
else
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
#endif
}

static int has_cert_password(void) static int has_cert_password(void)
{ {
if (ssl_cert == NULL || ssl_cert_password_required != 1) if (ssl_cert == NULL || ssl_cert_password_required != 1)
@ -462,6 +546,31 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY); curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY);
#endif #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) { if (curl_http_proxy) {
curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
#if LIBCURL_VERSION_NUM >= 0x071800 #if LIBCURL_VERSION_NUM >= 0x071800
@ -475,10 +584,18 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
#endif #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);
} }
#if LIBCURL_VERSION_NUM >= 0x070a07 init_curl_proxy_auth(result);
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
#endif


set_curl_keepalive(result); set_curl_keepalive(result);


@ -519,6 +636,9 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
if (remote && remote->http_proxy) if (remote && remote->http_proxy)
curl_http_proxy = xstrdup(remote->http_proxy); curl_http_proxy = xstrdup(remote->http_proxy);


if (remote)
var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);

pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");


@ -617,6 +737,18 @@ void http_cleanup(void)
curl_http_proxy = NULL; 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;

if (cert_auth.password != NULL) { if (cert_auth.password != NULL) {
memset(cert_auth.password, 0, strlen(cert_auth.password)); memset(cert_auth.password, 0, strlen(cert_auth.password));
free(cert_auth.password); free(cert_auth.password);
@ -946,6 +1078,8 @@ static int handle_curl_result(struct slot_results *results)


if (results->curl_result == CURLE_OK) { if (results->curl_result == CURLE_OK) {
credential_approve(&http_auth); credential_approve(&http_auth);
if (proxy_auth.password)
credential_approve(&proxy_auth);
return HTTP_OK; return HTTP_OK;
} else if (missing_target(results)) } else if (missing_target(results))
return HTTP_MISSING_TARGET; return HTTP_MISSING_TARGET;
@ -960,6 +1094,8 @@ static int handle_curl_result(struct slot_results *results)
return HTTP_REAUTH; return HTTP_REAUTH;
} }
} else { } else {
if (results->http_connectcode == 407)
credential_reject(&proxy_auth);
#if LIBCURL_VERSION_NUM >= 0x070c00 #if LIBCURL_VERSION_NUM >= 0x070c00
if (!curl_errorstr[0]) if (!curl_errorstr[0])
strlcpy(curl_errorstr, strlcpy(curl_errorstr,

1
http.h
View File

@ -54,6 +54,7 @@ struct slot_results {
CURLcode curl_result; CURLcode curl_result;
long http_code; long http_code;
long auth_avail; long auth_avail;
long http_connectcode;
}; };


struct active_request_slot { struct active_request_slot {

View File

@ -428,6 +428,9 @@ static int handle_config(const char *key, const char *value, void *cb)
} else if (!strcmp(subkey, ".proxy")) { } else if (!strcmp(subkey, ".proxy")) {
return git_config_string((const char **)&remote->http_proxy, return git_config_string((const char **)&remote->http_proxy,
key, value); key, value);
} else if (!strcmp(subkey, ".proxyauthmethod")) {
return git_config_string((const char **)&remote->http_proxy_authmethod,
key, value);
} else if (!strcmp(subkey, ".vcs")) { } else if (!strcmp(subkey, ".vcs")) {
return git_config_string(&remote->foreign_vcs, key, value); return git_config_string(&remote->foreign_vcs, key, value);
} }

View File

@ -54,6 +54,7 @@ struct remote {
* for curl remotes only * for curl remotes only
*/ */
char *http_proxy; char *http_proxy;
char *http_proxy_authmethod;
}; };


struct remote *remote_get(const char *name); struct remote *remote_get(const char *name);