summaryrefslogtreecommitdiff
path: root/src/gnutls_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gnutls_stream.c')
-rw-r--r--src/gnutls_stream.c165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/gnutls_stream.c b/src/gnutls_stream.c
new file mode 100644
index 000000000..37fc1000d
--- /dev/null
+++ b/src/gnutls_stream.c
@@ -0,0 +1,165 @@
+/*
+ * 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_GNUTLS
+
+#include <gnutls/gnutls.h>
+
+#include "stream.h"
+#include "socket_stream.h"
+#include "git2/transport.h"
+
+void set_gnutls_error(int error)
+{
+ giterr_set(GITERR_SSL, "gnutls: %s", gnutls_strerror(error));
+}
+
+typedef struct {
+ git_stream parent;
+ git_socket_stream *socket;
+ gnutls_session_t session;
+ git_cert_x509 cert_info;
+} gnutls_stream;
+
+static int verify_server_cert(gnutls_session_t session, const char *host)
+{
+ int error;
+ unsigned int status = 0;
+
+ if ((error = gnutls_certificate_verify_peers3(session, host, &status)) < 0) {
+ set_gnutls_error(error);
+ return -1;
+ }
+
+ if (!status)
+ return 0;
+ else
+ return GIT_ECERTIFICATE;
+}
+
+static ssize_t gnutls_stream_write(git_stream *stream, void *data, size_t len, int flags)
+{
+ ssize_t ret;
+ size_t off;
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ GIT_UNUSED(flags);
+
+ while (off < len) {
+ ret = gnutls_record_send(st->session, data + off, len - off);
+ if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)
+ continue; /* try again with the same params */
+
+ if (ret < 0) {
+ set_gnutls_error(ret);
+ return -1;
+ }
+
+ off += ret;
+ }
+
+ return ret;
+}
+
+static ssize_t gnutls_stream_read(git_stream *stream, void *data, size_t len)
+{
+ ssize_t ret;
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ do {
+ ret = gnutls_record_recv(st->session, data, len);
+ } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
+
+ if (ret < 0) {
+ set_gnutls_error(ret);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int gnutls_connect(git_stream *stream)
+{
+ int error;
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ if ((error = git_stream_connect((git_stream *)st->socket)) < 0)
+ return error;
+
+ /* ideally we'd have functions that talk to the socket, but for now */
+ gnutls_transport_set_int(st->session, st->socket->s);
+
+ do {
+ error = gnutls_handshake(st->session);
+ } while (!gnutls_error_is_fatal(error));
+
+ if (error < 0) {
+ set_gnutls_error(error);
+ return -1;
+ }
+
+ return verify_server_cert(st->session, st->socket->host);
+}
+
+static int gnutls_stream_close(git_stream *stream)
+{
+ gnutls_stream *st = (gnutls_stream *) stream;
+ int error;
+
+ do {
+ error = gnutls_bye(st->session, GNUTLS_SHUT_RDWR);
+ } while (error == GNUTLS_E_INTERRUPTED || error == GNUTLS_E_AGAIN);
+
+ return git_stream_close((git_stream *)st->socket);
+}
+
+static void gnutls_stream_free(git_stream *stream)
+{
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ gnutls_deinit(st->session);
+ git__free(st);
+}
+
+int git_gnutls_stream_new(git_stream **out, const char *host, const char *port)
+{
+ gnutls_stream *st;
+ int error;
+
+ st = git__calloc(1, sizeof(gnutls_stream));
+ GITERR_CHECK_ALLOC(st);
+
+ if (git_socket_stream_new((git_stream **) &st->socket, host, port))
+ return -1;
+
+ if ((error = gnutls_init(&st->session, GNUTLS_CLIENT)) < 0) {
+ git_stream_free((git_stream *) st->socket);
+ git__free(st);
+ return -1;
+ }
+
+ st->parent.encrypted = 1;
+ st->parent.connect = gnutls_connect;
+ st->parent.certificate = gnutls_certificate;
+ st->parent.read = gnutls_stream_read;
+ st->parent.write = gnutls_stream_write;
+ st->parent.close = gnutls_stream_close;
+ st->parent.free = gnutls_stream_free;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+#else
+
+int git_gnutls_stream_new(git_stream **out, const char *host, const char *port)
+{
+ giterr_set(GITERR_SSL, "GnuTLS is not supported in this version");
+ return -1;
+}
+
+#endif