From: Dan Winship Date: Thu, 8 Mar 2007 21:11:00 +0000 (+0000) Subject: don't return G_IO_STATUS_AGAIN if we're doing blocking I/O; just keep X-Git-Tag: LIBSOUP_2_2_105~7 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=22ae7a1e8f61e652e379615a052030390ada4270;p=platform%2Fupstream%2Flibsoup.git don't return G_IO_STATUS_AGAIN if we're doing blocking I/O; just keep * 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 --- diff --git a/ChangeLog b/ChangeLog index 54dfb3d..ae55ed3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2007-03-08 Dan Winship + + * 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 * configure.in: Get gcrypt libs/cflags. diff --git a/configure.in b/configure.in index 650773e..4d0f00a 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 e399a6f..0f043ef 100644 --- a/libsoup/soup-gnutls.c +++ b/libsoup/soup-gnutls.c @@ -2,10 +2,7 @@ /* * soup-gnutls.c * - * Authors: - * Ian Peters - * - * Copyright (C) 2003, Ximian, Inc. + * Copyright (C) 2003-2006, Novell, Inc. */ #ifdef HAVE_CONFIG_H @@ -15,6 +12,7 @@ #ifdef HAVE_SSL #include +#include #include #include #include @@ -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 e146a9a..fa46ff0 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 0000000..211c94a --- /dev/null +++ b/tests/ssl-test.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include +#include +#include +#include + +#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; +}