From dd4ff2c9b53dc9c8ba623477ead3e05f9baf73a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Nov 2014 12:35:54 +0100 Subject: [PATCH 1/8] Introduce stackable IO streams We currently have gitno for talking over TCP, but this needs to know about both plaintext and OpenSSL connections and the code has gotten somewhat messy with ifdefs determining which version of the function should be called. In order to clean this up and abstract away the details of sending over the different types of streams, we can instead use an interface and stack stream implementations. We may not be able to use the stackability with all streams, but we are definitely be able to use the abstraction which is currently spread between different bits of gitno. --- include/git2/sys/stream.h | 40 +++++++ src/socket_stream.c | 219 ++++++++++++++++++++++++++++++++++++++ src/stream.h | 48 +++++++++ 3 files changed, 307 insertions(+) create mode 100644 include/git2/sys/stream.h create mode 100644 src/socket_stream.c create mode 100644 src/stream.h diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h new file mode 100644 index 000000000..69f8554da --- /dev/null +++ b/include/git2/sys/stream.h @@ -0,0 +1,40 @@ +/* + * 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_sys_git_stream_h__ +#define INCLUDE_sys_git_stream_h__ + +#include "git2/common.h" +#include "git2/types.h" + +GIT_BEGIN_DECL + +#define GIT_STREAM_VERSION 1 + +/** + * Every stream must have this struct as its first element, so the + * API can talk to it. You'd define your stream as + * + * struct my_stream { + * git_stream parent; + * ... + * } + * + * and fill the functions + */ +typedef struct git_stream { + int version; + + int encrypted; + int (*connect)(struct git_stream *); + int (*certificate)(git_cert **, struct git_stream *); + ssize_t (*read)(struct git_stream *, void *, size_t); + ssize_t (*write)(struct git_stream *, void *, size_t, int); + int (*close)(struct git_stream *); + void (*free)(struct git_stream *); +} git_stream; + +#endif diff --git a/src/socket_stream.c b/src/socket_stream.c new file mode 100644 index 000000000..c182d6d6c --- /dev/null +++ b/src/socket_stream.c @@ -0,0 +1,219 @@ +/* + * 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 "common.h" +#include "posix.h" +#include "stream.h" +#include "netops.h" + +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +# include +#else +# include +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#ifdef GIT_WIN32 +static void net_set_error(const char *str) +{ + int error = WSAGetLastError(); + char * win32_error = git_win32_get_error_message(error); + + if (win32_error) { + giterr_set(GITERR_NET, "%s: %s", str, win32_error); + git__free(win32_error); + } else { + giterr_set(GITERR_NET, str); + } +} +#else +static void net_set_error(const char *str) +{ + giterr_set(GITERR_NET, "%s: %s", str, strerror(errno)); +} +#endif + +static int close_socket(GIT_SOCKET s) +{ + if (s == INVALID_SOCKET) + return 0; + +#ifdef GIT_WIN32 + if (SOCKET_ERROR == closesocket(s)) + return -1; + + if (0 != WSACleanup()) { + giterr_set(GITERR_OS, "Winsock cleanup failed"); + return -1; + } + + return 0; +#else + return close(s); +#endif + +} + +typedef struct { + git_stream parent; + char *host; + char *port; + GIT_SOCKET s; +} socket_stream; + +int socket_connect(git_stream *stream) +{ + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + socket_stream *st = (socket_stream *) stream; + GIT_SOCKET s = INVALID_SOCKET; + int ret; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } + + if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { + WSACleanup(); + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } +#endif + + memset(&hints, 0x0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { + giterr_set(GITERR_NET, + + "Failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); + return -1; + } + + for (p = info; p != NULL; p = p->ai_next) { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + + if (s == INVALID_SOCKET) { + net_set_error("error creating socket"); + break; + } + + if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + break; + + /* If we can't connect, try the next one */ + close_socket(s); + s = INVALID_SOCKET; + } + + /* Oops, we couldn't connect to any address */ + if (s == INVALID_SOCKET && p == NULL) { + giterr_set(GITERR_OS, "Failed to connect to %s", st->host); + p_freeaddrinfo(info); + return -1; + } + + st->s = s; + p_freeaddrinfo(info); + return 0; +} + +ssize_t socket_write(git_stream *stream, void *data, size_t len, int flags) +{ + ssize_t ret; + size_t off = 0; + socket_stream *st = (socket_stream *) stream; + + while (off < len) { + errno = 0; + ret = p_send(st->s, data + off, len - off, flags); + if (ret < 0) { + net_set_error("Error sending data"); + return -1; + } + + off += ret; + } + + return off; +} + +ssize_t socket_read(git_stream *stream, void *data, size_t len) +{ + ssize_t ret; + socket_stream *st = (socket_stream *) stream; + + if ((ret = p_recv(st->s, data, len, 0)) < 0) + net_set_error("Error receiving socket data"); + + return ret; +} + +int socket_close(git_stream *stream) +{ + socket_stream *st = (socket_stream *) stream; + int error; + + error = close_socket(st->s); + st->s = INVALID_SOCKET; + + return error; +} + +void socket_free(git_stream *stream) +{ + socket_stream *st = (socket_stream *) stream; + + git__free(st->host); + git__free(st->port); + git__free(st); +} + +int git_socket_stream_new(git_stream **out, const char *host, const char *port) +{ + socket_stream *st; + + assert(out && host); + + st = git__calloc(1, sizeof(socket_stream)); + GITERR_CHECK_ALLOC(st); + + st->host = git__strdup(host); + GITERR_CHECK_ALLOC(st->host); + + if (port) { + st->port = git__strdup(port); + GITERR_CHECK_ALLOC(st->port); + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.connect = socket_connect; + st->parent.write = socket_write; + st->parent.read = socket_read; + st->parent.close = socket_close; + st->parent.free = socket_free; + st->s = INVALID_SOCKET; + + *out = (git_stream *) st; + return 0; +} diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 000000000..8eec83009 --- /dev/null +++ b/src/stream.h @@ -0,0 +1,48 @@ +/* + * 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_stream_h__ +#define INCLUDE_stream_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +GIT_INLINE(int) git_stream_connect(git_stream *st) +{ + return st->connect(st); +} + +GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) +{ + if (!st->encrypted) { + giterr_set(GITERR_INVALID, "an unencrypted stream does not have a certificate"); + return -1; + } + + return st->certificate(out, st); +} + +GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) +{ + return st->read(st, data, len); +} + +GIT_INLINE(ssize_t) git_stream_write(git_stream *st, void *data, size_t len, int flags) +{ + return st->write(st, data, len, flags); +} + +GIT_INLINE(int) git_stream_close(git_stream *st) +{ + return st->close(st); +} + +GIT_INLINE(void) git_stream_free(git_stream *st) +{ + return st->free(st); +} + +#endif From 468d7b11f9642f37a2c54e6fd6d539b223a103aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Nov 2014 15:19:54 +0100 Subject: [PATCH 2/8] Add an OpenSSL IO stream This unfortunately isn't as stackable as could be possible, as it hard-codes the socket stream. This is because the method of using a custom openssl BIO is not clear, and we do not need this for now. We can still bring this in if and as we need it. --- src/openssl_stream.c | 374 +++++++++++++++++++++++++++++++++++++++++++ src/openssl_stream.h | 14 ++ src/socket_stream.c | 24 ++- src/socket_stream.h | 21 +++ 4 files changed, 418 insertions(+), 15 deletions(-) create mode 100644 src/openssl_stream.c create mode 100644 src/openssl_stream.h create mode 100644 src/socket_stream.h diff --git a/src/openssl_stream.c b/src/openssl_stream.c new file mode 100644 index 000000000..4c8b02c8c --- /dev/null +++ b/src/openssl_stream.c @@ -0,0 +1,374 @@ +/* + * 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_SSL + +#include +#include +#include + +#include + +#include "global.h" +#include "posix.h" +#include "stream.h" +#include "socket_stream.h" +#include "git2/transport.h" + +static int ssl_set_error(SSL *ssl, int error) +{ + int err; + unsigned long e; + + err = SSL_get_error(ssl, error); + + assert(err != SSL_ERROR_WANT_READ); + assert(err != SSL_ERROR_WANT_WRITE); + + switch (err) { + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + giterr_set(GITERR_NET, "SSL error: connection failure\n"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + giterr_set(GITERR_NET, "SSL error: x509 error\n"); + break; + case SSL_ERROR_SYSCALL: + e = ERR_get_error(); + if (e > 0) { + giterr_set(GITERR_NET, "SSL error: %s", + ERR_error_string(e, NULL)); + break; + } else if (error < 0) { + giterr_set(GITERR_OS, "SSL error: syscall failure"); + break; + } + giterr_set(GITERR_NET, "SSL error: received early EOF"); + break; + case SSL_ERROR_SSL: + e = ERR_get_error(); + giterr_set(GITERR_NET, "SSL error: %s", + ERR_error_string(e, NULL)); + break; + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + default: + giterr_set(GITERR_NET, "SSL error: unknown error"); + break; + } + return -1; +} + +static int ssl_teardown(SSL *ssl) +{ + int ret; + + ret = SSL_shutdown(ssl); + if (ret < 0) + ret = ssl_set_error(ssl, ret); + else + ret = 0; + + SSL_free(ssl); + return ret; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (gitno__match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(SSL *ssl, const char *host) +{ + X509 *cert; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr; + int i = -1,j; + + if (SSL_get_verify_result(ssl) != X509_V_OK) { + giterr_set(GITERR_SSL, "The SSL certificate is invalid"); + return -1; + } + + /* Try to parse the host as an IP address to see if it is */ + if (p_inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if(p_inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(ssl); + if (!cert) { + giterr_set(GITERR_SSL, "the server did not provide a certificate"); + return -1; + } + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto cert_fail_name; + + if (matched == 1) + return 0; + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GITERR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_data(str), size); + peer_cn[size] = '\0'; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GITERR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail_name; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail_name; + + OPENSSL_free(peer_cn); + + return 0; + +on_error: + OPENSSL_free(peer_cn); + return ssl_set_error(ssl, 0); + +cert_fail_name: + OPENSSL_free(peer_cn); + giterr_set(GITERR_SSL, "hostname does not match certificate"); + return GIT_ECERTIFICATE; +} + +typedef struct { + git_stream parent; + git_socket_stream *socket; + SSL *ssl; + git_cert_x509 cert_info; +} openssl_stream; + +int openssl_close(git_stream *stream); + +int openssl_connect(git_stream *stream) +{ + int ret; + openssl_stream *st = (openssl_stream *) stream; + + if ((ret = git_stream_connect((git_stream *)st->socket)) < 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); + } + + if ((ret = SSL_connect(st->ssl)) <= 0) + return ssl_set_error(st->ssl, ret); + + return verify_server_cert(st->ssl, st->socket->host); +} + +int openssl_certificate(git_cert **out, git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + int len; + X509 *cert = SSL_get_peer_certificate(st->ssl); + unsigned char *guard, *encoded_cert; + + /* Retrieve the length of the certificate first */ + len = i2d_X509(cert, NULL); + if (len < 0) { + giterr_set(GITERR_NET, "failed to retrieve certificate information"); + return -1; + } + + encoded_cert = git__malloc(len); + GITERR_CHECK_ALLOC(encoded_cert); + /* i2d_X509 makes 'guard' point to just after the data */ + guard = encoded_cert; + + len = i2d_X509(cert, &guard); + if (len < 0) { + git__free(encoded_cert); + giterr_set(GITERR_NET, "failed to retrieve certificate information"); + return -1; + } + + st->cert_info.cert_type = GIT_CERT_X509; + st->cert_info.data = encoded_cert; + st->cert_info.len = len; + + *out = (git_cert *)&st->cert_info; + return 0; +} + +ssize_t openssl_write(git_stream *stream, void *data, size_t len, int flags) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + size_t off = 0; + + GIT_UNUSED(flags); + + while (off < len) { + ret = SSL_write(st->ssl, data + off, len - off); + if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) + return ssl_set_error(st->ssl, ret); + + off += ret; + } + + return off; +} + +ssize_t openssl_read(git_stream *stream, void *data, size_t len) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + + do { + ret = SSL_read(st->ssl, data, len); + } while (SSL_get_error(st->ssl, ret) == SSL_ERROR_WANT_READ); + + if (ret < 0) { + ssl_set_error(st->ssl, ret); + return -1; + } + + return ret; +} + +int openssl_close(git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + + if ((ret = ssl_teardown(st->ssl)) < 0) + return -1; + + return git_stream_close((git_stream *)st->socket); +} + +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__free(st); +} + +int git_openssl_stream_new(git_stream **out, const char *host, const char *port) +{ + 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; + + st->ssl = SSL_new(git__ssl_ctx); + if (st->ssl == NULL) { + giterr_set(GITERR_SSL, "failed to create ssl object"); + return -1; + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.connect = openssl_connect; + st->parent.certificate = openssl_certificate; + st->parent.read = openssl_read; + st->parent.write = openssl_write; + st->parent.close = openssl_close; + st->parent.free = openssl_free; + + *out = (git_stream *) st; + return 0; +} + +#else + +#include "stream.h" + +int git_openssl_stream_new(git_stream **out, const char *host, const char *port) +{ + giterr_set(GITERR_SSL, "openssl is not supported in this version"); + return -1; +} + +#endif diff --git a/src/openssl_stream.h b/src/openssl_stream.h new file mode 100644 index 000000000..9ca06489e --- /dev/null +++ b/src/openssl_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_openssl_stream_h__ +#define INCLUDE_openssl_stream_h__ + +#include "git2/sys/stream.h" + +extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); + +#endif diff --git a/src/socket_stream.c b/src/socket_stream.c index c182d6d6c..113b8f698 100644 --- a/src/socket_stream.c +++ b/src/socket_stream.c @@ -7,8 +7,9 @@ #include "common.h" #include "posix.h" -#include "stream.h" #include "netops.h" +#include "stream.h" +#include "socket_stream.h" #ifndef _WIN32 # include @@ -67,18 +68,11 @@ static int close_socket(GIT_SOCKET s) } -typedef struct { - git_stream parent; - char *host; - char *port; - GIT_SOCKET s; -} socket_stream; - int socket_connect(git_stream *stream) { struct addrinfo *info = NULL, *p; struct addrinfo hints; - socket_stream *st = (socket_stream *) stream; + git_socket_stream *st = (git_socket_stream *) stream; GIT_SOCKET s = INVALID_SOCKET; int ret; @@ -142,7 +136,7 @@ ssize_t socket_write(git_stream *stream, void *data, size_t len, int flags) { ssize_t ret; size_t off = 0; - socket_stream *st = (socket_stream *) stream; + git_socket_stream *st = (git_socket_stream *) stream; while (off < len) { errno = 0; @@ -161,7 +155,7 @@ ssize_t socket_write(git_stream *stream, void *data, size_t len, int flags) ssize_t socket_read(git_stream *stream, void *data, size_t len) { ssize_t ret; - socket_stream *st = (socket_stream *) stream; + git_socket_stream *st = (git_socket_stream *) stream; if ((ret = p_recv(st->s, data, len, 0)) < 0) net_set_error("Error receiving socket data"); @@ -171,7 +165,7 @@ ssize_t socket_read(git_stream *stream, void *data, size_t len) int socket_close(git_stream *stream) { - socket_stream *st = (socket_stream *) stream; + git_socket_stream *st = (git_socket_stream *) stream; int error; error = close_socket(st->s); @@ -182,7 +176,7 @@ int socket_close(git_stream *stream) void socket_free(git_stream *stream) { - socket_stream *st = (socket_stream *) stream; + git_socket_stream *st = (git_socket_stream *) stream; git__free(st->host); git__free(st->port); @@ -191,11 +185,11 @@ void socket_free(git_stream *stream) int git_socket_stream_new(git_stream **out, const char *host, const char *port) { - socket_stream *st; + git_socket_stream *st; assert(out && host); - st = git__calloc(1, sizeof(socket_stream)); + st = git__calloc(1, sizeof(git_socket_stream)); GITERR_CHECK_ALLOC(st); st->host = git__strdup(host); diff --git a/src/socket_stream.h b/src/socket_stream.h new file mode 100644 index 000000000..8e9949fcd --- /dev/null +++ b/src/socket_stream.h @@ -0,0 +1,21 @@ +/* + * 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_socket_stream_h__ +#define INCLUDE_socket_stream_h__ + +#include "netops.h" + +typedef struct { + git_stream parent; + char *host; + char *port; + GIT_SOCKET s; +} git_socket_stream; + +extern int git_socket_stream_new(git_stream **out, const char *host, const char *port); + +#endif From 02b4c1e2a426404ad7cad8e8a114f7f36bdb8b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Nov 2014 16:58:20 +0100 Subject: [PATCH 3/8] Port the TCP transport to the new stream API --- src/netops.c | 19 +++++++ src/netops.h | 3 + src/transports/git.c | 128 ++++++++++++++++++++++++------------------- 3 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/netops.c b/src/netops.c index 23e7e9d3c..ab135c46b 100644 --- a/src/netops.c +++ b/src/netops.c @@ -104,6 +104,16 @@ static int ssl_set_error(gitno_ssl *ssl, int error) int gitno_recv(gitno_buffer *buf) { + if (buf->io) { + int ret; + ret = git_stream_read(buf->io, buf->data + buf->offset, buf->len - buf->offset); + if (ret < 0) + return -1; + + buf->offset += ret; + return ret; + } + return buf->recv(buf); } @@ -168,6 +178,15 @@ void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, siz gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL); } +void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) +{ + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->io = st; +} + /* Consume up to ptr and move the rest of the buffer to the beginning */ void gitno_consume(gitno_buffer *buf, const char *ptr) { diff --git a/src/netops.h b/src/netops.h index 8ad915301..fee6d82da 100644 --- a/src/netops.h +++ b/src/netops.h @@ -9,6 +9,7 @@ #include "posix.h" #include "common.h" +#include "stream.h" #ifdef GIT_SSL # include @@ -32,6 +33,7 @@ typedef struct gitno_buffer { char *data; size_t len; size_t offset; + git_stream *io; gitno_socket *socket; int (*recv)(struct gitno_buffer *buffer); void *cb_data; @@ -57,6 +59,7 @@ enum { int gitno__match_host(const char *pattern, const char *host); void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len); +void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len); void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); int gitno_recv(gitno_buffer *buf); diff --git a/src/transports/git.c b/src/transports/git.c index e2690fe36..6f25736b1 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -9,6 +9,8 @@ #include "buffer.h" #include "netops.h" #include "git2/sys/transport.h" +#include "stream.h" +#include "socket_stream.h" #define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) @@ -18,16 +20,16 @@ static const char cmd_receivepack[] = "git-receive-pack"; typedef struct { git_smart_subtransport_stream parent; - gitno_socket socket; + git_stream *io; const char *cmd; char *url; unsigned sent_command : 1; -} git_stream; +} git_proto_stream; typedef struct { git_smart_subtransport parent; git_transport *owner; - git_stream *current_stream; + git_proto_stream *current_stream; } git_subtransport; /* @@ -67,7 +69,7 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) return 0; } -static int send_command(git_stream *s) +static int send_command(git_proto_stream *s) { int error; git_buf request = GIT_BUF_INIT; @@ -76,10 +78,7 @@ static int send_command(git_stream *s) if (error < 0) goto cleanup; - /* It looks like negative values are errors here, and positive values - * are the number of bytes sent. */ - error = gitno_send(&s->socket, request.ptr, request.size, 0); - + error = git_stream_write(s->io, request.ptr, request.size, 0); if (error >= 0) s->sent_command = 1; @@ -88,14 +87,14 @@ cleanup: return error; } -static int git_stream_read( +static int git_proto_stream_read( git_smart_subtransport_stream *stream, char *buffer, size_t buf_size, size_t *bytes_read) { int error; - git_stream *s = (git_stream *)stream; + git_proto_stream *s = (git_proto_stream *)stream; gitno_buffer buf; *bytes_read = 0; @@ -103,7 +102,7 @@ static int git_stream_read( if (!s->sent_command && (error = send_command(s)) < 0) return error; - gitno_buffer_setup(&s->socket, &buf, buffer, buf_size); + gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size); if ((error = gitno_recv(&buf)) < 0) return error; @@ -113,23 +112,23 @@ static int git_stream_read( return 0; } -static int git_stream_write( +static int git_proto_stream_write( git_smart_subtransport_stream *stream, const char *buffer, size_t len) { int error; - git_stream *s = (git_stream *)stream; + git_proto_stream *s = (git_proto_stream *)stream; if (!s->sent_command && (error = send_command(s)) < 0) return error; - return gitno_send(&s->socket, buffer, len, 0); + return git_stream_write(s->io, buffer, len, 0); } -static void git_stream_free(git_smart_subtransport_stream *stream) +static void git_proto_stream_free(git_smart_subtransport_stream *stream) { - git_stream *s = (git_stream *)stream; + git_proto_stream *s = (git_proto_stream *)stream; git_subtransport *t = OWNING_SUBTRANSPORT(s); int ret; @@ -137,33 +136,31 @@ static void git_stream_free(git_smart_subtransport_stream *stream) t->current_stream = NULL; - if (s->socket.socket) { - ret = gitno_close(&s->socket); - assert(!ret); - } - + git_stream_free(s->io); git__free(s->url); git__free(s); } -static int git_stream_alloc( +static int git_proto_stream_alloc( git_subtransport *t, const char *url, const char *cmd, + const char *host, + const char *port, git_smart_subtransport_stream **stream) { - git_stream *s; + git_proto_stream *s; if (!stream) return -1; - s = git__calloc(sizeof(git_stream), 1); + s = git__calloc(sizeof(git_proto_stream), 1); GITERR_CHECK_ALLOC(s); s->parent.subtransport = &t->parent; - s->parent.read = git_stream_read; - s->parent.write = git_stream_write; - s->parent.free = git_stream_free; + s->parent.read = git_proto_stream_read; + s->parent.write = git_proto_stream_write; + s->parent.free = git_proto_stream_free; s->cmd = cmd; s->url = git__strdup(url); @@ -173,6 +170,11 @@ static int git_stream_alloc( return -1; } + if ((git_socket_stream_new(&s->io, host, port)) < 0) + return -1; + + GITERR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); + *stream = &s->parent; return 0; } @@ -184,7 +186,7 @@ static int _git_uploadpack_ls( { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *stream_url = url; - git_stream *s; + git_proto_stream *s; int error; *stream = NULL; @@ -192,26 +194,32 @@ static int _git_uploadpack_ls( if (!git__prefixcmp(url, prefix_git)) stream_url += strlen(prefix_git); - if ((error = git_stream_alloc(t, stream_url, cmd_uploadpack, stream)) < 0) + if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0) return error; - s = (git_stream *)*stream; + error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); - if (!(error = gitno_extract_url_parts( - &host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT))) { + git__free(host); + git__free(port); + git__free(path); + git__free(user); + git__free(pass); - if (!(error = gitno_connect(&s->socket, host, port, 0))) - t->current_stream = s; - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); - } else if (*stream) - git_stream_free(*stream); + if (error < 0) { + git_proto_stream_free(*stream); + return error; + } - return error; + s = (git_proto_stream *) *stream; + if ((error = git_stream_connect(s->io)) < 0) { + git_proto_stream_free(*stream); + return error; + } + + t->current_stream = s; + + return 0; } static int _git_uploadpack( @@ -237,31 +245,37 @@ static int _git_receivepack_ls( { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *stream_url = url; - git_stream *s; + git_proto_stream *s; int error; *stream = NULL; if (!git__prefixcmp(url, prefix_git)) stream_url += strlen(prefix_git); - if (git_stream_alloc(t, stream_url, cmd_receivepack, stream) < 0) - return -1; + if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0) + return error; - s = (git_stream *)*stream; + error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream); - if (!(error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT))) { - if (!(error = gitno_connect(&s->socket, host, port, 0))) - t->current_stream = s; + git__free(host); + git__free(port); + git__free(path); + git__free(user); + git__free(pass); - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); - } else if (*stream) - git_stream_free(*stream); + if (error < 0) { + git_proto_stream_free(*stream); + return error; + } - return error; + s = (git_proto_stream *) *stream; + + if ((error = git_stream_connect(s->io)) < 0) + return error; + + t->current_stream = s; + + return 0; } static int _git_receivepack( From b6f5464e2823b0782305d79c7d94b4f2fdfe0322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Nov 2014 21:35:06 +0100 Subject: [PATCH 4/8] Port HTTP(S) to the new stream API --- src/transports/http.c | 106 +++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 64 deletions(-) diff --git a/src/transports/http.c b/src/transports/http.c index 234ee229f..c433a5913 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -13,16 +13,14 @@ #include "smart.h" #include "auth.h" #include "auth_negotiate.h" +#include "openssl_stream.h" +#include "socket_stream.h" git_http_auth_scheme auth_schemes[] = { { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic }, }; -#ifdef GIT_SSL -# include -#endif - static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; static const char *upload_pack_service_url = "/git-upload-pack"; @@ -62,7 +60,7 @@ typedef struct { typedef struct { git_smart_subtransport parent; transport_smart *owner; - gitno_socket socket; + git_stream *io; gitno_connection_data connection_data; bool connected; @@ -474,7 +472,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(&t->socket, + gitno_buffer_setup_fromstream(t->io, &t->parse_buffer, t->parse_buffer_data, sizeof(t->parse_buffer_data)); @@ -498,7 +496,7 @@ static void clear_parser_state(http_subtransport *t) git_vector_free_deep(&t->www_authenticate); } -static int write_chunk(gitno_socket *socket, const char *buffer, size_t len) +static int write_chunk(git_stream *io, const char *buffer, size_t len) { git_buf buf = GIT_BUF_INIT; @@ -508,7 +506,7 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len) if (git_buf_oom(&buf)) return -1; - if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) { + if (git_stream_write(io, buf.ptr, buf.size, 0) < 0) { git_buf_free(&buf); return -1; } @@ -516,11 +514,11 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len) git_buf_free(&buf); /* Chunk body */ - if (len > 0 && gitno_send(socket, buffer, len, 0) < 0) + if (len > 0 && git_stream_write(io, buffer, len, 0) < 0) return -1; /* Chunk footer */ - if (gitno_send(socket, "\r\n", 2, 0) < 0) + if (git_stream_write(io, "\r\n", 2, 0) < 0) return -1; return 0; @@ -535,57 +533,36 @@ static int http_connect(http_subtransport *t) t->parse_finished) return 0; - if (t->socket.socket) - gitno_close(&t->socket); - - if (t->connection_data.use_ssl) { - int tflags; - - if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0) - return -1; - - flags |= GITNO_CONNECT_SSL; + if (t->io) { + git_stream_close(t->io); + git_stream_free(t->io); + t->io = NULL; } - error = gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags); + if (t->connection_data.use_ssl) { + error = git_openssl_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); + } + + if (error < 0) + return error; + + GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream"); + + error = git_stream_connect(t->io); #ifdef GIT_SSL if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL) { - X509 *cert = SSL_get_peer_certificate(t->socket.ssl.ssl); - git_cert_x509 cert_info, *cert_info_ptr; - int len, is_valid; - unsigned char *guard, *encoded_cert; + git_cert *cert; + int is_valid; - /* Retrieve the length of the certificate first */ - len = i2d_X509(cert, NULL); - if (len < 0) { - giterr_set(GITERR_NET, "failed to retrieve certificate information"); - return -1; - } - - - encoded_cert = git__malloc(len); - GITERR_CHECK_ALLOC(encoded_cert); - /* i2d_X509 makes 'copy' point to just after the data */ - guard = encoded_cert; - - len = i2d_X509(cert, &guard); - if (len < 0) { - git__free(encoded_cert); - giterr_set(GITERR_NET, "failed to retrieve certificate information"); - return -1; - } + if ((error = git_stream_certificate(&cert, t->io)) < 0) + return error; giterr_clear(); is_valid = error != GIT_ECERTIFICATE; - cert_info.cert_type = GIT_CERT_X509; - cert_info.data = encoded_cert; - cert_info.len = len; - - cert_info_ptr = &cert_info; - - error = t->owner->certificate_check_cb((git_cert *) cert_info_ptr, is_valid, t->connection_data.host, t->owner->message_cb_payload); - git__free(encoded_cert); + error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload); if (error < 0) { if (!giterr_last()) @@ -626,7 +603,7 @@ replay: if (gen_request(&request, s, 0) < 0) return -1; - if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { + if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) { git_buf_free(&request); return -1; } @@ -642,13 +619,13 @@ replay: /* Flush, if necessary */ if (s->chunk_buffer_len > 0 && - write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0) + write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0) return -1; s->chunk_buffer_len = 0; /* Write the final chunk. */ - if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0) + if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0) return -1; } @@ -743,7 +720,7 @@ static int http_stream_write_chunked( if (gen_request(&request, s, 0) < 0) return -1; - if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { + if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) { git_buf_free(&request); return -1; } @@ -756,14 +733,14 @@ static int http_stream_write_chunked( if (len > CHUNK_SIZE) { /* Flush, if necessary */ if (s->chunk_buffer_len > 0) { - if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0) + if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0) return -1; s->chunk_buffer_len = 0; } /* Write chunk directly */ - if (write_chunk(&t->socket, buffer, len) < 0) + if (write_chunk(t->io, buffer, len) < 0) return -1; } else { @@ -780,7 +757,7 @@ 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->socket, s->chunk_buffer, s->chunk_buffer_len) < 0) + if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0) return -1; s->chunk_buffer_len = 0; @@ -816,10 +793,10 @@ static int http_stream_write_single( if (gen_request(&request, s, len) < 0) return -1; - if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) + if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) goto on_error; - if (len && gitno_send(&t->socket, buffer, len, 0) < 0) + if (len && git_stream_write(t->io, buffer, len, 0) < 0) goto on_error; git_buf_free(&request); @@ -986,9 +963,10 @@ static int http_close(git_smart_subtransport *subtransport) clear_parser_state(t); - if (t->socket.socket) { - gitno_close(&t->socket); - memset(&t->socket, 0x0, sizeof(gitno_socket)); + if (t->io) { + git_stream_close(t->io); + git_stream_free(t->io); + t->io = NULL; } if (t->cred) { From 4fd4341fe5da5353d50769c007e3189adaf12fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 2 Nov 2014 10:52:03 +0100 Subject: [PATCH 5/8] ssh: use socket_stream to perform the connection Having an ssh stream would require extra work for stream capabilities we don't need anywhere else (oob auth and command execution) so for now let's move away from the gitno connection to use socket_stream. We can introduce an ssh stream interface if and as we need it. --- src/transports/ssh.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 1f6716f5d..33d0898ec 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -14,6 +14,7 @@ #include "netops.h" #include "smart.h" #include "cred.h" +#include "socket_stream.h" #ifdef GIT_SSH @@ -25,7 +26,7 @@ static const char cmd_receivepack[] = "git-receive-pack"; typedef struct { git_smart_subtransport_stream parent; - gitno_socket socket; + git_stream *io; LIBSSH2_SESSION *session; LIBSSH2_CHANNEL *channel; const char *cmd; @@ -183,9 +184,10 @@ static void ssh_stream_free(git_smart_subtransport_stream *stream) s->session = NULL; } - if (s->socket.socket) { - (void)gitno_close(&s->socket); - /* can't do anything here with error return value */ + if (s->io) { + git_stream_close(s->io); + git_stream_free(s->io); + s->io = NULL; } git__free(s->url); @@ -413,10 +415,11 @@ static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, static int _git_ssh_session_create( LIBSSH2_SESSION** session, - gitno_socket socket) + git_stream *io) { int rc = 0; LIBSSH2_SESSION* s; + git_socket_stream *socket = (git_socket_stream *) io; assert(session); @@ -427,7 +430,7 @@ static int _git_ssh_session_create( } do { - rc = libssh2_session_startup(s, socket.socket); + rc = libssh2_session_startup(s, socket->s); } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); if (rc != LIBSSH2_ERROR_NONE) { @@ -477,10 +480,11 @@ static int _git_ssh_setup_conn( GITERR_CHECK_ALLOC(port); } - if ((error = gitno_connect(&s->socket, host, port, 0)) < 0) + if ((error = git_socket_stream_new(&s->io, host, port)) < 0 || + (error = git_stream_connect(s->io)) < 0) goto done; - if ((error = _git_ssh_session_create(&session, s->socket)) < 0) + if ((error = _git_ssh_session_create(&session, s->io)) < 0) goto done; if (t->owner->certificate_check_cb != NULL) { From 1b75c29e3e175ad3ef4bc91a779b851dc76b9e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 2 Nov 2014 11:17:01 +0100 Subject: [PATCH 6/8] gitno: remove code which is no longer needed Most of the network-facing facilities have been copied to the socket and openssl streams. No code now uses these functions directly anymore, so we can now remove them. --- src/netops.c | 497 +---------------------------------------- src/netops.h | 10 +- src/openssl_stream.c | 3 +- src/socket_stream.c | 1 - src/transports/smart.c | 6 +- 5 files changed, 17 insertions(+), 500 deletions(-) diff --git a/src/netops.c b/src/netops.c index ab135c46b..6047cf1ac 100644 --- a/src/netops.c +++ b/src/netops.c @@ -4,27 +4,6 @@ * 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 _WIN32 -# include -# include -# include -# include -# include -# include -# include -#else -# include -# include -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif -#endif - -#ifdef GIT_SSL -# include -# include -# include -#endif #include #include "git2/errors.h" @@ -36,122 +15,12 @@ #include "http_parser.h" #include "global.h" -#ifdef GIT_WIN32 -static void net_set_error(const char *str) -{ - int error = WSAGetLastError(); - char * win32_error = git_win32_get_error_message(error); - - if (win32_error) { - giterr_set(GITERR_NET, "%s: %s", str, win32_error); - git__free(win32_error); - } else { - giterr_set(GITERR_NET, str); - } -} -#else -static void net_set_error(const char *str) -{ - giterr_set(GITERR_NET, "%s: %s", str, strerror(errno)); -} -#endif - -#ifdef GIT_SSL -static int ssl_set_error(gitno_ssl *ssl, int error) -{ - int err; - unsigned long e; - - err = SSL_get_error(ssl->ssl, error); - - assert(err != SSL_ERROR_WANT_READ); - assert(err != SSL_ERROR_WANT_WRITE); - - switch (err) { - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_ACCEPT: - giterr_set(GITERR_NET, "SSL error: connection failure\n"); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - giterr_set(GITERR_NET, "SSL error: x509 error\n"); - break; - case SSL_ERROR_SYSCALL: - e = ERR_get_error(); - if (e > 0) { - giterr_set(GITERR_NET, "SSL error: %s", - ERR_error_string(e, NULL)); - break; - } else if (error < 0) { - giterr_set(GITERR_OS, "SSL error: syscall failure"); - break; - } - giterr_set(GITERR_NET, "SSL error: received early EOF"); - break; - case SSL_ERROR_SSL: - e = ERR_get_error(); - giterr_set(GITERR_NET, "SSL error: %s", - ERR_error_string(e, NULL)); - break; - case SSL_ERROR_NONE: - case SSL_ERROR_ZERO_RETURN: - default: - giterr_set(GITERR_NET, "SSL error: unknown error"); - break; - } - return -1; -} -#endif - int gitno_recv(gitno_buffer *buf) { - if (buf->io) { - int ret; - ret = git_stream_read(buf->io, buf->data + buf->offset, buf->len - buf->offset); - if (ret < 0) - return -1; - - buf->offset += ret; - return ret; - } - return buf->recv(buf); } -#ifdef GIT_SSL -static int gitno__recv_ssl(gitno_buffer *buf) -{ - int ret; - - do { - ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset); - } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ); - - if (ret < 0) { - net_set_error("Error receiving socket data"); - return -1; - } - - buf->offset += ret; - return ret; -} -#endif - -static int gitno__recv(gitno_buffer *buf) -{ - int ret; - - ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0); - if (ret < 0) { - net_set_error("Error receiving socket data"); - return -1; - } - - buf->offset += ret; - return ret; -} - void gitno_buffer_setup_callback( - gitno_socket *socket, gitno_buffer *buf, char *data, size_t len, @@ -161,21 +30,21 @@ void gitno_buffer_setup_callback( buf->data = data; buf->len = len; buf->offset = 0; - buf->socket = socket; buf->recv = recv; buf->cb_data = cb_data; } -void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len) +static int recv_stream(gitno_buffer *buf) { -#ifdef GIT_SSL - if (socket->ssl.ssl) { - gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL); - return; - } -#endif + git_stream *io = (git_stream *) buf->cb_data; + int ret; - gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL); + ret = git_stream_read(io, buf->data + buf->offset, buf->len - buf->offset); + if (ret < 0) + return -1; + + buf->offset += ret; + return ret; } void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) @@ -184,7 +53,8 @@ void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data buf->data = data; buf->len = len; buf->offset = 0; - buf->io = st; + buf->recv = recv_stream; + buf->cb_data = st; } /* Consume up to ptr and move the rest of the buffer to the beginning */ @@ -210,24 +80,6 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -#ifdef GIT_SSL - -static int gitno_ssl_teardown(gitno_ssl *ssl) -{ - int ret; - - ret = SSL_shutdown(ssl->ssl); - if (ret < 0) - ret = ssl_set_error(ssl, ret); - else - ret = 0; - - SSL_free(ssl->ssl); - return ret; -} - -#endif - /* Match host names according to RFC 2818 rules */ int gitno__match_host(const char *pattern, const char *host) { @@ -267,333 +119,6 @@ int gitno__match_host(const char *pattern, const char *host) return -1; } -static int check_host_name(const char *name, const char *host) -{ - if (!strcasecmp(name, host)) - return 0; - - if (gitno__match_host(name, host) < 0) - return -1; - - return 0; -} - -#ifdef GIT_SSL - -static int verify_server_cert(gitno_ssl *ssl, const char *host) -{ - X509 *cert; - X509_NAME *peer_name; - ASN1_STRING *str; - unsigned char *peer_cn = NULL; - int matched = -1, type = GEN_DNS; - GENERAL_NAMES *alts; - struct in6_addr addr6; - struct in_addr addr4; - void *addr; - int i = -1,j; - - if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) { - giterr_set(GITERR_SSL, "The SSL certificate is invalid"); - return GIT_ECERTIFICATE; - } - - /* Try to parse the host as an IP address to see if it is */ - if (p_inet_pton(AF_INET, host, &addr4)) { - type = GEN_IPADD; - addr = &addr4; - } else { - if(p_inet_pton(AF_INET6, host, &addr6)) { - type = GEN_IPADD; - addr = &addr6; - } - } - - - cert = SSL_get_peer_certificate(ssl->ssl); - if (!cert) { - giterr_set(GITERR_SSL, "the server did not provide a certificate"); - return -1; - } - - /* Check the alternative names */ - alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); - if (alts) { - int num; - - num = sk_GENERAL_NAME_num(alts); - for (i = 0; i < num && matched != 1; i++) { - const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); - const char *name = (char *) ASN1_STRING_data(gn->d.ia5); - size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); - - /* Skip any names of a type we're not looking for */ - if (gn->type != type) - continue; - - if (type == GEN_DNS) { - /* If it contains embedded NULs, don't even try */ - if (memchr(name, '\0', namelen)) - continue; - - if (check_host_name(name, host) < 0) - matched = 0; - else - matched = 1; - } else if (type == GEN_IPADD) { - /* Here name isn't so much a name but a binary representation of the IP */ - matched = !!memcmp(name, addr, namelen); - } - } - } - GENERAL_NAMES_free(alts); - - if (matched == 0) - goto cert_fail_name; - - if (matched == 1) - return 0; - - /* If no alternative names are available, check the common name */ - peer_name = X509_get_subject_name(cert); - if (peer_name == NULL) - goto on_error; - - if (peer_name) { - /* Get the index of the last CN entry */ - while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) - i = j; - } - - if (i < 0) - goto on_error; - - str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); - if (str == NULL) - goto on_error; - - /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ - if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { - int size = ASN1_STRING_length(str); - - if (size > 0) { - peer_cn = OPENSSL_malloc(size + 1); - GITERR_CHECK_ALLOC(peer_cn); - memcpy(peer_cn, ASN1_STRING_data(str), size); - peer_cn[size] = '\0'; - } - } else { - int size = ASN1_STRING_to_UTF8(&peer_cn, str); - GITERR_CHECK_ALLOC(peer_cn); - if (memchr(peer_cn, '\0', size)) - goto cert_fail_name; - } - - if (check_host_name((char *)peer_cn, host) < 0) - goto cert_fail_name; - - OPENSSL_free(peer_cn); - - return 0; - -on_error: - OPENSSL_free(peer_cn); - return ssl_set_error(ssl, 0); - -cert_fail_name: - OPENSSL_free(peer_cn); - giterr_set(GITERR_SSL, "hostname does not match certificate"); - return GIT_ECERTIFICATE; -} - -static int ssl_setup(gitno_socket *socket, const char *host) -{ - int ret; - - if (git__ssl_ctx == NULL) { - giterr_set(GITERR_NET, "OpenSSL initialization failed"); - return -1; - } - - socket->ssl.ssl = SSL_new(git__ssl_ctx); - if (socket->ssl.ssl == NULL) - return ssl_set_error(&socket->ssl, 0); - - if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0) - return ssl_set_error(&socket->ssl, ret); - - if ((ret = SSL_connect(socket->ssl.ssl)) <= 0) - return ssl_set_error(&socket->ssl, ret); - - return verify_server_cert(&socket->ssl, host); -} -#endif - -static int gitno__close(GIT_SOCKET s) -{ -#ifdef GIT_WIN32 - if (SOCKET_ERROR == closesocket(s)) - return -1; - - if (0 != WSACleanup()) { - giterr_set(GITERR_OS, "Winsock cleanup failed"); - return -1; - } - - return 0; -#else - return close(s); -#endif -} - -int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags) -{ - struct addrinfo *info = NULL, *p; - struct addrinfo hints; - GIT_SOCKET s = INVALID_SOCKET; - int ret; - -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - WSADATA wsd; - - if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { - giterr_set(GITERR_OS, "Winsock init failed"); - return -1; - } - - if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { - WSACleanup(); - giterr_set(GITERR_OS, "Winsock init failed"); - return -1; - } -#endif - - /* Zero the socket structure provided */ - memset(s_out, 0x0, sizeof(gitno_socket)); - - memset(&hints, 0x0, sizeof(struct addrinfo)); - hints.ai_socktype = SOCK_STREAM; - hints.ai_family = AF_UNSPEC; - - if ((ret = p_getaddrinfo(host, port, &hints, &info)) != 0) { - giterr_set(GITERR_NET, - "Failed to resolve address for %s: %s", host, p_gai_strerror(ret)); - return -1; - } - - for (p = info; p != NULL; p = p->ai_next) { - s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); - - if (s == INVALID_SOCKET) { - net_set_error("error creating socket"); - break; - } - - if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) - break; - - /* If we can't connect, try the next one */ - gitno__close(s); - s = INVALID_SOCKET; - } - - /* Oops, we couldn't connect to any address */ - if (s == INVALID_SOCKET && p == NULL) { - giterr_set(GITERR_OS, "Failed to connect to %s", host); - p_freeaddrinfo(info); - return -1; - } - - s_out->socket = s; - p_freeaddrinfo(info); - -#ifdef GIT_SSL - if ((flags & GITNO_CONNECT_SSL) && - (ret = ssl_setup(s_out, host)) < 0) - return ret; -#else - /* SSL is not supported */ - if (flags & GITNO_CONNECT_SSL) { - giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2."); - return -1; - } -#endif - - return 0; -} - -#ifdef GIT_SSL -static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags) -{ - int ret; - size_t off = 0; - - GIT_UNUSED(flags); - - while (off < len) { - ret = SSL_write(ssl->ssl, msg + off, len - off); - if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) - return ssl_set_error(ssl, ret); - - off += ret; - } - - return off; -} -#endif - -int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) -{ - int ret; - size_t off = 0; - -#ifdef GIT_SSL - if (socket->ssl.ssl) - return gitno_send_ssl(&socket->ssl, msg, len, flags); -#endif - - while (off < len) { - errno = 0; - ret = p_send(socket->socket, msg + off, len - off, flags); - if (ret < 0) { - net_set_error("Error sending data"); - return -1; - } - - off += ret; - } - - return (int)off; -} - -int gitno_close(gitno_socket *s) -{ -#ifdef GIT_SSL - if (s->ssl.ssl && - gitno_ssl_teardown(&s->ssl) < 0) - return -1; -#endif - - return gitno__close(s->socket); -} - -int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) -{ - fd_set fds; - struct timeval tv; - - tv.tv_sec = sec; - tv.tv_usec = usec; - - FD_ZERO(&fds); - FD_SET(buf->socket->socket, &fds); - - /* The select(2) interface is silly */ - return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv); -} - static const char *prefix_http = "http://"; static const char *prefix_https = "https://"; diff --git a/src/netops.h b/src/netops.h index fee6d82da..d5f0ca3f3 100644 --- a/src/netops.h +++ b/src/netops.h @@ -33,8 +33,6 @@ typedef struct gitno_buffer { char *data; size_t len; size_t offset; - git_stream *io; - gitno_socket *socket; int (*recv)(struct gitno_buffer *buffer); void *cb_data; } gitno_buffer; @@ -58,19 +56,13 @@ enum { */ int gitno__match_host(const char *pattern, const char *host); -void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len); void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len); -void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); +void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); int gitno_recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags); -int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags); -int gitno_close(gitno_socket *s); -int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); - typedef struct gitno_connection_data { char *host; char *port; diff --git a/src/openssl_stream.c b/src/openssl_stream.c index 4c8b02c8c..0893bcc9d 100644 --- a/src/openssl_stream.c +++ b/src/openssl_stream.c @@ -17,6 +17,7 @@ #include "posix.h" #include "stream.h" #include "socket_stream.h" +#include "netops.h" #include "git2/transport.h" static int ssl_set_error(SSL *ssl, int error) @@ -103,7 +104,7 @@ static int verify_server_cert(SSL *ssl, const char *host) if (SSL_get_verify_result(ssl) != X509_V_OK) { giterr_set(GITERR_SSL, "The SSL certificate is invalid"); - return -1; + return GIT_ECERTIFICATE; } /* Try to parse the host as an IP address to see if it is */ diff --git a/src/socket_stream.c b/src/socket_stream.c index 113b8f698..ce41fb640 100644 --- a/src/socket_stream.c +++ b/src/socket_stream.c @@ -99,7 +99,6 @@ int socket_connect(git_stream *stream) if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { giterr_set(GITERR_NET, - "Failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); return -1; } diff --git a/src/transports/smart.c b/src/transports/smart.c index d0f9c90e8..ec0ba3784 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -158,7 +158,7 @@ static int git_smart__connect( /* Save off the current stream (i.e. socket) that we are working with */ t->current_stream = stream; - gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); /* 2 flushes for RPC; 1 for stateful */ if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) @@ -252,7 +252,7 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len if ((error = stream->write(stream, (const char *)data, len)) < 0) return error; - gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); return 0; } @@ -278,7 +278,7 @@ int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream /* Save off the current stream (i.e. socket) that we are working with */ t->current_stream = *stream; - gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); return 0; } From 49ae22baac96f14d63547c158a87b4de2d8373a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 10 Dec 2014 01:38:52 +0100 Subject: [PATCH 7/8] stream: constify the write buffer --- include/git2/sys/stream.h | 2 +- src/openssl_stream.c | 2 +- src/socket_stream.c | 2 +- src/stream.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index 69f8554da..c249055c9 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -32,7 +32,7 @@ typedef struct git_stream { int (*connect)(struct git_stream *); int (*certificate)(git_cert **, struct git_stream *); ssize_t (*read)(struct git_stream *, void *, size_t); - ssize_t (*write)(struct git_stream *, void *, size_t, int); + ssize_t (*write)(struct git_stream *, const char *, size_t, int); int (*close)(struct git_stream *); void (*free)(struct git_stream *); } git_stream; diff --git a/src/openssl_stream.c b/src/openssl_stream.c index 0893bcc9d..3a6369dee 100644 --- a/src/openssl_stream.c +++ b/src/openssl_stream.c @@ -277,7 +277,7 @@ int openssl_certificate(git_cert **out, git_stream *stream) return 0; } -ssize_t openssl_write(git_stream *stream, void *data, size_t len, int flags) +ssize_t openssl_write(git_stream *stream, const char *data, size_t len, int flags) { openssl_stream *st = (openssl_stream *) stream; int ret; diff --git a/src/socket_stream.c b/src/socket_stream.c index ce41fb640..71f49118e 100644 --- a/src/socket_stream.c +++ b/src/socket_stream.c @@ -131,7 +131,7 @@ int socket_connect(git_stream *stream) return 0; } -ssize_t socket_write(git_stream *stream, void *data, size_t len, int flags) +ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) { ssize_t ret; size_t off = 0; diff --git a/src/stream.h b/src/stream.h index 8eec83009..5267d90ad 100644 --- a/src/stream.h +++ b/src/stream.h @@ -30,7 +30,7 @@ GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) return st->read(st, data, len); } -GIT_INLINE(ssize_t) git_stream_write(git_stream *st, void *data, size_t len, int flags) +GIT_INLINE(ssize_t) git_stream_write(git_stream *st, const char *data, size_t len, int flags) { return st->write(st, data, len, flags); } From a2fd56ab18cfe1a16372193e8ddfbbe6cadb5a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 10 Dec 2014 16:22:50 +0100 Subject: [PATCH 8/8] Fix a couple of compiler warnings --- src/stream.h | 2 +- src/transports/http.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stream.h b/src/stream.h index 5267d90ad..3a7ef9514 100644 --- a/src/stream.h +++ b/src/stream.h @@ -42,7 +42,7 @@ GIT_INLINE(int) git_stream_close(git_stream *st) GIT_INLINE(void) git_stream_free(git_stream *st) { - return st->free(st); + st->free(st); } #endif diff --git a/src/transports/http.c b/src/transports/http.c index c433a5913..807e08044 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -526,7 +526,7 @@ static int write_chunk(git_stream *io, const char *buffer, size_t len) static int http_connect(http_subtransport *t) { - int flags = 0, error; + int error; if (t->connected && http_should_keep_alive(&t->parser) &&