mirror of
https://github.com/yrutschle/sslh.git
synced 2025-04-23 20:20:29 +03:00
Merge branch 'master' of https://github.com/yrutschle/sslh
Conflicts: probe.c
This commit is contained in:
commit
ed02db7514
21
ChangeLog
21
ChangeLog
@ -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.
|
||||
|
40
Makefile
40
Makefile
@ -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
|
||||
|
||||
|
90
README.md
90
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
|
||||
--------
|
||||
@ -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
|
||||
--------
|
||||
|
14
basic.cfg
14
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";
|
||||
|
||||
@ -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
151
common.c
@ -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);
|
||||
|
7
common.h
7
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)
|
||||
@ -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;
|
||||
|
69
example.cfg
69
example.cfg
@ -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";
|
||||
|
@ -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
108
probe.c
@ -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
13
probe.h
@ -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 */
|
||||
};
|
||||
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
170
sslh-main.c
170
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
|
||||
@ -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 */
|
||||
|
||||
|
13
sslh.pod
13
sslh.pod
@ -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
153
systemd-sslh-generator.c
Normal 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
327
tls.c
Normal 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
38
tls.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user