]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
tls-stream: add a new plugin providing TLS secured TCP streams tls-stream
authorMartin Willi <martin@revosec.ch>
Fri, 5 Jul 2013 14:05:10 +0000 (16:05 +0200)
committerMartin Willi <martin@revosec.ch>
Wed, 17 Jul 2013 15:11:01 +0000 (17:11 +0200)
configure.in
src/libstrongswan/Makefile.am
src/libstrongswan/plugins/tls_stream/Makefile.am [new file with mode: 0644]
src/libstrongswan/plugins/tls_stream/tls_stream.c [new file with mode: 0644]
src/libstrongswan/plugins/tls_stream/tls_stream.h [new file with mode: 0644]
src/libstrongswan/plugins/tls_stream/tls_stream_plugin.c [new file with mode: 0644]
src/libstrongswan/plugins/tls_stream/tls_stream_plugin.h [new file with mode: 0644]
src/libstrongswan/plugins/tls_stream/tls_stream_service.c [new file with mode: 0644]
src/libstrongswan/plugins/tls_stream/tls_stream_service.h [new file with mode: 0644]

index fbc771bf34ab577893e03bd4c1a952ea7143eda0..ae23cf10d0f9aeb998673dd273580ca9d347b102 100644 (file)
@@ -109,6 +109,7 @@ ARG_ENABL_SET([curl],           [enable CURL fetcher plugin to fetch files via l
 ARG_ENABL_SET([unbound],        [enable UNBOUND resolver plugin to perform DNS queries via libunbound. Requires libldns and libunbound.])
 ARG_ENABL_SET([soup],           [enable soup fetcher plugin to fetch from HTTP via libsoup. Requires libsoup.])
 ARG_ENABL_SET([ldap],           [enable LDAP fetching plugin to fetch files via libldap. Requires openLDAP.])
+ARG_ENABL_SET([tls-stream],     [enable libtls based TLS streams and services.])
 ARG_DISBL_SET([aes],            [disable AES software implementation plugin.])
 ARG_DISBL_SET([des],            [disable DES/3DES software implementation plugin.])
 ARG_ENABL_SET([blowfish],       [enable Blowfish software implementation plugin.])
@@ -326,7 +327,7 @@ if test x$eap_sim = xtrue; then
        simaka=true;
 fi
 
-if test x$eap_tls = xtrue -o x$eap_ttls = xtrue -o x$eap_peap = xtrue; then
+if test x$eap_tls = xtrue -o x$eap_ttls = xtrue -o x$eap_peap = xtrue -o x$tls_stream = xtrue; then
        tls=true;
 fi
 
@@ -979,6 +980,7 @@ ADD_PLUGIN([curl],                 [s charon scepclient scripts nm cmd])
 ADD_PLUGIN([soup],                 [s charon scripts nm cmd])
 ADD_PLUGIN([unbound],              [s charon scripts])
 ADD_PLUGIN([ldap],                 [s charon scepclient scripts nm cmd])
+ADD_PLUGIN([tls-stream],           [s charon])
 ADD_PLUGIN([mysql],                [s charon pool manager medsrv attest])
 ADD_PLUGIN([sqlite],               [s charon pool manager medsrv attest])
 ADD_PLUGIN([pkcs11],               [s charon pki nm cmd])
@@ -1115,6 +1117,7 @@ AM_CONDITIONAL(USE_CURL, test x$curl = xtrue)
 AM_CONDITIONAL(USE_UNBOUND, test x$unbound = xtrue)
 AM_CONDITIONAL(USE_SOUP, test x$soup = xtrue)
 AM_CONDITIONAL(USE_LDAP, test x$ldap = xtrue)
+AM_CONDITIONAL(USE_TLS_STREAM, test x$tls_stream = xtrue)
 AM_CONDITIONAL(USE_AES, test x$aes = xtrue)
 AM_CONDITIONAL(USE_DES, test x$des = xtrue)
 AM_CONDITIONAL(USE_BLOWFISH, test x$blowfish = xtrue)
@@ -1344,6 +1347,7 @@ AC_CONFIG_FILES([
        src/libstrongswan/plugins/unbound/Makefile
        src/libstrongswan/plugins/soup/Makefile
        src/libstrongswan/plugins/ldap/Makefile
+       src/libstrongswan/plugins/tls_stream/Makefile
        src/libstrongswan/plugins/mysql/Makefile
        src/libstrongswan/plugins/sqlite/Makefile
        src/libstrongswan/plugins/padlock/Makefile
index 8d2865d60b3a108d4b53053c2b1488a962077b59..e9cb00bf0f8efd15508cfa61fba73a37f4622d4a 100644 (file)
@@ -82,7 +82,7 @@ endif
 
 library.lo :   $(top_builddir)/config.status
 
-libstrongswan_la_LIBADD = $(PTHREADLIB) $(DLLIB) $(BTLIB) $(SOCKLIB) $(RTLIB) $(BFDLIB) $(UNWINDLIB)
+libstrongswan_la_LIBADD = $(PTHREADLIB) $(DLLIB) $(BTLIB) $(SOCKLIB) $(RTLIB) $(BFDLIB) $(UNWINDLIB) $(top_builddir)/src/libtls/libtls.la
 
 INCLUDES = -I$(top_srcdir)/src/libstrongswan
 AM_CFLAGS = \
@@ -375,6 +375,13 @@ if MONOLITHIC
 endif
 endif
 
+if USE_TLS_STREAM
+  SUBDIRS += plugins/tls_stream
+if MONOLITHIC
+  libstrongswan_la_LIBADD += plugins/tls_stream/libstrongswan-tls-stream.la
+endif
+endif
+
 if USE_MYSQL
   SUBDIRS += plugins/mysql
 if MONOLITHIC
diff --git a/src/libstrongswan/plugins/tls_stream/Makefile.am b/src/libstrongswan/plugins/tls_stream/Makefile.am
new file mode 100644 (file)
index 0000000..391f20a
--- /dev/null
@@ -0,0 +1,18 @@
+
+INCLUDES = -I$(top_srcdir)/src/libstrongswan -I$(top_srcdir)/src/libtls
+
+AM_CFLAGS = -rdynamic
+
+if MONOLITHIC
+noinst_LTLIBRARIES = libstrongswan-tls-stream.la
+else
+plugin_LTLIBRARIES = libstrongswan-tls-stream.la
+libstrongswan_tls_stream_la_LIBADD = $(top_builddir)/src/libtls/libtls.la
+endif
+
+libstrongswan_tls_stream_la_SOURCES = \
+       tls_stream_plugin.h tls_stream_plugin.c \
+       tls_stream.h tls_stream.c \
+       tls_stream_service.h tls_stream_service.c
+
+libstrongswan_tls_stream_la_LDFLAGS = -module -avoid-version
diff --git a/src/libstrongswan/plugins/tls_stream/tls_stream.c b/src/libstrongswan/plugins/tls_stream/tls_stream.c
new file mode 100644 (file)
index 0000000..f020f74
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+
+#include "tls_stream.h"
+
+#include <tls_socket.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+
+typedef struct private_tls_stream_t private_tls_stream_t;
+
+/**
+ * Private data of an tls_stream_t object.
+ */
+struct private_tls_stream_t {
+
+       /**
+        * Public tls_stream_t interface.
+        */
+       stream_t public;
+
+       /**
+        * Underlying TLS socket.
+        */
+       tls_socket_t *tls;
+
+       /**
+        * FD for encrypted data
+        */
+       int fd;
+
+       /**
+        * Callback if data is ready to read
+        */
+       stream_cb_t read_cb;
+
+       /**
+        * Data for read-ready callback
+        */
+       void *read_data;
+
+       /**
+        * Callback if write is non-blocking
+        */
+       stream_cb_t write_cb;
+
+       /**
+        * Data for write-ready callback
+        */
+       void *write_data;
+};
+
+METHOD(stream_t, read_, ssize_t,
+       private_tls_stream_t *this, void *buf, size_t len, bool block)
+{
+       return this->tls->read(this->tls, buf, len, block);
+}
+
+METHOD(stream_t, read_all, bool,
+       private_tls_stream_t *this, void *buf, size_t len)
+{
+       ssize_t ret;
+
+       while (len)
+       {
+               ret = read_(this, buf, len, TRUE);
+               if (ret < 0)
+               {
+                       return FALSE;
+               }
+               if (ret == 0)
+               {
+                       errno = ECONNRESET;
+                       return FALSE;
+               }
+               len -= ret;
+               buf += ret;
+       }
+       return TRUE;
+}
+
+METHOD(stream_t, write_, ssize_t,
+       private_tls_stream_t *this, void *buf, size_t len, bool block)
+{
+       return this->tls->write(this->tls, buf, len);
+}
+
+METHOD(stream_t, write_all, bool,
+       private_tls_stream_t *this, void *buf, size_t len)
+{
+       ssize_t ret;
+
+       while (len)
+       {
+               ret = write_(this, buf, len, TRUE);
+               if (ret < 0)
+               {
+                       return FALSE;
+               }
+               if (ret == 0)
+               {
+                       errno = ECONNRESET;
+                       return FALSE;
+               }
+               len -= ret;
+               buf += ret;
+       }
+       return TRUE;
+}
+
+/**
+ * Remove a registered watcher
+ */
+static void remove_watcher(private_tls_stream_t *this)
+{
+       if (this->read_cb || this->write_cb)
+       {
+               lib->watcher->remove(lib->watcher, this->fd);
+       }
+}
+
+/**
+ * Watcher callback
+ */
+static bool watch(private_tls_stream_t *this, int fd, watcher_event_t event)
+{
+       bool keep = FALSE;
+       stream_cb_t cb;
+
+       switch (event)
+       {
+               case WATCHER_READ:
+                       cb = this->read_cb;
+                       this->read_cb = NULL;
+                       keep = cb(this->read_data, &this->public);
+                       if (keep)
+                       {
+                               this->read_cb = cb;
+                       }
+                       break;
+               case WATCHER_WRITE:
+                       cb = this->write_cb;
+                       this->write_cb = NULL;
+                       keep = cb(this->write_data, &this->public);
+                       if (keep)
+                       {
+                               this->write_cb = cb;
+                       }
+                       break;
+               case WATCHER_EXCEPT:
+                       break;
+       }
+       return keep;
+}
+
+/**
+ * Register watcher for stream callbacks
+ */
+static void add_watcher(private_tls_stream_t *this)
+{
+       watcher_event_t events = 0;
+
+       if (this->read_cb)
+       {
+               events |= WATCHER_READ;
+       }
+       if (this->write_cb)
+       {
+               events |= WATCHER_WRITE;
+       }
+       if (events)
+       {
+               lib->watcher->add(lib->watcher, this->fd, events,
+                                                 (watcher_cb_t)watch, this);
+       }
+}
+
+METHOD(stream_t, on_read, void,
+       private_tls_stream_t *this, stream_cb_t cb, void *data)
+{
+       remove_watcher(this);
+
+       this->read_cb = cb;
+       this->read_data = data;
+
+       add_watcher(this);
+}
+
+METHOD(stream_t, on_write, void,
+       private_tls_stream_t *this, stream_cb_t cb, void *data)
+{
+       remove_watcher(this);
+
+       this->write_cb = cb;
+       this->write_data = data;
+
+       add_watcher(this);
+}
+
+#if defined(HAVE_FOPENCOOKIE)
+
+/**
+ * Read callback for fopencookie()
+ */
+static ssize_t cookie_read(private_tls_stream_t *this, char *buf, size_t len)
+{
+       return this->tls->read(this->tls, buf, len, TRUE);
+}
+
+/**
+ * Write callback for fopencookie()
+ */
+static ssize_t cookie_write(private_tls_stream_t *this, char *buf, size_t len)
+{
+       return this->tls->write(this->tls, buf, len);
+}
+
+METHOD(stream_t, get_file, FILE*,
+       private_tls_stream_t *this)
+{
+       static cookie_io_functions_t cookie_funcs = {
+               .read = (void*)cookie_read,
+               .write = (void*)cookie_write,
+               .seek = NULL,
+               .close = NULL,
+       };
+       return fopencookie(this, "r+", cookie_funcs);
+}
+
+#elif defined(HAVE_FUNOPEN)
+
+/**
+ * Read callback for funopen()
+ */
+static int fun_read(private_tls_stream_t *this, char *buf, int len)
+{
+       return this->tls->read(this->tls, buf, len, TRUE);
+}
+
+/**
+ * Write callback for funopen()
+ */
+static int fun_write(private_tls_stream_t *this, char *buf, int len)
+{
+       return this->tls->write(this->tls, buf, len);
+}
+
+METHOD(stream_t, get_file, FILE*,
+       private_tls_stream_t *this)
+{
+       return funopen(this, (void*)fun_read, (void*)fun_write, NULL, NULL);
+}
+
+#else /* !HAVE_FOPENCOOKIE && !HAVE_FUNOPEN */
+
+METHOD(stream_t, get_file, FILE*,
+       private_tls_stream_t *this)
+{
+       return NULL;
+}
+
+#endif /* HAVE_FOPENCOOKIE/HAVE_FUNOPEN */
+
+METHOD(stream_t, destroy, void,
+       private_tls_stream_t *this)
+{
+       this->tls->destroy(this->tls);
+       close(this->fd);
+       free(this);
+}
+
+/**
+ * See header
+ */
+stream_t *tls_stream_create_from_fd(int fd, bool is_server,
+                                                                       identification_t *server,
+                                                                       tls_cache_t *cache)
+{
+       private_tls_stream_t *this;
+
+       INIT(this,
+               .public = {
+                       .read = _read_,
+                       .read_all = _read_all,
+                       .on_read = _on_read,
+                       .write = _write_,
+                       .write_all = _write_all,
+                       .on_write = _on_write,
+                       .get_file = _get_file,
+                       .destroy = _destroy,
+               },
+               .tls = tls_socket_create(is_server, server, NULL, fd, cache),
+               .fd = fd,
+       );
+
+       if (!this->tls)
+       {
+               free(this);
+               return NULL;
+       }
+       return &this->public;
+}
+
+/**
+ * See header.
+ */
+int tls_stream_parse_uri(char *uri, struct sockaddr *addr,
+                                                identification_t **server)
+{
+       identification_t *id;
+       char *pos, buf[256];
+       host_t *host;
+       u_long port;
+       int len;
+
+       if (!strncaseeq(uri, "tcp+tls://", strlen("tcp+tls://")))
+       {
+               return -1;
+       }
+       uri += strlen("tcp+tls://");
+       pos = strrchr(uri, '@');
+       if (!pos)
+       {
+               return -1;
+       }
+       id = identification_create_from_data(chunk_create(uri, pos - uri));
+       uri = pos + 1;
+       pos = strrchr(uri, ':');
+       if (!pos)
+       {
+               id->destroy(id);
+               return -1;
+       }
+       if (*uri == '[' && pos > uri && *(pos - 1) == ']')
+       {
+               /* IPv6 URI */
+               snprintf(buf, sizeof(buf), "%.*s", (int)(pos - uri - 2), uri + 1);
+       }
+       else
+       {
+               snprintf(buf, sizeof(buf), "%.*s", (int)(pos - uri), uri);
+       }
+       port = strtoul(pos + 1, &pos, 10);
+       if (port == ULONG_MAX || *pos || port > 65535)
+       {
+               id->destroy(id);
+               return -1;
+       }
+       host = host_create_from_dns(buf, AF_UNSPEC, port);
+       if (!host)
+       {
+               id->destroy(id);
+               return -1;
+       }
+       len = *host->get_sockaddr_len(host);
+       memcpy(addr, host->get_sockaddr(host), len);
+       host->destroy(host);
+       *server = id;
+       return len;
+}
+
+/**
+ * See header
+ */
+stream_t *tls_stream_create(char *uri)
+{
+       union {
+               struct sockaddr_in in;
+               struct sockaddr_in6 in6;
+               struct sockaddr sa;
+       } addr;
+       int fd, len;
+       identification_t *server;
+       stream_t *stream;
+
+       len = tls_stream_parse_uri(uri, &addr.sa, &server);
+       if (len == -1)
+       {
+               DBG1(DBG_NET, "invalid stream URI: '%s'", uri);
+               return NULL;
+       }
+       fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+       if (fd < 0)
+       {
+               DBG1(DBG_NET, "opening socket '%s' failed: %s", uri, strerror(errno));
+               return NULL;
+       }
+       if (connect(fd, &addr.sa, len))
+       {
+               DBG1(DBG_NET, "connecting to '%s' failed: %s", uri, strerror(errno));
+               close(fd);
+               return NULL;
+       }
+       stream = tls_stream_create_from_fd(fd, FALSE, server, NULL);
+       server->destroy(server);
+       if (!stream)
+       {
+               close(fd);
+       }
+       return stream;
+}
diff --git a/src/libstrongswan/plugins/tls_stream/tls_stream.h b/src/libstrongswan/plugins/tls_stream/tls_stream.h
new file mode 100644 (file)
index 0000000..fad19e9
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup tls_stream tls_stream
+ * @{ @ingroup tls
+ */
+
+#ifndef TLS_STREAM_H_
+#define TLS_STREAM_H_
+
+#include <library.h>
+#include <tls_cache.h>
+
+/**
+ * Helper function to parse a tcp+tls:// URI.
+ *
+ * @param uri                  URI to parse
+ * @param addr                 sockaddr, large enough for URI address
+ * @param server               pointer receiving allocated server identity
+ * @return                             len of created addr, -1 on error
+ */
+int tls_stream_parse_uri(char *uri, struct sockaddr *addr,
+                                                identification_t **server);
+
+/**
+ * Helper function to create a stream from an FD and a server identity.
+ *
+ * @param fd                   file descripter
+ * @param is_server            TRUE to act as TLS server, FALSE for client
+ * @param server               server identity, gets cloned
+ * @param cache                        shared TLS session cache, if any
+ * @return                             client stream, NULL on error
+ */
+stream_t *tls_stream_create_from_fd(int fd, bool is_server,
+                                                                       identification_t *server,
+                                                                       tls_cache_t *cache);
+
+/**
+ * Create a tls_stream instance.
+ *
+ * The following URIs are currently accepted by this constructor:
+ * - tcp+tls://serverid@address:port
+ *   Server authenticates with a certificate for serverid, no client auth.
+ *
+ * @param uri                  URI to create a stream for
+ * @return                             stream instance, NULL on error
+ */
+stream_t *tls_stream_create(char *uri);
+
+#endif /** TLS_STREAM_H_ @}*/
diff --git a/src/libstrongswan/plugins/tls_stream/tls_stream_plugin.c b/src/libstrongswan/plugins/tls_stream/tls_stream_plugin.c
new file mode 100644 (file)
index 0000000..79d5368
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#include "tls_stream_plugin.h"
+#include "tls_stream.h"
+#include "tls_stream_service.h"
+
+#include <library.h>
+
+typedef struct private_tls_stream_plugin_t private_tls_stream_plugin_t;
+
+/**
+ * Private data of tls_stream_plugin
+ */
+struct private_tls_stream_plugin_t {
+
+       /**
+        * public functions
+        */
+       tls_stream_plugin_t public;
+};
+
+METHOD(plugin_t, get_name, char*,
+       private_tls_stream_plugin_t *this)
+{
+       return "tls-stream";
+}
+
+METHOD(plugin_t, get_features, int,
+       private_tls_stream_plugin_t *this, plugin_feature_t *features[])
+{
+       static plugin_feature_t f[] = {
+               PLUGIN_REGISTER(STREAM, tls_stream_create),
+                       PLUGIN_PROVIDE(STREAM, "tcp+tls://"),
+               PLUGIN_REGISTER(STREAM_SERVICE, tls_stream_service_create),
+                       PLUGIN_PROVIDE(STREAM_SERVICE, "tcp+tls://"),
+       };
+       *features = f;
+       return countof(f);
+}
+
+METHOD(plugin_t, destroy, void,
+       private_tls_stream_plugin_t *this)
+{
+       free(this);
+}
+
+/*
+ * see header file
+ */
+plugin_t *tls_stream_plugin_create()
+{
+       private_tls_stream_plugin_t *this;
+
+       INIT(this,
+               .public = {
+                       .plugin = {
+                               .get_name = _get_name,
+                               .get_features = _get_features,
+                               .destroy = _destroy,
+                       },
+               },
+       );
+
+       return &this->public.plugin;
+}
diff --git a/src/libstrongswan/plugins/tls_stream/tls_stream_plugin.h b/src/libstrongswan/plugins/tls_stream/tls_stream_plugin.h
new file mode 100644 (file)
index 0000000..9212fa0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup tls_stream tls_stream
+ * @ingroup plugins
+ *
+ * @defgroup tls_stream_plugin tls_stream_plugin
+ * @{ @ingroup tls_stream
+ */
+
+#ifndef TLS_STREAM_PLUGIN_H_
+#define TLS_STREAM_PLUGIN_H_
+
+#include <plugins/plugin.h>
+
+typedef struct tls_stream_plugin_t tls_stream_plugin_t;
+
+/**
+ * Plugin providing TLS protected streams and stream services.
+ */
+struct tls_stream_plugin_t {
+
+       /**
+        * Implements plugin interface.
+        */
+       plugin_t plugin;
+};
+
+#endif /** TLS_STREAM_PLUGIN_H_ @}*/
diff --git a/src/libstrongswan/plugins/tls_stream/tls_stream_service.c b/src/libstrongswan/plugins/tls_stream/tls_stream_service.c
new file mode 100644 (file)
index 0000000..b28d883
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#include "tls_stream_service.h"
+#include "tls_stream.h"
+
+#include <threading/thread.h>
+#include <threading/mutex.h>
+#include <threading/condvar.h>
+#include <processing/jobs/callback_job.h>
+
+#include <unistd.h>
+#include <errno.h>
+
+typedef struct private_tls_stream_service_t private_tls_stream_service_t;
+
+/**
+ * Private data of an tls_stream_service_t object.
+ */
+struct private_tls_stream_service_t {
+
+       /**
+        * Public tls_stream_service_t interface.
+        */
+       stream_service_t public;
+
+       /**
+        * Underlying socket
+        */
+       int fd;
+
+       /**
+        * Accept callback
+        */
+       stream_service_cb_t cb;
+
+       /**
+        * Accept callback data
+        */
+       void *data;
+
+       /**
+        * Job priority to invoke callback with
+        */
+       job_priority_t prio;
+
+       /**
+        * Maximum number of parallel callback invocations
+        */
+       u_int cncrncy;
+
+       /**
+        * Currently active jobs
+        */
+       u_int active;
+
+       /**
+        * mutex to lock active counter
+        */
+       mutex_t *mutex;
+
+       /**
+        * Condvar to wait for callback termination
+        */
+       condvar_t *condvar;
+
+       /**
+        * Server identity
+        */
+       identification_t *server;
+
+       /**
+        * TLS session cache for this service
+        */
+       tls_cache_t *cache;
+};
+
+/**
+ * Data to pass to async accept job
+ */
+typedef struct {
+       /** callback function */
+       stream_service_cb_t cb;
+       /** callback data */
+       void *data;
+       /** accepted connection */
+       int fd;
+       /** reference to stream service */
+       private_tls_stream_service_t *this;
+} async_data_t;
+
+/**
+ * Clean up accept data
+ */
+static void destroy_async_data(async_data_t *data)
+{
+       private_tls_stream_service_t *this = data->this;
+
+       this->mutex->lock(this->mutex);
+       if (this->active-- == this->cncrncy)
+       {
+               /* leaving concurrency limit, restart accept()ing. */
+               this->public.on_accept(&this->public, this->cb, this->data,
+                                                          this->prio, this->cncrncy);
+       }
+       this->condvar->signal(this->condvar);
+       this->mutex->unlock(this->mutex);
+
+       if (data->fd != -1)
+       {
+               close(data->fd);
+       }
+       free(data);
+}
+
+/**
+ * Async processing of accepted connection
+ */
+static job_requeue_t accept_async(async_data_t *data)
+{
+       stream_t *stream;
+
+       stream = tls_stream_create_from_fd(data->fd, TRUE, data->this->server,
+                                                                          data->this->cache);
+       if (stream)
+       {
+               /* FD is now owned by stream, don't close it during cleanup */
+               data->fd = -1;
+               thread_cleanup_push((void*)stream->destroy, stream);
+               thread_cleanup_pop(!data->cb(data->data, stream));
+       }
+       return JOB_REQUEUE_NONE;
+}
+
+/**
+ * Watcher callback function
+ */
+static bool watch(private_tls_stream_service_t *this,
+                                 int fd, watcher_event_t event)
+{
+       async_data_t *data;
+       bool keep = TRUE;
+
+       INIT(data,
+               .cb = this->cb,
+               .data = this->data,
+               .fd = accept(fd, NULL, NULL),
+               .this = this,
+       );
+
+       if (data->fd != -1)
+       {
+               this->mutex->lock(this->mutex);
+               if (++this->active == this->cncrncy)
+               {
+                       /* concurrency limit reached, stop accept()ing new connections */
+                       keep = FALSE;
+               }
+               this->mutex->unlock(this->mutex);
+
+               lib->processor->queue_job(lib->processor,
+                       (job_t*)callback_job_create_with_prio((void*)accept_async, data,
+                               (void*)destroy_async_data, (callback_job_cancel_t)return_false,
+                               this->prio));
+       }
+       else
+       {
+               free(data);
+       }
+       return keep;
+}
+
+METHOD(stream_service_t, on_accept, void,
+       private_tls_stream_service_t *this, stream_service_cb_t cb, void *data,
+       job_priority_t prio, u_int cncrncy)
+{
+       this->mutex->lock(this->mutex);
+
+       /* wait for all callbacks to return */
+       this->mutex->lock(this->mutex);
+       while (this->active)
+       {
+               this->condvar->wait(this->condvar, this->mutex);
+       }
+       this->mutex->unlock(this->mutex);
+
+       if (this->cb)
+       {
+               lib->watcher->remove(lib->watcher, this->fd);
+       }
+
+       this->cb = cb;
+       this->data = data;
+       if (prio <= JOB_PRIO_MAX)
+       {
+               this->prio = prio;
+       }
+       this->cncrncy = cncrncy;
+
+       if (this->cb)
+       {
+               lib->watcher->add(lib->watcher, this->fd,
+                                                 WATCHER_READ, (watcher_cb_t)watch, this);
+       }
+
+       this->mutex->unlock(this->mutex);
+}
+
+METHOD(stream_service_t, destroy, void,
+       private_tls_stream_service_t *this)
+{
+       on_accept(this, NULL, NULL, this->prio, this->cncrncy);
+       close(this->fd);
+       this->mutex->destroy(this->mutex);
+       this->condvar->destroy(this->condvar);
+       this->server->destroy(this->server);
+       this->cache->destroy(this->cache);
+       free(this);
+}
+
+/**
+ * See header
+ */
+stream_service_t *tls_stream_service_create_from_fd(int fd,
+                                                                                                       identification_t *server)
+{
+       private_tls_stream_service_t *this;
+
+       INIT(this,
+               .public = {
+                       .on_accept = _on_accept,
+                       .destroy = _destroy,
+               },
+               .fd = fd,
+               .prio = JOB_PRIO_MEDIUM,
+               .mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
+               .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
+               .server = server,
+               .cache = tls_cache_create(25, 180),
+       );
+
+       return &this->public;
+}
+
+/**
+ * See header
+ */
+stream_service_t *tls_stream_service_create(char *uri, int backlog)
+{
+       union {
+               struct sockaddr_in in;
+               struct sockaddr_in6 in6;
+               struct sockaddr sa;
+       } addr;
+       int fd, len, on = 1;
+       identification_t *server;
+
+       len = tls_stream_parse_uri(uri, &addr.sa, &server);
+       if (len == -1)
+       {
+               DBG1(DBG_NET, "invalid stream URI: '%s'", uri);
+               return NULL;
+       }
+       fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+       if (fd < 0)
+       {
+               DBG1(DBG_NET, "opening socket '%s' failed: %s", uri, strerror(errno));
+               server->destroy(server);
+               return NULL;
+       }
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
+       {
+               DBG1(DBG_NET, "SO_REUSADDR on '%s' failed: %s", uri, strerror(errno));
+       }
+       if (bind(fd, &addr.sa, len) < 0)
+       {
+               DBG1(DBG_NET, "binding socket '%s' failed: %s", uri, strerror(errno));
+               server->destroy(server);
+               close(fd);
+               return NULL;
+       }
+       if (listen(fd, backlog) < 0)
+       {
+               DBG1(DBG_NET, "listen on socket '%s' failed: %s", uri, strerror(errno));
+               server->destroy(server);
+               close(fd);
+               return NULL;
+       }
+       return tls_stream_service_create_from_fd(fd, server);
+}
diff --git a/src/libstrongswan/plugins/tls_stream/tls_stream_service.h b/src/libstrongswan/plugins/tls_stream/tls_stream_service.h
new file mode 100644 (file)
index 0000000..2610b9e
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup tls_stream_service tls_stream_service
+ * @{ @ingroup tls
+ */
+
+#ifndef TLS_STREAM_SERVICE_H_
+#define TLS_STREAM_SERVICE_H_
+
+#include <library.h>
+
+/**
+ * Create a service instance for TLS secured TCP sockets.
+ *
+ * @param uri          TLS socket specific URI, must start with "tcp+tls://"
+ * @param backlog      size of the backlog queue, as passed to listen()
+ * @return                     stream_service instance, NULL on failure
+ */
+stream_service_t* tls_stream_service_create(char *uri, int backlog);
+
+#endif /** TLS_STREAM_SERVICE_H_ @}*/