Conflicts:
	probe.c
This commit is contained in:
Christoph Sarnowski 2016-12-10 20:25:39 +01:00
commit ed02db7514
16 changed files with 1077 additions and 143 deletions

View File

@ -1,4 +1,23 @@
vNEXT:
v1.18: 29MAR2016
Added USELIBPCRE to make use of regex engine
optional.
Added support for RFC4366 SNI and RFC7301 ALPN
(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
'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.
Added 'keepalive' option.
v1.17: 09MAR2015
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
Transparant proxy support for FreeBSD.

View File

@ -1,11 +1,16 @@
# Configuration
VERSION=$(shell ./genver.sh -r)
ENABLE_REGEX=1 # Enable regex probes
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/local
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
MANDIR?=$(PREFIX)/share/man/man8
MAN=sslh.8.gz # man page name
@ -20,13 +25,22 @@ 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
CPPFLAGS+=-DLIBWRAP
endif
ifneq ($(strip $(ENABLE_REGEX)),)
CPPFLAGS+=-DENABLE_REGEX
endif
ifneq ($(strip $(USELIBPCRE)),)
CPPFLAGS+=-DLIBPCRE
LIBS:=$(LIBS) -lpcre
endif
ifneq ($(strip $(USELIBCONFIG)),)
LIBS:=$(LIBS) -lconfig
CPPFLAGS+=-DLIBCONFIG
@ -37,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
@ -55,8 +75,11 @@ 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 $(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)
@ -68,8 +91,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)/$(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)
@ -78,14 +103,14 @@ 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
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]
@ -96,4 +121,3 @@ cscope:
test:
./t

View File

@ -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
--------
@ -102,7 +104,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<x>.d so that the server
@ -217,7 +219,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
@ -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 <extaddr>: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/<name>.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.
Fail2ban
--------

View File

@ -6,7 +6,7 @@ foreground: false;
inetd: false;
numeric: false;
transparent: false;
timeout: "2";
timeout: 2;
user: "nobody";
pidfile: "/var/run/sslh.pid";
@ -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"; log_level: 0; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);

151
common.c
View File

@ -8,16 +8,26 @@
#include <stdarg.h>
#include <grp.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#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 <systemd/sd-daemon.h>
#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
@ -27,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;
@ -44,14 +53,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 +94,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++;
@ -87,6 +124,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)");
@ -120,6 +163,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 = (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 = (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;
}
}
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");
@ -141,7 +217,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)
@ -149,7 +225,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;
@ -160,10 +236,10 @@ 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",
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);
@ -173,16 +249,22 @@ 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");
}
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 {
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;
}
}
@ -191,10 +273,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 +340,7 @@ void dump_connection(struct connection *cnx)
}
/*
/*
* moves data from one fd to other
*
* returns number of bytes copied if success
@ -326,16 +408,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 +432,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)
@ -431,11 +513,14 @@ 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);
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);
@ -454,7 +539,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,
@ -471,16 +557,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 +578,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));
@ -534,7 +623,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;
@ -544,7 +633,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);
@ -616,7 +705,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);

View File

@ -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)
@ -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;

View File

@ -8,45 +8,84 @@ foreground: true;
inetd: false;
numeric: false;
transparent: false;
timeout: "2";
timeout: 2;
user: "nobody";
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
#
# 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' 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
# 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:
# 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.
#
# sslh will try each probe in order they are declared, and
# connect to the first that matches.
#
# You can specify several of 'regex' and 'tls'.
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
{ 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"; keepalive: true; },
{ name: "http"; host: "localhost"; port: "80"; },
# 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" ]; },
# 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";

View File

@ -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

108
probe.c
View File

@ -1,27 +1,33 @@
/*
# 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
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define _GNU_SOURCE
#include <stdio.h>
#ifdef ENABLE_REGEX
#ifdef LIBPCRE
#include <pcreposix.h>
#else
#include <regex.h>
#endif
#endif
#include <ctype.h>
#include "probe.h"
@ -33,20 +39,22 @@ 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
*/
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 },
{ "anyprot", NULL, NULL, 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;
@ -67,10 +75,10 @@ void set_ontimeout(const char* name)
CHECK_RES_DIE(res, "asprintf");
}
/* Returns the protocol to connect to in case of timeout;
* if not found, return the first protocol specified
/* Returns the protocol to connect to in case of timeout;
* if not found, return the first protocol specified
*/
struct proto* timeout_protocol(void)
struct proto* timeout_protocol(void)
{
struct proto* p = get_first_protocol();
for (; p && strcmp(p->description, on_timeout); p = p->next);
@ -113,7 +121,7 @@ void hexdump(const char *mem, unsigned int len)
if(j >= len) /* end of block, not really printing */
putchar(' ');
else if(isprint(mem[j])) /* printable char */
putchar(0xFF & mem[j]);
putchar(0xFF & mem[j]);
else /* other char */
putchar('.');
}
@ -153,7 +161,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)
{
@ -169,7 +178,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;
@ -209,6 +221,19 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
return PROBE_NEXT;
}
static int is_sni_alpn_protocol(const char *p, int len, struct proto *proto)
{
int valid_tls;
valid_tls = parse_tls_header(proto->data, p, len);
if(valid_tls < 0)
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
/* There *was* a valid match */
return PROBE_MATCH;
}
static int is_tls_protocol(const char *p, int len, struct proto *proto)
{
if (len < 3)
@ -221,8 +246,25 @@ 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)
{
#ifdef ENABLE_REGEX
char *str;
regex_t **probe = proto->data;
regmatch_t pos = { 0, len };
@ -240,11 +282,16 @@ static int regex_probe(const char *p, int len, struct proto *proto)
/* try them all */;
free(str);
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
}
/*
/*
* Read the beginning of data coming from the client connection and check if
* it's a known protocol.
* it's a known protocol.
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
* which case cnx->proto is set to the appropriate protocol.
*/
@ -276,9 +323,9 @@ int probe_client_protocol(struct connection *cnx)
return res;
}
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
/* If none worked, return the first one affected (that's completely
@ -301,7 +348,7 @@ static struct proto* get_protocol(const char* description)
}
/* Returns the probe for specified protocol:
* parameter is the description in builtins[], or "regex"
* parameter is the description in builtins[], or "regex"
* */
T_PROBE* get_probe(const char* description) {
struct proto* p = get_protocol(description);
@ -315,7 +362,14 @@ T_PROBE* get_probe(const char* description) {
if (!strcmp(description, "regex"))
return regex_probe;
/* 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 */
if (!strcmp(description, "timeout"))
return is_true;
return NULL;
}

13
probe.h
View File

@ -1,9 +1,10 @@
/* API for probe.c */
#ifndef __PROBE_H_
#define __PROBE_H_
#ifndef PROBE_H
#define PROBE_H
#include "common.h"
#include "tls.h"
typedef enum {
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
@ -19,11 +20,17 @@ 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
*/
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 */
T_PROBE* probe;
void* data; /* opaque pointer ; used to pass list of regex to regex 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 */
};

View File

@ -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'
}

View File

@ -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
@ -25,7 +25,13 @@
#ifdef LIBCONFIG
#include <libconfig.h>
#endif
#ifdef ENABLE_REGEX
#ifdef LIBPCRE
#include <pcreposix.h>
#else
#include <regex.h>
#endif
#endif
#include "common.h"
#include "probe.h"
@ -33,7 +39,7 @@
const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F <file>]\n"
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
"\t[-t <timeout>] [-P <pidfile>] -u <username> -p <add> [-p <addr> ...] \n" \
"%s\n\n" /* Dynamically built list of builtin protocols */ \
"\t[--on-timeout <addr>]\n" \
@ -43,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" \
@ -55,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' },
@ -117,16 +126,23 @@ 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 [%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->saddr->ai_addr->sa_family,
p->keepalive ? "keepalive " : "",
p->transparent ? "transparent" : ""
);
}
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[%s]\n",
sprintaddr(buf, sizeof(buf), a),
a->ai_flags & SO_KEEPALIVE ? "keepalive" : "");
}
fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout,
timeout_protocol()->description);
@ -140,7 +156,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");
@ -156,12 +172,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;
}
}
}
@ -174,6 +198,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 ENABLE_REGEX
int num_probes, errsize, i, res;
char *err;
const char * expr;
@ -201,6 +226,61 @@ 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
#ifdef LIBCONFIG
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 * config_item;
char** sni_hostname_list;
num_probes = config_setting_length(config_items);
if (!num_probes) {
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(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);
for (i = 0; i < num_probes; 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], 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* prot)
{
config_setting_t *sni_hostnames, *alpn_protocols;
p->data = (void*)new_tls_data();
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
@ -210,7 +290,7 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
{
config_setting_t *setting, *prot, *probes;
config_setting_t *setting, *prot, *patterns;
const char *hostname, *port, *name;
int i, num_prots;
struct proto *p, *prev = NULL;
@ -231,33 +311,34 @@ 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;
}
resolve_split_name(&(p->saddr), hostname, port);
p->probe = get_probe(name);
if (!p->probe || !strcmp(name, "sni_alpn")) {
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);
}
}
/* Probe-specific options: SNI/ALPN */
if (!strcmp(name, "tls")) {
setup_sni_alpn(p, prot);
}
}
}
}
@ -270,6 +351,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)
@ -280,13 +362,19 @@ 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),
config_error_text(&config));
exit(1);
}
fprintf(stderr, "%s:%s\n",
filename,
config_error_text(&config));
return 1;
}
@ -294,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;
@ -363,10 +451,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 */
@ -481,10 +571,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)
@ -521,6 +614,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 */

View File

@ -6,7 +6,7 @@
=head1 SYNOPSIS
sslh [B<-F> I<config file>] [ B<-t> I<num> ] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--ssl> I<target address for SSL>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
sslh [B<-F>I<config file>] [ B<-t> I<num> ] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--ssl> I<target address for SSL>] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
=head1 DESCRIPTION
@ -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<probe> parameter, and if the
expressions is given as the I<regex_patterns> parameter, and if the
first packet received from the client matches any of these
expressions, B<sslh> connects to that protocol.
Alternatively, the I<probe> 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<sslh> will read the
@ -82,12 +78,15 @@ connections and LOG_ERR for failures.
=over 4
=item B<-F> I<filename>, B<--config> I<filename>
=item B<-F>I<filename>, B<--config> I<filename>
Uses I<filename> 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<filename>.
=item B<-t> I<num>, B<--timeout> I<num>
Timeout before forwarding the connection to the timeout

153
systemd-sslh-generator.c Normal file
View File

@ -0,0 +1,153 @@
#include <libconfig.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

327
tls.c Normal file
View File

@ -0,0 +1,327 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
* 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 <stdio.h>
#include <stdlib.h> /* malloc() */
#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
struct TLSProtocol {
int use_alpn;
char** sni_hostname_list;
char** alpn_protocol_list;
};
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
* caller is responsible for freeing *hostname
* -1 - Incomplete request
* -2 - No Host header included in this request
* -3 - Invalid hostname pointer
* < -4 - Invalid TLS client hello
*/
int
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;
/* Check that our TCP payload is at least large enough for a TLS header */
if (data_len < TLS_HEADER_LEN)
return -1;
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 cannot be parsed.\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(tls_data, data + pos, len);
}
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];
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 */
/* 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;
return -2;
}
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];
if (pos + 3 + len > data_len)
return -5;
switch (data[pos]) { /* name type */
case 0x00: /* host_name */
if(has_match(tls_data->sni_hostname_list, data + pos + 3, len)) {
return len;
} else {
return -2;
}
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;
}
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;
}

38
tls.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
* 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"
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