Compare commits

..
4 Commits
49 changed files with 742 additions and 707 deletions

No files matched your search

+30 -34
View File
@@ -1,43 +1,39 @@
vNEXT:
Silently ignore EAGAIN/EWOULDBLOCK to avoid
spurrious error logs upon UDP port scans.
ech branch:
This is an experimental version and does not work.
Do not use it unless you are experimenting with ECH
and an experimental wolfSSL.
verbose_packet now also dumps UDP packets.
A lot of context is discussed here:
https://github.com/wolfSSL/wolfssl/issues/10485
print_message() restores errno, fix sslh-fork
exiting upong log errors.
What it does:
- It creates a public ECH configuration for the
first public name (configure in the `listen`
section of the configuration file). This gets
printed in a file configured in `ech_conf_sink`
(see ech_test.cfg example).
- Upon incoming connections, it will call an
imaginary wolfSSL_GetEchClientHelloInner().
The resulting packet is hexdumped.
- It will also use wolfSSL to decode the SNI and
print it (but does not yet use it to forward).
Add incoming connection timeout management, which
was strangely missing.
Most things happen in tls_ech.c, in particular there
is a stub of GetEchClientHelloInner() that needs to
be removed when the function becomes available in
wolfSSL.
v2.3.1:
Fix segmentation fault in sslh-fork.
TODO:
Change configure script to check for appropriate
wolfSSL version.
Create ECH configurations for each public name
Link decoded InnerHello to SNI extraction code
....
Error cases still to be worked out (see
conversations on github)
Profit!
Add file access when using Landlock and Libwrap
together.
Mixed IPv4/IPv6 proxyprotocol setups will move both
addresses to IPv6 format on the server side.
Add CAP_NET_RAW to systemd config so transparent
proxying can work. Removed sslh-select systemd file:
don't repeat the same configuration file!
Changed SNI matching log messages from
`msg_probe_error` to `msg_probe_info`: beware if you
happen to use them...
Close unused file descriptors upon forking a worker
for a connection (in sslh-ev and sslh-select). In
particular, this prevents forked processes to
inherit the listen sockets and block a new main
process from starting.
More configuration sanity checks (e.g. using regex
or proxyprotocol options if libraries are not compiled in)
-V now outputs compile-time options so it is
possible to know what is compiled in.
v2.3.0:
Added `max_connections` setting to `listen` and
+3 -26
View File
@@ -3,7 +3,8 @@ ARG TARGET_ARCH="library"
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
# Install build dependencies for sslh and libproxyprotocol
WORKDIR /sslh
RUN apk add --no-cache \
'gcc' \
'libconfig-dev' \
@@ -11,39 +12,15 @@ RUN apk add --no-cache \
'musl-dev' \
'pcre2-dev' \
'perl' \
'git' \
'autoconf' \
'automake' \
'libtool' \
;
# Build libproxyprotocol
WORKDIR /tmp
RUN git clone https://github.com/kosmas-valianos/libproxyprotocol.git && \
cd libproxyprotocol && \
make && \
mkdir -p /usr/local/include/ && \
mv libs/libproxyprotocol.so /usr/local/lib/ && \
mv src/* /usr/local/include/
# Set environment variables for sslh to find libproxyprotocol
ENV C_INCLUDE_PATH=/usr/local/include
ENV LIBRARY_PATH=/usr/local/lib
ENV LD_LIBRARY_PATH=/usr/local/lib
WORKDIR /sslh
COPY . /sslh
# Configure and build sslh
# The configure script should automatically detect libproxyprotocol
RUN ./configure && make sslh-select && strip sslh-select
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
# Copy libproxyprotocol.so from the build stage
COPY --from=build /usr/local/lib/libproxyprotocol.so* /usr/local/lib/
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
RUN apk add --no-cache \
'libconfig' \
'pcre2' \
@@ -58,4 +35,4 @@ COPY "./container-entrypoint.sh" "/init"
ENTRYPOINT [ "/init" ]
# required for updating iptables
USER root:root
USER root:root
+3 -3
View File
@@ -22,11 +22,11 @@ MAN=sslh.8.gz # man page name
# itself
ifneq ($(strip $(ENABLE_SANITIZER)),)
CFLAGS_SAN=-DENABLE_SANITIZER -fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
endif
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-DCOV_TEST -fprofile-arcs -ftest-coverage
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC ?= gcc
@@ -35,7 +35,7 @@ CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
LIBS=-lm -lpcre2-8 @LIBS@
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o tls_ech.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
OBJS_A=libsslh.a
FORK_OBJS=sslh-fork.o $(OBJS_A)
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
+7 -1
View File
@@ -5,7 +5,7 @@ sslh -- A ssl/ssh multiplexer
them further based on tests performed on the first data
packet sent by the remote client.
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, WireGuard,
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH,
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
protocol that can be tested using a regular expression, can
be recognised. A typical use case is to allow serving
@@ -112,6 +112,8 @@ docker run \
docker-compose example
```yaml
version: "3"
services:
sslh:
image: ghcr.io/yrutschle/sslh:latest
@@ -134,6 +136,8 @@ Transparent mode 1: using sslh container for networking
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
@@ -174,6 +178,8 @@ services:
Transparent mode 2: using host networking
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
+1 -1
View File
@@ -8,7 +8,7 @@ shouldn't be construed to mean that any of this will happen.
- have certain services available only from specified subnets
- some sort of "service knocking" allowing to activate a
service upon some external event, similar to port knocking;
service upon some external even, similar to port knocking;
for example, go to a specific URL to enable sslh forwarding
to sshd for a set period of time:
* sslh listens on 443 and only directs to httpd
+2 -10
View File
@@ -28,7 +28,6 @@
/* Info to keep track of all connections */
struct cnx_collection {
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
int max_fd;
};
/* Allocates and initialises a new collection of connections with at least
@@ -43,7 +42,6 @@ cnx_collection* collection_init(int len)
memset(collection, 0, sizeof(*collection));
collection->fd2cnx = gap_init(len);
collection->max_fd = 0;
return collection;
}
@@ -56,17 +54,10 @@ void collection_destroy(cnx_collection* collection)
free(collection);
}
int collection_max_fd(cnx_collection* collection)
{
return collection->max_fd;
}
/* Points the file descriptor to the specified connection index */
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
{
gap_set(collection->fd2cnx, fd, cnx);
if (fd > collection->max_fd)
collection->max_fd = fd;
return 0;
}
@@ -83,7 +74,8 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
cnx->state = ST_PROBING;
cnx->probe_timeout = time(NULL) + cfg.timeout;
collection_add_fd(collection, cnx, fd);
gap_set(collection->fd2cnx, fd, cnx);
return cnx;
}
-1
View File
@@ -9,7 +9,6 @@ void collection_destroy(cnx_collection* collection);
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
int collection_max_fd(cnx_collection* collection);
/* Remove a connection from the collection */
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
+8 -21
View File
@@ -74,13 +74,6 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
int sd = 0;
#ifdef SYSTEMD
/* Sockets passed by systemd socket activation do not correspond to a
* `listen` entry in the configuration, but code dereferences
* endpoint->endpoint_cfg unconditionally (e.g. inc_listen_connections()
* and probe_client_protocol()), so give them a default, empty
* configuration: no max_connections, no proxyprotocol. */
static struct sslhcfg_listen_item default_endpoint_cfg;
sd = sd_listen_fds(0);
if (sd < 0) {
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
@@ -93,7 +86,6 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
for (i = 0; i < sd; i++) {
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
(*sockfd)[i].type = SOCK_STREAM;
(*sockfd)[i].endpoint_cfg = &default_endpoint_cfg;
}
}
#endif
@@ -211,8 +203,6 @@ static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, str
/* Same, but for UNIX sockets */
static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
{
struct sockaddr_un sun;
int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
CHECK_RES_DIE(fd, "socket(AF_UNIX)");
@@ -222,10 +212,10 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
exit(4);
}
memset(&sun, 0, sizeof(sun));
struct sockaddr_un sun;
sun.sun_family = AF_UNIX;
strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1);
print_message(msg_config, "binding [%s]\n", sun.sun_path);
printf("binding [%s]\n", sun.sun_path);
res = bind(fd, (struct sockaddr*)&sun, sizeof(sun));
CHECK_RES_DIE(res, "bind(AF_UNIX)");
@@ -236,7 +226,7 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
(*sockfd)[num_addr-1].socketfd = fd;
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
(*sockfd)[num_addr-1].family = AF_UNIX;
(*sockfd)[num_addr-1].family = AF_INET;
(*sockfd)[num_addr-1].endpoint_cfg = cfg;
return num_addr;
@@ -432,9 +422,9 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
cnx->proto->port);
}
for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent, make sure both connections use
* the same address family (e.g. IP4 on both sides) */
if (transparent && (a->ai_family != from.ai_addr->sa_family))
/* When transparent or using proxyprotocol, make sure both
* connections use the same address family (e.g. IP4 on both sides) */
if ((transparent || cnx->proto->proxyprotocol_is_present) && (a->ai_family != from.ai_addr->sa_family))
continue;
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a),
@@ -485,13 +475,10 @@ static int connect_unix(struct connection *cnx, int fd_from, connect_blocking bl
struct sockaddr_storage ss;
struct sockaddr_un* sun = (struct sockaddr_un*)&ss;
memset(&ss, 0, sizeof(ss));
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
sun->sun_family = AF_UNIX;
strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1);
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
CHECK_RES_RETURN(fd, "socket", -1);
int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun));
CHECK_RES_RETURN(res, "connect", res);
@@ -912,7 +899,7 @@ int check_access_rights(int in_socket, const char* service)
}
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
print_message(msg_connections, "connection from %s(%s): access denied\n", host, addr_str);
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
close(in_socket);
return -1;
}
+3 -2
View File
@@ -48,6 +48,7 @@
#include "config.h"
#include "version.h"
#undef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
@@ -200,11 +201,11 @@ extern struct addrinfo *addr_listen;
extern const char* server_type;
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
extern int hosts_ctl(char*, char*, char*, char*);
extern int hosts_ctl();
#endif
/* sslh-fork.c */
void main_inetd(void);
void start_shoveler(int);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
+3
View File
@@ -18,4 +18,7 @@
/* libbsd, to change process name */
#undef HAVE_LIBBSD
/* wolfssl, to support Encrypted Client Hello (ECH) */
#undef HAVE_WOLFSSL
#endif
Vendored
+41
View File
@@ -3329,6 +3329,47 @@ then :
LIBS="$LIBS -lbsd"
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wolfSSL_Init in -lwolfssl" >&5
printf %s "checking for wolfSSL_Init in -lwolfssl... " >&6; }
if test ${ac_cv_lib_wolfssl_wolfSSL_Init+y}
then :
printf %s "(cached) " >&6
else $as_nop
ac_check_lib_save_LIBS=$LIBS
LIBS="-lwolfssl $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char wolfSSL_Init ();
int
main (void)
{
return wolfSSL_Init ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
ac_cv_lib_wolfssl_wolfSSL_Init=yes
else $as_nop
ac_cv_lib_wolfssl_wolfSSL_Init=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wolfssl_wolfSSL_Init" >&5
printf "%s\n" "$ac_cv_lib_wolfssl_wolfSSL_Init" >&6; }
if test "x$ac_cv_lib_wolfssl_wolfSSL_Init" = xyes
then :
printf "%s\n" "#define HAVE_WOLFSSL 1" >>confdefs.h
LIBS="$LIBS -lwolfssl"
fi
ac_header= ac_cache=
for ac_item in $ac_header_c_list
+8 -10
View File
@@ -7,17 +7,15 @@ AC_INIT
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_FILES([Makefile])
AC_CHECK_LIB([wrap], [hosts_ctl],
[AC_DEFINE([HAVE_LIBWRAP], [1], [Libwrap, to support host_ctl, /etc/allow and /etc/deny]) LIBS="$LIBS -lwrap"])
AC_CHECK_LIB([cap], [cap_get_proc],
[AC_DEFINE([HAVE_LIBCAP], [1], [libcap support, to use Linux capabilities]) LIBS="$LIBS -lcap"])
AC_CHECK_LIB([bsd], [setproctitle],
[AC_DEFINE([HAVE_LIBBSD], [1], [libbsd, to change process name]) LIBS="$LIBS -lbsd"])
AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ], [])
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], [])
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], [])
AC_CHECK_HEADERS(linux/landlock.h,
AC_DEFINE([HAVE_LANDLOCK], [1], [Landlock sandboxing Linux LSM]))
AC_CHECK_HEADERS(proxy_protocol.h,
[AC_DEFINE([HAVE_PROXYPROTOCOL], [1], [Support for Proxy-protocol using libproxyprotocol]) LIBS="$LIBS -lproxyprotocol"])
dnl TODO: check that wolfssl contains wolfSSL_GetEchClientHelloInner instead of just existing
AC_CHECK_LIB([wolfssl], [wolfSSL_Init], [AC_DEFINE(HAVE_WOLFSSL) LIBS="$LIBS -lwolfssl" ], [])
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
LIBS="$LIBS"
AC_SUBST([LIBS])
+2
View File
@@ -27,6 +27,7 @@ protocols:
(
{ name: "ssh"; host: "localhost"; port: "2222"; },
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
);
);
```
@@ -76,6 +77,7 @@ protocols:
(
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
{ name: "tls"; host: "localhost"; port: "8080"; }
);
);
```
+45
View File
@@ -0,0 +1,45 @@
# sslh configuration for saaz:
# ssh to localhost
# https to nextcloud
foreground: true;
inetd: false;
numeric: false;
transparent: false;
user: "yves";
pidfile: "/tmp/sslh.pid";
syslog_facility: "auth";
# Where to write the ECH configurations
ech_conf_sink: "/tmp/ech";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
verbose: 1;
# Change hostname with your external address name.
listen:
(
{ host: "inet"; port: "4443"; },
{ host: "chaton"; port: "4445"; }
);
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
{ name: "tls"; sni_hostnames: [
"inet.rutschle.net", "lists.rutschle.net"
];
host: "localhost"; port: "4453"; },
{ name: "tls"; sni_hostnames: [
"chaton.rutschle.net"
];
host: "localhost"; port: "4455"; }
);
on-timeout: "ssh";
+2 -11
View File
@@ -68,13 +68,9 @@ void start_echo(int fd)
int first = 1;
prefix_len = strlen(cfg.prefix);
if (prefix_len >= sizeof(buffer)) {
fprintf(stderr, "prefix too long\n");
return;
}
memset(buffer, 0, sizeof(buffer));
memcpy(buffer, cfg.prefix, prefix_len);
strcpy(buffer, cfg.prefix);
while (1) {
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
@@ -151,24 +147,19 @@ void udp_echo(struct listen_endpoint* listen_socket)
memset(data, 0, sizeof(data));
size_t prefix_len = strlen(cfg.prefix);
if (prefix_len >= sizeof(data) - 1) {
fprintf(stderr, "prefix too long\n");
return;
}
memcpy(data, cfg.prefix, prefix_len);
while (1) {
addrlen = sizeof(src_addr);
ssize_t len = recvfrom(listen_socket->socketfd,
data + prefix_len,
sizeof(data) - prefix_len - 1,
sizeof(data) - prefix_len,
0,
&src_addr,
&addrlen);
if (len < 0) {
perror("recvfrom");
continue;
}
*(data + prefix_len + len) = 0;
fprintf(stderr, "%zd %s\n", len, data + prefix_len);
+109
View File
@@ -0,0 +1,109 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sat Nov 7 09:19:26 2020.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2019 Yves Rutschle
# 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 C2S_SSLHCFG_H
#define C2S_SSLHCFG_H
#ifdef LIBCONFIG
# include <libconfig.h>
#endif
#include "probe.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
struct sslhcfg_listen_item {
char* host;
char* port;
int keepalive;
};
struct sslhcfg_protocols_item {
char* name;
char* host;
char* port;
int service_is_present;
char* service;
int fork;
int tfo_ok;
int log_level;
int keepalive;
size_t sni_hostnames_len;
char** sni_hostnames;
size_t alpn_protocols_len;
char** alpn_protocols;
size_t regex_patterns_len;
char** regex_patterns;
int minlength_is_present;
int minlength;
T_PROBE* probe;
struct addrinfo* saddr;
void* data;
};
struct sslhcfg_item {
char* prefix;
int verbose;
int foreground;
int inetd;
int numeric;
int transparent;
int timeout;
int user_is_present;
char* user;
int pidfile_is_present;
char* pidfile;
int chroot_is_present;
char* chroot;
char* syslog_facility;
char* on_timeout;
size_t listen_len;
struct sslhcfg_listen_item* listen;
size_t protocols_len;
struct sslhcfg_protocols_item* protocols;
};
int sslhcfg_parse_file(
const char* filename,
struct sslhcfg_item* sslhcfg,
const char** errmsg);
void sslhcfg_fprint(
FILE* out,
struct sslhcfg_item *sslhcfg,
int depth);
int sslhcfg_cl_parse(
int argc,
char* argv[],
struct sslhcfg_item *sslhcfg);
#endif
-3
View File
@@ -159,9 +159,6 @@ protocols:
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
# Remember that the regex needs to be adjusted for every supported QUIC version.
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
# More recent versions of QUIC migth work with this:
{ name: "regex"; host: "localhost"; is_udp: true; port: "4443"; regex_patterns: [ "^[\x40-\x7F\xC0-\xFF]" ]; },
# See https://github.com/yrutschle/sslh/issues/466 for discussions on QUIC
# Regex examples -- better use the built-in probes for real-world use!
# OpenVPN
+1 -4
View File
@@ -1,9 +1,6 @@
CFLAGS=-DHASH_DEBUG -O2 -Wall
OBJ=hash.o ../gap.o htest.o
hash.o: ../hash.c
$(CC) $(CFLAGS) -c -o $@ $<
OBJ=../hash.o ../gap.o htest.o
htest: $(OBJ)
$(CC) -o htest $(OBJ)
-1
View File
@@ -29,7 +29,6 @@ struct hash_item {
};
typedef struct hash_item* hash_item;
typedef struct hash hash;
#include "../hash.h"
+1 -1
View File
@@ -118,7 +118,7 @@ static int add_resolv(int ruleset_fd)
static int add_libwrap(int ruleset_fd)
{
/* Files for libwrap */
#ifdef HAVE_LIBWRAP
#ifdef LIBWRAP
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
#endif
+1 -9
View File
@@ -48,7 +48,7 @@ msg_info msg_fd = {
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
msg_info msg_int_error = {
LOG_CRIT,
&cfg.verbose_int_error
&cfg.verbose_system_error
};
/* System errors: when the system around us fails us: memory allocation, fork, ... */
@@ -107,12 +107,6 @@ void print_message(msg_info info, const char* str, ...)
{
va_list ap;
/* print_message() being used for logging, it is often used after system
* calls and before caller checks errno; as it itself does not do anything
* with errno (can't log an error if error logging fails), it just restores
* it on exit */
int saved_errno = errno;
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
va_start(ap, str);
vfprintf(stderr, str, ap);
@@ -131,8 +125,6 @@ void print_message(msg_info info, const char* str, ...)
fflush(logfile_fp);
va_end(ap);
}
errno = saved_errno;
}
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
+10 -23
View File
@@ -88,20 +88,20 @@ struct sslhcfg_protocols_item* timeout_protocol(void)
void hexdump(msg_info msg_info, const char *mem, unsigned int len)
{
unsigned int i, j;
char str[12 + HEXDUMP_COLS * 4 + 2]; /* offset <= 12, hex+ascii 64, '\n'+'\0' */
char str[10 + HEXDUMP_COLS * 4 + 2];
int c = 0; /* index in str */
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
{
/* print offset */
if(i % HEXDUMP_COLS == 0)
c += snprintf(&str[c], sizeof(str) - c, "0x%06x: ", i);
c += sprintf(&str[c], "0x%06x: ", i);
/* print hex data */
if(i < len)
c += snprintf(&str[c], sizeof(str) - c, "%02x ", 0xFF & mem[i]);
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]);
else /* end of block, just aligning for ASCII dump */
c += snprintf(&str[c], sizeof(str) - c, " ");
c+= sprintf(&str[c], " ");
/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
@@ -156,10 +156,7 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
if (len < 2)
return PROBE_AGAIN;
/* p may be unaligned (e.g. on ARM), do not cast to uint16_t* */
uint16_t wire_len;
memcpy(&wire_len, p, sizeof(wire_len));
packet_len = ntohs(wire_len);
packet_len = ntohs(*(uint16_t*)p);
return packet_len == len - 2;
} else {
if (len < 1)
@@ -208,9 +205,7 @@ static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_prot
return PROBE_NEXT;
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
uint32_t type;
memcpy(&type, p, sizeof(type));
if (ntohl(type) != 0x01000000)
if (ntohl(*(uint32_t*)p) != 0x01000000)
return PROBE_NEXT;
return PROBE_MATCH;
@@ -412,25 +407,20 @@ static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_prot
if (len < 8)
return PROBE_NEXT;
/* Compare a fixed-length prefix: strncmp(p, "TS3INIT1", len) would read
* len bytes of the 9-byte literal and never match a real packet, whose
* byte 8 is not NUL */
return !memcmp(p, "TS3INIT1", 8);
return !strncmp(p, "TS3INIT1", len);
}
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
char version;
uint16_t packet_len;
char packet_len;
if (len < 7)
return PROBE_NEXT;
version=*p;
if (version!=0x03)
return 0;
memcpy(&packet_len, p+2, sizeof(packet_len));
/* The declared TPKT length must fit the entire received packet; a char
* would truncate e.g. 0x0107 to 7 and accept truncated packets */
return ntohs(packet_len) == len;
packet_len = ntohs(*(uint16_t*)(p+2));
return packet_len == len;
}
#ifdef ENABLE_REGEX
@@ -475,9 +465,6 @@ int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item* p;
int i, res, again = 0;
print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, buf, len);
*proto_out = NULL;
for (i = 0; i < proto_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
+1 -6
View File
@@ -142,10 +142,6 @@ static int pid_cmp(hash_item item1, hash_item item2)
void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
struct pid2proto p2p = { .pid = pid };
struct pid2proto* found = hash_find(loop->pid2proto, &p2p);
if (!found) {
print_message(msg_int_error, "SIGCHLD for unknown pid %d\n", pid);
return;
}
dec_proto_connections(found->proto);
dec_listen_connections(found->endpoint);
@@ -200,7 +196,7 @@ static int next_power_of_two(int n)
return n + 1;
}
void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen)
void loop_init(struct loop_info* loop, int num_addr_listen)
{
memset(loop, 0, sizeof(*loop));
loop->collection = collection_init(0);
@@ -210,7 +206,6 @@ void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, i
udp_init(loop);
tcp_init();
loop->listen_sockets = listen_sockets;
loop->num_addr_listen = num_addr_listen;
int max_forks = next_power_of_two(max_forking_connections());
+1 -5
View File
@@ -23,8 +23,6 @@ struct loop_info {
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
watchers* watchers;
struct listen_endpoint* listen_sockets;
int num_addr_listen; /* How many listen endpoints do we have here */
cnx_collection* collection; /* Collection of connections linked to this loop */
@@ -34,7 +32,7 @@ void cnx_read_process(struct loop_info* fd_info, int fd);
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket);
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen);
void loop_init(struct loop_info* loop, int num_addr_listen);
void remember_child_data(struct loop_info* fd_info,
struct connection* cnx, pid_t pid);
@@ -49,6 +47,4 @@ void watchers_del_write(watchers* w, int fd);
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
void close_listen_endpoints(struct loop_info* loop);
#endif
+2 -2
View File
@@ -112,7 +112,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
if (res == -1) return -1;
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
res = get_info(cnx->q[0].fd, SOCK,
res = get_info(cnx->q[1].fd, SOCK,
&addr,
&pp_info_in_v1.dst_addr,
&pp_info_in_v1.dst_port
@@ -122,7 +122,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
if (!pp1_hdr) {
print_message(msg_system_error, "pp_create_hdr:%d:%s\n", error, pp_strerror(error));
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error));
return -1;
}
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
+1 -1
View File
@@ -13,7 +13,7 @@ int pp_header_len(char* buffer, int len);
#else /* HAVE_PROXYPROTOCOL */
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
static inline int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; }
static int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; }
static inline int pp_header_len(char*, int) { return 0; }
#endif /* HAVE_PROXYPROTOCOL */
+27
View File
@@ -0,0 +1,27 @@
[Unit]
Description=SSL/SSH multiplexer (select mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target
+3 -3
View File
@@ -1,5 +1,5 @@
[Unit]
Description=SSL/SSH multiplexer for %I
Description=SSL/SSH multiplexer (fork mode) for %I
After=network.target
[Service]
@@ -8,8 +8,8 @@ ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_SETGIT CAP_SETUID CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
+62 -16
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Wed Sep 10 15:49:49 2025.
* on Sat Jul 18 17:34:37 2026.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
@@ -482,6 +482,8 @@ struct arg_file* sslhcfg_conffile;
struct arg_str* sslhcfg_chroot;
struct arg_str* sslhcfg_syslog_facility;
struct arg_str* sslhcfg_logfile;
struct arg_str* sslhcfg_ech_conf_sink;
struct arg_str* sslhcfg_ech_public_name;
struct arg_str* sslhcfg_on_timeout;
struct arg_str* sslhcfg_prefix;
struct arg_str* sslhcfg_listen;
@@ -926,7 +928,7 @@ static struct config_desc table_sslhcfg_listen[] = {
},
{ 0 }
};
static struct config_desc table_sslhcfg[] = {
@@ -1314,6 +1316,38 @@ static struct config_desc table_sslhcfg[] = {
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "ech_conf_sink",
/* type */ CFG_STRING,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_ech_conf_sink,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, ech_conf_sink),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_item, ech_conf_sink_is_present),
/* size */ sizeof(char*),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 1,
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "ech_public_name",
/* type */ CFG_STRING,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_ech_public_name,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, ech_public_name),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_item, ech_public_name_is_present),
/* size */ sizeof(char*),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 1,
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "on_timeout",
/* type */ CFG_STRING,
@@ -1500,7 +1534,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: listen */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_listen,
.base_entry = & table_sslhcfg [26],
.base_entry = & table_sslhcfg [28],
.targets = sslhcfg_listen_targets,
@@ -1512,7 +1546,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: ssh */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssh,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_ssh_targets,
@@ -1524,7 +1558,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: tls */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_tls,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_tls_targets,
@@ -1536,7 +1570,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: ssl */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssl,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_ssl_targets,
@@ -1548,7 +1582,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: openvpn */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_openvpn,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_openvpn_targets,
@@ -1560,7 +1594,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: tinc */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_tinc,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_tinc_targets,
@@ -1572,7 +1606,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: wireguard */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_wireguard,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_wireguard_targets,
@@ -1584,7 +1618,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: xmpp */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_xmpp,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_xmpp_targets,
@@ -1596,7 +1630,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: http */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_http,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_http_targets,
@@ -1608,7 +1642,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: adb */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_adb,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_adb_targets,
@@ -1620,7 +1654,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: socks5 */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_socks5,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_socks5_targets,
@@ -1632,7 +1666,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: syslog */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_syslog,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_syslog_targets,
@@ -1644,7 +1678,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: msrdp */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_msrdp,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_msrdp_targets,
@@ -1656,7 +1690,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: anyprot */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_anyprot,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_anyprot_targets,
@@ -2345,6 +2379,8 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
sslhcfg_ech_conf_sink = arg_strn(NULL, "ech-conf-sink", "<str>", 0, 1, "File where to write ECH configurations"),
sslhcfg_ech_public_name = arg_strn(NULL, "ech-public-name", "<str>", 0, 1, "Public name for ECH client-facing proxy (usually, sslh's address)"),
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
@@ -2621,6 +2657,16 @@ void sslhcfg_fprint(
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "ech_conf_sink: %s", sslhcfg->ech_conf_sink);
if (! sslhcfg->ech_conf_sink_is_present)
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "ech_public_name: %s", sslhcfg->ech_public_name);
if (! sslhcfg->ech_public_name_is_present)
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
fprintf(out, "\n");
indent(out, depth);
+6 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Wed Sep 10 15:49:49 2025.
* on Sat Jul 18 17:34:37 2026.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
@@ -38,6 +38,7 @@
#include "probe.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <wolfssl/ssl.h>
#include <netdb.h>
struct sslhcfg_listen_item {
@@ -114,6 +115,10 @@ struct sslhcfg_item {
char* syslog_facility;
int logfile_is_present;
char* logfile;
int ech_conf_sink_is_present;
char* ech_conf_sink;
int ech_public_name_is_present;
char* ech_public_name;
char* on_timeout;
char* prefix;
size_t listen_len;
+4 -48
View File
@@ -26,7 +26,6 @@
#include "log.h"
#include "udp-listener.h"
#include "tcp-listener.h"
#include "sslh-conf.h"
const char* server_type = "sslh-ev";
@@ -168,41 +167,6 @@ static void temporary_stop_accept_watchers(ev_io* io)
ev_timer_start(EV_A_ timer);
}
/* Timer to timeout incoming connections that send no data.
* It's set to cnx.timeout during initialisation, then started when at least
* one connection is probing. It's stopped when the last probing connection is
* forwarded. Whenever it fires, it processes the first probing connection
* (which is due to time out), then resets the timer to trigger when the next
* connection is due. This relies on the loop->probing_list */
static ev_timer probe_timer;
static void probe_timeout_cb(EV_P_ ev_timer* timer, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
ev_timer_stop(EV_A_ &probe_timer);
/* Process all probing connections that have timed out */
if (info->num_probing > 0) {
struct connection* cnx = gap_get(info->probing_list, 0);
if (cnx) { /* impossible cnx is NULL? */
probing_read_process(cnx, info);
}
}
/* If there is at least one left, restart the timer to fire depending on
* the next connection to time out */
if (info->num_probing > 0) {
struct connection* cnx = gap_get(info->probing_list, 0);
time_t duration = cnx->probe_timeout - time(NULL);
if (duration <= 0) duration = 1;
ev_timer_set(EV_A_ &probe_timer, duration, 0);
ev_timer_start(EV_A_ &probe_timer);
}
}
/* The accept() callback, called when there is READ activity.
*
* accept()ing when the process as reached its file descriptor limit (ulimit -n) is can of worms with no solution (see libev's manual "The special problem of accept()ing when you can't").
@@ -212,14 +176,7 @@ static void probe_timeout_cb(EV_P_ ev_timer* timer, int revents)
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
struct connection* cnx = cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
if (cnx) {
if (info->num_probing == 1) { /* This is the first connection, start the timer */
ev_timer_set(EV_A_ &probe_timer, cfg.timeout, 0);
ev_timer_start(EV_A_ &probe_timer);
}
} else { /* accept() failed, probably all file descriptors are used */
if (!cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd))) {
switch (errno) {
case EMFILE:
case ENFILE: /* Not sure ENFILE should be included here */
@@ -250,14 +207,13 @@ void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pi
ev_child_start(EV_DEFAULT_ cw);
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
struct loop_info ev_info;
loop = EV_DEFAULT;
ev_timer_init(&probe_timer, &probe_timeout_cb, cfg.timeout, 0);
loop_init(&ev_info, listen_sockets, num_addr_listen);
loop_init(&ev_info, num_addr_listen);
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info);
@@ -265,7 +221,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
ev_run(EV_A_ 0);
}
void main_inetd(void) {
void start_shoveler(int listen_socket) {
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
exit(1);
}
+9 -28
View File
@@ -69,7 +69,7 @@ int shovel(struct connection *cnx)
/* Child process that finds out what to connect to and proxies
*/
static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
void start_shoveler(int in_socket)
{
fd_set fds;
struct timeval tv;
@@ -79,7 +79,6 @@ static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
init_cnx(&cnx);
cnx.q[0].fd = in_socket;
cnx.endpoint = endpoint;
FD_ZERO(&fds);
FD_SET(in_socket, &fds);
@@ -131,22 +130,6 @@ static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
exit(0);
}
void main_inetd(void)
{
struct listen_endpoint endpoint = {0};
struct sslhcfg_listen_item endpoint_cfg = {0};
/* Empty configuration: no connection limits, not proxyprotocol... */
endpoint.endpoint_cfg = &endpoint_cfg;
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0, &endpoint);
exit(0);
}
static pid_t *listener_pid;
static int listener_pid_number = 0;
@@ -224,10 +207,9 @@ static void process_signals(struct listen_endpoint* endpoint)
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
{
int i, in_socket;
struct listen_endpoint* self = &endpoint[active_endpoint];
while (1) {
in_socket = accept(self->socketfd, 0, 0);
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__FILE__, __LINE__, "accept", errno, strerror(errno));
@@ -244,34 +226,33 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
continue;
case EINTR:
process_signals(self);
process_signals(endpoint);
continue;
default: /* Otherwise, it's something wrong in our parameters, we fail */
return;
}
}
self->num_connections++;
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, self->num_connections);
if ((self->endpoint_cfg->max_connections_is_present)
&& (self->num_connections > self->endpoint_cfg->max_connections))
endpoint->num_connections++;
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, endpoint->num_connections);
if ((endpoint->endpoint_cfg->max_connections_is_present)
&& (endpoint->num_connections > endpoint->endpoint_cfg->max_connections))
{
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
self->num_connections--;
endpoint->num_connections--;
close(in_socket);
continue;
}
switch(fork()) {
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
self->num_connections--; /* no child will reap it */
break;
case 0: /* In child process */
/* Shoveler processes don't need to hog file descriptors */
for (i = 0; i < num_endpoints; i++)
close(endpoint[i].socketfd);
start_shoveler(in_socket, self);
start_shoveler(in_socket);
exit(0);
default: /* In parent process */
+10 -125
View File
@@ -30,8 +30,6 @@
#include <pcre2.h>
#endif
#include <sys/un.h>
#include "common.h"
#include "probe.h"
#include "log.h"
@@ -74,16 +72,16 @@ static void printsettings(void)
for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i];
if (p->is_unix) {
snprintf(buf, sizeof(buf), "unix socket: %s", p->host);
sprintf(buf, "unix socket: %s", p->host);
} else {
if (p->resolve_on_forward) {
strcpy(buf, "resolve on forward: ");
size_t len = strlen(buf);
snprintf(buf+len, sizeof(buf)-len, "%s:%s", p->host, p->port);
sprintf(buf+len, "%s:%s", p->host, p->port);
} else {
sprintaddr(buf, sizeof(buf), p->saddr);
size_t len = strlen(buf);
snprintf(buf+len, sizeof(buf)-len, " family %d %d",
sprintf(buf+len, " family %d %d",
p->saddr->ai_family,
p->saddr->ai_addr->sa_family);
}
@@ -222,77 +220,6 @@ static void config_protocols()
}
}
static void config_sanity_regex(const struct sslhcfg_protocols_item* prot)
{
if (prot->regex_patterns_len > 0) {
if (strcmp(prot->name, "regex") != 0) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"regex_patterns setting is only applicable to `regex' probe. Ignoring setting.\n",
prot->name, prot->host, prot->port);
}
#ifndef ENABLE_REGEX
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Uses regex_patterns, but libpcre2 support was not compiled in. Ignoring setting.\n",
prot->name, prot->host, prot->port);
#endif
}
}
/* If user specified proxyprotocol but it is not compiled in, it should not be
* ignored as the resulting configuration will most likely not work. In that
* case, die. */
static void config_sanity_proxyprotocol(const struct sslhcfg_protocols_item* prot)
{
#ifndef HAVE_PROXYPROTOCOL
if (prot->proxyprotocol_is_present) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Uses proxyprotocol, but libproxyprotocol support was not compiled in.\n",
prot->name, prot->host, prot->port);
exit(1);
}
#endif
}
static void config_sanity_listen_proxyprotocol(const struct sslhcfg_listen_item* endpoint)
{
#ifndef HAVE_PROXYPROTOCOL
if (endpoint->proxyprotocol) {
print_message(msg_config_error, "listen on host: \"%s\"; port: \"%s\": "
"Uses proxyprotocol, but libproxyprotocol support was not compiled in.\n",
endpoint->host, endpoint->port);
exit(1);
}
#endif
}
/* If user specifies libwrap setting but it is not compiled in, it should
* result in a working configuration, but not what they expected, so warn */
static void config_sanity_libwrap(const struct sslhcfg_protocols_item* prot)
{
#ifndef HAVE_LIBWRAP
if (prot->service_is_present) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"WARNING: `service' specified but libwrap not compiled in. Ignoring setting.\n",
prot->name, prot->host, prot->port);
}
#endif
}
/* sun_path has a fixed, compile-time size (typically 108 chars): a configured
* unix socket path that does not fit can never work, so reject it once at
* startup rather than at bind()/connect() time */
static void config_sanity_unix_path(const char* host)
{
size_t path_max = sizeof(((struct sockaddr_un*)0)->sun_path);
if (host && strlen(host) >= path_max) {
print_message(msg_config_error,
"unix socket path too long (%zd, max %zd): %s\n",
strlen(host), path_max - 1, host);
exit(4);
}
}
void config_sanity_check(struct sslhcfg_item* cfg)
{
@@ -306,12 +233,6 @@ void config_sanity_check(struct sslhcfg_item* cfg)
}
#endif
for (i = 0; i < cfg->listen_len; i++) {
config_sanity_listen_proxyprotocol(&cfg->listen[i]);
if (cfg->listen[i].is_unix)
config_sanity_unix_path(cfg->listen[i].host);
}
for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
if (cfg->protocols[i].sni_hostnames_len) {
@@ -328,12 +249,6 @@ void config_sanity_check(struct sslhcfg_item* cfg)
}
}
config_sanity_regex(&cfg->protocols[i]);
config_sanity_proxyprotocol(&cfg->protocols[i]);
config_sanity_libwrap(&cfg->protocols[i]);
if (cfg->protocols[i].is_unix)
config_sanity_unix_path(cfg->protocols[i].host);
if (cfg->protocols[i].is_udp) {
if (cfg->protocols[i].tfo_ok) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
@@ -369,41 +284,6 @@ void close_std(void)
}
}
static void print_version(void)
{
printf("%s %s\n", server_type, VERSION);
#ifdef ENABLE_SANITIZER
printf("ENABLE_SANITIZER\n");
#endif
#ifdef ENABLE_REGEX
printf("ENABLE_REGEX\n");
#endif
#ifdef LIBCONFIG
printf("LIBCONFIG\n");
#endif
#ifdef SYSTEMD
printf("SYSTEMD\n");
#endif
#ifdef COV_TEST
printf("COV_TEST\n");
#endif
#ifdef HAVE_LIBWRAP
printf("HAVE_LIBWRAP\n");
#endif
#ifdef HAVE_LANDLOCK
printf("HAVE_LANDLOCK\n");
#endif
#ifdef HAVE_PROXYPROTOCOL
printf("HAVE_PROXYPROTOCOL\n");
#endif
#ifdef HAVE_LIBCAP
printf("HAVE_LIBCAP\n");
#endif
#ifdef HAVE_LIBBSD
printf("HAVE_LIBBSD\n");
#endif
}
int main(int argc, char *argv[], char* envp[])
{
int res, num_addr_listen;
@@ -419,7 +299,7 @@ int main(int argc, char *argv[], char* envp[])
config_finish(&cfg);
if (cfg.version) {
print_version();
printf("%s %s\n", server_type, VERSION);
exit(0);
}
@@ -428,7 +308,10 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd)
{
main_inetd(); /* Does not return */
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0);
exit(0);
}
printsettings();
@@ -464,6 +347,8 @@ int main(int argc, char *argv[], char* envp[])
/* Open log file for writing */
setup_logfile();
ech_init(listen_sockets, num_addr_listen);
if (cfg.user || cfg.chroot)
drop_privileges(cfg.user, cfg.chroot);
setup_landlock();
+2 -2
View File
@@ -204,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
int i, res;
setup_sigalrm();
loop_init(&fd_info, listen_sockets, num_addr_listen);
loop_init(&fd_info, num_addr_listen);
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
@@ -292,7 +292,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
}
void main_inetd(void) {
void start_shoveler(int listen_socket) {
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
exit(1);
}
+3 -8
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<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--wireguard> I<target address for WireGuard>] [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<-C> I<chroot>] [B<-P> I<pidfile>] [B<-v> I<n>] [-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<--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<-C> I<chroot>] [B<-P> I<pidfile>] [B<-v> I<n>] [-i] [-V] [-f] [-n]
=head1 DESCRIPTION
@@ -87,7 +87,7 @@ configuration file's settings.
=item B<-t> I<num>, B<--timeout> I<num>
Timeout before forwarding the connection to the timeout
protocol (which should usually be SSH). Default is 5s.
protocol (which should usually be SSH). Default is 2s.
=item B<--on-timeout> I<protocol name>
@@ -104,7 +104,7 @@ Makes B<sslh> behave as a transparent proxy, i.e. the
receiving service sees the original client's IP address.
This works on Linux only and involves B<iproute2> settings.
In some cases also B<iptables/nftables> settings are needed.
Refer to the README or L<https://github.com/yrutschle/sslh/blob/master/doc/simple_transparent_proxy.md>
Refer to the README or L<https://github.com/ftasnetamot/sslh/blob/2014-08-16--close-filehandles-with-detach/doc/simple_transparent_proxy.md>
for more information.
=item B<-p> I<listening address>, B<--listen> I<listening address>
@@ -136,11 +136,6 @@ If you wish to accept SSLv2, use B<--anyprot> instead.
Interface and port on which to forward SSH connections,
typically I<localhost:22>.
=item B<--wireguard> I<target address>
Interface and port on which to forward WireGuard connections,
typically I<localhost:51820>.
=item B<--openvpn> I<target address>
Interface and port on which to forward OpenVPN connections,
+3
View File
@@ -18,6 +18,7 @@ includes: (
"probe.h",
" <sys/types.h>",
" <sys/socket.h>",
" <wolfssl/ssl.h>",
" <netdb.h>"
);
@@ -85,6 +86,8 @@ config: {
description: "Facility to syslog to"; },
{ name: "logfile"; type: "string"; optional: true;
description: "Log messages to a file" },
{ name: "ech_conf_sink"; type: "string"; optional: true;
description: "File where to write ECH configurations" },
{ name: "on-timeout"; type: "string"; default: "ssh";
description: "Target to connect to when timing out"; },
+3 -3
View File
@@ -307,12 +307,12 @@ int main(int argc, char *argv[]) {
err_log = stderr;
}
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
}
if (systemd_invoked) {
fclose(err_log);
}
if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
}
return r < 0 ? -1 : 0;
} else {
printf("This program takes three or no arguments.\n");
+8 -70
View File
@@ -10,7 +10,6 @@
# ./run # run all tests
# ./run -l # list all tests
# ./run 1 3 5 # run specified tests
# ./run --binary sslh-ev # run only one binary
use strict;
use IO::Socket::INET6;
@@ -18,13 +17,9 @@ use Test::More qw/no_plan/;
use Conf::Libconfig 1.0.3;
use Getopt::Long;
# --cover: request test coverage analysis (not implemented)
# --binary: specify which binary to run (default: all)
# --list: list all available tests (do not run any)
my ($coverage, $list_tests, $param_binary);
my ($coverage, $list_tests);
GetOptions(
'cover' => \$coverage,
'binary=s' => \$param_binary,
'list' => \$list_tests,
);
@@ -197,8 +192,7 @@ sub run_ending_test
# Runs one test for one probe. Start echosrv's if required.
# Connect to port specified in the test, otherwise to the
# first port in the sslh configuration; to host specified in
# the test, otherwise to localhost
# first port in the sslh configuration.
# run sslh, connect, write the test pattern, read the result,
# check it connected to the right echosrv, check the data
# was transfered ok.
@@ -212,10 +206,9 @@ sub run_test_probe
my $expected = $test->{expected};
my $sslh_host = $test->{host} // "localhost";
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
print "test_probe [$expected] $sslh_host:$sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "$sslh_host:$sslh_port");
print "test_probe [$expected] $sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "t: $!\n" unless $cnx;
return unless $cnx;
@@ -323,7 +316,6 @@ my @tests = (
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
},
# check socks5 does not
{
desc => "Server-side ProxyProtocol not active",
@@ -333,55 +325,6 @@ my @tests = (
expected => "socks5",
},
# Check proxyprotocol from IPv4 to IPv4 works.
{
desc => "Server-side IPv4 to IPv4 ProxyProtocol active",
host => "ip4-localhost",
run => \&run_test_probe,
cfg => "test_pp3.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# Check pp with IPv6 to IPv6 works.
{
desc => "Server-side IPv6 to IPv6 ProxyProtocol active",
host => "ip6-localhost",
run => \&run_test_probe,
cfg => "test_pp4.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# Check pp with IPv4 to IPv6 works.
{
desc => "Server-side IPv4 to IPv6 ProxyProtocol active",
host => "ip4-localhost",
run => \&run_test_probe,
cfg => "test_pp5.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# Check pp with IPv6 to IPv4 works.
{
desc => "Server-side IPv6 to IPv4 ProxyProtocol active",
host => "ip6-localhost",
run => \&run_test_probe,
cfg => "test_pp5.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# test_pp2.cfg has proxyprotocol on port 8080
{
desc => "Client-side ProxyProtocol active",
@@ -445,15 +388,10 @@ if ($list_tests) {
################################################################################
# Run selected tests
my @binaries = qw/sslh-fork sslh-select sslh-ev/;
@binaries = $param_binary if defined $param_binary;
foreach my $binary (@binaries) {
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $code = ($test->{run})->($binary, $test);
}
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $binary = 'sslh-ev';
my $code = ($test->{run})->($binary, $test);
}
stop_echosrv();
+4 -4
View File
@@ -29,15 +29,15 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
# Options:
listen:
(
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
{ host: "localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip4-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip4-localhost"; port: "9099"; }
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
-44
View File
@@ -1,44 +0,0 @@
# Testing for server-side ProxyProtocol, IPv4 to IPv4
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 1; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
-44
View File
@@ -1,44 +0,0 @@
# Testing for server-side ProxyProtocol, IPv6 to IPv6
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 1; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "ip6-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip6-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
);
on_timeout: "ssh";
-44
View File
@@ -1,44 +0,0 @@
# Testing for server-side ProxyProtocol, IPv4 to IPv6
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 1; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "ip4-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip6-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
);
on_timeout: "ssh";
-44
View File
@@ -1,44 +0,0 @@
# Testing for server-side ProxyProtocol, IPv6 to IPv4
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 1; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "ip6-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
);
on_timeout: "ssh";
+7 -39
View File
@@ -30,16 +30,14 @@
/* Removes cnx from probing list */
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
{
if (!gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing)) {
fd_info->num_probing--;
}
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
fd_info->num_probing--;
}
static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
{
if (!gap_set(fd_info->probing_list, fd_info->num_probing, cnx)) {
fd_info->num_probing++;
}
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
fd_info->num_probing++;
}
/* shovels data from active fd to the other
@@ -55,6 +53,7 @@ static void shovel(struct connection *cnx, int active_fd, struct loop_info* fd_i
print_message(msg_fd, "activity on fd%d\n", read_q->fd);
switch(fd2fd(write_q, read_q)) {
case -1:
case FD_CNXCLOSED:
tidy_connection(cnx, fd_info);
break;
@@ -297,37 +296,6 @@ static void connect_proxy(struct connection *cnx)
}
/* This ensures the kernel will close the listening socket when the
* parent process closes; otherwise, the listening socket is kept
* around by the inactive file descriptor in the child */
void close_listen_endpoints(struct loop_info* loop)
{
struct listen_endpoint* listen_sockets = loop->listen_sockets;
watchers* w = loop->watchers;
for (int i = 0; i < loop->num_addr_listen; i++) {
int fd = listen_sockets[i].socketfd;
watchers_del_read(w, fd);
watchers_del_write(w, fd);
close(fd);
}
}
/* Close all connections except specified.
* This saves file descriptors and allocated memory when forking */
void tidy_other_connections(struct loop_info* fd_info, struct connection* keep_cnx)
{
struct connection* cnx;
cnx_collection* cnx_coll = fd_info->collection;
for (int i = 0; i < collection_max_fd(cnx_coll); i++) {
cnx = collection_get_cnx_from_fd(cnx_coll, i);
if (cnx && (cnx != keep_cnx))
tidy_connection(cnx, fd_info);
}
}
/* Forks a shoveler process for one protocol */
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
pid_t pid;
@@ -339,8 +307,8 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
switch (pid = fork()) {
case 0: /* child */
close_listen_endpoints(fd_info);
tidy_other_connections(fd_info, cnx);
/* TODO: close all file descriptors except 2 */
/* free(cnx); */
connect_proxy(cnx);
exit(0);
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
+4
View File
@@ -46,6 +46,10 @@ int probe_client_protocol(struct connection *cnx)
if (n > 0) {
defer_write(&cnx->q[1], buffer, n);
print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size);
size_t pp_len = 0;
if (cnx->endpoint->endpoint_cfg->proxyprotocol) {
pp_len = pp_header_len(cnx->q[1].begin_deferred_data,
+7 -1
View File
@@ -61,6 +61,7 @@ static int parse_server_name_extension(const struct TLSProtocol *, const char *,
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
static int has_match(const char**, size_t, 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
*
@@ -81,6 +82,11 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
if (data_len < TLS_HEADER_LEN)
return TLS_ELENGTH;
/* If if it is an ECH, decrypt and match */
ech_match_sni(data, data_len);
tls_content_type = data[0];
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
@@ -277,7 +283,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
for (i = 0; i < list_len; i++) {
item = &list[i];
print_message(msg_probe_info, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
if(!fnmatch(*item, name_nullterminated, 0)) {
free(name_nullterminated);
return 1;
+1
View File
@@ -44,5 +44,6 @@ struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, s
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
#define TLS_EPROTOCOL -5 /* Protocol error */
void ech_match_sni(const char* buffer_in, size_t buffer_len);
#endif
+284
View File
@@ -0,0 +1,284 @@
/*
# tls_ech.c: support for RFC9849 TLS Encrypted Client Hello (ECH)
#
# Copyright (C) 2026 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 WOLFSSL_USE_OPTION_H
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/wolfcrypt/random.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "config.h"
#include "log.h"
#include "sslh-conf.h"
#include "tls.h"
WOLFSSL_CTX* wolfssl_ctx;
// Hard-coded server certificate (PEM format)
// from snakeoil -- these serves no purpose beyond initialising the context
static const char* SERVER_CERT_PEM =
"-----BEGIN CERTIFICATE-----\n"
"MIICyjCCAbKgAwIBAgIUZrCzD4tEIu1r9EhkOn96raiupe4wDQYJKoZIhvcNAQEL\n"
"BQAwDzENMAsGA1UEAwwEc2FhejAeFw0yMTA1MjgyMDM0MjRaFw0zMTA1MjYyMDM0\n"
"MjRaMA8xDTALBgNVBAMMBHNhYXowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
"AoIBAQC5s1UhjcmsrRnSdJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6J\n"
"MAL1MbPtX0pfR+yBILWUdn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoe\n"
"gD9FE3lE895GIhEv7Jd9bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bK\n"
"fJzuOxi7oIeINLEiR0Z4hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2\n"
"QFMByfkEmAGp9Br+2tquRUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd\n"
"6G6TLenPbQPEaac5UgGZ7rJvL9idAgMBAAGjHjAcMAkGA1UdEwQCMAAwDwYDVR0R\n"
"BAgwBoIEc2FhejANBgkqhkiG9w0BAQsFAAOCAQEAZPasi1Hu7q4S9wJsbIzcGj7m\n"
"ZCAxNdm3AINeV3mxfhToeqtXSoa2sujCEgwCuiYi0OE8wmseLO8e1xIsRc/NEpOl\n"
"h9u5OupV2Xbf7nuKzPl31YQ7kv+90mnixizf6CFpHXAdU3WEZ33+fMtS+lV/ZBYo\n"
"Nh9GGNJ7Grv5KIKu/b2cR403pRF1hGrjWABrIDZO3ZCtNgRp/My1Qr+/04rTs8VF\n"
"WyDshOe0u6Xo//M3sCMbx5+62BopCMmAu2TxiPew0OTM8eDa3IBbc2kLGxp8c4J+\n"
"EYpAKTZbkhs/6SleNMC4BOEmZQ1tk6D0N0u4Y0b1IKEjgx7HqwZ6mIdmbU1lZQ==\n"
"-----END CERTIFICATE-----\n";
static const char* SERVER_KEY_PEM =
"-----BEGIN PRIVATE KEY-----\n"
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5s1UhjcmsrRnS\n"
"dJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6JMAL1MbPtX0pfR+yBILWU\n"
"dn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoegD9FE3lE895GIhEv7Jd9\n"
"bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bKfJzuOxi7oIeINLEiR0Z4\n"
"hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2QFMByfkEmAGp9Br+2tqu\n"
"RUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd6G6TLenPbQPEaac5UgGZ\n"
"7rJvL9idAgMBAAECggEAbXNQctViD596/7rryhze+KztsI/UNsVU6uH3T+LN1XxU\n"
"jurnRVNFV4JU3DDrHV9tIDQw3pXCxJzlyRpKHDWGNgTZU2fI6sZGuX+4w1Apd8ss\n"
"3Y5mEkGYyaslF/MY13rR2B0wXf/IR4nYA/ssiaBjLcGqHHnQOVseHNgtdXFPXW9n\n"
"5IXJ2PSeaFW0Z13vbTQm9Cx2kCBKL4J3TymhkuSW3DUHxmlDfWUK14lkyXCZIZ3i\n"
"TbXQAXhwTrz3wqbG++iEP4MZM0Ka7/+WXqrsxA/yCirb+HnMewxwBKzGvkym2I2H\n"
"3BkjDn9exDgTePD7i0w22UYCF5uezDB9YOiPVZ634QKBgQDyKIgxD7XMMJezeg0Y\n"
"PaG+Eig/hE27mm2pyTQSd6MMZ1HpbDUpLQ/GO3Npy+bKS0/5eefNT4PWVcmpRL4W\n"
"blNIfixkcZq1a6wax9Nhk89jXZdhBinO8gfXFOiANFDS7B4fEXeTrfhmuryqUw9m\n"
"L6GJ0jk/H6fs6ads15y+O7A6tQKBgQDEUKlZg4qXxNMNKq7l+v7R6IH8VrNLt0I/\n"
"f4fhpW3IhmPJCRuOFOS0UpDPYjTeu14i3EFcpe0D+DVoQiggBTAh3I/qMHvRko5g\n"
"DT83YHp2/ec0AY0ocwnLwye224t6GF7S8R8vhG1FkIBk6DTJgWFZLDtY+dekZugz\n"
"IHvgtxSPSQKBgHWD7fPBJ/xbaIMUq94jqqZOsXGBhyePncBTgA2mOV3/leStOm8t\n"
"CwasOyoQZYOuYLU0z6T2/Ye6Qg7+6TCBgbEgafKknuuDwRWN+6rSzEXwWVIgZ5Gi\n"
"KuPZparxuHdjVorFMz5borxXys7tV//DBaWYe2eCuT9jdHiBefNni/IxAoGAWHTn\n"
"+deVqVEcsAAdkq8IBtk3SNZgL6vBhA8Y0QnTb9luOPWLnve3HbFeYrOjkwDmJ9sK\n"
"8I4rP/ClT/cPUW5FA9z4U9PI1uOsl4cghvlH9Tnu5bYVPranIVMsH+7I6Bj3ESFo\n"
"peaLvh6gW9dtgaZ0kNOnYi4hhoh/9Bmc9+JGt1ECgYAbGSQLa21L4Hx3NvMparVQ\n"
"bGjN26igvqKmhBaKrH8YolOlhEw/mCwTlg9zI7jrhdO0VClPrB8IVBoL+6HvrT21\n"
"LJM31J3ddqME9/5fQgKMQsD60kRDD6iV/iK2S6TOUjxJn1f6MuLmGSOHZYcbdBPZ\n"
"DyBKgbz/xGV6katjXvg74A==\n"
"-----END PRIVATE KEY-----\n";
#if 1
/* Will be provided by wolfSSL */
int wolfSSL_GetEchClientHelloInner(
WOLFSSL* ssl, /* Connected to an existing context with the ECH configs. Stores hpke state */
const byte* buffer_in, /* The full ClientHelloOuter */
word32 buffer_len, /* How many bytes in the buffer */
byte* buffer_out,
word32* buffer_out_len
)
{
char* inner = "some clienthelloinner";
*buffer_out_len = strlen(inner);
memcpy(buffer_out, inner, *buffer_out_len);
print_message(msg_fd, "GetEchClientHelloInner set\n");
return 0;
}
#endif
void ech_get_client_hello_inner(WOLFSSL_CTX* ctx, const byte* data, size_t datalen)
{
WOLFSSL* ssl = NULL;
printf("wolfSSL_new(%p)\n", ctx);
ssl = wolfSSL_new(ctx);
if (ssl == NULL) {
fprintf(stderr, "Failed to create WOLFSSL object\n");
return;
}
// Disable certificate verification on this connection
wolfSSL_set_verify(ssl, WOLFSSL_VERIFY_NONE, NULL);
byte client_hello_inner[1024];
word32 ch_len = sizeof(client_hello_inner);;
int res = wolfSSL_GetEchClientHelloInner(
ssl,
data, /* The full ClientHelloOuter */
datalen, /* How many bytes in the buffer */
(byte*)&client_hello_inner,
&ch_len
);
client_hello_inner[ch_len] = 0;
if (!res) {
print_message(msg_fd, "GetEchClientHelloInner: Innerlen: %d\n", ch_len);
hexdump(msg_fd, (char*)client_hello_inner, ch_len);
print_message(msg_fd, "/GetEchClientHelloInner\n");
}
byte sni_data[1024];
unsigned int sni_len = sizeof(sni_data);
memset(sni_data, 'A', sni_len);
printf("SNI_GetFromBuffer\n");
wolfSSL_SNI_GetFromBuffer(data, datalen, 0, (byte*)&sni_data, &sni_len);
sni_data[sni_len] = 0;
printf("sni_len = %d\n", sni_len);
printf("sni: %s\n", sni_data);
}
/* See if the context matches */
void ech_match_sni(const char* buffer_in, size_t buffer_len)
{
ech_get_client_hello_inner(wolfssl_ctx, (byte*)buffer_in, buffer_len);
}
/* Appends a configuration to the DNS configuration file */
static void append_conf(const char* sni, const char* ech)
{
FILE* f = fopen(cfg.ech_conf_sink, "a");
if (!f) {
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
return;
}
int res = fprintf(f, "%s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\"\n", sni, ech);
printf("append: %s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\" : %d\n", sni, ech, res);
fclose(f);
}
/* Erases the configuration file */
static void reset_conf(void)
{
FILE* f = fopen(cfg.ech_conf_sink, "w");
if (!f) {
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
return;
}
printf("erased and opend %s\n", cfg.ech_conf_sink);
}
/* Generate a keypair for a config; later this will be moved to a separate
* process, and sslh will just read it */
static void ech_genkey(WOLFSSL_CTX* ctx, const char* sni)
{
int ret;
printf("ech_genkey for ctx %p\n", ctx);
print_message(msg_config, "Generating ECH config for outer SNI: %s\n", sni);
ret = wolfSSL_CTX_GenerateEchConfig(ctx, sni, 0, 0, 0);
if (ret != WOLFSSL_SUCCESS) {
print_message(msg_system_error, "Failed to generate ECH config: %d\n", ret);
exit(1);
}
byte echConfig[512];
word32 echConfigLen = sizeof(echConfig);
ret = wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen);
if (ret != WOLFSSL_SUCCESS) {
print_message(msg_system_error, "Failed to get ECH config: %d\n", ret);
exit(1);
}
char out[512];
word32 outLen = sizeof(out);
if (Base64_Encode_NoNl(echConfig, echConfigLen, (byte*)out, &outLen) != 0) {
print_message(msg_system_error, "Failed to encode to base64: %d\n", ret);
exit(1);
}
append_conf(sni, out);
}
/* For each public names, create a keypair and configuration.
* (Later, this will load the configurations from some place else */
static void ech_create_configs(WOLFSSL_CTX** ctx)
{
int ret;
reset_conf();
*ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
if (!*ctx) {
print_message(msg_system_error, "Failed to create WOLFSSL_CTX\n");
exit(1);
}
// Load certificate from memory
ret = wolfSSL_CTX_use_certificate_buffer(*ctx,
(const unsigned char*)SERVER_CERT_PEM,
strlen(SERVER_CERT_PEM),
WOLFSSL_FILETYPE_PEM);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Failed to load certificate: %d\n", ret);
fprintf(stderr, "Please replace SERVER_CERT_PEM with a valid certificate\n");
wolfSSL_CTX_free(*ctx);
exit(1);
}
// Load private key from memory
ret = wolfSSL_CTX_use_PrivateKey_buffer(*ctx,
(const unsigned char*)SERVER_KEY_PEM,
strlen(SERVER_KEY_PEM),
WOLFSSL_FILETYPE_PEM);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Failed to load private key: %d\n", ret);
fprintf(stderr, "Please replace SERVER_KEY_PEM with a valid private key\n");
wolfSSL_CTX_free(*ctx);
exit(1);
}
printf("Certificate and key loaded successfully\n");
ech_genkey(*ctx, cfg.listen[0].host); /* For now this only works on the first host */
}
/* Initialises the ECH subsystem: create wolfSSL context, create ECH configs,
* etc */
void ech_init()
{
wolfSSL_Init();
ech_create_configs(&wolfssl_ctx);
}
+10 -8
View File
@@ -239,11 +239,12 @@ static int nonblocking_socket(struct sslhcfg_protocols_item* proto)
}
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
CHECK_RES_RETURN(out, "udp:socket:socket", -1);
res = set_nonblock(out);
CHECK_RES_RETURN(out, "udp:socket:nonblock", -1);
if (res == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, "udp:socket:nonblock", errno, strerror(errno));
close(out);
return -1;
}
return out;
}
@@ -272,9 +273,10 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
addrlen = sizeof(src_addr);
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
if ((len == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return NULL;
CHECK_RES_DIE(len, "udp_listener/c2s/recvfrom");
if (len < 0) {
perror("recvfrom");
return NULL;
}
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
addrinfo.ai_addr = (struct sockaddr*) &src_addr;
addrinfo.ai_addrlen = addrlen;
@@ -335,7 +337,7 @@ void udp_s2c_forward(struct connection* cnx)
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
CHECK_RES_DIE(res, "udp_listener/s2c/recvfrom");
CHECK_RES_DIE(res, "udp_listener/recvfrom");
res = sendto(cnx->local_endpoint, data, res, 0,
(struct sockaddr*)&cnx->client_addr, cnx->addrlen);
mark_active(cnx);