-/* GIO - GLib Input, Output and Streaming Library
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
*
- * Copyright © 2009 Red Hat, Inc
+ * Copyright 2009 Red Hat, Inc
+ * Copyright 2015, 2016 Collabora, Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
+ * version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
*/
#include "config.h"
#include "glib.h"
#include <errno.h>
+#include <stdarg.h>
+#include <gnutls/dtls.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include "gtlsconnection-gnutls.h"
#include "gtlsbackend-gnutls.h"
#include "gtlscertificate-gnutls.h"
+#include "gtlsclientconnection-gnutls.h"
#include "gtlsinputstream-gnutls.h"
#include "gtlsoutputstream-gnutls.h"
#include "gtlsserverconnection-gnutls.h"
+
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <winerror.h>
+
+/* It isn’t clear whether MinGW always defines EMSGSIZE. */
+#ifndef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#endif
+#endif
+
#include <glib/gi18n-lib.h>
+#include <glib/gprintf.h>
+
+/*
+ * GTlsConnectionGnutls is the base abstract implementation of TLS and DTLS
+ * support, for both the client and server side of a connection. The choice
+ * between TLS and DTLS is made by setting the base-io-stream or
+ * base-socket properties — exactly one of them must be set at
+ * construction time.
+ *
+ * Client and server specific code is in the GTlsClientConnectionGnutls and
+ * GTlsServerConnectionGnutls concrete subclasses, although the line about where
+ * code is put is a little blurry, and there are various places in
+ * GTlsConnectionGnutls which check G_IS_TLS_CLIENT_CONNECTION(self) to switch
+ * to a client-only code path.
+ *
+ * This abstract class implements a lot of interfaces:
+ * • Derived from GTlsConnection (itself from GIOStream), for TLS and streaming
+ * communications.
+ * • Implements GDtlsConnection and GDatagramBased, for DTLS and datagram
+ * communications.
+ * • Implements GInitable for failable GnuTLS initialisation.
+ *
+ * The GTlsClientConnectionGnutls and GTlsServerConnectionGnutls subclasses are
+ * both derived from GTlsConnectionGnutls (and hence GIOStream), and both
+ * implement the relevant TLS and DTLS interfaces:
+ * • GTlsClientConnection
+ * • GDtlsClientConnection
+ * • GTlsServerConnection
+ * • GDtlsServerConnection
+ */
-static void g_tls_connection_gnutls_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec);
-static void g_tls_connection_gnutls_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec);
-static void g_tls_connection_gnutls_finalize (GObject *object);
-
-static gboolean g_tls_connection_gnutls_handshake (GTlsConnection *connection,
- GCancellable *cancellable,
- GError **error);
-static void g_tls_connection_gnutls_handshake_async (GTlsConnection *conn,
- int io_priority,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-static gboolean g_tls_connection_gnutls_handshake_finish (GTlsConnection *conn,
- GAsyncResult *result,
- GError **error);
-
-static GInputStream *g_tls_connection_gnutls_get_input_stream (GIOStream *stream);
-static GOutputStream *g_tls_connection_gnutls_get_output_stream (GIOStream *stream);
-
-static gboolean g_tls_connection_gnutls_close (GIOStream *stream,
- GCancellable *cancellable,
- GError **error);
-static void g_tls_connection_gnutls_close_async (GIOStream *stream,
- int io_priority,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-static gboolean g_tls_connection_gnutls_close_finish (GIOStream *stream,
- GAsyncResult *result,
- GError **error);
+#include "TIZEN.h"
+
+#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG)
+#include <sys/prctl.h>
+#ifndef PR_TASK_PERF_USER_TRACE
+#define PR_TASK_PERF_USER_TRACE 666
+#endif
+#define HWCLOCK_LOG(s) {const char *str=s; prctl(PR_TASK_PERF_USER_TRACE, str, strlen(str));}
+#endif
static ssize_t g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data,
- const void *buf,
- size_t buflen);
+ const void *buf,
+ size_t buflen);
+static ssize_t g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data,
+ const giovec_t *iov,
+ int iovcnt);
static ssize_t g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
- void *buf,
- size_t buflen);
+ void *buf,
+ size_t buflen);
+
+static int g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
+ unsigned int ms);
+
static void g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface);
static gboolean g_tls_connection_gnutls_initable_init (GInitable *initable,
- GCancellable *cancellable,
- GError **error);
+ GCancellable *cancellable,
+ GError **error);
+static void g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface);
+static void g_tls_connection_gnutls_datagram_based_iface_init (GDatagramBasedInterface *iface);
static void g_tls_connection_gnutls_init_priorities (void);
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION,
- G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
- g_tls_connection_gnutls_initable_iface_init);
- g_tls_connection_gnutls_init_priorities ();
- );
+static int verify_certificate_cb (gnutls_session_t session);
+static gboolean do_implicit_handshake (GTlsConnectionGnutls *gnutls,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean finish_handshake (GTlsConnectionGnutls *gnutls,
+ GTask *task,
+ GError **error);
enum
{
PROP_0,
+ /* For this class: */
PROP_BASE_IO_STREAM,
+ PROP_BASE_SOCKET,
+ /* For GTlsConnection and GDtlsConnection: */
PROP_REQUIRE_CLOSE_NOTIFY,
PROP_REHANDSHAKE_MODE,
- PROP_USE_SYSTEM_CERTDB
+ PROP_USE_SYSTEM_CERTDB,
+ PROP_DATABASE,
+ PROP_CERTIFICATE,
+ PROP_INTERACTION,
+ PROP_PEER_CERTIFICATE,
+ PROP_PEER_CERTIFICATE_ERRORS,
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ PROP_ADVERTISED_PROTOCOLS,
+ PROP_NEGOTIATED_PROTOCOL,
+#endif
};
-struct _GTlsConnectionGnutlsPrivate
+typedef struct
{
+ /* When operating in stream mode, as a GTlsConnection. These are
+ * mutually-exclusive with base_socket. There are two different
+ * GIOStreams here: (a) base_io_stream and (b) the GTlsConnectionGnutls
+ * itself. base_io_stream is the GIOStream used to create the GTlsConnection,
+ * and corresponds to the GTlsConnection::base-io-stream property.
+ * base_istream and base_ostream are the GInputStream and GOutputStream,
+ * respectively, of base_io_stream. These are for the underlying sockets that
+ * don't know about TLS.
+ *
+ * Then the GTlsConnectionGnutls also has tls_istream and tls_ostream which
+ * wrap the aforementioned base streams with a TLS session.
+ *
+ * When operating in datagram mode, none of these are used.
+ */
GIOStream *base_io_stream;
GPollableInputStream *base_istream;
GPollableOutputStream *base_ostream;
-
- GList *ca_list;
- gnutls_certificate_credentials creds;
- gnutls_session session;
- gboolean require_close_notify;
- GTlsRehandshakeMode rehandshake_mode;
- gboolean use_system_certdb;
- gboolean need_handshake, handshaking, ever_handshaked;
- gboolean closing;
-
GInputStream *tls_istream;
GOutputStream *tls_ostream;
- GError *error;
- GCancellable *cancellable;
- gboolean blocking, eof;
- GIOCondition internal_direction;
-};
-
-static void
-g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GTlsConnectionClass *connection_class = G_TLS_CONNECTION_CLASS (klass);
- GIOStreamClass *iostream_class = G_IO_STREAM_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (GTlsConnectionGnutlsPrivate));
+ /* When operating in datagram mode, as a GDtlsConnection, the
+ * GTlsConnectionGnutls is itself the DTLS GDatagramBased. It uses base_socket
+ * for the underlying I/O. It is mutually-exclusive with base_io_stream and
+ * the other streams.
+ */
+ GDatagramBased *base_socket;
- gobject_class->get_property = g_tls_connection_gnutls_get_property;
- gobject_class->set_property = g_tls_connection_gnutls_set_property;
- gobject_class->finalize = g_tls_connection_gnutls_finalize;
+ gnutls_certificate_credentials_t creds;
+ gnutls_session_t session;
- connection_class->handshake = g_tls_connection_gnutls_handshake;
- connection_class->handshake_async = g_tls_connection_gnutls_handshake_async;
- connection_class->handshake_finish = g_tls_connection_gnutls_handshake_finish;
+ GTlsCertificate *certificate, *peer_certificate;
+ GTlsCertificateFlags peer_certificate_errors;
- iostream_class->get_input_stream = g_tls_connection_gnutls_get_input_stream;
- iostream_class->get_output_stream = g_tls_connection_gnutls_get_output_stream;
- iostream_class->close_fn = g_tls_connection_gnutls_close;
- iostream_class->close_async = g_tls_connection_gnutls_close_async;
- iostream_class->close_finish = g_tls_connection_gnutls_close_finish;
+ GMutex verify_certificate_mutex;
+ GCond verify_certificate_condition;
+ gboolean peer_certificate_accepted;
+ gboolean peer_certificate_examined;
- g_object_class_override_property (gobject_class, PROP_BASE_IO_STREAM, "base-io-stream");
- g_object_class_override_property (gobject_class, PROP_REQUIRE_CLOSE_NOTIFY, "require-close-notify");
- g_object_class_override_property (gobject_class, PROP_REHANDSHAKE_MODE, "rehandshake-mode");
- g_object_class_override_property (gobject_class, PROP_USE_SYSTEM_CERTDB, "use-system-certdb");
-}
+ gboolean require_close_notify;
+ GTlsRehandshakeMode rehandshake_mode;
+ gboolean is_system_certdb;
+ GTlsDatabase *database;
+ gboolean database_is_unset;
+
+ /* need_handshake means the next claim_op() will get diverted into
+ * an implicit handshake (unless it's an OP_HANDSHAKE or OP_CLOSE*).
+ * need_finish_handshake means the next claim_op() will get diverted
+ * into finish_handshake() (unless it's an OP_CLOSE*).
+ *
+ * handshaking is TRUE as soon as a handshake thread is queued. For
+ * a sync handshake it becomes FALSE after finish_handshake()
+ * completes in the calling thread, but for an async implicit
+ * handshake, it becomes FALSE (and need_finish_handshake becomes
+ * TRUE) at the end of the handshaking thread (and then the next
+ * non-close op will call finish_handshake()). We can't just wait
+ * for handshake_thread_completed() to run, because it's possible
+ * that its main loop is being blocked by a synchronous op which is
+ * waiting for handshaking to become FALSE...
+ *
+ * started_handshake indicates that the current handshake attempt
+ * got at least as far as calling gnutls_handshake() (and so any
+ * error should be copied to handshake_error and returned on all
+ * future operations). ever_handshaked indicates that TLS has
+ * been successfully negotiated at some point.
+ */
+ gboolean need_handshake, need_finish_handshake, sync_handshake_completed;
+ gboolean started_handshake, handshaking, ever_handshaked;
+ GMainContext *handshake_context;
+ GTask *implicit_handshake;
+ GError *handshake_error;
+ GByteArray *app_data_buf;
+
+ /* read_closed means the read direction has closed; write_closed similarly.
+ * If (and only if) both are set, the entire GTlsConnection is closed. */
+ gboolean read_closing, read_closed;
+ gboolean write_closing, write_closed;
+
+ GTlsInteraction *interaction;
+ gchar *interaction_id;
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ gchar **advertised_protocols;
+ gchar *negotiated_protocol;
+#endif
+
+ GMutex op_mutex;
+ GCancellable *waiting_for_op;
+
+ gboolean reading;
+ gint64 read_timeout;
+ GError *read_error;
+ GCancellable *read_cancellable;
+
+ gboolean writing;
+ gint64 write_timeout;
+ GError *write_error;
+ GCancellable *write_cancellable;
+} GTlsConnectionGnutlsPrivate;
-static void
-g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface)
-{
- iface->init = g_tls_connection_gnutls_initable_init;
-}
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION,
+ G_ADD_PRIVATE (GTlsConnectionGnutls);
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ g_tls_connection_gnutls_initable_iface_init);
+ G_IMPLEMENT_INTERFACE (G_TYPE_DATAGRAM_BASED,
+ g_tls_connection_gnutls_datagram_based_iface_init);
+ G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CONNECTION,
+ g_tls_connection_gnutls_dtls_connection_iface_init);
+ g_tls_connection_gnutls_init_priorities ();
+ );
+
+static gint unique_interaction_id = 0;
static void
g_tls_connection_gnutls_init (GTlsConnectionGnutls *gnutls)
{
- gnutls->priv = G_TYPE_INSTANCE_GET_PRIVATE (gnutls, G_TYPE_TLS_CONNECTION_GNUTLS, GTlsConnectionGnutlsPrivate);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gint unique_id;
+
+ gnutls_certificate_allocate_credentials (&priv->creds);
+
+ g_mutex_init (&priv->verify_certificate_mutex);
+ g_cond_init (&priv->verify_certificate_condition);
+
+ priv->need_handshake = TRUE;
+
+ priv->database_is_unset = TRUE;
+ priv->is_system_certdb = TRUE;
- gnutls_certificate_allocate_credentials (&gnutls->priv->creds);
- gnutls_certificate_set_verify_flags (gnutls->priv->creds,
- GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
+ unique_id = g_atomic_int_add (&unique_interaction_id, 1);
+ priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id);
- gnutls->priv->need_handshake = TRUE;
+ priv->waiting_for_op = g_cancellable_new ();
+ g_cancellable_cancel (priv->waiting_for_op);
+ g_mutex_init (&priv->op_mutex);
}
+/* First field is "fallback", second is "allow unsafe rehandshaking" */
static gnutls_priority_t priorities[2][2];
+#if ENABLE(TIZEN_TV_UPDATE_DEFAULT_PRIORITY)
+#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT:!VERS-SSL3.0:%LATEST_RECORD_VERSION"
+#else
+/* TODO: Get rid of this in favor of gnutls_set_default_priority_append()
+ * when upgrading to GnuTLS 3.6.3.
+ */
+#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT"
+#endif
+
static void
g_tls_connection_gnutls_init_priorities (void)
{
- /* First field is "ssl3 only", second is "allow unsafe rehandshaking" */
+ const gchar *base_priority;
+ gchar *fallback_priority, *unsafe_rehandshake_priority, *fallback_unsafe_rehandshake_priority;
+ const guint *protos;
+ int ret, i, nprotos, fallback_proto;
+
+ base_priority = g_getenv ("G_TLS_GNUTLS_PRIORITY");
+ if (!base_priority)
+ base_priority = DEFAULT_BASE_PRIORITY;
+ ret = gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL);
+ if (ret == GNUTLS_E_INVALID_REQUEST)
+ {
+ g_warning ("G_TLS_GNUTLS_PRIORITY is invalid; ignoring!");
+ base_priority = DEFAULT_BASE_PRIORITY;
+ ret = gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL);
+ g_warn_if_fail (ret == 0);
+ }
- gnutls_priority_init (&priorities[FALSE][FALSE],
- "NORMAL",
- NULL);
- gnutls_priority_init (&priorities[TRUE][FALSE],
- "NORMAL:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0",
- NULL);
- gnutls_priority_init (&priorities[FALSE][TRUE],
- "NORMAL:%UNSAFE_RENEGOTIATION",
- NULL);
- gnutls_priority_init (&priorities[TRUE][TRUE],
- "NORMAL:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0:%UNSAFE_RENEGOTIATION",
- NULL);
+ unsafe_rehandshake_priority = g_strdup_printf ("%s:%%UNSAFE_RENEGOTIATION", base_priority);
+ ret = gnutls_priority_init (&priorities[FALSE][TRUE], unsafe_rehandshake_priority, NULL);
+ g_warn_if_fail (ret == 0);
+ g_free (unsafe_rehandshake_priority);
+
+ /* Figure out the lowest SSl/TLS version supported by base_priority */
+ nprotos = gnutls_priority_protocol_list (priorities[FALSE][FALSE], &protos);
+ fallback_proto = G_MAXUINT;
+ for (i = 0; i < nprotos; i++)
+ {
+ if (protos[i] < fallback_proto)
+ fallback_proto = protos[i];
+ }
+ if (fallback_proto == G_MAXUINT)
+ {
+ g_warning ("All GNUTLS protocol versions disabled?");
+ fallback_priority = g_strdup (base_priority);
+ }
+ else
+ {
+ /* %COMPAT is intentionally duplicated here, to ensure it gets added for
+ * the fallback even if the default priority has been changed. */
+ fallback_priority = g_strdup_printf ("%s:%%COMPAT:!VERS-TLS-ALL:+VERS-%s:%%FALLBACK_SCSV",
+ DEFAULT_BASE_PRIORITY,
+ gnutls_protocol_get_name (fallback_proto));
+ }
+ fallback_unsafe_rehandshake_priority = g_strdup_printf ("%s:%%UNSAFE_RENEGOTIATION",
+ fallback_priority);
+
+ ret = gnutls_priority_init (&priorities[TRUE][FALSE], fallback_priority, NULL);
+ g_warn_if_fail (ret == 0);
+ ret = gnutls_priority_init (&priorities[TRUE][TRUE], fallback_unsafe_rehandshake_priority, NULL);
+ g_warn_if_fail (ret == 0);
+ g_free (fallback_priority);
+ g_free (fallback_unsafe_rehandshake_priority);
}
static void
g_tls_connection_gnutls_set_handshake_priority (GTlsConnectionGnutls *gnutls)
{
- gboolean use_ssl3, unsafe_rehandshake;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gboolean fallback, unsafe_rehandshake;
if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
- use_ssl3 = g_tls_client_connection_get_use_ssl3 (G_TLS_CLIENT_CONNECTION (gnutls));
- unsafe_rehandshake = (gnutls->priv->rehandshake_mode == G_TLS_REHANDSHAKE_UNSAFELY);
- gnutls_priority_set (gnutls->priv->session,
- priorities[use_ssl3][unsafe_rehandshake]);
+ {
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ fallback = g_tls_client_connection_get_use_ssl3 (G_TLS_CLIENT_CONNECTION (gnutls));
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+ }
+ else
+ fallback = FALSE;
+ unsafe_rehandshake = (priv->rehandshake_mode == G_TLS_REHANDSHAKE_UNSAFELY);
+ gnutls_priority_set (priv->session,
+ priorities[fallback][unsafe_rehandshake]);
+}
+
+static gboolean
+g_tls_connection_gnutls_is_dtls (GTlsConnectionGnutls *gnutls)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ return (priv->base_socket != NULL);
}
static gboolean
g_tls_connection_gnutls_initable_init (GInitable *initable,
- GCancellable *cancellable,
- GError **error)
+ GCancellable *cancellable,
+ GError **error)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
+ guint flags = client ? GNUTLS_CLIENT : GNUTLS_SERVER;
int status;
- g_return_val_if_fail (gnutls->priv->base_istream != NULL &&
- gnutls->priv->base_ostream != NULL, FALSE);
+ g_return_val_if_fail ((priv->base_istream == NULL) ==
+ (priv->base_ostream == NULL), FALSE);
+ g_return_val_if_fail ((priv->base_socket == NULL) !=
+ (priv->base_istream == NULL), FALSE);
- /* Make sure gnutls->priv->session has been initialized (it may have
- * already been initialized by a construct-time property setter).
- */
- g_tls_connection_gnutls_get_session (gnutls);
+ /* Check whether to use DTLS or TLS. */
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
+ flags |= GNUTLS_DATAGRAM;
+
+ gnutls_init (&priv->session, flags);
- status = gnutls_credentials_set (gnutls->priv->session,
- GNUTLS_CRD_CERTIFICATE,
- gnutls->priv->creds);
+ gnutls_session_set_ptr (priv->session, gnutls);
+ gnutls_session_set_verify_function (priv->session, verify_certificate_cb);
+
+ status = gnutls_credentials_set (priv->session,
+ GNUTLS_CRD_CERTIFICATE,
+ priv->creds);
if (status != 0)
{
g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS connection: %s"),
- gnutls_strerror (status));
+ _("Could not create TLS connection: %s"),
+ gnutls_strerror (status));
return FALSE;
}
- gnutls_transport_set_push_function (gnutls->priv->session,
- g_tls_connection_gnutls_push_func);
- gnutls_transport_set_pull_function (gnutls->priv->session,
- g_tls_connection_gnutls_pull_func);
- gnutls_transport_set_ptr (gnutls->priv->session, gnutls);
+ gnutls_transport_set_push_function (priv->session,
+ g_tls_connection_gnutls_push_func);
+ gnutls_transport_set_pull_function (priv->session,
+ g_tls_connection_gnutls_pull_func);
+ gnutls_transport_set_pull_timeout_function (priv->session,
+ g_tls_connection_gnutls_pull_timeout_func);
+ gnutls_transport_set_ptr (priv->session, gnutls);
+
+ /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */
+ if (priv->base_socket != NULL)
+ {
+ gnutls_transport_set_vec_push_function (priv->session,
+ g_tls_connection_gnutls_vec_push_func);
+ }
+
+ /* Set reasonable MTU */
+ if (flags & GNUTLS_DATAGRAM)
+ gnutls_dtls_set_mtu (priv->session, 1400);
- gnutls->priv->tls_istream = g_tls_input_stream_gnutls_new (gnutls);
- gnutls->priv->tls_ostream = g_tls_output_stream_gnutls_new (gnutls);
+ /* Create output streams if operating in streaming mode. */
+ if (!(flags & GNUTLS_DATAGRAM))
+ {
+ priv->tls_istream = g_tls_input_stream_gnutls_new (gnutls);
+ priv->tls_ostream = g_tls_output_stream_gnutls_new (gnutls);
+ }
return TRUE;
}
static void
g_tls_connection_gnutls_finalize (GObject *object)
{
- GTlsConnectionGnutls *connection = G_TLS_CONNECTION_GNUTLS (object);
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ g_clear_object (&priv->base_io_stream);
+ g_clear_object (&priv->base_socket);
+
+ g_clear_object (&priv->tls_istream);
+ g_clear_object (&priv->tls_ostream);
- if (connection->priv->base_io_stream)
- g_object_unref (connection->priv->base_io_stream);
+ if (priv->session)
+ gnutls_deinit (priv->session);
+ if (priv->creds)
+ gnutls_certificate_free_credentials (priv->creds);
- if (connection->priv->session)
- gnutls_deinit (connection->priv->session);
+ g_clear_object (&priv->database);
+ g_clear_object (&priv->certificate);
+ g_clear_object (&priv->peer_certificate);
- if (connection->priv->tls_istream)
- g_object_unref (connection->priv->tls_istream);
- if (connection->priv->tls_ostream)
- g_object_unref (connection->priv->tls_ostream);
+ g_mutex_clear (&priv->verify_certificate_mutex);
+ g_cond_clear (&priv->verify_certificate_condition);
- if (connection->priv->creds)
- gnutls_certificate_free_credentials (connection->priv->creds);
- if (connection->priv->error)
- g_error_free (connection->priv->error);
+ g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
+
+ g_free (priv->interaction_id);
+ g_clear_object (&priv->interaction);
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ g_clear_pointer (&priv->advertised_protocols, g_strfreev);
+ g_clear_pointer (&priv->negotiated_protocol, g_free);
+#endif
+
+ g_clear_error (&priv->handshake_error);
+ g_clear_error (&priv->read_error);
+ g_clear_error (&priv->write_error);
+
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
+ /* This must always be NULL here, as it holds a reference to @gnutls as
+ * its source object. However, we clear it anyway just in case this changes
+ * in future. */
+ g_clear_object (&priv->implicit_handshake);
+
+ g_clear_object (&priv->read_cancellable);
+ g_clear_object (&priv->write_cancellable);
+
+ g_clear_object (&priv->waiting_for_op);
+ g_mutex_clear (&priv->op_mutex);
G_OBJECT_CLASS (g_tls_connection_gnutls_parent_class)->finalize (object);
}
static void
g_tls_connection_gnutls_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GTlsBackend *backend;
switch (prop_id)
{
case PROP_BASE_IO_STREAM:
- g_value_set_object (value, gnutls->priv->base_io_stream);
+ g_value_set_object (value, priv->base_io_stream);
+ break;
+
+ case PROP_BASE_SOCKET:
+ g_value_set_object (value, priv->base_socket);
break;
case PROP_REQUIRE_CLOSE_NOTIFY:
- g_value_set_boolean (value, gnutls->priv->require_close_notify);
+ g_value_set_boolean (value, priv->require_close_notify);
break;
case PROP_REHANDSHAKE_MODE:
- g_value_set_enum (value, gnutls->priv->rehandshake_mode);
+ g_value_set_enum (value, priv->rehandshake_mode);
break;
case PROP_USE_SYSTEM_CERTDB:
- g_value_set_boolean (value, gnutls->priv->use_system_certdb);
+ g_value_set_boolean (value, priv->is_system_certdb);
+ break;
+
+ case PROP_DATABASE:
+ if (priv->database_is_unset)
+ {
+ backend = g_tls_backend_get_default ();
+ priv->database = g_tls_backend_get_default_database (backend);
+ priv->database_is_unset = FALSE;
+ }
+ g_value_set_object (value, priv->database);
+ break;
+
+ case PROP_CERTIFICATE:
+ g_value_set_object (value, priv->certificate);
+ break;
+
+ case PROP_INTERACTION:
+ g_value_set_object (value, priv->interaction);
+ break;
+
+ case PROP_PEER_CERTIFICATE:
+ g_value_set_object (value, priv->peer_certificate);
+ break;
+
+ case PROP_PEER_CERTIFICATE_ERRORS:
+ g_value_set_flags (value, priv->peer_certificate_errors);
+ break;
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ case PROP_ADVERTISED_PROTOCOLS:
+ g_value_set_boxed (value, priv->advertised_protocols);
+ break;
+
+ case PROP_NEGOTIATED_PROTOCOL:
+ g_value_set_string (value, priv->negotiated_protocol);
break;
+#endif
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
static void
g_tls_connection_gnutls_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GInputStream *istream;
GOutputStream *ostream;
+ gboolean system_certdb;
+ GTlsBackend *backend;
switch (prop_id)
{
case PROP_BASE_IO_STREAM:
- if (gnutls->priv->base_io_stream)
- {
- g_object_unref (gnutls->priv->base_io_stream);
- gnutls->priv->base_istream = NULL;
- gnutls->priv->base_ostream = NULL;
- }
- gnutls->priv->base_io_stream = g_value_dup_object (value);
- if (!gnutls->priv->base_io_stream)
- return;
-
- istream = g_io_stream_get_input_stream (gnutls->priv->base_io_stream);
- ostream = g_io_stream_get_output_stream (gnutls->priv->base_io_stream);
+ g_assert (g_value_get_object (value) == NULL ||
+ priv->base_socket == NULL);
+
+ if (priv->base_io_stream)
+ {
+ g_object_unref (priv->base_io_stream);
+ priv->base_istream = NULL;
+ priv->base_ostream = NULL;
+ }
+ priv->base_io_stream = g_value_dup_object (value);
+ if (!priv->base_io_stream)
+ return;
+
+ istream = g_io_stream_get_input_stream (priv->base_io_stream);
+ ostream = g_io_stream_get_output_stream (priv->base_io_stream);
if (G_IS_POLLABLE_INPUT_STREAM (istream) &&
- g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (istream)))
- gnutls->priv->base_istream = G_POLLABLE_INPUT_STREAM (istream);
+ g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (istream)))
+ priv->base_istream = G_POLLABLE_INPUT_STREAM (istream);
if (G_IS_POLLABLE_OUTPUT_STREAM (ostream) &&
- g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (ostream)))
- gnutls->priv->base_ostream = G_POLLABLE_OUTPUT_STREAM (ostream);
+ g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (ostream)))
+ priv->base_ostream = G_POLLABLE_OUTPUT_STREAM (ostream);
+ break;
+
+ case PROP_BASE_SOCKET:
+ g_assert (g_value_get_object (value) == NULL ||
+ priv->base_io_stream == NULL);
+
+ g_clear_object (&priv->base_socket);
+ priv->base_socket = g_value_dup_object (value);
break;
case PROP_REQUIRE_CLOSE_NOTIFY:
- gnutls->priv->require_close_notify = g_value_get_boolean (value);
+ priv->require_close_notify = g_value_get_boolean (value);
break;
case PROP_REHANDSHAKE_MODE:
- gnutls->priv->rehandshake_mode = g_value_get_enum (value);
+ priv->rehandshake_mode = g_value_get_enum (value);
break;
case PROP_USE_SYSTEM_CERTDB:
- gnutls->priv->use_system_certdb = g_value_get_boolean (value);
+ system_certdb = g_value_get_boolean (value);
+ if (system_certdb != priv->is_system_certdb)
+ {
+ g_clear_object (&priv->database);
+ if (system_certdb)
+ {
+ backend = g_tls_backend_get_default ();
+ priv->database = g_tls_backend_get_default_database (backend);
+ }
+ priv->is_system_certdb = system_certdb;
+ priv->database_is_unset = FALSE;
+ }
+ break;
+
+ case PROP_DATABASE:
+ g_clear_object (&priv->database);
+ priv->database = g_value_dup_object (value);
+ priv->is_system_certdb = FALSE;
+ priv->database_is_unset = FALSE;
+ break;
+
+ case PROP_CERTIFICATE:
+ if (priv->certificate)
+ g_object_unref (priv->certificate);
+ priv->certificate = g_value_dup_object (value);
+ break;
- gnutls_certificate_free_cas (gnutls->priv->creds);
- if (gnutls->priv->use_system_certdb)
- {
- gnutls_x509_crt_t *cas;
- int num_cas;
+ case PROP_INTERACTION:
+ g_clear_object (&priv->interaction);
+ priv->interaction = g_value_dup_object (value);
+ break;
- g_tls_backend_gnutls_get_system_ca_list_gnutls (&cas, &num_cas);
- gnutls_certificate_set_x509_trust (gnutls->priv->creds, cas, num_cas);
- }
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ case PROP_ADVERTISED_PROTOCOLS:
+ g_clear_pointer (&priv->advertised_protocols, g_strfreev);
+ priv->advertised_protocols = g_value_dup_boxed (value);
break;
+#endif
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
-gnutls_certificate_credentials
+gnutls_certificate_credentials_t
g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *gnutls)
{
- return gnutls->priv->creds;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ return priv->creds;
}
-gnutls_session
+gnutls_session_t
g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *gnutls)
{
- /* Ideally we would initialize gnutls->priv->session from
- * g_tls_connection_gnutls_init(), but we can't tell if it's a
- * client or server connection at that point... And
- * g_tls_connection_gnutls_initiable_init() is too late, because
- * construct-time property setters may need to modify it.
- */
- if (!gnutls->priv->session)
- {
- gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
- gnutls_init (&gnutls->priv->session, client ? GNUTLS_CLIENT : GNUTLS_SERVER);
- }
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- return gnutls->priv->session;
+ return priv->session;
}
void
-g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls,
- gnutls_retr_st *st)
+g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls,
+ gnutls_pcert_st **pcert,
+ unsigned int *pcert_length,
+ gnutls_privkey_t *pkey)
{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GTlsCertificate *cert;
cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (gnutls));
- st->type = GNUTLS_CRT_X509;
if (cert)
{
- GTlsCertificateGnutls *gnutlscert = G_TLS_CERTIFICATE_GNUTLS (cert);
-
- st->ncerts = 1;
- st->cert.x509 = gnutls_malloc (sizeof (gnutls_x509_crt_t));
- st->cert.x509[0] = g_tls_certificate_gnutls_copy_cert (gnutlscert);
- st->key.x509 = g_tls_certificate_gnutls_copy_key (gnutlscert);
- st->deinit_all = TRUE;
+ g_tls_certificate_gnutls_copy (G_TLS_CERTIFICATE_GNUTLS (cert),
+ priv->interaction_id,
+ pcert, pcert_length, pkey);
}
else
- st->ncerts = 0;
+ {
+ *pcert = NULL;
+ *pcert_length = 0;
+ *pkey = NULL;
+ }
}
-GTlsCertificateFlags
-g_tls_connection_gnutls_validate_peer (GTlsConnectionGnutls *gnutls)
+typedef enum {
+ G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
+ G_TLS_CONNECTION_GNUTLS_OP_READ,
+ G_TLS_CONNECTION_GNUTLS_OP_WRITE,
+ G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ,
+ G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE,
+ G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH,
+} GTlsConnectionGnutlsOp;
+
+static gboolean
+claim_op (GTlsConnectionGnutls *gnutls,
+ GTlsConnectionGnutlsOp op,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
{
- int status;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ try_again:
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ g_mutex_lock (&priv->op_mutex);
+
+ if (((op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE ||
+ op == G_TLS_CONNECTION_GNUTLS_OP_READ) &&
+ (priv->read_closing || priv->read_closed)) ||
+ ((op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE ||
+ op == G_TLS_CONNECTION_GNUTLS_OP_WRITE) &&
+ (priv->write_closing || priv->write_closed)))
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
+ _("Connection is closed"));
+ g_mutex_unlock (&priv->op_mutex);
+ return FALSE;
+ }
+
+ if (priv->handshake_error &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE)
+ {
+ if (error)
+ *error = g_error_copy (priv->handshake_error);
+ g_mutex_unlock (&priv->op_mutex);
+ return FALSE;
+ }
+
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE)
+ {
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE &&
+ priv->need_handshake)
+ {
+ priv->need_handshake = FALSE;
+ priv->handshaking = TRUE;
+ if (!do_implicit_handshake (gnutls, timeout, cancellable, error))
+ {
+ g_mutex_unlock (&priv->op_mutex);
+ return FALSE;
+ }
+ }
+
+ if (priv->need_finish_handshake &&
+ priv->implicit_handshake)
+ {
+ GError *my_error = NULL;
+ gboolean success;
+
+ priv->need_finish_handshake = FALSE;
+
+ g_mutex_unlock (&priv->op_mutex);
+ success = finish_handshake (gnutls, priv->implicit_handshake, &my_error);
+ g_clear_object (&priv->implicit_handshake);
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+ g_mutex_lock (&priv->op_mutex);
+
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ &&
+ op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE &&
+ (!success || g_cancellable_set_error_if_cancelled (cancellable, &my_error)))
+ {
+ g_propagate_error (error, my_error);
+ g_mutex_unlock (&priv->op_mutex);
+ return FALSE;
+ }
+
+ g_clear_error (&my_error);
+ }
+ }
- status = gnutls_certificate_verify_peers (gnutls->priv->session);
- return g_tls_certificate_gnutls_convert_flags (status);
+ if (priv->handshaking &&
+ timeout != 0 &&
+ g_main_context_is_owner (priv->handshake_context))
+ {
+ /* Cannot perform a blocking operation during a handshake on the
+ * same thread that triggered the handshake. The only way this can
+ * occur is if the application is doing something weird in its
+ * accept-certificate callback. Allowing a blocking op would stall
+ * the handshake (forever, if there's no timeout). Even a close
+ * op would deadlock here.
+ */
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot perform blocking operation during TLS handshake"));
+ g_mutex_unlock (&priv->op_mutex);
+ return FALSE;
+ }
+
+ if ((op != G_TLS_CONNECTION_GNUTLS_OP_WRITE && priv->reading) ||
+ (op != G_TLS_CONNECTION_GNUTLS_OP_READ && priv->writing) ||
+ (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE && priv->handshaking))
+ {
+ GPollFD fds[2];
+ int nfds;
+ gint64 start_time;
+ gint result = 1; /* if the loop is never entered, it’s as if we cancelled early */
+
+ g_cancellable_reset (priv->waiting_for_op);
+
+ g_mutex_unlock (&priv->op_mutex);
+
+ if (timeout == 0)
+ {
+ /* Intentionally not translated because this is not a fatal error to be
+ * presented to the user, and to avoid this showing up in profiling. */
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
+ return FALSE;
+ }
+
+ g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]);
+ if (g_cancellable_make_pollfd (cancellable, &fds[1]))
+ nfds = 2;
+ else
+ nfds = 1;
+
+ /* Convert from microseconds to milliseconds. */
+ if (timeout != -1)
+ timeout = timeout / 1000;
+
+ /* Poll until cancellation or the timeout is reached. */
+ start_time = g_get_monotonic_time ();
+
+ while (!g_cancellable_is_cancelled (priv->waiting_for_op) &&
+ !g_cancellable_is_cancelled (cancellable))
+ {
+ result = g_poll (fds, nfds, timeout);
+
+ if (result == 0)
+ break;
+ if (result != -1 || errno != EINTR)
+ continue;
+
+ if (timeout != -1)
+ {
+ timeout -= (g_get_monotonic_time () - start_time) / 1000;
+ if (timeout < 0)
+ timeout = 0;
+ }
+ }
+
+ if (nfds > 1)
+ g_cancellable_release_fd (cancellable);
+
+ if (result == 0)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("Socket I/O timed out"));
+ return FALSE;
+ }
+
+ goto try_again;
+ }
+
+ if (op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE)
+ {
+ priv->handshaking = TRUE;
+ priv->need_handshake = FALSE;
+ }
+ if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+ op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ)
+ priv->read_closing = TRUE;
+ if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+ op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE)
+ priv->write_closing = TRUE;
+
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_WRITE)
+ priv->reading = TRUE;
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_READ)
+ priv->writing = TRUE;
+
+ g_mutex_unlock (&priv->op_mutex);
+ return TRUE;
+}
+
+static void
+yield_op (GTlsConnectionGnutls *gnutls,
+ GTlsConnectionGnutlsOp op)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ g_mutex_lock (&priv->op_mutex);
+
+ if (op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE)
+ priv->handshaking = FALSE;
+ if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+ op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ)
+ priv->read_closing = FALSE;
+ if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH ||
+ op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE)
+ priv->write_closing = FALSE;
+
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_WRITE)
+ priv->reading = FALSE;
+ if (op != G_TLS_CONNECTION_GNUTLS_OP_READ)
+ priv->writing = FALSE;
+
+ g_cancellable_cancel (priv->waiting_for_op);
+ g_mutex_unlock (&priv->op_mutex);
}
static void
begin_gnutls_io (GTlsConnectionGnutls *gnutls,
- gboolean blocking,
- GCancellable *cancellable)
+ GIOCondition direction,
+ gint64 timeout,
+ GCancellable *cancellable)
{
- gnutls->priv->blocking = blocking;
- gnutls->priv->cancellable = cancellable;
- gnutls->priv->internal_direction = 0;
- g_clear_error (&gnutls->priv->error);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ g_assert (direction & (G_IO_IN | G_IO_OUT));
+
+ if (direction & G_IO_IN)
+ {
+ priv->read_timeout = timeout;
+ priv->read_cancellable = cancellable;
+ g_clear_error (&priv->read_error);
+ }
+
+ if (direction & G_IO_OUT)
+ {
+ priv->write_timeout = timeout;
+ priv->write_cancellable = cancellable;
+ g_clear_error (&priv->write_error);
+ }
}
static int
end_gnutls_io (GTlsConnectionGnutls *gnutls,
- int status,
- const char *generic_error,
- GError **error)
+ GIOCondition direction,
+ int status,
+ GError **error,
+ const char *err_prefix);
+
+static int
+end_gnutls_io (GTlsConnectionGnutls *gnutls,
+ GIOCondition direction,
+ int status,
+ GError **error,
+ const char *err_prefix)
{
- gnutls->priv->cancellable = NULL;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GError *my_error = NULL;
- if (status >= 0)
+ g_assert (direction & (G_IO_IN | G_IO_OUT));
+ g_assert (!error || !*error);
+
+ /* We intentionally do not check for GNUTLS_E_INTERRUPTED here
+ * Instead, the caller may poll for the source to become ready again.
+ * (Note that GTlsOutputStreamGnutls and GTlsInputStreamGnutls inherit
+ * from GPollableOutputStream and GPollableInputStream, respectively.)
+ * See also the comment in set_gnutls_error().
+ */
+ if (status == GNUTLS_E_AGAIN ||
+ status == GNUTLS_E_WARNING_ALERT_RECEIVED)
+ return GNUTLS_E_AGAIN;
+
+ if (direction & G_IO_IN)
{
- g_clear_error (&gnutls->priv->error);
- return status;
+ priv->read_cancellable = NULL;
+ if (status < 0)
+ {
+ my_error = priv->read_error;
+ priv->read_error = NULL;
+ }
+ else
+ g_clear_error (&priv->read_error);
+ }
+ if (direction & G_IO_OUT)
+ {
+ priv->write_cancellable = NULL;
+ if (status < 0 && !my_error)
+ {
+ my_error = priv->write_error;
+ priv->write_error = NULL;
+ }
+ else
+ g_clear_error (&priv->write_error);
}
- if (gnutls->priv->handshaking && !gnutls->priv->ever_handshaked)
+ if (status >= 0)
+ return status;
+
+ if (priv->handshaking && !priv->ever_handshaked)
{
- if (g_error_matches (gnutls->priv->error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
- status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
- status == GNUTLS_E_FATAL_ALERT_RECEIVED ||
- status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
- {
- g_clear_error (&gnutls->priv->error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
- _("Peer failed to perform TLS handshake"));
- return GNUTLS_E_PULL_ERROR;
- }
+ if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
+ g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) ||
+ status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
+ status == GNUTLS_E_DECRYPTION_FAILED ||
+ status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+ _("Peer failed to perform TLS handshake"));
+ return GNUTLS_E_PULL_ERROR;
+ }
}
- if (gnutls->priv->error)
+ if (my_error)
{
- if (g_error_matches (gnutls->priv->error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- status = GNUTLS_E_AGAIN;
- g_propagate_error (error, gnutls->priv->error);
- gnutls->priv->error = NULL;
+ if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) &&
+ !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+ G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
+ g_propagate_error (error, my_error);
return status;
}
else if (status == GNUTLS_E_REHANDSHAKE)
{
- if (gnutls->priv->rehandshake_mode == G_TLS_REHANDSHAKE_NEVER)
- {
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Peer requested illegal TLS rehandshake"));
- return GNUTLS_E_PULL_ERROR;
- }
-
- gnutls->priv->need_handshake = TRUE;
+ if (priv->rehandshake_mode == G_TLS_REHANDSHAKE_NEVER)
+ {
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Peer requested illegal TLS rehandshake"));
+ return GNUTLS_E_PULL_ERROR;
+ }
+
+ g_mutex_lock (&priv->op_mutex);
+ if (!priv->handshaking)
+ priv->need_handshake = TRUE;
+ g_mutex_unlock (&priv->op_mutex);
+ return status;
+ }
+ else if (status == GNUTLS_E_PREMATURE_TERMINATION)
+ {
+ if (priv->handshaking && !priv->ever_handshaked)
+ {
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+ _("Peer failed to perform TLS handshake"));
+ return GNUTLS_E_PULL_ERROR;
+ }
+ else if (priv->require_close_notify)
+ {
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
+ _("TLS connection closed unexpectedly"));
+ G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
+ return status;
+ }
+ else
+ return 0;
+ }
+ else if (status == GNUTLS_E_NO_CERTIFICATE_FOUND)
+ {
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+ _("TLS connection peer did not send a certificate"));
+ return status;
+ }
+ else if (status == GNUTLS_E_CERTIFICATE_ERROR)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
+ return status;
+ }
+ else if (status == GNUTLS_E_FATAL_ALERT_RECEIVED)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Peer sent fatal TLS alert: %s"),
+ gnutls_alert_get_name (gnutls_alert_get (priv->session)));
return status;
}
- else if (status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH)
+ else if (status == GNUTLS_E_INAPPROPRIATE_FALLBACK)
{
- if (gnutls->priv->eof)
- {
- if (gnutls->priv->require_close_notify)
- {
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
- _("TLS connection closed unexpectedly"));
- return status;
- }
- else
- return 0;
- }
+ g_set_error_literal (error, G_TLS_ERROR,
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ G_TLS_ERROR_INAPPROPRIATE_FALLBACK,
+#else
+ G_TLS_ERROR_MISC,
+#endif
+ _("Protocol version downgrade attack detected"));
+ return status;
+ }
+ else if (status == GNUTLS_E_LARGE_PACKET)
+ {
+ guint mtu = gnutls_dtls_get_data_mtu (priv->session);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+ ngettext ("Message is too large for DTLS connection; maximum is %u byte",
+ "Message is too large for DTLS connection; maximum is %u bytes", mtu), mtu);
+ return status;
+ }
+ else if (status == GNUTLS_E_TIMEDOUT)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("The operation timed out"));
+ return status;
}
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- generic_error, gnutls_strerror (status));
+ if (error)
+ {
+ *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s",
+ err_prefix, gnutls_strerror (status));
+ }
return status;
}
-#define BEGIN_GNUTLS_IO(gnutls, blocking, cancellable) \
- begin_gnutls_io (gnutls, blocking, cancellable); \
+#define BEGIN_GNUTLS_IO(gnutls, direction, timeout, cancellable) \
+ begin_gnutls_io (gnutls, direction, timeout, cancellable); \
do {
-#define END_GNUTLS_IO(gnutls, ret, errmsg, error) \
- } while ((ret == GNUTLS_E_AGAIN || \
- ret == GNUTLS_E_WARNING_ALERT_RECEIVED) && \
- !gnutls->priv->error); \
- ret = end_gnutls_io (gnutls, ret, errmsg, error)
+#define END_GNUTLS_IO(gnutls, direction, ret, errmsg, err) \
+ } while ((ret = end_gnutls_io (gnutls, direction, ret, err, errmsg)) == GNUTLS_E_AGAIN);
+
+/* Checks whether the underlying base stream or GDatagramBased meets
+ * @condition. */
+static gboolean
+g_tls_connection_gnutls_base_check (GTlsConnectionGnutls *gnutls,
+ GIOCondition condition)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
+ return g_datagram_based_condition_check (priv->base_socket,
+ condition);
+ else if (condition & G_IO_IN)
+ return g_pollable_input_stream_is_readable (priv->base_istream);
+ else if (condition & G_IO_OUT)
+ return g_pollable_output_stream_is_writable (priv->base_ostream);
+ else
+ g_assert_not_reached ();
+}
+/* Checks whether the (D)TLS stream meets @condition; not the underlying base
+ * stream or GDatagramBased. */
gboolean
g_tls_connection_gnutls_check (GTlsConnectionGnutls *gnutls,
- GIOCondition condition)
+ GIOCondition condition)
{
- if (gnutls->priv->handshaking || gnutls->priv->closing)
- condition = gnutls->priv->internal_direction;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- if (condition & G_IO_IN)
- return g_pollable_input_stream_is_readable (gnutls->priv->base_istream);
- else
- return g_pollable_output_stream_is_writable (gnutls->priv->base_ostream);
+ /* Racy, but worst case is that we just get WOULD_BLOCK back */
+ if (priv->need_finish_handshake)
+ return TRUE;
+
+ /* If a handshake or close is in progress, then tls_istream and
+ * tls_ostream are blocked, regardless of the base stream status.
+ */
+ if (priv->handshaking)
+ return FALSE;
+
+ if (((condition & G_IO_IN) && priv->read_closing) ||
+ ((condition & G_IO_OUT) && priv->write_closing))
+ return FALSE;
+
+ /* Defer to the base stream or GDatagramBased. */
+ return g_tls_connection_gnutls_base_check (gnutls, condition);
}
typedef struct {
- GSource source;
+ GSource source;
GTlsConnectionGnutls *gnutls;
- GObject *stream;
+ /* Either a GDatagramBased (datagram mode), or a GPollableInputStream or
+ * GPollableOutputStream (streaming mode):
+ */
+ GObject *base;
GSource *child_source;
- GIOCondition base_direction;
- GIOCondition current_direction;
+ GIOCondition condition;
+
+ gboolean io_waiting;
+ gboolean op_waiting;
} GTlsConnectionGnutlsSource;
static gboolean
gnutls_source_prepare (GSource *source,
- gint *timeout)
+ gint *timeout)
{
*timeout = -1;
return FALSE;
return FALSE;
}
+/* Use a custom dummy callback instead of g_source_set_dummy_callback(), as that
+ * uses a GClosure and is slow. (The GClosure is necessary to deal with any
+ * function prototype.) */
+static gboolean
+dummy_callback (gpointer data)
+{
+ return G_SOURCE_CONTINUE;
+}
+
static void
-gnutls_source_sync_child_source (GTlsConnectionGnutlsSource *gnutls_source,
- GIOCondition direction)
+gnutls_source_sync (GTlsConnectionGnutlsSource *gnutls_source)
{
GTlsConnectionGnutls *gnutls = gnutls_source->gnutls;
- GSource *source = (GSource *)gnutls_source;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gboolean io_waiting, op_waiting;
+
+ /* Was the source destroyed earlier in this main context iteration? */
+ if (g_source_is_destroyed ((GSource *)gnutls_source))
+ return;
+
+ g_mutex_lock (&priv->op_mutex);
+ if (((gnutls_source->condition & G_IO_IN) && priv->reading) ||
+ ((gnutls_source->condition & G_IO_OUT) && priv->writing) ||
+ (priv->handshaking && !priv->need_finish_handshake))
+ op_waiting = TRUE;
+ else
+ op_waiting = FALSE;
+
+ if (!op_waiting && !priv->need_handshake &&
+ !priv->need_finish_handshake)
+ io_waiting = TRUE;
+ else
+ io_waiting = FALSE;
+ g_mutex_unlock (&priv->op_mutex);
- if (direction == gnutls_source->current_direction)
+ if (op_waiting == gnutls_source->op_waiting &&
+ io_waiting == gnutls_source->io_waiting)
return;
+ gnutls_source->op_waiting = op_waiting;
+ gnutls_source->io_waiting = io_waiting;
if (gnutls_source->child_source)
{
- g_source_remove_child_source (source, gnutls_source->child_source);
+ g_source_remove_child_source ((GSource *)gnutls_source,
+ gnutls_source->child_source);
g_source_unref (gnutls_source->child_source);
}
- if (direction & G_IO_IN)
- gnutls_source->child_source = g_pollable_input_stream_create_source (gnutls->priv->base_istream, NULL);
+ if (op_waiting)
+ gnutls_source->child_source = g_cancellable_source_new (priv->waiting_for_op);
+ else if (io_waiting && G_IS_DATAGRAM_BASED (gnutls_source->base))
+ gnutls_source->child_source = g_datagram_based_create_source (priv->base_socket, gnutls_source->condition, NULL);
+ else if (io_waiting && G_IS_POLLABLE_INPUT_STREAM (gnutls_source->base))
+ gnutls_source->child_source = g_pollable_input_stream_create_source (priv->base_istream, NULL);
+ else if (io_waiting && G_IS_POLLABLE_OUTPUT_STREAM (gnutls_source->base))
+ gnutls_source->child_source = g_pollable_output_stream_create_source (priv->base_ostream, NULL);
else
- gnutls_source->child_source = g_pollable_output_stream_create_source (gnutls->priv->base_ostream, NULL);
+ gnutls_source->child_source = g_timeout_source_new (0);
- g_source_set_dummy_callback (gnutls_source->child_source);
- g_source_add_child_source (source, gnutls_source->child_source);
- gnutls_source->current_direction = direction;
+ g_source_set_callback (gnutls_source->child_source, dummy_callback, NULL, NULL);
+ g_source_add_child_source ((GSource *)gnutls_source, gnutls_source->child_source);
}
static gboolean
gnutls_source_dispatch (GSource *source,
- GSourceFunc callback,
- gpointer user_data)
+ GSourceFunc callback,
+ gpointer user_data)
{
- GPollableSourceFunc func = (GPollableSourceFunc)callback;
+ GDatagramBasedSourceFunc datagram_based_func = (GDatagramBasedSourceFunc)callback;
+ GPollableSourceFunc pollable_func = (GPollableSourceFunc)callback;
GTlsConnectionGnutlsSource *gnutls_source = (GTlsConnectionGnutlsSource *)source;
- GTlsConnectionGnutls *gnutls = gnutls_source->gnutls;
gboolean ret;
- ret = (*func) (gnutls_source->stream, user_data);
- if (ret)
- {
- GIOCondition direction = gnutls->priv->internal_direction ? gnutls->priv->internal_direction : gnutls_source->base_direction;
+ if (G_IS_DATAGRAM_BASED (gnutls_source->base))
+ ret = (*datagram_based_func) (G_DATAGRAM_BASED (gnutls_source->base),
+ gnutls_source->condition, user_data);
+ else
+ ret = (*pollable_func) (gnutls_source->base, user_data);
- gnutls_source_sync_child_source (gnutls_source, direction);
- }
+ if (ret)
+ gnutls_source_sync (gnutls_source);
return ret;
}
GTlsConnectionGnutlsSource *gnutls_source = (GTlsConnectionGnutlsSource *)source;
g_object_unref (gnutls_source->gnutls);
+ g_source_unref (gnutls_source->child_source);
+}
- if (gnutls_source->child_source)
- g_source_unref (gnutls_source->child_source);
+static gboolean
+g_tls_connection_gnutls_source_closure_callback (GObject *stream,
+ gpointer data)
+{
+ GClosure *closure = data;
+
+ GValue param = { 0, };
+ GValue result_value = { 0, };
+ gboolean result;
+
+ g_value_init (&result_value, G_TYPE_BOOLEAN);
+
+ g_value_init (¶m, G_TYPE_OBJECT);
+ g_value_set_object (¶m, stream);
+
+ g_closure_invoke (closure, &result_value, 1, ¶m, NULL);
+
+ result = g_value_get_boolean (&result_value);
+ g_value_unset (&result_value);
+ g_value_unset (¶m);
+
+ return result;
+}
+
+static gboolean
+g_tls_connection_gnutls_source_dtls_closure_callback (GObject *stream,
+ GIOCondition condition,
+ gpointer data)
+{
+ GClosure *closure = data;
+
+ GValue param[2] = { G_VALUE_INIT, G_VALUE_INIT };
+ GValue result_value = G_VALUE_INIT;
+ gboolean result;
+
+ g_value_init (&result_value, G_TYPE_BOOLEAN);
+
+ g_value_init (¶m[0], G_TYPE_DATAGRAM_BASED);
+ g_value_set_object (¶m[0], stream);
+ g_value_init (¶m[1], G_TYPE_IO_CONDITION);
+ g_value_set_flags (¶m[1], condition);
+
+ g_closure_invoke (closure, &result_value, 2, param, NULL);
+
+ result = g_value_get_boolean (&result_value);
+ g_value_unset (&result_value);
+ g_value_unset (¶m[0]);
+ g_value_unset (¶m[1]);
+
+ return result;
}
-static GSourceFuncs gnutls_source_funcs =
+static GSourceFuncs gnutls_tls_source_funcs =
{
gnutls_source_prepare,
gnutls_source_check,
gnutls_source_dispatch,
- gnutls_source_finalize
+ gnutls_source_finalize,
+ (GSourceFunc)g_tls_connection_gnutls_source_closure_callback,
+ (GSourceDummyMarshal)g_cclosure_marshal_generic
};
-GSource *
-g_tls_connection_gnutls_create_source (GTlsConnectionGnutls *gnutls,
- GIOCondition condition,
- GCancellable *cancellable)
+static GSourceFuncs gnutls_dtls_source_funcs =
{
+ gnutls_source_prepare,
+ gnutls_source_check,
+ gnutls_source_dispatch,
+ gnutls_source_finalize,
+ (GSourceFunc)g_tls_connection_gnutls_source_dtls_closure_callback,
+ (GSourceDummyMarshal)g_cclosure_marshal_generic
+};
+
+GSource *
+g_tls_connection_gnutls_create_source (GTlsConnectionGnutls *gnutls,
+ GIOCondition condition,
+ GCancellable *cancellable)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GSource *source, *cancellable_source;
GTlsConnectionGnutlsSource *gnutls_source;
- source = g_source_new (&gnutls_source_funcs, sizeof (GTlsConnectionGnutlsSource));
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
+ {
+ source = g_source_new (&gnutls_dtls_source_funcs,
+ sizeof (GTlsConnectionGnutlsSource));
+ }
+ else
+ {
+ source = g_source_new (&gnutls_tls_source_funcs,
+ sizeof (GTlsConnectionGnutlsSource));
+ }
g_source_set_name (source, "GTlsConnectionGnutlsSource");
gnutls_source = (GTlsConnectionGnutlsSource *)source;
gnutls_source->gnutls = g_object_ref (gnutls);
- gnutls_source->base_direction = condition & (G_IO_IN | G_IO_OUT);
- if (gnutls_source->base_direction == G_IO_IN)
- gnutls_source->stream = G_OBJECT (gnutls->priv->tls_istream);
- else if (gnutls_source->base_direction == G_IO_OUT)
- gnutls_source->stream = G_OBJECT (gnutls->priv->tls_ostream);
+ gnutls_source->condition = condition;
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
+ gnutls_source->base = G_OBJECT (gnutls);
+ else if (priv->tls_istream != NULL && condition & G_IO_IN)
+ gnutls_source->base = G_OBJECT (priv->tls_istream);
+ else if (priv->tls_ostream != NULL && condition & G_IO_OUT)
+ gnutls_source->base = G_OBJECT (priv->tls_ostream);
else
- {
- gnutls_source->base_direction = gnutls->priv->internal_direction;
- gnutls_source->stream = NULL;
- }
- gnutls_source_sync_child_source (gnutls_source, gnutls_source->base_direction);
+ g_assert_not_reached ();
+
+ gnutls_source->op_waiting = (gboolean) -1;
+ gnutls_source->io_waiting = (gboolean) -1;
+ gnutls_source_sync (gnutls_source);
if (cancellable)
{
return source;
}
+static GSource *
+g_tls_connection_gnutls_dtls_create_source (GDatagramBased *datagram_based,
+ GIOCondition condition,
+ GCancellable *cancellable)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+
+ return g_tls_connection_gnutls_create_source (gnutls, condition, cancellable);
+}
+
+static GIOCondition
+g_tls_connection_gnutls_condition_check (GDatagramBased *datagram_based,
+ GIOCondition condition)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+
+ return (g_tls_connection_gnutls_check (gnutls, condition)) ? condition : 0;
+}
+
+static gboolean
+g_tls_connection_gnutls_condition_wait (GDatagramBased *datagram_based,
+ GIOCondition condition,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GPollFD fds[2];
+ guint n_fds;
+ gint result = 1; /* if the loop is never entered, it’s as if we cancelled early */
+ gint64 start_time;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ /* Convert from microseconds to milliseconds. */
+ if (timeout != -1)
+ timeout = timeout / 1000;
+
+ start_time = g_get_monotonic_time ();
+
+ g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]);
+ n_fds = 1;
+
+ if (g_cancellable_make_pollfd (cancellable, &fds[1]))
+ n_fds++;
+
+ while (!g_tls_connection_gnutls_condition_check (datagram_based, condition) &&
+ !g_cancellable_is_cancelled (cancellable))
+ {
+ result = g_poll (fds, n_fds, timeout);
+ if (result == 0)
+ break;
+ if (result != -1 || errno != EINTR)
+ continue;
+
+ if (timeout != -1)
+ {
+ timeout -= (g_get_monotonic_time () - start_time) / 1000;
+ if (timeout < 0)
+ timeout = 0;
+ }
+ }
+
+ if (n_fds > 1)
+ g_cancellable_release_fd (cancellable);
+
+ if (result == 0)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("Socket I/O timed out"));
+ return FALSE;
+ }
+
+ return !g_cancellable_set_error_if_cancelled (cancellable, error);
+}
+
static void
-set_gnutls_error (GTlsConnectionGnutls *gnutls, GIOCondition direction)
+set_gnutls_error (GTlsConnectionGnutls *gnutls,
+ GError *error)
{
- if (g_error_matches (gnutls->priv->error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- gnutls_transport_set_errno (gnutls->priv->session, EINTR);
- else if (g_error_matches (gnutls->priv->error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
+ * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
+ * reasons, not for actual socket EAGAINs (and we have access
+ * to @error at the higher levels, so we can distinguish them
+ * that way later).
+ */
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ gnutls_transport_set_errno (priv->session, EINTR);
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
{
- gnutls_transport_set_errno (gnutls->priv->session, EAGAIN);
- gnutls->priv->internal_direction = direction;
+ /* Return EAGAIN while handshaking so that GnuTLS handles retries for us
+ * internally in its handshaking code. */
+ if (priv->base_socket && priv->handshaking)
+ gnutls_transport_set_errno (priv->session, EAGAIN);
+ else
+ gnutls_transport_set_errno (priv->session, EINTR);
}
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+ gnutls_transport_set_errno (priv->session, EINTR);
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
+ gnutls_transport_set_errno (priv->session, EMSGSIZE);
else
- gnutls_transport_set_errno (gnutls->priv->session, EIO);
+ gnutls_transport_set_errno (priv->session, EIO);
}
static ssize_t
g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
- void *buf,
- size_t buflen)
+ void *buf,
+ size_t buflen)
{
GTlsConnectionGnutls *gnutls = transport_data;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
ssize_t ret;
- /* If gnutls->priv->error is non-%NULL when we're called, it means
+ /* If priv->read_error is non-%NULL when we're called, it means
* that an error previously occurred, but gnutls decided not to
* propagate it. So it's correct for us to just clear it. (Usually
* this means it ignored an EAGAIN after a short read, and now
* we'll return EAGAIN again, which it will obey this time.)
*/
- g_clear_error (&gnutls->priv->error);
+ g_clear_error (&priv->read_error);
- if (gnutls->priv->blocking)
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
{
- ret = g_input_stream_read (G_INPUT_STREAM (gnutls->priv->base_istream),
- buf, buflen,
- gnutls->priv->cancellable,
- &gnutls->priv->error);
+ GInputVector vector = { buf, buflen };
+ GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
+
+ ret = g_datagram_based_receive_messages (priv->base_socket,
+ &message, 1, 0,
+ priv->handshaking ? 0 : priv->read_timeout,
+ priv->read_cancellable,
+ &priv->read_error);
+
+ if (ret > 0)
+ ret = message.bytes_received;
}
else
{
- ret = g_pollable_input_stream_read_nonblocking (gnutls->priv->base_istream,
- buf, buflen,
- gnutls->priv->cancellable,
- &gnutls->priv->error);
+ ret = g_pollable_stream_read (G_INPUT_STREAM (priv->base_istream),
+ buf, buflen,
+ (priv->read_timeout != 0),
+ priv->read_cancellable,
+ &priv->read_error);
}
if (ret < 0)
- set_gnutls_error (gnutls, G_IO_IN);
- else if (ret == 0)
- gnutls->priv->eof = TRUE;
+ set_gnutls_error (gnutls, priv->read_error);
return ret;
}
static ssize_t
g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data,
- const void *buf,
- size_t buflen)
+ const void *buf,
+ size_t buflen)
{
GTlsConnectionGnutls *gnutls = transport_data;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
ssize_t ret;
/* See comment in pull_func. */
- g_clear_error (&gnutls->priv->error);
+ g_clear_error (&priv->write_error);
- if (gnutls->priv->blocking)
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
{
- ret = g_output_stream_write (G_OUTPUT_STREAM (gnutls->priv->base_ostream),
- buf, buflen,
- gnutls->priv->cancellable,
- &gnutls->priv->error);
+ GOutputVector vector = { buf, buflen };
+ GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
+
+ ret = g_datagram_based_send_messages (priv->base_socket,
+ &message, 1, 0,
+ priv->write_timeout,
+ priv->write_cancellable,
+ &priv->write_error);
+
+ if (ret > 0)
+ ret = message.bytes_sent;
}
else
{
- ret = g_pollable_output_stream_write_nonblocking (gnutls->priv->base_ostream,
- buf, buflen,
- gnutls->priv->cancellable,
- &gnutls->priv->error);
+ ret = g_pollable_stream_write (G_OUTPUT_STREAM (priv->base_ostream),
+ buf, buflen,
+ (priv->write_timeout != 0),
+ priv->write_cancellable,
+ &priv->write_error);
}
+
if (ret < 0)
- set_gnutls_error (gnutls, G_IO_OUT);
+ set_gnutls_error (gnutls, priv->write_error);
return ret;
}
-static gboolean
-handshake_internal (GTlsConnectionGnutls *gnutls,
- gboolean blocking,
- GCancellable *cancellable,
- GError **error)
+static ssize_t
+g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data,
+ const giovec_t *iov,
+ int iovcnt)
{
- int ret;
+ GTlsConnectionGnutls *gnutls = transport_data;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ ssize_t ret;
+ GOutputMessage message = { NULL, };
+ GOutputVector *vectors;
- if (G_IS_TLS_SERVER_CONNECTION_GNUTLS (gnutls) &&
- gnutls->priv->ever_handshaked && !gnutls->priv->need_handshake)
- {
- BEGIN_GNUTLS_IO (gnutls, blocking, cancellable);
- ret = gnutls_rehandshake (gnutls->priv->session);
- END_GNUTLS_IO (gnutls, ret, _("Error performing TLS handshake: %s"), error);
+ /* This function should only be set if we’re using base_socket. */
+ g_assert (priv->base_socket != NULL);
- if (ret != 0)
- return FALSE;
+ /* See comment in pull_func. */
+ g_clear_error (&priv->write_error);
+
+ /* this entire expression will be evaluated at compile time */
+ if (sizeof *iov == sizeof *vectors &&
+ sizeof iov->iov_base == sizeof vectors->buffer &&
+ G_STRUCT_OFFSET (giovec_t, iov_base) ==
+ G_STRUCT_OFFSET (GOutputVector, buffer) &&
+ sizeof iov->iov_len == sizeof vectors->size &&
+ G_STRUCT_OFFSET (giovec_t, iov_len) ==
+ G_STRUCT_OFFSET (GOutputVector, size))
+ /* ABI is compatible */
+ {
+ message.vectors = (GOutputVector *)iov;
+ message.num_vectors = iovcnt;
+ }
+ else
+ /* ABI is incompatible */
+ {
+ gint i;
+
+ message.vectors = g_newa (GOutputVector, iovcnt);
+ for (i = 0; i < iovcnt; i++)
+ {
+ message.vectors[i].buffer = (void *)iov[i].iov_base;
+ message.vectors[i].size = iov[i].iov_len;
+ }
+ message.num_vectors = iovcnt;
}
- g_tls_connection_gnutls_set_handshake_priority (gnutls);
-
- gnutls->priv->handshaking = TRUE;
- G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->begin_handshake (gnutls);
-
- BEGIN_GNUTLS_IO (gnutls, blocking, cancellable);
- ret = gnutls_handshake (gnutls->priv->session);
- END_GNUTLS_IO (gnutls, ret, _("Error performing TLS handshake: %s"), error);
-
- if (ret == GNUTLS_E_AGAIN)
- return FALSE;
+ ret = g_datagram_based_send_messages (priv->base_socket,
+ &message, 1, 0,
+ priv->write_timeout,
+ priv->write_cancellable,
+ &priv->write_error);
- gnutls->priv->handshaking = FALSE;
- gnutls->priv->need_handshake = FALSE;
- gnutls->priv->ever_handshaked = TRUE;
+ if (ret > 0)
+ ret = message.bytes_sent;
+ else if (ret < 0)
+ set_gnutls_error (gnutls, priv->write_error);
- if (ret == 0 &&
- gnutls_certificate_type_get (gnutls->priv->session) == GNUTLS_CRT_X509)
- {
- GTlsCertificate *chain, *cert;
- const gnutls_datum_t *certs;
- unsigned int num_certs;
- int i;
+ return ret;
+}
- certs = gnutls_certificate_get_peers (gnutls->priv->session, &num_certs);
- chain = NULL;
- if (certs)
- {
- for (i = num_certs - 1; i >= 0; i--)
- {
- cert = g_tls_certificate_gnutls_new (&certs[i], chain);
- chain = cert;
- }
- }
+static gboolean
+read_pollable_cb (GPollableInputStream *istream,
+ gpointer user_data)
+{
+ gboolean *read_done = user_data;
- g_tls_connection_set_peer_certificate (G_TLS_CONNECTION (gnutls), chain);
- }
- else
- g_tls_connection_set_peer_certificate (G_TLS_CONNECTION (gnutls), NULL);
+ *read_done = TRUE;
- return G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, ret == 0, error);
+ return G_SOURCE_CONTINUE;
}
static gboolean
-handshake_in_progress_or_failed (GTlsConnectionGnutls *gnutls,
- gboolean blocking,
- GCancellable *cancellable,
- GError **error)
+read_datagram_based_cb (GDatagramBased *datagram_based,
+ GIOCondition condition,
+ gpointer user_data)
{
- if (!(gnutls->priv->need_handshake || gnutls->priv->handshaking))
- return FALSE;
+ gboolean *read_done = user_data;
- return !handshake_internal (gnutls, blocking, cancellable, error);
+ *read_done = TRUE;
+
+ return G_SOURCE_CONTINUE;
}
static gboolean
-g_tls_connection_gnutls_handshake (GTlsConnection *conn,
- GCancellable *cancellable,
- GError **error)
+read_timeout_cb (gpointer user_data)
{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+ gboolean *timed_out = user_data;
+
+ *timed_out = TRUE;
- return handshake_internal (gnutls, TRUE, cancellable, error);
+ return G_SOURCE_REMOVE;
}
-static gboolean
-g_tls_connection_gnutls_handshake_ready (GObject *pollable_stream,
- gpointer user_data)
+static int
+g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
+ unsigned int ms)
{
- GTlsConnectionGnutls *gnutls;
- GSimpleAsyncResult *simple = user_data;
- gboolean success;
- GError *error = NULL;
+ GTlsConnectionGnutls *gnutls = transport_data;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- gnutls = G_TLS_CONNECTION_GNUTLS (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
- g_object_unref (gnutls);
+ /* Fast path. */
+ if (g_tls_connection_gnutls_base_check (gnutls, G_IO_IN) ||
+ g_cancellable_is_cancelled (priv->read_cancellable))
+ return 1;
- success = handshake_internal (gnutls, FALSE, NULL, &error);
- if (!success && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ /* If @ms is 0, GnuTLS wants an instant response, so there’s no need to
+ * construct and query a #GSource. */
+ if (ms > 0)
{
- g_error_free (error);
- return TRUE;
+ GMainContext *ctx = NULL;
+ GSource *read_source = NULL, *timeout_source = NULL;
+ gboolean read_done = FALSE, timed_out = FALSE;
+
+ ctx = g_main_context_new ();
+
+ /* Create a timeout source. */
+ timeout_source = g_timeout_source_new (ms);
+ g_source_set_callback (timeout_source, (GSourceFunc)read_timeout_cb,
+ &timed_out, NULL);
+
+ /* Create a read source. We cannot use g_source_set_ready_time() on this
+ * to combine it with the @timeout_source, as that could mess with the
+ * internals of the #GDatagramBased’s #GSource implementation. */
+ if (g_tls_connection_gnutls_is_dtls (gnutls))
+ {
+ read_source = g_datagram_based_create_source (priv->base_socket, G_IO_IN, NULL);
+ g_source_set_callback (read_source, (GSourceFunc)read_datagram_based_cb,
+ &read_done, NULL);
+ }
+ else
+ {
+ read_source = g_pollable_input_stream_create_source (priv->base_istream, NULL);
+ g_source_set_callback (read_source, (GSourceFunc)read_pollable_cb,
+ &read_done, NULL);
+ }
+
+ g_source_attach (read_source, ctx);
+ g_source_attach (timeout_source, ctx);
+
+ while (!read_done && !timed_out)
+ g_main_context_iteration (ctx, TRUE);
+
+ g_source_destroy (read_source);
+ g_source_destroy (timeout_source);
+
+ g_main_context_unref (ctx);
+ g_source_unref (read_source);
+ g_source_unref (timeout_source);
+
+ /* If @read_source was dispatched due to cancellation, the resulting error
+ * will be handled in g_tls_connection_gnutls_pull_func(). */
+ if (g_tls_connection_gnutls_base_check (gnutls, G_IO_IN) ||
+ g_cancellable_is_cancelled (priv->read_cancellable))
+ return 1;
}
- if (error)
- {
- g_simple_async_result_set_from_error (simple, error);
- g_error_free (error);
- }
- else
- g_simple_async_result_set_op_res_gboolean (simple, success);
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
+ return 0;
+}
- return FALSE;
+static GTlsCertificate *
+get_peer_certificate_from_session (GTlsConnectionGnutls *gnutls)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ const gnutls_datum_t *certs;
+ GTlsCertificateGnutls *chain;
+ unsigned int num_certs;
+
+ certs = gnutls_certificate_get_peers (priv->session, &num_certs);
+ if (!certs || !num_certs)
+ return NULL;
+
+ chain = g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER);
+ if (!chain)
+ return NULL;
+
+ return G_TLS_CERTIFICATE (chain);
}
-static void
-g_tls_connection_gnutls_handshake_async (GTlsConnection *conn,
- int io_priority,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+static GTlsCertificateFlags
+verify_peer_certificate (GTlsConnectionGnutls *gnutls,
+ GTlsCertificate *peer_certificate)
{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
- GSimpleAsyncResult *simple;
- gboolean success;
- GError *error = NULL;
- GSource *source;
+ GTlsConnection *conn = G_TLS_CONNECTION (gnutls);
+ GSocketConnectable *peer_identity;
+ GTlsDatabase *database;
+ GTlsCertificateFlags errors;
+ gboolean is_client;
+
+ is_client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
+
+ if (!is_client)
+ peer_identity = NULL;
+ else if (!g_tls_connection_gnutls_is_dtls (gnutls))
+ peer_identity = g_tls_client_connection_get_server_identity (G_TLS_CLIENT_CONNECTION (gnutls));
+ else
+ peer_identity = g_dtls_client_connection_get_server_identity (G_DTLS_CLIENT_CONNECTION (gnutls));
- simple = g_simple_async_result_new (G_OBJECT (conn), callback, user_data,
- g_tls_connection_gnutls_handshake_async);
- success = handshake_internal (gnutls, FALSE, cancellable, &error);
- if (success)
+ errors = 0;
+
+ database = g_tls_connection_get_database (conn);
+ if (database == NULL)
{
- g_simple_async_result_set_op_res_gboolean (simple, TRUE);
- g_simple_async_result_complete_in_idle (simple);
- g_object_unref (simple);
+ errors |= G_TLS_CERTIFICATE_UNKNOWN_CA;
+ errors |= g_tls_certificate_verify (peer_certificate, peer_identity, NULL);
}
- else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ else
{
- g_simple_async_result_set_from_error (simple, error);
- g_error_free (error);
- g_simple_async_result_complete_in_idle (simple);
- g_object_unref (simple);
+ GError *error = NULL;
+
+ errors |= g_tls_database_verify_chain (database, peer_certificate,
+ is_client ?
+ G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER :
+ G_TLS_DATABASE_PURPOSE_AUTHENTICATE_CLIENT,
+ peer_identity,
+ g_tls_connection_get_interaction (conn),
+ G_TLS_DATABASE_VERIFY_NONE,
+ NULL, &error);
+ if (error)
+ {
+ g_warning ("failure verifying certificate chain: %s",
+ error->message);
+ g_assert (errors != 0);
+ g_clear_error (&error);
+ }
}
- source = g_tls_connection_gnutls_create_source (gnutls, 0, cancellable);
- g_source_set_callback (source,
- (GSourceFunc) g_tls_connection_gnutls_handshake_ready,
- simple, NULL);
- g_source_set_priority (source, io_priority);
- g_source_attach (source, g_main_context_get_thread_default ());
- g_source_unref (source);
+ return errors;
}
-static gboolean
-g_tls_connection_gnutls_handshake_finish (GTlsConnection *conn,
- GAsyncResult *result,
- GError **error)
+static void
+update_peer_certificate_and_compute_errors (GTlsConnectionGnutls *gnutls)
{
- GSimpleAsyncResult *simple;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (conn), g_tls_connection_gnutls_handshake_async), FALSE);
+ /* This function must be called from the handshake context thread
+ * (probably the main thread, NOT the handshake thread) because it
+ * emits notifies that are application-visible.
+ *
+ * verify_certificate_mutex should be locked.
+ */
+ g_assert (priv->handshake_context);
+ g_assert (g_main_context_is_owner (priv->handshake_context));
- simple = G_SIMPLE_ASYNC_RESULT (result);
+ g_clear_object (&priv->peer_certificate);
+ priv->peer_certificate_errors = 0;
- if (g_simple_async_result_propagate_error (simple, error))
- return FALSE;
+ if (gnutls_certificate_type_get (priv->session) == GNUTLS_CRT_X509)
+ {
+ priv->peer_certificate = get_peer_certificate_from_session (gnutls);
+ if (priv->peer_certificate)
+ priv->peer_certificate_errors = verify_peer_certificate (gnutls, priv->peer_certificate);
+ }
- return g_simple_async_result_get_op_res_gboolean (simple);
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate");
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
}
-gssize
-g_tls_connection_gnutls_read (GTlsConnectionGnutls *gnutls,
- void *buffer,
- gsize count,
- gboolean blocking,
- GCancellable *cancellable,
- GError **error)
+static gboolean
+accept_or_reject_peer_certificate (gpointer user_data)
{
- gssize ret;
+ GTlsConnectionGnutls *gnutls = user_data;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gboolean accepted = FALSE;
- again:
- if (handshake_in_progress_or_failed (gnutls, blocking, cancellable, error))
- return -1;
+ g_assert (g_main_context_is_owner (priv->handshake_context));
- BEGIN_GNUTLS_IO (gnutls, blocking, cancellable);
- ret = gnutls_record_recv (gnutls->priv->session, buffer, count);
- END_GNUTLS_IO (gnutls, ret, _("Error reading data from TLS socket: %s"), error);
+ g_mutex_lock (&priv->verify_certificate_mutex);
- if (ret >= 0)
- return ret;
- else if (ret == GNUTLS_E_REHANDSHAKE)
- goto again;
- else
- return -1;
-}
+ update_peer_certificate_and_compute_errors (gnutls);
-gssize
-g_tls_connection_gnutls_write (GTlsConnectionGnutls *gnutls,
- const void *buffer,
- gsize count,
- gboolean blocking,
- GCancellable *cancellable,
- GError **error)
-{
- gssize ret;
+ if (G_IS_TLS_CLIENT_CONNECTION (gnutls) && priv->peer_certificate != NULL)
+ {
+ GTlsCertificateFlags validation_flags;
- again:
- if (handshake_in_progress_or_failed (gnutls, blocking, cancellable, error))
- return -1;
+ if (!g_tls_connection_gnutls_is_dtls (gnutls))
+ validation_flags =
+ g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls));
+ else
+ validation_flags =
+ g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (gnutls));
- BEGIN_GNUTLS_IO (gnutls, blocking, cancellable);
- ret = gnutls_record_send (gnutls->priv->session, buffer, count);
- END_GNUTLS_IO (gnutls, ret, _("Error writing data to TLS socket: %s"), error);
+ if ((priv->peer_certificate_errors & validation_flags) == 0)
+ accepted = TRUE;
+ }
- if (ret >= 0)
- return ret;
- else if (ret == GNUTLS_E_REHANDSHAKE)
- goto again;
- else
- return -1;
-}
+ if (!accepted)
+ {
+ g_main_context_pop_thread_default (priv->handshake_context);
+ accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
+ priv->peer_certificate,
+ priv->peer_certificate_errors);
+ g_main_context_push_thread_default (priv->handshake_context);
+ }
-static GInputStream *
-g_tls_connection_gnutls_get_input_stream (GIOStream *stream)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+ priv->peer_certificate_accepted = accepted;
+
+ /* This has to be the very last statement before signaling the
+ * condition variable because otherwise the code could spuriously
+ * wakeup and continue before we are done here.
+ */
+ priv->peer_certificate_examined = TRUE;
+
+ g_cond_signal (&priv->verify_certificate_condition);
+ g_mutex_unlock (&priv->verify_certificate_mutex);
- return gnutls->priv->tls_istream;
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate");
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+
+ return G_SOURCE_REMOVE;
}
-static GOutputStream *
-g_tls_connection_gnutls_get_output_stream (GIOStream *stream)
+static int
+verify_certificate_cb (gnutls_session_t session)
{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+ GTlsConnectionGnutls *gnutls = gnutls_session_get_ptr (session);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gboolean accepted;
+
+ g_mutex_lock (&priv->verify_certificate_mutex);
+ priv->peer_certificate_examined = FALSE;
+ priv->peer_certificate_accepted = FALSE;
+ g_mutex_unlock (&priv->verify_certificate_mutex);
+
+ /* Invoke the callback on the handshake context's thread. This is
+ * necessary because we need to ensure the accept-certificate signal
+ * is emitted on the original thread.
+ */
+ g_assert (priv->handshake_context);
+ g_main_context_invoke (priv->handshake_context, accept_or_reject_peer_certificate, gnutls);
- return gnutls->priv->tls_ostream;
+ /* We'll block the handshake thread until the original thread has
+ * decided whether to accept the certificate.
+ */
+ g_mutex_lock (&priv->verify_certificate_mutex);
+ while (!priv->peer_certificate_examined)
+ g_cond_wait (&priv->verify_certificate_condition, &priv->verify_certificate_mutex);
+ accepted = priv->peer_certificate_accepted;
+ g_mutex_unlock (&priv->verify_certificate_mutex);
+
+ /* Return 0 for the handshake to continue, non-zero to terminate. */
+ return !accepted;
}
-static gboolean
-close_internal (GTlsConnectionGnutls *gnutls,
- gboolean blocking,
- GCancellable *cancellable,
- GError **error)
+static void
+handshake_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
{
+ GTlsConnectionGnutls *gnutls = object;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GError *error = NULL;
int ret;
+ gint64 start_time;
+ gint64 timeout;
- if (!gnutls->priv->require_close_notify)
- return TRUE;
+ /* A timeout, in microseconds, must be provided as a gint64* task_data. */
+ g_assert (task_data != NULL);
- /* If we haven't finished the initial handshake yet, there's no
- * reason to finish it just so we can close.
- */
- if (gnutls->priv->handshaking && !gnutls->priv->ever_handshaked)
- return TRUE;
+ timeout = *((gint64 *)task_data);
+ start_time = g_get_monotonic_time ();
+ priv->started_handshake = FALSE;
- if (handshake_in_progress_or_failed (gnutls, blocking, cancellable, error))
- return FALSE;
+ if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
+ timeout, cancellable, &error))
+ {
+ g_task_return_error (task, error);
+ return;
+ }
- gnutls->priv->closing = TRUE;
- BEGIN_GNUTLS_IO (gnutls, blocking, cancellable);
- ret = gnutls_bye (gnutls->priv->session, GNUTLS_SHUT_WR);
- END_GNUTLS_IO (gnutls, ret, _("Error performing TLS close: %s"), error);
- if (ret == 0 || !error || !g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- gnutls->priv->closing = FALSE;
+ g_clear_error (&priv->handshake_error);
- return ret == 0;
-}
+ if (priv->ever_handshaked && !priv->implicit_handshake)
+ {
+ if (priv->rehandshake_mode != G_TLS_REHANDSHAKE_UNSAFELY &&
+ !gnutls_safe_renegotiation_status (priv->session))
+ {
+ g_task_return_new_error (task, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Peer does not support safe renegotiation"));
+ return;
+ }
+
+ if (!G_IS_TLS_CLIENT_CONNECTION (gnutls))
+ {
+ /* Adjust the timeout for the next operation in the sequence. */
+ if (timeout > 0)
+ {
+ unsigned int timeout_ms;
+
+ timeout -= (g_get_monotonic_time () - start_time);
+ if (timeout <= 0)
+ timeout = 1;
+
+ /* Convert from microseconds to milliseconds, but ensure the timeout
+ * remains positive. */
+ timeout_ms = (timeout + 999) / 1000;
+
+ gnutls_handshake_set_timeout (priv->session, timeout_ms);
+ gnutls_dtls_set_timeouts (priv->session, 1000 /* default */,
+ timeout_ms);
+ }
+
+ BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
+ ret = gnutls_rehandshake (priv->session);
+ END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
+ _("Error performing TLS handshake"), &error);
+
+ if (error)
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+ }
+ }
-static gboolean
-g_tls_connection_gnutls_close (GIOStream *stream,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+ priv->started_handshake = TRUE;
- if (!close_internal (gnutls, TRUE, cancellable, error))
- return FALSE;
- return g_io_stream_close (gnutls->priv->base_io_stream,
- cancellable, error);
-}
+ if (!priv->ever_handshaked)
+ g_tls_connection_gnutls_set_handshake_priority (gnutls);
-typedef struct {
- GSimpleAsyncResult *simple;
- GCancellable *cancellable;
- int io_priority;
-} AsyncCloseData;
+ /* Adjust the timeout for the next operation in the sequence. */
+ if (timeout > 0)
+ {
+ unsigned int timeout_ms;
-static void
-close_base_stream_cb (GObject *base_stream,
- GAsyncResult *result,
- gpointer user_data)
-{
- gboolean success;
- GError *error = NULL;
- AsyncCloseData *acd = user_data;
+ timeout -= (g_get_monotonic_time () - start_time);
+ if (timeout <= 0)
+ timeout = 1;
- success = g_io_stream_close_finish (G_IO_STREAM (base_stream),
- result, &error);
- if (success)
- g_simple_async_result_set_op_res_gboolean (acd->simple, TRUE);
- else
+ /* Convert from microseconds to milliseconds, but ensure the timeout
+ * remains positive. */
+ timeout_ms = (timeout + 999) / 1000;
+
+ gnutls_handshake_set_timeout (priv->session, timeout_ms);
+ gnutls_dtls_set_timeouts (priv->session, 1000 /* default */,
+ timeout_ms);
+ }
+
+ BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
+ ret = gnutls_handshake (priv->session);
+ if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
{
- g_simple_async_result_set_from_error (acd->simple, error);
- g_error_free (error);
+ guint8 buf[1024];
+
+ /* Got app data while waiting for rehandshake; buffer it and try again */
+ ret = gnutls_record_recv (priv->session, buf, sizeof (buf));
+ if (ret > -1)
+ {
+ if (!priv->app_data_buf)
+ priv->app_data_buf = g_byte_array_new ();
+ g_byte_array_append (priv->app_data_buf, buf, ret);
+ ret = GNUTLS_E_AGAIN;
+ }
}
+ END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
+ _("Error performing TLS handshake"), &error);
+
+ /* This calls the finish_handshake code of GTlsClientConnectionGnutls
+ * or GTlsServerConnectionGnutls. It has nothing to do with
+ * GTlsConnectionGnutls's own finish_handshake function, which still
+ * needs to be called at this point.
+ */
+ G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, &error);
- g_simple_async_result_complete (acd->simple);
- g_object_unref (acd->simple);
- if (acd->cancellable)
- g_object_unref (acd->cancellable);
- g_slice_free (AsyncCloseData, acd);
+ if (error)
+ {
+ g_task_return_error (task, error);
+ }
+ else
+ {
+ priv->ever_handshaked = TRUE;
+ g_task_return_boolean (task, TRUE);
+ }
}
-static gboolean
-g_tls_connection_gnutls_close_ready (GObject *pollable_stream,
- gpointer user_data)
+static void
+begin_handshake (GTlsConnectionGnutls *gnutls)
{
- GTlsConnectionGnutls *gnutls;
- AsyncCloseData *acd = user_data;
- gboolean success;
- GError *error = NULL;
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- gnutls = G_TLS_CONNECTION_GNUTLS (g_async_result_get_source_object (G_ASYNC_RESULT (acd->simple)));
- g_object_unref (gnutls);
+#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG)
+ HWCLOCK_LOG("[BGN] gnutls_verify_peer");
+#endif
- success = close_internal (gnutls, FALSE, NULL, &error);
- if (!success && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ if (priv->advertised_protocols)
{
- g_error_free (error);
- return TRUE;
+ gnutls_datum_t *protocols;
+ int n_protos, i;
+
+ n_protos = g_strv_length (priv->advertised_protocols);
+ protocols = g_new (gnutls_datum_t, n_protos);
+ for (i = 0; priv->advertised_protocols[i]; i++)
+ {
+ protocols[i].size = strlen (priv->advertised_protocols[i]);
+ protocols[i].data = g_memdup (priv->advertised_protocols[i], protocols[i].size);
+ }
+ gnutls_alpn_set_protocols (priv->session, protocols, n_protos, 0);
+ g_free (protocols);
}
+#endif
- if (error)
+#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG)
+ HWCLOCK_LOG("[END] gnutls_verify_peer");
+#endif
+
+ G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->begin_handshake (gnutls);
+}
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+static void
+update_negotiated_protocol (GTlsConnectionGnutls *gnutls)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gchar *orig_negotiated_protocol;
+ gnutls_datum_t protocol;
+
+ /*
+ * Preserve the prior negotiated protocol before clearing it
+ */
+ orig_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
+
+
+ if (gnutls_alpn_get_selected_protocol (priv->session, &protocol) == 0 && protocol.size > 0)
+ priv->negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
+
+ /*
+ * Notify only if the negotiated protocol changed
+ */
+ if (g_strcmp0 (orig_negotiated_protocol, priv->negotiated_protocol) != 0)
+ g_object_notify (G_OBJECT (gnutls), "negotiated-protocol");
+
+ g_free (orig_negotiated_protocol);
+}
+#endif
+
+static gboolean
+finish_handshake (GTlsConnectionGnutls *gnutls,
+ GTask *task,
+ GError **error)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ g_assert (error != NULL);
+
+ if (gnutls_session_is_resumed (priv->session))
{
- g_simple_async_result_set_from_error (acd->simple, error);
- g_simple_async_result_complete (acd->simple);
- g_error_free (error);
- g_object_unref (acd->simple);
- if (acd->cancellable)
- g_object_unref (acd->cancellable);
- g_slice_free (AsyncCloseData, acd);
+ /* Because this session was resumed, we skipped certificate
+ * verification on this handshake, so we missed our earlier
+ * chance to set peer_certificate and peer_certificate_errors.
+ * Do so here instead.
+ *
+ * The certificate has already been accepted, so we don't do
+ * anything with the result here.
+ */
+ g_mutex_lock (&priv->verify_certificate_mutex);
+ update_peer_certificate_and_compute_errors (gnutls);
+ priv->peer_certificate_examined = TRUE;
+ priv->peer_certificate_accepted = TRUE;
+ g_mutex_unlock (&priv->verify_certificate_mutex);
}
- else
+
+ if (g_task_propagate_boolean (task, error) &&
+ priv->peer_certificate && !priv->peer_certificate_accepted)
{
- g_io_stream_close_async (gnutls->priv->base_io_stream,
- acd->io_priority, acd->cancellable,
- close_base_stream_cb, acd);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
}
- return FALSE;
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ if (!*error && priv->advertised_protocols)
+ update_negotiated_protocol (gnutls);
+#endif
+
+ if (*error && priv->started_handshake)
+ priv->handshake_error = g_error_copy (*error);
+
+ return (*error == NULL);
}
static void
-g_tls_connection_gnutls_close_async (GIOStream *stream,
- int io_priority,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+sync_handshake_thread_completed (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
- GSimpleAsyncResult *simple;
- gboolean success;
- GError *error = NULL;
- AsyncCloseData *acd;
- GSource *source;
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- simple = g_simple_async_result_new (G_OBJECT (stream), callback, user_data,
- g_tls_connection_gnutls_close_async);
+ g_assert (g_main_context_is_owner (priv->handshake_context));
- success = close_internal (gnutls, FALSE, cancellable, &error);
- if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- {
- g_simple_async_result_set_from_error (simple, error);
- g_error_free (error);
- g_simple_async_result_complete_in_idle (simple);
- g_object_unref (simple);
- }
+ g_mutex_lock (&priv->op_mutex);
+ priv->sync_handshake_completed = TRUE;
+ g_mutex_unlock (&priv->op_mutex);
- if (error)
- g_error_free (error);
+ g_main_context_wakeup (priv->handshake_context);
+}
- acd = g_slice_new (AsyncCloseData);
- acd->simple = simple;
- acd->cancellable = g_object_ref (cancellable);
- acd->io_priority = io_priority;
+static void
+crank_sync_handshake_context (GTlsConnectionGnutls *gnutls,
+ GCancellable *cancellable)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- if (success)
+ /* need_finish_handshake will be set inside sync_handshake_thread_completed(),
+ * which should only ever be invoked while iterating the handshake context
+ * here. So need_finish_handshake should only change on this thread.
+ */
+ g_mutex_lock (&priv->op_mutex);
+ priv->sync_handshake_completed = FALSE;
+ while (!priv->sync_handshake_completed && !g_cancellable_is_cancelled (cancellable))
{
- g_io_stream_close_async (gnutls->priv->base_io_stream,
- io_priority, cancellable,
- close_base_stream_cb, acd);
- return;
+ g_mutex_unlock (&priv->op_mutex);
+ g_main_context_iteration (priv->handshake_context, TRUE);
+ g_mutex_lock (&priv->op_mutex);
}
+ g_mutex_unlock (&priv->op_mutex);
+}
+
+static gboolean
+g_tls_connection_gnutls_handshake (GTlsConnection *conn,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GTask *task;
+ gboolean success;
+ gint64 *timeout = NULL;
+ GError *my_error = NULL;
+
+ g_assert (priv->handshake_context == NULL);
+ priv->handshake_context = g_main_context_new ();
- source = g_tls_connection_gnutls_create_source (gnutls, 0, acd->cancellable);
- g_source_set_callback (source,
- (GSourceFunc) g_tls_connection_gnutls_close_ready,
- acd, NULL);
- g_source_set_priority (source, acd->io_priority);
- g_source_attach (source, g_main_context_get_thread_default ());
- g_source_unref (source);
+ g_main_context_push_thread_default (priv->handshake_context);
+
+ begin_handshake (gnutls);
+
+ task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL);
+ g_task_set_source_tag (task, g_tls_connection_gnutls_handshake);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ timeout = g_new0 (gint64, 1);
+ *timeout = -1; /* blocking */
+ g_task_set_task_data (task, timeout, g_free);
+
+ g_task_run_in_thread (task, handshake_thread);
+ crank_sync_handshake_context (gnutls, cancellable);
+
+ success = finish_handshake (gnutls, task, &my_error);
+
+ g_main_context_pop_thread_default (priv->handshake_context);
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+ g_object_unref (task);
+
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
+
+ if (my_error)
+ g_propagate_error (error, my_error);
+ return success;
}
static gboolean
-g_tls_connection_gnutls_close_finish (GIOStream *stream,
- GAsyncResult *result,
- GError **error)
+g_tls_connection_gnutls_dtls_handshake (GDtlsConnection *conn,
+ GCancellable *cancellable,
+ GError **error)
{
- GSimpleAsyncResult *simple;
+ return g_tls_connection_gnutls_handshake (G_TLS_CONNECTION (conn),
+ cancellable, error);
+}
- g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (stream), g_tls_connection_gnutls_close_async), FALSE);
+/* In the async version we use two GTasks; one to run handshake_thread() and
+ * then call handshake_thread_completed(), and a second to call the caller's
+ * original callback after we call finish_handshake().
+ */
- simple = G_SIMPLE_ASYNC_RESULT (result);
+static void
+handshake_thread_completed (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *caller_task = user_data;
+ GTlsConnectionGnutls *gnutls = g_task_get_source_object (caller_task);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GError *error = NULL;
+ gboolean need_finish_handshake, success;
- if (g_simple_async_result_propagate_error (simple, error))
- return FALSE;
+ g_mutex_lock (&priv->op_mutex);
+ if (priv->need_finish_handshake)
+ {
+ need_finish_handshake = TRUE;
+ priv->need_finish_handshake = FALSE;
+ }
+ else
+ need_finish_handshake = FALSE;
+ g_mutex_unlock (&priv->op_mutex);
+
+ if (need_finish_handshake)
+ {
+ success = finish_handshake (gnutls, G_TASK (result), &error);
+ if (success)
+ g_task_return_boolean (caller_task, TRUE);
+ else
+ g_task_return_error (caller_task, error);
+ }
+ else if (priv->handshake_error)
+ g_task_return_error (caller_task, g_error_copy (priv->handshake_error));
+ else
+ g_task_return_boolean (caller_task, TRUE);
+
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+ g_object_unref (caller_task);
+}
+
+static void
+async_handshake_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GTlsConnectionGnutls *gnutls = object;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ handshake_thread (task, object, task_data, cancellable);
+
+ g_mutex_lock (&priv->op_mutex);
+ priv->need_finish_handshake = TRUE;
+ /* yield_op will clear handshaking too, but we don't want the
+ * connection to be briefly "handshaking && need_finish_handshake"
+ * after we unlock the mutex.
+ */
+ priv->handshaking = FALSE;
+ g_mutex_unlock (&priv->op_mutex);
+
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
+}
+
+static void
+g_tls_connection_gnutls_handshake_async (GTlsConnection *conn,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (G_TLS_CONNECTION_GNUTLS (conn));
+ GTask *thread_task, *caller_task;
+ gint64 *timeout = NULL;
+
+ g_assert (!priv->handshake_context);
+ priv->handshake_context = g_main_context_ref_thread_default ();
+
+ caller_task = g_task_new (conn, cancellable, callback, user_data);
+ g_task_set_source_tag (caller_task, g_tls_connection_gnutls_handshake_async);
+ g_task_set_priority (caller_task, io_priority);
+
+ begin_handshake (G_TLS_CONNECTION_GNUTLS (conn));
+
+ thread_task = g_task_new (conn, cancellable,
+ handshake_thread_completed, caller_task);
+ g_task_set_source_tag (thread_task, g_tls_connection_gnutls_handshake_async);
+ g_task_set_priority (thread_task, io_priority);
+
+ timeout = g_new0 (gint64, 1);
+ *timeout = -1; /* blocking */
+ g_task_set_task_data (thread_task, timeout, g_free);
+
+ g_task_run_in_thread (thread_task, async_handshake_thread);
+ g_object_unref (thread_task);
+}
+
+static gboolean
+g_tls_connection_gnutls_handshake_finish (GTlsConnection *conn,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, conn), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+g_tls_connection_gnutls_dtls_handshake_async (GDtlsConnection *conn,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_tls_connection_gnutls_handshake_async (G_TLS_CONNECTION (conn), io_priority,
+ cancellable, callback, user_data);
+}
+
+static gboolean
+g_tls_connection_gnutls_dtls_handshake_finish (GDtlsConnection *conn,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_tls_connection_gnutls_handshake_finish (G_TLS_CONNECTION (conn),
+ result, error);
+}
+
+static gboolean
+do_implicit_handshake (GTlsConnectionGnutls *gnutls,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gint64 *thread_timeout = NULL;
+
+ /* We have op_mutex */
+
+ g_assert (priv->handshake_context == NULL);
+ if (timeout != 0)
+ {
+ priv->handshake_context = g_main_context_new ();
+ g_main_context_push_thread_default (priv->handshake_context);
+ }
+ else
+ {
+ priv->handshake_context = g_main_context_ref_thread_default ();
+ }
+
+ g_assert (priv->implicit_handshake == NULL);
+ priv->implicit_handshake = g_task_new (gnutls, cancellable,
+ timeout ? sync_handshake_thread_completed : NULL,
+ NULL);
+ g_task_set_source_tag (priv->implicit_handshake,
+ do_implicit_handshake);
+
+ thread_timeout = g_new0 (gint64, 1);
+ g_task_set_task_data (priv->implicit_handshake,
+ thread_timeout, g_free);
+
+ begin_handshake (gnutls);
+
+ if (timeout != 0)
+ {
+ GError *my_error = NULL;
+ gboolean success;
+
+ /* In the blocking case, run the handshake operation synchronously in
+ * another thread, and delegate handling the timeout to that thread; it
+ * should return G_IO_ERROR_TIMED_OUT iff (timeout > 0) and the operation
+ * times out. If (timeout < 0) it should block indefinitely until the
+ * operation is complete or errors. */
+ *thread_timeout = timeout;
+
+ g_mutex_unlock (&priv->op_mutex);
+
+ g_task_set_return_on_cancel (priv->implicit_handshake, TRUE);
+ g_task_run_in_thread (priv->implicit_handshake, handshake_thread);
+
+ crank_sync_handshake_context (gnutls, cancellable);
+
+ success = finish_handshake (gnutls,
+ priv->implicit_handshake,
+ &my_error);
+
+ g_main_context_pop_thread_default (priv->handshake_context);
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+ g_clear_object (&priv->implicit_handshake);
+
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE);
+
+ g_mutex_lock (&priv->op_mutex);
+
+ if (my_error)
+ g_propagate_error (error, my_error);
+ return success;
+ }
+ else
+ {
+ /* In the non-blocking case, start the asynchronous handshake operation
+ * and return EWOULDBLOCK to the caller, who will handle polling for
+ * completion of the handshake and whatever operation they actually cared
+ * about. Run the actual operation as blocking in its thread. */
+ *thread_timeout = -1; /* blocking */
+
+ g_task_run_in_thread (priv->implicit_handshake,
+ async_handshake_thread);
+
+ /* Intentionally not translated because this is not a fatal error to be
+ * presented to the user, and to avoid this showing up in profiling. */
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
+ return FALSE;
+ }
+}
+
+gssize
+g_tls_connection_gnutls_read (GTlsConnectionGnutls *gnutls,
+ void *buffer,
+ gsize count,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gssize ret;
+
+ if (priv->app_data_buf && !priv->handshaking)
+ {
+ ret = MIN (count, priv->app_data_buf->len);
+ memcpy (buffer, priv->app_data_buf->data, ret);
+ if (ret == priv->app_data_buf->len)
+ g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
+ else
+ g_byte_array_remove_range (priv->app_data_buf, 0, ret);
+ return ret;
+ }
+
+ again:
+ if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
+ timeout, cancellable, error))
+ return -1;
+
+ BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
+ ret = gnutls_record_recv (priv->session, buffer, count);
+ END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket"), error);
+
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ);
+
+ if (ret >= 0)
+ return ret;
+ else if (ret == GNUTLS_E_REHANDSHAKE)
+ goto again;
+ else
+ return -1;
+}
+
+static gsize
+input_vectors_from_gnutls_datum_t (GInputVector *vectors,
+ guint num_vectors,
+ const gnutls_datum_t *datum)
+{
+ guint i;
+ gsize total = 0;
+
+ /* Copy into the receive vectors. */
+ for (i = 0; i < num_vectors && total < datum->size; i++)
+ {
+ gsize count;
+ GInputVector *vec = &vectors[i];
+
+ count = MIN (vec->size, datum->size - total);
+
+ memcpy (vec->buffer, datum->data + total, count);
+ total += count;
+ }
+
+ g_assert (total <= datum->size);
+
+ return total;
+}
+
+static gssize
+g_tls_connection_gnutls_read_message (GTlsConnectionGnutls *gnutls,
+ GInputVector *vectors,
+ guint num_vectors,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ guint i;
+ gssize ret;
+ gnutls_packet_t packet = { 0, };
+
+ /* Copy data out of the app data buffer first. */
+ if (priv->app_data_buf && !priv->handshaking)
+ {
+ ret = 0;
+
+ for (i = 0; i < num_vectors; i++)
+ {
+ gsize count;
+ GInputVector *vec = &vectors[i];
+
+ count = MIN (vec->size, priv->app_data_buf->len);
+ ret += count;
+
+ memcpy (vec->buffer, priv->app_data_buf->data, count);
+ if (count == priv->app_data_buf->len)
+ g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
+ else
+ g_byte_array_remove_range (priv->app_data_buf, 0, count);
+ }
+
+ return ret;
+ }
+
+ again:
+ if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ,
+ timeout, cancellable, error))
+ return -1;
+
+ BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
+
+ /* Receive the entire datagram (zero-copy). */
+ ret = gnutls_record_recv_packet (priv->session, &packet);
+
+ if (ret > 0)
+ {
+ gnutls_datum_t data = { 0, };
+
+ gnutls_packet_get (packet, &data, NULL);
+ ret = input_vectors_from_gnutls_datum_t (vectors, num_vectors, &data);
+ gnutls_packet_deinit (packet);
+ }
+
+ END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket"), error);
+
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ);
+
+ if (ret >= 0)
+ return ret;
+ else if (ret == GNUTLS_E_REHANDSHAKE)
+ goto again;
+ else
+ return -1;
+}
+
+static gint
+g_tls_connection_gnutls_receive_messages (GDatagramBased *datagram_based,
+ GInputMessage *messages,
+ guint num_messages,
+ gint flags,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutls *gnutls;
+ guint i;
+ GError *child_error = NULL;
+
+ gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+
+ if (flags != G_SOCKET_MSG_NONE)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Receive flags are not supported"));
+ return -1;
+ }
+
+ for (i = 0; i < num_messages && child_error == NULL; i++)
+ {
+ GInputMessage *message = &messages[i];
+ gssize n_bytes_read;
+
+ n_bytes_read = g_tls_connection_gnutls_read_message (gnutls,
+ message->vectors,
+ message->num_vectors,
+ timeout,
+ cancellable,
+ &child_error);
+
+ if (message->address != NULL)
+ *message->address = NULL;
+ message->flags = G_SOCKET_MSG_NONE;
+ if (message->control_messages != NULL)
+ *message->control_messages = NULL;
+ message->num_control_messages = 0;
+
+ if (n_bytes_read > 0)
+ {
+ message->bytes_received = n_bytes_read;
+ }
+ else if (n_bytes_read == 0)
+ {
+ /* EOS. */
+ break;
+ }
+ else if (i > 0 &&
+ (g_error_matches (child_error,
+ G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
+ g_error_matches (child_error,
+ G_IO_ERROR, G_IO_ERROR_TIMED_OUT)))
+ {
+ /* Blocked or timed out after receiving some messages successfully. */
+ g_clear_error (&child_error);
+ break;
+ }
+ else
+ {
+ /* Error, including G_IO_ERROR_WOULD_BLOCK or G_IO_ERROR_TIMED_OUT on
+ * the first message; or G_IO_ERROR_CANCELLED at any time. */
+ break;
+ }
+ }
+
+ if (child_error != NULL)
+ {
+ g_propagate_error (error, child_error);
+ return -1;
+ }
+
+ return i;
+}
+
+gssize
+g_tls_connection_gnutls_write (GTlsConnectionGnutls *gnutls,
+ const void *buffer,
+ gsize count,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gssize ret;
+
+ again:
+ if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
+ timeout, cancellable, error))
+ return -1;
+
+ BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
+ ret = gnutls_record_send (priv->session, buffer, count);
+ END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket"), error);
+
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE);
+
+ if (ret >= 0)
+ return ret;
+ else if (ret == GNUTLS_E_REHANDSHAKE)
+ goto again;
+ else
+ return -1;
+}
+
+static gssize
+g_tls_connection_gnutls_write_message (GTlsConnectionGnutls *gnutls,
+ GOutputVector *vectors,
+ guint num_vectors,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gssize ret;
+ guint i;
+ gsize total_message_size;
+
+ again:
+ if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE,
+ timeout, cancellable, error))
+ return -1;
+
+ /* Calculate the total message size and check it’s not too big. */
+ for (i = 0, total_message_size = 0; i < num_vectors; i++)
+ total_message_size += vectors[i].size;
+
+ if (priv->base_socket != NULL &&
+ gnutls_dtls_get_data_mtu (priv->session) < total_message_size)
+ {
+ char *message;
+ guint mtu = gnutls_dtls_get_data_mtu (priv->session);
+
+ ret = GNUTLS_E_LARGE_PACKET;
+ message = g_strdup_printf("%s %s",
+ ngettext ("Message of size %lu byte is too large for DTLS connection",
+ "Message of size %lu bytes is too large for DTLS connection", total_message_size),
+ ngettext ("(maximum is %u byte)", "(maximum is %u bytes)", mtu));
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+ message,
+ total_message_size,
+ mtu);
+ g_free (message);
+
+ goto done;
+ }
+
+ /* Queue up the data from all the vectors. */
+ gnutls_record_cork (priv->session);
+
+ for (i = 0; i < num_vectors; i++)
+ {
+ ret = gnutls_record_send (priv->session,
+ vectors[i].buffer, vectors[i].size);
+
+ if (ret < 0 || ret < vectors[i].size)
+ {
+ /* Uncork to restore state, then bail. The peer will receive a
+ * truncated datagram. */
+ break;
+ }
+ }
+
+ BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
+ ret = gnutls_record_uncork (priv->session, 0 /* flags */);
+ END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket"), error);
+
+ done:
+ yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE);
+
+ if (ret >= 0)
+ return ret;
+ else if (ret == GNUTLS_E_REHANDSHAKE)
+ goto again;
+ else
+ return -1;
+}
+
+static gint
+g_tls_connection_gnutls_send_messages (GDatagramBased *datagram_based,
+ GOutputMessage *messages,
+ guint num_messages,
+ gint flags,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutls *gnutls;
+ guint i;
+ GError *child_error = NULL;
+
+ gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based);
+
+ if (flags != G_SOCKET_MSG_NONE)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Send flags are not supported"));
+ return -1;
+ }
+
+ for (i = 0; i < num_messages && child_error == NULL; i++)
+ {
+ GOutputMessage *message = &messages[i];
+ gssize n_bytes_sent;
+
+ n_bytes_sent = g_tls_connection_gnutls_write_message (gnutls,
+ message->vectors,
+ message->num_vectors,
+ timeout,
+ cancellable,
+ &child_error);
+
+ if (n_bytes_sent >= 0)
+ {
+ message->bytes_sent = n_bytes_sent;
+ }
+ else if (i > 0 &&
+ (g_error_matches (child_error,
+ G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
+ g_error_matches (child_error,
+ G_IO_ERROR, G_IO_ERROR_TIMED_OUT)))
+ {
+ /* Blocked or timed out after sending some messages successfully. */
+ g_clear_error (&child_error);
+ break;
+ }
+ else
+ {
+ /* Error, including G_IO_ERROR_WOULD_BLOCK or G_IO_ERROR_TIMED_OUT
+ * on the first message; or G_IO_ERROR_CANCELLED at any time. */
+ break;
+ }
+ }
+
+ if (child_error != NULL)
+ {
+ g_propagate_error (error, child_error);
+ return -1;
+ }
+
+ return i;
+}
+
+static GInputStream *
+g_tls_connection_gnutls_get_input_stream (GIOStream *stream)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ return priv->tls_istream;
+}
+
+static GOutputStream *
+g_tls_connection_gnutls_get_output_stream (GIOStream *stream)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ return priv->tls_ostream;
+}
+
+gboolean
+g_tls_connection_gnutls_close_internal (GIOStream *stream,
+ GTlsDirection direction,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GTlsConnectionGnutlsOp op;
+ gboolean success = TRUE;
+ int ret = 0;
+ GError *gnutls_error = NULL, *stream_error = NULL;
+
+ /* This can be called from g_io_stream_close(), g_input_stream_close(),
+ * g_output_stream_close() or g_tls_connection_close(). In all cases, we only
+ * do the gnutls_bye() for writing. The difference is how we set the flags on
+ * this class and how the underlying stream is closed.
+ */
+
+ g_return_val_if_fail (direction != G_TLS_DIRECTION_NONE, FALSE);
+
+ if (direction == G_TLS_DIRECTION_BOTH)
+ op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH;
+ else if (direction == G_TLS_DIRECTION_READ)
+ op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ;
+ else
+ op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE;
+
+ if (!claim_op (gnutls, op, timeout, cancellable, error))
+ return FALSE;
+
+ if (priv->ever_handshaked && !priv->write_closed &&
+ direction & G_TLS_DIRECTION_WRITE)
+ {
+ BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
+ ret = gnutls_bye (priv->session, GNUTLS_SHUT_WR);
+ END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
+ _("Error performing TLS close"), &gnutls_error);
+
+ priv->write_closed = TRUE;
+ }
+
+ if (!priv->read_closed && direction & G_TLS_DIRECTION_READ)
+ priv->read_closed = TRUE;
+
+ /* Close the underlying streams. Do this even if the gnutls_bye() call failed,
+ * as the parent GIOStream will have set its internal closed flag and hence
+ * this implementation will never be called again. */
+ if (priv->base_io_stream != NULL)
+ {
+ if (direction == G_TLS_DIRECTION_BOTH)
+ success = g_io_stream_close (priv->base_io_stream,
+ cancellable, &stream_error);
+ else if (direction & G_TLS_DIRECTION_READ)
+ success = g_input_stream_close (g_io_stream_get_input_stream (priv->base_io_stream),
+ cancellable, &stream_error);
+ else if (direction & G_TLS_DIRECTION_WRITE)
+ success = g_output_stream_close (g_io_stream_get_output_stream (priv->base_io_stream),
+ cancellable, &stream_error);
+ }
+ else if (g_tls_connection_gnutls_is_dtls (gnutls))
+ {
+ /* We do not close underlying #GDatagramBaseds. There is no
+ * g_datagram_based_close() method since different datagram-based
+ * protocols vary wildly in how they close. */
+ success = TRUE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ yield_op (gnutls, op);
+
+ /* Propagate errors. */
+ if (ret != 0)
+ {
+ g_propagate_error (error, gnutls_error);
+ g_clear_error (&stream_error);
+ }
+ else if (!success)
+ {
+ g_propagate_error (error, stream_error);
+ g_clear_error (&gnutls_error);
+ }
+
+ return success && (ret == 0);
+}
+
+static gboolean
+g_tls_connection_gnutls_close (GIOStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return g_tls_connection_gnutls_close_internal (stream,
+ G_TLS_DIRECTION_BOTH,
+ -1, /* blocking */
+ cancellable, error);
+}
+
+static gboolean
+g_tls_connection_gnutls_dtls_shutdown (GDtlsConnection *conn,
+ gboolean shutdown_read,
+ gboolean shutdown_write,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsDirection direction = G_TLS_DIRECTION_NONE;
+
+ if (shutdown_read)
+ direction |= G_TLS_DIRECTION_READ;
+ if (shutdown_write)
+ direction |= G_TLS_DIRECTION_WRITE;
+
+ return g_tls_connection_gnutls_close_internal (G_IO_STREAM (conn),
+ direction,
+ -1, /* blocking */
+ cancellable, error);
+}
+
+/* We do async close as synchronous-in-a-thread so we don't need to
+ * implement G_IO_IN/G_IO_OUT flip-flopping just for this one case
+ * (since handshakes are also done synchronously now).
+ */
+static void
+close_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GIOStream *stream = object;
+ GTlsDirection direction;
+ GError *error = NULL;
+
+ direction = GPOINTER_TO_INT (g_task_get_task_data (task));
+
+ if (!g_tls_connection_gnutls_close_internal (stream, direction,
+ -1, /* blocking */
+ cancellable, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+g_tls_connection_gnutls_close_internal_async (GIOStream *stream,
+ GTlsDirection direction,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (stream, cancellable, callback, user_data);
+ g_task_set_source_tag (task, g_tls_connection_gnutls_close_internal_async);
+ g_task_set_priority (task, io_priority);
+ g_task_set_task_data (task, GINT_TO_POINTER (direction), NULL);
+ g_task_run_in_thread (task, close_thread);
+ g_object_unref (task);
+}
+
+static void
+g_tls_connection_gnutls_close_async (GIOStream *stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_tls_connection_gnutls_close_internal_async (stream, G_TLS_DIRECTION_BOTH,
+ io_priority, cancellable,
+ callback, user_data);
+}
+
+static gboolean
+g_tls_connection_gnutls_close_finish (GIOStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+g_tls_connection_gnutls_dtls_shutdown_async (GDtlsConnection *conn,
+ gboolean shutdown_read,
+ gboolean shutdown_write,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTlsDirection direction = G_TLS_DIRECTION_NONE;
+
+ if (shutdown_read)
+ direction |= G_TLS_DIRECTION_READ;
+ if (shutdown_write)
+ direction |= G_TLS_DIRECTION_WRITE;
+
+ g_tls_connection_gnutls_close_internal_async (G_IO_STREAM (conn), direction,
+ io_priority, cancellable,
+ callback, user_data);
+}
+
+static gboolean
+g_tls_connection_gnutls_dtls_shutdown_finish (GDtlsConnection *conn,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, conn), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+#if GLIB_CHECK_VERSION(2, 60, 0)
+static void
+g_tls_connection_gnutls_dtls_set_advertised_protocols (GDtlsConnection *conn,
+ const gchar * const *protocols)
+{
+ g_object_set (conn, "advertised-protocols", protocols, NULL);
+}
+
+const gchar *
+g_tls_connection_gnutls_dtls_get_negotiated_protocol (GDtlsConnection *conn)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ return priv->negotiated_protocol;
+}
+#endif
+
+static void
+g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GTlsConnectionClass *connection_class = G_TLS_CONNECTION_CLASS (klass);
+ GIOStreamClass *iostream_class = G_IO_STREAM_CLASS (klass);
+
+ gobject_class->get_property = g_tls_connection_gnutls_get_property;
+ gobject_class->set_property = g_tls_connection_gnutls_set_property;
+ gobject_class->finalize = g_tls_connection_gnutls_finalize;
+
+ connection_class->handshake = g_tls_connection_gnutls_handshake;
+ connection_class->handshake_async = g_tls_connection_gnutls_handshake_async;
+ connection_class->handshake_finish = g_tls_connection_gnutls_handshake_finish;
+
+ iostream_class->get_input_stream = g_tls_connection_gnutls_get_input_stream;
+ iostream_class->get_output_stream = g_tls_connection_gnutls_get_output_stream;
+ iostream_class->close_fn = g_tls_connection_gnutls_close;
+ iostream_class->close_async = g_tls_connection_gnutls_close_async;
+ iostream_class->close_finish = g_tls_connection_gnutls_close_finish;
+
+ /* For GTlsConnection and GDtlsConnection: */
+ g_object_class_override_property (gobject_class, PROP_BASE_IO_STREAM, "base-io-stream");
+ g_object_class_override_property (gobject_class, PROP_BASE_SOCKET, "base-socket");
+ g_object_class_override_property (gobject_class, PROP_REQUIRE_CLOSE_NOTIFY, "require-close-notify");
+ g_object_class_override_property (gobject_class, PROP_REHANDSHAKE_MODE, "rehandshake-mode");
+ g_object_class_override_property (gobject_class, PROP_USE_SYSTEM_CERTDB, "use-system-certdb");
+ g_object_class_override_property (gobject_class, PROP_DATABASE, "database");
+ g_object_class_override_property (gobject_class, PROP_CERTIFICATE, "certificate");
+ g_object_class_override_property (gobject_class, PROP_INTERACTION, "interaction");
+ g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE, "peer-certificate");
+ g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE_ERRORS, "peer-certificate-errors");
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ g_object_class_override_property (gobject_class, PROP_ADVERTISED_PROTOCOLS, "advertised-protocols");
+ g_object_class_override_property (gobject_class, PROP_NEGOTIATED_PROTOCOL, "negotiated-protocol");
+#endif
+}
+
+static void
+g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = g_tls_connection_gnutls_initable_init;
+}
+
+static void
+g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface)
+{
+ iface->handshake = g_tls_connection_gnutls_dtls_handshake;
+ iface->handshake_async = g_tls_connection_gnutls_dtls_handshake_async;
+ iface->handshake_finish = g_tls_connection_gnutls_dtls_handshake_finish;
+ iface->shutdown = g_tls_connection_gnutls_dtls_shutdown;
+ iface->shutdown_async = g_tls_connection_gnutls_dtls_shutdown_async;
+ iface->shutdown_finish = g_tls_connection_gnutls_dtls_shutdown_finish;
+#if GLIB_CHECK_VERSION(2, 60, 0)
+ iface->set_advertised_protocols = g_tls_connection_gnutls_dtls_set_advertised_protocols;
+ iface->get_negotiated_protocol = g_tls_connection_gnutls_dtls_get_negotiated_protocol;
+#endif
+}
+
+static void
+g_tls_connection_gnutls_datagram_based_iface_init (GDatagramBasedInterface *iface)
+{
+ iface->receive_messages = g_tls_connection_gnutls_receive_messages;
+ iface->send_messages = g_tls_connection_gnutls_send_messages;
+ iface->create_source = g_tls_connection_gnutls_dtls_create_source;
+ iface->condition_check = g_tls_connection_gnutls_condition_check;
+ iface->condition_wait = g_tls_connection_gnutls_condition_wait;
+}
+
+gboolean
+g_tls_connection_gnutls_request_certificate (GTlsConnectionGnutls *gnutls,
+ GError **error)
+{
+ GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ GTlsInteraction *interaction;
+ GTlsConnection *conn;
+
+ g_return_val_if_fail (G_IS_TLS_CONNECTION_GNUTLS (gnutls), FALSE);
+
+ conn = G_TLS_CONNECTION (gnutls);
+
+ interaction = g_tls_connection_get_interaction (conn);
+ if (!interaction)
+ return FALSE;
+
+ res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0,
+ priv->read_cancellable, error);
+ return res != G_TLS_INTERACTION_FAILED;
+}
+
+void
+GTLS_DEBUG (gpointer gnutls,
+ const char *message,
+ ...)
+{
+ char *result = NULL;
+ int ret;
+
+ g_assert (G_IS_TLS_CONNECTION (gnutls));
+
+ va_list args;
+ va_start (args, message);
+
+ ret = g_vasprintf (&result, message, args);
+ g_assert (ret > 0);
+
+ if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
+ g_printf ("CLIENT %p: ", gnutls);
+ else if (G_IS_TLS_SERVER_CONNECTION (gnutls))
+ g_printf ("SERVER %p: ", gnutls);
+ else
+ g_assert_not_reached ();
+
+ g_printf ("%s\n", result);
+
+ fflush (stdout);
- return g_simple_async_result_get_op_res_gboolean (simple);
+ g_free (result);
+ va_end (args);
}