summaryrefslogtreecommitdiff
path: root/src/netops.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/netops.c')
-rw-r--r--src/netops.c516
1 files changed, 469 insertions, 47 deletions
diff --git a/src/netops.c b/src/netops.c
index 4d461a049..69179dd1c 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,15 +10,26 @@
# include <sys/select.h>
# include <sys/time.h>
# include <netdb.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
#else
-# include <winsock2.h>
-# include <Ws2tcpip.h>
+# include <ws2tcpip.h>
# ifdef _MSC_VER
-# pragma comment(lib, "Ws2_32.lib")
+# pragma comment(lib, "ws2_32")
# endif
#endif
+#ifdef __FreeBSD__
+# include <netinet/in.h>
+#endif
+
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+#endif
+#include <ctype.h>
#include "git2/errors.h"
#include "common.h"
@@ -29,14 +40,15 @@
#ifdef GIT_WIN32
static void net_set_error(const char *str)
{
- int size, error = WSAGetLastError();
- LPSTR err_str = NULL;
+ int error = WSAGetLastError();
+ char * win32_error = git_win32_get_error_message(error);
- size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- 0, error, 0, (LPSTR)&err_str, 0, 0);
-
- giterr_set(GITERR_NET, "%s: %s", str, err_str);
- LocalFree(err_str);
+ 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)
@@ -45,21 +57,66 @@ static void net_set_error(const char *str)
}
#endif
-void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd)
+#ifdef GIT_SSL
+static int ssl_set_error(gitno_ssl *ssl, int error)
{
- memset(buf, 0x0, sizeof(gitno_buffer));
- memset(data, 0x0, len);
- buf->data = data;
- buf->len = len;
- buf->offset = 0;
- buf->fd = fd;
+ 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)
{
+ return buf->recv(buf);
+}
+
+#ifdef GIT_SSL
+static int gitno__recv_ssl(gitno_buffer *buf)
+{
int ret;
- ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
+ 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;
@@ -68,6 +125,49 @@ int gitno_recv(gitno_buffer *buf)
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,
+ int (*recv)(gitno_buffer *buf), void *cb_data)
+{
+ memset(data, 0x0, len);
+ 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)
+{
+#ifdef GIT_SSL
+ if (socket->ssl.ctx) {
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
+ return;
+ }
+#endif
+
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
+}
/* Consume up to ptr and move the rest of the buffer to the beginning */
void gitno_consume(gitno_buffer *buf, const char *ptr)
@@ -92,24 +192,284 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
buf->offset -= cons;
}
-int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port)
+#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);
+ SSL_CTX_free(ssl->ctx);
+ return ret;
+}
+
+/* Match host names according to RFC 2818 rules */
+static int match_host(const char *pattern, const char *host)
+{
+ for (;;) {
+ char c = tolower(*pattern++);
+
+ if (c == '\0')
+ return *host ? -1 : 0;
+
+ if (c == '*') {
+ c = *pattern;
+ /* '*' at the end matches everything left */
+ if (c == '\0')
+ return 0;
+
+ /*
+ * We've found a pattern, so move towards the next matching
+ * char. The '.' is handled specially because wildcards aren't
+ * allowed to cross subdomains.
+ */
+
+ while(*host) {
+ char h = tolower(*host);
+ if (c == h)
+ return match_host(pattern, host++);
+ if (h == '.')
+ return match_host(pattern, host);
+ host++;
+ }
+ return -1;
+ }
+
+ if (c != tolower(*host++))
+ return -1;
+ }
+
+ return -1;
+}
+
+static int check_host_name(const char *name, const char *host)
+{
+ if (!strcasecmp(name, host))
+ return 0;
+
+ if (match_host(name, host) < 0)
+ return -1;
+
+ return 0;
+}
+
+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 -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->ssl);
+
+ /* 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;
+
+ 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;
+ }
+
+ if (check_host_name((char *)peer_cn, host) < 0)
+ goto cert_fail;
+
+ OPENSSL_free(peer_cn);
+
+ return 0;
+
+on_error:
+ OPENSSL_free(peer_cn);
+ return ssl_set_error(ssl, 0);
+
+cert_fail:
+ OPENSSL_free(peer_cn);
+ giterr_set(GITERR_SSL, "Certificate host name check failed");
+ return -1;
+}
+
+static int ssl_setup(gitno_socket *socket, const char *host, int flags)
+{
+ int ret;
+
+ SSL_library_init();
+ SSL_load_error_strings();
+ socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
+ if (socket->ssl.ctx == NULL)
+ return ssl_set_error(&socket->ssl, 0);
+
+ SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
+ return ssl_set_error(&socket->ssl, 0);
+
+ socket->ssl.ssl = SSL_new(socket->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);
+
+ if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
+ return 0;
+
+ 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;
- int ret;
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_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC;
- if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) {
- giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret));
+ 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;
@@ -119,30 +479,67 @@ int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port)
break;
/* If we can't connect, try the next one */
- gitno_close(s);
+ 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) && ssl_setup(s_out, host, flags) < 0)
+ return -1;
+#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
- freeaddrinfo(info);
- *sock = s;
return 0;
}
-int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags)
+#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) {
- errno = 0;
+ 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.ctx)
+ return gitno_send_ssl(&socket->ssl, msg, len, flags);
+#endif
- ret = p_send(s, msg + off, len - off, flags);
+ 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;
@@ -154,19 +551,17 @@ int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags)
return (int)off;
}
-
-#ifdef GIT_WIN32
-int gitno_close(GIT_SOCKET s)
-{
- return closesocket(s) == SOCKET_ERROR ? -1 : 0;
-}
-#else
-int gitno_close(GIT_SOCKET s)
+int gitno_close(gitno_socket *s)
{
- return close(s);
-}
+#ifdef GIT_SSL
+ if (s->ssl.ctx &&
+ 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;
@@ -176,33 +571,60 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
tv.tv_usec = usec;
FD_ZERO(&fds);
- FD_SET(buf->fd, &fds);
+ FD_SET(buf->socket->socket, &fds);
/* The select(2) interface is silly */
- return select((int)buf->fd + 1, &fds, NULL, NULL, &tv);
+ return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
}
-int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port)
{
- char *colon, *slash, *delim;
+ char *colon, *slash, *at, *end;
+ const char *start;
+
+ /*
+ *
+ * ==> [user[:pass]@]hostname.tld[:port]/resource
+ */
colon = strchr(url, ':');
slash = strchr(url, '/');
+ at = strchr(url, '@');
if (slash == NULL) {
giterr_set(GITERR_NET, "Malformed URL: missing /");
return -1;
}
+ start = url;
+ if (at && at < slash) {
+ start = at+1;
+ *username = git__substrdup(url, at - url);
+ }
+
+ if (colon && colon < at) {
+ git__free(*username);
+ *username = git__substrdup(url, colon-url);
+ *password = git__substrdup(colon+1, at-colon-1);
+ colon = strchr(at, ':');
+ }
+
if (colon == NULL) {
*port = git__strdup(default_port);
} else {
- *port = git__strndup(colon + 1, slash - colon - 1);
+ *port = git__substrdup(colon + 1, slash - colon - 1);
}
GITERR_CHECK_ALLOC(*port);
- delim = colon == NULL ? slash : colon;
- *host = git__strndup(url, delim - url);
+ end = colon == NULL ? slash : colon;
+
+ *host = git__substrdup(start, end - start);
GITERR_CHECK_ALLOC(*host);
return 0;