+ 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);
+
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate");
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+
+ return G_SOURCE_REMOVE;
+}
+
+static int
+verify_certificate_cb (gnutls_session_t session)
+{
+ 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);
+
+ /* 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 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;
+
+ /* 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);
+
+ 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;
+ }
+ }
+ }
+
+ priv->started_handshake = TRUE;
+
+ if (!priv->ever_handshaked)
+ 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);
+
+ /* 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);
+
+ if (error)
+ {
+ g_task_return_error (task, error);
+ }
+ else
+ {
+ priv->ever_handshaked = TRUE;
+ g_task_return_boolean (task, TRUE);
+ }