--- /dev/null
+NAME
+
+tlstunnel -- Forward TLS/DTLS connections as TCP/UDP/SCTP, and vice versa.
+
+SYNOPSIS
+
+tlstunnel -c|-s [-u|-t|-x #|-y #] -l id [-r id] [-S path] inaddr inport fwaddr fwport
+
+DESCRIPTION
+
+The tlstunnel command wraps TLS around connections that are otherwise
+plaintext. This cannot used for protocols that do STARTTLS in the
+course of their protocol interactions; it is purely meant to support
+protocols that iniatiate TLS immediately over newly established
+connections; for example, a HTTPS connection wraps TLS around a plain
+HTTP connection.
+
+The tunnel can be built up using either TLS or DTLS, the latter of which
+is the datagram variety of normal, stream-oriented TLS. When relaying
+a TCP connection, TLS is the only available option; when relaying a
+UDP connection, DTLS is the only available option; when relaying an
+SCPT connection, both varieties are possible but DTLS is advised for
+efficiency reasons.
+
+The tunnel runs in either client or server mode. Used as a client, the
+service listens for plain text connections made to address inaddr and
+port inport, wraps them in TLS as a client and forwards the connection
+to address fwaddr and fwport. When used as a server, the same thing
+is done, however the wrapping of TLS is changed to unwrapping of TLS.
+Addresses are written as IPv6 addresses, with backward compatibility to
+IPv4 through the prefixing of :: in front of an IPv4 address. It is
+not required that inaddr and fwaddr are of the same IP version.
+
+The end points authenticate, and are authorized, as configured in the
+system-wide TLS Pool configuration settings. These settings ensure a
+consistent default behaviour for all TLS Pool connections. These
+settings include certificate capabilities and visibilities.
+
+
+OPTIONS
+
+-c | --client
+
+ Run the TLS Tunnel as a client, meaning that it is connected to by
+ a plaintext connection and forwarded as a TLS-wrapped variety.
+
+ Client authentication and authorization are implemented as specified
+ in the TLS Pool configuration. This includes choice of certificates
+ and settings about their visibility.
+
+-s | --server
+
+ Run the TLS Tunnel as a server, meaning that it is connected to by
+ a TLS-wrapped connection and forwarded as a plaintext variety.
+
+ Client authentication and authorization are implemented as specified
+ in the TLS Pool configuration. This includes choice of certificates
+ and settings about their visibility.
+
+-t | --tcp | --tcp-tls
+
+ Listen to, and forward as, TCP connection. The secure wrapping
+ is implemented through stream-oriented TLS.
+
+ This is also the default option, selected when none of --tcp-tls,
+ --udp-dtls, --sctp-tls or --sctp-dtls has been supplied.
+
+-u | --udp | --udp-dtls
+
+ THIS FEATURE IS NOT IMPLEMENTED. The semantics cannot be clearly
+ defined, because UDP has no notion of a connection, while TLS does
+ need it to keep state. Rather than implementing guesses here and
+ load it with configuration options such as timeouts, it is advised
+ to implement DTLS directly in the UDP application.
+
+-x stream | --sctp-dtls stream
+
+ Listen to, and forward as, SCTP. The secure wrapping
+ is implemented through frame-oriented DTLS, as advised for
+ SCTP because that retains the indepent delivery that SCTP offers.
+
+ The stream is the number of the stream withing SCTP over which the
+ DTLS negotiations are made.
+
+-y stream | --sctp-tls stream
+
+ Listen to, and forward as, SCTP. The secure wrapping
+ is implemented through stream-oriented TLS, which is not advised for
+ SCTP but which may nonetheless be used by a protocol implementation.
+
+ The stream is the number of the stream withing SCTP over which the
+ TLS negotiations are made.
+
+-L id | --local-id id
+
+ Set the local identity. This should be of the form of a domain
+ name, or user@domainname. This paramater is required.
+
+-R id | --remote-id id
+
+ Constrain the remote identity. This should be of the form of a domain
+ name, or user@domainname. This parameter is optional.
+
+-S path | --tlspool-socket-path path
+
+ Override the built-in default path for the socket pool. This can be
+ used to address a specific instance of the TLS Pool, for instance one
+ that is run under a personal account, accessing a personally held
+ PKCS #11 token.
+
+
+LIMITATIONS
+
+The TLS Pool can handle various options when connecting to a remote site.
+These options have not yet been implemented as commandline switches, so
+this level of refinement cannot be added when using a TLS Tunnel.
+
+The TLS Pool usually negotiates local and remote identities as part of
+the authentication and authorization procedures. These procedures do not
+interface optimally with the TLS Tunnel, because there is no place to
+leave this information. What has been implemented is the mandatory
+-l option that sets the local identifier to use, and an optional -r option
+tp constrain the remote identity.
+
+Note that the TLS Tunnel does not implement a PIN entry interface; this
+is a function that is separately coordinated with the TLS Pool, so it can
+bring up a dialog in a trusted environment, making it easier to secure than
+any per-command PIN entry dialog.
+
+Where TLS and even DTLS have a notion of connection state, necessary for the
+management of things like session keys, there is no concept of a connection
+for UDP transmission. For this reason, the UDP variant of this tool has not
+been implemented; it would involve close-to-NAT practices that can easily
+lead to confusion.
+
+AUTHOR
+
+Written by Rick van Rein of OpenFortress.
+
+SEE ALSO
+
+tlspool
--- /dev/null
+/* tlspool/tlstunnel.c -- Simple utility, forward TLS as TCP and vice versa */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <tlspool/starttls.h>
+
+
+static starttls_t tlsdata = {
+ .flags = 0,
+ .local = 0,
+};
+
+
+struct copydata {
+ pthread_t thread;
+ int cnx;
+ int fwd;
+};
+
+
+void copycat (void *cd_void) {
+ struct copydata *cd = (struct copydata *) cd_void;
+ struct pollfd inout [2];
+ char buf [1024];
+ ssize_t sz;
+ inout [0].fd = cd->cnx;
+ inout [1].fd = cd->fwd;
+ inout [0].events = inout [1].events = POLLIN;
+ printf ("DEBUG: Starting copycat cycle for insox=%d, fwsox=%d\n", cd->cnx, cd->fwd);
+ while (1) {
+ if (poll (inout, 2, -1) == -1) {
+ printf ("DEBUG: Copycat polling returned an error\n");
+ break;
+ }
+ if ((inout [0].revents | inout [1].revents) & ~POLLIN) {
+ printf ("DEBUG: Copycat polling returned a special condition\n");
+ break;
+ }
+ if (inout [0].revents & POLLIN) {
+ sz = recv (cd->cnx, buf, sizeof (buf), MSG_DONTWAIT);
+ printf ("DEBUG: Copycat received %d local bytes from %d\n", (int) sz, cd->cnx);
+ if (sz == -1) {
+ break; // stream error
+ } else if (sz == 0) {
+ errno = 0;
+ break; // orderly shutdown
+ } else if (send (cd->fwd, buf, sz, MSG_DONTWAIT) != sz) {
+ break; // communication error
+ } else {
+ printf ("Copycat forwarded %d bytes to %d\n", (int) sz, cd->fwd);
+ }
+ }
+ if (inout [1].revents & POLLIN) {
+ sz = recv (cd->fwd, buf, sizeof (buf), MSG_DONTWAIT);
+ printf ("DEBUG: Copycat received %d reply bytes from %d\n", (int) sz, cd->fwd);
+ if (sz == -1) {
+ break; // stream error
+ } else if (sz == 0) {
+ errno = 0;
+ break; // orderly shutdown
+ } else if (send (cd->cnx, buf, sz, MSG_DONTWAIT) != sz) {
+ break; // communication error
+ } else {
+ printf ("Copycat returned %d bytes to %d\n", (int) sz, cd->cnx);
+ }
+ }
+ }
+ printf ("DEBUG: Ending copycat cycle for insox=%d, fwsox=%d\n", cd->cnx, cd->fwd);
+ close (cd->cnx);
+ close (cd->fwd);
+ free (cd); // Contains pthread_t but that's only an ID
+}
+
+
+int str2port (char *portstr) {
+ char *portrest;
+ long int retval = strtol (portstr, &portrest, 0);
+ if (*portrest) {
+ fprintf (stderr, "Not a port number: %s\n", portstr);
+ exit (1);
+ }
+ if ((retval <= 0) || (retval > 65535)) {
+ fprintf (stderr, "Port numbers range from 1 to 65535: %s\n", portstr);
+ exit (1);
+ }
+ return (int) retval;
+}
+
+
+int main (int argc, char *argv []) {
+ int parsing = 1;
+ int carrier = -1;
+ int dtls = 0;
+ int role = -1;
+ int stream;
+ struct sockaddr_in6 insa = { .sin6_family = AF_INET6, 0 };
+ struct sockaddr_in6 fwsa = { .sin6_family = AF_INET6, 0 };
+ char *localid = NULL;
+ char *remotid = NULL;
+ char *cmdsoxpath = NULL;
+ int sox = -1;
+ //
+ // Parse the command line arguments
+ while (parsing) {
+ //TODO// getlongopt
+ int opt = getopt (argc, argv, "csutx:y:L:R:S:");
+ switch (opt) {
+ case 'c':
+ case 's':
+ if (role != -1) {
+ fprintf (stderr, "Specify -c or -s only once\n");
+ exit (1);
+ }
+ role = opt;
+ break;
+ case 'u':
+ fprintf (stderr, "UDP mode wrapping with DTLS is not implemented -- see man page\n");
+ exit (1);
+ case 't':
+ case 'x':
+ case 'y':
+ if (carrier != -1) {
+ fprintf (stderr, "Specify -t or -x or -y only once\n");
+ exit (1);
+ }
+ carrier = opt;
+ if ((carrier == 'x') || (carrier == 'y')) {
+ long int parsed = strtol (optarg, &optarg, 0);
+ if (*optarg) {
+ fprintf (stderr, "Syntax error in stream number to -%d\n", opt);
+ exit (1);
+ }
+ if ((parsed < 0) || (parsed > 65535)) {
+ fprintf (stderr, "Stream numbers range from 0 to 65535\n");
+ exit (1);
+ }
+ stream = parsed;
+ }
+ break;
+ case 'L':
+ if (*tlsdata.localid) {
+ fprintf (stderr, "For now, it is not permitted to specify multiple local identities\n");
+ exit (1);
+ }
+ if (1 + strlen (optarg) > sizeof (tlsdata.localid)) {
+ fprintf (stderr, "Local identity is too long\n");
+ exit (1);
+ }
+ strcpy (tlsdata.localid, optarg);
+ break;
+ case 'R':
+ if (*tlsdata.remoteid) {
+ fprintf (stderr, "Cannot constrain to multiple remote identities at the same time\n");
+ exit (1);
+ }
+ if (1 + strlen (optarg) > sizeof (tlsdata.remoteid)) {
+ fprintf (stderr, "Remote identity is too long\n");
+ exit (1);
+ }
+ strcpy (tlsdata.remoteid, optarg);
+ break;
+ case 'S':
+ if (cmdsoxpath) {
+ fprintf (stderr, "You can specify only one TLS Pool command socket path\n");
+ exit (1);
+ }
+ cmdsoxpath = strdup (optarg);
+ break;
+ case -1:
+ parsing = 0;
+ }
+ }
+ if (role == -1) {
+ fprintf (stderr, "Specify either -c or -s\n");
+ exit (1);
+ }
+ if (!*tlsdata.localid) {
+ fprintf (stderr, "You need to specify -L\n");
+ exit (1);
+ }
+ if (argc != optind + 4) {
+ fprintf (stderr, "After options, specify: inaddr inport fwaddr fwport\n");
+ exit (1);
+ }
+ if (carrier == -1) {
+ carrier = 't';
+ } else if (carrier == 'x') {
+ dtls = 1;
+ } else if (carrier == 'y') {
+ carrier = 'x'; // With dtls==0
+ }
+ if (cmdsoxpath) {
+ if (tlspool_socket (cmdsoxpath) == -1) {
+ perror ("Failed to open TLS Pool command socket");
+ exit (1);
+ }
+ }
+ tlsdata.flags = (dtls? PIOF_STARTTLS_DTLS: 0);
+ //
+ // Parse addresses and ports in the remaining arguments
+ if (inet_pton (AF_INET6, argv [optind + 0], &insa.sin6_addr) == 0) {
+ fprintf (stderr, "Not an incoming IPv6 address: %s\n", argv [optind + 0]);
+ exit (1);
+ }
+ if (inet_pton (AF_INET6, argv [optind + 2], &fwsa.sin6_addr) == 0) {
+ fprintf (stderr, "Not a forwarding IPv6 address: %s\n", argv [optind + 2]);
+ exit (1);
+ }
+ insa.sin6_port = htons (str2port (argv [optind + 1]));
+ fwsa.sin6_port = htons (str2port (argv [optind + 3]));
+ //
+ // Listen to the incoming address
+ switch (carrier) {
+ case 't':
+ tlsdata.ipproto = IPPROTO_TCP;
+ sox = socket (AF_INET6, SOCK_STREAM, 0);
+ break;
+ case 'u':
+ tlsdata.ipproto = IPPROTO_UDP;
+ sox = socket (AF_INET6, SOCK_DGRAM, 0);
+ break;
+ case 'x':
+ tlsdata.ipproto = IPPROTO_SCTP;
+ tlsdata.streamid = stream;
+ sox = socket (AF_INET6, SOCK_SEQPACKET, 0);
+ break;
+ }
+ if (sox == -1) {
+ perror ("Failed to open socket");
+ exit (1);
+ }
+ if (bind (sox, (struct sockaddr *) &insa, sizeof (insa)) == -1) {
+ perror ("Failed to bind socket to inaddr:inport");
+ exit (1);
+ }
+ if (listen (sox, 5) == -1) {
+ perror ("Failed to listen to socket");
+ exit (1);
+ }
+ //
+ // Fork off a daemon process
+ switch (fork ()) {
+ case -1:
+ perror ("Failed to fork background daemon");
+ exit (1);
+ case 0:
+ if (setsid () == (pid_t) -1) {
+ perror ("Failed to create background session");
+ exit (1);
+ }
+ break;
+ default:
+ printf ("DEBUG: Forked daemon process to the background\n");
+ exit (0);
+ }
+ //
+ // Handle incoming connections
+ while (1) {
+ int cnx;
+ int fwd;
+ struct copydata *sub;
+ cnx = accept (sox, NULL, 0);
+ if (cnx == -1) {
+ perror ("Failed to accept incoming connection");
+ continue;
+ }
+ if (role == 's') {
+ cnx = starttls_server (cnx, &tlsdata, NULL);
+ if (cnx == -1) {
+ perror ("Failed to setup TLS server");
+ continue;
+ }
+ }
+ switch (carrier) {
+ case 't':
+ fwd = socket (AF_INET6, SOCK_STREAM, 0);
+ break;
+ case 'u':
+ fwd = socket (AF_INET6, SOCK_DGRAM, 0);
+ break;
+ case 'x':
+ fwd = socket (AF_INET6, SOCK_SEQPACKET, 0);
+ break;
+ }
+ if (fwd == -1) {
+ perror ("Failed to create forwarding socket");
+ exit (1);
+ }
+ if (connect (fwd, (struct sockaddr *) &fwsa, sizeof (fwsa)) == -1) {
+ perror ("Failed to forward connection");
+ close (fwd);
+ close (cnx);
+ continue;
+ }
+ if (role == 'c') {
+ fwd = starttls_client (fwd, &tlsdata);
+ if (fwd == -1) {
+ perror ("Failed to setup TLS client");
+ close (cnx);
+ continue;
+ }
+ }
+ if (!(sub = (struct copydata *) malloc (sizeof (struct copydata)))) {
+ perror ("Failed to allocate thread data");
+ exit (1);
+ }
+ sub->cnx = cnx;
+ sub->fwd = fwd;
+ errno = pthread_create (&sub->thread, NULL, copycat, (void *) sub);
+ if (errno) {
+ perror ("Failed to start copycat thread");
+ close (cnx);
+ close (fwd);
+ free (sub);
+ }
+ }
+ exit (1);
+}
+