/* -*- 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 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.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
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* 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, 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 HAVE_PKCS11
#include <p11-kit/pin.h>
#include "pkcs11/gpkcs11pin.h"
#endif
#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>
/*
* 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 ssize_t g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data,
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);
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);
static void g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface);
static void g_tls_connection_gnutls_datagram_based_iface_init (GDatagramBasedInterface *iface);
#ifdef HAVE_PKCS11
static P11KitPin* on_pin_prompt_callback (const char *pinfile,
P11KitUri *pin_uri,
const char *pin_description,
P11KitPinFlags pin_flags,
void *callback_data);
#endif
static void g_tls_connection_gnutls_init_priorities (void);
static gboolean do_implicit_handshake (GTlsConnectionGnutls *gnutls,
gint64 timeout,
GCancellable *cancellable,
GError **error);
static gboolean finish_handshake (GTlsConnectionGnutls *gnutls,
GTask *thread_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_DATABASE,
PROP_CERTIFICATE,
PROP_INTERACTION,
PROP_PEER_CERTIFICATE,
PROP_PEER_CERTIFICATE_ERRORS,
};
typedef struct
{
/* When operating in stream mode.
* Mutually exclusive with base_socket.
*/
GIOStream *base_io_stream;
GPollableInputStream *base_istream;
GPollableOutputStream *base_ostream;
/* When operating in stream mode; when operating in datagram mode, the
* GTlsConnectionGnutls itself is the DTLS GDatagramBased (and uses
* base_socket for its underlying I/O):
*/
GInputStream *tls_istream;
GOutputStream *tls_ostream;
/* When operating in datagram mode.
* Mutually exclusive with base_io_stream.
*/
GDatagramBased *base_socket;
gnutls_certificate_credentials_t creds;
gnutls_session_t session;
GTlsCertificate *certificate, *peer_certificate;
GTlsCertificateFlags peer_certificate_errors;
GTlsCertificate *peer_certificate_tmp;
GTlsCertificateFlags peer_certificate_errors_tmp;
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;
gboolean started_handshake, handshaking, ever_handshaked;
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;
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;
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)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
gint unique_id;
gnutls_certificate_allocate_credentials (&priv->creds);
gnutls_certificate_set_verify_flags (priv->creds,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
priv->need_handshake = TRUE;
priv->database_is_unset = TRUE;
priv->is_system_certdb = TRUE;
unique_id = g_atomic_int_add (&unique_interaction_id, 1);
priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id);
#ifdef HAVE_PKCS11
p11_kit_pin_register_callback (priv->interaction_id,
on_pin_prompt_callback, gnutls, NULL);
#endif
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];
#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT"
static void
g_tls_connection_gnutls_init_priorities (void)
{
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);
}
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",
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)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
gboolean fallback, unsafe_rehandshake;
if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
{
#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)
{
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 ((priv->base_istream == NULL) ==
(priv->base_ostream == NULL), FALSE);
g_return_val_if_fail ((priv->base_socket == NULL) !=
(priv->base_istream == NULL), FALSE);
/* 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 (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));
return FALSE;
}
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);
/* 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 *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 (priv->session)
gnutls_deinit (priv->session);
if (priv->creds)
gnutls_certificate_free_credentials (priv->creds);
g_clear_object (&priv->database);
g_clear_object (&priv->certificate);
g_clear_object (&priv->peer_certificate);
g_clear_object (&priv->peer_certificate_tmp);
g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
#ifdef HAVE_PKCS11
p11_kit_pin_unregister_callback (priv->interaction_id,
on_pin_prompt_callback, gnutls);
#endif
g_free (priv->interaction_id);
g_clear_object (&priv->interaction);
g_clear_error (&priv->handshake_error);
g_clear_error (&priv->read_error);
g_clear_error (&priv->write_error);
/* This must always be NULL at this, as it holds a referehce 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)
{
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, 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, priv->require_close_notify);
break;
case PROP_REHANDSHAKE_MODE:
g_value_set_enum (value, priv->rehandshake_mode);
break;
case PROP_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;
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)
{
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:
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)))
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)))
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:
priv->require_close_notify = g_value_get_boolean (value);
break;
case PROP_REHANDSHAKE_MODE:
priv->rehandshake_mode = g_value_get_enum (value);
break;
case PROP_USE_SYSTEM_CERTDB:
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;
case PROP_INTERACTION:
g_clear_object (&priv->interaction);
priv->interaction = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
gnutls_certificate_credentials_t
g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *gnutls)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
return priv->creds;
}
gnutls_session_t
g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *gnutls)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
return priv->session;
}
void
g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls,
gnutls_retr2_st *st)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GTlsCertificate *cert;
cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (gnutls));
st->cert_type = GNUTLS_CRT_X509;
st->ncerts = 0;
if (cert)
g_tls_certificate_gnutls_copy (G_TLS_CERTIFICATE_GNUTLS (cert),
priv->interaction_id, st);
}
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)
{
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_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);
}
}
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)
{
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,
GIOCondition direction,
gint64 timeout,
GCancellable *cancellable)
{
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,
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)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GError *my_error = NULL;
g_assert (direction & (G_IO_IN | G_IO_OUT));
g_assert (!error || !*error);
if (status == GNUTLS_E_AGAIN ||
status == GNUTLS_E_WARNING_ALERT_RECEIVED)
return GNUTLS_E_AGAIN;
if (direction & G_IO_IN)
{
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 (status >= 0)
return status;
if (priv->handshaking && !priv->ever_handshaked)
{
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 (my_error)
{
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 (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_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_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;
}
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, direction, timeout, cancellable) \
begin_gnutls_io (gnutls, direction, timeout, cancellable); \
do {
#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)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
/* 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;
GTlsConnectionGnutls *gnutls;
/* Either a GDatagramBased (datagram mode), or a GPollableInputStream or
* GPollableOutputStream (streaming mode):
*/
GObject *base;
GSource *child_source;
GIOCondition condition;
gboolean io_waiting;
gboolean op_waiting;
} GTlsConnectionGnutlsSource;
static gboolean
gnutls_source_prepare (GSource *source,
gint *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
gnutls_source_check (GSource *source)
{
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 (GTlsConnectionGnutlsSource *gnutls_source)
{
GTlsConnectionGnutls *gnutls = gnutls_source->gnutls;
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 (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 ((GSource *)gnutls_source,
gnutls_source->child_source);
g_source_unref (gnutls_source->child_source);
}
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_timeout_source_new (0);
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)
{
GDatagramBasedSourceFunc datagram_based_func = (GDatagramBasedSourceFunc) callback;
GPollableSourceFunc pollable_func = (GPollableSourceFunc) callback;
GTlsConnectionGnutlsSource *gnutls_source = (GTlsConnectionGnutlsSource *) source;
gboolean ret;
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);
if (ret)
gnutls_source_sync (gnutls_source);
return ret;
}
static void
gnutls_source_finalize (GSource *source)
{
GTlsConnectionGnutlsSource *gnutls_source = (GTlsConnectionGnutlsSource *)source;
g_object_unref (gnutls_source->gnutls);
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_tls_source_funcs =
{
gnutls_source_prepare,
gnutls_source_check,
gnutls_source_dispatch,
gnutls_source_finalize,
(GSourceFunc)g_tls_connection_gnutls_source_closure_callback,
(GSourceDummyMarshal)g_cclosure_marshal_generic
};
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;
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->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
g_assert_not_reached ();
gnutls_source->op_waiting = (gboolean) -1;
gnutls_source->io_waiting = (gboolean) -1;
gnutls_source_sync (gnutls_source);
if (cancellable)
{
cancellable_source = g_cancellable_source_new (cancellable);
g_source_set_dummy_callback (cancellable_source);
g_source_add_child_source (source, cancellable_source);
g_source_unref (cancellable_source);
}
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,
GError *error)
{
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))
{
/* 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 (priv->session, EIO);
}
static ssize_t
g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
void *buf,
size_t buflen)
{
GTlsConnectionGnutls *gnutls = transport_data;
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
ssize_t ret;
/* 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 (&priv->read_error);
if (g_tls_connection_gnutls_is_dtls (gnutls))
{
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_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, 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)
{
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 (&priv->write_error);
if (g_tls_connection_gnutls_is_dtls (gnutls))
{
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_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, priv->write_error);
return ret;
}
static ssize_t
g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data,
const giovec_t *iov,
int iovcnt)
{
GTlsConnectionGnutls *gnutls = transport_data;
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
ssize_t ret;
GOutputMessage message = { NULL, };
GOutputVector *vectors;
/* This function should only be set if we’re using base_socket. */
g_assert (priv->base_socket != NULL);
/* 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;
}
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 if (ret < 0)
set_gnutls_error (gnutls, priv->write_error);
return ret;
}
static gboolean
read_pollable_cb (GPollableInputStream *istream,
gpointer user_data)
{
gboolean *read_done = user_data;
*read_done = TRUE;
return G_SOURCE_CONTINUE;
}
static gboolean
read_datagram_based_cb (GDatagramBased *datagram_based,
GIOCondition condition,
gpointer user_data)
{
gboolean *read_done = user_data;
*read_done = TRUE;
return G_SOURCE_CONTINUE;
}
static gboolean
read_timeout_cb (gpointer user_data)
{
gboolean *timed_out = user_data;
*timed_out = TRUE;
return G_SOURCE_REMOVE;
}
static int
g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
unsigned int ms)
{
GTlsConnectionGnutls *gnutls = transport_data;
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
/* Fast path. */
if (g_tls_connection_gnutls_base_check (gnutls, G_IO_IN) ||
g_cancellable_is_cancelled (priv->read_cancellable))
return 1;
/* If @ms is 0, GnuTLS wants an instant response, so there’s no need to
* construct and query a #GSource. */
if (ms > 0)
{
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;
}
return 0;
}
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 GTlsCertificateFlags
verify_peer_certificate (GTlsConnectionGnutls *gnutls,
GTlsCertificate *peer_certificate)
{
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));
errors = 0;
database = g_tls_connection_get_database (conn);
if (database == NULL)
{
errors |= G_TLS_CERTIFICATE_UNKNOWN_CA;
errors |= g_tls_certificate_verify (peer_certificate, peer_identity, NULL);
}
else
{
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);
}
}
return errors;
}
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);
gboolean is_client;
GError *error = NULL;
int ret;
gint64 start_time;
gint64 timeout;
/* A timeout, in microseconds, must be provided as a gint64* task_data. */
g_assert (task_data != NULL);
timeout = *((gint64 *) task_data);
start_time = g_get_monotonic_time ();
priv->started_handshake = FALSE;
if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE,
timeout, cancellable, &error))
{
g_task_return_error (task, error);
return;
}
g_clear_error (&priv->handshake_error);
is_client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
if (!is_client && priv->ever_handshaked && !priv->implicit_handshake)
{
/* 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;
}
}
priv->started_handshake = TRUE;
g_clear_object (&priv->peer_certificate);
priv->peer_certificate_errors = 0;
g_tls_connection_gnutls_set_handshake_priority (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_handshake (priv->session);
if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
{
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);
if (ret == 0 && gnutls_certificate_type_get (priv->session) == GNUTLS_CRT_X509)
{
priv->peer_certificate_tmp = get_peer_certificate_from_session (gnutls);
if (priv->peer_certificate_tmp)
priv->peer_certificate_errors_tmp = verify_peer_certificate (gnutls, priv->peer_certificate_tmp);
else if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
{
g_set_error_literal (&error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
_("Server did not return a valid TLS certificate"));
}
}
G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, &error);
if (error)
{
g_task_return_error (task, error);
}
else
{
priv->ever_handshaked = TRUE;
g_task_return_boolean (task, TRUE);
}
}
static gboolean
accept_peer_certificate (GTlsConnectionGnutls *gnutls,
GTlsCertificate *peer_certificate,
GTlsCertificateFlags peer_certificate_errors)
{
gboolean accepted = FALSE;
if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
{
GTlsCertificateFlags validation_flags;
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));
if ((peer_certificate_errors & validation_flags) == 0)
accepted = TRUE;
}
if (!accepted)
{
accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
peer_certificate,
peer_certificate_errors);
}
return accepted;
}
static void
begin_handshake (GTlsConnectionGnutls *gnutls)
{
G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->begin_handshake (gnutls);
}
static gboolean
finish_handshake (GTlsConnectionGnutls *gnutls,
GTask *task,
GError **error)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GTlsCertificate *peer_certificate;
GTlsCertificateFlags peer_certificate_errors;
g_assert (error != NULL);
peer_certificate = priv->peer_certificate_tmp;
priv->peer_certificate_tmp = NULL;
peer_certificate_errors = priv->peer_certificate_errors_tmp;
priv->peer_certificate_errors_tmp = 0;
if (g_task_propagate_boolean (task, error) && peer_certificate)
{
if (!accept_peer_certificate (gnutls, peer_certificate,
peer_certificate_errors))
{
g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
_("Unacceptable TLS certificate"));
}
priv->peer_certificate = peer_certificate;
priv->peer_certificate_errors = peer_certificate_errors;
g_object_notify (G_OBJECT (gnutls), "peer-certificate");
g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
}
if (*error && priv->started_handshake)
priv->handshake_error = g_error_copy (*error);
return (*error == NULL);
}
static gboolean
g_tls_connection_gnutls_handshake (GTlsConnection *conn,
GCancellable *cancellable,
GError **error)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
GTask *task;
gboolean success;
gint64 *timeout = NULL;
GError *my_error = NULL;
task = g_task_new (conn, cancellable, NULL, NULL);
g_task_set_source_tag (task, g_tls_connection_gnutls_handshake);
timeout = g_new0 (gint64, 1);
*timeout = -1; /* blocking */
g_task_set_task_data (task, timeout, g_free);
begin_handshake (gnutls);
g_task_run_in_thread_sync (task, handshake_thread);
success = finish_handshake (gnutls, task, &my_error);
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_dtls_handshake (GDtlsConnection *conn,
GCancellable *cancellable,
GError **error)
{
return g_tls_connection_gnutls_handshake (G_TLS_CONNECTION (conn),
cancellable, error);
}
/* 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().
*/
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;
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_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)
{
GTask *thread_task, *caller_task;
gint64 *timeout = NULL;
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->implicit_handshake == NULL);
priv->implicit_handshake = g_task_new (gnutls, cancellable, 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_run_in_thread_sync (priv->implicit_handshake,
handshake_thread);
success = finish_handshake (gnutls,
priv->implicit_handshake,
&my_error);
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);
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);
}
#ifdef HAVE_PKCS11
static P11KitPin*
on_pin_prompt_callback (const char *pinfile,
P11KitUri *pin_uri,
const char *pin_description,
P11KitPinFlags pin_flags,
void *callback_data)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (callback_data);
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GTlsInteractionResult result;
GTlsPasswordFlags flags = 0;
GTlsPassword *password;
P11KitPin *pin = NULL;
GError *error = NULL;
if (!priv->interaction)
return NULL;
if (pin_flags & P11_KIT_PIN_FLAGS_RETRY)
flags |= G_TLS_PASSWORD_RETRY;
if (pin_flags & P11_KIT_PIN_FLAGS_MANY_TRIES)
flags |= G_TLS_PASSWORD_MANY_TRIES;
if (pin_flags & P11_KIT_PIN_FLAGS_FINAL_TRY)
flags |= G_TLS_PASSWORD_FINAL_TRY;
password = g_pkcs11_pin_new (flags, pin_description);
result = g_tls_interaction_ask_password (priv->interaction, password,
g_cancellable_get_current (), &error);
switch (result)
{
case G_TLS_INTERACTION_FAILED:
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("couldn't ask for password: %s", error->message);
pin = NULL;
break;
case G_TLS_INTERACTION_UNHANDLED:
default:
pin = NULL;
break;
case G_TLS_INTERACTION_HANDLED:
pin = g_pkcs11_pin_steal_internal (G_PKCS11_PIN (password));
break;
}
g_object_unref (password);
return pin;
}
#endif /* HAVE_PKCS11 */
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");
}
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;
}
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;
}