/*
* soup-gnutls.c
*
- * Authors:
- * Ian Peters <itp@ximian.com>
- *
- * Copyright (C) 2003, Ximian, Inc.
+ * Copyright (C) 2003-2006, Novell, Inc.
*/
#ifdef HAVE_CONFIG_H
#ifdef HAVE_SSL
#include <errno.h>
+#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
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) {
*bytes_read = 0;
+again:
if (!chan->established) {
result = do_handshake (chan, err);
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");
*bytes_written = 0;
+again:
if (!chan->established) {
result = do_handshake (chan, err);
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");
--- /dev/null
+#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;
+}