/* * * Web service library with GLib integration * * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "giognutls.h" //#define DBG(fmt, arg...) printf("%s: " fmt "\n" , __func__ , ## arg) #define DBG(fmt, arg...) typedef struct _GIOGnuTLSChannel GIOGnuTLSChannel; typedef struct _GIOGnuTLSWatch GIOGnuTLSWatch; struct _GIOGnuTLSChannel { GIOChannel channel; gint fd; gnutls_certificate_credentials_t cred; gnutls_session_t session; bool established; bool again; }; struct _GIOGnuTLSWatch { GSource source; GPollFD pollfd; GIOChannel *channel; GIOCondition condition; }; static volatile int global_init_done = 0; static inline void g_io_gnutls_global_init(void) { if (__sync_bool_compare_and_swap(&global_init_done, 0, 1)) gnutls_global_init(); } static GIOStatus check_handshake(GIOChannel *channel, GError **err) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; int result; DBG("channel %p", channel); if (gnutls_channel->established) return G_IO_STATUS_NORMAL; again: result = gnutls_handshake(gnutls_channel->session); if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) { GIOFlags flags = g_io_channel_get_flags(channel); if (gnutls_channel->again) return G_IO_STATUS_AGAIN; if (flags & G_IO_FLAG_NONBLOCK) return G_IO_STATUS_AGAIN; goto again; } if (result < 0) { g_set_error(err, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED, "Handshake failed"); return G_IO_STATUS_ERROR; } gnutls_channel->established = true; DBG("handshake done"); return G_IO_STATUS_NORMAL; } static GIOStatus g_io_gnutls_read(GIOChannel *channel, gchar *buf, gsize count, gsize *bytes_read, GError **err) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; GIOStatus status; ssize_t result; DBG("channel %p count %zu", channel, count); *bytes_read = 0; again: status = check_handshake(channel, err); if (status != G_IO_STATUS_NORMAL) return status; result = gnutls_record_recv(gnutls_channel->session, buf, count); DBG("result %zd", result); if (result == GNUTLS_E_REHANDSHAKE) { gnutls_channel->established = false; goto again; } if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) { GIOFlags flags = g_io_channel_get_flags(channel); if (gnutls_channel->again) return G_IO_STATUS_AGAIN; if (flags & G_IO_FLAG_NONBLOCK) return G_IO_STATUS_AGAIN; goto again; } if (result == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) return G_IO_STATUS_EOF; if (result < 0) { g_set_error(err, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED, "Stream corrupted"); return G_IO_STATUS_ERROR; } *bytes_read = result; return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF; } static GIOStatus g_io_gnutls_write(GIOChannel *channel, const gchar *buf, gsize count, gsize *bytes_written, GError **err) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; GIOStatus status; ssize_t result; DBG("channel %p count %zu", channel, count); *bytes_written = 0; again: status = check_handshake(channel, err); if (status != G_IO_STATUS_NORMAL) return status; result = gnutls_record_send(gnutls_channel->session, buf, count); DBG("result %zd", result); if (result == GNUTLS_E_REHANDSHAKE) { gnutls_channel->established = false; goto again; } if (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) { GIOFlags flags = g_io_channel_get_flags(channel); if (gnutls_channel->again) return G_IO_STATUS_AGAIN; if (flags & G_IO_FLAG_NONBLOCK) return G_IO_STATUS_AGAIN; goto again; } if (result < 0) { g_set_error(err, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED, "Stream corrupted"); return G_IO_STATUS_ERROR; } *bytes_written = result; return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF; } static GIOStatus g_io_gnutls_seek(GIOChannel *channel, gint64 offset, GSeekType type, GError **err) { DBG("channel %p", channel); g_set_error_literal(err, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED, "Not supported"); return G_IO_STATUS_ERROR; } static GIOStatus g_io_gnutls_close(GIOChannel *channel, GError **err) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; DBG("channel %p", channel); if (gnutls_channel->established) gnutls_bye(gnutls_channel->session, GNUTLS_SHUT_RDWR); if (close(gnutls_channel->fd) < 0) { g_set_error_literal(err, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED, "Closing failed"); return G_IO_STATUS_ERROR; } return G_IO_STATUS_NORMAL; } static void g_io_gnutls_free(GIOChannel *channel) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; DBG("channel %p", channel); gnutls_deinit(gnutls_channel->session); tpkp_gnutls_cleanup(); gnutls_certificate_free_credentials(gnutls_channel->cred); g_free(gnutls_channel); } static GIOStatus g_io_gnutls_set_flags(GIOChannel *channel, GIOFlags flags, GError **err) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; glong fcntl_flags = 0; DBG("channel %p flags %u", channel, flags); if (flags & G_IO_FLAG_NONBLOCK) fcntl_flags |= O_NONBLOCK; if (fcntl(gnutls_channel->fd, F_SETFL, fcntl_flags) < 0) { g_set_error_literal(err, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED, "Setting flags failed"); return G_IO_STATUS_ERROR; } return G_IO_STATUS_NORMAL; } static GIOFlags g_io_gnutls_get_flags(GIOChannel *channel) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; GIOFlags flags = 0; glong fcntl_flags; DBG("channel %p", channel); fcntl_flags = fcntl(gnutls_channel->fd, F_GETFL); if (fcntl_flags < 0) return 0; if (fcntl_flags & O_NONBLOCK) flags |= G_IO_FLAG_NONBLOCK; return flags; } static gboolean g_io_gnutls_prepare(GSource *source, gint *timeout) { DBG("source %p", source); *timeout = -1; return FALSE; } static gboolean g_io_gnutls_check(GSource *source) { GIOGnuTLSWatch *watch = (GIOGnuTLSWatch *) source; GIOCondition condition = watch->pollfd.revents; DBG("source %p condition %u", source, condition); if (condition & watch->condition) return TRUE; return FALSE; } static gboolean g_io_gnutls_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { GIOGnuTLSWatch *watch = (GIOGnuTLSWatch *) source; GIOFunc func = (GIOFunc) callback; GIOCondition condition = watch->pollfd.revents; DBG("source %p condition %u", source, condition); if (!func) return FALSE; return func(watch->channel, condition & watch->condition, user_data); } static void g_io_gnutls_finalize(GSource *source) { GIOGnuTLSWatch *watch = (GIOGnuTLSWatch *) source; DBG("source %p", source); g_io_channel_unref(watch->channel); } static GSourceFuncs gnutls_watch_funcs = { g_io_gnutls_prepare, g_io_gnutls_check, g_io_gnutls_dispatch, g_io_gnutls_finalize, }; static GSource *g_io_gnutls_create_watch(GIOChannel *channel, GIOCondition condition) { GIOGnuTLSChannel *gnutls_channel = (GIOGnuTLSChannel *) channel; GIOGnuTLSWatch *watch; GSource *source; DBG("channel %p condition %u", channel, condition); source = g_source_new(&gnutls_watch_funcs, sizeof(GIOGnuTLSWatch)); watch = (GIOGnuTLSWatch *) source; watch->channel = channel; g_io_channel_ref(channel); watch->condition = condition; watch->pollfd.fd = gnutls_channel->fd; watch->pollfd.events = condition; g_source_add_poll(source, &watch->pollfd); return source; } static GIOFuncs gnutls_channel_funcs = { g_io_gnutls_read, g_io_gnutls_write, g_io_gnutls_seek, g_io_gnutls_close, g_io_gnutls_create_watch, g_io_gnutls_free, g_io_gnutls_set_flags, g_io_gnutls_get_flags, }; static ssize_t g_io_gnutls_push_func(gnutls_transport_ptr_t transport_data, const void *buf, size_t count) { GIOGnuTLSChannel *gnutls_channel = transport_data; ssize_t result; DBG("count %zu", count); result = write(gnutls_channel->fd, buf, count); if (result < 0 && errno == EAGAIN) gnutls_channel->again = true; else gnutls_channel->again = false; DBG("result %zd", result); return result; } static ssize_t g_io_gnutls_pull_func(gnutls_transport_ptr_t transport_data, void *buf, size_t count) { GIOGnuTLSChannel *gnutls_channel = transport_data; ssize_t result; DBG("count %zu", count); result = read(gnutls_channel->fd, buf, count); if (result < 0 && errno == EAGAIN) gnutls_channel->again = true; else gnutls_channel->again = false; DBG("result %zd", result); return result; } bool g_io_channel_supports_tls(void) { return true; } GIOChannel *g_io_channel_gnutls_new(int fd) { GIOGnuTLSChannel *gnutls_channel; GIOChannel *channel; int err; DBG(""); gnutls_channel = g_new(GIOGnuTLSChannel, 1); channel = (GIOChannel *) gnutls_channel; g_io_channel_init(channel); channel->funcs = &gnutls_channel_funcs; gnutls_channel->fd = fd; channel->is_seekable = FALSE; channel->is_readable = TRUE; channel->is_writeable = TRUE; channel->do_encode = FALSE; g_io_gnutls_global_init(); err = gnutls_init(&gnutls_channel->session, GNUTLS_CLIENT); if (err < 0) { g_free(gnutls_channel); return NULL; } gnutls_transport_set_ptr(gnutls_channel->session, gnutls_channel); gnutls_transport_set_push_function(gnutls_channel->session, g_io_gnutls_push_func); gnutls_transport_set_pull_function(gnutls_channel->session, g_io_gnutls_pull_func); #if GNUTLS_VERSION_NUMBER < 0x020c00 gnutls_transport_set_lowat(gnutls_channel->session, 0); gnutls_priority_set_direct(gnutls_channel->session, "NORMAL:%COMPAT", NULL); #else gnutls_priority_set_direct(gnutls_channel->session, "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.0:+VERS-SSL3.0:%COMPAT", NULL); #endif gnutls_certificate_allocate_credentials(&gnutls_channel->cred); gnutls_credentials_set(gnutls_channel->session, GNUTLS_CRD_CERTIFICATE, gnutls_channel->cred); #if defined TIZEN_SYS_CA_BUNDLE #define QUOTEME(x) #x gnutls_certificate_set_verify_function(gnutls_channel->cred, &tpkp_gnutls_verify_callback); /* * TODO: get ca-bundle path build-time configuration unless gnutls set it as a default */ DBG("tizen sys ca bundle : %s", QUOTEME(TIZEN_SYS_CA_BUNDLE)); gnutls_certificate_set_x509_trust_file(gnutls_channel->cred, QUOTEME(TIZEN_SYS_CA_BUNDLE), GNUTLS_X509_FMT_PEM); #endif DBG("channel %p", channel); return channel; }