Commit 9d641283 by Philip Kelley

Merge pull request #1048 from pwkelley/basic_auth

Basic authentication for http and winhttp
parents 8ff2b0c7 11fa8472
...@@ -287,6 +287,19 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha ...@@ -287,6 +287,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); 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 * 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. * to bypass the automatic discovery of a transport by URL scheme (i.e.
* http://, https://, git://) and supply their own transport to be used * http://, https://, git://) and supply their own transport to be used
...@@ -297,7 +310,9 @@ GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); ...@@ -297,7 +310,9 @@ GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
* @param remote the remote to configure * @param remote the remote to configure
* @param transport the transport object for the remote to use * @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 * Argument to the completion callback which tells it which operation
......
...@@ -20,6 +20,54 @@ ...@@ -20,6 +20,54 @@
GIT_BEGIN_DECL 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 *** *** Begin base transport interface ***
*/ */
...@@ -43,6 +91,7 @@ typedef struct git_transport { ...@@ -43,6 +91,7 @@ typedef struct git_transport {
* direction. */ * direction. */
int (*connect)(struct git_transport *transport, int (*connect)(struct git_transport *transport,
const char *url, const char *url,
git_cred_acquire_cb cred_acquire_cb,
int direction, int direction,
int flags); int flags);
......
...@@ -197,12 +197,15 @@ static int gitno_ssl_teardown(gitno_ssl *ssl) ...@@ -197,12 +197,15 @@ static int gitno_ssl_teardown(gitno_ssl *ssl)
do { do {
ret = SSL_shutdown(ssl->ssl); ret = SSL_shutdown(ssl->ssl);
} while (ret == 0); } while (ret == 0);
if (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_free(ssl->ssl);
SSL_CTX_free(ssl->ctx); SSL_CTX_free(ssl->ctx);
return 0; return ret;
} }
/* Match host names according to RFC 2818 rules */ /* Match host names according to RFC 2818 rules */
......
...@@ -492,7 +492,7 @@ int git_remote_connect(git_remote *remote, int direction) ...@@ -492,7 +492,7 @@ int git_remote_connect(git_remote *remote, int direction)
if (!remote->check_cert) if (!remote->check_cert)
flags |= GIT_TRANSPORTFLAGS_NO_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; goto on_error;
remote->transport = t; remote->transport = t;
...@@ -809,6 +809,15 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback ...@@ -809,6 +809,15 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
remote->callbacks.data); 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) int git_remote_set_transport(git_remote *remote, git_transport *transport)
{ {
assert(remote && transport); assert(remote && transport);
......
...@@ -22,6 +22,7 @@ struct git_remote { ...@@ -22,6 +22,7 @@ struct git_remote {
git_vector refs; git_vector refs;
struct git_refspec fetch; struct git_refspec fetch;
struct git_refspec push; struct git_refspec push;
git_cred_acquire_cb cred_acquire_cb;
git_transport *transport; git_transport *transport;
git_repository *repo; git_repository *repo;
git_remote_callbacks callbacks; git_remote_callbacks callbacks;
......
/*
* 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
...@@ -130,7 +130,11 @@ on_error: ...@@ -130,7 +130,11 @@ on_error:
* Try to open the url as a git directory. The direction doesn't * Try to open the url as a git directory. The direction doesn't
* matter in this case because we're calulating the heads ourselves. * 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; git_repository *repo;
int error; int error;
...@@ -138,6 +142,8 @@ static int local_connect(git_transport *transport, const char *url, int directio ...@@ -138,6 +142,8 @@ static int local_connect(git_transport *transport, const char *url, int directio
const char *path; const char *path;
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
GIT_UNUSED(cred_acquire_cb);
t->url = git__strdup(url); t->url = git__strdup(url);
GITERR_CHECK_ALLOC(t->url); GITERR_CHECK_ALLOC(t->url);
t->direction = direction; t->direction = direction;
......
...@@ -52,7 +52,12 @@ static int git_smart__set_callbacks( ...@@ -52,7 +52,12 @@ static int git_smart__set_callbacks(
return 0; 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; transport_smart *t = (transport_smart *)transport;
git_smart_subtransport_stream *stream; git_smart_subtransport_stream *stream;
...@@ -66,6 +71,7 @@ static int git_smart__connect(git_transport *transport, const char *url, int dir ...@@ -66,6 +71,7 @@ static int git_smart__connect(git_transport *transport, const char *url, int dir
t->direction = direction; t->direction = direction;
t->flags = flags; t->flags = flags;
t->cred_acquire_cb = cred_acquire_cb;
if (GIT_DIR_FETCH == direction) if (GIT_DIR_FETCH == direction)
{ {
......
...@@ -99,6 +99,7 @@ typedef void (*packetsize_cb)(int received, void *payload); ...@@ -99,6 +99,7 @@ typedef void (*packetsize_cb)(int received, void *payload);
typedef struct { typedef struct {
git_transport parent; git_transport parent;
char *url; char *url;
git_cred_acquire_cb cred_acquire_cb;
int direction; int direction;
int flags; int flags;
git_transport_message_cb progress_cb; git_transport_message_cb progress_cb;
......
...@@ -30,6 +30,7 @@ static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-p ...@@ -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 char *upload_pack_service_url = "/git-upload-pack";
static const wchar_t *get_verb = L"GET"; static const wchar_t *get_verb = L"GET";
static const wchar_t *post_verb = L"POST"; 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 wchar_t *pragma_nocache = L"Pragma: no-cache";
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
...@@ -37,6 +38,10 @@ static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_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) #define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
typedef enum {
GIT_WINHTTP_AUTH_BASIC = 1,
} winhttp_authmechanism_t;
typedef struct { typedef struct {
git_smart_subtransport_stream parent; git_smart_subtransport_stream parent;
const char *service; const char *service;
...@@ -49,16 +54,74 @@ typedef struct { ...@@ -49,16 +54,74 @@ typedef struct {
typedef struct { typedef struct {
git_smart_subtransport parent; git_smart_subtransport parent;
git_transport *owner; transport_smart *owner;
const char *path; const char *path;
char *host; char *host;
char *port; char *port;
git_cred *cred;
int auth_mechanism;
HINTERNET session; HINTERNET session;
HINTERNET connection; HINTERNET connection;
unsigned use_ssl : 1, unsigned use_ssl : 1;
no_check_cert : 1;
} winhttp_subtransport; } 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) static int winhttp_stream_connect(winhttp_stream *s)
{ {
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
...@@ -119,14 +182,27 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -119,14 +182,27 @@ static int winhttp_stream_connect(winhttp_stream *s)
} }
/* If requested, disable certificate validation */ /* If requested, disable certificate validation */
if (t->use_ssl && t->no_check_cert) { if (t->use_ssl) {
if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, 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))) { (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
goto on_error; goto on_error;
} }
} }
/* 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. */ /* We've done everything up to calling WinHttpSendRequest. */
return 0; return 0;
...@@ -136,6 +212,64 @@ on_error: ...@@ -136,6 +212,64 @@ on_error:
return -1; 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( static int winhttp_stream_read(
git_smart_subtransport_stream *stream, git_smart_subtransport_stream *stream,
char *buffer, char *buffer,
...@@ -145,6 +279,7 @@ static int winhttp_stream_read( ...@@ -145,6 +279,7 @@ static int winhttp_stream_read(
winhttp_stream *s = (winhttp_stream *)stream; winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
replay:
/* Connect if necessary */ /* Connect if necessary */
if (!s->request && winhttp_stream_connect(s) < 0) if (!s->request && winhttp_stream_connect(s) < 0)
return -1; return -1;
...@@ -182,6 +317,31 @@ static int winhttp_stream_read( ...@@ -182,6 +317,31 @@ static int winhttp_stream_read(
return -1; 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) { if (HTTP_STATUS_OK != status_code) {
giterr_set(GITERR_NET, "Request failed with status code: %d", status_code); giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
return -1; return -1;
...@@ -432,6 +592,11 @@ static void winhttp_free(git_smart_subtransport *smart_transport) ...@@ -432,6 +592,11 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
git__free(t->host); git__free(t->host);
git__free(t->port); git__free(t->port);
if (t->cred) {
t->cred->free(t->cred);
t->cred = NULL;
}
if (t->connection) { if (t->connection) {
WinHttpCloseHandle(t->connection); WinHttpCloseHandle(t->connection);
t->connection = NULL; t->connection = NULL;
...@@ -448,7 +613,6 @@ static void winhttp_free(git_smart_subtransport *smart_transport) ...@@ -448,7 +613,6 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
{ {
winhttp_subtransport *t; winhttp_subtransport *t;
int flags;
if (!out) if (!out)
return -1; return -1;
...@@ -456,18 +620,10 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own ...@@ -456,18 +620,10 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1); t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1);
GITERR_CHECK_ALLOC(t); GITERR_CHECK_ALLOC(t);
t->owner = owner; t->owner = (transport_smart *)owner;
t->parent.action = winhttp_action; t->parent.action = winhttp_action;
t->parent.free = winhttp_free; 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; *out = (git_smart_subtransport *) t;
return 0; return 0;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment