diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2018-11-28 20:31:30 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-28 20:31:30 +0000 |
commit | a904fc6d12ffc20aa1a238a78000921494222e89 (patch) | |
tree | c978c4a381618ff7d6c6cd665ed7bb703973d0f8 | |
parent | dcd00638a594b463b247530769f4883cd7d983b1 (diff) | |
parent | 30ac46aa469b06cd47efa9625c2bf4862f8494b7 (diff) | |
download | libgit2-a904fc6d12ffc20aa1a238a78000921494222e89.tar.gz |
Merge pull request #4870 from libgit2/ethomson/proxy
Add builtin proxy support for the http transport
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | ci/test.ps1 | 2 | ||||
-rwxr-xr-x | ci/test.sh | 4 | ||||
-rw-r--r-- | include/git2/sys/stream.h | 81 | ||||
-rw-r--r-- | src/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/features.h.in | 1 | ||||
-rw-r--r-- | src/global.c | 4 | ||||
-rw-r--r-- | src/streams/curl.c | 385 | ||||
-rw-r--r-- | src/streams/curl.h | 17 | ||||
-rw-r--r-- | src/streams/mbedtls.c | 80 | ||||
-rw-r--r-- | src/streams/mbedtls.h | 7 | ||||
-rw-r--r-- | src/streams/openssl.c | 79 | ||||
-rw-r--r-- | src/streams/openssl.h | 7 | ||||
-rw-r--r-- | src/streams/registry.c | 116 | ||||
-rw-r--r-- | src/streams/registry.h | 19 | ||||
-rw-r--r-- | src/streams/socket.c | 34 | ||||
-rw-r--r-- | src/streams/stransport.c | 63 | ||||
-rw-r--r-- | src/streams/stransport.h | 5 | ||||
-rw-r--r-- | src/streams/tls.c | 68 | ||||
-rw-r--r-- | src/streams/tls.h | 16 | ||||
-rw-r--r-- | src/transports/auth.c | 7 | ||||
-rw-r--r-- | src/transports/auth.h | 2 | ||||
-rw-r--r-- | src/transports/auth_negotiate.c | 3 | ||||
-rw-r--r-- | src/transports/http.c | 774 | ||||
-rw-r--r-- | src/transports/http.h | 2 | ||||
-rw-r--r-- | src/transports/winhttp.c | 2 | ||||
-rw-r--r-- | tests/core/stream.c | 108 | ||||
-rw-r--r-- | tests/online/clone.c | 79 |
28 files changed, 1209 insertions, 769 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b8aa16334..513dd7518 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/ci/test.ps1 b/ci/test.ps1 index fdfa1fec7..ed09633d9 100644 --- a/ci/test.ps1 +++ b/ci/test.ps1 @@ -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" diff --git a/ci/test.sh b/ci/test.sh index 10c4d84b5..f3bf19012 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -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 diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index eeeb68dae..938793124 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8ba3aa590..5ad8b1447 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/features.h.in b/src/features.h.in index f414c5843..694a61c02 100644 --- a/src/features.h.in +++ b/src/features.h.in @@ -22,7 +22,6 @@ #cmakedefine GIT_GSSAPI 1 #cmakedefine GIT_WINHTTP 1 -#cmakedefine GIT_CURL 1 #cmakedefine GIT_HTTPS 1 #cmakedefine GIT_OPENSSL 1 diff --git a/src/global.c b/src/global.c index d33772e91..86a35a2ff 100644 --- a/src/global.c +++ b/src/global.c @@ -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(); diff --git a/src/streams/curl.c b/src/streams/curl.c deleted file mode 100644 index 3c0af3b04..000000000 --- a/src/streams/curl.c +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 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 diff --git a/src/streams/curl.h b/src/streams/curl.h deleted file mode 100644 index 511cd894a..000000000 --- a/src/streams/curl.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ -#ifndef INCLUDE_streams_curl_h__ -#define INCLUDE_streams_curl_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); - -#endif diff --git a/src/streams/mbedtls.c b/src/streams/mbedtls.c index bd7bd3126..d22f77069 100644 --- a/src/streams/mbedtls.c +++ b/src/streams/mbedtls.c @@ -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 diff --git a/src/streams/mbedtls.h b/src/streams/mbedtls.h index 7283698ff..7de94b9fb 100644 --- a/src/streams/mbedtls.h +++ b/src/streams/mbedtls.h @@ -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 diff --git a/src/streams/openssl.c b/src/streams/openssl.c index a68f9a9cc..bc129217d 100644 --- a/src/streams/openssl.c +++ b/src/streams/openssl.c @@ -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 diff --git a/src/streams/openssl.h b/src/streams/openssl.h index 496d7efbf..826d1efbc 100644 --- a/src/streams/openssl.h +++ b/src/streams/openssl.h @@ -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 diff --git a/src/streams/registry.c b/src/streams/registry.c new file mode 100644 index 000000000..02fd3491b --- /dev/null +++ b/src/streams/registry.c @@ -0,0 +1,116 @@ +/* + * 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, ®istration); + } else { + return git_stream_register(GIT_STREAM_TLS, NULL); + } +} diff --git a/src/streams/registry.h b/src/streams/registry.h new file mode 100644 index 000000000..adc2b8bdf --- /dev/null +++ b/src/streams/registry.h @@ -0,0 +1,19 @@ +/* + * 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. + */ +#ifndef INCLUDE_streams_registry_h__ +#define INCLUDE_streams_registry_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +/** 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 diff --git a/src/streams/socket.c b/src/streams/socket.c index 0c6073b66..732b45940 100644 --- a/src/streams/socket.c +++ b/src/streams/socket.c @@ -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); +} diff --git a/src/streams/stransport.c b/src/streams/stransport.c index 15b305745..6626e0f68 100644 --- a/src/streams/stransport.c +++ b/src/streams/stransport.c @@ -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 diff --git a/src/streams/stransport.h b/src/streams/stransport.h index 4c02d07e8..1026e204b 100644 --- a/src/streams/stransport.h +++ b/src/streams/stransport.h @@ -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 diff --git a/src/streams/tls.c b/src/streams/tls.c index 1bcb0d984..0ea47fb2f 100644 --- a/src/streams/tls.c +++ b/src/streams/tls.c @@ -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); } diff --git a/src/streams/tls.h b/src/streams/tls.h index 6d110e8ad..465a6ea89 100644 --- a/src/streams/tls.h +++ b/src/streams/tls.h @@ -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 diff --git a/src/transports/auth.c b/src/transports/auth.c index c8e6adb12..dcc3e09a7 100644 --- a/src/transports/auth.c +++ b/src/transports/auth.c @@ -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; diff --git a/src/transports/auth.h b/src/transports/auth.h index 3b8b29eb9..e5cf7eff0 100644 --- a/src/transports/auth.h +++ b/src/transports/auth.h @@ -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); diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c index eeabe8a6d..173ae992e 100644 --- a/src/transports/auth_negotiate.c +++ b/src/transports/auth_negotiate.c @@ -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"); diff --git a/src/transports/http.c b/src/transports/http.c index 7f9d35012..3a0d2cc02 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -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); } diff --git a/src/transports/http.h b/src/transports/http.h index 6c4ecc9a4..b09475755 100644 --- a/src/transports/http.h +++ b/src/transports/http.h @@ -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(); diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 5e7bde73c..30e2ecb73 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -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; } diff --git a/tests/core/stream.c b/tests/core/stream.c index 262888b10..f15dce3cd 100644 --- a/tests/core/stream.c +++ b/tests/core/stream.c @@ -1,12 +1,18 @@ #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, ®istration)); + 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, ®istration)); 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, ®istration)); + + 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); +} diff --git a/tests/online/clone.c b/tests/online/clone.c index 09eff4823..ce49f180f 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -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); |