Getting colder with our second freeze... it's 3.31.91 release day and string freeze, upload a tarball and lock those strings 🏂

Commit 97bacf30 authored by Dan Winship's avatar Dan Winship

SoupSocket: port to GSocketConnection/GTlsConnection

and remove libsoup's built-in TLS support, which is no longer needed
parent ac667c18
......@@ -4,7 +4,7 @@ and the glib main loop, to integrate well with GNOME applications.
Features:
* Both asynchronous (GMainLoop and callback-based) and synchronous APIs
* Automatically caches connections
* SSL Support using GnuTLS
* SSL support
* Proxy support, including authentication and SSL tunneling
* Client support for Digest, NTLM, and Basic authentication
* Server support for Digest and Basic authentication
......
......@@ -72,9 +72,9 @@ dnl ***********************
dnl *** Checks for glib ***
dnl ***********************
AM_PATH_GLIB_2_0(2.21.3,,,gobject gthread gio)
AM_PATH_GLIB_2_0(2.27.4,,,gobject gthread gio)
if test "$GLIB_LIBS" = ""; then
AC_MSG_ERROR(GLIB 2.21.3 or later is required to build libsoup)
AC_MSG_ERROR(GLIB 2.27.4 or later is required to build libsoup)
fi
GLIB_CFLAGS="$GLIB_CFLAGS -DG_DISABLE_SINGLE_INCLUDES"
......@@ -106,39 +106,6 @@ AC_CHECK_FUNCS(gmtime_r)
AC_CHECK_FUNCS(mmap)
AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket))
dnl **********************************
dnl *** SSL Library check (GnuTLS) ***
dnl **********************************
AC_ARG_ENABLE(ssl,
AS_HELP_STRING([--disable-ssl], [Disable SSL/TLS support (not recommended)]),,
enable_ssl=auto)
have_ssl=no
if test "$enable_ssl" != "no"; then
PKG_CHECK_MODULES(LIBGNUTLS, gnutls >= 2.1.7,
[AM_PATH_LIBGCRYPT([], have_ssl=yes, have_ssl=no)], have_ssl=no)
fi
if test "$have_ssl" = "yes"; then
AC_DEFINE(HAVE_SSL, 1, [Defined if you have SSL support])
SSL_REQUIREMENT="gnutls"
else
if test "$enable_ssl" = "no"; then
AC_MSG_WARN(Disabling SSL support);
else
AC_MSG_ERROR([Could not configure SSL support.
Pass "--disable-ssl" if you really want to build without SSL support]);
fi
fi
AC_SUBST(LIBGNUTLS_CFLAGS)
AC_SUBST(LIBGNUTLS_LIBS)
AC_SUBST(SSL_REQUIREMENT)
dnl This is not supposed to be conditional, but...
AM_CONDITIONAL(HAVE_SSL, test $enable_ssl != no)
dnl *********************
dnl *** GNOME support ***
dnl *********************
......@@ -256,9 +223,6 @@ dnl *** Stuff for regression tests
dnl ******************************
AC_MSG_NOTICE([checking for programs needed for regression tests])
MISSING_REGRESSION_TEST_PACKAGES=""
if test $have_ssl = "no"; then
MISSING_REGRESSION_TEST_PACKAGES=" gnutls"
fi
AC_ARG_WITH(apache-httpd,
AS_HELP_STRING([--with-apache-httpd], [Path to apache httpd (for tests)]),
......
......@@ -7,6 +7,6 @@ Name: libsoup
Description: a glib-based HTTP library
Version: @VERSION@
Requires: glib-2.0 gobject-2.0 gio-2.0
Requires.private: libxml-2.0 @SSL_REQUIREMENT@
Requires.private: libxml-2.0
Libs: -L${libdir} -lsoup-2.4
Cflags: -I${includedir}/libsoup-2.4
......@@ -14,9 +14,7 @@ INCLUDES = \
$(GCONF_CFLAGS) \
$(LIBPROXY_CFLAGS) \
$(SQLITE_CFLAGS) \
$(GNOME_KEYRING_CFLAGS) \
$(LIBGCRYPT_CFLAGS) \
$(LIBGNUTLS_CFLAGS)
$(GNOME_KEYRING_CFLAGS)
MARSHAL_GENERATED = soup-marshal.c soup-marshal.h
MKENUMS_GENERATED = soup-enum-types.c soup-enum-types.h
......@@ -100,9 +98,6 @@ libsoup_2_4_la_LIBADD = \
$(GLIB_LIBS) \
$(XML_LIBS) \
-lz \
$(LIBGNUTLS_LIBS_STATIC) \
$(LIBGNUTLS_LIBS) \
$(LIBGCRYPT_LIBS) \
$(LIBWS2_32)
libsoup_2_4_la_SOURCES = \
......@@ -135,7 +130,6 @@ libsoup_2_4_la_SOURCES = \
soup-cookie-jar-text.c \
soup-date.c \
soup-form.c \
soup-gnutls.c \
soup-headers.c \
soup-logger.c \
soup-message.c \
......@@ -150,7 +144,6 @@ libsoup_2_4_la_SOURCES = \
soup-method.c \
soup-misc.c \
soup-multipart.c \
soup-nossl.c \
soup-password-manager.c \
soup-path-map.h \
soup-path-map.c \
......@@ -166,6 +159,7 @@ libsoup_2_4_la_SOURCES = \
soup-session-sync.c \
soup-socket.c \
soup-ssl.h \
soup-ssl.c \
soup-status.c \
soup-uri.c \
soup-value-utils.c \
......
......@@ -8,8 +8,6 @@
#include <sys/types.h>
#include <gio/gio.h>
#include <libsoup/soup-portability.h>
#include <libsoup/soup-types.h>
......
......@@ -6,8 +6,6 @@
#ifndef SOUP_CONNECTION_H
#define SOUP_CONNECTION_H 1
#include <gio/gio.h>
#include "soup-types.h"
#include "soup-message-private.h"
#include "soup-misc.h"
......
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-gnutls.c
*
* Copyright (C) 2003-2006, Novell, Inc.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_SSL
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#ifndef G_OS_WIN32
#include <pthread.h>
#endif
#include <gcrypt.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include "soup-ssl.h"
#include "soup-misc.h"
/**
* soup_ssl_supported:
*
* Can be used to test if libsoup was compiled with ssl support.
**/
const gboolean soup_ssl_supported = TRUE;
#define DH_BITS 1024
struct SoupSSLCredentials {
gnutls_certificate_credentials creds;
gboolean have_ca_file;
};
typedef struct {
GIOChannel channel;
GIOChannel *real_sock;
int sockfd;
gboolean non_blocking, eagain;
gnutls_session session;
SoupSSLCredentials *creds;
char *hostname;
gboolean established;
SoupSSLType type;
} SoupGNUTLSChannel;
static gboolean
verify_certificate (gnutls_session session, const char *hostname, GError **err)
{
int status;
status = gnutls_certificate_verify_peers (session);
if (status == GNUTLS_E_NO_CERTIFICATE_FOUND) {
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"No SSL certificate was sent.");
return FALSE;
}
if (status & GNUTLS_CERT_INVALID ||
#ifdef GNUTLS_CERT_NOT_TRUSTED
status & GNUTLS_CERT_NOT_TRUSTED ||
#endif
status & GNUTLS_CERT_REVOKED)
{
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"The SSL certificate is not trusted.");
return FALSE;
}
if (gnutls_certificate_expiration_time_peers (session) < time (0)) {
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"The SSL certificate has expired.");
return FALSE;
}
if (gnutls_certificate_activation_time_peers (session) > time (0)) {
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"The SSL certificate is not yet activated.");
return FALSE;
}
if (gnutls_certificate_type_get (session) == GNUTLS_CRT_X509) {
const gnutls_datum* cert_list;
guint cert_list_size;
gnutls_x509_crt cert;
if (gnutls_x509_crt_init (&cert) < 0) {
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"Error initializing SSL certificate.");
return FALSE;
}
cert_list = gnutls_certificate_get_peers (
session, &cert_list_size);
if (cert_list == NULL) {
gnutls_x509_crt_deinit (cert);
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"No SSL certificate was found.");
return FALSE;
}
if (gnutls_x509_crt_import (cert, &cert_list[0],
GNUTLS_X509_FMT_DER) < 0) {
gnutls_x509_crt_deinit (cert);
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"The SSL certificate could not be parsed.");
return FALSE;
}
if (!gnutls_x509_crt_check_hostname (cert, hostname)) {
gnutls_x509_crt_deinit (cert);
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_CERTIFICATE,
"The SSL certificate does not match the hostname.");
return FALSE;
}
gnutls_x509_crt_deinit (cert);
}
return TRUE;
}
static GIOStatus
do_handshake (SoupGNUTLSChannel *chan, GError **err)
{
int result;
again:
result = gnutls_handshake (chan->session);
if (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED) {
if (chan->non_blocking) {
g_set_error (err, SOUP_SSL_ERROR,
(gnutls_record_get_direction (chan->session) ?
SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE :
SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ),
"Handshaking...");
return G_IO_STATUS_AGAIN;
} else
goto again;
}
if (result < 0) {
g_set_error (err, SOUP_SSL_ERROR,
SOUP_SSL_ERROR_HANDSHAKE_FAILED,
"SSL handshake failed: %s",
gnutls_strerror (result));
return G_IO_STATUS_ERROR;
}
chan->established = TRUE;
if (chan->type == SOUP_SSL_TYPE_CLIENT && chan->creds->have_ca_file &&
!verify_certificate (chan->session, chan->hostname, err))
return G_IO_STATUS_ERROR;
return G_IO_STATUS_NORMAL;
}
static GIOStatus
soup_gnutls_read (GIOChannel *channel,
gchar *buf,
gsize count,
gsize *bytes_read,
GError **err)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
gint result;
*bytes_read = 0;
again:
if (!chan->established) {
result = do_handshake (chan, err);
if (result == G_IO_STATUS_AGAIN ||
result == G_IO_STATUS_ERROR)
return result;
}
result = gnutls_record_recv (chan->session, buf, count);
if (result == GNUTLS_E_REHANDSHAKE) {
chan->established = FALSE;
goto again;
}
if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
if (chan->non_blocking || chan->eagain)
return G_IO_STATUS_AGAIN;
else
goto again;
}
if (result == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
/* This means the connection was either corrupted or
* interrupted. One particular thing that it can mean
* is that the remote end closed the connection
* abruptly without doing a proper TLS Close. There
* are security reasons why it's bad to treat this as
* not-an-error, but for compatibility reasons (eg,
* bug 577386) we kinda have to. And it's not like
* we're very secure anyway.
*/
return G_IO_STATUS_EOF;
}
if (result < 0) {
g_set_error (err, G_IO_CHANNEL_ERROR,
G_IO_CHANNEL_ERROR_FAILED,
"Received corrupted data");
return G_IO_STATUS_ERROR;
} else {
*bytes_read = result;
return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
}
}
static GIOStatus
soup_gnutls_write (GIOChannel *channel,
const gchar *buf,
gsize count,
gsize *bytes_written,
GError **err)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
gint result;
*bytes_written = 0;
again:
if (!chan->established) {
result = do_handshake (chan, err);
if (result == G_IO_STATUS_AGAIN ||
result == G_IO_STATUS_ERROR)
return result;
}
result = gnutls_record_send (chan->session, buf, count);
/* I'm pretty sure this can't actually happen in response to a
* write, but...
*/
if (result == GNUTLS_E_REHANDSHAKE) {
chan->established = FALSE;
goto again;
}
if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
if (chan->non_blocking || chan->eagain)
return G_IO_STATUS_AGAIN;
else
goto again;
}
if (result < 0) {
g_set_error (err, G_IO_CHANNEL_ERROR,
G_IO_CHANNEL_ERROR_FAILED,
"Received corrupted data");
return G_IO_STATUS_ERROR;
} else {
*bytes_written = result;
return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
}
}
static GIOStatus
soup_gnutls_seek (GIOChannel *channel,
gint64 offset,
GSeekType type,
GError **err)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
return chan->real_sock->funcs->io_seek (chan->real_sock, offset, type, err);
}
static GIOStatus
soup_gnutls_close (GIOChannel *channel,
GError **err)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
if (chan->established) {
int ret;
do {
ret = gnutls_bye (chan->session, GNUTLS_SHUT_WR);
} while (ret == GNUTLS_E_INTERRUPTED);
}
return chan->real_sock->funcs->io_close (chan->real_sock, err);
}
static GSource *
soup_gnutls_create_watch (GIOChannel *channel,
GIOCondition condition)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
return chan->real_sock->funcs->io_create_watch (chan->real_sock,
condition);
}
static void
soup_gnutls_free (GIOChannel *channel)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
g_io_channel_unref (chan->real_sock);
gnutls_deinit (chan->session);
g_free (chan->hostname);
g_slice_free (SoupGNUTLSChannel, chan);
}
static GIOStatus
soup_gnutls_set_flags (GIOChannel *channel,
GIOFlags flags,
GError **err)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
return chan->real_sock->funcs->io_set_flags (chan->real_sock, flags, err);
}
static GIOFlags
soup_gnutls_get_flags (GIOChannel *channel)
{
SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
return chan->real_sock->funcs->io_get_flags (chan->real_sock);
}
static const GIOFuncs soup_gnutls_channel_funcs = {
soup_gnutls_read,
soup_gnutls_write,
soup_gnutls_seek,
soup_gnutls_close,
soup_gnutls_create_watch,
soup_gnutls_free,
soup_gnutls_set_flags,
soup_gnutls_get_flags
};
static gnutls_dh_params dh_params = NULL;
static gboolean
init_dh_params (void)
{
static volatile gsize inited_dh_params = 0;
if (g_once_init_enter (&inited_dh_params)) {
if (gnutls_dh_params_init (&dh_params) != 0 ||
gnutls_dh_params_generate2 (dh_params, DH_BITS) != 0) {
if (dh_params) {
gnutls_dh_params_deinit (dh_params);
dh_params = NULL;
}
}
g_once_init_leave (&inited_dh_params, TRUE);
}
return dh_params != NULL;
}
static ssize_t
soup_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
void *buf, size_t buflen)
{
SoupGNUTLSChannel *chan = transport_data;
ssize_t nread;
nread = recv (chan->sockfd, buf, buflen, 0);
#ifdef G_OS_WIN32
{
int wsa_errno = WSAGetLastError ();
chan->eagain = (nread == SOCKET_ERROR && (wsa_errno == WSAEWOULDBLOCK ||
wsa_errno == WSAETIMEDOUT));
if (nread == SOCKET_ERROR)
gnutls_transport_set_errno (chan->session,
((wsa_errno == WSAEWOULDBLOCK ||
wsa_errno == WSAETIMEDOUT) ? EAGAIN :
(wsa_errno == WSAEINTR ? EINTR :
EIO)));
}
#else
chan->eagain = (nread == -1 && errno == EAGAIN);
#endif
return nread;
}
static ssize_t
soup_gnutls_push_func (gnutls_transport_ptr_t transport_data,
const void *buf, size_t buflen)
{
SoupGNUTLSChannel *chan = transport_data;
ssize_t nwrote;
nwrote = send (chan->sockfd, buf, buflen, 0);
#ifdef G_OS_WIN32
{
int wsa_errno = WSAGetLastError ();
chan->eagain = (nwrote == SOCKET_ERROR && wsa_errno == WSAEWOULDBLOCK);
if (nwrote == SOCKET_ERROR)
gnutls_transport_set_errno (chan->session,
(wsa_errno == WSAEWOULDBLOCK ? EAGAIN :
(wsa_errno == WSAEINTR ? EINTR :
EIO)));
}
#else
chan->eagain = (nwrote == -1 && errno == EAGAIN);
#endif
return nwrote;
}
/**
* soup_ssl_wrap_iochannel:
* @sock: a #GIOChannel wrapping a TCP socket.
* @non_blocking: whether the underlying socket is blocking or not
* @type: whether this is a client or server socket
* @remote_host: the hostname of the remote machine
* @creds: a client or server credentials structure
*
* This attempts to wrap a new #GIOChannel around @sock that
* will SSL-encrypt/decrypt all traffic through it.
*
* Return value: an SSL-encrypting #GIOChannel, or %NULL on
* failure.
**/
GIOChannel *
soup_ssl_wrap_iochannel (GIOChannel *sock, gboolean non_blocking,
SoupSSLType type, const char *remote_host,
SoupSSLCredentials *creds)
{
SoupGNUTLSChannel *chan = NULL;
GIOChannel *gchan = NULL;
gnutls_session session = NULL;
int sockfd;
int ret;
g_return_val_if_fail (sock != NULL, NULL);
g_return_val_if_fail (creds != NULL, NULL);
sockfd = g_io_channel_unix_get_fd (sock);
if (!sockfd) {
g_warning ("Failed to get channel fd.");
goto THROW_CREATE_ERROR;
}
ret = gnutls_init (&session,
(type == SOUP_SSL_TYPE_CLIENT) ? GNUTLS_CLIENT : GNUTLS_SERVER);
if (ret)
goto THROW_CREATE_ERROR;
/* See http://bugzilla.gnome.org/show_bug.cgi?id=581342 */
if (gnutls_priority_set_direct (session, "NORMAL:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0", NULL) != 0)
goto THROW_CREATE_ERROR;
if (gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE,
creds->creds) != 0)
goto THROW_CREATE_ERROR;
if (type == SOUP_SSL_TYPE_SERVER)
gnutls_dh_set_prime_bits (session, DH_BITS);
else {
// gnutls defaults to requiring at least 768-bit keys,
// but there are some lame servers out there...
gnutls_dh_set_prime_bits (session, 256);
}
chan = g_slice_new0 (SoupGNUTLSChannel);
chan->real_sock = sock;
chan->sockfd = sockfd;
chan->session = session;
chan->creds = creds;
chan->hostname = g_strdup (remote_host);
chan->type = type;
chan->non_blocking = non_blocking;
g_io_channel_ref (sock);
gnutls_transport_set_ptr (session, chan);
gnutls_transport_set_push_function (session, soup_gnutls_push_func);
gnutls_transport_set_pull_function (session, soup_gnutls_pull_func);
gchan = (GIOChannel *) chan;
gchan->funcs = (GIOFuncs *)&soup_gnutls_channel_funcs;
g_io_channel_init (gchan);
gchan->is_readable = gchan->is_writeable = TRUE;
gchan->use_buffer = FALSE;
return gchan;
THROW_CREATE_ERROR:
if (session)
gnutls_deinit (session);
return NULL;
}
#if defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) && !defined(G_OS_WIN32)
GCRY_THREAD_OPTION_PTHREAD_IMPL;
#endif
#ifdef G_OS_WIN32
static int
soup_gcry_win32_mutex_init (void **priv)
{
int err = 0;
CRITICAL_SECTION *lock = (CRITICAL_SECTION*)malloc (sizeof (CRITICAL_SECTION));
if (!lock)
err = ENOMEM;
if (!err) {
InitializeCriticalSection (lock);
*priv = lock;
}
return err;
}
static int
soup_gcry_win32_mutex_destroy (void **lock)
{
DeleteCriticalSection ((CRITICAL_SECTION*)*lock);
free (*lock);
return 0;
}
static int
soup_gcry_win32_mutex_lock (void **lock)
{
EnterCriticalSection ((CRITICAL_SECTION*)*lock);
return 0;
}
static int
soup_gcry_win32_mutex_unlock (void **lock)
{
LeaveCriticalSection ((CRITICAL_SECTION*)*lock);
return 0;
}
static struct gcry_thread_cbs soup_gcry_threads_win32 = { \
(GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)), \
NULL, soup_gcry_win32_mutex_init, soup_gcry_win32_mutex_destroy, \
soup_gcry_win32_mutex_lock, soup_gcry_win32_mutex_unlock, \
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
#endif
static void
soup_gnutls_init (void)
{
static volatile gsize inited_gnutls = 0;
if (g_once_init_enter (&inited_gnutls)) {
#if defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) && !defined(G_OS_WIN32)
gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
#elif defined(G_OS_WIN32)
gcry_control (GCRYCTL_SET_THREAD_CBS, &soup_gcry_threads_win32);
#endif
gnutls_global_init ();
g_once_init_leave (&inited_gnutls, TRUE);
}
}
/**
* soup_ssl_get_client_credentials:
* @ca_file: path to a file containing X509-encoded Certificate
* Authority certificates.
*
* Creates an opaque client credentials object which can later be
* passed to soup_ssl_wrap_iochannel().
*
* If @ca_file is non-%NULL, any certificate received from a server
* must be signed by one of the CAs in the file, or an error will
* be returned.
*
* Return value: the client credentials, which must be freed with
* soup_ssl_free_client_credentials().
**/
SoupSSLCredentials *