From 091361f569dd86e8550c04afb193bfb516a21a74 Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Tue, 6 Nov 2012 08:52:03 -0500 Subject: [PATCH 1/3] Basic authentication for http and winhttp --- include/git2/remote.h | 17 +- include/git2/transport.h | 49 ++++++ src/remote.c | 11 +- src/remote.h | 1 + src/transports/cred.c | 57 +++++++ src/transports/http.c | 351 +++++++++++++++++++++++++++++---------- src/transports/local.c | 8 +- src/transports/smart.c | 8 +- src/transports/smart.h | 1 + src/transports/winhttp.c | 164 +++++++++++++++++- 10 files changed, 570 insertions(+), 97 deletions(-) create mode 100644 src/transports/cred.c diff --git a/include/git2/remote.h b/include/git2/remote.h index 1827e12b0..33b7dfdec 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -286,6 +286,19 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha */ GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); +/** + * Set a credentials acquisition callback for this remote. If the remote is + * not available for anonymous access, then you must set this callback in order + * to provide credentials to the transport at the time of authentication + * failure so that retry can be performed. + * + * @param remote the remote to configure + * @param The credentials acquisition callback to use (defaults to NULL) + */ +GIT_EXTERN(void) git_remote_set_cred_acquire_cb( + git_remote *remote, + git_cred_acquire_cb cred_acquire_cb); + /** * Sets a custom transport for the remote. The caller can use this function * to bypass the automatic discovery of a transport by URL scheme (i.e. @@ -297,7 +310,9 @@ GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); * @param remote the remote to configure * @param transport the transport object for the remote to use */ -GIT_EXTERN(int) git_remote_set_transport(git_remote *remote, git_transport *transport); +GIT_EXTERN(int) git_remote_set_transport( + git_remote *remote, + git_transport *transport); /** * Argument to the completion callback which tells it which operation diff --git a/include/git2/transport.h b/include/git2/transport.h index fd0d56fbe..b2bdaae71 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -20,6 +20,54 @@ GIT_BEGIN_DECL /* + *** Begin interface for credentials acquisition *** + */ + +typedef enum { + /* git_cred_userpass_plaintext */ + GIT_CREDTYPE_USERPASS_PLAINTEXT = 1, +} git_credtype_t; + +/* The base structure for all credential types */ +typedef struct git_cred { + git_credtype_t credtype; + void (*free)( + struct git_cred *cred); +} git_cred; + +/* A plaintext username and password */ +typedef struct git_cred_userpass_plaintext { + git_cred parent; + char *username; + char *password; +} git_cred_userpass_plaintext; + +/** + * Creates a new plain-text username and password credential object. + * + * @param cred The newly created credential object. + * @param username The username of the credential. + * @param password The password of the credential. + */ +GIT_EXTERN(int) git_cred_userpass_plaintext_new( + git_cred **cred, + const char *username, + const char *password); + +/** + * Signature of a function which acquires a credential object. + * + * @param cred The newly created credential object. + * @param url The resource for which we are demanding a credential. + * @param allowed_types A bitmask stating which cred types are OK to return. + */ +typedef int (*git_cred_acquire_cb)( + git_cred **cred, + const char *url, + int allowed_types); + +/* + *** End interface for credentials acquisition *** *** Begin base transport interface *** */ @@ -43,6 +91,7 @@ typedef struct git_transport { * direction. */ int (*connect)(struct git_transport *transport, const char *url, + git_cred_acquire_cb cred_acquire_cb, int direction, int flags); diff --git a/src/remote.c b/src/remote.c index a873a27b6..98660fe3b 100644 --- a/src/remote.c +++ b/src/remote.c @@ -492,7 +492,7 @@ int git_remote_connect(git_remote *remote, int direction) if (!remote->check_cert) flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; - if (t->connect(t, url, direction, flags) < 0) + if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0) goto on_error; remote->transport = t; @@ -809,6 +809,15 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback remote->callbacks.data); } +void git_remote_set_cred_acquire_cb( + git_remote *remote, + git_cred_acquire_cb cred_acquire_cb) +{ + assert(remote); + + remote->cred_acquire_cb = cred_acquire_cb; +} + int git_remote_set_transport(git_remote *remote, git_transport *transport) { assert(remote && transport); diff --git a/src/remote.h b/src/remote.h index b0df2d649..6a90bfcb4 100644 --- a/src/remote.h +++ b/src/remote.h @@ -22,6 +22,7 @@ struct git_remote { git_vector refs; struct git_refspec fetch; struct git_refspec push; + git_cred_acquire_cb cred_acquire_cb; git_transport *transport; git_repository *repo; git_remote_callbacks callbacks; diff --git a/src/transports/cred.c b/src/transports/cred.c new file mode 100644 index 000000000..55295372f --- /dev/null +++ b/src/transports/cred.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2.h" +#include "smart.h" + +static void plaintext_free(struct git_cred *cred) +{ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + int pass_len = strlen(c->password); + + git__free(c->username); + + /* Zero the memory which previously held the password */ + memset(c->password, 0x0, pass_len); + git__free(c->password); + + git__free(c); +} + +int git_cred_userpass_plaintext_new( + git_cred **cred, + const char *username, + const char *password) +{ + git_cred_userpass_plaintext *c; + + if (!cred) + return -1; + + c = (git_cred_userpass_plaintext *)git__malloc(sizeof(git_cred_userpass_plaintext)); + GITERR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; + c->parent.free = plaintext_free; + c->username = git__strdup(username); + + if (!c->username) { + git__free(c); + return -1; + } + + c->password = git__strdup(password); + + if (!c->password) { + git__free(c->username); + git__free(c); + return -1; + } + + *cred = &c->parent; + return 0; +} \ No newline at end of file diff --git a/src/transports/http.c b/src/transports/http.c index f33cad7ea..4b48779f9 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -10,6 +10,7 @@ #include "http_parser.h" #include "buffer.h" #include "netops.h" +#include "smart.h" static const char *prefix_http = "http://"; static const char *prefix_https = "https://"; @@ -18,15 +19,23 @@ static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-p static const char *upload_pack_service_url = "/git-upload-pack"; static const char *get_verb = "GET"; static const char *post_verb = "POST"; +static const char *basic_authtype = "Basic"; #define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) +#define PARSE_ERROR_GENERIC -1 +#define PARSE_ERROR_REPLAY -2 + enum last_cb { NONE, FIELD, VALUE }; +typedef enum { + GIT_HTTP_AUTH_BASIC = 1, +} http_authmechanism_t; + typedef struct { git_smart_subtransport_stream parent; const char *service; @@ -37,11 +46,13 @@ typedef struct { typedef struct { git_smart_subtransport parent; - git_transport *owner; + transport_smart *owner; gitno_socket socket; const char *path; char *host; - char *port; + char *port; + git_cred *cred; + http_authmechanism_t auth_mechanism; unsigned connected : 1, use_ssl : 1, no_check_cert : 1; @@ -50,14 +61,14 @@ typedef struct { http_parser parser; http_parser_settings settings; gitno_buffer parse_buffer; - git_buf parse_temp; + git_buf parse_header_name; + git_buf parse_header_value; char parse_buffer_data[2048]; char *content_type; + git_vector www_authenticate; enum last_cb last_cb; - int parse_error; - unsigned parse_finished : 1, - ct_found : 1, - ct_finished : 1; + int parse_error; + unsigned parse_finished : 1; } http_subtransport; typedef struct { @@ -70,10 +81,42 @@ typedef struct { size_t *bytes_read; } parser_context; -static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, - const char *service, const char *service_url, ssize_t content_length) +static int apply_basic_credential(git_buf *buf, git_cred *cred) { - if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + git_buf raw = GIT_BUF_INIT; + int error = -1; + + git_buf_printf(&raw, "%s:%s", c->username, c->password); + + if (git_buf_oom(&raw) || + git_buf_puts(buf, "Authorization: Basic ") < 0 || + git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 || + git_buf_puts(buf, "\r\n") < 0) + goto on_error; + + error = 0; + +on_error: + if (raw.size) + memset(raw.ptr, 0x0, raw.size); + + git_buf_free(&raw); + return error; +} + +static int gen_request( + git_buf *buf, + const char *path, + const char *host, + git_cred *cred, + http_authmechanism_t auth_mechanism, + const char *op, + const char *service, + const char *service_url, + ssize_t content_length) +{ + if (!path) path = "/"; git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url); @@ -86,6 +129,13 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c } else { git_buf_puts(buf, "Accept: */*\r\n"); } + + /* Apply credentials to the request */ + if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && + auth_mechanism == GIT_HTTP_AUTH_BASIC && + apply_basic_credential(buf, cred) < 0) + return -1; + git_buf_puts(buf, "\r\n"); if (git_buf_oom(buf)) @@ -94,88 +144,157 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } +static int parse_unauthorized_response( + git_vector *www_authenticate, + int *allowed_types, + http_authmechanism_t *auth_mechanism) +{ + unsigned i; + char *entry; + + git_vector_foreach(www_authenticate, i, entry) { + if (!strncmp(entry, basic_authtype, 5) && + (entry[5] == '\0' || entry[5] == ' ')) { + *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *auth_mechanism = GIT_HTTP_AUTH_BASIC; + } + } + + return 0; +} + +static int on_header_ready(http_subtransport *t) +{ + git_buf *name = &t->parse_header_name; + git_buf *value = &t->parse_header_value; + char *dup; + + if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) { + t->content_type = git__strdup(git_buf_cstr(value)); + GITERR_CHECK_ALLOC(t->content_type); + } + else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) { + dup = git__strdup(git_buf_cstr(value)); + GITERR_CHECK_ALLOC(dup); + git_vector_insert(&t->www_authenticate, dup); + } + + return 0; +} + static int on_header_field(http_parser *parser, const char *str, size_t len) { parser_context *ctx = (parser_context *) parser->data; http_subtransport *t = ctx->t; - git_buf *buf = &t->parse_temp; - if (t->last_cb == VALUE && t->ct_found) { - t->ct_finished = 1; - t->ct_found = 0; - t->content_type = git__strdup(git_buf_cstr(buf)); - GITERR_CHECK_ALLOC(t->content_type); - git_buf_clear(buf); - } + /* Both parse_header_name and parse_header_value are populated + * and ready for consumption */ + if (VALUE == t->last_cb) + if (on_header_ready(t) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; - if (t->ct_found) { - t->last_cb = FIELD; - return 0; - } + if (NONE == t->last_cb || VALUE == t->last_cb) + git_buf_clear(&t->parse_header_name); - if (t->last_cb != FIELD) - git_buf_clear(buf); + if (git_buf_put(&t->parse_header_name, str, len) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; - git_buf_put(buf, str, len); t->last_cb = FIELD; - - return git_buf_oom(buf); + return 0; } static int on_header_value(http_parser *parser, const char *str, size_t len) { parser_context *ctx = (parser_context *) parser->data; http_subtransport *t = ctx->t; - git_buf *buf = &t->parse_temp; - if (t->ct_finished) { - t->last_cb = VALUE; - return 0; - } + assert(NONE != t->last_cb); - if (t->last_cb == VALUE) - git_buf_put(buf, str, len); + if (FIELD == t->last_cb) + git_buf_clear(&t->parse_header_value); - if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), "Content-Type")) { - t->ct_found = 1; - git_buf_clear(buf); - git_buf_put(buf, str, len); - } + if (git_buf_put(&t->parse_header_value, str, len) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; t->last_cb = VALUE; - - return git_buf_oom(buf); + return 0; } static int on_headers_complete(http_parser *parser) { parser_context *ctx = (parser_context *) parser->data; http_subtransport *t = ctx->t; - git_buf *buf = &t->parse_temp; + http_stream *s = ctx->s; + git_buf buf = GIT_BUF_INIT; - /* The content-type is text/plain for 404, so don't validate */ - if (parser->status_code == 404) { - git_buf_clear(buf); - return 0; + /* Both parse_header_name and parse_header_value are populated + * and ready for consumption. */ + if (VALUE == t->last_cb) + if (on_header_ready(t) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; + + /* Check for an authentication failure. */ + if (parser->status_code == 401 && + get_verb == s->verb && + t->owner->cred_acquire_cb) { + int allowed_types = 0; + + if (parse_unauthorized_response(&t->www_authenticate, + &allowed_types, &t->auth_mechanism) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; + + if (allowed_types && + (!t->cred || 0 == (t->cred->credtype & allowed_types))) { + + if (t->owner->cred_acquire_cb(&t->cred, + t->owner->url, + allowed_types) < 0) + return PARSE_ERROR_GENERIC; + + assert(t->cred); + + /* Successfully acquired a credential. */ + return t->parse_error = PARSE_ERROR_REPLAY; + } } - if (t->content_type == NULL) { - t->content_type = git__strdup(git_buf_cstr(buf)); - if (t->content_type == NULL) - return t->parse_error = -1; + /* Check for a 200 HTTP status code. */ + if (parser->status_code != 200) { + giterr_set(GITERR_NET, + "Unexpected HTTP status code: %d", + parser->status_code); + return t->parse_error = PARSE_ERROR_GENERIC; } - git_buf_clear(buf); - git_buf_printf(buf, "application/x-git-%s-advertisement", ctx->s->service); - if (git_buf_oom(buf)) - return t->parse_error = -1; - - if (strcmp(t->content_type, git_buf_cstr(buf))) { - giterr_set(GITERR_NET, "Invalid content-type: %s", t->content_type); - return t->parse_error = -1; + /* The response must contain a Content-Type header. */ + if (!t->content_type) { + giterr_set(GITERR_NET, "No Content-Type header in response"); + return t->parse_error = PARSE_ERROR_GENERIC; } - git_buf_clear(buf); + /* The Content-Type header must match our expectation. */ + if (get_verb == s->verb) + git_buf_printf(&buf, + "application/x-git-%s-advertisement", + ctx->s->service); + else + git_buf_printf(&buf, + "application/x-git-%s-result", + ctx->s->service); + + if (git_buf_oom(&buf)) + return t->parse_error = PARSE_ERROR_GENERIC; + + if (strcmp(t->content_type, git_buf_cstr(&buf))) { + git_buf_free(&buf); + giterr_set(GITERR_NET, + "Invalid Content-Type: %s", + t->content_type); + return t->parse_error = PARSE_ERROR_GENERIC; + } + + git_buf_free(&buf); + return 0; } @@ -186,11 +305,6 @@ static int on_message_complete(http_parser *parser) t->parse_finished = 1; - if (parser->status_code == 404) { - giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->parse_temp)); - t->parse_error = -1; - } - return 0; } @@ -201,7 +315,7 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) if (ctx->buf_size < len) { giterr_set(GITERR_NET, "Can't fit data in the buffer"); - return t->parse_error = -1; + return t->parse_error = PARSE_ERROR_GENERIC; } memcpy(ctx->buffer, str, len); @@ -212,6 +326,36 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) return 0; } +static void clear_parser_state(http_subtransport *t) +{ + unsigned i; + char *entry; + + http_parser_init(&t->parser, HTTP_RESPONSE); + gitno_buffer_setup(&t->socket, + &t->parse_buffer, + t->parse_buffer_data, + sizeof(t->parse_buffer_data)); + + t->last_cb = NONE; + t->parse_error = 0; + t->parse_finished = 0; + + git_buf_free(&t->parse_header_name); + git_buf_init(&t->parse_header_name, 0); + + git_buf_free(&t->parse_header_value); + git_buf_init(&t->parse_header_value, 0); + + git__free(t->content_type); + t->content_type = NULL; + + git_vector_foreach(&t->www_authenticate, i, entry) + git__free(entry); + + git_vector_free(&t->www_authenticate); +} + static int http_stream_read( git_smart_subtransport_stream *stream, char *buffer, @@ -219,17 +363,22 @@ static int http_stream_read( size_t *bytes_read) { http_stream *s = (http_stream *)stream; - http_subtransport *t = OWNING_SUBTRANSPORT(s); + http_subtransport *t = OWNING_SUBTRANSPORT(s); git_buf request = GIT_BUF_INIT; parser_context ctx; +replay: *bytes_read = 0; assert(t->connected); if (!s->sent_request) { - if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, 0) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); + clear_parser_state(t); + + if (gen_request(&request, t->path, t->host, + t->cred, t->auth_mechanism, s->verb, + s->service, s->service_url, 0) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); return -1; } @@ -239,7 +388,7 @@ static int http_stream_read( } git_buf_free(&request); - s->sent_request = 1; + s->sent_request = 1; } t->parse_buffer.offset = 0; @@ -250,10 +399,11 @@ static int http_stream_read( if (gitno_recv(&t->parse_buffer) < 0) return -1; - /* This call to http_parser_execute will result in invocations of the on_* family of - * callbacks. The most interesting of these is on_body_fill_buffer, which is called - * when data is ready to be copied into the target buffer. We need to marshal the - * buffer, buf_size, and bytes_read parameters to this callback. */ + /* This call to http_parser_execute will result in invocations of the on_* + * family of callbacks. The most interesting of these is + * on_body_fill_buffer, which is called when data is ready to be copied + * into the target buffer. We need to marshal the buffer, buf_size, and + * bytes_read parameters to this callback. */ ctx.t = t; ctx.s = s; ctx.buffer = buffer; @@ -262,11 +412,23 @@ static int http_stream_read( /* Set the context, call the parser, then unset the context. */ t->parser.data = &ctx; - http_parser_execute(&t->parser, &t->settings, t->parse_buffer.data, t->parse_buffer.offset); + + http_parser_execute(&t->parser, + &t->settings, + t->parse_buffer.data, + t->parse_buffer.offset); + t->parser.data = NULL; + /* If there was a handled authentication failure, then parse_error + * will have signaled us that we should replay the request. */ + if (PARSE_ERROR_REPLAY == t->parse_error) { + s->sent_request = 0; + goto replay; + } + if (t->parse_error < 0) - return t->parse_error; + return -1; return 0; } @@ -277,7 +439,7 @@ static int http_stream_write( size_t len) { http_stream *s = (http_stream *)stream; - http_subtransport *t = OWNING_SUBTRANSPORT(s); + http_subtransport *t = OWNING_SUBTRANSPORT(s); git_buf request = GIT_BUF_INIT; assert(t->connected); @@ -287,7 +449,11 @@ static int http_stream_write( assert(!s->sent_request); if (!s->sent_request) { - if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, len) < 0) { + clear_parser_state(t); + + if (gen_request(&request, t->path, t->host, + t->cred, t->auth_mechanism, s->verb, + s->service, s->service_url, len) < 0) { giterr_set(GITERR_NET, "Failed to generate request"); return -1; } @@ -316,9 +482,10 @@ static void http_stream_free(git_smart_subtransport_stream *stream) git__free(s); } -static int http_stream_alloc(http_subtransport *t, git_smart_subtransport_stream **stream) +static int http_stream_alloc(http_subtransport *t, + git_smart_subtransport_stream **stream) { - http_stream *s; + http_stream *s; if (!stream) return -1; @@ -396,7 +563,8 @@ static int http_action( t->use_ssl = 1; } - if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, + url, default_port)) < 0) return ret; t->path = strchr(url, '/'); @@ -412,15 +580,10 @@ static int http_action( if (gitno_connect(&t->socket, t->host, t->port, flags) < 0) return -1; - - t->parser.data = t; - - http_parser_init(&t->parser, HTTP_RESPONSE); - gitno_buffer_setup(&t->socket, &t->parse_buffer, t->parse_buffer_data, sizeof(t->parse_buffer_data)); t->connected = 1; } - + t->parse_finished = 0; t->parse_error = 0; @@ -432,7 +595,7 @@ static int http_action( case GIT_SERVICE_UPLOADPACK: return http_uploadpack(t, stream); } - + *stream = NULL; return -1; } @@ -441,14 +604,20 @@ static void http_free(git_smart_subtransport *smart_transport) { http_subtransport *t = (http_subtransport *) smart_transport; - git_buf_free(&t->parse_temp); - git__free(t->content_type); + clear_parser_state(t); + + if (t->cred) { + t->cred->free(t->cred); + t->cred = NULL; + } + git__free(t->host); git__free(t->port); git__free(t); } -int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) +int git_smart_subtransport_http(git_smart_subtransport **out, + git_transport *owner) { http_subtransport *t; int flags; @@ -459,7 +628,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1); GITERR_CHECK_ALLOC(t); - t->owner = owner; + t->owner = (transport_smart *)owner; t->parent.action = http_action; t->parent.free = http_free; diff --git a/src/transports/local.c b/src/transports/local.c index 5a279ef00..0a63a12ea 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -130,7 +130,11 @@ on_error: * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calulating the heads ourselves. */ -static int local_connect(git_transport *transport, const char *url, int direction, int flags) +static int local_connect( + git_transport *transport, + const char *url, + git_cred_acquire_cb cred_acquire_cb, + int direction, int flags) { git_repository *repo; int error; @@ -138,6 +142,8 @@ static int local_connect(git_transport *transport, const char *url, int directio const char *path; git_buf buf = GIT_BUF_INIT; + GIT_UNUSED(cred_acquire_cb); + t->url = git__strdup(url); GITERR_CHECK_ALLOC(t->url); t->direction = direction; diff --git a/src/transports/smart.c b/src/transports/smart.c index b9c90dfde..8f9715a3f 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -52,7 +52,12 @@ static int git_smart__set_callbacks( return 0; } -static int git_smart__connect(git_transport *transport, const char *url, int direction, int flags) +static int git_smart__connect( + git_transport *transport, + const char *url, + git_cred_acquire_cb cred_acquire_cb, + int direction, + int flags) { transport_smart *t = (transport_smart *)transport; git_smart_subtransport_stream *stream; @@ -66,6 +71,7 @@ static int git_smart__connect(git_transport *transport, const char *url, int dir t->direction = direction; t->flags = flags; + t->cred_acquire_cb = cred_acquire_cb; if (GIT_DIR_FETCH == direction) { diff --git a/src/transports/smart.h b/src/transports/smart.h index 5784713eb..046bc89a4 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -99,6 +99,7 @@ typedef void (*packetsize_cb)(int received, void *payload); typedef struct { git_transport parent; char *url; + git_cred_acquire_cb cred_acquire_cb; int direction; int flags; git_transport_message_cb progress_cb; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 0411a70d4..ef47616ad 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -30,6 +30,7 @@ static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-p static const char *upload_pack_service_url = "/git-upload-pack"; static const wchar_t *get_verb = L"GET"; static const wchar_t *post_verb = L"POST"; +static const wchar_t *basic_authtype = L"Basic"; static const wchar_t *pragma_nocache = L"Pragma: no-cache"; static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | @@ -37,6 +38,10 @@ static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | #define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) +typedef enum { + GIT_WINHTTP_AUTH_BASIC = 1, +} winhttp_authmechanism_t; + typedef struct { git_smart_subtransport_stream parent; const char *service; @@ -49,16 +54,75 @@ typedef struct { typedef struct { git_smart_subtransport parent; - git_transport *owner; + transport_smart *owner; const char *path; char *host; char *port; + git_cred *cred; + int auth_mechanism; HINTERNET session; HINTERNET connection; unsigned use_ssl : 1, no_check_cert : 1; } winhttp_subtransport; +static int apply_basic_credential(HINTERNET request, git_cred *cred) +{ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT; + wchar_t *wide = NULL; + int error = -1, wide_len; + + git_buf_printf(&raw, "%s:%s", c->username, c->password); + + if (git_buf_oom(&raw) || + git_buf_puts(&buf, "Authorization: Basic ") < 0 || + git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0) + goto on_error; + + wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + git_buf_cstr(&buf), -1, NULL, 0); + + if (!wide_len) { + giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); + goto on_error; + } + + wide = (wchar_t *)git__malloc(wide_len * sizeof(wchar_t)); + + if (!wide) + goto on_error; + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + git_buf_cstr(&buf), -1, wide, wide_len)) { + giterr_set(GITERR_OS, "Failed to convert string to wide form"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + error = 0; + +on_error: + /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */ + if (wide) + memset(wide, 0x0, wide_len * sizeof(wchar_t)); + + if (buf.size) + memset(buf.ptr, 0x0, buf.size); + + if (raw.size) + memset(raw.ptr, 0x0, raw.size); + + git__free(wide); + git_buf_free(&buf); + git_buf_free(&raw); + return error; +} + static int winhttp_stream_connect(winhttp_stream *s) { winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); @@ -127,6 +191,13 @@ static int winhttp_stream_connect(winhttp_stream *s) } } + /* If we have a credential on the subtransport, apply it to the request */ + if (t->cred && + t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && + t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC && + apply_basic_credential(s->request, t->cred) < 0) + goto on_error; + /* We've done everything up to calling WinHttpSendRequest. */ return 0; @@ -136,6 +207,64 @@ on_error: return -1; } +static int parse_unauthorized_response( + HINTERNET request, + int *allowed_types, + int *auth_mechanism) +{ + DWORD index, buf_size, last_error; + int error = 0; + wchar_t *buf = NULL; + + *allowed_types = 0; + + for (index = 0; ; index++) { + /* Make a first call to ask for the size of the buffer to allocate + * to hold the WWW-Authenticate header */ + if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, + WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER, + &buf_size, &index)) + { + last_error = GetLastError(); + + if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) { + /* End of enumeration */ + break; + } else if (ERROR_INSUFFICIENT_BUFFER == last_error) { + git__free(buf); + buf = (wchar_t *)git__malloc(buf_size); + + if (!buf) { + error = -1; + break; + } + } else { + giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); + error = -1; + break; + } + } + + /* Actually receive the data into our now-allocated buffer */ + if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, + WINHTTP_HEADER_NAME_BY_INDEX, buf, + &buf_size, &index)) { + giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); + error = -1; + break; + } + + if (!wcsncmp(buf, basic_authtype, 5) && + (buf[5] == L'\0' || buf[5] == L' ')) { + *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *auth_mechanism = GIT_WINHTTP_AUTH_BASIC; + } + } + + git__free(buf); + return error; +} + static int winhttp_stream_read( git_smart_subtransport_stream *stream, char *buffer, @@ -145,6 +274,7 @@ static int winhttp_stream_read( winhttp_stream *s = (winhttp_stream *)stream; winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); +replay: /* Connect if necessary */ if (!s->request && winhttp_stream_connect(s) < 0) return -1; @@ -182,6 +312,31 @@ static int winhttp_stream_read( return -1; } + /* Handle authentication failures */ + if (HTTP_STATUS_DENIED == status_code && + get_verb == s->verb && t->owner->cred_acquire_cb) { + int allowed_types; + + if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0) + return -1; + + if (allowed_types && + (!t->cred || 0 == (t->cred->credtype & allowed_types))) { + + if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, allowed_types) < 0) + return -1; + + assert(t->cred); + + WinHttpCloseHandle(s->request); + s->request = NULL; + s->sent_request = 0; + + /* Successfully acquired a credential */ + goto replay; + } + } + if (HTTP_STATUS_OK != status_code) { giterr_set(GITERR_NET, "Request failed with status code: %d", status_code); return -1; @@ -432,6 +587,11 @@ static void winhttp_free(git_smart_subtransport *smart_transport) git__free(t->host); git__free(t->port); + if (t->cred) { + t->cred->free(t->cred); + t->cred = NULL; + } + if (t->connection) { WinHttpCloseHandle(t->connection); t->connection = NULL; @@ -456,7 +616,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1); GITERR_CHECK_ALLOC(t); - t->owner = owner; + t->owner = (transport_smart *)owner; t->parent.action = winhttp_action; t->parent.free = winhttp_free; From 2f7538ec00e8391c156bcdd64ae6dbb558758afe Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Tue, 6 Nov 2012 09:36:04 -0500 Subject: [PATCH 2/3] Fix connection leak in http subtransport --- src/netops.c | 9 ++++++--- src/transports/http.c | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/netops.c b/src/netops.c index 3e2743486..2d079efab 100644 --- a/src/netops.c +++ b/src/netops.c @@ -193,16 +193,19 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) static int gitno_ssl_teardown(gitno_ssl *ssl) { int ret; - + do { ret = SSL_shutdown(ssl->ssl); } while (ret == 0); + if (ret < 0) - return ssl_set_error(ssl, ret); + ret = ssl_set_error(ssl, ret); + else + ret = 0; SSL_free(ssl->ssl); SSL_CTX_free(ssl->ctx); - return 0; + return ret; } /* Match host names according to RFC 2818 rules */ diff --git a/src/transports/http.c b/src/transports/http.c index 4b48779f9..f2ff2d6e2 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -606,6 +606,9 @@ static void http_free(git_smart_subtransport *smart_transport) clear_parser_state(t); + if (t->socket.socket) + gitno_close(&t->socket); + if (t->cred) { t->cred->free(t->cred); t->cred = NULL; From 11fa84728312aecdd8bc038cebd3458ec162e603 Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Tue, 6 Nov 2012 11:27:23 -0500 Subject: [PATCH 3/3] Don't store no_check_cert; fetch it on demand --- src/transports/http.c | 18 +++++++----------- src/transports/winhttp.c | 22 +++++++++------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/transports/http.c b/src/transports/http.c index f2ff2d6e2..78977f44a 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -54,8 +54,7 @@ typedef struct { git_cred *cred; http_authmechanism_t auth_mechanism; unsigned connected : 1, - use_ssl : 1, - no_check_cert : 1; + use_ssl : 1; /* Parser structures */ http_parser parser; @@ -572,9 +571,14 @@ static int http_action( if (!t->connected || !http_should_keep_alive(&t->parser)) { if (t->use_ssl) { + int transport_flags; + + if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0) + return -1; + flags |= GITNO_CONNECT_SSL; - if (t->no_check_cert) + if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags) flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT; } @@ -635,14 +639,6 @@ int git_smart_subtransport_http(git_smart_subtransport **out, t->parent.action = http_action; t->parent.free = http_free; - /* Read the flags from the owning transport */ - if (owner->read_flags && owner->read_flags(owner, &flags) < 0) { - git__free(t); - return -1; - } - - t->no_check_cert = flags & GIT_TRANSPORTFLAGS_NO_CHECK_CERT; - t->settings.on_header_field = on_header_field; t->settings.on_header_value = on_header_value; t->settings.on_headers_complete = on_headers_complete; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index ef47616ad..44617f389 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -62,8 +62,7 @@ typedef struct { int auth_mechanism; HINTERNET session; HINTERNET connection; - unsigned use_ssl : 1, - no_check_cert : 1; + unsigned use_ssl : 1; } winhttp_subtransport; static int apply_basic_credential(HINTERNET request, git_cred *cred) @@ -183,8 +182,14 @@ static int winhttp_stream_connect(winhttp_stream *s) } /* If requested, disable certificate validation */ - if (t->use_ssl && t->no_check_cert) { - if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, + if (t->use_ssl) { + int flags; + + if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) + goto on_error; + + if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) && + !WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) { giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); goto on_error; @@ -608,7 +613,6 @@ static void winhttp_free(git_smart_subtransport *smart_transport) int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) { winhttp_subtransport *t; - int flags; if (!out) return -1; @@ -620,14 +624,6 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own t->parent.action = winhttp_action; t->parent.free = winhttp_free; - /* Read the flags from the owning transport */ - if (owner->read_flags && owner->read_flags(owner, &flags) < 0) { - git__free(t); - return -1; - } - - t->no_check_cert = flags & GIT_TRANSPORTFLAGS_NO_CHECK_CERT; - *out = (git_smart_subtransport *) t; return 0; }