summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPhilip Kelley <phkelley@hotmail.com>2012-11-06 08:52:03 -0500
committerPhilip Kelley <phkelley@hotmail.com>2012-11-06 08:52:03 -0500
commit091361f569dd86e8550c04afb193bfb516a21a74 (patch)
treea6afafde40743ddde98fcb8e4d36031b6d7f47a0 /src
parenta5e85d86b7e13352c553b0a43bc36fee5880b5c7 (diff)
downloadlibgit2-091361f569dd86e8550c04afb193bfb516a21a74.tar.gz
Basic authentication for http and winhttp
Diffstat (limited to 'src')
-rw-r--r--src/remote.c11
-rw-r--r--src/remote.h1
-rw-r--r--src/transports/cred.c57
-rw-r--r--src/transports/http.c349
-rw-r--r--src/transports/local.c8
-rw-r--r--src/transports/smart.c8
-rw-r--r--src/transports/smart.h1
-rw-r--r--src/transports/winhttp.c164
8 files changed, 504 insertions, 95 deletions
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)
+{
+ 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 == NULL) /* Is 'git fetch http://host.com/' valid? */
+ 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;
+ /* 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;
+ }
- 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 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_clear(buf);
+ 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;