/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */ /* Lac - Library for asynchronous communication * Copyright (C) 2007 Søren Sandmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "lac.h" #include "lacinternals.h" #include #include #include struct _LacTlsConnection { LacConnection * tcp_connection; LacTlsConnectionFunc callback; gpointer data; nul_buffer_t * buffer; nul_buffer_t * unwritten; gnutls_certificate_credentials_t xcred; gnutls_session_t session; gboolean did_handshake; }; static void emit_error (LacTlsConnection *tls, GError *err) { LacConnectionEvent error_event; error_event.type = LAC_CONNECTION_EVENT_ERROR; error_event.error.err = err; tls->callback (tls, &error_event); g_error_free (err); } static void print_alert (gnutls_session session) { gnutls_alert_description_t d; d = gnutls_alert_get (session); g_print ("alert no: %d\n", d); g_print ("alert: %s\n", gnutls_alert_get_name (d)); } static void do_handshake (LacTlsConnection *tls) { if (tls->did_handshake) return; int res = gnutls_handshake (tls->session); if (res == 0) { LacConnectionEvent connect; #if 0 g_print (" handshake complete\n"); #endif tls->did_handshake = TRUE; /* We consider CONNECT to mean 'handshake complete' * Maybe worthwhile considering a separate HANDSHAKE * event. */ connect.type = LAC_CONNECTION_EVENT_CONNECT; tls->callback (tls, &connect); } else if (res < 0) { if (res != GNUTLS_E_INTERRUPTED && res != GNUTLS_E_AGAIN) { g_print (" handshake error: %d (%s)\n", res, gnutls_strerror (res)); if (res == GNUTLS_E_WARNING_ALERT_RECEIVED) print_alert (tls->session); else if (res == GNUTLS_E_FATAL_ALERT_RECEIVED) print_alert (tls->session); /* FIXME: emit an error */ if (gnutls_error_is_fatal (res)) lac_connection_close (tls->tcp_connection); } } } static void do_writes (LacTlsConnection *connection) { gsize n_written; GError *err = NULL; if (!lac_connection_is_connected (connection->tcp_connection)) return; if (!connection->did_handshake) return; do { gsize n_available; const gchar *buffer; n_written = 0; buffer = nul_buffer_peek (connection->unwritten, &n_available); if (n_available > 0) { n_written = gnutls_record_send (connection->session, buffer, n_available); if (n_written < 0) { if (n_written != GNUTLS_E_INTERRUPTED && n_written != GNUTLS_E_AGAIN) { err = (GError *)0x01; /* FIXME - make a new error */ } n_written = 0; } } nul_buffer_delete_head (connection->unwritten, n_written); } while (n_written > 0); if (err) { emit_error (connection, err); /* Closing here gurantees that the next event emitted by * the TCP connection is going to be "close" */ lac_connection_close (connection->tcp_connection); } } static void handle_read (LacTlsConnection *tls, const LacConnectionReadEvent *event) { nul_buffer_t *buffer; gssize n_read; enum { BUF_SIZE = 8192 }; GError *err = NULL; if (!tls->did_handshake) return; buffer = nul_buffer_new (); do { gchar *tail = nul_buffer_alloc_tail (buffer, BUF_SIZE); n_read = gnutls_record_recv (tls->session, tail, BUF_SIZE); if (n_read < 0) { if (n_read != GNUTLS_E_INTERRUPTED && n_read != GNUTLS_E_AGAIN) { err = (GError *)0x01; /* FIXME - make a new error */ } n_read = 0; } nul_buffer_delete_tail (buffer, BUF_SIZE - n_read); } while (n_read > 0); if (!nul_buffer_is_empty (buffer)) { LacConnectionEvent read_event; gsize n_bytes; read_event.type = LAC_CONNECTION_EVENT_READ; read_event.read.buffer = buffer; read_event.read.data = nul_buffer_peek (buffer, &n_bytes); read_event.read.len = n_bytes; tls->callback (tls, &read_event); } if (err) { emit_error (tls, err); lac_connection_close (tls->tcp_connection); } nul_buffer_free (buffer, TRUE); } static void tcp_callback (LacConnection *connection, const LacConnectionEvent *event) { LacTlsConnection *tls = lac_connection_get_data (connection); switch (event->type) { case LAC_CONNECTION_EVENT_CLOSE: case LAC_CONNECTION_EVENT_ERROR: tls->callback (tls, event); break; case LAC_CONNECTION_EVENT_CONNECT: do_handshake (tls); do_writes (tls); break; case LAC_CONNECTION_EVENT_READ: nul_buffer_append (tls->buffer, event->read.data, event->read.len); do_handshake (tls); do_writes (tls); handle_read (tls, &event->read); break; } } static ssize_t tls_push (gnutls_transport_ptr_t tptr, const void *data, size_t n_bytes) { LacTlsConnection *tls = (LacTlsConnection *)tptr; lac_connection_write (tls->tcp_connection, data, n_bytes); return n_bytes; } static ssize_t tls_pull (gnutls_transport_ptr_t tptr, void *data, size_t n_bytes) { LacTlsConnection *tls = (LacTlsConnection *)tptr; const gchar *buf; gsize n_available; buf = nul_buffer_peek (tls->buffer, &n_available); if (n_available > 0) { gsize minimum = n_bytes < n_available? n_bytes : n_available; memcpy (data, buf, minimum); errno = 0; nul_buffer_delete_head (tls->buffer, minimum); return minimum; } else { /* FIXME: we should use the 'set_errno' function from gnutls */ errno = EAGAIN; return -1; } } LacTlsConnection * lac_tls_connection_new (const LacAddress *address, gint port, LacTlsConnectionFunc callback, gpointer data) { LacTlsConnection *tls = g_new0 (LacTlsConnection, 1); tls->tcp_connection = lac_connection_new_tcp ( address, port, tcp_callback, tls); tls->did_handshake = FALSE; tls->callback = callback; tls->data = data; tls->buffer = nul_buffer_new (); tls->unwritten = nul_buffer_new (); gnutls_global_init (); /* sets the trusted cas file */ gnutls_certificate_allocate_credentials (&tls->xcred); #if 0 g_print ("result: %d\n", gnutls_certificate_set_x509_trust_file ( tls->xcred, "/home/ssp/verisign/VeriSign_Roots/C1_PCA_G3v2.cer", GNUTLS_X509_FMT_PEM)); #endif gnutls_init (&tls->session, GNUTLS_CLIENT); gnutls_set_default_priority (tls->session); gnutls_credentials_set (tls->session, GNUTLS_CRD_CERTIFICATE, tls->xcred); gnutls_transport_set_ptr (tls->session, (gnutls_transport_ptr_t)tls); gnutls_transport_set_push_function (tls->session, tls_push); gnutls_transport_set_pull_function (tls->session, tls_pull); return tls; } void lac_tls_connection_free (LacTlsConnection *connection) { lac_connection_unref (connection->tcp_connection); nul_buffer_free (connection->buffer, TRUE); nul_buffer_free (connection->unwritten, TRUE); gnutls_deinit (connection->session); gnutls_certificate_free_credentials (connection->xcred); g_free (connection); } void lac_tls_connection_write (LacTlsConnection *tls, const gchar *data, guint len) { nul_buffer_append (tls->unwritten, data, len); do_writes (tls); } void lac_tls_connection_close (LacTlsConnection *connection) { lac_connection_close (connection->tcp_connection); } gboolean lac_tls_connection_is_connected (LacTlsConnection *connection) { return lac_connection_is_connected (connection->tcp_connection) && connection->did_handshake; } void lac_tls_connection_flush (LacTlsConnection *connection) { lac_connection_flush (connection->tcp_connection); } gpointer lac_tls_connection_get_data (LacTlsConnection *tls) { return tls->data; }