summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@src.gnome.org>2007-03-08 21:11:00 +0000
committerDan Winship <danw@src.gnome.org>2007-03-08 21:11:00 +0000
commit22ae7a1e8f61e652e379615a052030390ada4270 (patch)
treee812ba9c627f2b1e2f56c44b9f47f4aec0f01dfa
parentb9585a06ccdf8d0229d2d4967fe8189ac1d83566 (diff)
downloadlibsoup-gnome-2-18.tar.gz
don't return G_IO_STATUS_AGAIN if we're doing blocking I/O; just keepgnome-2-18
* libsoup/soup-gnutls.c (do_handshake): don't return G_IO_STATUS_AGAIN if we're doing blocking I/O; just keep retrying until the handshake is complete. (soup_gnutls_read, soup_gnutls_write): if we get GNUTLS_E_REHANDSHAKE, call do_handshake() immediately rather than returning G_IO_STATUS_AGAIN; if the socket is blocking then G_IO_STATUS_AGAIN is wrong, and if the socket is non-blocking, we might already need to return SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE or SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ. #415402, based on a patch from Jacob Berkman. * tests/ssl-test.c: basic ssl test. In particular, tests that rehandshake requests are handled correctly during both synchronous and asynchronous I/O. Might eventually test other stuff too... * configure.in: * tests/Makefile.am: updates for ssl-test svn path=/trunk/; revision=914
-rw-r--r--ChangeLog21
-rw-r--r--configure.in3
-rw-r--r--libsoup/soup-gnutls.c56
-rw-r--r--tests/Makefile.am9
-rw-r--r--tests/ssl-test.c322
5 files changed, 389 insertions, 22 deletions
diff --git a/ChangeLog b/ChangeLog
index 54dfb3db..ae55ed31 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2007-03-08 Dan Winship <danw@novell.com>
+
+ * libsoup/soup-gnutls.c (do_handshake): don't return
+ G_IO_STATUS_AGAIN if we're doing blocking I/O; just keep retrying
+ until the handshake is complete.
+ (soup_gnutls_read, soup_gnutls_write): if we get
+ GNUTLS_E_REHANDSHAKE, call do_handshake() immediately rather than
+ returning G_IO_STATUS_AGAIN; if the socket is blocking then
+ G_IO_STATUS_AGAIN is wrong, and if the socket is non-blocking, we
+ might already need to return SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE
+ or SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ.
+
+ #415402, based on a patch from Jacob Berkman.
+
+ * tests/ssl-test.c: basic ssl test. In particular, tests that
+ rehandshake requests are handled correctly during both synchronous
+ and asynchronous I/O. Might eventually test other stuff too...
+
+ * configure.in:
+ * tests/Makefile.am: updates for ssl-test
+
2007-02-19 Dan Winship <danw@novell.com>
* configure.in: Get gcrypt libs/cflags.
diff --git a/configure.in b/configure.in
index 650773e2..4d0f00a1 100644
--- a/configure.in
+++ b/configure.in
@@ -153,6 +153,9 @@ 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 *** gtk-doc ***
dnl ***************
diff --git a/libsoup/soup-gnutls.c b/libsoup/soup-gnutls.c
index e399a6f3..0f043ef1 100644
--- a/libsoup/soup-gnutls.c
+++ b/libsoup/soup-gnutls.c
@@ -2,10 +2,7 @@
/*
* soup-gnutls.c
*
- * Authors:
- * Ian Peters <itp@ximian.com>
- *
- * Copyright (C) 2003, Ximian, Inc.
+ * Copyright (C) 2003-2006, Novell, Inc.
*/
#ifdef HAVE_CONFIG_H
@@ -15,6 +12,7 @@
#ifdef HAVE_SSL
#include <errno.h>
+#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
@@ -129,21 +127,26 @@ verify_certificate (gnutls_session session, const char *hostname, GError **err)
return TRUE;
}
+#define SOUP_GNUTLS_CHANNEL_NONBLOCKING(chan) (fcntl ((chan)->fd, F_GETFL, 0) & O_NONBLOCK)
+
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) {
- 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;
+ if (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED) {
+ if (SOUP_GNUTLS_CHANNEL_NONBLOCKING (chan)) {
+ 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) {
@@ -172,6 +175,7 @@ soup_gnutls_read (GIOChannel *channel,
*bytes_read = 0;
+again:
if (!chan->established) {
result = do_handshake (chan, err);
@@ -186,13 +190,17 @@ soup_gnutls_read (GIOChannel *channel,
if (result == GNUTLS_E_REHANDSHAKE) {
chan->established = FALSE;
- return G_IO_STATUS_AGAIN;
+ goto again;
}
- if (result < 0) {
- if ((result == GNUTLS_E_INTERRUPTED) ||
- (result == GNUTLS_E_AGAIN))
+ if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
+ if (SOUP_GNUTLS_CHANNEL_NONBLOCKING (chan))
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");
@@ -216,6 +224,7 @@ soup_gnutls_write (GIOChannel *channel,
*bytes_written = 0;
+again:
if (!chan->established) {
result = do_handshake (chan, err);
@@ -228,15 +237,22 @@ soup_gnutls_write (GIOChannel *channel,
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;
- return G_IO_STATUS_AGAIN;
+ goto again;
}
- if (result < 0) {
- if ((result == GNUTLS_E_INTERRUPTED) ||
- (result == GNUTLS_E_AGAIN))
+ if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) {
+ if (SOUP_GNUTLS_CHANNEL_NONBLOCKING (chan))
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");
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e146a9aa..fa46ff01 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -6,7 +6,6 @@ INCLUDES = \
LIBS = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la
noinst_PROGRAMS = \
- auth-test \
date \
dict \
dns \
@@ -17,6 +16,8 @@ noinst_PROGRAMS = \
simple-httpd \
simple-proxy \
uri-parsing \
+ $(APACHE_TESTS) \
+ $(SSL_TESTS) \
$(XMLRPC_TESTS)
auth_test_SOURCES = auth-test.c apache-wrapper.c apache-wrapper.h
@@ -29,17 +30,21 @@ header_parsing_SOURCES = header-parsing.c
revserver_SOURCES = revserver.c
simple_httpd_SOURCES = simple-httpd.c
simple_proxy_SOURCES = simple-proxy.c
+ssl_test_SOURCES = ssl-test.c
uri_parsing_SOURCES = uri-parsing.c
xmlrpc_test_SOURCES = xmlrpc-test.c apache-wrapper.c apache-wrapper.h
if HAVE_APACHE
APACHE_TESTS = auth-test
endif
+if HAVE_SSL
+SSL_TESTS = ssl-test
+endif
if HAVE_XMLRPC_EPI_PHP
XMLRPC_TESTS = xmlrpc-test
endif
-TESTS = date header-parsing uri-parsing $(APACHE_TESTS) $(XMLRPC_TESTS)
+TESTS = date header-parsing uri-parsing $(APACHE_TESTS) $(SSL_TESTS) $(XMLRPC_TESTS)
EXTRA_DIST = \
libsoup.supp \
diff --git a/tests/ssl-test.c b/tests/ssl-test.c
new file mode 100644
index 00000000..211c94a1
--- /dev/null
+++ b/tests/ssl-test.c
@@ -0,0 +1,322 @@
+#include <gnutls/gnutls.h>
+#include <glib.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libsoup/soup-socket.h"
+#include "libsoup/soup-ssl.h"
+
+#define BUFSIZE 1024
+#define DH_BITS 1024
+
+GMainLoop *loop;
+gnutls_dh_params_t dh_params;
+
+/* SERVER */
+
+/* Read @bufsize bytes into @buf from @session. */
+static void
+server_read (gnutls_session_t session, char *buf, int bufsize)
+{
+ int total, nread;
+
+ total = 0;
+ while (total < bufsize) {
+ nread = gnutls_record_recv (session, buf + total,
+ bufsize - total);
+ if (nread <= 0)
+ g_error ("server read failed at position %d", total);
+ total += nread;
+ }
+}
+
+/* Write @bufsize bytes from @buf to @session, forcing 3 rehandshakes
+ * along the way. (We do an odd number of rehandshakes to make sure
+ * they occur at weird times relative to the client's read buffer
+ * size.)
+ */
+static void
+server_write (gnutls_session_t session, char *buf, int bufsize)
+{
+ int total, nwrote;
+ int next_rehandshake = bufsize / 3;
+
+ total = 0;
+ while (total < bufsize) {
+ if (total >= next_rehandshake) {
+ if (gnutls_rehandshake (session) < 0)
+ g_error ("client refused rehandshake at position %d", total);
+ if (gnutls_handshake (session) < 0)
+ g_error ("server rehandshake failed at position %d", total);
+ next_rehandshake = MIN (bufsize, next_rehandshake + bufsize / 3);
+ }
+
+ nwrote = gnutls_record_send (session, buf + total,
+ next_rehandshake - total);
+ if (nwrote <= 0)
+ g_error ("server write failed at position %d: %d", total, nwrote);
+ total += nwrote;
+ }
+}
+
+const char *ssl_cert_file = "test-cert.pem";
+const char *ssl_key_file = "test-key.pem";
+
+static gpointer
+server_thread (gpointer user_data)
+{
+ int listener = GPOINTER_TO_INT (user_data), client;
+ gnutls_certificate_credentials creds;
+ gnutls_session_t session;
+ struct sockaddr_in sin;
+ int len;
+ char buf[BUFSIZE];
+ int status;
+
+ gnutls_certificate_allocate_credentials (&creds);
+ if (gnutls_certificate_set_x509_key_file (creds,
+ ssl_cert_file, ssl_key_file,
+ GNUTLS_X509_FMT_PEM) != 0) {
+ g_error ("Failed to set SSL certificate and key files "
+ "(%s, %s).", ssl_cert_file, ssl_key_file);
+ }
+ gnutls_certificate_set_dh_params (creds, dh_params);
+
+ /* Create a new session */
+ gnutls_init (&session, GNUTLS_SERVER);
+ gnutls_set_default_priority (session);
+ gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, creds);
+ gnutls_dh_set_prime_bits (session, DH_BITS);
+
+ /* Wait for client thread to connect */
+ len = sizeof (sin);
+ client = accept (listener, (struct sockaddr *) &sin, (void *)&len);
+ gnutls_transport_set_ptr (session, GINT_TO_POINTER (client));
+
+ /* Initial handshake */
+ status = gnutls_handshake (session);
+ if (status < 0)
+ g_error ("initial handshake failed: %d", status);
+
+ /* Synchronous client test. */
+ server_read (session, buf, BUFSIZE);
+ server_write (session, buf, BUFSIZE);
+
+ /* Async client test. */
+ server_read (session, buf, BUFSIZE);
+ server_write (session, buf, BUFSIZE);
+
+ /* That's all, folks. */
+ gnutls_bye (session, GNUTLS_SHUT_WR);
+ gnutls_deinit (session);
+ close (client);
+
+ return NULL;
+}
+
+/* async client code */
+
+typedef struct {
+ char writebuf[BUFSIZE], readbuf[BUFSIZE];
+ int total;
+} AsyncData;
+
+static void
+async_read (SoupSocket *sock, gpointer user_data)
+{
+ AsyncData *data = user_data;
+ SoupSocketIOStatus status;
+ gsize n;
+
+ do {
+ status = soup_socket_read (sock, data->readbuf + data->total,
+ BUFSIZE - data->total, &n);
+ if (status == SOUP_SOCKET_OK)
+ data->total += n;
+ } while (status == SOUP_SOCKET_OK && data->total < BUFSIZE);
+
+ if (status == SOUP_SOCKET_ERROR || status == SOUP_SOCKET_EOF)
+ g_error ("Async read got status %d", status);
+ else if (status == SOUP_SOCKET_WOULD_BLOCK)
+ return;
+
+ if (memcmp (data->writebuf, data->readbuf, BUFSIZE) != 0)
+ g_error ("Sync read didn't match write");
+
+ g_main_loop_quit (loop);
+}
+
+static void
+async_write (SoupSocket *sock, gpointer user_data)
+{
+ AsyncData *data = user_data;
+ SoupSocketIOStatus status;
+ gsize n;
+
+ do {
+ status = soup_socket_write (sock, data->writebuf + data->total,
+ BUFSIZE - data->total, &n);
+ if (status == SOUP_SOCKET_OK)
+ data->total += n;
+ } while (status == SOUP_SOCKET_OK && data->total < BUFSIZE);
+
+ if (status == SOUP_SOCKET_ERROR || status == SOUP_SOCKET_EOF)
+ g_error ("Async write got status %d", status);
+ else if (status == SOUP_SOCKET_WOULD_BLOCK)
+ return;
+
+ data->total = 0;
+ async_read (sock, user_data);
+}
+
+static gboolean
+start_writing (gpointer user_data)
+{
+ SoupSocket *sock = user_data;
+ AsyncData *data;
+ int i;
+
+ data = g_new (AsyncData, 1);
+ for (i = 0; i < BUFSIZE; i++)
+ data->writebuf[i] = i & 0xFF;
+
+ g_signal_connect (sock, "writable",
+ G_CALLBACK (async_write), data);
+ g_signal_connect (sock, "readable",
+ G_CALLBACK (async_read), data);
+
+ async_write (sock, data);
+ return FALSE;
+}
+
+int debug;
+
+static void
+debug_log (int level, const char *str)
+{
+ fputs (str, stderr);
+}
+
+int
+main (int argc, char **argv)
+{
+ int opt, listener, sin_len, port, i;
+ struct sockaddr_in sin;
+ GThread *server;
+ char writebuf[BUFSIZE], readbuf[BUFSIZE];
+ SoupSocket *sock;
+ gsize n, total;
+ SoupSocketIOStatus status;
+
+ g_type_init ();
+ g_thread_init (NULL);
+
+ while ((opt = getopt (argc, argv, "c:d:k:")) != -1) {
+ switch (opt) {
+ case 'c':
+ ssl_cert_file = optarg;
+ break;
+ case 'd':
+ debug = atoi (optarg);
+ break;
+ case 'k':
+ ssl_key_file = optarg;
+ break;
+
+ case '?':
+ fprintf (stderr, "Usage: %s [-d debuglevel] [-c ssl-cert-file] [-k ssl-key-file]\n",
+ argv[0]);
+ break;
+ }
+ }
+
+ if (debug) {
+ gnutls_global_set_log_function (debug_log);
+ gnutls_global_set_log_level (debug);
+ }
+
+ /* Create server socket */
+ listener = socket (AF_INET, SOCK_STREAM, 0);
+ if (listener == -1) {
+ perror ("creating listening socket");
+ exit (1);
+ }
+
+ memset (&sin, 0, sizeof (sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind (listener, (struct sockaddr *) &sin, sizeof (sin)) == -1) {
+ perror ("binding listening socket");
+ exit (1);
+ }
+
+ if (listen (listener, 1) == -1) {
+ perror ("listening on socket");
+ exit (1);
+ }
+
+ sin_len = sizeof (sin);
+ getsockname (listener, (struct sockaddr *)&sin, (void *)&sin_len);
+ port = ntohs (sin.sin_port);
+
+ /* Now spawn server thread */
+ server = g_thread_create (server_thread, GINT_TO_POINTER (listener),
+ FALSE, NULL);
+
+ /* And create the client */
+ sock = soup_socket_client_new_sync ("127.0.0.1", port,
+ soup_ssl_get_client_credentials (NULL),
+ &status);
+ if (status != SOUP_STATUS_OK) {
+ g_error ("Could not create client socket: %s",
+ soup_status_get_phrase (status));
+ }
+
+ soup_socket_start_ssl (sock);
+
+ /* Synchronous client test */
+ for (i = 0; i < BUFSIZE; i++)
+ writebuf[i] = i & 0xFF;
+
+ total = 0;
+ while (total < BUFSIZE) {
+ status = soup_socket_write (sock, writebuf + total,
+ BUFSIZE - total, &n);
+ if (status != SOUP_SOCKET_OK)
+ g_error ("Sync write got status %d", status);
+ total += n;
+ }
+
+ total = 0;
+ while (total < BUFSIZE) {
+ status = soup_socket_read (sock, readbuf + total,
+ BUFSIZE - total, &n);
+ if (status != SOUP_SOCKET_OK)
+ g_error ("Sync read got status %d", status);
+ total += n;
+ }
+
+ if (memcmp (writebuf, readbuf, BUFSIZE) != 0)
+ g_error ("Sync read didn't match write");
+
+ printf ("SYNCHRONOUS SSL TEST PASSED\n");
+
+ /* Switch socket to async and do it again */
+
+ g_object_set (sock,
+ SOUP_SOCKET_FLAG_NONBLOCKING, TRUE,
+ NULL);
+
+ g_idle_add (start_writing, sock);
+ loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (loop);
+
+ printf ("ASYNCHRONOUS SSL TEST PASSED\n");
+
+ /* Success */
+ return 0;
+}