summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/curl_stream.c257
-rw-r--r--src/curl_stream.h14
-rw-r--r--src/openssl_stream.c122
-rw-r--r--src/stransport_stream.c18
-rw-r--r--src/stream.h15
-rw-r--r--src/transports/http.c18
6 files changed, 430 insertions, 14 deletions
diff --git a/src/curl_stream.c b/src/curl_stream.c
new file mode 100644
index 000000000..6534bdbbe
--- /dev/null
+++ b/src/curl_stream.c
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+
+#ifdef GIT_CURL
+
+#include <curl/curl.h>
+
+#include "stream.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "vector.h"
+
+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;
+} curl_stream;
+
+static int seterr_curl(curl_stream *s)
+{
+ giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
+ return -1;
+}
+
+static int curls_connect(git_stream *stream)
+{
+ curl_stream *s = (curl_stream *) stream;
+ long sockextr;
+ int failed_cert = 0;
+ CURLcode res;
+ res = curl_easy_perform(s->handle);
+
+ 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_LASTSOCKET, &sockextr)) != CURLE_OK)
+ return seterr_curl(s);
+
+ 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.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);
+ }
+
+ /* 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.cert_type = GIT_CERT_STRARRAY;
+ s->cert_info.data = &s->cert_info_strings;
+ s->cert_info.len = strings.length;
+
+ *out = (git_cert *) &s->cert_info;
+
+ return 0;
+}
+
+static int curls_set_proxy(git_stream *stream, const char *proxy_url)
+{
+ CURLcode res;
+ curl_stream *s = (curl_stream *) stream;
+
+ if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, proxy_url)) != 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);
+
+ 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__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");
+ return -1;
+ }
+
+ if ((error = git__strtol32(&iport, port, NULL, 10)) < 0)
+ 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_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_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/curl_stream.h b/src/curl_stream.h
new file mode 100644
index 000000000..283f0fe40
--- /dev/null
+++ b/src/curl_stream.h
@@ -0,0 +1,14 @@
+/*
+ * 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_curl_stream_h__
+#define INCLUDE_curl_stream_h__
+
+#include "git2/sys/stream.h"
+
+extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
+
+#endif
diff --git a/src/openssl_stream.c b/src/openssl_stream.c
index 9b2d5951c..412dee739 100644
--- a/src/openssl_stream.c
+++ b/src/openssl_stream.c
@@ -16,6 +16,10 @@
#include "netops.h"
#include "git2/transport.h"
+#ifdef GIT_CURL
+# include "curl_stream.h"
+#endif
+
#ifndef GIT_WIN32
# include <sys/types.h>
# include <sys/socket.h>
@@ -25,6 +29,79 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
+#include <openssl/bio.h>
+
+static int bio_create(BIO *b)
+{
+ b->init = 1;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+
+ return 1;
+}
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+ b->init = 0;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+
+ return 1;
+}
+
+static int bio_read(BIO *b, char *buf, int len)
+{
+ git_stream *io = (git_stream *) b->ptr;
+ return (int) git_stream_read(io, buf, len);
+}
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+ git_stream *io = (git_stream *) b->ptr;
+ return (int) git_stream_write(io, buf, len, 0);
+}
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+ GIT_UNUSED(b);
+ GIT_UNUSED(num);
+ GIT_UNUSED(ptr);
+
+ if (cmd == BIO_CTRL_FLUSH)
+ return 1;
+
+ return 0;
+}
+
+static int bio_gets(BIO *b, char *buf, int len)
+{
+ GIT_UNUSED(b);
+ GIT_UNUSED(buf);
+ GIT_UNUSED(len);
+ return -1;
+}
+
+static int bio_puts(BIO *b, const char *str)
+{
+ return bio_write(b, str, strlen(str));
+}
+
+static BIO_METHOD git_stream_bio_method = {
+ BIO_TYPE_SOURCE_SINK,
+ "git_stream",
+ bio_write,
+ bio_read,
+ bio_puts,
+ bio_gets,
+ bio_ctrl,
+ bio_create,
+ bio_destroy
+};
static int ssl_set_error(SSL *ssl, int error)
{
@@ -224,7 +301,8 @@ cert_fail_name:
typedef struct {
git_stream parent;
- git_socket_stream *socket;
+ git_stream *io;
+ char *host;
SSL *ssl;
git_cert_x509 cert_info;
} openssl_stream;
@@ -234,23 +312,24 @@ int openssl_close(git_stream *stream);
int openssl_connect(git_stream *stream)
{
int ret;
+ BIO *bio;
openssl_stream *st = (openssl_stream *) stream;
- if ((ret = git_stream_connect((git_stream *)st->socket)) < 0)
+ if ((ret = git_stream_connect(st->io)) < 0)
return ret;
- if ((ret = SSL_set_fd(st->ssl, st->socket->s)) <= 0) {
- openssl_close((git_stream *) st);
- return ssl_set_error(st->ssl, ret);
- }
+ bio = BIO_new(&git_stream_bio_method);
+ GITERR_CHECK_ALLOC(bio);
+ bio->ptr = st->io;
+ SSL_set_bio(st->ssl, bio, bio);
/* specify the host in case SNI is needed */
- SSL_set_tlsext_host_name(st->ssl, st->socket->host);
+ SSL_set_tlsext_host_name(st->ssl, st->host);
if ((ret = SSL_connect(st->ssl)) <= 0)
return ssl_set_error(st->ssl, ret);
- return verify_server_cert(st->ssl, st->socket->host);
+ return verify_server_cert(st->ssl, st->host);
}
int openssl_certificate(git_cert **out, git_stream *stream)
@@ -287,6 +366,13 @@ int openssl_certificate(git_cert **out, git_stream *stream)
return 0;
}
+static int openssl_set_proxy(git_stream *stream, const char *proxy_url)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy_url);
+}
+
ssize_t openssl_write(git_stream *stream, const char *data, size_t len, int flags)
{
openssl_stream *st = (openssl_stream *) stream;
@@ -320,7 +406,7 @@ int openssl_close(git_stream *stream)
if ((ret = ssl_teardown(st->ssl)) < 0)
return -1;
- return git_stream_close((git_stream *)st->socket);
+ return git_stream_close(st->io);
}
void openssl_free(git_stream *stream)
@@ -328,19 +414,26 @@ void openssl_free(git_stream *stream)
openssl_stream *st = (openssl_stream *) stream;
git__free(st->cert_info.data);
- git_stream_free((git_stream *) st->socket);
+ git_stream_free(st->io);
git__free(st);
}
int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
{
+ int error;
openssl_stream *st;
st = git__calloc(1, sizeof(openssl_stream));
GITERR_CHECK_ALLOC(st);
- if (git_socket_stream_new((git_stream **) &st->socket, host, port))
- return -1;
+#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)
+ return error;
st->ssl = SSL_new(git__ssl_ctx);
if (st->ssl == NULL) {
@@ -348,10 +441,15 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
return -1;
}
+ st->host = git__strdup(host);
+ GITERR_CHECK_ALLOC(st->host);
+
st->parent.version = GIT_STREAM_VERSION;
st->parent.encrypted = 1;
+ st->parent.proxy_support = git_stream_supports_proxy(st->io);
st->parent.connect = openssl_connect;
st->parent.certificate = openssl_certificate;
+ st->parent.set_proxy = openssl_set_proxy;
st->parent.read = openssl_read;
st->parent.write = openssl_write;
st->parent.close = openssl_close;
diff --git a/src/stransport_stream.c b/src/stransport_stream.c
index 34c38b22d..bbc2d32d6 100644
--- a/src/stransport_stream.c
+++ b/src/stransport_stream.c
@@ -14,6 +14,7 @@
#include "git2/transport.h"
#include "socket_stream.h"
+#include "curl_stream.h"
int stransport_error(OSStatus ret)
{
@@ -115,6 +116,13 @@ int stransport_certificate(git_cert **out, git_stream *stream)
return 0;
}
+int stransport_set_proxy(git_stream *stream, const char *proxy)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy);
+}
+
/*
* Contrary to typical network IO callbacks, Secure Transport write callback is
* expected to write *all* passed data, not just as much as it can, and any
@@ -233,7 +241,13 @@ int git_stransport_stream_new(git_stream **out, const char *host, const char *po
st = git__calloc(1, sizeof(stransport_stream));
GITERR_CHECK_ALLOC(st);
- if ((error = git_socket_stream_new(&st->io, host, port)) < 0){
+#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;
}
@@ -256,8 +270,10 @@ int git_stransport_stream_new(git_stream **out, const char *host, const char *po
st->parent.version = GIT_STREAM_VERSION;
st->parent.encrypted = 1;
+ st->parent.proxy_support = git_stream_supports_proxy(st->io);
st->parent.connect = stransport_connect;
st->parent.certificate = stransport_certificate;
+ st->parent.set_proxy = stransport_set_proxy;
st->parent.read = stransport_read;
st->parent.write = stransport_write;
st->parent.close = stransport_close;
diff --git a/src/stream.h b/src/stream.h
index d810e704d..43fcc3045 100644
--- a/src/stream.h
+++ b/src/stream.h
@@ -30,6 +30,21 @@ GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st)
return st->certificate(out, st);
}
+GIT_INLINE(int) git_stream_supports_proxy(git_stream *st)
+{
+ return st->proxy_support;
+}
+
+GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const char *proxy_url)
+{
+ if (!st->proxy_support) {
+ giterr_set(GITERR_INVALID, "proxy not supported on this stream");
+ return -1;
+ }
+
+ return st->set_proxy(st, proxy_url);
+}
+
GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len)
{
return st->read(st, data, len);
diff --git a/src/transports/http.c b/src/transports/http.c
index 4aca2755b..bae2a328d 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -10,11 +10,13 @@
#include "http_parser.h"
#include "buffer.h"
#include "netops.h"
+#include "remote.h"
#include "smart.h"
#include "auth.h"
#include "auth_negotiate.h"
#include "tls_stream.h"
#include "socket_stream.h"
+#include "curl_stream.h"
git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
@@ -532,6 +534,7 @@ static int write_chunk(git_stream *io, const char *buffer, size_t len)
static int http_connect(http_subtransport *t)
{
int error;
+ char *proxy_url;
if (t->connected &&
http_should_keep_alive(&t->parser) &&
@@ -547,7 +550,11 @@ static int http_connect(http_subtransport *t)
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
}
if (error < 0)
@@ -555,9 +562,18 @@ static int http_connect(http_subtransport *t)
GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
+ if (git_stream_supports_proxy(t->io) &&
+ !git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url)) {
+ error = git_stream_set_proxy(t->io, proxy_url);
+ git__free(proxy_url);
+
+ if (error < 0)
+ return error;
+ }
+
error = git_stream_connect(t->io);
-#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT)
+#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT) || defined(GIT_CURL)
if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
git_stream_is_encrypted(t->io)) {
git_cert *cert;