don't return G_IO_STATUS_AGAIN if we're doing blocking I/O; just keep
authorDan Winship <danw@src.gnome.org>
Thu, 8 Mar 2007 21:11:00 +0000 (21:11 +0000)
committerDan Winship <danw@src.gnome.org>
Thu, 8 Mar 2007 21:11:00 +0000 (21:11 +0000)
* 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

ChangeLog
configure.in
libsoup/soup-gnutls.c
tests/Makefile.am
tests/ssl-test.c [new file with mode: 0644]

index 54dfb3d..ae55ed3 100644 (file)
--- 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.
index 650773e..4d0f00a 100644 (file)
@@ -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 ***************
index e399a6f..0f043ef 100644 (file)
@@ -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");
index e146a9a..fa46ff0 100644 (file)
@@ -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 (file)
index 0000000..211c94a
--- /dev/null
@@ -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;
+}