Unverified Commit a904fc6d by Edward Thomson Committed by GitHub

Merge pull request #4870 from libgit2/ethomson/proxy

Add builtin proxy support for the http transport
parents dcd00638 30ac46aa
......@@ -60,7 +60,6 @@ OPTION(USE_HTTPS "Enable HTTPS support. Can be set to a specific backend" ON)
OPTION(USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF)
OPTION(USE_STANDALONE_FUZZERS "Enable standalone fuzzers (compatible with gcc)" OFF)
OPTION(VALGRIND "Configure build for valgrind" OFF)
OPTION(CURL "Use curl for HTTP if available" ON)
OPTION(USE_EXT_HTTP_PARSER "Use system HTTP_Parser if available" ON)
OPTION(DEBUG_POOL "Enable debug pool allocator" OFF)
OPTION(ENABLE_WERROR "Enable compilation with -Werror" OFF)
......@@ -234,6 +233,7 @@ ELSE ()
ENABLE_WARNINGS(format)
ENABLE_WARNINGS(format-security)
ENABLE_WARNINGS(int-conversion)
DISABLE_WARNINGS(documentation-deprecated-sync)
IF (APPLE) # Apple deprecated OpenSSL
DISABLE_WARNINGS(deprecated-declarations)
......
......@@ -65,7 +65,7 @@ if (-not $Env:SKIP_PROXY_TESTS) {
Write-Host "Running proxy tests"
Write-Host ""
$Env:GITTEST_REMOTE_PROXY_URL="localhost:8080"
$Env:GITTEST_REMOTE_PROXY_HOST="localhost:8080"
$Env:GITTEST_REMOTE_PROXY_USER="foo"
$Env:GITTEST_REMOTE_PROXY_PASS="bar"
......
......@@ -164,11 +164,11 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then
echo "Running proxy tests"
echo ""
export GITTEST_REMOTE_PROXY_URL="localhost:8080"
export GITTEST_REMOTE_PROXY_HOST="localhost:8080"
export GITTEST_REMOTE_PROXY_USER="foo"
export GITTEST_REMOTE_PROXY_PASS="bar"
run_test proxy
unset GITTEST_REMOTE_PROXY_URL
unset GITTEST_REMOTE_PROXY_HOST
unset GITTEST_REMOTE_PROXY_USER
unset GITTEST_REMOTE_PROXY_PASS
fi
......
......@@ -40,19 +40,90 @@ typedef struct git_stream {
void (*free)(struct git_stream *);
} git_stream;
typedef int (*git_stream_cb)(git_stream **out, const char *host, const char *port);
typedef struct {
/** The `version` field should be set to `GIT_STREAM_VERSION`. */
int version;
/**
* Called to create a new connection to a given host.
*
* @param out The created stream
* @param host The hostname to connect to; may be a hostname or
* IP address
* @param port The port to connect to; may be a port number or
* service name
* @return 0 or an error code
*/
int (*init)(git_stream **out, const char *host, const char *port);
/**
* Called to create a new connection on top of the given stream. If
* this is a TLS stream, then this function may be used to proxy a
* TLS stream over an HTTP CONNECT session. If this is unset, then
* HTTP CONNECT proxies will not be supported.
*
* @param out The created stream
* @param in An existing stream to add TLS to
* @param host The hostname that the stream is connected to,
* for certificate validation
* @return 0 or an error code
*/
int (*wrap)(git_stream **out, git_stream *in, const char *host);
} git_stream_registration;
/**
* Register a TLS stream constructor for the library to use
* The type of stream to register.
*/
typedef enum {
/** A standard (non-TLS) socket. */
GIT_STREAM_STANDARD = 1,
/** A TLS-encrypted socket. */
GIT_STREAM_TLS = 2,
} git_stream_t;
/**
* Register stream constructors for the library to use
*
* If a registration structure is already set, it will be overwritten.
* Pass `NULL` in order to deregister the current constructor and return
* to the system defaults.
*
* If a constructor is already set, it will be overwritten. Pass
* `NULL` in order to deregister the current constructor.
* The type parameter may be a bitwise AND of types.
*
* @param ctor the constructor to use
* @param type the type or types of stream to register
* @param registration the registration data
* @return 0 or an error code
*/
GIT_EXTERN(int) git_stream_register(
git_stream_t type, git_stream_registration *registration);
/** @name Deprecated TLS Stream Registration Functions
*
* These typedefs and functions are retained for backward compatibility.
* The newer versions of these functions and structures should be preferred
* in all new code.
*/
/**@{*/
/**
* @deprecated Provide a git_stream_registration to git_stream_register
* @see git_stream_registration
*/
typedef int (*git_stream_cb)(git_stream **out, const char *host, const char *port);
/**
* Register a TLS stream constructor for the library to use. This stream
* will not support HTTP CONNECT proxies.
*
* @deprecated Provide a git_stream_registration to git_stream_register
* @see git_stream_register
*/
GIT_EXTERN(int) git_stream_register_tls(git_stream_cb ctor);
/**@}*/
GIT_END_DECL
#endif
......@@ -125,17 +125,6 @@ IF (WIN32 AND WINHTTP)
LIST(APPEND LIBGIT2_LIBS "rpcrt4" "crypt32" "ole32")
LIST(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32")
ELSE ()
IF (CURL)
FIND_PKGLIBRARIES(CURL libcurl)
ENDIF ()
IF (CURL_FOUND)
SET(GIT_CURL 1)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${CURL_INCLUDE_DIRS})
LIST(APPEND LIBGIT2_LIBS ${CURL_LIBRARIES})
LIST(APPEND LIBGIT2_PC_LIBS ${CURL_LDFLAGS})
ENDIF()
ADD_FEATURE_INFO(cURL GIT_CURL "cURL for HTTP proxy support")
ENDIF()
IF (USE_HTTPS)
......
......@@ -22,7 +22,6 @@
#cmakedefine GIT_GSSAPI 1
#cmakedefine GIT_WINHTTP 1
#cmakedefine GIT_CURL 1
#cmakedefine GIT_HTTPS 1
#cmakedefine GIT_OPENSSL 1
......
......@@ -12,7 +12,7 @@
#include "sysdir.h"
#include "filter.h"
#include "merge_driver.h"
#include "streams/curl.h"
#include "streams/registry.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "thread-utils.h"
......@@ -67,8 +67,8 @@ static int init_common(void)
(ret = git_filter_global_init()) == 0 &&
(ret = git_merge_driver_global_init()) == 0 &&
(ret = git_transport_ssh_global_init()) == 0 &&
(ret = git_stream_registry_global_init()) == 0 &&
(ret = git_openssl_stream_global_init()) == 0 &&
(ret = git_curl_stream_global_init()) == 0 &&
(ret = git_mbedtls_stream_global_init()) == 0)
ret = git_mwindow_global_init();
......
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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 "streams/curl.h"
#ifdef GIT_CURL
#include <curl/curl.h>
#include "stream.h"
#include "git2/transport.h"
#include "buffer.h"
#include "global.h"
#include "vector.h"
#include "proxy.h"
/* This is for backwards compatibility with curl<7.45.0. */
#ifndef CURLINFO_ACTIVESOCKET
# define CURLINFO_ACTIVESOCKET CURLINFO_LASTSOCKET
# define GIT_CURL_BADSOCKET -1
# define git_activesocket_t long
#else
# define GIT_CURL_BADSOCKET CURL_SOCKET_BAD
# define git_activesocket_t curl_socket_t
#endif
typedef struct {
git_stream parent;
CURL *handle;
curl_socket_t socket;
char curl_error[CURL_ERROR_SIZE + 1];
git_cert_x509 cert_info;
git_strarray cert_info_strings;
git_proxy_options proxy;
git_cred *proxy_cred;
} curl_stream;
int git_curl_stream_global_init(void)
{
if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
giterr_set(GITERR_NET, "could not initialize curl");
return -1;
}
/* `curl_global_cleanup` is provided by libcurl */
git__on_shutdown(curl_global_cleanup);
return 0;
}
static int seterr_curl(curl_stream *s)
{
giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
return -1;
}
GIT_INLINE(int) error_no_credentials(void)
{
giterr_set(GITERR_NET, "proxy authentication required, but no callback provided");
return GIT_EAUTH;
}
static int apply_proxy_creds(curl_stream *s)
{
CURLcode res;
git_cred_userpass_plaintext *userpass;
if (!s->proxy_cred)
return GIT_ENOTFOUND;
userpass = (git_cred_userpass_plaintext *) s->proxy_cred;
if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYUSERNAME, userpass->username)) != CURLE_OK)
return seterr_curl(s);
if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYPASSWORD, userpass->password)) != CURLE_OK)
return seterr_curl(s);
return 0;
}
static int ask_and_apply_proxy_creds(curl_stream *s)
{
int error;
git_proxy_options *opts = &s->proxy;
if (!opts->credentials)
return error_no_credentials();
/* TODO: see if PROXYAUTH_AVAIL helps us here */
git_cred_free(s->proxy_cred);
s->proxy_cred = NULL;
giterr_clear();
error = opts->credentials(&s->proxy_cred, opts->url, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, opts->payload);
if (error == GIT_PASSTHROUGH)
return error_no_credentials();
if (error < 0) {
if (!giterr_last())
giterr_set(GITERR_NET, "proxy authentication was aborted by the user");
return error;
}
if (s->proxy_cred->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
giterr_set(GITERR_NET, "credentials callback returned invalid credential type");
return -1;
}
return apply_proxy_creds(s);
}
static int curls_connect(git_stream *stream)
{
curl_stream *s = (curl_stream *) stream;
git_activesocket_t sockextr;
long connect_last = 0;
int failed_cert = 0, error;
bool retry_connect;
CURLcode res;
/* Apply any credentials we've already established */
error = apply_proxy_creds(s);
if (error < 0 && error != GIT_ENOTFOUND)
return seterr_curl(s);
do {
retry_connect = 0;
res = curl_easy_perform(s->handle);
curl_easy_getinfo(s->handle, CURLINFO_HTTP_CONNECTCODE, &connect_last);
/* HTTP 407 Proxy Authentication Required */
if (connect_last == 407) {
if ((error = ask_and_apply_proxy_creds(s)) < 0)
return error;
retry_connect = true;
}
} while (retry_connect);
if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION)
return seterr_curl(s);
if (res == CURLE_PEER_FAILED_VERIFICATION)
failed_cert = 1;
if ((res = curl_easy_getinfo(s->handle, CURLINFO_ACTIVESOCKET, &sockextr)) != CURLE_OK) {
return seterr_curl(s);
}
if (sockextr == GIT_CURL_BADSOCKET) {
giterr_set(GITERR_NET, "curl socket is no longer valid");
return -1;
}
s->socket = sockextr;
if (s->parent.encrypted && failed_cert)
return GIT_ECERTIFICATE;
return 0;
}
static int curls_certificate(git_cert **out, git_stream *stream)
{
int error;
CURLcode res;
struct curl_slist *slist;
struct curl_certinfo *certinfo;
git_vector strings = GIT_VECTOR_INIT;
curl_stream *s = (curl_stream *) stream;
if ((res = curl_easy_getinfo(s->handle, CURLINFO_CERTINFO, &certinfo)) != CURLE_OK)
return seterr_curl(s);
/* No information is available, can happen with SecureTransport */
if (certinfo->num_of_certs == 0) {
s->cert_info.parent.cert_type = GIT_CERT_NONE;
s->cert_info.data = NULL;
s->cert_info.len = 0;
return 0;
}
if ((error = git_vector_init(&strings, 8, NULL)) < 0)
return error;
for (slist = certinfo->certinfo[0]; slist; slist = slist->next) {
char *str = git__strdup(slist->data);
GITERR_CHECK_ALLOC(str);
git_vector_insert(&strings, str);
}
/* Copy the contents of the vector into a strarray so we can expose them */
s->cert_info_strings.strings = (char **) strings.contents;
s->cert_info_strings.count = strings.length;
s->cert_info.parent.cert_type = GIT_CERT_STRARRAY;
s->cert_info.data = &s->cert_info_strings;
s->cert_info.len = strings.length;
*out = &s->cert_info.parent;
return 0;
}
static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
{
int error;
CURLcode res;
curl_stream *s = (curl_stream *) stream;
git_proxy_options_clear(&s->proxy);
if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0)
return error;
if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, s->proxy.url)) != CURLE_OK)
return seterr_curl(s);
if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)) != CURLE_OK)
return seterr_curl(s);
return 0;
}
static int wait_for(curl_socket_t fd, bool reading)
{
int ret;
fd_set infd, outfd, errfd;
FD_ZERO(&infd);
FD_ZERO(&outfd);
FD_ZERO(&errfd);
assert(fd >= 0);
FD_SET(fd, &errfd);
if (reading)
FD_SET(fd, &infd);
else
FD_SET(fd, &outfd);
if ((ret = select(fd + 1, &infd, &outfd, &errfd, NULL)) < 0) {
giterr_set(GITERR_OS, "error in select");
return -1;
}
return 0;
}
static ssize_t curls_write(git_stream *stream, const char *data, size_t len, int flags)
{
int error;
size_t off = 0, sent;
CURLcode res;
curl_stream *s = (curl_stream *) stream;
GIT_UNUSED(flags);
do {
if ((error = wait_for(s->socket, false)) < 0)
return error;
res = curl_easy_send(s->handle, data + off, len - off, &sent);
if (res == CURLE_OK)
off += sent;
} while ((res == CURLE_OK || res == CURLE_AGAIN) && off < len);
if (res != CURLE_OK)
return seterr_curl(s);
return len;
}
static ssize_t curls_read(git_stream *stream, void *data, size_t len)
{
int error;
size_t read;
CURLcode res;
curl_stream *s = (curl_stream *) stream;
do {
if ((error = wait_for(s->socket, true)) < 0)
return error;
res = curl_easy_recv(s->handle, data, len, &read);
} while (res == CURLE_AGAIN);
if (res != CURLE_OK)
return seterr_curl(s);
return read;
}
static int curls_close(git_stream *stream)
{
curl_stream *s = (curl_stream *) stream;
if (!s->handle)
return 0;
curl_easy_cleanup(s->handle);
s->handle = NULL;
s->socket = 0;
return 0;
}
static void curls_free(git_stream *stream)
{
curl_stream *s = (curl_stream *) stream;
curls_close(stream);
git_strarray_free(&s->cert_info_strings);
git_proxy_options_clear(&s->proxy);
git_cred_free(s->proxy_cred);
git__free(s);
}
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
{
curl_stream *st;
CURL *handle;
int iport = 0, error;
st = git__calloc(1, sizeof(curl_stream));
GITERR_CHECK_ALLOC(st);
handle = curl_easy_init();
if (handle == NULL) {
giterr_set(GITERR_NET, "failed to create curl handle");
git__free(st);
return -1;
}
if ((error = git__strntol32(&iport, port, strlen(port), NULL, 10)) < 0) {
git__free(st);
return error;
}
curl_easy_setopt(handle, CURLOPT_URL, host);
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error);
curl_easy_setopt(handle, CURLOPT_PORT, iport);
curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 1);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(handle, CURLOPT_CERTINFO, 1);
curl_easy_setopt(handle, CURLOPT_HTTPPROXYTUNNEL, 1);
curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
/* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */
st->parent.version = GIT_STREAM_VERSION;
st->parent.encrypted = 0; /* we don't encrypt ourselves */
st->parent.proxy_support = 1;
st->parent.connect = curls_connect;
st->parent.certificate = curls_certificate;
st->parent.set_proxy = curls_set_proxy;
st->parent.read = curls_read;
st->parent.write = curls_write;
st->parent.close = curls_close;
st->parent.free = curls_free;
st->handle = handle;
*out = (git_stream *) st;
return 0;
}
#else
#include "stream.h"
int git_curl_stream_global_init(void)
{
return 0;
}
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(out);
GIT_UNUSED(host);
GIT_UNUSED(port);
giterr_set(GITERR_NET, "curl is not supported in this version");
return -1;
}
#endif
......@@ -18,10 +18,6 @@
#include "git2/transport.h"
#include "util.h"
#ifdef GIT_CURL
# include "streams/curl.h"
#endif
#ifndef GIT_DEFAULT_CERT_LOCATION
#define GIT_DEFAULT_CERT_LOCATION NULL
#endif
......@@ -242,6 +238,7 @@ static int verify_server_cert(mbedtls_ssl_context *ssl)
typedef struct {
git_stream parent;
git_stream *io;
int owned;
bool connected;
char *host;
mbedtls_ssl_context *ssl;
......@@ -254,7 +251,7 @@ int mbedtls_connect(git_stream *stream)
int ret;
mbedtls_stream *st = (mbedtls_stream *) stream;
if ((ret = git_stream_connect(st->io)) < 0)
if (st->owned && (ret = git_stream_connect(st->io)) < 0)
return ret;
st->connected = true;
......@@ -345,37 +342,37 @@ int mbedtls_stream_close(git_stream *stream)
st->connected = false;
return git_stream_close(st->io);
return st->owned ? git_stream_close(st->io) : 0;
}
void mbedtls_stream_free(git_stream *stream)
{
mbedtls_stream *st = (mbedtls_stream *) stream;
if (st->owned)
git_stream_free(st->io);
git__free(st->host);
git__free(st->cert_info.data);
git_stream_free(st->io);
mbedtls_ssl_free(st->ssl);
git__free(st->ssl);
git__free(st);
}
int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
static int mbedtls_stream_wrap(
git_stream **out,
git_stream *in,
const char *host,
int owned)
{
int error;
mbedtls_stream *st;
int error;
st = git__calloc(1, sizeof(mbedtls_stream));
GITERR_CHECK_ALLOC(st);
#ifdef GIT_CURL
error = git_curl_stream_new(&st->io, host, port);
#else
error = git_socket_stream_new(&st->io, host, port);
#endif
if (error < 0)
goto out_err;
st->io = in;
st->owned = owned;
st->ssl = git__malloc(sizeof(mbedtls_ssl_context));
GITERR_CHECK_ALLOC(st->ssl);
......@@ -405,12 +402,42 @@ int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
out_err:
mbedtls_ssl_free(st->ssl);
git_stream_close(st->io);
git_stream_free(st->io);
git__free(st);
return error;
}
int git_mbedtls_stream_wrap(
git_stream **out,
git_stream *in,
const char *host)
{
return mbedtls_stream_wrap(out, in, host, 0);
}
int git_mbedtls_stream_new(
git_stream **out,
const char *host,
const char *port)
{
git_stream *stream;
int error;
assert(out && host && port);
if ((error = git_socket_stream_new(&stream, host, port)) < 0)
return error;
if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) {
git_stream_close(stream);
git_stream_free(stream);
}
return error;
}
int git_mbedtls__set_cert_location(const char *path, int is_dir)
{
int ret = 0;
......@@ -453,23 +480,4 @@ int git_mbedtls_stream_global_init(void)
return 0;
}
int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(out);
GIT_UNUSED(host);
GIT_UNUSED(port);
giterr_set(GITERR_SSL, "mbedTLS is not supported in this version");
return -1;
}
int git_mbedtls__set_cert_location(const char *path, int is_dir)
{
GIT_UNUSED(path);
GIT_UNUSED(is_dir);
giterr_set(GITERR_SSL, "mbedTLS is not supported in this version");
return -1;
}
#endif
......@@ -13,8 +13,11 @@
extern int git_mbedtls_stream_global_init(void);
extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port);
#ifdef GIT_MBEDTLS
extern int git_mbedtls__set_cert_location(const char *path, int is_dir);
extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port);
extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host);
#endif
#endif
......@@ -19,10 +19,6 @@
#include "git2/transport.h"
#include "git2/sys/openssl.h"
#ifdef GIT_CURL
# include "streams/curl.h"
#endif
#ifndef GIT_WIN32
# include <sys/types.h>
# include <sys/socket.h>
......@@ -569,6 +565,7 @@ cleanup:
typedef struct {
git_stream parent;
git_stream *io;
int owned;
bool connected;
char *host;
SSL *ssl;
......@@ -583,7 +580,7 @@ int openssl_connect(git_stream *stream)
BIO *bio;
openssl_stream *st = (openssl_stream *) stream;
if ((ret = git_stream_connect(st->io)) < 0)
if (st->owned && (ret = git_stream_connect(st->io)) < 0)
return ret;
bio = BIO_new(git_stream_bio_method);
......@@ -682,43 +679,43 @@ int openssl_close(git_stream *stream)
st->connected = false;
return git_stream_close(st->io);
return st->owned ? git_stream_close(st->io) : 0;
}
void openssl_free(git_stream *stream)
{
openssl_stream *st = (openssl_stream *) stream;
if (st->owned)
git_stream_free(st->io);
SSL_free(st->ssl);
git__free(st->host);
git__free(st->cert_info.data);
git_stream_free(st->io);
git__free(st);
}
int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
static int openssl_stream_wrap(
git_stream **out,
git_stream *in,
const char *host,
int owned)
{
int error;
openssl_stream *st;
assert(out && in && host);
st = git__calloc(1, sizeof(openssl_stream));
GITERR_CHECK_ALLOC(st);
st->io = NULL;
#ifdef GIT_CURL
error = git_curl_stream_new(&st->io, host, port);
#else
error = git_socket_stream_new(&st->io, host, port);
#endif
if (error < 0)
goto out_err;
st->io = in;
st->owned = owned;
st->ssl = SSL_new(git__ssl_ctx);
if (st->ssl == NULL) {
giterr_set(GITERR_SSL, "failed to create ssl object");
error = -1;
goto out_err;
git__free(st);
return -1;
}
st->host = git__strdup(host);
......@@ -737,10 +734,27 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
*out = (git_stream *) st;
return 0;
}
out_err:
git_stream_free(st->io);
git__free(st);
int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host)
{
return openssl_stream_wrap(out, in, host, 0);
}
int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
{
git_stream *stream = NULL;
int error;
assert(out && host && port);
if ((error = git_socket_stream_new(&stream, host, port)) < 0)
return error;
if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) {
git_stream_close(stream);
git_stream_free(stream);
}
return error;
}
......@@ -775,23 +789,4 @@ int git_openssl_set_locking(void)
return -1;
}
int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(out);
GIT_UNUSED(host);
GIT_UNUSED(port);
giterr_set(GITERR_SSL, "openssl is not supported in this version");
return -1;
}
int git_openssl__set_cert_location(const char *file, const char *path)
{
GIT_UNUSED(file);
GIT_UNUSED(path);
giterr_set(GITERR_SSL, "openssl is not supported in this version");
return -1;
}
#endif
......@@ -13,8 +13,11 @@
extern int git_openssl_stream_global_init(void);
extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
#ifdef GIT_OPENSSL
extern int git_openssl__set_cert_location(const char *file, const char *path);
extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host);
#endif
#endif
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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/errors.h"
#include "common.h"
#include "global.h"
#include "streams/tls.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "streams/stransport.h"
struct stream_registry {
git_rwlock lock;
git_stream_registration callbacks;
git_stream_registration tls_callbacks;
};
static struct stream_registry stream_registry;
static void shutdown_stream_registry(void)
{
git_rwlock_free(&stream_registry.lock);
}
int git_stream_registry_global_init(void)
{
if (git_rwlock_init(&stream_registry.lock) < 0)
return -1;
git__on_shutdown(shutdown_stream_registry);
return 0;
}
GIT_INLINE(void) stream_registration_cpy(
git_stream_registration *target,
git_stream_registration *src)
{
if (src)
memcpy(target, src, sizeof(git_stream_registration));
else
memset(target, 0, sizeof(git_stream_registration));
}
int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type)
{
git_stream_registration *target;
int error = GIT_ENOTFOUND;
assert(out);
switch(type) {
case GIT_STREAM_STANDARD:
target = &stream_registry.callbacks;
break;
case GIT_STREAM_TLS:
target = &stream_registry.tls_callbacks;
break;
default:
assert(0);
return -1;
}
if (git_rwlock_rdlock(&stream_registry.lock) < 0) {
giterr_set(GITERR_OS, "failed to lock stream registry");
return -1;
}
if (target->init) {
stream_registration_cpy(out, target);
error = 0;
}
git_rwlock_rdunlock(&stream_registry.lock);
return error;
}
int git_stream_register(git_stream_t type, git_stream_registration *registration)
{
assert(!registration || registration->init);
GITERR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration");
if (git_rwlock_wrlock(&stream_registry.lock) < 0) {
giterr_set(GITERR_OS, "failed to lock stream registry");
return -1;
}
if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD)
stream_registration_cpy(&stream_registry.callbacks, registration);
if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS)
stream_registration_cpy(&stream_registry.tls_callbacks, registration);
git_rwlock_wrunlock(&stream_registry.lock);
return 0;
}
int git_stream_register_tls(git_stream_cb ctor)
{
git_stream_registration registration = {0};
if (ctor) {
registration.version = GIT_STREAM_VERSION;
registration.init = ctor;
registration.wrap = NULL;
return git_stream_register(GIT_STREAM_TLS, &registration);
} else {
return git_stream_register(GIT_STREAM_TLS, NULL);
}
}
......@@ -4,14 +4,16 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_streams_curl_h__
#define INCLUDE_streams_curl_h__
#ifndef INCLUDE_streams_registry_h__
#define INCLUDE_streams_registry_h__
#include "common.h"
#include "git2/sys/stream.h"
extern int git_curl_stream_global_init(void);
extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
/** Configure stream registry. */
int git_stream_registry_global_init(void);
/** Lookup a stream registration. */
extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type);
#endif
......@@ -9,6 +9,7 @@
#include "posix.h"
#include "netops.h"
#include "registry.h"
#include "stream.h"
#ifndef _WIN32
......@@ -180,11 +181,14 @@ void socket_free(git_stream *stream)
git__free(st);
}
int git_socket_stream_new(git_stream **out, const char *host, const char *port)
static int default_socket_stream_new(
git_stream **out,
const char *host,
const char *port)
{
git_socket_stream *st;
assert(out && host);
assert(out && host && port);
st = git__calloc(1, sizeof(git_socket_stream));
GITERR_CHECK_ALLOC(st);
......@@ -208,3 +212,29 @@ int git_socket_stream_new(git_stream **out, const char *host, const char *port)
*out = (git_stream *) st;
return 0;
}
int git_socket_stream_new(
git_stream **out,
const char *host,
const char *port)
{
int (*init)(git_stream **, const char *, const char *) = NULL;
git_stream_registration custom = {0};
int error;
assert(out && host && port);
if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0)
init = custom.init;
else if (error == GIT_ENOTFOUND)
init = default_socket_stream_new;
else
return error;
if (!init) {
giterr_set(GITERR_NET, "there is no socket stream available");
return -1;
}
return init(out, host, port);
}
......@@ -16,7 +16,6 @@
#include "git2/transport.h"
#include "streams/socket.h"
#include "streams/curl.h"
static int stransport_error(OSStatus ret)
{
......@@ -34,8 +33,8 @@ static int stransport_error(OSStatus ret)
giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
CFRelease(message);
#else
giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
GIT_UNUSED(message);
giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
GIT_UNUSED(message);
#endif
return -1;
......@@ -44,6 +43,7 @@ static int stransport_error(OSStatus ret)
typedef struct {
git_stream parent;
git_stream *io;
int owned;
SSLContextRef ctx;
CFDataRef der_data;
git_cert_x509 cert_info;
......@@ -57,7 +57,7 @@ static int stransport_connect(git_stream *stream)
SecTrustResultType sec_res;
OSStatus ret;
if ((error = git_stream_connect(st->io)) < 0)
if (st->owned && (error = git_stream_connect(st->io)) < 0)
return error;
ret = SSLHandshake(st->ctx);
......@@ -226,41 +226,38 @@ static int stransport_close(git_stream *stream)
if (ret != noErr && ret != errSSLClosedGraceful)
return stransport_error(ret);
return git_stream_close(st->io);
return st->owned ? git_stream_close(st->io) : 0;
}
static void stransport_free(git_stream *stream)
{
stransport_stream *st = (stransport_stream *) stream;
git_stream_free(st->io);
if (st->owned)
git_stream_free(st->io);
CFRelease(st->ctx);
if (st->der_data)
CFRelease(st->der_data);
git__free(st);
}
int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
static int stransport_wrap(
git_stream **out,
git_stream *in,
const char *host,
int owned)
{
stransport_stream *st;
int error;
OSStatus ret;
assert(out && host);
assert(out && in && host);
st = git__calloc(1, sizeof(stransport_stream));
GITERR_CHECK_ALLOC(st);
#ifdef GIT_CURL
error = git_curl_stream_new(&st->io, host, port);
#else
error = git_socket_stream_new(&st->io, host, port);
#endif
if (error < 0){
git__free(st);
return error;
}
st->io = in;
st->owned = owned;
st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
if (!st->ctx) {
......@@ -295,4 +292,32 @@ int git_stransport_stream_new(git_stream **out, const char *host, const char *po
return 0;
}
int git_stransport_stream_wrap(
git_stream **out,
git_stream *in,
const char *host)
{
return stransport_wrap(out, in, host, 0);
}
int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
{
git_stream *stream = NULL;
int error;
assert(out && host);
error = git_socket_stream_new(&stream, host, port);
if (!error)
error = stransport_wrap(out, stream, host, 1);
if (error < 0 && stream) {
git_stream_close(stream);
git_stream_free(stream);
}
return error;
}
#endif
......@@ -11,6 +11,11 @@
#include "git2/sys/stream.h"
#ifdef GIT_SECURE_TRANSPORT
extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port);
extern int git_stransport_stream_wrap(git_stream **out, git_stream *in, const char *host);
#endif
#endif
......@@ -5,41 +5,69 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "streams/tls.h"
#include "git2/errors.h"
#include "common.h"
#include "global.h"
#include "streams/registry.h"
#include "streams/tls.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "streams/stransport.h"
static git_stream_cb tls_ctor;
int git_stream_register_tls(git_stream_cb ctor)
int git_tls_stream_new(git_stream **out, const char *host, const char *port)
{
tls_ctor = ctor;
int (*init)(git_stream **, const char *, const char *) = NULL;
git_stream_registration custom = {0};
int error;
assert(out && host && port);
return 0;
if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) {
init = custom.init;
} else if (error == GIT_ENOTFOUND) {
#ifdef GIT_SECURE_TRANSPORT
init = git_stransport_stream_new;
#elif defined(GIT_OPENSSL)
init = git_openssl_stream_new;
#elif defined(GIT_MBEDTLS)
init = git_mbedtls_stream_new;
#endif
} else {
return error;
}
if (!init) {
giterr_set(GITERR_SSL, "there is no TLS stream available");
return -1;
}
return init(out, host, port);
}
int git_tls_stream_new(git_stream **out, const char *host, const char *port)
int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host)
{
int (*wrap)(git_stream **, git_stream *, const char *) = NULL;
git_stream_registration custom = {0};
if (tls_ctor)
return tls_ctor(out, host, port);
assert(out && in);
if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) {
wrap = custom.wrap;
} else {
#ifdef GIT_SECURE_TRANSPORT
return git_stransport_stream_new(out, host, port);
wrap = git_stransport_stream_wrap;
#elif defined(GIT_OPENSSL)
return git_openssl_stream_new(out, host, port);
wrap = git_openssl_stream_wrap;
#elif defined(GIT_MBEDTLS)
return git_mbedtls_stream_new(out, host, port);
#else
GIT_UNUSED(out);
GIT_UNUSED(host);
GIT_UNUSED(port);
giterr_set(GITERR_SSL, "there is no TLS stream available");
return -1;
wrap = git_mbedtls_stream_wrap;
#endif
}
if (!wrap) {
giterr_set(GITERR_SSL, "there is no TLS stream available");
return -1;
}
return wrap(out, in, host);
}
......@@ -13,11 +13,19 @@
/**
* Create a TLS stream with the most appropriate backend available for
* the current platform.
*
* This allows us to ask for a SecureTransport or OpenSSL stream
* according to being on general Unix vs OS X.
* the current platform, whether that's SecureTransport on macOS,
* OpenSSL or mbedTLS on other Unixes, or something else entirely.
*/
extern int git_tls_stream_new(git_stream **out, const char *host, const char *port);
/**
* Create a TLS stream on top of an existing insecure stream, using
* the most appropriate backend available for the current platform.
*
* This allows us to create a CONNECT stream on top of a proxy;
* using SecureTransport on macOS, OpenSSL or mbedTLS on other
* Unixes, or something else entirely.
*/
extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host);
#endif
......@@ -11,7 +11,10 @@
#include "buffer.h"
static int basic_next_token(
git_buf *out, git_http_auth_context *ctx, git_cred *c)
git_buf *out,
git_http_auth_context *ctx,
const char *header_name,
git_cred *c)
{
git_cred_userpass_plaintext *cred;
git_buf raw = GIT_BUF_INIT;
......@@ -29,7 +32,7 @@ static int basic_next_token(
git_buf_printf(&raw, "%s:%s", cred->username, cred->password);
if (git_buf_oom(&raw) ||
git_buf_puts(out, "Authorization: Basic ") < 0 ||
git_buf_printf(out, "%s: Basic ", header_name) < 0 ||
git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 ||
git_buf_puts(out, "\r\n") < 0)
goto on_error;
......
......@@ -31,7 +31,7 @@ struct git_http_auth_context {
int (*set_challenge)(git_http_auth_context *ctx, const char *challenge);
/** Gets the next authentication token from the context */
int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_cred *cred);
int (*next_token)(git_buf *out, git_http_auth_context *ctx, const char *header_name, git_cred *cred);
/** Frees the authentication context */
void (*free)(git_http_auth_context *ctx);
......
......@@ -73,6 +73,7 @@ static int negotiate_set_challenge(
static int negotiate_next_token(
git_buf *buf,
git_http_auth_context *c,
const char *header_name,
git_cred *cred)
{
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
......@@ -155,7 +156,7 @@ static int negotiate_next_token(
goto done;
}
git_buf_puts(buf, "Authorization: Negotiate ");
git_buf_printf(buf, "%s: Negotiate ", header_name);
git_buf_encode_base64(buf, output_token.value, output_token.length);
git_buf_puts(buf, "\r\n");
......
......@@ -21,7 +21,6 @@
#include "auth_negotiate.h"
#include "streams/tls.h"
#include "streams/socket.h"
#include "streams/curl.h"
git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
......@@ -37,6 +36,12 @@ static const char *receive_pack_service_url = "/git-receive-pack";
static const char *get_verb = "GET";
static const char *post_verb = "POST";
#define AUTH_HEADER_SERVER "Authorization"
#define AUTH_HEADER_PROXY "Proxy-Authorization"
#define SERVER_TYPE_REMOTE "remote"
#define SERVER_TYPE_PROXY "proxy"
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
#define PARSE_ERROR_GENERIC -1
......@@ -62,17 +67,32 @@ typedef struct {
unsigned chunk_buffer_len;
unsigned sent_request : 1,
received_response : 1,
chunked : 1,
redirect_count : 3;
chunked : 1;
} http_stream;
typedef struct {
gitno_connection_data url;
git_stream *stream;
git_cred *cred;
git_cred *url_cred;
git_vector auth_challenges;
git_vector auth_contexts;
} http_server;
typedef struct {
git_smart_subtransport parent;
transport_smart *owner;
git_stream *io;
gitno_connection_data connection_data;
git_stream *gitserver_stream;
bool connected;
http_server server;
http_server proxy;
char *proxy_url;
git_proxy_options proxy_opts;
/* Parser structures */
http_parser parser;
http_parser_settings settings;
......@@ -81,17 +101,13 @@ typedef struct {
git_buf parse_header_value;
char parse_buffer_data[NETIO_BUFSIZE];
char *content_type;
char *content_length;
char *location;
git_vector www_authenticate;
enum last_cb last_cb;
int parse_error;
int error;
unsigned parse_finished : 1;
/* Authentication */
git_cred *cred;
git_cred *url_cred;
git_vector auth_contexts;
unsigned parse_finished : 1,
replay_count : 3;
} http_subtransport;
typedef struct {
......@@ -124,7 +140,7 @@ static bool challenge_match(git_http_auth_scheme *scheme, void *data)
static int auth_context_match(
git_http_auth_context **out,
http_subtransport *t,
http_server *server,
bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
void *data)
{
......@@ -145,7 +161,7 @@ static int auth_context_match(
return 0;
/* See if authentication has already started for this scheme */
git_vector_foreach(&t->auth_contexts, i, c) {
git_vector_foreach(&server->auth_contexts, i, c) {
if (c->type == scheme->type) {
context = c;
break;
......@@ -153,11 +169,11 @@ static int auth_context_match(
}
if (!context) {
if (scheme->init_context(&context, &t->connection_data) < 0)
if (scheme->init_context(&context, &server->url) < 0)
return -1;
else if (!context)
return 0;
else if (git_vector_insert(&t->auth_contexts, context) < 0)
else if (git_vector_insert(&server->auth_contexts, context) < 0)
return -1;
}
......@@ -166,32 +182,36 @@ static int auth_context_match(
return 0;
}
static int apply_credentials(git_buf *buf, http_subtransport *t)
static int apply_credentials(
git_buf *buf,
http_server *server,
const char *header_name)
{
git_cred *cred = t->cred;
git_cred *cred = server->cred;
git_http_auth_context *context;
/* Apply the credentials given to us in the URL */
if (!cred && t->connection_data.user && t->connection_data.pass) {
if (!t->url_cred &&
git_cred_userpass_plaintext_new(&t->url_cred,
t->connection_data.user, t->connection_data.pass) < 0)
if (!cred && server->url.user && server->url.pass) {
if (!server->url_cred &&
git_cred_userpass_plaintext_new(&server->url_cred,
server->url.user, server->url.pass) < 0)
return -1;
cred = t->url_cred;
cred = server->url_cred;
}
if (!cred)
return 0;
/* Get or create a context for the best scheme for this cred type */
if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
if (auth_context_match(&context, server,
credtype_match, &cred->credtype) < 0)
return -1;
if (!context)
return 0;
return context->next_token(buf, context, cred);
return context->next_token(buf, context, header_name, cred);
}
static int gen_request(
......@@ -200,17 +220,26 @@ static int gen_request(
size_t content_length)
{
http_subtransport *t = OWNING_SUBTRANSPORT(s);
const char *path = t->connection_data.path ? t->connection_data.path : "/";
const char *path = t->server.url.path ? t->server.url.path : "/";
size_t i;
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED)
git_buf_printf(buf, "%s %s://%s:%s%s%s HTTP/1.1\r\n",
s->verb,
t->server.url.use_ssl ? "https" : "http",
t->server.url.host,
t->server.url.port,
path, s->service_url);
else
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n",
s->verb, path, s->service_url);
git_buf_puts(buf, "User-Agent: ");
git_http__user_agent(buf);
git_buf_puts(buf, "\r\n");
git_buf_printf(buf, "Host: %s", t->connection_data.host);
if (strcmp(t->connection_data.port, gitno__default_port(&t->connection_data)) != 0) {
git_buf_printf(buf, ":%s", t->connection_data.port);
git_buf_printf(buf, "Host: %s", t->server.url.host);
if (strcmp(t->server.url.port, gitno__default_port(&t->server.url)) != 0) {
git_buf_printf(buf, ":%s", t->server.url.port);
}
git_buf_puts(buf, "\r\n");
......@@ -230,8 +259,12 @@ static int gen_request(
git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]);
}
/* Apply credentials to the request */
if (apply_credentials(buf, t) < 0)
/* Apply proxy and server credentials to the request */
if (t->proxy_opts.type != GIT_PROXY_NONE &&
apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0)
return -1;
if (apply_credentials(buf, &t->server, AUTH_HEADER_SERVER) < 0)
return -1;
git_buf_puts(buf, "\r\n");
......@@ -243,16 +276,16 @@ static int gen_request(
}
static int parse_authenticate_response(
git_vector *www_authenticate,
http_subtransport *t,
http_server *server,
int *allowed_types)
{
git_http_auth_context *context;
char *challenge;
size_t i;
git_vector_foreach(www_authenticate, i, challenge) {
if (auth_context_match(&context, t, challenge_match, challenge) < 0)
git_vector_foreach(&server->auth_challenges, i, challenge) {
if (auth_context_match(&context, server,
challenge_match, challenge) < 0)
return -1;
else if (!context)
continue;
......@@ -273,22 +306,45 @@ static int on_header_ready(http_subtransport *t)
git_buf *value = &t->parse_header_value;
if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
if (!t->content_type) {
t->content_type = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->content_type);
if (t->content_type) {
giterr_set(GITERR_NET, "multiple Content-Type headers");
return -1;
}
t->content_type = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->content_type);
}
else if (!strcasecmp("Content-Length", git_buf_cstr(name))) {
if (t->content_length) {
giterr_set(GITERR_NET, "multiple Content-Length headers");
return -1;
}
t->content_length = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->content_length);
}
else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) {
char *dup = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(dup);
if (git_vector_insert(&t->proxy.auth_challenges, dup) < 0)
return -1;
}
else if (!strcasecmp("WWW-Authenticate", git_buf_cstr(name))) {
char *dup = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(dup);
git_vector_insert(&t->www_authenticate, dup);
if (git_vector_insert(&t->server.auth_challenges, dup) < 0)
return -1;
}
else if (!strcasecmp("Location", git_buf_cstr(name))) {
if (!t->location) {
t->location = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->location);
if (t->location) {
giterr_set(GITERR_NET, "multiple Location headers");
return -1;
}
t->location = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->location);
}
return 0;
......@@ -332,13 +388,78 @@ static int on_header_value(http_parser *parser, const char *str, size_t len)
return 0;
}
GIT_INLINE(void) free_cred(git_cred **cred)
{
if (*cred) {
git_cred_free(*cred);
(*cred) = NULL;
}
}
static int on_auth_required(
git_cred **creds,
http_parser *parser,
const char *url,
const char *type,
git_cred_acquire_cb callback,
void *callback_payload,
const char *username,
int allowed_types)
{
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
int ret;
if (!allowed_types) {
giterr_set(GITERR_NET, "%s requested authentication but did not negotiate mechanisms", type);
t->parse_error = PARSE_ERROR_GENERIC;
return t->parse_error;
}
if (callback) {
free_cred(creds);
ret = callback(creds, url, username, allowed_types, callback_payload);
if (ret == GIT_PASSTHROUGH) {
/* treat GIT_PASSTHROUGH as if callback isn't set */
} else if (ret < 0) {
t->error = ret;
t->parse_error = PARSE_ERROR_EXT;
return t->parse_error;
} else {
assert(*creds);
if (!((*creds)->credtype & allowed_types)) {
giterr_set(GITERR_NET, "%s credential provider returned an invalid cred type", type);
t->parse_error = PARSE_ERROR_GENERIC;
return t->parse_error;
}
/* Successfully acquired a credential. */
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
}
giterr_set(GITERR_NET, "%s authentication required but no callback set",
type);
t->parse_error = PARSE_ERROR_GENERIC;
return t->parse_error;
}
static int on_headers_complete(http_parser *parser)
{
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
int error = 0, no_callback = 0, allowed_auth_types = 0;
int proxy_auth_types = 0, server_auth_types = 0;
/* Enforce a reasonable cap on the number of replays */
if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
giterr_set(GITERR_NET, "too many redirects or authentication replays");
return t->parse_error = PARSE_ERROR_GENERIC;
}
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
......@@ -346,57 +467,36 @@ static int on_headers_complete(http_parser *parser)
if (on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Capture authentication headers which may be a 401 (authentication
* is not complete) or a 200 (simply informing us that auth *is*
* complete.)
/*
* Capture authentication headers for the proxy or final endpoint,
* these may be 407/401 (authentication is not complete) or a 200
* (informing us that auth has completed).
*/
if (parse_authenticate_response(&t->www_authenticate, t,
&allowed_auth_types) < 0)
if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0 ||
parse_authenticate_response(&t->server, &server_auth_types) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Check for an authentication failure. */
if (parser->status_code == 401 && get_verb == s->verb) {
if (!t->owner->cred_acquire_cb) {
no_callback = 1;
} else {
if (allowed_auth_types) {
if (t->cred) {
t->cred->free(t->cred);
t->cred = NULL;
}
error = t->owner->cred_acquire_cb(&t->cred,
t->owner->url,
t->connection_data.user,
allowed_auth_types,
t->owner->cred_acquire_payload);
/* treat GIT_PASSTHROUGH as if callback isn't set */
if (error == GIT_PASSTHROUGH) {
no_callback = 1;
} else if (error < 0) {
t->error = error;
return t->parse_error = PARSE_ERROR_EXT;
} else {
assert(t->cred);
if (!(t->cred->credtype & allowed_auth_types)) {
giterr_set(GITERR_NET, "credentials callback returned an invalid cred type");
return t->parse_error = PARSE_ERROR_GENERIC;
}
/* Successfully acquired a credential. */
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
}
}
/* Check for a proxy authentication failure. */
if (parser->status_code == 407 && get_verb == s->verb)
return on_auth_required(&t->proxy.cred,
parser,
t->proxy_opts.url,
SERVER_TYPE_PROXY,
t->proxy_opts.credentials,
t->proxy_opts.payload,
t->proxy.url.user,
proxy_auth_types);
if (no_callback) {
giterr_set(GITERR_NET, "authentication required but no callback set");
return t->parse_error = PARSE_ERROR_GENERIC;
}
}
/* Check for an authentication failure. */
if (parser->status_code == 401 && get_verb == s->verb)
return on_auth_required(&t->server.cred,
parser,
t->owner->url,
SERVER_TYPE_REMOTE,
t->owner->cred_acquire_cb,
t->owner->cred_acquire_payload,
t->server.url.user,
server_auth_types);
/* Check for a redirect.
* Right now we only permit a redirect to the same hostname. */
......@@ -407,12 +507,7 @@ static int on_headers_complete(http_parser *parser)
parser->status_code == 308) &&
t->location) {
if (s->redirect_count >= 7) {
giterr_set(GITERR_NET, "too many redirects");
return t->parse_error = PARSE_ERROR_GENERIC;
}
if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
if (gitno_connection_data_from_url(&t->server.url, t->location, s->service_url) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Set the redirect URL on the stream. This is a transfer of
......@@ -424,8 +519,6 @@ static int on_headers_complete(http_parser *parser)
t->location = NULL;
t->connected = 0;
s->redirect_count++;
t->parse_error = PARSE_ERROR_REPLAY;
return 0;
}
......@@ -492,15 +585,19 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
if (t->parse_error == PARSE_ERROR_REPLAY)
return 0;
if (ctx->buf_size < len) {
giterr_set(GITERR_NET, "can't fit data in the buffer");
return t->parse_error = PARSE_ERROR_GENERIC;
/* If there's no buffer set, we're explicitly ignoring the body. */
if (ctx->buffer) {
if (ctx->buf_size < len) {
giterr_set(GITERR_NET, "can't fit data in the buffer");
return t->parse_error = PARSE_ERROR_GENERIC;
}
memcpy(ctx->buffer, str, len);
ctx->buffer += len;
ctx->buf_size -= len;
}
memcpy(ctx->buffer, str, len);
*(ctx->bytes_read) += len;
ctx->buffer += len;
ctx->buf_size -= len;
return 0;
}
......@@ -508,7 +605,7 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
static void clear_parser_state(http_subtransport *t)
{
http_parser_init(&t->parser, HTTP_RESPONSE);
gitno_buffer_setup_fromstream(t->io,
gitno_buffer_setup_fromstream(t->server.stream,
&t->parse_buffer,
t->parse_buffer_data,
sizeof(t->parse_buffer_data));
......@@ -526,10 +623,14 @@ static void clear_parser_state(http_subtransport *t)
git__free(t->content_type);
t->content_type = NULL;
git__free(t->content_length);
t->content_length = NULL;
git__free(t->location);
t->location = NULL;
git_vector_free_deep(&t->www_authenticate);
git_vector_free_deep(&t->proxy.auth_challenges);
git_vector_free_deep(&t->server.auth_challenges);
}
static int write_chunk(git_stream *io, const char *buffer, size_t len)
......@@ -560,102 +661,350 @@ static int write_chunk(git_stream *io, const char *buffer, size_t len)
return 0;
}
static int apply_proxy_config(http_subtransport *t)
static int load_proxy_config(http_subtransport *t)
{
int error;
git_proxy_t proxy_type;
if (!git_stream_supports_proxy(t->io))
switch (t->owner->proxy.type) {
case GIT_PROXY_NONE:
return 0;
proxy_type = t->owner->proxy.type;
if (proxy_type == GIT_PROXY_NONE)
return 0;
case GIT_PROXY_AUTO:
git__free(t->proxy_url);
t->proxy_url = NULL;
if (proxy_type == GIT_PROXY_AUTO) {
char *url;
git_proxy_options opts = GIT_PROXY_OPTIONS_INIT;
git_proxy_init_options(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION);
if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0)
if ((error = git_remote__get_http_proxy(t->owner->owner,
!!t->server.url.use_ssl, &t->proxy_url)) < 0)
return error;
opts.credentials = t->owner->proxy.credentials;
opts.certificate_check = t->owner->proxy.certificate_check;
opts.payload = t->owner->proxy.payload;
opts.type = GIT_PROXY_SPECIFIED;
opts.url = url;
error = git_stream_set_proxy(t->io, &opts);
git__free(url);
t->proxy_opts.type = GIT_PROXY_SPECIFIED;
t->proxy_opts.url = t->proxy_url;
t->proxy_opts.credentials = t->owner->proxy.credentials;
t->proxy_opts.certificate_check = t->owner->proxy.certificate_check;
t->proxy_opts.payload = t->owner->proxy.payload;
break;
case GIT_PROXY_SPECIFIED:
memcpy(&t->proxy_opts, &t->owner->proxy, sizeof(git_proxy_options));
break;
default:
assert(0);
return -1;
}
if ((error = gitno_connection_data_from_url(&t->proxy.url, t->proxy_opts.url, NULL)) < 0)
return error;
if (t->proxy.url.use_ssl) {
giterr_set(GITERR_NET, "SSL connections to proxy are not supported");
return -1;
}
return git_stream_set_proxy(t->io, &t->owner->proxy);
return error;
}
static int http_connect(http_subtransport *t)
static int check_certificate(
git_stream *stream,
gitno_connection_data *url,
int is_valid,
git_transport_certificate_check_cb cert_cb,
void *cert_cb_payload)
{
git_cert *cert;
git_error_state last_error = {0};
int error;
if (t->connected &&
http_should_keep_alive(&t->parser) &&
t->parse_finished)
return 0;
if ((error = git_stream_certificate(&cert, stream)) < 0)
return error;
if (t->io) {
git_stream_close(t->io);
git_stream_free(t->io);
t->io = NULL;
t->connected = 0;
giterr_state_capture(&last_error, GIT_ECERTIFICATE);
error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
if (error == GIT_PASSTHROUGH && !is_valid)
return giterr_state_restore(&last_error);
else if (error == GIT_PASSTHROUGH)
error = 0;
else if (error && !giterr_last())
giterr_set(GITERR_NET, "user rejected certificate for %s", url->host);
giterr_state_free(&last_error);
return error;
}
static int stream_connect(
git_stream *stream,
gitno_connection_data *url,
git_transport_certificate_check_cb cert_cb,
void *cb_payload)
{
int error;
GITERR_CHECK_VERSION(stream, GIT_STREAM_VERSION, "git_stream");
error = git_stream_connect(stream);
if (error && error != GIT_ECERTIFICATE)
return error;
if (git_stream_is_encrypted(stream) && cert_cb != NULL)
error = check_certificate(stream, url, !error, cert_cb, cb_payload);
return error;
}
static int gen_connect_req(git_buf *buf, http_subtransport *t)
{
git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n",
t->server.url.host, t->server.url.port);
git_buf_puts(buf, "User-Agent: ");
git_http__user_agent(buf);
git_buf_puts(buf, "\r\n");
git_buf_printf(buf, "Host: %s\r\n", t->proxy.url.host);
if (apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0)
return -1;
git_buf_puts(buf, "\r\n");
return git_buf_oom(buf) ? -1 : 0;
}
static int proxy_headers_complete(http_parser *parser)
{
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
int proxy_auth_types = 0;
/* Enforce a reasonable cap on the number of replays */
if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
giterr_set(GITERR_NET, "too many redirects or authentication replays");
return t->parse_error = PARSE_ERROR_GENERIC;
}
if (t->connection_data.use_ssl) {
error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
} else {
#ifdef GIT_CURL
error = git_curl_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
#else
error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
#endif
/* 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;
/*
* Capture authentication headers for the proxy or final endpoint,
* these may be 407/401 (authentication is not complete) or a 200
* (informing us that auth has completed).
*/
if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Check for a proxy authentication failure. */
if (parser->status_code == 407)
return on_auth_required(&t->proxy.cred,
parser,
t->proxy_opts.url,
SERVER_TYPE_PROXY,
t->proxy_opts.credentials,
t->proxy_opts.payload,
t->proxy.url.user,
proxy_auth_types);
if (parser->status_code != 200) {
giterr_set(GITERR_NET, "unexpected status code from proxy: %d",
parser->status_code);
return t->parse_error = PARSE_ERROR_GENERIC;
}
if (error < 0)
return error;
if (!t->content_length || strcmp(t->content_length, "0") == 0)
t->parse_finished = 1;
return 0;
}
static int proxy_connect(
git_stream **out, git_stream *proxy_stream, http_subtransport *t)
{
git_buf request = GIT_BUF_INIT;
static http_parser_settings proxy_parser_settings = {0};
size_t bytes_read = 0, bytes_parsed;
parser_context ctx;
int error;
GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
/* Use the parser settings only to parser headers. */
proxy_parser_settings.on_header_field = on_header_field;
proxy_parser_settings.on_header_value = on_header_value;
proxy_parser_settings.on_headers_complete = proxy_headers_complete;
proxy_parser_settings.on_message_complete = on_message_complete;
apply_proxy_config(t);
replay:
clear_parser_state(t);
error = git_stream_connect(t->io);
gitno_buffer_setup_fromstream(proxy_stream,
&t->parse_buffer,
t->parse_buffer_data,
sizeof(t->parse_buffer_data));
if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
git_stream_is_encrypted(t->io)) {
git_cert *cert;
int is_valid = (error == GIT_OK);
if ((error = gen_connect_req(&request, t)) < 0)
goto done;
if ((error = git_stream_certificate(&cert, t->io)) < 0)
return error;
if ((error = git_stream_write(proxy_stream,
request.ptr, request.size, 0)) < 0)
goto done;
giterr_clear();
error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
git_buf_dispose(&request);
if (error == GIT_PASSTHROUGH)
error = is_valid ? 0 : GIT_ECERTIFICATE;
while (!bytes_read && !t->parse_finished) {
t->parse_buffer.offset = 0;
if (error < 0) {
if (!giterr_last())
giterr_set(GITERR_NET, "user cancelled certificate check");
if ((error = gitno_recv(&t->parse_buffer)) < 0)
goto done;
return error;
/*
* This call to http_parser_execute will invoke the on_*
* callbacks. Since we don't care about the body of the response,
* we can set our buffer to NULL.
*/
ctx.t = t;
ctx.s = NULL;
ctx.buffer = NULL;
ctx.buf_size = 0;
ctx.bytes_read = &bytes_read;
/* Set the context, call the parser, then unset the context. */
t->parser.data = &ctx;
bytes_parsed = http_parser_execute(&t->parser,
&proxy_parser_settings, t->parse_buffer.data, t->parse_buffer.offset);
t->parser.data = NULL;
/* Ensure that we didn't get a redirect; unsupported. */
if (t->location) {
giterr_set(GITERR_NET, "proxy server sent unsupported redirect during CONNECT");
error = -1;
goto done;
}
/* Replay the request with authentication headers. */
if (PARSE_ERROR_REPLAY == t->parse_error)
goto replay;
if (t->parse_error < 0) {
error = t->parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1;
goto done;
}
if (bytes_parsed != t->parse_buffer.offset) {
giterr_set(GITERR_NET,
"HTTP parser error: %s",
http_errno_description((enum http_errno)t->parser.http_errno));
error = -1;
goto done;
}
}
if (error < 0)
if ((error = git_tls_stream_wrap(out, proxy_stream, t->server.url.host)) == 0)
error = stream_connect(*out, &t->server.url,
t->owner->certificate_check_cb,
t->owner->message_cb_payload);
/*
* Since we've connected via a HTTPS proxy tunnel, we don't behave
* as if we have an HTTP proxy.
*/
t->proxy_opts.type = GIT_PROXY_NONE;
t->replay_count = 0;
done:
return error;
}
static int http_connect(http_subtransport *t)
{
gitno_connection_data *url;
git_stream *proxy_stream = NULL, *stream = NULL;
git_transport_certificate_check_cb cert_cb;
void *cb_payload;
int error;
if (t->connected &&
http_should_keep_alive(&t->parser) &&
t->parse_finished)
return 0;
if ((error = load_proxy_config(t)) < 0)
return error;
if (t->server.stream) {
git_stream_close(t->server.stream);
git_stream_free(t->server.stream);
t->server.stream = NULL;
}
if (t->proxy.stream) {
git_stream_close(t->proxy.stream);
git_stream_free(t->proxy.stream);
t->proxy.stream = NULL;
}
t->connected = 0;
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) {
url = &t->proxy.url;
cert_cb = t->proxy_opts.certificate_check;
cb_payload = t->proxy_opts.payload;
} else {
url = &t->server.url;
cert_cb = t->owner->certificate_check_cb;
cb_payload = t->owner->message_cb_payload;
}
if (url->use_ssl)
error = git_tls_stream_new(&stream, url->host, url->port);
else
error = git_socket_stream_new(&stream, url->host, url->port);
if (error < 0)
goto on_error;
if ((error = stream_connect(stream, url, cert_cb, cb_payload)) < 0)
goto on_error;
/*
* At this point we have a connection to the remote server or to
* a proxy. If it's a proxy and the remote server is actually
* an HTTPS connection, then we need to build a CONNECT tunnel.
*/
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED &&
t->server.url.use_ssl) {
proxy_stream = stream;
stream = NULL;
if ((error = proxy_connect(&stream, proxy_stream, t)) < 0)
goto on_error;
}
t->proxy.stream = proxy_stream;
t->server.stream = stream;
t->connected = 1;
t->replay_count = 0;
return 0;
on_error:
if (stream) {
git_stream_close(stream);
git_stream_free(stream);
}
if (proxy_stream) {
git_stream_close(proxy_stream);
git_stream_free(proxy_stream);
}
return error;
}
static int http_stream_read(
......@@ -682,7 +1031,8 @@ replay:
if (gen_request(&request, s, 0) < 0)
return -1;
if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
if (git_stream_write(t->server.stream,
request.ptr, request.size, 0) < 0) {
git_buf_dispose(&request);
return -1;
}
......@@ -698,13 +1048,14 @@ replay:
/* Flush, if necessary */
if (s->chunk_buffer_len > 0 &&
write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
write_chunk(t->server.stream,
s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
/* Write the final chunk. */
if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0)
if (git_stream_write(t->server.stream, "0\r\n\r\n", 5, 0) < 0)
return -1;
}
......@@ -803,7 +1154,8 @@ static int http_stream_write_chunked(
if (gen_request(&request, s, 0) < 0)
return -1;
if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
if (git_stream_write(t->server.stream,
request.ptr, request.size, 0) < 0) {
git_buf_dispose(&request);
return -1;
}
......@@ -816,14 +1168,15 @@ static int http_stream_write_chunked(
if (len > CHUNK_SIZE) {
/* Flush, if necessary */
if (s->chunk_buffer_len > 0) {
if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
if (write_chunk(t->server.stream,
s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
}
/* Write chunk directly */
if (write_chunk(t->io, buffer, len) < 0)
if (write_chunk(t->server.stream, buffer, len) < 0)
return -1;
}
else {
......@@ -840,7 +1193,8 @@ static int http_stream_write_chunked(
/* Is the buffer full? If so, then flush */
if (CHUNK_SIZE == s->chunk_buffer_len) {
if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
if (write_chunk(t->server.stream,
s->chunk_buffer, s->chunk_buffer_len) < 0)
return -1;
s->chunk_buffer_len = 0;
......@@ -876,10 +1230,11 @@ static int http_stream_write_single(
if (gen_request(&request, s, len) < 0)
return -1;
if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
if (git_stream_write(t->server.stream,
request.ptr, request.size, 0) < 0)
goto on_error;
if (len && git_stream_write(t->io, buffer, len, 0) < 0)
if (len && git_stream_write(t->server.stream, buffer, len, 0) < 0)
goto on_error;
git_buf_dispose(&request);
......@@ -1010,13 +1365,21 @@ static int http_action(
http_subtransport *t = (http_subtransport *)subtransport;
int ret;
if (!stream)
return -1;
assert(stream);
if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
(ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
/*
* If we've seen a redirect then preserve the location that we've
* been given. This is important to continue authorization against
* the redirect target, not the user-given source; the endpoint may
* have redirected us from HTTP->HTTPS and is using an auth mechanism
* that would be insecure in plaintext (eg, HTTP Basic).
*/
if ((!t->server.url.host || !t->server.url.port || !t->server.url.path) &&
(ret = gitno_connection_data_from_url(&t->server.url, url, NULL)) < 0)
return ret;
assert(t->server.url.host && t->server.url.port && t->server.url.path);
if ((ret = http_connect(t)) < 0)
return ret;
......@@ -1038,41 +1401,55 @@ static int http_action(
return -1;
}
static int http_close(git_smart_subtransport *subtransport)
static void free_auth_contexts(git_vector *contexts)
{
http_subtransport *t = (http_subtransport *) subtransport;
git_http_auth_context *context;
size_t i;
git_vector_foreach(contexts, i, context) {
if (context->free)
context->free(context);
}
git_vector_clear(contexts);
}
static int http_close(git_smart_subtransport *subtransport)
{
http_subtransport *t = (http_subtransport *) subtransport;
clear_parser_state(t);
t->connected = 0;
if (t->io) {
git_stream_close(t->io);
git_stream_free(t->io);
t->io = NULL;
if (t->server.stream) {
git_stream_close(t->server.stream);
git_stream_free(t->server.stream);
t->server.stream = NULL;
}
if (t->cred) {
t->cred->free(t->cred);
t->cred = NULL;
if (t->proxy.stream) {
git_stream_close(t->proxy.stream);
git_stream_free(t->proxy.stream);
t->proxy.stream = NULL;
}
if (t->url_cred) {
t->url_cred->free(t->url_cred);
t->url_cred = NULL;
}
free_cred(&t->server.cred);
free_cred(&t->server.url_cred);
free_cred(&t->proxy.cred);
free_cred(&t->proxy.url_cred);
git_vector_foreach(&t->auth_contexts, i, context) {
if (context->free)
context->free(context);
}
free_auth_contexts(&t->server.auth_contexts);
free_auth_contexts(&t->proxy.auth_contexts);
gitno_connection_data_free_ptrs(&t->server.url);
memset(&t->server.url, 0x0, sizeof(gitno_connection_data));
git_vector_clear(&t->auth_contexts);
gitno_connection_data_free_ptrs(&t->proxy.url);
memset(&t->proxy.url, 0x0, sizeof(gitno_connection_data));
gitno_connection_data_free_ptrs(&t->connection_data);
memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
git__free(t->proxy_url);
t->proxy_url = NULL;
return 0;
}
......@@ -1083,7 +1460,8 @@ static void http_free(git_smart_subtransport *subtransport)
http_close(subtransport);
git_vector_free(&t->auth_contexts);
git_vector_free(&t->server.auth_contexts);
git_vector_free(&t->proxy.auth_contexts);
git__free(t);
}
......
......@@ -10,6 +10,8 @@
#include "buffer.h"
#define GIT_HTTP_REPLAY_MAX 7
GIT_INLINE(int) git_http__user_agent(git_buf *buf)
{
const char *ua = git_libgit2__user_agent();
......
......@@ -932,7 +932,7 @@ static int winhttp_stream_read(
replay:
/* Enforce a reasonable cap on the number of replays */
if (++replay_count >= 7) {
if (replay_count++ >= GIT_HTTP_REPLAY_MAX) {
giterr_set(GITERR_NET, "too many redirects or authentication replays");
return -1;
}
......
#include "clar_libgit2.h"
#include "git2/sys/stream.h"
#include "streams/tls.h"
#include "streams/socket.h"
#include "stream.h"
static git_stream test_stream;
static int ctor_called;
static int test_ctor(git_stream **out, const char *host, const char *port)
void test_core_stream__cleanup(void)
{
cl_git_pass(git_stream_register(GIT_STREAM_TLS | GIT_STREAM_STANDARD, NULL));
}
static int test_stream_init(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(host);
GIT_UNUSED(port);
......@@ -17,20 +23,62 @@ static int test_ctor(git_stream **out, const char *host, const char *port)
return 0;
}
static int test_stream_wrap(git_stream **out, git_stream *in, const char *host)
{
GIT_UNUSED(in);
GIT_UNUSED(host);
ctor_called = 1;
*out = &test_stream;
return 0;
}
void test_core_stream__register_insecure(void)
{
git_stream *stream;
git_stream_registration registration = {0};
registration.version = 1;
registration.init = test_stream_init;
registration.wrap = test_stream_wrap;
ctor_called = 0;
cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, &registration));
cl_git_pass(git_socket_stream_new(&stream, "localhost", "80"));
cl_assert_equal_i(1, ctor_called);
cl_assert_equal_p(&test_stream, stream);
ctor_called = 0;
stream = NULL;
cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, NULL));
cl_git_pass(git_socket_stream_new(&stream, "localhost", "80"));
cl_assert_equal_i(0, ctor_called);
cl_assert(&test_stream != stream);
git_stream_free(stream);
}
void test_core_stream__register_tls(void)
{
git_stream *stream;
git_stream_registration registration = {0};
int error;
registration.version = 1;
registration.init = test_stream_init;
registration.wrap = test_stream_wrap;
ctor_called = 0;
cl_git_pass(git_stream_register_tls(test_ctor));
cl_git_pass(git_stream_register(GIT_STREAM_TLS, &registration));
cl_git_pass(git_tls_stream_new(&stream, "localhost", "443"));
cl_assert_equal_i(1, ctor_called);
cl_assert_equal_p(&test_stream, stream);
ctor_called = 0;
stream = NULL;
cl_git_pass(git_stream_register_tls(NULL));
cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL));
error = git_tls_stream_new(&stream, "localhost", "443");
/* We don't have TLS support enabled, or we're on Windows,
......@@ -47,3 +95,57 @@ void test_core_stream__register_tls(void)
git_stream_free(stream);
}
void test_core_stream__register_both(void)
{
git_stream *stream;
git_stream_registration registration = {0};
registration.version = 1;
registration.init = test_stream_init;
registration.wrap = test_stream_wrap;
cl_git_pass(git_stream_register(GIT_STREAM_STANDARD | GIT_STREAM_TLS, &registration));
ctor_called = 0;
cl_git_pass(git_tls_stream_new(&stream, "localhost", "443"));
cl_assert_equal_i(1, ctor_called);
cl_assert_equal_p(&test_stream, stream);
ctor_called = 0;
cl_git_pass(git_socket_stream_new(&stream, "localhost", "80"));
cl_assert_equal_i(1, ctor_called);
cl_assert_equal_p(&test_stream, stream);
}
void test_core_stream__register_tls_deprecated(void)
{
git_stream *stream;
int error;
ctor_called = 0;
cl_git_pass(git_stream_register_tls(test_stream_init));
cl_git_pass(git_tls_stream_new(&stream, "localhost", "443"));
cl_assert_equal_i(1, ctor_called);
cl_assert_equal_p(&test_stream, stream);
ctor_called = 0;
stream = NULL;
cl_git_pass(git_stream_register_tls(NULL));
error = git_tls_stream_new(&stream, "localhost", "443");
/*
* We don't have TLS support enabled, or we're on Windows,
* which has no arbitrary TLS stream support.
*/
#if defined(GIT_WIN32) || !defined(GIT_HTTPS)
cl_git_fail_with(-1, error);
#else
cl_git_pass(error);
#endif
cl_assert_equal_i(0, ctor_called);
cl_assert(&test_stream != stream);
git_stream_free(stream);
}
......@@ -20,18 +20,33 @@ static git_clone_options g_options;
static char *_remote_url = NULL;
static char *_remote_user = NULL;
static char *_remote_pass = NULL;
static char *_remote_sslnoverify = NULL;
static char *_remote_ssh_pubkey = NULL;
static char *_remote_ssh_privkey = NULL;
static char *_remote_ssh_passphrase = NULL;
static char *_remote_ssh_fingerprint = NULL;
static char *_remote_proxy_url = NULL;
static char *_remote_proxy_scheme = NULL;
static char *_remote_proxy_host = NULL;
static char *_remote_proxy_user = NULL;
static char *_remote_proxy_pass = NULL;
static char *_remote_proxy_selfsigned = NULL;
static int _orig_proxies_need_reset = 0;
static char *_orig_http_proxy = NULL;
static char *_orig_https_proxy = NULL;
static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(host);
GIT_UNUSED(payload);
if (_remote_sslnoverify != NULL)
valid = 1;
return valid ? 0 : GIT_ECERTIFICATE;
}
void test_online_clone__initialize(void)
{
git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT;
......@@ -44,17 +59,21 @@ void test_online_clone__initialize(void)
g_options.checkout_opts = dummy_opts;
g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
g_options.fetch_opts = dummy_fetch;
g_options.fetch_opts.callbacks.certificate_check = ssl_cert;
_remote_url = cl_getenv("GITTEST_REMOTE_URL");
_remote_user = cl_getenv("GITTEST_REMOTE_USER");
_remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
_remote_sslnoverify = cl_getenv("GITTEST_REMOTE_SSL_NOVERIFY");
_remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
_remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY");
_remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
_remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT");
_remote_proxy_url = cl_getenv("GITTEST_REMOTE_PROXY_URL");
_remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME");
_remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST");
_remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER");
_remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS");
_remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED");
_orig_proxies_need_reset = 0;
}
......@@ -70,13 +89,16 @@ void test_online_clone__cleanup(void)
git__free(_remote_url);
git__free(_remote_user);
git__free(_remote_pass);
git__free(_remote_sslnoverify);
git__free(_remote_ssh_pubkey);
git__free(_remote_ssh_privkey);
git__free(_remote_ssh_passphrase);
git__free(_remote_ssh_fingerprint);
git__free(_remote_proxy_url);
git__free(_remote_proxy_scheme);
git__free(_remote_proxy_host);
git__free(_remote_proxy_user);
git__free(_remote_proxy_pass);
git__free(_remote_proxy_selfsigned);
if (_orig_proxies_need_reset) {
cl_setenv("HTTP_PROXY", _orig_http_proxy);
......@@ -477,6 +499,7 @@ void test_online_clone__ssh_auth_methods(void)
#endif
g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods;
g_options.fetch_opts.callbacks.payload = &with_user;
g_options.fetch_opts.callbacks.certificate_check = NULL;
with_user = 0;
cl_git_fail_with(GIT_EUSER,
......@@ -529,6 +552,7 @@ void test_online_clone__ssh_with_paths(void)
g_options.fetch_opts.callbacks.transport = git_transport_ssh_with_paths;
g_options.fetch_opts.callbacks.credentials = cred_cb;
g_options.fetch_opts.callbacks.payload = &arr;
g_options.fetch_opts.callbacks.certificate_check = NULL;
cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options));
......@@ -713,7 +737,7 @@ void test_online_clone__start_with_http(void)
}
static int called_proxy_creds;
static int proxy_creds(git_cred **out, const char *url, const char *username, unsigned int allowed, void *payload)
static int proxy_cred_cb(git_cred **out, const char *url, const char *username, unsigned int allowed, void *payload)
{
GIT_UNUSED(url);
GIT_UNUSED(username);
......@@ -724,18 +748,45 @@ static int proxy_creds(git_cred **out, const char *url, const char *username, un
return git_cred_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass);
}
static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payload)
{
char *colon;
size_t host_len;
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
cl_assert(_remote_proxy_host);
if ((colon = strchr(_remote_proxy_host, ':')) != NULL)
host_len = (colon - _remote_proxy_host);
else
host_len = strlen(_remote_proxy_host);
if (_remote_proxy_selfsigned != NULL &&
strlen(host) == host_len &&
strncmp(_remote_proxy_host, host, host_len) == 0)
valid = 1;
return valid ? 0 : GIT_ECERTIFICATE;
}
void test_online_clone__proxy_credentials_request(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass)
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
cl_git_pass(git_buf_printf(&url, "http://%s/", _remote_proxy_url));
cl_git_pass(git_buf_printf(&url, "%s://%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_host));
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
g_options.fetch_opts.proxy_opts.url = url.ptr;
g_options.fetch_opts.proxy_opts.credentials = proxy_creds;
g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
called_proxy_creds = 0;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
cl_assert(called_proxy_creds);
......@@ -747,13 +798,16 @@ void test_online_clone__proxy_credentials_in_url(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass)
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
cl_git_pass(git_buf_printf(&url, "http://%s:%s@%s/", _remote_proxy_user, _remote_proxy_pass, _remote_proxy_url));
cl_git_pass(git_buf_printf(&url, "%s://%s:%s@%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
g_options.fetch_opts.proxy_opts.url = url.ptr;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
called_proxy_creds = 0;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
cl_assert(called_proxy_creds == 0);
......@@ -765,7 +819,7 @@ void test_online_clone__proxy_credentials_in_environment(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass)
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
_orig_http_proxy = cl_getenv("HTTP_PROXY");
......@@ -773,8 +827,11 @@ void test_online_clone__proxy_credentials_in_environment(void)
_orig_proxies_need_reset = 1;
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
cl_git_pass(git_buf_printf(&url, "http://%s:%s@%s/", _remote_proxy_user, _remote_proxy_pass, _remote_proxy_url));
cl_git_pass(git_buf_printf(&url, "%s://%s:%s@%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
cl_setenv("HTTP_PROXY", url.ptr);
cl_setenv("HTTPS_PROXY", url.ptr);
......
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