diff options
Diffstat (limited to 'src/netops.c')
-rw-r--r-- | src/netops.c | 492 |
1 files changed, 18 insertions, 474 deletions
diff --git a/src/netops.c b/src/netops.c index 23e7e9d3c..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 <sys/types.h> -# include <sys/socket.h> -# 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> -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif -#endif - -#ifdef GIT_SSL -# include <openssl/ssl.h> -# include <openssl/err.h> -# include <openssl/x509v3.h> -#endif #include <ctype.h> #include "git2/errors.h" @@ -36,136 +15,46 @@ #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) { return buf->recv(buf); } -#ifdef GIT_SSL -static int gitno__recv_ssl(gitno_buffer *buf) +void gitno_buffer_setup_callback( + gitno_buffer *buf, + char *data, + size_t len, + int (*recv)(gitno_buffer *buf), void *cb_data) { - 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; + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->recv = recv; + buf->cb_data = cb_data; } -#endif -static int gitno__recv(gitno_buffer *buf) +static int recv_stream(gitno_buffer *buf) { + git_stream *io = (git_stream *) buf->cb_data; 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"); + 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_callback( - gitno_socket *socket, - gitno_buffer *buf, - char *data, - size_t len, - int (*recv)(gitno_buffer *buf), void *cb_data) +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->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.ssl) { - 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); + buf->recv = recv_stream; + buf->cb_data = st; } /* Consume up to ptr and move the rest of the buffer to the beginning */ @@ -191,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) { @@ -248,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://"; |