]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
tlsproxy: included but not as submodule
authorNikos Mavrogiannopoulos <nmav@redhat.com>
Mon, 2 Jul 2018 12:12:48 +0000 (14:12 +0200)
committerNikos Mavrogiannopoulos <nmav@redhat.com>
Mon, 2 Jul 2018 13:33:21 +0000 (15:33 +0200)
This allows updating the example when necessary within the repository
and reduces the amount of external dependencies for CI.

Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
doc/examples/Makefile.am
doc/examples/tlsproxy/.gitignore [new file with mode: 0644]
doc/examples/tlsproxy/LICENSE [new file with mode: 0644]
doc/examples/tlsproxy/README.md [new file with mode: 0644]
doc/examples/tlsproxy/buffer.c [new file with mode: 0644]
doc/examples/tlsproxy/buffer.h [new file with mode: 0644]
doc/examples/tlsproxy/crypto-gnutls.c [new file with mode: 0644]
doc/examples/tlsproxy/crypto-gnutls.h [new file with mode: 0644]
doc/examples/tlsproxy/tlsproxy.c [new file with mode: 0644]

index f230eca8a4bc7d09d843bed457bfec57e3b5237a..c40bf4f53364d2469cee09af7e7ec39afe100faf 100644 (file)
@@ -41,9 +41,7 @@ LDADD = libexamples.la                                \
 CXX_LDADD = ../../lib/libgnutlsxx.la \
        $(LDADD)
 
-EXTRA_DIST = tlsproxy/buffer.c tlsproxy/buffer.h tlsproxy/crypto-gnutls.c \
-       tlsproxy/crypto-gnutls.h tlsproxy/LICENSE tlsproxy/tlsproxy.c \
-       tlsproxy/README.md tlsproxy/Makefile
+EXTRA_DIST = tlsproxy/LICENSE tlsproxy/README.md
 
 noinst_PROGRAMS = ex-client-resume ex-client-dtls
 noinst_PROGRAMS += ex-cert-select ex-client-x509
@@ -76,6 +74,11 @@ if ENABLE_SRP
 noinst_PROGRAMS += ex-client-srp ex-serv-srp
 endif
 
+noinst_PROGRAMS += tlsproxy/tlsproxy
+
+tlsproxy_tlsproxy_SOURCES = tlsproxy/buffer.c tlsproxy/buffer.h tlsproxy/crypto-gnutls.c \
+       tlsproxy/crypto-gnutls.h tlsproxy/tlsproxy.c
+
 noinst_LTLIBRARIES = libexamples.la
 
 if ENABLE_OCSP
diff --git a/doc/examples/tlsproxy/.gitignore b/doc/examples/tlsproxy/.gitignore
new file mode 100644 (file)
index 0000000..4716367
--- /dev/null
@@ -0,0 +1,3 @@
+*~
+*.o
+tlsproxy
diff --git a/doc/examples/tlsproxy/LICENSE b/doc/examples/tlsproxy/LICENSE
new file mode 100644 (file)
index 0000000..43f5934
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Wrymouth Innovation Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/doc/examples/tlsproxy/README.md b/doc/examples/tlsproxy/README.md
new file mode 100644 (file)
index 0000000..a34a18c
--- /dev/null
@@ -0,0 +1,53 @@
+tlsproxy
+========
+
+`tlsproxy` is a TLS proxy written with GnuTLS. It is mostly designed as an
+example of how to use asynchronous (non-blocking) I/O with GnuTLS. More
+accurately, it was designed so I could learn how to do it. I think I've
+got it right.
+
+To that end, it's been divided up as follows:
+
+* `crypto.c` does all the crypto, and `tlssession_mainloop()` does the hard work.
+* `buffer.c` provides ring buffer support.
+* `tlsproxy.c` deals with command line options and connecting sockets.
+
+It can be used in two modes:
+
+* Client mode (default). Listens on an unencrypted port, connects to
+  an encrypted port.
+* Server mode (run with `-s`). Listens on an encrypted port, connects to
+  an unencrypted port.
+
+Usage
+=====
+
+```
+tlsproxy
+
+Usage:
+     tlsproxy [OPTIONS]
+
+A TLS client or server proxy
+
+Options:
+     -c, --connect ADDRRESS    Connect to ADDRESS
+     -l, --listen ADDRESS      Listen on ADDRESS
+     -K, --key FILE            Use FILE as private key
+     -C, --cert FILE           Use FILE as public key
+     -A, --cacert FILE         Use FILE as public CA cert file
+     -H, --hostname HOSTNAME   Use HOSTNAME to validate the CN of the peer
+                               rather than hostname extracted from -C option
+     -s, --server              Run the listen port encrypted rather than the
+                               connect port
+     -i, --insecure            Do not validate certificates
+     -n, --nofork              Do not fork off (aids debugging); specify twice
+                               to stop forking on accept as well
+     -d, --debug               Turn on debugging
+     -h, --help                Show this usage message
+```
+
+License
+=======
+
+MIT
\ No newline at end of file
diff --git a/doc/examples/tlsproxy/buffer.c b/doc/examples/tlsproxy/buffer.c
new file mode 100644 (file)
index 0000000..8224d79
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Wrymouth Innovation Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include "config.h"
+#include <sys/types.h>
+
+#include "buffer.h"
+
+typedef struct buffer
+{
+  char *buf;
+  ssize_t size;
+  ssize_t hwm;
+  ssize_t ridx;
+  ssize_t widx;
+  int empty;
+} buffer_t;
+
+/* the buffer is organised internally as follows:
+ *
+ * * There are b->size bytes in the buffer.
+ *
+ * * Bytes are at offsets 0 to b->size-1
+ * 
+ * * b->ridx points to the first readable byte
+ *
+ * * b->widx points to the first empty space
+ *
+ * * b->ridx < b->widx indicates a non-wrapped buffer:
+ *
+ *     0       ridx     widx            size
+ *     |       |        |               |
+ *     V       V        V               V
+ *     ........XXXXXXXXX................
+ *
+ * * b->ridx > b->widx indicates a wrapped buffer:
+ *
+ *     0       widx     ridx            size
+ *     |       |        |               |
+ *     V       V        V               V
+ *     XXXXXXXX.........XXXXXXXXXXXXXXXX
+ *
+ * * b->ridx == b->widx indicates a FULL buffer:
+ *
+ * * b->ridx == b->widx indicates a wrapped buffer:
+ *
+ *     0       widx == ridx            size
+ *     |       |                       |
+ *     V       V                       V
+ *     XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * An empty buffer is indicated by empty=1
+ *
+ */
+
+buffer_t *
+bufNew (ssize_t size, ssize_t hwm)
+{
+  buffer_t *b = calloc (1, sizeof (buffer_t));
+  b->buf = calloc (1, size);
+  b->size = size;
+  b->hwm = hwm;
+  b->empty = 1;
+  return b;
+}
+
+
+void
+bufFree (buffer_t * b)
+{
+  free (b->buf);
+  free (b);
+}
+
+/* get a maximal span to read. Returns 0 if buffer
+ * is empty
+ */
+ssize_t
+bufGetReadSpan (buffer_t * b, void **addr)
+{
+  if (b->empty)
+    {
+      *addr = NULL;
+      return 0;
+    }
+  *addr = &(b->buf[b->ridx]);
+  ssize_t len = b->widx - b->ridx;
+  if (len <= 0)
+    len = b->size - b->ridx;
+  return len;
+}
+
+/* get a maximal span to write. Returns 0 id buffer is full
+ */
+ssize_t
+bufGetWriteSpan (buffer_t * b, void **addr)
+{
+  if (b->empty)
+    {
+      *addr = b->buf;
+      b->ridx = 0;
+      b->widx = 0;
+      return b->size;
+    }
+  if (b->ridx == b->widx)
+    {
+      *addr = NULL;
+      return 0;
+    }
+  *addr = &(b->buf[b->widx]);
+  ssize_t len = b->ridx - b->widx;
+  if (len <= 0)
+    len = b->size - b->widx;
+  return len;
+}
+
+/* mark size bytes as read */
+void
+bufDoneRead (buffer_t * b, ssize_t size)
+{
+  while (!b->empty && (size > 0))
+    {
+      /* empty can't occur here, so equal pointers means full */
+      ssize_t len = b->widx - b->ridx;
+      if (len <= 0)
+       len = b->size - b->ridx;
+
+      /* len is the number of bytes in one read span */
+      if (len > size)
+       len = size;
+
+      b->ridx += len;
+      if (b->ridx >= b->size)
+       b->ridx = 0;
+
+      if (b->ridx == b->widx)
+       {
+         b->ridx = 0;
+         b->widx = 0;
+         b->empty = 1;
+       }
+
+      size -= len;
+    }
+}
+
+/* mark size bytes as written */
+void
+bufDoneWrite (buffer_t * b, ssize_t size)
+{
+  while ((b->empty || (b->ridx != b->widx)) && (size > 0))
+    {
+      /* full can't occur here, so equal pointers means empty */
+      ssize_t len = b->ridx - b->widx;
+      if (len <= 0)
+       len = b->size - b->widx;
+
+      /* len is the number of bytes in one write span */
+      if (len > size)
+       len = size;
+
+      b->widx += len;
+      if (b->widx >= b->size)
+       b->widx = 0;
+
+      /* it can't be empty as we've written at least one byte */
+      b->empty = 0;
+
+      size -= len;
+    }
+}
+
+int
+bufIsEmpty (buffer_t * b)
+{
+  return b->empty;
+}
+
+int
+bufIsFull (buffer_t * b)
+{
+  return !b->empty && (b->ridx == b->widx);
+}
+
+int
+bufIsOverHWM (buffer_t * b)
+{
+  return bufGetCount (b) > b->hwm;
+}
+
+ssize_t
+bufGetFree (buffer_t * b)
+{
+  return b->size - bufGetCount (b);
+}
+
+ssize_t
+bufGetCount (buffer_t * b)
+{
+  if (b->empty)
+    return 0;
+  return b->widx - b->ridx + ((b->ridx < b->widx) ? 0 : b->size);
+}
diff --git a/doc/examples/tlsproxy/buffer.h b/doc/examples/tlsproxy/buffer.h
new file mode 100644 (file)
index 0000000..c92b9a6
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 Wrymouth Innovation Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef __TLSPROXY_BUFFERS_H
+#define __TLSPROXY_BUFFERS_H
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+typedef struct buffer buffer_t;
+
+buffer_t *bufNew (ssize_t size, ssize_t hwm);
+void bufFree (buffer_t * b);
+ssize_t bufGetReadSpan (buffer_t * b, void **addr);
+ssize_t bufGetWriteSpan (buffer_t * b, void **addr);
+void bufDoneRead (buffer_t * b, ssize_t size);
+void bufDoneWrite (buffer_t * b, ssize_t size);
+int bufIsEmpty (buffer_t * b);
+int bufIsFull (buffer_t * b);
+int bufIsOverHWM (buffer_t * b);
+ssize_t bufGetFree (buffer_t * b);
+ssize_t bufGetCount (buffer_t * b);
+
+#endif
diff --git a/doc/examples/tlsproxy/crypto-gnutls.c b/doc/examples/tlsproxy/crypto-gnutls.c
new file mode 100644 (file)
index 0000000..14abb56
--- /dev/null
@@ -0,0 +1,583 @@
+/*
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Wrymouth Innovation Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <gnutls/x509.h>
+#include <gnutls/abstract.h>
+
+#include "crypto-gnutls.h"
+#include "buffer.h"
+
+#define FALSE 0
+#define TRUE 1
+
+typedef struct tlssession
+{
+  gnutls_certificate_credentials_t creds;
+  gnutls_session_t session;
+  char *hostname;
+  int (*quitfn) (void *opaque);
+  int (*erroutfn) (void *opaque, const char *format, va_list ap);
+  int debug;
+  void *opaque;
+} tlssession_t;
+
+#define BUF_SIZE 65536
+#define BUF_HWM ((BUF_SIZE*3)/4)
+
+static int
+falsequit (void *opaque)
+{
+  return FALSE;
+}
+
+static int
+quit (tlssession_t * s)
+{
+  return s->quitfn (s->opaque);
+}
+
+#if defined __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+#endif
+
+static int stderrout (void *opaque, const char *format, va_list ap)
+{
+  return vfprintf (stderr, format, ap);
+}
+
+static int
+errout (tlssession_t * s, const char *format, ...)
+{
+  va_list ap;
+  int ret;
+  va_start (ap, format);
+  ret = s->erroutfn (s->opaque, format, ap);
+  va_end (ap);
+  return ret;
+}
+
+static int
+debugout (tlssession_t * s, const char *format, ...)
+{
+  va_list ap;
+  int ret = 0;
+  va_start (ap, format);
+  if (s->debug)
+    ret = s->erroutfn (s->opaque, format, ap);
+  va_end (ap);
+  return ret;
+}
+
+static int
+socksetnonblock (int fd, int nb)
+{
+  int sf = fcntl (fd, F_GETFL, 0);
+  if (sf == -1)
+    return -1;
+  return fcntl (fd, F_SETFL, nb ? (sf | O_NONBLOCK) : (sf & ~O_NONBLOCK));
+}
+
+/* From (public domain) example file in GNUTLS
+ *
+ * This function will try to verify the peer's certificate, and
+ * also check if the hostname matches, and the activation, expiration dates.
+ */
+static int
+verify_certificate_callback (gnutls_session_t session)
+{
+  unsigned int status;
+  int ret;
+  tlssession_t *s;
+
+  /* read session pointer */
+  s = (tlssession_t *) gnutls_session_get_ptr (session);
+
+  if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509)
+    return GNUTLS_E_CERTIFICATE_ERROR;
+
+  /* This verification function uses the trusted CAs in the credentials
+   * structure. So you must have installed one or more CA certificates.
+   */
+  if (s->hostname && *s->hostname)
+    ret = gnutls_certificate_verify_peers3 (session, s->hostname, &status);
+  else
+    ret = gnutls_certificate_verify_peers2 (session, &status);
+
+  if (ret < 0)
+    {
+      debugout (s, "Could not verfify peer certificate due to an error\n");
+      return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+  if (status)
+    {
+      gnutls_datum_t txt;
+      ret = gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509,
+                                                        &txt, 0);
+      if (ret >= 0)
+        {
+          debugout (s, "verification error: %s\n", txt.data);
+          gnutls_free(txt.data);
+        }
+
+      return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+  debugout (s, "Peer passed certificate verification\n");
+
+  /* notify gnutls to continue handshake normally */
+  return 0;
+}
+
+tlssession_t *
+tlssession_new (int isserver,
+               char *keyfile, char *certfile, char *cacertfile,
+               char *hostname, int insecure, int debug,
+               int (*quitfn) (void *opaque),
+               int (*erroutfn) (void *opaque, const char *format,
+                                va_list ap), void *opaque)
+{
+  int ret;
+  tlssession_t *s = calloc (1, sizeof (tlssession_t));
+
+  if (quitfn)
+    s->quitfn = quitfn;
+  else
+    s->quitfn = falsequit;
+
+  if (erroutfn)
+    s->erroutfn = erroutfn;
+  else
+    s->erroutfn = stderrout;
+
+  if (hostname)
+    s->hostname = strdup (hostname);
+
+  s->debug = debug;
+
+  if (gnutls_certificate_allocate_credentials (&s->creds) < 0)
+    {
+      errout (s, "Certificate allocation memory error\n");
+      goto error;
+    }
+
+  if (cacertfile != NULL)
+    {
+      ret =
+       gnutls_certificate_set_x509_trust_file (s->creds, cacertfile,
+                                               GNUTLS_X509_FMT_PEM);
+      if (ret < 0)
+       {
+         errout (s, "Error setting the x509 trust file: %s\n",
+                 gnutls_strerror (ret));
+         goto error;
+       }
+
+      if (!insecure)
+       {
+         gnutls_certificate_set_verify_function (s->creds,
+                                                 verify_certificate_callback);
+         gnutls_certificate_set_verify_flags (s->creds,
+                                              GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
+       }
+    }
+
+  if (keyfile && !certfile)
+    certfile = keyfile;
+
+  if (certfile != NULL && keyfile != NULL)
+    {
+      ret =
+       gnutls_certificate_set_x509_key_file (s->creds, certfile, keyfile,
+                                             GNUTLS_X509_FMT_PEM);
+
+      if (ret < 0)
+       {
+         errout (s,
+                 "Error loading certificate or key file (%s, %s): %s\n",
+                 certfile, keyfile, gnutls_strerror (ret));
+         goto error;
+       }
+    }
+
+  if (isserver)
+    ret = gnutls_init (&s->session, GNUTLS_SERVER);
+  else
+    ret = gnutls_init (&s->session, GNUTLS_CLIENT);
+
+  if (ret < 0)
+    {
+      errout (s, "Cannot initialize GNUTLS session: %s\n",
+             gnutls_strerror (ret));
+      goto error;
+    }
+
+  gnutls_session_set_ptr (s->session, (void *) s);
+
+  if (!isserver && s->hostname && *s->hostname)
+    {
+      ret = gnutls_server_name_set (s->session, GNUTLS_NAME_DNS, s->hostname,
+                                   strlen (s->hostname));
+      if (ret < 0)
+        {
+          errout (s, "Cannot set server name: %s\n",
+                 gnutls_strerror (ret));
+          goto error;
+        }
+    }
+
+  ret = gnutls_set_default_priority (s->session);
+  if (ret < 0)
+    {
+      errout (s, "Cannot set default GNUTLS session priority: %s\n",
+             gnutls_strerror (ret));
+      goto error;
+    }
+
+  ret = gnutls_credentials_set (s->session, GNUTLS_CRD_CERTIFICATE, s->creds);
+  if (ret < 0)
+    {
+      errout (s, "Cannot set session GNUTL credentials: %s\n",
+             gnutls_strerror (ret));
+      goto error;
+    }
+
+  if (isserver)
+    {
+      /* requests but does not check a client certificate */
+      gnutls_certificate_server_set_request (s->session, GNUTLS_CERT_REQUEST);
+    }
+
+
+  return s;
+
+error:
+  if (s->session)
+    gnutls_deinit (s->session);
+  free (s);
+  return NULL;
+}
+
+void
+tlssession_close (tlssession_t * s)
+{
+  if (s->session)
+    gnutls_deinit (s->session);
+  free (s->hostname);
+  free (s);
+}
+
+int
+tlssession_init (void)
+{
+  return gnutls_global_init ();
+}
+
+
+int
+tlssession_mainloop (int cryptfd, int plainfd, tlssession_t * s)
+{
+  fd_set readfds;
+  fd_set writefds;
+  int maxfd;
+  int tls_wr_interrupted = 0;
+  int plainEOF = FALSE;
+  int cryptEOF = FALSE;
+  ssize_t ret;
+
+  buffer_t *plainToCrypt = bufNew (BUF_SIZE, BUF_HWM);
+  buffer_t *cryptToPlain = bufNew (BUF_SIZE, BUF_HWM);
+
+  if (socksetnonblock (cryptfd, 0) < 0)
+    {
+      errout (s, "Could not turn on blocking: %m");
+      goto error;
+    }
+
+  /* set it up to work with our FD */
+  gnutls_transport_set_ptr (s->session,
+                           (gnutls_transport_ptr_t) (intptr_t) cryptfd);
+
+
+  /* Now do the handshake */
+  ret = gnutls_handshake (s->session);
+  if (ret < 0)
+    {
+      errout (s, "TLS handshake failed: %s\n", gnutls_strerror (ret));
+      goto error;
+    }
+
+  if (socksetnonblock (cryptfd, 1) < 0)
+    {
+      errout (s, "Could not turn on non-blocking on crypt FD: %m");
+      goto error;
+    }
+
+  if (socksetnonblock (plainfd, 1) < 0)
+    {
+      errout (s, "Could not turn on non-blocking on plain FD: %m");
+      goto error;
+    }
+
+  maxfd = (plainfd > cryptfd) ? plainfd + 1 : cryptfd + 1;
+
+  while ((!plainEOF || !cryptEOF) && !quit (s))
+    {
+      struct timeval timeout;
+      int result;
+      int selecterrno;
+      int wait = TRUE;
+
+      FD_ZERO (&readfds);
+      FD_ZERO (&writefds);
+
+      size_t buffered = gnutls_record_check_pending (s->session);
+      if (buffered)
+       wait = FALSE;           /* do not wait for select to return if we have buffered data */
+
+      if (plainEOF)
+       {
+         /* plain text end has closed, but me may still have
+          * data yet to write to the crypt end */
+         if (bufIsEmpty (plainToCrypt) && !tls_wr_interrupted)
+           {
+             cryptEOF = TRUE;
+             break;
+           }
+       }
+      else
+       {
+         if (!bufIsEmpty (cryptToPlain))
+           FD_SET (plainfd, &writefds);
+         if (!bufIsOverHWM (plainToCrypt))
+           FD_SET (plainfd, &readfds);
+       }
+
+      if (cryptEOF)
+       {
+         /* crypt end has closed, but me way still have data to
+          * write from the crypt buffer */
+         if (bufIsEmpty (cryptToPlain) && !buffered)
+           {
+             plainEOF = TRUE;
+             break;
+           }
+       }
+      else
+       {
+         if (!bufIsEmpty (plainToCrypt) || tls_wr_interrupted)
+           FD_SET (cryptfd, &writefds);
+         if (!bufIsOverHWM (cryptToPlain))
+           FD_SET (cryptfd, &readfds);
+       }
+
+      /* Repeat select whilst EINTR happens */
+      do
+       {
+         timeout.tv_sec = wait ? 1 : 0;
+         timeout.tv_usec = 0;
+         result = select (maxfd, &readfds, &writefds, NULL, &timeout);
+
+         selecterrno = errno;
+       }
+      while ((result == -1) && (selecterrno == EINTR) && !quit (s));
+      if (quit (s))
+       break;
+
+      if (FD_ISSET (plainfd, &readfds))
+       {
+         /* we can read at least one byte */
+         void *addr = NULL;
+         /* get a span of characters to write to the
+          * buffer. As the empty portion may wrap the end of the
+          * circular buffer this might not be all we could read.
+          */
+         ssize_t len = bufGetWriteSpan (plainToCrypt, &addr);
+         if (len > 0)
+           {
+             do
+               {
+                 ret = read (plainfd, addr, (size_t) len);
+               }
+             while ((ret < 0) && (errno == EINTR) && !quit (s));
+             if (quit (s))
+               break;
+             if (ret < 0)
+               {
+                 errout (s, "Error on read from plain socket: %m\n");
+                 goto error;
+               }
+             if (ret == 0)
+               {
+                 plainEOF = TRUE;
+               }
+             else
+               {
+                 bufDoneWrite (plainToCrypt, ret);     /* mark ret bytes as written to the buffer */
+               }
+           }
+       }
+
+      if (FD_ISSET (plainfd, &writefds))
+       {
+         /* we can write at least one byte */
+         void *addr = NULL;
+         /* get a span of characters to read from the buffer
+          * as the full portion may wrap the end of the circular buffer
+          * this might not be all we have to write.
+          */
+         ssize_t len = bufGetReadSpan (cryptToPlain, &addr);
+         if (len > 0)
+           {
+             do
+               {
+                 ret = write (plainfd, addr, (size_t) len);
+               }
+             while ((ret < 0) && (errno == EINTR) && !quit (s));
+             if (quit (s))
+               break;
+             if (ret < 0)
+               {
+                 errout (s, "Error on write to plain socket: %m\n");
+                 goto error;
+               }
+             bufDoneRead (cryptToPlain, ret);  /* mark ret bytes as read from the buffer */
+           }
+       }
+
+      if (FD_ISSET (cryptfd, &readfds) || buffered)
+       {
+         /* we can read at least one byte */
+         void *addr = NULL;
+         /* get a span of characters to write to the
+          * buffer. As the empty portion may wrap the end of the
+          * circular buffer this might not be all we could read.
+          */
+         ssize_t len = bufGetWriteSpan (cryptToPlain, &addr);
+         if (len > 0)
+           {
+             do
+               {
+                 ret = gnutls_record_recv (s->session, addr, (size_t) len);
+               }
+             while (ret == GNUTLS_E_INTERRUPTED && !quit (s));
+             /* do not loop on GNUTLS_E_AGAIN - this means we'd block so we'd loop for
+              * ever
+              */
+             if (quit (s))
+               break;
+             if (ret < 0 && ret != GNUTLS_E_AGAIN)
+               {
+                 errout (s, "Error on read from crypt socket: %s\n",
+                         gnutls_strerror (ret));
+                 goto error;
+               }
+             if (ret == 0)
+               {
+                 cryptEOF = TRUE;
+               }
+             else
+               {
+                 bufDoneWrite (cryptToPlain, ret);     /* mark ret bytes as written to the buffer */
+               }
+           }
+       }
+
+      if (FD_ISSET (cryptfd, &writefds))
+       {
+         /* we can write at least one byte */
+         void *addr = NULL;
+         /* get a span of characters to read from the buffer
+          * as the full portion may wrap the end of the circular buffer
+          * this might not be all we have to write.
+          */
+         ssize_t len = bufGetReadSpan (plainToCrypt, &addr);
+         if (len > 0)
+           {
+             do
+               {
+                 if (tls_wr_interrupted)
+                   {
+                     ret = gnutls_record_send (s->session, NULL, 0);
+                   }
+                 else
+                   {
+                     ret = gnutls_record_send (s->session, addr, len);
+                   }
+               }
+             while (ret == GNUTLS_E_INTERRUPTED && !quit (s));
+             if (quit (s))
+               break;
+             if (ret == GNUTLS_E_AGAIN)
+               {
+                 /* we need to call this again with NULL parameters
+                  * as it blocked
+                  */
+                 tls_wr_interrupted = TRUE;
+               }
+             else if (ret < 0)
+               {
+                 errout (s, "Error on write to crypto socket: %s\n",
+                         gnutls_strerror (ret));
+                 goto error;
+               }
+             else
+               {
+                 bufDoneRead (plainToCrypt, ret);      /* mark ret bytes as read from the buffer */
+               }
+           }
+       }
+    }
+
+  ret = 0;
+  goto freereturn;
+
+error:
+  ret = -1;
+
+freereturn:
+  gnutls_bye (s->session, GNUTLS_SHUT_RDWR);
+  shutdown (plainfd, SHUT_RDWR);
+  bufFree (plainToCrypt);
+  bufFree (cryptToPlain);
+  return ret;
+}
diff --git a/doc/examples/tlsproxy/crypto-gnutls.h b/doc/examples/tlsproxy/crypto-gnutls.h
new file mode 100644 (file)
index 0000000..2b6c402
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Wrymouth Innovation Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#ifndef __TLSPROXY_CRYPTO_GNUTLS_H
+#define __TLSPROXY_CRYPTO_GNUTLS_H
+
+int tlssession_init (void);
+
+typedef struct tlssession tlssession_t;
+tlssession_t *tlssession_new (int isserver,
+                             char *keyfile, char *certfile, char *cacertfile,
+                             char *hostname, int insecure, int debug,
+                             int (*quitfn) (void *opaque),
+                             int (*erroutfn) (void *opaque,
+                                              const char *format,
+                                              va_list ap), void *opaque);
+void tlssession_close (tlssession_t * s);
+int tlssession_mainloop (int cryptfd, int plainfd, tlssession_t * session);
+
+#endif
diff --git a/doc/examples/tlsproxy/tlsproxy.c b/doc/examples/tlsproxy/tlsproxy.c
new file mode 100644 (file)
index 0000000..537b227
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Wrymouth Innovation Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "crypto-gnutls.h"
+
+static char *connectaddr = NULL;
+static char *listenaddr = NULL;
+static char *keyfile = NULL;
+static char *certfile = NULL;
+static char *cacertfile = NULL;
+static char *hostname = NULL;
+static int debug = 0;
+static int insecure = 0;
+static int nofork = 0;
+static int server = 0;
+
+static const char *defaultport = "12345";
+
+static volatile sig_atomic_t rxsigquit = 0;
+
+static int
+bindtoaddress (char *addrport)
+{
+  struct addrinfo hints;
+  struct addrinfo *result, *rp;
+  int fd, s;
+
+  memset (&hints, 0, sizeof (struct addrinfo));
+  hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
+  hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+  hints.ai_socktype = SOCK_STREAM;     /* Stream socket */
+  hints.ai_protocol = 0;       /* any protocol */
+
+  char *addr = strdupa (addrport);
+  char *colon = strrchr (addr, ':');
+  char *port = defaultport;
+  if (colon)
+    {
+      *colon = 0;
+      port = colon + 1;
+    }
+
+  s = getaddrinfo (addr, port, &hints, &result);
+  if (s != 0)
+    {
+      fprintf (stderr, "Error in address %s: %s\n", addr, gai_strerror (s));
+      return -1;
+    }
+
+  /* attempt to bind to each address */
+
+  for (rp = result; rp != NULL; rp = rp->ai_next)
+    {
+      fd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+
+      if (fd >= 0)
+       {
+         int one = 1;
+         if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one)) <
+             0)
+           {
+             close (fd);
+             continue;
+           }
+         if (bind (fd, rp->ai_addr, rp->ai_addrlen) == 0)
+           break;
+         close (fd);
+       }
+    }
+
+  if (!rp)
+    {
+      fprintf (stderr, "Error binding to %s:%s: %m\n", addr, port);
+      return -1;
+    }
+
+  freeaddrinfo (result);       /* No longer needed */
+
+  if (listen (fd, 5) < 0)
+    {
+      close (fd);
+      return -1;
+    }
+
+  return fd;
+}
+
+static int
+connecttoaddress (char *addrport)
+{
+  struct addrinfo hints;
+  struct addrinfo *result, *rp;
+  int fd, s;
+
+  memset (&hints, 0, sizeof (struct addrinfo));
+  hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
+  hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+  hints.ai_socktype = SOCK_STREAM;     /* Stream socket */
+  hints.ai_protocol = 0;       /* any protocol */
+
+  char *addr = strdupa (addrport);
+  char *colon = strrchr (addr, ':');
+  char *port = defaultport;
+  if (colon)
+    {
+      *colon = 0;
+      port = colon + 1;
+    }
+
+  if (!hostname && !server)
+    hostname = strdup (addr);
+
+  s = getaddrinfo (addr, port, &hints, &result);
+  if (s != 0)
+    {
+      fprintf (stderr, "Error in address %s: %s\n", addr, gai_strerror (s));
+      return -1;
+    }
+
+  /* attempt to connect to each address */
+  for (rp = result; rp != NULL; rp = rp->ai_next)
+    {
+      fd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+      if (fd >= 0)
+       {
+         if (connect (fd, rp->ai_addr, rp->ai_addrlen) == 0)
+           break;
+         close (fd);
+       }
+    }
+
+  if (!rp)
+    {
+      fprintf (stderr, "Error connecting to %s:%s: %m\n", addr, port);
+      return -1;
+    }
+
+  freeaddrinfo (result);       /* No longer needed */
+
+  return fd;
+}
+
+static int
+quitfn (void *opaque)
+{
+  return rxsigquit;
+}
+
+static int
+runproxy (int acceptfd)
+{
+  int connectfd;
+  if ((connectfd = connecttoaddress (connectaddr)) < 0)
+    {
+      fprintf (stderr, "Could not connect\n");
+      close (acceptfd);
+      return -1;
+    }
+
+  tlssession_t *session =
+    tlssession_new (server, keyfile, certfile, cacertfile, hostname, insecure,
+                   debug, quitfn, NULL, NULL);
+  if (!session)
+    {
+      fprintf (stderr, "Could create TLS session\n");
+      close (connectfd);
+      close (acceptfd);
+      return -1;
+    }
+
+  int ret;
+  if (server)
+    ret = tlssession_mainloop (acceptfd, connectfd, session);
+  else
+    ret = tlssession_mainloop (connectfd, acceptfd, session);
+
+  tlssession_close (session);
+  close (connectfd);
+  close (acceptfd);
+
+  if (ret < 0)
+    {
+      fprintf (stderr, "TLS proxy exited with an error\n");
+      return -1;
+    }
+  return 0;
+}
+
+static int
+runlistener ()
+{
+  int listenfd;
+  if ((listenfd = bindtoaddress (listenaddr)) < 0)
+    {
+      fprintf (stderr, "Could not bind listener\n");
+      return -1;
+    }
+
+  /*
+     if (!nofork)
+     daemon (FALSE, FALSE);
+   */
+
+  int fd;
+  while (!rxsigquit)
+    {
+      do
+       {
+         if ((fd = accept (listenfd, NULL, NULL)) < 0)
+           {
+             if (errno != EINTR)
+               {
+                 fprintf (stderr, "Accept failed\n");
+                 return -1;
+               }
+           }
+       }
+      while (fd < 0 && !rxsigquit);
+      if (rxsigquit)
+       break;
+      if (nofork < 2)
+       {
+         int ret = runproxy (fd);
+         if (ret < 0)
+           return -1;
+       }
+      else
+       {
+         int cpid = fork ();
+         if (cpid == 0)
+           {
+             /* we're the child */
+             runproxy (fd);
+             exit (0);
+           }
+         else
+           close (fd);
+       }
+    }
+  return 0;
+}
+
+static void
+usage ()
+{
+  fprintf (stderr, "tlsproxy\n\n\
+Usage:\n\
+     tlsproxy [OPTIONS]\n\
+\n\
+A TLS client or server proxy\n\
+\n\
+Options:\n\
+     -c, --connect ADDRRESS    Connect to ADDRESS\n\
+     -l, --listen ADDRESS      Listen on ADDRESS\n\
+     -K, --key FILE            Use FILE as private key\n\
+     -C, --cert FILE           Use FILE as public key\n\
+     -A, --cacert FILE         Use FILE as public CA cert file\n\
+     -H, --hostname HOSTNAME   Use HOSTNAME to validate the CN of the peer\n\
+                               rather than hostname extracted from -C option\n\
+     -s, --server              Run the listen port encrypted rather than the\n\
+                               connect port\n\
+     -i, --insecure            Do not validate certificates\n\
+     -n, --nofork              Do not fork off (aids debugging); specify twice\n\
+                               to stop forking on accept as well\n\
+     -d, --debug               Turn on debugging\n\
+     -h, --help                Show this usage message\n\
+\n\
+\n");
+}
+
+static void
+processoptions (int argc, char **argv)
+{
+  while (1)
+    {
+      static const struct option longopts[] = {
+       {"connect", required_argument, 0, 'c'},
+       {"listen", required_argument, 0, 'l'},
+       {"key", required_argument, 0, 'K'},
+       {"cert", required_argument, 0, 'C'},
+       {"cacert", required_argument, 0, 'A'},
+       {"hostname", required_argument, 0, 'H'},
+       {"server", no_argument, 0, 's'},
+       {"insecure", no_argument, 0, 'i'},
+       {"nofork", no_argument, 0, 'n'},
+       {"debug", no_argument, 0, 'd'},
+       {"help", no_argument, 0, 'h'},
+       {0, 0, 0, 0}
+      };
+
+      int optind = 0;
+
+      int c =
+       getopt_long (argc, argv, "c:l:K:C:A:H:sindh", longopts, &optind);
+      if (c == -1)
+       break;
+
+      switch (c)
+       {
+       case 0:         /* set a flag, nothing else to do */
+         break;
+
+       case 'c':
+         connectaddr = strdup (optarg);
+         break;
+
+       case 'l':
+         listenaddr = strdup (optarg);
+         break;
+
+       case 'K':
+         keyfile = strdup (optarg);
+         break;
+
+       case 'C':
+         certfile = strdup (optarg);
+         break;
+
+       case 'A':
+         cacertfile = strdup (optarg);
+         break;
+
+       case 'H':
+         hostname = strdup (optarg);
+         break;
+
+       case 's':
+         server = 1;
+         break;
+
+       case 'i':
+         insecure = 1;
+         break;
+
+       case 'n':
+         nofork++;
+         break;
+
+       case 'd':
+         debug++;
+         break;
+
+       case 'h':
+         usage ();
+         exit (0);
+         break;
+
+       default:
+         usage ();
+         exit (1);
+       }
+    }
+
+  if (optind != argc || !connectaddr || !listenaddr)
+    {
+      usage ();
+      exit (1);
+    }
+
+  if (!certfile && keyfile)
+    certfile = strdup (keyfile);
+}
+
+static void
+handlesignal (int sig)
+{
+  switch (sig)
+    {
+    case SIGINT:
+    case SIGTERM:
+      rxsigquit++;
+      break;
+    default:
+      break;
+    }
+}
+
+static void
+setsignalmasks ()
+{
+  struct sigaction sa;
+  /* Set up the structure to specify the new action. */
+  memset (&sa, 0, sizeof (struct sigaction));
+  sa.sa_handler = handlesignal;
+  sigemptyset (&sa.sa_mask);
+  sa.sa_flags = 0;
+  sigaction (SIGINT, &sa, NULL);
+  sigaction (SIGTERM, &sa, NULL);
+
+  memset (&sa, 0, sizeof (struct sigaction));
+  sa.sa_handler = SIG_IGN;
+  sa.sa_flags = SA_RESTART;
+  sigaction (SIGPIPE, &sa, NULL);
+}
+
+int
+main (int argc, char **argv)
+{
+  processoptions (argc, argv);
+
+  setsignalmasks ();
+
+  if (tlssession_init ())
+    exit (1);
+
+  runlistener ();
+
+  free (connectaddr);
+  free (listenaddr);
+  free (keyfile);
+  free (certfile);
+  free (cacertfile);
+  free (hostname);
+
+  exit (0);
+}