From 21552fc176db7b1dfd3c430f62b8ec1670fdab42 Mon Sep 17 00:00:00 2001 From: gapato Date: Wed, 4 Feb 2015 12:53:32 +0100 Subject: [PATCH 01/36] Fix typo for CentOS install instructions in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7000160..66e3192 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Installation * For CentOS: - cp scripts/etc.rc.d.init.d.sslh /etc/rc.d/init.d/sslh + cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh You might need to create links in /etc/rc.d so that the server From bdeccfd9fff5317f86d1023099b9d73880c8684b Mon Sep 17 00:00:00 2001 From: Justin Matlock Date: Fri, 6 Mar 2015 02:58:52 -0500 Subject: [PATCH 02/36] add longer check for xmpp preamble original wasn't catching the preamble from Adium or Pidgin XMPP clients, because of a newline after the initial line. Grew the length of the check string so it'd see the word 'jabber' faster. --- probe.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/probe.c b/probe.c index cf7711f..8cff937 100644 --- a/probe.c +++ b/probe.c @@ -169,7 +169,10 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto) * */ static int is_xmpp_protocol( const char *p, int len, struct proto *proto) { - if (len < 6) + /* sometimes the word 'jabber' shows up late in the initial string, + sometimes after a newline. this makes sure we snarf the entire preamble + and detect it. (fixed for adium/pidgin) */ + if (len < 50) return PROBE_AGAIN; return memmem(p, len, "jabber", 6) ? 1 : 0; From 3550cbe77c3429d32f849c2a7074896719055a0c Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Mon, 9 Mar 2015 21:51:39 +0100 Subject: [PATCH 03/36] Finalised v1.17 --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 422445b..d9d6f6c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -vNEXT: +v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. Transparant proxy support for FreeBSD. From 48164c4d7780fed222c1f94e2ede506d21b5984c Mon Sep 17 00:00:00 2001 From: muxator Date: Sun, 15 Mar 2015 00:44:39 +0100 Subject: [PATCH 04/36] typo: "transparent proying" -> "transparent proxying" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66e3192..dabb757 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ Transparent proxy support ------------------------- On Linux and FreeBSD you can use the `--transparent` option to -request transparent proying. This means services behind `sslh` +request transparent proxying. This means services behind `sslh` (Apache, `sshd` and so on) will see the external IP and ports as if the external world connected directly to them. This simplifies IP-based access control (or makes it possible at From 2192b2830337c5420f58a689271de0e5adbca710 Mon Sep 17 00:00:00 2001 From: James Hogarth Date: Fri, 17 Apr 2015 23:40:57 +0100 Subject: [PATCH 05/36] Check line number of error so that this works with libconfig-1.3.2 in CentOS6 --- sslh-main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sslh-main.c b/sslh-main.c index 9cc8a06..607b44f 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -280,7 +280,10 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto * config_init(&config); if (config_read_file(&config, filename) == CONFIG_FALSE) { - if (config_error_type(&config) == CONFIG_ERR_PARSE) { +/* If it's a parse error then there will be a line number for the failure + * an I/O error (such as non-existent file) will have the error line as 0 + */ + if (config_error_line(&config) != 0) { fprintf(stderr, "%s:%d:%s\n", filename, config_error_line(&config), From 3bad96865dd961e19e252c27c06ad94d02b7e105 Mon Sep 17 00:00:00 2001 From: Alex Dunn Date: Thu, 14 May 2015 23:14:40 -0700 Subject: [PATCH 06/36] Makefile: fix `install` for Mac OS --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ef77957..45c2345 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? COV_TEST= # Perform test coverage? -PREFIX=/usr/local +PREFIX?=/usr/local MAN=sslh.8.gz # man page name @@ -68,8 +68,10 @@ release: # generic install: install binary and man page install: sslh $(MAN) - install -pD sslh-fork $(DESTDIR)$(PREFIX)/sbin/sslh - install -pD -m 0644 $(MAN) $(DESTDIR)$(PREFIX)/share/man/man8/$(MAN) + mkdir -p $(DESTDIR)$(PREFIX)/sbin + mkdir -p $(DESTDIR)$(PREFIX)/share/man/man8 + install -p sslh-fork $(DESTDIR)$(PREFIX)/sbin/sslh + install -p -m 0644 $(MAN) $(DESTDIR)$(PREFIX)/share/man/man8/$(MAN) # "extended" install for Debian: install startup script install-debian: install sslh $(MAN) @@ -96,4 +98,3 @@ cscope: test: ./t - From 3469f56012c5311187dbded8d102d1a11ec4cf3c Mon Sep 17 00:00:00 2001 From: Kevin Cernekee Date: Tue, 9 Jun 2015 15:09:44 -0700 Subject: [PATCH 07/36] Add builtin handler for Android Debug Bridge (ADB) protocol This allows Android devices to run multiple services on one port. A common use case involves muxing SSH for SCP / SFTP, and ADB for sideloading packages or running CTS. Signed-off-by: Kevin Cernekee --- probe.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/probe.c b/probe.c index 8cff937..a2b2218 100644 --- a/probe.c +++ b/probe.c @@ -33,6 +33,7 @@ static int is_tinc_protocol(const char *p, int len, struct proto*); static int is_xmpp_protocol(const char *p, int len, struct proto*); static int is_http_protocol(const char *p, int len, struct proto*); static int is_tls_protocol(const char *p, int len, struct proto*); +static int is_adb_protocol(const char *p, int len, struct proto*); static int is_true(const char *p, int len, struct proto* proto) { return 1; } /* Table of protocols that have a built-in probe @@ -46,6 +47,7 @@ static struct proto builtins[] = { { "http", NULL, NULL, is_http_protocol }, { "ssl", NULL, NULL, is_tls_protocol }, { "tls", NULL, NULL, is_tls_protocol }, + { "adb", NULL, NULL, is_adb_protocol }, { "anyprot", NULL, NULL, is_true } }; @@ -224,6 +226,22 @@ static int is_tls_protocol(const char *p, int len, struct proto *proto) return p[0] == 0x16 && p[1] == 0x03 && ( p[2] >= 0 && p[2] <= 0x03); } +static int is_adb_protocol(const char *p, int len, struct proto *proto) +{ + if (len < 30) + return PROBE_AGAIN; + + /* The initial ADB host->device packet has a command type of CNXN, and a + * data payload starting with "host:". Note that current versions of the + * client hardcode "host::" (with empty serialno and banner fields) but + * other clients may populate those fields. + * + * We aren't checking amessage.data_length, under the assumption that + * a packet >= 30 bytes long will have "something" in the payload field. + */ + return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5); +} + static int regex_probe(const char *p, int len, struct proto *proto) { regex_t **probe = proto->data; From eb3d3be3abfa4a3563a5e6018aa1814010f2fbd4 Mon Sep 17 00:00:00 2001 From: Kylie McClain Date: Mon, 22 Jun 2015 00:25:18 -0400 Subject: [PATCH 08/36] Makefile: use more variables for install directories --- Makefile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 45c2345..342ef13 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,9 @@ USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? COV_TEST= # Perform test coverage? -PREFIX?=/usr/local +PREFIX?=/usr +BINDIR?=$(PREFIX)/sbin +MANDIR?=$(PREFIX)/share/man/man8 MAN=sslh.8.gz # man page name @@ -68,10 +70,10 @@ release: # generic install: install binary and man page install: sslh $(MAN) - mkdir -p $(DESTDIR)$(PREFIX)/sbin - mkdir -p $(DESTDIR)$(PREFIX)/share/man/man8 - install -p sslh-fork $(DESTDIR)$(PREFIX)/sbin/sslh - install -p -m 0644 $(MAN) $(DESTDIR)$(PREFIX)/share/man/man8/$(MAN) + mkdir -p $(DESTDIR)/$(BINDIR) + mkdir -p $(DESTDIR)/$(MANDIR) + install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh + install -p -m 0644 $(MAN) $(DESTDIR)/$(MANDIR)/$(MAN) # "extended" install for Debian: install startup script install-debian: install sslh $(MAN) @@ -80,7 +82,7 @@ install-debian: install sslh $(MAN) update-rc.d sslh defaults uninstall: - rm -f $(DESTDIR)$(PREFIX)/sbin/sslh $(DESTDIR)$(PREFIX)/share/man/man8/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh + rm -f $(DESTDIR)$(BINDIR)/sslh $(DESTDIR)$(MANDIR)/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh update-rc.d sslh remove distclean: clean From 3aefaf300478cd6fbc4892d5baaf70521ed323af Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 9 Jul 2015 15:31:42 +0200 Subject: [PATCH 09/36] Added Makefile option to build without libpcre --- ChangeLog | 4 ++++ Makefile | 5 +++++ probe.c | 8 ++++++++ sslh-main.c | 7 +++++++ 4 files changed, 24 insertions(+) diff --git a/ChangeLog b/ChangeLog index d9d6f6c..0ff2914 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +vNEXT: + Added USELIBPCRE to make use of regex engine + optional. + v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. diff --git a/Makefile b/Makefile index 342ef13..001e7f5 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ VERSION=$(shell ./genver.sh -r) USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) +USELIBPCRE=1 # Use libpcre? (necessary to use regex probe) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? COV_TEST= # Perform test coverage? @@ -29,6 +30,10 @@ ifneq ($(strip $(USELIBWRAP)),) CPPFLAGS+=-DLIBWRAP endif +ifneq ($(strip $(USELIBPCRE)),) + CPPFLAGS+=-DLIBPCRE +endif + ifneq ($(strip $(USELIBCONFIG)),) LIBS:=$(LIBS) -lconfig CPPFLAGS+=-DLIBCONFIG diff --git a/probe.c b/probe.c index a2b2218..c5500b5 100644 --- a/probe.c +++ b/probe.c @@ -21,7 +21,9 @@ #define _GNU_SOURCE #include +#ifdef LIBPCRE #include +#endif #include #include "probe.h" @@ -244,6 +246,7 @@ static int is_adb_protocol(const char *p, int len, struct proto *proto) static int regex_probe(const char *p, int len, struct proto *proto) { +#ifdef LIBPCRE regex_t **probe = proto->data; regmatch_t pos = { 0, len }; @@ -251,6 +254,11 @@ static int regex_probe(const char *p, int len, struct proto *proto) /* try them all */; return (*probe != NULL); +#else + /* Should never happen as we check when loading config file */ + fprintf(stderr, "FATAL: regex probe called but not built in\n"); + exit(5); +#endif } /* diff --git a/sslh-main.c b/sslh-main.c index 607b44f..2029856 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -25,7 +25,9 @@ #ifdef LIBCONFIG #include #endif +#ifdef LIBPCRE #include +#endif #include "common.h" #include "probe.h" @@ -174,6 +176,7 @@ static int config_listen(config_t *config, struct addrinfo **listen) #ifdef LIBCONFIG static void setup_regex_probe(struct proto *p, config_setting_t* probes) { +#ifdef LIBPCRE int num_probes, errsize, i, res; char *err; const char * expr; @@ -201,6 +204,10 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes) exit(1); } } +#else + fprintf(stderr, "line %d: regex probe specified but not compiled in\n", config_setting_source_line(probes)); + exit(5); +#endif } #endif From b9885401050ad27d9fa13ffa67d5e43441f495c0 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Sun, 12 Jul 2015 23:10:53 -0400 Subject: [PATCH 10/36] Add SNI hostname based probe --- Makefile | 4 +- example.cfg | 1 + probe.c | 31 +++++++ probe.h | 3 +- sslh-main.c | 39 ++++++++- tls.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tls.h | 33 ++++++++ 7 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 tls.c create mode 100644 tls.h diff --git a/Makefile b/Makefile index 001e7f5..146d29d 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ CC ?= gcc CFLAGS ?=-Wall -g $(CFLAGS_COV) LIBS= -OBJS=common.o sslh-main.o probe.o +OBJS=common.o sslh-main.o probe.o tls.o ifneq ($(strip $(USELIBWRAP)),) LIBS:=$(LIBS) -lwrap @@ -63,7 +63,7 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h #strip sslh-select echosrv: $(OBJS) echosrv.o - $(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o $(LIBS) + $(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS) $(MAN): sslh.pod Makefile pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN) diff --git a/example.cfg b/example.cfg index 20299c9..f9d76fa 100644 --- a/example.cfg +++ b/example.cfg @@ -36,6 +36,7 @@ listen: protocols: ( { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, + { name: "sni"; host: "localhost"; port: "993"; probe: "builtin"; sni_hostnames: [ "imap.example.org", "imap.example.com" ]; }, { name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, { name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; }, { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, diff --git a/probe.c b/probe.c index c5500b5..1a5f194 100644 --- a/probe.c +++ b/probe.c @@ -216,6 +216,33 @@ static int is_http_protocol(const char *p, int len, struct proto *proto) return PROBE_NEXT; } +static int is_sni_protocol(const char *p, int len, struct proto *proto) +{ + int valid_tls; + char *hostname; + + valid_tls = parse_tls_header(p, len, &hostname); + + if(valid_tls < 0) + return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT; + + if (verbose) fprintf(stderr, "sni hostname: %s\n", hostname); + + /* Assume does not match */ + valid_tls = PROBE_NEXT; + + char **sni_hostname = proto->data; + + for (; *sni_hostname; sni_hostname++) + if(!strcmp(hostname, *sni_hostname)) { + valid_tls = PROBE_MATCH; + break; + } + + free(hostname); + return valid_tls; +} + static int is_tls_protocol(const char *p, int len, struct proto *proto) { if (len < 3) @@ -334,6 +361,10 @@ T_PROBE* get_probe(const char* description) { if (!strcmp(description, "regex")) return regex_probe; + /* Special case of "sni" probe for same reason as above*/ + if (!strcmp(description, "sni")) + return is_sni_protocol; + return NULL; } diff --git a/probe.h b/probe.h index d79b795..81ac684 100644 --- a/probe.h +++ b/probe.h @@ -4,6 +4,7 @@ #define __PROBE_H_ #include "common.h" +#include "tls.h" typedef enum { PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */ @@ -23,7 +24,7 @@ struct proto { /* function to probe that protocol; parameters are buffer and length * containing the data to probe, and a pointer to the protocol structure */ T_PROBE* probe; - void* data; /* opaque pointer ; used to pass list of regex to regex probe */ + void* data; /* opaque pointer ; used to pass list of regex to regex probe, or sni hostnames to sni probe */ struct proto *next; /* pointer to next protocol in list, NULL if last */ }; diff --git a/sslh-main.c b/sslh-main.c index 2029856..60fe6ce 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -211,13 +211,45 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes) } #endif +#ifdef LIBCONFIG +static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames) +{ + int num_probes, i, max_server_name_len, server_name_len; + const char * sni_hostname; + char** sni_hostname_list; + + num_probes = config_setting_length(sni_hostnames); + if (!num_probes) { + fprintf(stderr, "%s: no sni_hostnames specified\n", p->description); + exit(1); + } + + max_server_name_len = 0; + for (i = 0; i < num_probes; i++) { + server_name_len = strlen(config_setting_get_string_elem(sni_hostnames, i)); + if(server_name_len > max_server_name_len) + max_server_name_len = server_name_len; + } + + sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len); + p->data = (void*)sni_hostname_list; + + for (i = 0; i < num_probes; i++) { + sni_hostname = config_setting_get_string_elem(sni_hostnames, i); + sni_hostname_list[i] = malloc(max_server_name_len); + strcpy (sni_hostname_list[i], sni_hostname); + if(verbose) fprintf(stderr, "sni_hostnames[%d]: %s\n", i, sni_hostname_list[i]); + } +} +#endif + /* Extract configuration for protocols to connect to. * out: newly-allocated list of protocols */ #ifdef LIBCONFIG static int config_protocols(config_t *config, struct proto **prots) { - config_setting_t *setting, *prot, *probes; + config_setting_t *setting, *prot, *probes, *sni_hostnames; const char *hostname, *port, *name; int i, num_prots; struct proto *p, *prev = NULL; @@ -265,6 +297,11 @@ static int config_protocols(config_t *config, struct proto **prots) } } } + + sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); + if (sni_hostnames && config_setting_is_array(sni_hostnames)) { + setup_sni_hostnames(p, sni_hostnames); + } } } } diff --git a/tls.c b/tls.c new file mode 100644 index 0000000..16c072c --- /dev/null +++ b/tls.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2011 and 2012, Dustin Lundquist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This is a minimal TLS implementation intended only to parse the server name + * extension. This was created based primarily on Wireshark dissection of a + * TLS handshake and RFC4366. + */ +#include +#include /* malloc() */ +#include /* strncpy() */ +#include +#include "tls.h" + +#define TLS_HEADER_LEN 5 +#define TLS_HANDSHAKE_CONTENT_TYPE 0x16 +#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01 + +#ifndef MIN +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif + +static int parse_extensions(const char *, size_t, char **); +static int parse_server_name_extension(const char *, size_t, char **); + +const char tls_alert[] = { + 0x15, /* TLS Alert */ + 0x03, 0x01, /* TLS version */ + 0x00, 0x02, /* Payload length */ + 0x02, 0x28, /* Fatal, handshake failure */ +}; + +/* Parse a TLS packet for the Server Name Indication extension in the client + * hello handshake, returning the first servername found (pointer to static + * array) + * + * Returns: + * >=0 - length of the hostname and updates *hostname + * caller is responsible for freeing *hostname + * -1 - Incomplete request + * -2 - No Host header included in this request + * -3 - Invalid hostname pointer + * -4 - malloc failure + * < -4 - Invalid TLS client hello + */ +int +parse_tls_header(const char *data, size_t data_len, char **hostname) { + char tls_content_type; + char tls_version_major; + char tls_version_minor; + size_t pos = TLS_HEADER_LEN; + size_t len; + + if (hostname == NULL) + return -3; + + /* Check that our TCP payload is at least large enough for a TLS header */ + if (data_len < TLS_HEADER_LEN) + return -1; + + /* SSL 2.0 compatible Client Hello + * + * High bit of first byte (length) and content type is Client Hello + * + * See RFC5246 Appendix E.2 + */ + if (data[0] & 0x80 && data[2] == 1) { + if (verbose) fprintf(stderr, "Received SSL 2.0 Client Hello which can not support SNI.\n"); + return -2; + } + + tls_content_type = data[0]; + if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { + if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n"); + return -5; + } + + tls_version_major = data[1]; + tls_version_minor = data[2]; + if (tls_version_major < 3) { + if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which which can not support SNI.\n", + tls_version_major, tls_version_minor); + + return -2; + } + + /* TLS record length */ + len = ((unsigned char)data[3] << 8) + + (unsigned char)data[4] + TLS_HEADER_LEN; + data_len = MIN(data_len, len); + + /* Check we received entire TLS record length */ + if (data_len < len) + return -1; + + /* + * Handshake + */ + if (pos + 1 > data_len) { + return -5; + } + if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { + if (verbose) fprintf(stderr, "Not a client hello\n"); + + return -5; + } + + /* Skip past fixed length records: + 1 Handshake Type + 3 Length + 2 Version (again) + 32 Random + to Session ID Length + */ + pos += 38; + + /* Session ID */ + if (pos + 1 > data_len) + return -5; + len = (unsigned char)data[pos]; + pos += 1 + len; + + /* Cipher Suites */ + if (pos + 2 > data_len) + return -5; + len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; + pos += 2 + len; + + /* Compression Methods */ + if (pos + 1 > data_len) + return -5; + len = (unsigned char)data[pos]; + pos += 1 + len; + + if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { + if (verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n"); + return -2; + } + + /* Extensions */ + if (pos + 2 > data_len) + return -5; + len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; + pos += 2; + + if (pos + len > data_len) + return -5; + return parse_extensions(data + pos, len, hostname); +} + +int +parse_extensions(const char *data, size_t data_len, char **hostname) { + size_t pos = 0; + size_t len; + + /* Parse each 4 bytes for the extension header */ + while (pos + 4 <= data_len) { + /* Extension Length */ + len = ((unsigned char)data[pos + 2] << 8) + + (unsigned char)data[pos + 3]; + + /* Check if it's a server name extension */ + if (data[pos] == 0x00 && data[pos + 1] == 0x00) { + /* There can be only one extension of each type, so we break + our state and move p to beinnging of the extension here */ + if (pos + 4 + len > data_len) + return -5; + return parse_server_name_extension(data + pos + 4, len, hostname); + } + pos += 4 + len; /* Advance to the next extension header */ + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} + +int +parse_server_name_extension(const char *data, size_t data_len, + char **hostname) { + size_t pos = 2; /* skip server name list length */ + size_t len; + + while (pos + 3 < data_len) { + len = ((unsigned char)data[pos + 1] << 8) + + (unsigned char)data[pos + 2]; + + if (pos + 3 + len > data_len) + return -5; + + switch (data[pos]) { /* name type */ + case 0x00: /* host_name */ + *hostname = malloc(len + 1); + if (*hostname == NULL) { + if (verbose) fprintf(stderr, "malloc() failure\n"); + return -4; + } + + strncpy(*hostname, data + pos + 3, len); + + (*hostname)[len] = '\0'; + + return len; + default: + if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n", + data[pos]); + } + pos += 3 + len; + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} diff --git a/tls.h b/tls.h new file mode 100644 index 0000000..ce7a8c6 --- /dev/null +++ b/tls.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 and 2012, Dustin Lundquist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef TLS_H +#define TLS_H + +#include "common.h" + +int parse_tls_header(const char *data, size_t data_len, char **hostname); + +#endif From fecfb170c84cd6119165581a419a6545286f203a Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Wed, 15 Jul 2015 13:34:53 +0200 Subject: [PATCH 11/36] added reference to Tinc documentation --- probe.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/probe.c b/probe.c index c5500b5..9acae71 100644 --- a/probe.c +++ b/probe.c @@ -157,7 +157,8 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) } /* Is the buffer the beginning of a tinc connections? - * (protocol is undocumented, but starts with "0 " in 1.0.15) + * Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf + * First connection starts with "0 " in 1.0.15) * */ static int is_tinc_protocol( const char *p, int len, struct proto *proto) { From 9475d9689b3eb1c6357c4532151ac5aca18e20b7 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Wed, 15 Jul 2015 15:02:37 +0200 Subject: [PATCH 12/36] Comment for SNI inclusion --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index 0ff2914..413ebb4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,9 @@ vNEXT: Added USELIBPCRE to make use of regex engine optional. + Added support for RFC4366 SNI + (Travis Burtrum) + v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. From 77ef29358d7cfb5962cab175995e9e48399a991d Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Wed, 15 Jul 2015 15:09:39 +0200 Subject: [PATCH 13/36] make code C-compliant --- probe.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/probe.c b/probe.c index 4b76ecf..5626983 100644 --- a/probe.c +++ b/probe.c @@ -1,7 +1,7 @@ /* # probe.c: Code for probing protocols # -# Copyright (C) 2007-2012 Yves Rutschle +# Copyright (C) 2007-2015 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public @@ -221,6 +221,7 @@ static int is_sni_protocol(const char *p, int len, struct proto *proto) { int valid_tls; char *hostname; + char **sni_hostname; valid_tls = parse_tls_header(p, len, &hostname); @@ -232,9 +233,7 @@ static int is_sni_protocol(const char *p, int len, struct proto *proto) /* Assume does not match */ valid_tls = PROBE_NEXT; - char **sni_hostname = proto->data; - - for (; *sni_hostname; sni_hostname++) + for (sni_hostname = proto->data; *sni_hostname; sni_hostname++) if(!strcmp(hostname, *sni_hostname)) { valid_tls = PROBE_MATCH; break; From 5886bd2d43db3ee6a9ae58e3e47c34e07cf80f00 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 16 Jul 2015 17:43:05 +0200 Subject: [PATCH 14/36] Print error message upon non-existent configuration file --- sslh-main.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/sslh-main.c b/sslh-main.c index 2029856..6936b47 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -287,17 +287,14 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto * config_init(&config); if (config_read_file(&config, filename) == CONFIG_FALSE) { -/* If it's a parse error then there will be a line number for the failure - * an I/O error (such as non-existent file) will have the error line as 0 - */ - if (config_error_line(&config) != 0) { - fprintf(stderr, "%s:%d:%s\n", - filename, - config_error_line(&config), - config_error_text(&config)); - exit(1); - } - return 1; + /* If it's a parse error then there will be a line number for the failure + * an I/O error (such as non-existent file) will have the error line as 0 + */ + fprintf(stderr, "%s:%d:%s\n", + filename, + config_error_line(&config), + config_error_text(&config)); + exit(1); } config_lookup_bool(&config, "verbose", &verbose); From 8fdaf6eb08555e01c9993ffbf52f7e4ac40c3d15 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Fri, 17 Jul 2015 15:04:04 +0200 Subject: [PATCH 15/36] changed configuration file to accomodate SNI in a cleaner way --- ChangeLog | 5 +++++ basic.cfg | 12 ++++++------ example.cfg | 45 +++++++++++++++++++++++++++++++-------------- probe.c | 9 ++++++++- sslh-main.c | 42 +++++++++++++++++------------------------- sslh.pod | 6 +----- 6 files changed, 68 insertions(+), 51 deletions(-) diff --git a/ChangeLog b/ChangeLog index 413ebb4..05ccaa5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,11 @@ vNEXT: Added support for RFC4366 SNI (Travis Burtrum) + Changed configuration file format: 'probe' field is + no longer required, 'name' field can now contain + 'sni' or 'regex', with corresponding options (see + example.org) + v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. diff --git a/basic.cfg b/basic.cfg index 526ffbf..64942e5 100644 --- a/basic.cfg +++ b/basic.cfg @@ -19,11 +19,11 @@ listen: protocols: ( - { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, - { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; }, - { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; }, - { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, - { name: "ssl"; host: "localhost"; port: "443"; probe: "builtin"; }, - { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; } + { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; }, + { name: "openvpn"; host: "localhost"; port: "1194"; }, + { name: "xmpp"; host: "localhost"; port: "5222"; }, + { name: "http"; host: "localhost"; port: "80"; }, + { name: "ssl"; host: "localhost"; port: "443"; }, + { name: "anyprot"; host: "localhost"; port: "443"; } ); diff --git a/example.cfg b/example.cfg index f9d76fa..5ca9af6 100644 --- a/example.cfg +++ b/example.cfg @@ -23,31 +23,48 @@ listen: # List of protocols # # Each protocol entry consists of: -# name: name of the protocol +# name: name of the probe. These are listed on the command +# line (ssh -?), plus 'regex', 'sni' and 'timeout'. + # service: (optional) libwrap service name (see hosts_access(5)) -# host: host name to connect that protocol -# port: port number to connect that protocol -# probe: "builtin" or a list of regular expressions -# (can be left out, e.g. to use with on-timeout) +# host, port: where to connect when this probe succeeds +# +# Probe-specific options: +# sni: +# sni_hotnames: list of FQDN for that target +# regex: +# regex_patterns: list of patterns to match for +# that target. # # sslh will try each probe in order they are declared, and # connect to the first that matches. - +# +# You can specify several of 'regex' and 'sni'. + protocols: ( - { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, - { name: "sni"; host: "localhost"; port: "993"; probe: "builtin"; sni_hostnames: [ "imap.example.org", "imap.example.com" ]; }, - { name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, - { name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; }, - { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, - { name: "ssl"; host: "localhost"; port: "443"; probe: [ "" ]; }, + { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; }, + { name: "http"; host: "localhost"; port: "80"; }, + + { name: "sni"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; }, + { name: "sni"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; }, + +# OpenVPN + { name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, +# Jabber + { name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ]; }, + +# Catch-all + { name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; }, + +# Where to connect in case of timeout (defaults to ssh) { name: "timeout"; service: "daytime"; host: "localhost"; port: "daytime"; } ); # Optionally, specify to which protocol to connect in case # of timeout (defaults to "ssh"). -# You can timeout to any arbitrary address by setting a -# protocol with no probe, as is the case with this example. +# You can timeout to any arbitrary address by setting an +# entry in 'protocols' named "timeout". # This enables you to set a tcpd service name for this # protocol too. on-timeout: "timeout"; diff --git a/probe.c b/probe.c index 5626983..27120c4 100644 --- a/probe.c +++ b/probe.c @@ -233,11 +233,13 @@ static int is_sni_protocol(const char *p, int len, struct proto *proto) /* Assume does not match */ valid_tls = PROBE_NEXT; - for (sni_hostname = proto->data; *sni_hostname; sni_hostname++) + for (sni_hostname = proto->data; *sni_hostname; sni_hostname++) { + fprintf(stderr, "matching [%s] with [%s]\n", hostname, *sni_hostname); if(!strcmp(hostname, *sni_hostname)) { valid_tls = PROBE_MATCH; break; } + } free(hostname); return valid_tls; @@ -365,6 +367,11 @@ T_PROBE* get_probe(const char* description) { if (!strcmp(description, "sni")) return is_sni_protocol; + /* Special case of "timeout" is allowed as a probe name in the + * configuration file even though it's not really a probe */ + if (!strcmp(description, "timeout")) + return is_true; + return NULL; } diff --git a/sslh-main.c b/sslh-main.c index 60fe6ce..4cd6def 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -249,7 +249,7 @@ static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames #ifdef LIBCONFIG static int config_protocols(config_t *config, struct proto **prots) { - config_setting_t *setting, *prot, *probes, *sni_hostnames; + config_setting_t *setting, *prot, *patterns, *sni_hostnames; const char *hostname, *port, *name; int i, num_prots; struct proto *p, *prev = NULL; @@ -273,34 +273,26 @@ static int config_protocols(config_t *config, struct proto **prots) resolve_split_name(&(p->saddr), hostname, port); + p->probe = get_probe(name); + if (!p->probe) { + fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name); + exit(1); + } - probes = config_setting_get_member(prot, "probe"); - if (probes) { - if (config_setting_is_array(probes)) { - /* If 'probe' is an array, setup a regex probe using the - * array of strings as pattern */ - - setup_regex_probe(p, probes); - - } else { - /* if 'probe' is 'builtin', set the probe to the - * appropriate builtin protocol */ - if (!strcmp(config_setting_get_string(probes), "builtin")) { - p->probe = get_probe(name); - if (!p->probe) { - fprintf(stderr, "%s: no builtin probe for this protocol\n", name); - exit(1); - } - } else { - fprintf(stderr, "%s: illegal probe name\n", name); - exit(1); - } + /* Probe-specific options: regex patterns */ + if (!strcmp(name, "regex")) { + patterns = config_setting_get_member(prot, "regex_patterns"); + if (patterns && config_setting_is_array(patterns)) { + setup_regex_probe(p, patterns); } } - sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); - if (sni_hostnames && config_setting_is_array(sni_hostnames)) { - setup_sni_hostnames(p, sni_hostnames); + /* Probe-specific options: SNI hostnames */ + if (!strcmp(name, "sni")) { + sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); + if (sni_hostnames && config_setting_is_array(sni_hostnames)) { + setup_sni_hostnames(p, sni_hostnames); + } } } } diff --git a/sslh.pod b/sslh.pod index c0ce606..a90d4bd 100644 --- a/sslh.pod +++ b/sslh.pod @@ -51,14 +51,10 @@ and the list of protocols). The configuration file makes it possible to specify protocols using regular expressions: a list of regular -expressions is given as the I parameter, and if the +expressions is given as the I parameter, and if the first packet received from the client matches any of these expressions, B connects to that protocol. -Alternatively, the I parameter can be set to -"builtin", to use the compiled probes which are much faster -than regular expressions. - =head2 Probing protocols When receiving an incoming connection, B will read the From 4cbaf447b5a634f703bc571bf0e12f3ae1514d8a Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 28 Jul 2015 15:14:21 +0200 Subject: [PATCH 16/36] Print error message upon non-existent configuration file --- sslh-main.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/sslh-main.c b/sslh-main.c index b6abc4f..0025017 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -306,6 +306,7 @@ static int config_protocols(config_t *config, struct proto **prots) * in: *filename * out: *listen, a newly-allocated linked list of listen addrinfo * *prots, a newly-allocated linked list of protocols + * 1 on error, 0 on success */ #ifdef LIBCONFIG static int config_parse(char *filename, struct addrinfo **listen, struct proto **prots) @@ -319,11 +320,17 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto * /* If it's a parse error then there will be a line number for the failure * an I/O error (such as non-existent file) will have the error line as 0 */ - fprintf(stderr, "%s:%d:%s\n", + if (config_error_line(&config) != 0) { + fprintf(stderr, "%s:%d:%s\n", + filename, + config_error_line(&config), + config_error_text(&config)); + exit(1); + } + fprintf(stderr, "%s:%s\n", filename, - config_error_line(&config), config_error_text(&config)); - exit(1); + return 1; } config_lookup_bool(&config, "verbose", &verbose); @@ -399,10 +406,12 @@ static void cmdline_config(int argc, char* argv[], struct proto** prots) optind = 1; opterr = 0; /* we're missing protocol options at this stage so don't output errors */ while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) { + if (c == 'v') { + verbose++; + } if (c == 'F') { config_filename = optarg; if (config_filename) { - fprintf(stderr, "config: %s\n", config_filename); res = config_parse(config_filename, &addr_listen, prots); } else { /* No configuration file specified -- try default file locations */ From ab3324be477b2663196e0cc73d96aa38d59da65a Mon Sep 17 00:00:00 2001 From: John Regan Date: Thu, 13 Aug 2015 14:28:17 -0400 Subject: [PATCH 17/36] Enable PCRE as RegEx Library --- Makefile | 8 +++++++- probe.c | 6 +++++- sslh-main.c | 6 +++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 146d29d..2c71550 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ # Configuration VERSION=$(shell ./genver.sh -r) +ENABLE_REGEX=1 # Enable regex probes USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) -USELIBPCRE=1 # Use libpcre? (necessary to use regex probe) +USELIBPCRE=1 # Use libpcre? (needed for regex on musl) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? COV_TEST= # Perform test coverage? @@ -30,8 +31,13 @@ ifneq ($(strip $(USELIBWRAP)),) CPPFLAGS+=-DLIBWRAP endif +ifneq ($(strip $(ENABLE_REGEX)),) + CPPFLAGS+=-DENABLE_REGEX +endif + ifneq ($(strip $(USELIBPCRE)),) CPPFLAGS+=-DLIBPCRE + LIBS:=$(LIBS) -lpcre endif ifneq ($(strip $(USELIBCONFIG)),) diff --git a/probe.c b/probe.c index 27120c4..3514a48 100644 --- a/probe.c +++ b/probe.c @@ -21,9 +21,13 @@ #define _GNU_SOURCE #include +#ifdef ENABLE_REGEX #ifdef LIBPCRE +#include +#else #include #endif +#endif #include #include "probe.h" @@ -275,7 +279,7 @@ static int is_adb_protocol(const char *p, int len, struct proto *proto) static int regex_probe(const char *p, int len, struct proto *proto) { -#ifdef LIBPCRE +#ifdef ENABLE_REGEX regex_t **probe = proto->data; regmatch_t pos = { 0, len }; diff --git a/sslh-main.c b/sslh-main.c index 0025017..e5043b5 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -25,9 +25,13 @@ #ifdef LIBCONFIG #include #endif +#ifdef ENABLE_REGEX #ifdef LIBPCRE +#include +#else #include #endif +#endif #include "common.h" #include "probe.h" @@ -176,7 +180,7 @@ static int config_listen(config_t *config, struct addrinfo **listen) #ifdef LIBCONFIG static void setup_regex_probe(struct proto *p, config_setting_t* probes) { -#ifdef LIBPCRE +#ifdef ENABLE_REGEX int num_probes, errsize, i, res; char *err; const char * expr; From 717c285b311db5d809356597b2df5270360f1751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eero=20H=C3=A4kkinen?= Date: Sun, 27 Sep 2015 22:14:00 +0300 Subject: [PATCH 18/36] Fix access rights checking to work with IPv6. --- common.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/common.c b/common.c index 100753a..0120047 100644 --- a/common.c +++ b/common.c @@ -471,16 +471,19 @@ void log_connection(struct connection *cnx) int check_access_rights(int in_socket, const char* service) { #ifdef LIBWRAP - struct sockaddr peeraddr; - socklen_t size = sizeof(peeraddr); + union { + struct sockaddr saddr; + struct sockaddr_storage ss; + } peer; + socklen_t size = sizeof(peer); char addr_str[NI_MAXHOST], host[NI_MAXHOST]; int res; - res = getpeername(in_socket, &peeraddr, &size); + res = getpeername(in_socket, &peer.saddr, &size); CHECK_RES_RETURN(res, "getpeername"); /* extract peer address */ - res = getnameinfo(&peeraddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST); + res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST); if (res) { if (verbose) fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res)); @@ -489,7 +492,7 @@ int check_access_rights(int in_socket, const char* service) /* extract peer name */ strcpy(host, STRING_UNKNOWN); if (!numeric) { - res = getnameinfo(&peeraddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD); + res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD); if (res) { if (verbose) fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res)); From b108809a78dbedce34bf8dee060fd4a2e72480c8 Mon Sep 17 00:00:00 2001 From: ViKing Date: Wed, 7 Oct 2015 00:09:33 +0800 Subject: [PATCH 19/36] Fix the connection problem in transparent mode. When the source and destination are the same, the bind_peer() will fail, thus end the connection. Therefore a check of all the interface IPs are checked to skip bind() if they are the same. --- common.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/common.c b/common.c index 0120047..6d2732c 100644 --- a/common.c +++ b/common.c @@ -8,6 +8,10 @@ #include #include +#include +#include +#include + #include "common.h" #include "probe.h" @@ -120,6 +124,39 @@ int bind_peer(int fd, int fd_from) * got here */ res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen); CHECK_RES_RETURN(res, "getpeername"); + + // if the destination is the same machine, there's no need to do bind + struct ifaddrs *ifaddrs_p = NULL, *ifa; + + getifaddrs(&ifaddrs_p); + + for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next) + { + if (!ifa->ifa_addr) + continue; + int match = 0; + if (from.ai_addr->sa_family == ifa->ifa_addr->sa_family) + { + int family = ifa->ifa_addr->sa_family; + if (family == AF_INET) + { + struct sockaddr_in *from_addr = from.ai_addr; + struct sockaddr_in *ifa_addr = ifa->ifa_addr; + if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) + match = 1; + } + else if (family == AF_INET6) + { + struct sockaddr_in6 *from_addr = from.ai_addr; + struct sockaddr_in6 *ifa_addr = ifa->ifa_addr; + if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) + match = 1; + } + } + if (match) // the destination is the same as the source, should not create a transparent bind + return 0; + } + #ifndef IP_BINDANY /* use IP_TRANSPARENT */ res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans)); CHECK_RES_DIE(res, "setsockopt"); From b09c3aab3163c38a0684c12024ae2c6c5d185410 Mon Sep 17 00:00:00 2001 From: Nikhil Choudhary Date: Mon, 19 Oct 2015 13:40:51 -0500 Subject: [PATCH 20/36] Fix daemon start syntax Per the changelog for 1.17: argument to -F can no longer be separated from the option by a space, e.g. must be -Ffoo.cfg). This fixes the `/etc/sslh/sslh.cfg:file I/O error` message if the config file is stored in `/etc/sslh.cfg` as given by Readme.md. --- scripts/etc.init.d.sslh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/etc.init.d.sslh b/scripts/etc.init.d.sslh index 54aa0d1..790f066 100755 --- a/scripts/etc.init.d.sslh +++ b/scripts/etc.init.d.sslh @@ -27,7 +27,7 @@ DAEMON=$PREFIX/sbin/sslh start() { echo "Start services: sslh" - $DAEMON -F /etc/sslh.cfg + $DAEMON -F/etc/sslh.cfg logger -t ${tag} -p ${facility} -i 'Started sslh' } From 2cb424c6464be24045c9f41b66811c3992970189 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 15 Dec 2015 15:51:18 +0100 Subject: [PATCH 21/36] Added log_level option to configuration file, which switches off log at each connection --- ChangeLog | 3 +++ basic.cfg | 2 +- common.c | 3 +++ example.cfg | 4 ++-- probe.c | 20 ++++++++++---------- probe.h | 3 +++ sslh-main.c | 7 ++++++- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 05ccaa5..3262f78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,9 @@ vNEXT: no longer required, 'name' field can now contain 'sni' or 'regex', with corresponding options (see example.org) + Added 'log_level' option to each protocol, which + allows to turn off generation of log at each + connection. v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. diff --git a/basic.cfg b/basic.cfg index 64942e5..2f8252c 100644 --- a/basic.cfg +++ b/basic.cfg @@ -23,7 +23,7 @@ protocols: { name: "openvpn"; host: "localhost"; port: "1194"; }, { name: "xmpp"; host: "localhost"; port: "5222"; }, { name: "http"; host: "localhost"; port: "80"; }, - { name: "ssl"; host: "localhost"; port: "443"; }, + { name: "ssl"; host: "localhost"; port: "443"; log_level: 0; }, { name: "anyprot"; host: "localhost"; port: "443"; } ); diff --git a/common.c b/common.c index 0120047..218bd91 100644 --- a/common.c +++ b/common.c @@ -431,6 +431,9 @@ void log_connection(struct connection *cnx) local[MAX_NAMELENGTH], target[MAX_NAMELENGTH]; int res; + if (cnx->proto->log_level < 1) + return; + addr.ai_addr = (struct sockaddr*)&ss; addr.ai_addrlen = sizeof(ss); diff --git a/example.cfg b/example.cfg index 5ca9af6..91f0f36 100644 --- a/example.cfg +++ b/example.cfg @@ -46,8 +46,8 @@ protocols: { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; }, { name: "http"; host: "localhost"; port: "80"; }, - { name: "sni"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; }, - { name: "sni"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; }, + { name: "sni"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; }, + { name: "sni"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;}, # OpenVPN { name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, diff --git a/probe.c b/probe.c index 27120c4..73be132 100644 --- a/probe.c +++ b/probe.c @@ -41,16 +41,16 @@ static int is_true(const char *p, int len, struct proto* proto) { return 1; } /* Table of protocols that have a built-in probe */ static struct proto builtins[] = { - /* description service saddr probe */ - { "ssh", "sshd", NULL, is_ssh_protocol}, - { "openvpn", NULL, NULL, is_openvpn_protocol }, - { "tinc", NULL, NULL, is_tinc_protocol }, - { "xmpp", NULL, NULL, is_xmpp_protocol }, - { "http", NULL, NULL, is_http_protocol }, - { "ssl", NULL, NULL, is_tls_protocol }, - { "tls", NULL, NULL, is_tls_protocol }, - { "adb", NULL, NULL, is_adb_protocol }, - { "anyprot", NULL, NULL, is_true } + /* description service saddr log_level probe */ + { "ssh", "sshd", NULL, 1, is_ssh_protocol}, + { "openvpn", NULL, NULL, 1, is_openvpn_protocol }, + { "tinc", NULL, NULL, 1, is_tinc_protocol }, + { "xmpp", NULL, NULL, 1, is_xmpp_protocol }, + { "http", NULL, NULL, 1, is_http_protocol }, + { "ssl", NULL, NULL, 1, is_tls_protocol }, + { "tls", NULL, NULL, 1, is_tls_protocol }, + { "adb", NULL, NULL, 1, is_adb_protocol }, + { "anyprot", NULL, NULL, 1, is_true } }; static struct proto *protocols; diff --git a/probe.h b/probe.h index 81ac684..0a663fb 100644 --- a/probe.h +++ b/probe.h @@ -20,6 +20,9 @@ struct proto { const char* description; /* a string that says what it is (for logging and command-line parsing) */ const char* service; /* service name to do libwrap checks */ struct addrinfo *saddr; /* list of addresses to try and switch that protocol */ + int log_level; /* 0: No logging of connection + * 1: Log incoming connection + */ /* function to probe that protocol; parameters are buffer and length * containing the data to probe, and a pointer to the protocol structure */ diff --git a/sslh-main.c b/sslh-main.c index 0025017..e35bfd9 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -119,10 +119,11 @@ static void printsettings(void) for (p = get_first_protocol(); p; p = p->next) { fprintf(stderr, - "%s addr: %s. libwrap service: %s family %d %d\n", + "%s addr: %s. libwrap service: %s log_level: %d family %d %d\n", p->description, sprintaddr(buf, sizeof(buf), p->saddr), p->service, + p->log_level, p->saddr->ai_family, p->saddr->ai_addr->sa_family); } @@ -271,6 +272,10 @@ static int config_protocols(config_t *config, struct proto **prots) p->description = name; config_setting_lookup_string(prot, "service", &(p->service)); + if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) { + p->log_level = 1; + } + resolve_split_name(&(p->saddr), hostname, port); p->probe = get_probe(name); From 8758a298ba730832838ef982fa8a2e07d3ca6f69 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 15 Dec 2015 16:06:14 +0100 Subject: [PATCH 22/36] Changed connection log to include the name of the probe that triggered. --- ChangeLog | 3 +++ common.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 3262f78..f107380 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,9 @@ vNEXT: Added support for RFC4366 SNI (Travis Burtrum) + Changed connection log to include the name of the probe that + triggered. + Changed configuration file format: 'probe' field is no longer required, 'name' field can now contain 'sni' or 'regex', with corresponding options (see diff --git a/common.c b/common.c index 218bd91..bff10be 100644 --- a/common.c +++ b/common.c @@ -457,7 +457,8 @@ void log_connection(struct connection *cnx) if (res == -1) return; sprintaddr(local, sizeof(local), &addr); - log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n", + log_message(LOG_INFO, "%s:connection from %s to %s forwarded from %s to %s\n", + cnx->proto->description, peer, service, local, From 8af039d3eb3e890482fd6755532d7786a104cae8 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Tue, 5 Jan 2016 00:32:10 -0500 Subject: [PATCH 23/36] Add ALPN protocol based probe --- ChangeLog | 6 +- example.cfg | 28 ++++++-- probe.c | 29 ++------ probe.h | 3 +- sslh-main.c | 48 ++++++++----- tls.c | 199 +++++++++++++++++++++++++++++++++++++--------------- tls.h | 7 +- 7 files changed, 216 insertions(+), 104 deletions(-) diff --git a/ChangeLog b/ChangeLog index f107380..c3f8d58 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,7 @@ vNEXT: Added USELIBPCRE to make use of regex engine optional. - Added support for RFC4366 SNI + Added support for RFC4366 SNI and RFC7301 ALPN (Travis Burtrum) Changed connection log to include the name of the probe that @@ -10,8 +10,8 @@ vNEXT: Changed configuration file format: 'probe' field is no longer required, 'name' field can now contain - 'sni' or 'regex', with corresponding options (see - example.org) + 'tls' or 'regex', with corresponding options (see + example.cfg) Added 'log_level' option to each protocol, which allows to turn off generation of log at each connection. diff --git a/example.cfg b/example.cfg index 91f0f36..06a439c 100644 --- a/example.cfg +++ b/example.cfg @@ -24,14 +24,19 @@ listen: # # Each protocol entry consists of: # name: name of the probe. These are listed on the command -# line (ssh -?), plus 'regex', 'sni' and 'timeout'. +# line (ssh -?), plus 'regex' and 'timeout'. # service: (optional) libwrap service name (see hosts_access(5)) # host, port: where to connect when this probe succeeds # # Probe-specific options: -# sni: -# sni_hotnames: list of FQDN for that target +# tls: +# sni_hostnames: list of FQDN for that target +# alpn_protocols: list of ALPN protocols for that target, see: +# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids +# +# if both sni_hostnames AND alpn_protocols are specified, both must match +# if neither are set, it is just checked whether this is the TLS protocol or not # regex: # regex_patterns: list of patterns to match for # that target. @@ -39,15 +44,26 @@ listen: # sslh will try each probe in order they are declared, and # connect to the first that matches. # -# You can specify several of 'regex' and 'sni'. +# You can specify several of 'regex' and 'tls'. protocols: ( { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; }, { name: "http"; host: "localhost"; port: "80"; }, - { name: "sni"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; }, - { name: "sni"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;}, +# match BOTH ALPN/SNI + { name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0;}, + +# just match ALPN + { name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; }, + { name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0;}, + +# just match SNI + { name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; }, + { name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;}, + +# catch anything else TLS + { name: "tls"; host: "localhost"; port: "443"; }, # OpenVPN { name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, diff --git a/probe.c b/probe.c index 73be132..a2d0d55 100644 --- a/probe.c +++ b/probe.c @@ -217,32 +217,17 @@ static int is_http_protocol(const char *p, int len, struct proto *proto) return PROBE_NEXT; } -static int is_sni_protocol(const char *p, int len, struct proto *proto) +static int is_sni_alpn_protocol(const char *p, int len, struct proto *proto) { int valid_tls; - char *hostname; - char **sni_hostname; - valid_tls = parse_tls_header(p, len, &hostname); + valid_tls = parse_tls_header(proto->data, p, len); if(valid_tls < 0) return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT; - if (verbose) fprintf(stderr, "sni hostname: %s\n", hostname); - - /* Assume does not match */ - valid_tls = PROBE_NEXT; - - for (sni_hostname = proto->data; *sni_hostname; sni_hostname++) { - fprintf(stderr, "matching [%s] with [%s]\n", hostname, *sni_hostname); - if(!strcmp(hostname, *sni_hostname)) { - valid_tls = PROBE_MATCH; - break; - } - } - - free(hostname); - return valid_tls; + /* There *was* a valid match */ + return PROBE_MATCH; } static int is_tls_protocol(const char *p, int len, struct proto *proto) @@ -363,9 +348,9 @@ T_PROBE* get_probe(const char* description) { if (!strcmp(description, "regex")) return regex_probe; - /* Special case of "sni" probe for same reason as above*/ - if (!strcmp(description, "sni")) - return is_sni_protocol; + /* Special case of "sni/alpn" probe for same reason as above*/ + if (!strcmp(description, "sni_alpn")) + return is_sni_alpn_protocol; /* Special case of "timeout" is allowed as a probe name in the * configuration file even though it's not really a probe */ diff --git a/probe.h b/probe.h index 0a663fb..bc69acf 100644 --- a/probe.h +++ b/probe.h @@ -27,7 +27,8 @@ struct proto { /* function to probe that protocol; parameters are buffer and length * containing the data to probe, and a pointer to the protocol structure */ T_PROBE* probe; - void* data; /* opaque pointer ; used to pass list of regex to regex probe, or sni hostnames to sni probe */ + /* opaque pointer ; used to pass list of regex to regex probe, or TLSProtocol struct to sni/alpn probe */ + void* data; struct proto *next; /* pointer to next protocol in list, NULL if last */ }; diff --git a/sslh-main.c b/sslh-main.c index e35bfd9..246814d 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -213,34 +213,47 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes) #endif #ifdef LIBCONFIG -static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames) +static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char* name, int alpn) { int num_probes, i, max_server_name_len, server_name_len; - const char * sni_hostname; + const char * config_item; char** sni_hostname_list; - num_probes = config_setting_length(sni_hostnames); + if(!config_items || !config_setting_is_array(config_items)) { + fprintf(stderr, "%s: no %s specified\n", p->description, name); + return; + } + num_probes = config_setting_length(config_items); if (!num_probes) { - fprintf(stderr, "%s: no sni_hostnames specified\n", p->description); - exit(1); + fprintf(stderr, "%s: no %s specified\n", p->description, name); + return; } max_server_name_len = 0; for (i = 0; i < num_probes; i++) { - server_name_len = strlen(config_setting_get_string_elem(sni_hostnames, i)); + server_name_len = strlen(config_setting_get_string_elem(config_items, i)); if(server_name_len > max_server_name_len) max_server_name_len = server_name_len; } sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len); - p->data = (void*)sni_hostname_list; for (i = 0; i < num_probes; i++) { - sni_hostname = config_setting_get_string_elem(sni_hostnames, i); + config_item = config_setting_get_string_elem(config_items, i); sni_hostname_list[i] = malloc(max_server_name_len); - strcpy (sni_hostname_list[i], sni_hostname); - if(verbose) fprintf(stderr, "sni_hostnames[%d]: %s\n", i, sni_hostname_list[i]); + strcpy (sni_hostname_list[i], config_item); + if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]); } + + p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list); +} + +static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, config_setting_t* alpn_protocols) +{ + p->data = (void*)new_tls_data(); + p->probe = get_probe("sni_alpn"); + setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0); + setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1); } #endif @@ -250,7 +263,7 @@ static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames #ifdef LIBCONFIG static int config_protocols(config_t *config, struct proto **prots) { - config_setting_t *setting, *prot, *patterns, *sni_hostnames; + config_setting_t *setting, *prot, *patterns, *sni_hostnames, *alpn_protocols; const char *hostname, *port, *name; int i, num_prots; struct proto *p, *prev = NULL; @@ -279,7 +292,7 @@ static int config_protocols(config_t *config, struct proto **prots) resolve_split_name(&(p->saddr), hostname, port); p->probe = get_probe(name); - if (!p->probe) { + if (!p->probe || !strcmp(name, "sni_alpn")) { fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name); exit(1); } @@ -292,13 +305,16 @@ static int config_protocols(config_t *config, struct proto **prots) } } - /* Probe-specific options: SNI hostnames */ - if (!strcmp(name, "sni")) { + /* Probe-specific options: SNI/ALPN */ + if (!strcmp(name, "tls")) { sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); - if (sni_hostnames && config_setting_is_array(sni_hostnames)) { - setup_sni_hostnames(p, sni_hostnames); + alpn_protocols = config_setting_get_member(prot, "alpn_protocols"); + + if((sni_hostnames && config_setting_is_array(sni_hostnames)) || (alpn_protocols && config_setting_is_array(alpn_protocols))) { + setup_sni_alpn(p, sni_hostnames, alpn_protocols); } } + } } } diff --git a/tls.c b/tls.c index 16c072c..8640dec 100644 --- a/tls.c +++ b/tls.c @@ -30,8 +30,6 @@ */ #include #include /* malloc() */ -#include /* strncpy() */ -#include #include "tls.h" #define TLS_HEADER_LEN 5 @@ -42,19 +40,20 @@ #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #endif -static int parse_extensions(const char *, size_t, char **); -static int parse_server_name_extension(const char *, size_t, char **); -const char tls_alert[] = { - 0x15, /* TLS Alert */ - 0x03, 0x01, /* TLS version */ - 0x00, 0x02, /* Payload length */ - 0x02, 0x28, /* Fatal, handshake failure */ +struct TLSProtocol { + int use_alpn; + char** sni_hostname_list; + char** alpn_protocol_list; }; -/* Parse a TLS packet for the Server Name Indication extension in the client - * hello handshake, returning the first servername found (pointer to static - * array) +static int parse_extensions(const struct TLSProtocol *, const char *, size_t); +static int parse_server_name_extension(const struct TLSProtocol *, const char *, size_t); +static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t); +static int has_match(char**, const char*, size_t); + +/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client + * hello handshake, returning a status code * * Returns: * >=0 - length of the hostname and updates *hostname @@ -62,35 +61,20 @@ const char tls_alert[] = { * -1 - Incomplete request * -2 - No Host header included in this request * -3 - Invalid hostname pointer - * -4 - malloc failure * < -4 - Invalid TLS client hello */ int -parse_tls_header(const char *data, size_t data_len, char **hostname) { +parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { char tls_content_type; char tls_version_major; char tls_version_minor; size_t pos = TLS_HEADER_LEN; size_t len; - if (hostname == NULL) - return -3; - /* Check that our TCP payload is at least large enough for a TLS header */ if (data_len < TLS_HEADER_LEN) return -1; - /* SSL 2.0 compatible Client Hello - * - * High bit of first byte (length) and content type is Client Hello - * - * See RFC5246 Appendix E.2 - */ - if (data[0] & 0x80 && data[2] == 1) { - if (verbose) fprintf(stderr, "Received SSL 2.0 Client Hello which can not support SNI.\n"); - return -2; - } - tls_content_type = data[0]; if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n"); @@ -100,7 +84,7 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) { tls_version_major = data[1]; tls_version_minor = data[2]; if (tls_version_major < 3) { - if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which which can not support SNI.\n", + if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n", tls_version_major, tls_version_minor); return -2; @@ -108,7 +92,7 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) { /* TLS record length */ len = ((unsigned char)data[3] << 8) + - (unsigned char)data[4] + TLS_HEADER_LEN; + (unsigned char)data[4] + TLS_HEADER_LEN; data_len = MIN(data_len, len); /* Check we received entire TLS record length */ @@ -167,30 +151,75 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) { if (pos + len > data_len) return -5; - return parse_extensions(data + pos, len, hostname); + return parse_extensions(tls_data, data + pos, len); } -int -parse_extensions(const char *data, size_t data_len, char **hostname) { +static int +parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { size_t pos = 0; size_t len; + int last_matched = 0; + + if (tls_data == NULL) + return -3; /* Parse each 4 bytes for the extension header */ while (pos + 4 <= data_len) { /* Extension Length */ - len = ((unsigned char)data[pos + 2] << 8) + - (unsigned char)data[pos + 3]; + len = ((unsigned char) data[pos + 2] << 8) + + (unsigned char) data[pos + 3]; + + if (pos + 4 + len > data_len) + return -5; + + size_t extension_type = ((unsigned char) data[pos] << 8) + + (unsigned char) data[pos + 1]; + /* Check if it's a server name extension */ - if (data[pos] == 0x00 && data[pos + 1] == 0x00) { - /* There can be only one extension of each type, so we break - our state and move p to beinnging of the extension here */ - if (pos + 4 + len > data_len) - return -5; - return parse_server_name_extension(data + pos + 4, len, hostname); + /* There can be only one extension of each type, so we break + our state and move pos to beginning of the extension here */ + if (tls_data->use_alpn == 2) { + /* we want BOTH alpn and sni to match */ + if (extension_type == 0x00) { /* Server Name */ + if (parse_server_name_extension(tls_data, data + pos + 4, len)) { + /* SNI matched */ + if(last_matched) { + /* this is only true if ALPN matched, so return true */ + return last_matched; + } else { + /* otherwise store that SNI matched */ + last_matched = 1; + } + } else { + // both can't match + return -2; + } + } else if (extension_type == 0x10) { /* ALPN */ + if (parse_alpn_extension(tls_data, data + pos + 4, len)) { + /* ALPN matched */ + if(last_matched) { + /* this is only true if SNI matched, so return true */ + return last_matched; + } else { + /* otherwise store that ALPN matched */ + last_matched = 1; + } + } else { + // both can't match + return -2; + } + } + + } else if (extension_type == 0x00 && tls_data->use_alpn == 0) { /* Server Name */ + return parse_server_name_extension(tls_data, data + pos + 4, len); + } else if (extension_type == 0x10 && tls_data->use_alpn == 1) { /* ALPN */ + return parse_alpn_extension(tls_data, data + pos + 4, len); } + pos += 4 + len; /* Advance to the next extension header */ } + /* Check we ended where we expected to */ if (pos != data_len) return -5; @@ -198,32 +227,25 @@ parse_extensions(const char *data, size_t data_len, char **hostname) { return -2; } -int -parse_server_name_extension(const char *data, size_t data_len, - char **hostname) { +static int +parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { size_t pos = 2; /* skip server name list length */ size_t len; while (pos + 3 < data_len) { len = ((unsigned char)data[pos + 1] << 8) + - (unsigned char)data[pos + 2]; + (unsigned char)data[pos + 2]; if (pos + 3 + len > data_len) return -5; switch (data[pos]) { /* name type */ case 0x00: /* host_name */ - *hostname = malloc(len + 1); - if (*hostname == NULL) { - if (verbose) fprintf(stderr, "malloc() failure\n"); - return -4; + if(has_match(tls_data->sni_hostname_list, data + pos + 3, len)) { + return len; + } else { + return -2; } - - strncpy(*hostname, data + pos + 3, len); - - (*hostname)[len] = '\0'; - - return len; default: if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n", data[pos]); @@ -236,3 +258,70 @@ parse_server_name_extension(const char *data, size_t data_len, return -2; } + +static int +parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { + size_t pos = 2; + size_t len; + + while (pos + 1 < data_len) { + len = (unsigned char)data[pos]; + + if (pos + 1 + len > data_len) + return -5; + + if (len > 0 && has_match(tls_data->alpn_protocol_list, data + pos + 1, len)) { + return len; + } else if (len > 0) { + if (verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1); + } + pos += 1 + len; + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} + +static int +has_match(char** list, const char* name, size_t name_len) { + char **item; + + for (item = list; *item; item++) { + if (verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item); + if(!strncmp(*item, name, name_len)) { + return 1; + } + } + return 0; +} + +struct TLSProtocol * +new_tls_data() { + struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol)); + if (tls_data != NULL) { + tls_data->use_alpn = -1; + } + + return tls_data; +} + +struct TLSProtocol * +tls_data_set_list(struct TLSProtocol *tls_data, int alpn, char** list) { + if (alpn) { + tls_data->alpn_protocol_list = list; + if(tls_data->use_alpn == 0) + tls_data->use_alpn = 2; + else + tls_data->use_alpn = 1; + } else { + tls_data->sni_hostname_list = list; + if(tls_data->use_alpn == 1) + tls_data->use_alpn = 2; + else + tls_data->use_alpn = 0; + } + + return tls_data; +} diff --git a/tls.h b/tls.h index ce7a8c6..0e79e0c 100644 --- a/tls.h +++ b/tls.h @@ -28,6 +28,11 @@ #include "common.h" -int parse_tls_header(const char *data, size_t data_len, char **hostname); +struct TLSProtocol; + +int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len); + +struct TLSProtocol *new_tls_data(); +struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, char**); #endif From 0b6cc0d9091ffe0a8709a47fbf88cac71e54e976 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Fri, 22 Jan 2016 11:25:53 +0100 Subject: [PATCH 24/36] Added a note on configuring transparent proxying for both IPv4 and IPv6 --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index dabb757..a6c50de 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,24 @@ This will not work: sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443 +Transparent proxying means the target server sees the real +origin address, so it means if the client connects using +IPv6, the server must also support IPv6. It is easy to +support both IPv4 and IPv6 by configuring the server +accordingly, and setting `sslh` to connect to a name that +resolves to both IPv4 and IPv6, e.g.: + + sslh --transparent --listen :443 --ssh insideaddr:22 + + /etc/hosts: + 192.168.0.1 insideaddr + 201::::2 insideaddr + +Upon incoming IPv6 connection, `sslh` will first try to +connect to the IPv4 address (which will fail), then connect +to the IPv6 address. + + Fail2ban -------- From e511534b57b73ae52d3253541385a42c1a1d591b Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Fri, 22 Jan 2016 17:09:02 +0100 Subject: [PATCH 25/36] Make USELIBPCRE not the default --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2c71550..46bc9c6 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ VERSION=$(shell ./genver.sh -r) ENABLE_REGEX=1 # Enable regex probes USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) -USELIBPCRE=1 # Use libpcre? (needed for regex on musl) +USELIBPCRE= # Use libpcre? (needed for regex on musl) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? COV_TEST= # Perform test coverage? From 555005f455df706c6754c9407654c66ca1b4feb3 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 26 Jan 2016 18:49:57 +0100 Subject: [PATCH 26/36] Complies to DCL37-C (Issue 59) --- common.h | 4 ++-- genver.sh | 4 ++-- probe.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common.h b/common.h index db0ccc5..701b337 100644 --- a/common.h +++ b/common.h @@ -1,5 +1,5 @@ -#ifndef __COMMON_H_ -#define __COMMON_H_ +#ifndef COMMON_H +#define COMMON_H /* FD_SETSIZE is 64 on Cygwin, which is really low. Just redefining it is * enough for the macros to adapt (http://support.microsoft.com/kb/111855) diff --git a/genver.sh b/genver.sh index 4d6e76a..79fd0a0 100755 --- a/genver.sh +++ b/genver.sh @@ -40,8 +40,8 @@ fi if [ $QUIET -ne 1 ]; then - printf "#ifndef _VERSION_H_ \n" - printf "#define _VERSION_H_ \n\n" + printf "#ifndef VERSION_H \n" + printf "#define VERSION_H \n\n" printf "#define VERSION \"$release\"\n" printf "#endif\n" else diff --git a/probe.h b/probe.h index bc69acf..4d9b473 100644 --- a/probe.h +++ b/probe.h @@ -1,7 +1,7 @@ /* API for probe.c */ -#ifndef __PROBE_H_ -#define __PROBE_H_ +#ifndef PROBE_H +#define PROBE_H #include "common.h" #include "tls.h" From b3f48d98767634007dbe29ee3cb609a2e4283b7d Mon Sep 17 00:00:00 2001 From: James Hogarth Date: Fri, 29 Jan 2016 16:20:54 +0000 Subject: [PATCH 27/36] Add systemd capabilities to sslh, rebased on current master --- Makefile | 12 ++- README.md | 86 ++++++++++++++++++++++ common.c | 76 +++++++++++++------ sslh-main.c | 10 +++ systemd-sslh-generator.c | 153 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 315 insertions(+), 22 deletions(-) create mode 100644 systemd-sslh-generator.c diff --git a/Makefile b/Makefile index 46bc9c6..b1cf9ce 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) USELIBPCRE= # Use libpcre? (needed for regex on musl) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? +USESYSTEMD= # Make use of systemd socket activation COV_TEST= # Perform test coverage? PREFIX?=/usr BINDIR?=$(PREFIX)/sbin @@ -50,6 +51,12 @@ ifneq ($(strip $(USELIBCAP)),) CPPFLAGS+=-DLIBCAP endif +ifneq ($(strip $(USESYSTEMD)),) + LIBS:=$(LIBS) -lsystemd + CPPFLAGS+=-DSYSTEMD +endif + + all: sslh $(MAN) echosrv .c.o: *.h @@ -68,6 +75,9 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h $(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS) #strip sslh-select +systemd-sslh-generator: systemd-sslh-generator.o + $(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig + echosrv: $(OBJS) echosrv.o $(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS) @@ -100,7 +110,7 @@ distclean: clean rm -f tags cscope.* clean: - rm -f sslh-fork sslh-select echosrv version.h $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info + rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info tags: ctags --globals -T *.[ch] diff --git a/README.md b/README.md index a6c50de..c657e85 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ of the Makefile: file. You will need `libconfig` headers to compile (`libconfig8-dev` in Debian). +* `USESYSTEMD` compiles support for using systemd socket activation. + You will need `systemd` headers to compile (`systemd-devel` in Fedora). Binaries -------- @@ -303,6 +305,90 @@ explicit IP addresses (or names): This will not work: sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443 + +Transparent proxying means the target server sees the real +origin address, so it means if the client connects using +IPv6, the server must also support IPv6. It is easy to +support both IPv4 and IPv6 by configuring the server +accordingly, and setting `sslh` to connect to a name that +resolves to both IPv4 and IPv6, e.g.: + + sslh --transparent --listen :443 --ssh insideaddr:22 + + /etc/hosts: + 192.168.0.1 insideaddr + 201::::2 insideaddr + +Upon incoming IPv6 connection, `sslh` will first try to +connect to the IPv4 address (which will fail), then connect +to the IPv6 address. + +Systemd Socket Activation +------------------------- +If compiled with `USESYSTEMD` then it is possible to activate +the service on demand and avoid running any code as root. + +In this mode any listen configuration options are ignored and +the sockets are passed by systemd to the service. + +Example socket unit: + + [Unit] + Before=sslh.service + + [Socket] + ListenStream=1.2.3.4:443 + ListenStream=5.6.7.8:444 + ListenStream=9.10.11.12:445 + FreeBind=true + + [Install] + WantedBy=sockets.target + +Example service unit: + + [Unit] + PartOf=sslh.socket + + [Service] + ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443 + KillMode=process + CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID + PrivateTmp=true + PrivateDevices=true + ProtectSystem=full + ProtectHome=true + User=sslh + + +With this setup only the socket needs to be enabled. The sslh service +will be started on demand and does not need to run as root to bind the +sockets as systemd has already bound and passed them over. If the sslh +service is started on its own without the sockets being passed by systemd +then it will look to use those defined on the command line or config +file as usual. Any number of ListenStreams can be defined in the socket +file and systemd will pass them all over to sslh to use as usual. + +To avoid inconsistency between starting via socket and starting directly +via the service Requires=sslh.socket can be added to the service unit to +mandate the use of the socket configuration. + +Rather than overwriting the entire socket file drop in values can be placed +in /etc/systemd/system/sslh.socket.d/.conf with additional ListenStream +values that will be merged. + +In addition to the above with manual .socket file configuration there is an +optional systemd generator which can be compiled - systemd-sslh-generator + +This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists +instead) configuration file and dynamically generates a socket file to use. + +This will also merge with any sslh.socket.d drop in configuration but will be +overriden by a /etc/systemd/system/sslh.socket file. + +To use the generator place it in /usr/lib/systemd/system-generators and then +call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate +the new dynamic socket unit. Transparent proxying means the target server sees the real origin address, so it means if the client connects using diff --git a/common.c b/common.c index bff10be..5266718 100644 --- a/common.c +++ b/common.c @@ -11,13 +11,19 @@ #include "common.h" #include "probe.h" -/* Added to make the code compilable under CYGWIN +/* Added to make the code compilable under CYGWIN * */ #ifndef SA_NOCLDWAIT #define SA_NOCLDWAIT 0 #endif -/* +/* Make use of systemd socket activation + * */ +#ifdef SYSTEMD +#include +#endif + +/* * Settings that depend on the command line. They're set in main(), but also * used in other places in common.c, and it'd be heavy-handed to pass it all as * parameters @@ -44,14 +50,35 @@ void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall) char buf[NI_MAXHOST]; if (res == -1) { - fprintf(stderr, "%s:%s: %s\n", - sprintaddr(buf, sizeof(buf), addr), - syscall, + fprintf(stderr, "%s:%s: %s\n", + sprintaddr(buf, sizeof(buf), addr), + syscall, strerror(errno)); exit(1); } } +int get_fd_sockets(int *sockfd[]) +{ + int sd = 0; + +#ifdef SYSTEMD + sd = sd_listen_fds(0); + if (sd < 0) { + fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd)); + exit(1); + } + if (sd > 0) { + *sockfd = malloc(sd * sizeof(*sockfd[0])); + for (int i = 0; i < sd; i++) { + (*sockfd)[i] = SD_LISTEN_FDS_START + i; + } + } +#endif + + return sd; +} + /* Starts listening sockets on specified addresses. * IN: addr[], num_addr * OUT: *sockfd[] pointer to newly-allocated array of file descriptors @@ -64,6 +91,13 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list) struct addrinfo *addr; int i, res, one; int num_addr = 0; + int sd_socks = 0; + + sd_socks = get_fd_sockets(sockfd); + + if (sd_socks > 0) { + return sd_socks; + } for (addr = addr_list; addr; addr = addr->ai_next) num_addr++; @@ -141,7 +175,7 @@ int bind_peer(int fd, int fd_from) } /* Connect to first address that works and returns a file descriptor, or -1 if - * none work. + * none work. * If transparent proxying is on, use fd_from peer address on external address * of new file descriptor. */ int connect_addr(struct connection *cnx, int fd_from) @@ -162,8 +196,8 @@ int connect_addr(struct connection *cnx, int fd_from) /* When transparent, make sure both connections use the same address family */ if (transparent && a->ai_family != from.ai_addr->sa_family) continue; - if (verbose) - fprintf(stderr, "connecting to %s family %d len %d\n", + if (verbose) + fprintf(stderr, "connecting to %s family %d len %d\n", sprintaddr(buf, sizeof(buf), a), a->ai_addr->sa_family, a->ai_addrlen); @@ -179,7 +213,7 @@ int connect_addr(struct connection *cnx, int fd_from) } res = connect(fd, a->ai_addr, a->ai_addrlen); if (res == -1) { - log_message(LOG_ERR, "forward to %s failed:connect: %s\n", + log_message(LOG_ERR, "forward to %s failed:connect: %s\n", cnx->proto->description, strerror(errno)); close(fd); } else { @@ -191,10 +225,10 @@ int connect_addr(struct connection *cnx, int fd_from) } /* Store some data to write to the queue later */ -int defer_write(struct queue *q, void* data, int data_size) +int defer_write(struct queue *q, void* data, int data_size) { char *p; - if (verbose) + if (verbose) fprintf(stderr, "**** writing deferred on fd %d\n", q->fd); p = realloc(q->begin_deferred_data, q->deferred_data_size + data_size); @@ -258,7 +292,7 @@ void dump_connection(struct connection *cnx) } -/* +/* * moves data from one fd to other * * returns number of bytes copied if success @@ -326,16 +360,16 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a) int res; res = getnameinfo(a->ai_addr, a->ai_addrlen, - host, sizeof(host), - serv, sizeof(serv), + host, sizeof(host), + serv, sizeof(serv), numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 ); if (res) { log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res)); /* Name resolution failed: do it numerically instead */ res = getnameinfo(a->ai_addr, a->ai_addrlen, - host, sizeof(host), - serv, sizeof(serv), + host, sizeof(host), + serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); /* should not fail but... */ if (res) { @@ -350,7 +384,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a) return buf; } -/* Turns a hostname and port (or service) into a list of struct addrinfo +/* Turns a hostname and port (or service) into a list of struct addrinfo * returns 0 on success, -1 otherwise and logs error **/ int resolve_split_name(struct addrinfo **out, const char* host, const char* serv) @@ -438,7 +472,7 @@ void log_connection(struct connection *cnx) addr.ai_addrlen = sizeof(ss); res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen); - if (res == -1) return; /* Can happen if connection drops before we get here. + if (res == -1) return; /* Can happen if connection drops before we get here. In that case, don't log anything (there is no connection) */ sprintaddr(peer, sizeof(peer), &addr); @@ -541,7 +575,7 @@ void setup_signals(void) } -/* Open syslog connection with appropriate banner; +/* Open syslog connection with appropriate banner; * banner is made up of basename(bin_name)+"[pid]" */ void setup_syslog(const char* bin_name) { char *name1, *name2; @@ -551,7 +585,7 @@ void setup_syslog(const char* bin_name) { res = asprintf(&name2, "%s[%d]", basename(name1), getpid()); CHECK_RES_DIE(res, "asprintf"); openlog(name2, LOG_CONS, LOG_AUTH); - free(name1); + free(name1); /* Don't free name2, as openlog(3) uses it (at least in glibc) */ log_message(LOG_INFO, "%s %s started\n", server_type, VERSION); @@ -623,7 +657,7 @@ void drop_privileges(const char* user_name) /* remove extraneous groups in case we belong to several extra groups that * may have unwanted rights. If non-root when calling setgroups(), it - * fails, which is fine because... we have no unwanted rights + * fails, which is fine because... we have no unwanted rights * (see POS36-C for security context) * */ setgroups(0, NULL); diff --git a/sslh-main.c b/sslh-main.c index 5b47fe4..6ab8a66 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -551,10 +551,13 @@ next_arg: set_protocol_list(prots); +/* If compiling with systemd socket support no need to require listen address */ +#ifndef SYSTEMD if (!addr_listen && !inetd) { fprintf(stderr, "No listening address specified; use at least one -p option\n"); exit(1); } +#endif /* Did command-line override foreground setting? */ if (background) @@ -591,6 +594,13 @@ int main(int argc, char *argv[]) num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen); +#ifdef SYSTEMD + if (num_addr_listen < 1) { + fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n"); + exit(1); + } +#endif + if (!foreground) { if (fork() > 0) exit(0); /* Detach */ diff --git a/systemd-sslh-generator.c b/systemd-sslh-generator.c new file mode 100644 index 0000000..5909a5b --- /dev/null +++ b/systemd-sslh-generator.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include + + +static char* resolve_listen(const char *hostname, const char *port) { + +/* Need room in the strcat for \0 and : + * the format in the socket unit file is hostname:port */ + char *conn = (char*)malloc(strlen(hostname)+strlen(port)+2); + strcpy(conn, hostname); + strcat(conn, ":"); + strcat(conn, port); + + return conn; + +} + + +static int get_listen_from_conf(const char *filename, char **listen) { + + config_t config; + config_setting_t *setting, *addr; + const char *hostname, *port; + int len = 0; + +/* look up the listen stanzas in the config file so these + * can be used in the socket file generated */ + + config_init(&config); + if (config_read_file(&config, filename) == CONFIG_FALSE) { + /* we don't care if file is missing, skip it */ + if (config_error_line(&config) != 0) { + fprintf(stderr, "%s:%d:%s\n", + filename, + config_error_line(&config), + config_error_text(&config)); + return -1; + } + } else { + setting = config_lookup(&config, "listen"); + if (setting) { + len = config_setting_length(setting); + for (int i = 0; i < len; i++) { + addr = config_setting_get_elem(setting, i); + if (! (config_setting_lookup_string(addr, "host", &hostname) && + config_setting_lookup_string(addr, "port", &port))) { + fprintf(stderr, + "line %d:Incomplete specification (hostname and port required)\n", + config_setting_source_line(addr)); + return -1; + } else { + + listen[i] = malloc(strlen(resolve_listen(hostname, port))); + strcpy(listen[i], resolve_listen(hostname, port)); + } + } + } + } + + return len; + +} + +static int write_socket_unit(FILE *socket, char **listen, int num_addr, const char *source) { + + fprintf(socket, + "# Automatically generated by systemd-sslh-generator\n\n" + "[Unit]\n" + "Before=sslh.service\n" + "SourcePath=%s\n" + "Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n" + "[Socket]\n" + "FreeBind=true\n", + source); + + for (int i = 0; i < num_addr; i++) { + fprintf(socket, "ListenStream=%s\n", listen[i]); + } + +return 0; +} + +static int gen_sslh_config(char *runtime_unit_dir) { + + char *sslh_conf; + int num_addr; + FILE *config; + char **listen; + FILE *runtime_conf_fd = stdout; + const char *unit_file; + +/* There are two default locations so check both with first given preference */ + sslh_conf = "/etc/sslh.cfg"; + + config = fopen(sslh_conf, "r"); + if (config == NULL) { + sslh_conf="/etc/sslh/sslh.cfg"; + config = fopen(sslh_conf, "r"); + if (config == NULL) { + return -1; + } + } + + fclose(config); + + + num_addr = get_listen_from_conf(sslh_conf, listen); + if (num_addr < 0) + return -1; + +/* If this is run by systemd directly write to the location told to + * otherwise write to standard out so that it's trivial to check what + * will be written */ + if (runtime_unit_dir != "") { + unit_file = "/sslh.socket"; + size_t uf_len = strlen(unit_file); + size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1; + char *runtime_conf = malloc(runtime_len); + strcpy(runtime_conf, runtime_unit_dir); + strcat(runtime_conf, unit_file); + runtime_conf_fd = fopen(runtime_conf, "w"); + } + + + return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf); + +} + +int main(int argc, char *argv[]){ + + int r = 0; + int k; + char *runtime_unit_dest = ""; + + if (argc > 1 && (argc != 4) ) { + printf("This program takes three or no arguments.\n"); + return -1; + } + + if (argc > 1) + runtime_unit_dest = argv[1]; + + k = gen_sslh_config(runtime_unit_dest); + if (k < 0) + r = k; + + return r < 0 ? -1 : 0; + +} + + From 1814bcb43cdf455b847c17f1313eb35d73f94716 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 2 Feb 2016 20:53:10 +0100 Subject: [PATCH 28/36] Fixed typo in example configuration files --- basic.cfg | 2 +- example.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/basic.cfg b/basic.cfg index 2f8252c..54a799c 100644 --- a/basic.cfg +++ b/basic.cfg @@ -6,7 +6,7 @@ foreground: false; inetd: false; numeric: false; transparent: false; -timeout: "2"; +timeout: 2; user: "nobody"; pidfile: "/var/run/sslh.pid"; diff --git a/example.cfg b/example.cfg index 06a439c..d28372f 100644 --- a/example.cfg +++ b/example.cfg @@ -8,7 +8,7 @@ foreground: true; inetd: false; numeric: false; transparent: false; -timeout: "2"; +timeout: 2; user: "nobody"; pidfile: "/var/run/sslh.pid"; From 1b9937b293a932bf394da6623075b35b5e7bd9d2 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 2 Feb 2016 21:07:47 +0100 Subject: [PATCH 29/36] Support keepalive for connections on the listening side --- common.c | 6 ++++++ example.cfg | 9 +++++++-- sslh-main.c | 21 ++++++++++++++++----- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/common.c b/common.c index bff10be..0814246 100644 --- a/common.c +++ b/common.c @@ -87,6 +87,12 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list) res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); check_res_dumpdie(res, addr, "setsockopt(SO_REUSEADDR)"); + if (addr->ai_flags & SO_KEEPALIVE) { + res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one)); + check_res_dumpdie(res, addr, "setsockopt(SO_KEEPALIVE)"); + printf("set up keepalive\n"); + } + if (IP_FREEBIND) { res = setsockopt((*sockfd)[i], IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one)); check_res_dumpdie(res, addr, "setsockopt(IP_FREEBIND)"); diff --git a/example.cfg b/example.cfg index d28372f..371bcf0 100644 --- a/example.cfg +++ b/example.cfg @@ -14,10 +14,11 @@ pidfile: "/var/run/sslh.pid"; # List of interfaces on which we should listen +# Options: listen: ( { host: "thelonious"; port: "443"; }, - { host: "thelonious"; port: "8080"; } + { host: "thelonious"; port: "8080"; keepalive: true; } ); # List of protocols @@ -28,6 +29,10 @@ listen: # service: (optional) libwrap service name (see hosts_access(5)) # host, port: where to connect when this probe succeeds +# log_level: 0 to turn off logging +# 1 to log each incoming connection +# keepalive: Should TCP keepalive be on or off for that +# connection (default is off) # # Probe-specific options: # tls: @@ -48,7 +53,7 @@ listen: protocols: ( - { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; }, + { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; keepalive: true; }, { name: "http"; host: "localhost"; port: "80"; }, # match BOTH ALPN/SNI diff --git a/sslh-main.c b/sslh-main.c index 5b47fe4..220a867 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -2,7 +2,7 @@ # main: processing of config file, command line options and start the main # loop. # -# Copyright (C) 2007-2014 Yves Rutschle +# Copyright (C) 2007-2016 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public @@ -133,7 +133,10 @@ static void printsettings(void) } fprintf(stderr, "listening on:\n"); for (a = addr_listen; a; a = a->ai_next) { - fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a)); + fprintf(stderr, + "\t%s\t[keepalive: %d]\n", + sprintaddr(buf, sizeof(buf), a), + a->ai_flags & SO_KEEPALIVE ? 1 : 0); } fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout, timeout_protocol()->description); @@ -147,7 +150,7 @@ static void printsettings(void) static int config_listen(config_t *config, struct addrinfo **listen) { config_setting_t *setting, *addr; - int len, i; + int len, i, keepalive; const char *hostname, *port; setting = config_lookup(config, "listen"); @@ -163,12 +166,20 @@ static int config_listen(config_t *config, struct addrinfo **listen) return -1; } + keepalive = 0; + config_setting_lookup_bool(addr, "keepalive", &keepalive); + resolve_split_name(listen, hostname, port); /* getaddrinfo returned a list of addresses corresponding to the * specification; move the pointer to the end of that list before - * processing the next specification */ - for (; *listen; listen = &((*listen)->ai_next)); + * processing the next specification, while setting flags for + * start_listen_sockets() through ai_flags (which is not meant for + * that, but is only used as hint in getaddrinfo, so it's OK) */ + for (; *listen; listen = &((*listen)->ai_next)) { + if (keepalive) + (*listen)->ai_flags = SO_KEEPALIVE; + } } } From 414ed7de1108da241c640db0319e0b18a3d5b4c8 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Thu, 4 Feb 2016 09:19:54 +0100 Subject: [PATCH 30/36] Support keepalive for connections on the connecting side --- ChangeLog | 1 + common.c | 8 +++++++- probe.c | 20 ++++++++++---------- probe.h | 1 + sslh-main.c | 10 ++++++---- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index c3f8d58..a834320 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,7 @@ vNEXT: Added 'log_level' option to each protocol, which allows to turn off generation of log at each connection. + Added 'keepalive' option. v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. diff --git a/common.c b/common.c index 0814246..abf485c 100644 --- a/common.c +++ b/common.c @@ -155,7 +155,7 @@ int connect_addr(struct connection *cnx, int fd_from) struct addrinfo *a, from; struct sockaddr_storage ss; char buf[NI_MAXHOST]; - int fd, res; + int fd, res, one; memset(&from, 0, sizeof(from)); from.ai_addr = (struct sockaddr*)&ss; @@ -189,6 +189,12 @@ int connect_addr(struct connection *cnx, int fd_from) cnx->proto->description, strerror(errno)); close(fd); } else { + if (cnx->proto->keepalive) { + one = 1; + res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one)); + CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)"); + printf("set up keepalive\n"); + } return fd; } } diff --git a/probe.c b/probe.c index 9d025ac..9b4a63e 100644 --- a/probe.c +++ b/probe.c @@ -45,16 +45,16 @@ static int is_true(const char *p, int len, struct proto* proto) { return 1; } /* Table of protocols that have a built-in probe */ static struct proto builtins[] = { - /* description service saddr log_level probe */ - { "ssh", "sshd", NULL, 1, is_ssh_protocol}, - { "openvpn", NULL, NULL, 1, is_openvpn_protocol }, - { "tinc", NULL, NULL, 1, is_tinc_protocol }, - { "xmpp", NULL, NULL, 1, is_xmpp_protocol }, - { "http", NULL, NULL, 1, is_http_protocol }, - { "ssl", NULL, NULL, 1, is_tls_protocol }, - { "tls", NULL, NULL, 1, is_tls_protocol }, - { "adb", NULL, NULL, 1, is_adb_protocol }, - { "anyprot", NULL, NULL, 1, is_true } + /* description service saddr log_level keepalive probe */ + { "ssh", "sshd", NULL, 1, 0, is_ssh_protocol}, + { "openvpn", NULL, NULL, 1, 0, is_openvpn_protocol }, + { "tinc", NULL, NULL, 1, 0, is_tinc_protocol }, + { "xmpp", NULL, NULL, 1, 0, is_xmpp_protocol }, + { "http", NULL, NULL, 1, 0, is_http_protocol }, + { "ssl", NULL, NULL, 1, 0, is_tls_protocol }, + { "tls", NULL, NULL, 1, 0, is_tls_protocol }, + { "adb", NULL, NULL, 1, 0, is_adb_protocol }, + { "anyprot", NULL, NULL, 1, 0, is_true } }; static struct proto *protocols; diff --git a/probe.h b/probe.h index 4d9b473..8c576a2 100644 --- a/probe.h +++ b/probe.h @@ -23,6 +23,7 @@ struct proto { int log_level; /* 0: No logging of connection * 1: Log incoming connection */ + int keepalive; /* 0: No keepalive ; 1: Set Keepalive for this connection */ /* function to probe that protocol; parameters are buffer and length * containing the data to probe, and a pointer to the protocol structure */ diff --git a/sslh-main.c b/sslh-main.c index 220a867..81f937b 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -123,20 +123,21 @@ static void printsettings(void) for (p = get_first_protocol(); p; p = p->next) { fprintf(stderr, - "%s addr: %s. libwrap service: %s log_level: %d family %d %d\n", + "%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s]\n", p->description, sprintaddr(buf, sizeof(buf), p->saddr), p->service, p->log_level, p->saddr->ai_family, - p->saddr->ai_addr->sa_family); + p->saddr->ai_addr->sa_family, + p->keepalive ? "keepalive" : ""); } fprintf(stderr, "listening on:\n"); for (a = addr_listen; a; a = a->ai_next) { fprintf(stderr, - "\t%s\t[keepalive: %d]\n", + "\t%s\t[%s]\n", sprintaddr(buf, sizeof(buf), a), - a->ai_flags & SO_KEEPALIVE ? 1 : 0); + a->ai_flags & SO_KEEPALIVE ? "keepalive" : ""); } fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout, timeout_protocol()->description); @@ -299,6 +300,7 @@ static int config_protocols(config_t *config, struct proto **prots) )) { p->description = name; config_setting_lookup_string(prot, "service", &(p->service)); + config_setting_lookup_bool(prot, "keepalive", &p->keepalive); if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) { p->log_level = 1; From 38447c815821ae8b6379d8005628ea3c9f94f1ee Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Tue, 29 Mar 2016 21:19:05 +0200 Subject: [PATCH 31/36] v1.18 --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index a834320..847bd41 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -vNEXT: +v1.18: 29MAR2016 Added USELIBPCRE to make use of regex engine optional. From e6cb3596d1648f39df425c24d44f211017c90203 Mon Sep 17 00:00:00 2001 From: James Hogarth Date: Wed, 30 Mar 2016 11:48:45 +0100 Subject: [PATCH 32/36] Merge error in README.md mixing sections --- README.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/README.md b/README.md index c657e85..ceb781a 100644 --- a/README.md +++ b/README.md @@ -390,24 +390,6 @@ To use the generator place it in /usr/lib/systemd/system-generators and then call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate the new dynamic socket unit. -Transparent proxying means the target server sees the real -origin address, so it means if the client connects using -IPv6, the server must also support IPv6. It is easy to -support both IPv4 and IPv6 by configuring the server -accordingly, and setting `sslh` to connect to a name that -resolves to both IPv4 and IPv6, e.g.: - - sslh --transparent --listen :443 --ssh insideaddr:22 - - /etc/hosts: - 192.168.0.1 insideaddr - 201::::2 insideaddr - -Upon incoming IPv6 connection, `sslh` will first try to -connect to the IPv4 address (which will fail), then connect -to the IPv6 address. - - Fail2ban -------- From 245fd264594cfa7b938721fab37c7b13fb557c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yves=20Rutschl=C3=A9?= Date: Tue, 3 May 2016 08:37:23 +0000 Subject: [PATCH 33/36] fix compiler warnings --- common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common.c b/common.c index b1fbc53..394cdb1 100644 --- a/common.c +++ b/common.c @@ -180,15 +180,15 @@ int bind_peer(int fd, int fd_from) int family = ifa->ifa_addr->sa_family; if (family == AF_INET) { - struct sockaddr_in *from_addr = from.ai_addr; - struct sockaddr_in *ifa_addr = ifa->ifa_addr; + struct sockaddr_in *from_addr = (struct sockaddr_in*)from.ai_addr; + struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr; if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) match = 1; } else if (family == AF_INET6) { - struct sockaddr_in6 *from_addr = from.ai_addr; - struct sockaddr_in6 *ifa_addr = ifa->ifa_addr; + struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from.ai_addr; + struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr; if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) match = 1; } From 63a83cf0416ab89720b15a4a649786812b630b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yves=20Rutschl=C3=A9?= Date: Fri, 20 May 2016 07:52:24 +0000 Subject: [PATCH 34/36] fix SNI/ALPN option parsing (issue 90) --- sslh-main.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/sslh-main.c b/sslh-main.c index 8f9c515..3242cc4 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -235,10 +235,6 @@ static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char * config_item; char** sni_hostname_list; - if(!config_items || !config_setting_is_array(config_items)) { - fprintf(stderr, "%s: no %s specified\n", p->description, name); - return; - } num_probes = config_setting_length(config_items); if (!num_probes) { fprintf(stderr, "%s: no %s specified\n", p->description, name); @@ -264,12 +260,22 @@ static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list); } -static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, config_setting_t* alpn_protocols) +static void setup_sni_alpn(struct proto *p, config_setting_t* prot) { + config_setting_t *sni_hostnames, *alpn_protocols; + p->data = (void*)new_tls_data(); - p->probe = get_probe("sni_alpn"); - setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0); - setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1); + sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); + alpn_protocols = config_setting_get_member(prot, "alpn_protocols"); + + if(sni_hostnames && config_setting_is_array(sni_hostnames)) { + p->probe = get_probe("sni_alpn"); + setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0); + } + if(alpn_protocols && config_setting_is_array(alpn_protocols)) { + p->probe = get_probe("sni_alpn"); + setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1); + } } #endif @@ -279,7 +285,7 @@ static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, con #ifdef LIBCONFIG static int config_protocols(config_t *config, struct proto **prots) { - config_setting_t *setting, *prot, *patterns, *sni_hostnames, *alpn_protocols; + config_setting_t *setting, *prot, *patterns; const char *hostname, *port, *name; int i, num_prots; struct proto *p, *prev = NULL; @@ -324,12 +330,7 @@ static int config_protocols(config_t *config, struct proto **prots) /* Probe-specific options: SNI/ALPN */ if (!strcmp(name, "tls")) { - sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); - alpn_protocols = config_setting_get_member(prot, "alpn_protocols"); - - if((sni_hostnames && config_setting_is_array(sni_hostnames)) || (alpn_protocols && config_setting_is_array(alpn_protocols))) { - setup_sni_alpn(p, sni_hostnames, alpn_protocols); - } + setup_sni_alpn(p, prot); } } From 975560aa4a197bc7aeb4a8952ed8a764b8a90e3a Mon Sep 17 00:00:00 2001 From: Michael Scherer Date: Sat, 4 Jun 2016 11:57:08 +0200 Subject: [PATCH 35/36] Add more options in the synopsis --- sslh.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sslh.pod b/sslh.pod index a90d4bd..d195fa1 100644 --- a/sslh.pod +++ b/sslh.pod @@ -6,7 +6,7 @@ =head1 SYNOPSIS -sslh [B<-F> I] [ B<-t> I ] [B<--transparent>] [B<-p> I [B<-p> I ...] [B<--ssl> I] [B<--ssh> I] [B<--openvpn> I] [B<--http> I] [B<--anyprot> I] [B<--on-timeout> I] [B<-u> I] [B<-P> I] [-v] [-i] [-V] [-f] [-n] +sslh [B<-F> I] [ B<-t> I ] [B<--transparent>] [B<-p> I [B<-p> I ...] [B<--ssl> I] [B<--tls> I] [B<--ssh> I] [B<--openvpn> I] [B<--http> I] [B<--xmpp> I] [B<--tinc> I] [B<--anyprot> I] [B<--on-timeout> I] [B<-u> I] [B<-P> I] [-v] [-i] [-V] [-f] [-n] =head1 DESCRIPTION From f02ce3821c018719536971dbb1bc1ed1517530a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yves=20Rutschl=C3=A9?= Date: Sun, 6 Nov 2016 19:44:47 +0000 Subject: [PATCH 36/36] clarify no space after -F (issue 108) --- common.c | 5 ++--- common.h | 3 +-- probe.c | 20 ++++++++++---------- probe.h | 1 + sslh-main.c | 18 ++++++++++++------ sslh.pod | 7 +++++-- 6 files changed, 31 insertions(+), 23 deletions(-) diff --git a/common.c b/common.c index 394cdb1..8187b4d 100644 --- a/common.c +++ b/common.c @@ -37,7 +37,6 @@ int probing_timeout = 2; int inetd = 0; int foreground = 0; int background = 0; -int transparent = 0; int numeric = 0; const char *user_name, *pid_file; @@ -237,7 +236,7 @@ int connect_addr(struct connection *cnx, int fd_from) for (a = cnx->proto->saddr; a; a = a->ai_next) { /* When transparent, make sure both connections use the same address family */ - if (transparent && a->ai_family != from.ai_addr->sa_family) + if (cnx->proto->transparent && a->ai_family != from.ai_addr->sa_family) continue; if (verbose) fprintf(stderr, "connecting to %s family %d len %d\n", @@ -250,7 +249,7 @@ int connect_addr(struct connection *cnx, int fd_from) log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx->proto->description, strerror(errno)); } else { - if (transparent) { + if (cnx->proto->transparent) { res = bind_peer(fd, fd_from); CHECK_RES_RETURN(res, "bind_peer"); } diff --git a/common.h b/common.h index 701b337..1002d23 100644 --- a/common.h +++ b/common.h @@ -113,8 +113,7 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list); int defer_write(struct queue *q, void* data, int data_size); int flush_deferred(struct queue *q); -extern int probing_timeout, verbose, inetd, foreground, - background, transparent, numeric; +extern int probing_timeout, verbose, inetd, foreground, background, numeric; extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn; extern struct addrinfo *addr_listen; extern const char* USAGE_STRING; diff --git a/probe.c b/probe.c index 9b4a63e..22bdc3b 100644 --- a/probe.c +++ b/probe.c @@ -45,16 +45,16 @@ static int is_true(const char *p, int len, struct proto* proto) { return 1; } /* Table of protocols that have a built-in probe */ static struct proto builtins[] = { - /* description service saddr log_level keepalive probe */ - { "ssh", "sshd", NULL, 1, 0, is_ssh_protocol}, - { "openvpn", NULL, NULL, 1, 0, is_openvpn_protocol }, - { "tinc", NULL, NULL, 1, 0, is_tinc_protocol }, - { "xmpp", NULL, NULL, 1, 0, is_xmpp_protocol }, - { "http", NULL, NULL, 1, 0, is_http_protocol }, - { "ssl", NULL, NULL, 1, 0, is_tls_protocol }, - { "tls", NULL, NULL, 1, 0, is_tls_protocol }, - { "adb", NULL, NULL, 1, 0, is_adb_protocol }, - { "anyprot", NULL, NULL, 1, 0, is_true } + /* description service saddr log_level keepalive transparent probe */ + { "ssh", "sshd", NULL, 1, 0, 0, is_ssh_protocol}, + { "openvpn", NULL, NULL, 1, 0, 0, is_openvpn_protocol }, + { "tinc", NULL, NULL, 1, 0, 0, is_tinc_protocol }, + { "xmpp", NULL, NULL, 1, 0, 0, is_xmpp_protocol }, + { "http", NULL, NULL, 1, 0, 0, is_http_protocol }, + { "ssl", NULL, NULL, 1, 0, 0, is_tls_protocol }, + { "tls", NULL, NULL, 1, 0, 0, is_tls_protocol }, + { "adb", NULL, NULL, 1, 0, 0, is_adb_protocol }, + { "anyprot", NULL, NULL, 1, 0, 0, is_true } }; static struct proto *protocols; diff --git a/probe.h b/probe.h index 8c576a2..492e42f 100644 --- a/probe.h +++ b/probe.h @@ -24,6 +24,7 @@ struct proto { * 1: Log incoming connection */ int keepalive; /* 0: No keepalive ; 1: Set Keepalive for this connection */ + int transparent; /* 0: opaque proxy ; 1: transparent proxy */ /* function to probe that protocol; parameters are buffer and length * containing the data to probe, and a pointer to the protocol structure */ diff --git a/sslh-main.c b/sslh-main.c index 3242cc4..b72a2c0 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -39,7 +39,7 @@ const char* USAGE_STRING = "sslh " VERSION "\n" \ "usage:\n" \ -"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F ]\n" +"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F]\n" "\t[-t ] [-P ] -u -p [-p ...] \n" \ "%s\n\n" /* Dynamically built list of builtin protocols */ \ "\t[--on-timeout ]\n" \ @@ -49,7 +49,7 @@ const char* USAGE_STRING = "-n: numeric output\n" \ "-u: specify under which user to run\n" \ "--transparent: behave as a transparent proxy\n" \ -"-F: use configuration file\n" \ +"-F: use configuration file (warning: no space between -F and file name!)\n" \ "--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \ "-t: seconds to wait before connecting to --on-timeout address.\n" \ "-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \ @@ -61,11 +61,14 @@ const char* USAGE_STRING = /* Constants for options that have no one-character shorthand */ #define OPT_ONTIMEOUT 257 +/* Global setting for transparent proxying */ +int g_transparent = 0; + static struct option const_options[] = { { "inetd", no_argument, &inetd, 1 }, { "foreground", no_argument, &foreground, 1 }, { "background", no_argument, &background, 1 }, - { "transparent", no_argument, &transparent, 1 }, + { "transparent", no_argument, &g_transparent, 1 }, { "numeric", no_argument, &numeric, 1 }, { "verbose", no_argument, &verbose, 1 }, { "user", required_argument, 0, 'u' }, @@ -123,14 +126,16 @@ static void printsettings(void) for (p = get_first_protocol(); p; p = p->next) { fprintf(stderr, - "%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s]\n", + "%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s%s]\n", p->description, sprintaddr(buf, sizeof(buf), p->saddr), p->service, p->log_level, p->saddr->ai_family, p->saddr->ai_addr->sa_family, - p->keepalive ? "keepalive" : ""); + p->keepalive ? "keepalive " : "", + p->transparent ? "transparent" : "" + ); } fprintf(stderr, "listening on:\n"); for (a = addr_listen; a; a = a->ai_next) { @@ -307,6 +312,7 @@ static int config_protocols(config_t *config, struct proto **prots) p->description = name; config_setting_lookup_string(prot, "service", &(p->service)); config_setting_lookup_bool(prot, "keepalive", &p->keepalive); + config_setting_lookup_bool(prot, "transparent", &p->transparent); if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) { p->log_level = 1; @@ -376,7 +382,7 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto * config_lookup_bool(&config, "inetd", &inetd); config_lookup_bool(&config, "foreground", &foreground); config_lookup_bool(&config, "numeric", &numeric); - config_lookup_bool(&config, "transparent", &transparent); + config_lookup_bool(&config, "transparent", &g_transparent); if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) { probing_timeout = timeout; diff --git a/sslh.pod b/sslh.pod index d195fa1..95246f7 100644 --- a/sslh.pod +++ b/sslh.pod @@ -6,7 +6,7 @@ =head1 SYNOPSIS -sslh [B<-F> I] [ B<-t> I ] [B<--transparent>] [B<-p> I [B<-p> I ...] [B<--ssl> I] [B<--tls> I] [B<--ssh> I] [B<--openvpn> I] [B<--http> I] [B<--xmpp> I] [B<--tinc> I] [B<--anyprot> I] [B<--on-timeout> I] [B<-u> I] [B<-P> I] [-v] [-i] [-V] [-f] [-n] +sslh [B<-F>I] [ B<-t> I ] [B<--transparent>] [B<-p> I [B<-p> I ...] [B<--ssl> I] [B<--tls> I] [B<--ssh> I] [B<--openvpn> I] [B<--http> I] [B<--xmpp> I] [B<--tinc> I] [B<--anyprot> I] [B<--on-timeout> I] [B<-u> I] [B<-P> I] [-v] [-i] [-V] [-f] [-n] =head1 DESCRIPTION @@ -78,12 +78,15 @@ connections and LOG_ERR for failures. =over 4 -=item B<-F> I, B<--config> I +=item B<-F>I, B<--config> I Uses I has configuration file. If other command-line options are specified, they will override the configuration file's settings. +When using the shorthand version, make sure there should be +no space between B<-F> and the I. + =item B<-t> I, B<--timeout> I Timeout before forwarding the connection to the timeout