Compare commits

...
47 Commits
Author SHA1 Message Date
Stefan Meinecke 39d164f5a8 hashtest: fix build so the suite actually runs
htest.c was missing the "typedef struct hash hash" that hash.h
requires, and the Makefile linked the top-level ../hash.o which is
built without -DHASH_DEBUG, leaving hash_dump/hash_item_print
undefined. Build a local hash.o with the flag instead. All 16 tests
now compile and pass.
2026-10-18 22:53:40 +02:00
Stefan Meinecke bb89714216 misc hardening: connection drop, NULL deref, OOB writes, dead option
- shovel(): fd2fd() returns FD_NODATA (-1) when a spurious wakeup
  meets an empty socket (EAGAIN), but the switch treated -1 like
  FD_CNXCLOSED and tore down a healthy connection. Only FD_CNXCLOSED
  closes now.

- decrease_forked_connection(): hash_find() can return NULL (stale
  SIGCHLD after a fork-limit rejection); dereferencing it crashed.
  Guard and log instead.

- hexdump(): sprintf() into a 76-byte stack buffer with a "%06x"
  offset that needs 12 chars once the dump passes 16 MB. Enlarge the
  buffer and bound all writes with snprintf().

- printsettings(): sprintf() of config-supplied host strings into a
  fixed buffer; use snprintf().

- echosrv: recvfrom() into data+prefix_len with size
  sizeof(data)-prefix_len lets the terminating NUL write land one
  byte past the stack buffer (verified with ASan at udp_echo, and a
  >64KB prefix overflows the memcpy). Bound the prefix, reserve one
  byte for the terminator, and skip the echo on recvfrom() error
  instead of writing data[prefix_len-1].

- msg_int_error used verbose_system_error, leaving the documented
  --verbose-int-error option dead. Point it at its own setting.

- Remove "echoѕrv-conf.h" (with Cyrillic ѕ, U+0455): a stale 2020
  sslh-conf.h copy committed under a filename that is visually
  identical to echosrv-conf.h but referenced by nothing.
2026-10-18 22:53:40 +02:00
Stefan Meinecke b9d9f9112f sslh-fork: decrement num_connections when fork() fails
num_connections was incremented before fork(), but the fork-failure
path only logged and closed the socket - no child exists to decrement
the counter, so each failed fork permanently inflates it. Under fork
failure conditions (process limits, memory pressure) the listener
eventually reports max_connections reached and refuses all connections
until restart.
2026-10-18 22:26:58 +02:00
Stefan Meinecke 91736dbd17 sslh-fork: apply the active listener's configuration, not listener 0's
tcp_listener() accepts on endpoint[active_endpoint] but then used
endpoint-> - the base of the array, i.e. endpoint[0] - for the
connection counter, the max_connections limit, SIGCHLD accounting and
start_shoveler(). With several listen addresses, every listener
process therefore:

- enforces listener 0's max_connections (verified: a listener with
  limit 100 drops its second connection when listener 0 has limit 1),
- and passes listener 0's endpoint_cfg to children, so e.g. the
  proxyprotocol flag of listener 0 decides whether a PROXY header is
  expected on sockets accepted by other listeners.

Use a pointer to the active endpoint throughout the loop.
2026-10-18 22:26:58 +02:00
Stefan Meinecke bbc4f91f68 systemd: give socket-activated endpoints a default endpoint_cfg
Sockets passed by systemd socket activation never get endpoint_cfg set,
but inc_listen_connections() dereferences it unconditionally at accept()
time. The first connection to a socket-activated unit therefore crashed
the daemon with a NULL pointer dereference, and probe_client_protocol()
dereferences it as well for the proxyprotocol check.

Verified with a stub sd_listen_fds(): sslh-select segfaults (SIGSEGV) on
the first connection to an activated socket before the fix, and forwards
the connection normally after it.

Assign a static zeroed sslhcfg_listen_item so these sockets behave as if
configured with no max_connections limit and no proxyprotocol.
2026-10-18 22:23:01 +02:00
Stefan Meinecke e20f2381c3 move unix path length checks to config_sanity_check()
cfg->host is static configuration, so an over-long path is a config
error: validate it once at startup in config_sanity_check() for both
is_unix listen endpoints and is_unix protocol targets, instead of at
bind()/connect() time. This also fails fast for protocol targets,
where the previous runtime check only dropped each connection instead
of reporting the bad config.

The memset()/bounded strncpy() calls stay in place: they are what make
sun_path termination structurally safe regardless of the input length.
2026-10-18 22:04:22 +02:00
Stefan Meinecke 7afa284efc unix sockets: terminate sun_path, reject over-long paths early
Both start_listen_unix() and connect_unix() copied the configured
path into sun_path without checking its length and without zeroing
the sockaddr first. A path of 107+ bytes fills sun_path completely,
leaving it unterminated; bind()/connect() then read past the array
into uninitialised stack bytes (verified: garbage bytes visible in
the sockaddr passed to bind).

Fail listen startup with a clear message when the path cannot fit -
before unlink()ing whatever file currently sits at that path - and
make connect_unix() return -1 so the connection is dropped cleanly.
Zero both sockaddr structures, check the socket() return value in
connect_unix(), label unix listen endpoints AF_UNIX instead of
AF_INET, and route the "binding" message through print_message() so
it respects verbosity flags like everything else.
2026-10-18 22:04:22 +02:00
Yves Rutschle d51da940da remove leftover debug messages 2026-10-18 17:34:21 +02:00
Yves Rutschle e3020e8441 Merge branch 'master' of github.com:yrutschle/sslh 2026-10-18 17:30:41 +02:00
Yves Rutschle cb894df702 sslh-ev: add timeout management for incoming connections (should help with #539) 2026-10-18 17:30:37 +02:00
Stefan Meinecke 7cc9dc90be probes: fix msrdp length truncation, teamspeak match, unaligned reads
- is_msrdp_protocol() stored the 16-bit TPKT length in a char, so a
  packet claiming length 263 (0x0107) but carrying only 7 bytes
  compared 7 == 7 and matched - any sufficiently short packet whose
  declared length low byte equals its size is accepted as RDP.
  Keep the declared length in a uint16_t. Verified with a harness:
  {0x03 0x00 0x01 0x07 ...} no longer matches.

- is_teamspeak_protocol() used strncmp(p, "TS3INIT1", len): for the
  first datagram of a real TS3 client (len > 8) this compares the
  string literal's terminating NUL against packet byte 8, which is a
  version field, so it could never match real traffic (and reads
  past the literal for len > 9). Use a fixed 8-byte memcmp prefix.

- is_openvpn_protocol() and is_wireguard_protocol() dereferenced the
  packet buffer through uint16_t*/uint32_t* casts; the buffer is not
  aligned, which is UB and traps on strict-alignment targets (ARM).
  Use memcpy() instead, as already done elsewhere in this file.
2026-10-18 11:44:36 +02:00
Yves Rutschle f1b4fa1744 robustness: check return values before changing num_probing 2026-10-17 22:33:52 +02:00
Yves Rutschle ca5e7fe6f3 check socket() return value 2026-10-07 15:20:21 +02:00
Yves Rutschle bebc109b94 print_message() restores errno (fix #538) 2026-10-04 11:07:55 +02:00
Iain 1c98792a55 fix transparent proxy link and timeout default in man page 2026-09-30 18:32:13 +02:00
Yves Rutschle 1e1cde863b Merge branch 'TigerP-improve-configure-ac' 2026-09-19 18:25:18 +02:00
Yves Rutschle 224efd36c3 Merge branch 'improve-configure-ac' of github.com:TigerP/sslh into TigerP-improve-configure-ac 2026-09-19 18:24:16 +02:00
Aniket 1df05999b7 Remove compose version 2026-09-19 18:20:34 +02:00
Kai Bolay 0d696eb007 fix typo 2026-09-19 18:19:35 +02:00
Yves Rutschle 03067e9ab7 respect hosts_ctl() prototype 2026-08-02 09:03:51 +02:00
Yves Rutschle eb976475be declare pp_log_connection() as inline (avoids warning with proxyprotocol not present) 2026-07-12 18:10:34 +02:00
Yves Rutschle 7eabafc6e2 added updated QUIC example 2026-05-31 09:58:57 +02:00
Yves Rutschle 5f9ff68f1c mention Wireguard probe in documentation (fix #535) 2026-05-31 09:51:06 +02:00
Yves Rutschle 4752c8647d Merge branch 'master' of github.com:yrutschle/sslh 2026-05-08 22:52:40 +02:00
Yves Rutschle a8352c137c udp_c2s_forward: silently ignor EAGAIN/EWOULDBLOCK on recvfrom (fix #534) 2026-05-08 22:52:26 +02:00
keanehatescoding 6c0dc269c7 fix: close err_log after writing success message in main()
err_log was fclose()'d before the "Successfully generated all targets"
fprintf(), resulting in a write-after-close that corrupted glibc's
internal stdio buffers and triggered an abort() via SIGABRT on every
systemd-invoked run regardless of whether /etc/sslh/ exists.

Fix by moving the success fprintf() above the fclose() call.
2026-05-08 22:42:13 +02:00
Tiger!P 0bc0fb62b7 Improve configure.ac to work with newer autoconf versions 2026-04-18 22:25:53 +02:00
Yves Rutschle dc70cc0d3b verbose_packet now dumps UDP packets as well (fix #527) 2026-03-15 22:12:00 +01:00
Yves Rutschle d39388d1ed prepare v2.3.1 2026-03-05 19:14:57 +01:00
Yves Rutschle 70a776fb9a -V now outputs compile-time configuration (fix #519) 2026-03-04 22:35:56 +01:00
Yves Rutschle 9fadd60eed sanity check that libwrap is present when using service option 2026-03-03 22:42:52 +01:00
Yves Rutschle 83c9465c37 sanity check that proxyprotocol is compiled in when used by listening endpoints 2026-03-03 22:34:36 +01:00
Yves Rutschle 1137779b9c sanity check that libproxyprocol is compiled in when option is used 2026-03-03 22:29:01 +01:00
Yves Rutschle 4d3aa6c6e0 sanity checks that regex options are only used with the right probe and with libpcre2 support compiled in 2026-03-03 22:05:01 +01:00
Yves Rutschle 579baae363 fix syntax error in documentation example 2026-02-28 21:42:01 +01:00
Yves Rutschle e2d01a7440 close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
Yves Rutschle ecc76071c1 downgrade SNI matching logs from error to info to reduce log verboseness (Fix #520) 2026-02-08 09:50:45 +01:00
Yves Rutschle e5d7cd0575 revert some useless changes to really fix #515 2026-02-06 22:08:59 +01:00
Yves Rutschle d288125ff3 test combinations of ipv4 <-> ipv6 over proxyprotocol 2026-02-06 22:05:27 +01:00
Yves Rutschle 2cc1e1f1d9 add CAP_NET_RAW in systemd configuration to support transparent proxying 2026-02-03 21:00:40 +01:00
yrutschle 77dc76a0f2 Change IPv4 to IPv6 addresses when using proxyprotocol and protocols are different (Fix #515) 2026-02-01 18:02:03 +01:00
4O4-Not-F0und 86188cdd28 build sslh with libproxyprotocol 2025-12-20 17:23:10 +01:00
Yves Rutschle c29081f71d Merge branch 'master' of github.com:yrutschle/sslh 2025-11-14 14:54:17 +01:00
Yves Rutschle 8d58164fdd Libwrap requires access to some more files when using landlock (fix #450 again) 2025-11-14 14:53:56 +01:00
yrutschle 894eacfb42 Fix sslh-fork segmentation fault (Fix #508) 2025-10-30 08:22:48 +01:00
benok 6310e6fc9c Add missing trailing "\n" to log output 2025-10-29 12:28:01 +01:00
Yves Rutschle 0dd580f45b move symbol definition for Cygwin (fix #506) 2025-09-24 14:48:16 +02:00
41 changed files with 703 additions and 260 deletions

No files matched your search

+41
View File
@@ -1,3 +1,44 @@
vNEXT:
Silently ignore EAGAIN/EWOULDBLOCK to avoid
spurrious error logs upon UDP port scans.
verbose_packet now also dumps UDP packets.
print_message() restores errno, fix sslh-fork
exiting upong log errors.
Add incoming connection timeout management, which
was strangely missing.
v2.3.1:
Fix segmentation fault in sslh-fork.
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: v2.3.0:
Added `max_connections` setting to `listen` and Added `max_connections` setting to `listen` and
`protocol` configuration; see the `protocol` configuration; see the
+26 -3
View File
@@ -3,8 +3,7 @@ ARG TARGET_ARCH="library"
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
WORKDIR /sslh # Install build dependencies for sslh and libproxyprotocol
RUN apk add --no-cache \ RUN apk add --no-cache \
'gcc' \ 'gcc' \
'libconfig-dev' \ 'libconfig-dev' \
@@ -12,15 +11,39 @@ RUN apk add --no-cache \
'musl-dev' \ 'musl-dev' \
'pcre2-dev' \ 'pcre2-dev' \
'perl' \ '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 COPY . /sslh
# Configure and build sslh
# The configure script should automatically detect libproxyprotocol
RUN ./configure && make sslh-select && strip sslh-select RUN ./configure && make sslh-select && strip sslh-select
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} 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" COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
RUN apk add --no-cache \ RUN apk add --no-cache \
'libconfig' \ 'libconfig' \
'pcre2' \ 'pcre2' \
@@ -35,4 +58,4 @@ COPY "./container-entrypoint.sh" "/init"
ENTRYPOINT [ "/init" ] ENTRYPOINT [ "/init" ]
# required for updating iptables # required for updating iptables
USER root:root USER root:root
+2 -2
View File
@@ -22,11 +22,11 @@ MAN=sslh.8.gz # man page name
# itself # itself
ifneq ($(strip $(ENABLE_SANITIZER)),) ifneq ($(strip $(ENABLE_SANITIZER)),)
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment CFLAGS_SAN=-DENABLE_SANITIZER -fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
endif endif
ifneq ($(strip $(COV_TEST)),) ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage CFLAGS_COV=-DCOV_TEST -fprofile-arcs -ftest-coverage
endif endif
CC ?= gcc CC ?= gcc
+1 -7
View File
@@ -5,7 +5,7 @@ sslh -- A ssl/ssh multiplexer
them further based on tests performed on the first data them further based on tests performed on the first data
packet sent by the remote client. packet sent by the remote client.
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, WireGuard,
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
protocol that can be tested using a regular expression, can protocol that can be tested using a regular expression, can
be recognised. A typical use case is to allow serving be recognised. A typical use case is to allow serving
@@ -112,8 +112,6 @@ docker run \
docker-compose example docker-compose example
```yaml ```yaml
version: "3"
services: services:
sslh: sslh:
image: ghcr.io/yrutschle/sslh:latest image: ghcr.io/yrutschle/sslh:latest
@@ -136,8 +134,6 @@ 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**_ _Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
```yaml ```yaml
version: "3"
services: services:
sslh: sslh:
build: https://github.com/yrutschle/sslh.git build: https://github.com/yrutschle/sslh.git
@@ -178,8 +174,6 @@ services:
Transparent mode 2: using host networking Transparent mode 2: using host networking
```yaml ```yaml
version: "3"
services: services:
sslh: sslh:
build: https://github.com/yrutschle/sslh.git 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 - have certain services available only from specified subnets
- some sort of "service knocking" allowing to activate a - some sort of "service knocking" allowing to activate a
service upon some external even, similar to port knocking; service upon some external event, similar to port knocking;
for example, go to a specific URL to enable sslh forwarding for example, go to a specific URL to enable sslh forwarding
to sshd for a set period of time: to sshd for a set period of time:
* sslh listens on 443 and only directs to httpd * sslh listens on 443 and only directs to httpd
+10 -2
View File
@@ -28,6 +28,7 @@
/* Info to keep track of all connections */ /* Info to keep track of all connections */
struct cnx_collection { struct cnx_collection {
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */ 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 /* Allocates and initialises a new collection of connections with at least
@@ -42,6 +43,7 @@ cnx_collection* collection_init(int len)
memset(collection, 0, sizeof(*collection)); memset(collection, 0, sizeof(*collection));
collection->fd2cnx = gap_init(len); collection->fd2cnx = gap_init(len);
collection->max_fd = 0;
return collection; return collection;
} }
@@ -54,10 +56,17 @@ void collection_destroy(cnx_collection* collection)
free(collection); free(collection);
} }
int collection_max_fd(cnx_collection* collection)
{
return collection->max_fd;
}
/* Points the file descriptor to the specified connection index */ /* Points the file descriptor to the specified connection index */
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd) int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
{ {
gap_set(collection->fd2cnx, fd, cnx); gap_set(collection->fd2cnx, fd, cnx);
if (fd > collection->max_fd)
collection->max_fd = fd;
return 0; return 0;
} }
@@ -74,8 +83,7 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
cnx->state = ST_PROBING; cnx->state = ST_PROBING;
cnx->probe_timeout = time(NULL) + cfg.timeout; cnx->probe_timeout = time(NULL) + cfg.timeout;
gap_set(collection->fd2cnx, fd, cnx); collection_add_fd(collection, cnx, fd);
return cnx; return cnx;
} }
+1
View File
@@ -9,6 +9,7 @@ void collection_destroy(cnx_collection* collection);
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd); 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_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
int collection_max_fd(cnx_collection* collection);
/* Remove a connection from the collection */ /* Remove a connection from the collection */
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx); int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
+21 -14
View File
@@ -25,12 +25,6 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#endif #endif
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
/* Make use of systemd socket activation /* Make use of systemd socket activation
* */ * */
#ifdef SYSTEMD #ifdef SYSTEMD
@@ -80,6 +74,13 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
int sd = 0; int sd = 0;
#ifdef SYSTEMD #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); sd = sd_listen_fds(0);
if (sd < 0) { if (sd < 0) {
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd)); print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
@@ -92,6 +93,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
for (i = 0; i < sd; i++) { for (i = 0; i < sd; i++) {
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i; (*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
(*sockfd)[i].type = SOCK_STREAM; (*sockfd)[i].type = SOCK_STREAM;
(*sockfd)[i].endpoint_cfg = &default_endpoint_cfg;
} }
} }
#endif #endif
@@ -209,6 +211,8 @@ static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, str
/* Same, but for UNIX sockets */ /* Same, but for UNIX sockets */
static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg) 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); int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
CHECK_RES_DIE(fd, "socket(AF_UNIX)"); CHECK_RES_DIE(fd, "socket(AF_UNIX)");
@@ -218,10 +222,10 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
exit(4); exit(4);
} }
struct sockaddr_un sun; memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX; sun.sun_family = AF_UNIX;
strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1); strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1);
printf("binding [%s]\n", sun.sun_path); print_message(msg_config, "binding [%s]\n", sun.sun_path);
res = bind(fd, (struct sockaddr*)&sun, sizeof(sun)); res = bind(fd, (struct sockaddr*)&sun, sizeof(sun));
CHECK_RES_DIE(res, "bind(AF_UNIX)"); CHECK_RES_DIE(res, "bind(AF_UNIX)");
@@ -232,7 +236,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])); memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
(*sockfd)[num_addr-1].socketfd = fd; (*sockfd)[num_addr-1].socketfd = fd;
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM; (*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
(*sockfd)[num_addr-1].family = AF_INET; (*sockfd)[num_addr-1].family = AF_UNIX;
(*sockfd)[num_addr-1].endpoint_cfg = cfg; (*sockfd)[num_addr-1].endpoint_cfg = cfg;
return num_addr; return num_addr;
@@ -428,9 +432,9 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
cnx->proto->port); cnx->proto->port);
} }
for (a = cnx->proto->saddr; a; a = a->ai_next) { for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent or using proxyprotocol, make sure both /* When transparent, make sure both connections use
* connections use the same address family (e.g. IP4 on both sides) */ * 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)) if (transparent && (a->ai_family != from.ai_addr->sa_family))
continue; continue;
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n", print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a), sprintaddr(buf, sizeof(buf), a),
@@ -481,10 +485,13 @@ static int connect_unix(struct connection *cnx, int fd_from, connect_blocking bl
struct sockaddr_storage ss; struct sockaddr_storage ss;
struct sockaddr_un* sun = (struct sockaddr_un*)&ss; struct sockaddr_un* sun = (struct sockaddr_un*)&ss;
int fd = socket(AF_UNIX, SOCK_STREAM, 0); memset(&ss, 0, sizeof(ss));
sun->sun_family = AF_UNIX; sun->sun_family = AF_UNIX;
strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1); 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)); int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun));
CHECK_RES_RETURN(res, "connect", res); CHECK_RES_RETURN(res, "connect", res);
@@ -905,7 +912,7 @@ int check_access_rights(int in_socket, const char* service)
} }
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) { if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str); print_message(msg_connections, "connection from %s(%s): access denied\n", host, addr_str);
close(in_socket); close(in_socket);
return -1; return -1;
} }
+9 -2
View File
@@ -9,6 +9,13 @@
#define FD_SETSIZE 4096 #define FD_SETSIZE 4096
#endif #endif
/* Cygwin does not define some symbols
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
#define _GNU_SOURCE #define _GNU_SOURCE
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h> #include <fcntl.h>
@@ -193,11 +200,11 @@ extern struct addrinfo *addr_listen;
extern const char* server_type; extern const char* server_type;
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080) #if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
extern int hosts_ctl(); extern int hosts_ctl(char*, char*, char*, char*);
#endif #endif
/* sslh-fork.c */ /* sslh-fork.c */
void start_shoveler(int); void main_inetd(void);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen); void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
+10 -5
View File
@@ -7,12 +7,17 @@ AC_INIT
AC_CONFIG_HEADERS(config.h) AC_CONFIG_HEADERS(config.h)
AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([Makefile])
AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ], []) AC_CHECK_LIB([wrap], [hosts_ctl],
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], []) [AC_DEFINE([HAVE_LIBWRAP], [1], [Libwrap, to support host_ctl, /etc/allow and /etc/deny]) LIBS="$LIBS -lwrap"])
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], []) 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_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), []) AC_CHECK_HEADERS(linux/landlock.h,
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], []) 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"])
LIBS="$LIBS" LIBS="$LIBS"
AC_SUBST([LIBS]) AC_SUBST([LIBS])
-2
View File
@@ -27,7 +27,6 @@ protocols:
( (
{ name: "ssh"; host: "localhost"; port: "2222"; }, { name: "ssh"; host: "localhost"; port: "2222"; },
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; } { name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
);
); );
``` ```
@@ -77,7 +76,6 @@ protocols:
( (
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; }, { name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
{ name: "tls"; host: "localhost"; port: "8080"; } { name: "tls"; host: "localhost"; port: "8080"; }
);
); );
``` ```
+11 -2
View File
@@ -68,9 +68,13 @@ void start_echo(int fd)
int first = 1; int first = 1;
prefix_len = strlen(cfg.prefix); prefix_len = strlen(cfg.prefix);
if (prefix_len >= sizeof(buffer)) {
fprintf(stderr, "prefix too long\n");
return;
}
memset(buffer, 0, sizeof(buffer)); memset(buffer, 0, sizeof(buffer));
strcpy(buffer, cfg.prefix); memcpy(buffer, cfg.prefix, prefix_len);
while (1) { while (1) {
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len); ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
@@ -147,19 +151,24 @@ void udp_echo(struct listen_endpoint* listen_socket)
memset(data, 0, sizeof(data)); memset(data, 0, sizeof(data));
size_t prefix_len = strlen(cfg.prefix); 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); memcpy(data, cfg.prefix, prefix_len);
while (1) { while (1) {
addrlen = sizeof(src_addr); addrlen = sizeof(src_addr);
ssize_t len = recvfrom(listen_socket->socketfd, ssize_t len = recvfrom(listen_socket->socketfd,
data + prefix_len, data + prefix_len,
sizeof(data) - prefix_len, sizeof(data) - prefix_len - 1,
0, 0,
&src_addr, &src_addr,
&addrlen); &addrlen);
if (len < 0) { if (len < 0) {
perror("recvfrom"); perror("recvfrom");
continue;
} }
*(data + prefix_len + len) = 0; *(data + prefix_len + len) = 0;
fprintf(stderr, "%zd %s\n", len, data + prefix_len); fprintf(stderr, "%zd %s\n", len, data + prefix_len);
-109
View File
@@ -1,109 +0,0 @@
/* 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,6 +159,9 @@ protocols:
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30") # Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
# Remember that the regex needs to be adjusted for every supported QUIC version. # 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" ]; }, { 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! # Regex examples -- better use the built-in probes for real-world use!
# OpenVPN # OpenVPN
+4 -1
View File
@@ -1,6 +1,9 @@
CFLAGS=-DHASH_DEBUG -O2 -Wall CFLAGS=-DHASH_DEBUG -O2 -Wall
OBJ=../hash.o ../gap.o htest.o OBJ=hash.o ../gap.o htest.o
hash.o: ../hash.c
$(CC) $(CFLAGS) -c -o $@ $<
htest: $(OBJ) htest: $(OBJ)
$(CC) -o htest $(OBJ) $(CC) -o htest $(OBJ)
+1
View File
@@ -29,6 +29,7 @@ struct hash_item {
}; };
typedef struct hash_item* hash_item; typedef struct hash_item* hash_item;
typedef struct hash hash;
#include "../hash.h" #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) static int add_libwrap(int ruleset_fd)
{ {
/* Files for libwrap */ /* Files for libwrap */
#ifdef LIBWRAP #ifdef HAVE_LIBWRAP
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow"); add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny"); add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
#endif #endif
+9 -1
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 */ /* 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 = { msg_info msg_int_error = {
LOG_CRIT, LOG_CRIT,
&cfg.verbose_system_error &cfg.verbose_int_error
}; };
/* System errors: when the system around us fails us: memory allocation, fork, ... */ /* System errors: when the system around us fails us: memory allocation, fork, ... */
@@ -107,6 +107,12 @@ void print_message(msg_info info, const char* str, ...)
{ {
va_list ap; 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) { if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
va_start(ap, str); va_start(ap, str);
vfprintf(stderr, str, ap); vfprintf(stderr, str, ap);
@@ -125,6 +131,8 @@ void print_message(msg_info info, const char* str, ...)
fflush(logfile_fp); fflush(logfile_fp);
va_end(ap); va_end(ap);
} }
errno = saved_errno;
} }
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */ static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
+23 -10
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) void hexdump(msg_info msg_info, const char *mem, unsigned int len)
{ {
unsigned int i, j; unsigned int i, j;
char str[10 + HEXDUMP_COLS * 4 + 2]; char str[12 + HEXDUMP_COLS * 4 + 2]; /* offset <= 12, hex+ascii 64, '\n'+'\0' */
int c = 0; /* index in str */ int c = 0; /* index in str */
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
{ {
/* print offset */ /* print offset */
if(i % HEXDUMP_COLS == 0) if(i % HEXDUMP_COLS == 0)
c += sprintf(&str[c], "0x%06x: ", i); c += snprintf(&str[c], sizeof(str) - c, "0x%06x: ", i);
/* print hex data */ /* print hex data */
if(i < len) if(i < len)
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]); c += snprintf(&str[c], sizeof(str) - c, "%02x ", 0xFF & mem[i]);
else /* end of block, just aligning for ASCII dump */ else /* end of block, just aligning for ASCII dump */
c+= sprintf(&str[c], " "); c += snprintf(&str[c], sizeof(str) - c, " ");
/* print ASCII dump */ /* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) { if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
@@ -156,7 +156,10 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
if (len < 2) if (len < 2)
return PROBE_AGAIN; return PROBE_AGAIN;
packet_len = ntohs(*(uint16_t*)p); /* 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);
return packet_len == len - 2; return packet_len == len - 2;
} else { } else {
if (len < 1) if (len < 1)
@@ -205,7 +208,9 @@ static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_prot
return PROBE_NEXT; return PROBE_NEXT;
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved) // Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
if (ntohl(*(uint32_t*)p) != 0x01000000) uint32_t type;
memcpy(&type, p, sizeof(type));
if (ntohl(type) != 0x01000000)
return PROBE_NEXT; return PROBE_NEXT;
return PROBE_MATCH; return PROBE_MATCH;
@@ -407,20 +412,25 @@ static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_prot
if (len < 8) if (len < 8)
return PROBE_NEXT; return PROBE_NEXT;
return !strncmp(p, "TS3INIT1", len); /* 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);
} }
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{ {
char version; char version;
char packet_len; uint16_t packet_len;
if (len < 7) if (len < 7)
return PROBE_NEXT; return PROBE_NEXT;
version=*p; version=*p;
if (version!=0x03) if (version!=0x03)
return 0; return 0;
packet_len = ntohs(*(uint16_t*)(p+2)); memcpy(&packet_len, p+2, sizeof(packet_len));
return packet_len == 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;
} }
#ifdef ENABLE_REGEX #ifdef ENABLE_REGEX
@@ -465,6 +475,9 @@ int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item* p; struct sslhcfg_protocols_item* p;
int i, res, again = 0; int i, res, again = 0;
print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, buf, len);
*proto_out = NULL; *proto_out = NULL;
for (i = 0; i < proto_len; i++) { for (i = 0; i < proto_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"}; char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
+6 -1
View File
@@ -142,6 +142,10 @@ static int pid_cmp(hash_item item1, hash_item item2)
void decrease_forked_connection(struct loop_info* loop, pid_t pid) { void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
struct pid2proto p2p = { .pid = pid }; struct pid2proto p2p = { .pid = pid };
struct pid2proto* found = hash_find(loop->pid2proto, &p2p); 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_proto_connections(found->proto);
dec_listen_connections(found->endpoint); dec_listen_connections(found->endpoint);
@@ -196,7 +200,7 @@ static int next_power_of_two(int n)
return n + 1; return n + 1;
} }
void loop_init(struct loop_info* loop, int num_addr_listen) void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen)
{ {
memset(loop, 0, sizeof(*loop)); memset(loop, 0, sizeof(*loop));
loop->collection = collection_init(0); loop->collection = collection_init(0);
@@ -206,6 +210,7 @@ void loop_init(struct loop_info* loop, int num_addr_listen)
udp_init(loop); udp_init(loop);
tcp_init(); tcp_init();
loop->listen_sockets = listen_sockets;
loop->num_addr_listen = num_addr_listen; loop->num_addr_listen = num_addr_listen;
int max_forks = next_power_of_two(max_forking_connections()); int max_forks = next_power_of_two(max_forking_connections());
+5 -1
View File
@@ -23,6 +23,8 @@ struct loop_info {
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */ hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
watchers* watchers; watchers* watchers;
struct listen_endpoint* listen_sockets;
int num_addr_listen; /* How many listen endpoints do we have here */ int num_addr_listen; /* How many listen endpoints do we have here */
cnx_collection* collection; /* Collection of connections linked to this loop */ cnx_collection* collection; /* Collection of connections linked to this loop */
@@ -32,7 +34,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); 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); int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
void loop_init(struct loop_info* loop, int num_addr_listen); void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen);
void remember_child_data(struct loop_info* fd_info, void remember_child_data(struct loop_info* fd_info,
struct connection* cnx, pid_t pid); struct connection* cnx, pid_t pid);
@@ -47,4 +49,6 @@ void watchers_del_write(watchers* w, int fd);
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid); void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
void close_listen_endpoints(struct loop_info* loop);
#endif #endif
+2 -2
View File
@@ -112,7 +112,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
if (res == -1) return -1; if (res == -1) return -1;
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family); pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
res = get_info(cnx->q[1].fd, SOCK, res = get_info(cnx->q[0].fd, SOCK,
&addr, &addr,
&pp_info_in_v1.dst_addr, &pp_info_in_v1.dst_addr,
&pp_info_in_v1.dst_port &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); uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
if (!pp1_hdr) { if (!pp1_hdr) {
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error)); print_message(msg_system_error, "pp_create_hdr:%d:%s\n", error, pp_strerror(error));
return -1; return -1;
} }
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len); 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 */ #else /* HAVE_PROXYPROTOCOL */
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; } static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
static int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; } static inline int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; }
static inline int pp_header_len(char*, int) { return 0; } static inline int pp_header_len(char*, int) { return 0; }
#endif /* HAVE_PROXYPROTOCOL */ #endif /* HAVE_PROXYPROTOCOL */
-27
View File
@@ -1,27 +0,0 @@
[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] [Unit]
Description=SSL/SSH multiplexer (fork mode) for %I Description=SSL/SSH multiplexer for %I
After=network.target After=network.target
[Service] [Service]
@@ -8,8 +8,8 @@ ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process KillMode=process
#Hardening #Hardening
PrivateTmp=true PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_SETGIT CAP_SETUID CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
SecureBits=noroot-locked SecureBits=noroot-locked
ProtectSystem=strict ProtectSystem=strict
ProtectHome=true ProtectHome=true
+48 -4
View File
@@ -26,6 +26,7 @@
#include "log.h" #include "log.h"
#include "udp-listener.h" #include "udp-listener.h"
#include "tcp-listener.h" #include "tcp-listener.h"
#include "sslh-conf.h"
const char* server_type = "sslh-ev"; const char* server_type = "sslh-ev";
@@ -167,6 +168,41 @@ static void temporary_stop_accept_watchers(ev_io* io)
ev_timer_start(EV_A_ timer); 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. /* 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"). * 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").
@@ -176,7 +212,14 @@ static void temporary_stop_accept_watchers(ev_io* io)
static void cnx_accept_cb(EV_P_ ev_io *w, int revents) static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
{ {
struct loop_info* info = ev_userdata(EV_A); struct loop_info* info = ev_userdata(EV_A);
if (!cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd))) { 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 */
switch (errno) { switch (errno) {
case EMFILE: case EMFILE:
case ENFILE: /* Not sure ENFILE should be included here */ case ENFILE: /* Not sure ENFILE should be included here */
@@ -207,13 +250,14 @@ void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pi
ev_child_start(EV_DEFAULT_ cw); ev_child_start(EV_DEFAULT_ cw);
} }
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{ {
struct loop_info ev_info; struct loop_info ev_info;
loop = EV_DEFAULT; loop = EV_DEFAULT;
loop_init(&ev_info, num_addr_listen); ev_timer_init(&probe_timer, &probe_timeout_cb, cfg.timeout, 0);
loop_init(&ev_info, listen_sockets, num_addr_listen);
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen); watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info); ev_set_userdata(EV_A_ &ev_info);
@@ -221,7 +265,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
ev_run(EV_A_ 0); ev_run(EV_A_ 0);
} }
void start_shoveler(int listen_socket) { void main_inetd(void) {
print_message(msg_config_error, "inetd mode is not supported in libev mode\n"); print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
exit(1); exit(1);
} }
+28 -9
View File
@@ -69,7 +69,7 @@ int shovel(struct connection *cnx)
/* Child process that finds out what to connect to and proxies /* Child process that finds out what to connect to and proxies
*/ */
void start_shoveler(int in_socket) static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
{ {
fd_set fds; fd_set fds;
struct timeval tv; struct timeval tv;
@@ -79,6 +79,7 @@ void start_shoveler(int in_socket)
init_cnx(&cnx); init_cnx(&cnx);
cnx.q[0].fd = in_socket; cnx.q[0].fd = in_socket;
cnx.endpoint = endpoint;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(in_socket, &fds); FD_SET(in_socket, &fds);
@@ -130,6 +131,22 @@ void start_shoveler(int in_socket)
exit(0); 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 pid_t *listener_pid;
static int listener_pid_number = 0; static int listener_pid_number = 0;
@@ -207,9 +224,10 @@ static void process_signals(struct listen_endpoint* endpoint)
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint) void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
{ {
int i, in_socket; int i, in_socket;
struct listen_endpoint* self = &endpoint[active_endpoint];
while (1) { while (1) {
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0); in_socket = accept(self->socketfd, 0, 0);
if (in_socket == -1) { if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__FILE__, __LINE__, "accept", errno, strerror(errno)); __FILE__, __LINE__, "accept", errno, strerror(errno));
@@ -226,33 +244,34 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
continue; continue;
case EINTR: case EINTR:
process_signals(endpoint); process_signals(self);
continue; continue;
default: /* Otherwise, it's something wrong in our parameters, we fail */ default: /* Otherwise, it's something wrong in our parameters, we fail */
return; return;
} }
} }
endpoint->num_connections++; self->num_connections++;
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, endpoint->num_connections); print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, self->num_connections);
if ((endpoint->endpoint_cfg->max_connections_is_present) if ((self->endpoint_cfg->max_connections_is_present)
&& (endpoint->num_connections > endpoint->endpoint_cfg->max_connections)) && (self->num_connections > self->endpoint_cfg->max_connections))
{ {
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket); print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
endpoint->num_connections--; self->num_connections--;
close(in_socket); close(in_socket);
continue; continue;
} }
switch(fork()) { switch(fork()) {
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno)); 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; break;
case 0: /* In child process */ case 0: /* In child process */
/* Shoveler processes don't need to hog file descriptors */ /* Shoveler processes don't need to hog file descriptors */
for (i = 0; i < num_endpoints; i++) for (i = 0; i < num_endpoints; i++)
close(endpoint[i].socketfd); close(endpoint[i].socketfd);
start_shoveler(in_socket); start_shoveler(in_socket, self);
exit(0); exit(0);
default: /* In parent process */ default: /* In parent process */
+125 -8
View File
@@ -30,6 +30,8 @@
#include <pcre2.h> #include <pcre2.h>
#endif #endif
#include <sys/un.h>
#include "common.h" #include "common.h"
#include "probe.h" #include "probe.h"
#include "log.h" #include "log.h"
@@ -72,16 +74,16 @@ static void printsettings(void)
for (i = 0; i < cfg.protocols_len; i++ ) { for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i]; p = &cfg.protocols[i];
if (p->is_unix) { if (p->is_unix) {
sprintf(buf, "unix socket: %s", p->host); snprintf(buf, sizeof(buf), "unix socket: %s", p->host);
} else { } else {
if (p->resolve_on_forward) { if (p->resolve_on_forward) {
strcpy(buf, "resolve on forward: "); strcpy(buf, "resolve on forward: ");
size_t len = strlen(buf); size_t len = strlen(buf);
sprintf(buf+len, "%s:%s", p->host, p->port); snprintf(buf+len, sizeof(buf)-len, "%s:%s", p->host, p->port);
} else { } else {
sprintaddr(buf, sizeof(buf), p->saddr); sprintaddr(buf, sizeof(buf), p->saddr);
size_t len = strlen(buf); size_t len = strlen(buf);
sprintf(buf+len, " family %d %d", snprintf(buf+len, sizeof(buf)-len, " family %d %d",
p->saddr->ai_family, p->saddr->ai_family,
p->saddr->ai_addr->sa_family); p->saddr->ai_addr->sa_family);
} }
@@ -220,6 +222,77 @@ 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) void config_sanity_check(struct sslhcfg_item* cfg)
{ {
@@ -233,6 +306,12 @@ void config_sanity_check(struct sslhcfg_item* cfg)
} }
#endif #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) { for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "tls") != 0) { if (strcmp(cfg->protocols[i].name, "tls") != 0) {
if (cfg->protocols[i].sni_hostnames_len) { if (cfg->protocols[i].sni_hostnames_len) {
@@ -249,6 +328,12 @@ 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].is_udp) {
if (cfg->protocols[i].tfo_ok) { if (cfg->protocols[i].tfo_ok) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": " print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
@@ -284,6 +369,41 @@ 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 main(int argc, char *argv[], char* envp[])
{ {
int res, num_addr_listen; int res, num_addr_listen;
@@ -299,7 +419,7 @@ int main(int argc, char *argv[], char* envp[])
config_finish(&cfg); config_finish(&cfg);
if (cfg.version) { if (cfg.version) {
printf("%s %s\n", server_type, VERSION); print_version();
exit(0); exit(0);
} }
@@ -308,10 +428,7 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd) if (cfg.inetd)
{ {
close(fileno(stderr)); /* Make sure no error will go to client */ main_inetd(); /* Does not return */
tcp_init();
start_shoveler(0);
exit(0);
} }
printsettings(); printsettings();
+2 -2
View File
@@ -204,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
int i, res; int i, res;
setup_sigalrm(); setup_sigalrm();
loop_init(&fd_info, num_addr_listen); loop_init(&fd_info, listen_sockets, num_addr_listen);
watchers_init(&fd_info.watchers, listen_sockets, 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 start_shoveler(int listen_socket) { void main_inetd(void) {
print_message(msg_config_error, "inetd mode is not supported in select mode\n"); print_message(msg_config_error, "inetd mode is not supported in select mode\n");
exit(1); exit(1);
} }
+8 -3
View File
@@ -6,7 +6,7 @@
=head1 SYNOPSIS =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<--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<--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]
=head1 DESCRIPTION =head1 DESCRIPTION
@@ -87,7 +87,7 @@ configuration file's settings.
=item B<-t> I<num>, B<--timeout> I<num> =item B<-t> I<num>, B<--timeout> I<num>
Timeout before forwarding the connection to the timeout Timeout before forwarding the connection to the timeout
protocol (which should usually be SSH). Default is 2s. protocol (which should usually be SSH). Default is 5s.
=item B<--on-timeout> I<protocol name> =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. receiving service sees the original client's IP address.
This works on Linux only and involves B<iproute2> settings. This works on Linux only and involves B<iproute2> settings.
In some cases also B<iptables/nftables> settings are needed. In some cases also B<iptables/nftables> settings are needed.
Refer to the README or L<https://github.com/ftasnetamot/sslh/blob/2014-08-16--close-filehandles-with-detach/doc/simple_transparent_proxy.md> Refer to the README or L<https://github.com/yrutschle/sslh/blob/master/doc/simple_transparent_proxy.md>
for more information. for more information.
=item B<-p> I<listening address>, B<--listen> I<listening address> =item B<-p> I<listening address>, B<--listen> I<listening address>
@@ -136,6 +136,11 @@ If you wish to accept SSLv2, use B<--anyprot> instead.
Interface and port on which to forward SSH connections, Interface and port on which to forward SSH connections,
typically I<localhost:22>. 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> =item B<--openvpn> I<target address>
Interface and port on which to forward OpenVPN connections, Interface and port on which to forward OpenVPN connections,
+3 -3
View File
@@ -307,12 +307,12 @@ int main(int argc, char *argv[]) {
err_log = stderr; err_log = stderr;
} }
const int r = gen_sslh_config(systemd_invoked ? argv[1] : ""); const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
if (systemd_invoked) {
fclose(err_log);
}
if (!r) { if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n"); fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
} }
if (systemd_invoked) {
fclose(err_log);
}
return r < 0 ? -1 : 0; return r < 0 ? -1 : 0;
} else { } else {
printf("This program takes three or no arguments.\n"); printf("This program takes three or no arguments.\n");
+70 -8
View File
@@ -10,6 +10,7 @@
# ./run # run all tests # ./run # run all tests
# ./run -l # list all tests # ./run -l # list all tests
# ./run 1 3 5 # run specified tests # ./run 1 3 5 # run specified tests
# ./run --binary sslh-ev # run only one binary
use strict; use strict;
use IO::Socket::INET6; use IO::Socket::INET6;
@@ -17,9 +18,13 @@ use Test::More qw/no_plan/;
use Conf::Libconfig 1.0.3; use Conf::Libconfig 1.0.3;
use Getopt::Long; use Getopt::Long;
my ($coverage, $list_tests); # --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);
GetOptions( GetOptions(
'cover' => \$coverage, 'cover' => \$coverage,
'binary=s' => \$param_binary,
'list' => \$list_tests, 'list' => \$list_tests,
); );
@@ -192,7 +197,8 @@ sub run_ending_test
# Runs one test for one probe. Start echosrv's if required. # Runs one test for one probe. Start echosrv's if required.
# Connect to port specified in the test, otherwise to the # Connect to port specified in the test, otherwise to the
# first port in the sslh configuration. # first port in the sslh configuration; to host specified in
# the test, otherwise to localhost
# run sslh, connect, write the test pattern, read the result, # run sslh, connect, write the test pattern, read the result,
# check it connected to the right echosrv, check the data # check it connected to the right echosrv, check the data
# was transfered ok. # was transfered ok.
@@ -206,9 +212,10 @@ sub run_test_probe
my $expected = $test->{expected}; my $expected = $test->{expected};
my $sslh_host = $test->{host} // "localhost";
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port}; my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
print "test_probe [$expected] $sslh_port\n"; print "test_probe [$expected] $sslh_host:$sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx = new IO::Socket::INET(PeerHost => "$sslh_host:$sslh_port");
warn "t: $!\n" unless $cnx; warn "t: $!\n" unless $cnx;
return unless $cnx; return unless $cnx;
@@ -316,6 +323,7 @@ my @tests = (
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA", data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
}, },
# check socks5 does not # check socks5 does not
{ {
desc => "Server-side ProxyProtocol not active", desc => "Server-side ProxyProtocol not active",
@@ -325,6 +333,55 @@ my @tests = (
expected => "socks5", 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 # test_pp2.cfg has proxyprotocol on port 8080
{ {
desc => "Client-side ProxyProtocol active", desc => "Client-side ProxyProtocol active",
@@ -388,10 +445,15 @@ if ($list_tests) {
################################################################################ ################################################################################
# Run selected tests # Run selected tests
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n"; my @binaries = qw/sslh-fork sslh-select sslh-ev/;
my $binary = 'sslh-ev'; @binaries = $param_binary if defined $param_binary;
my $code = ($test->{run})->($binary, $test);
foreach my $binary (@binaries) {
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $code = ($test->{run})->($binary, $test);
}
} }
stop_echosrv(); stop_echosrv();
+4 -4
View File
@@ -29,15 +29,15 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
# Options: # Options:
listen: listen:
( (
{ host: "localhost"; port: "8080"; keepalive: true; } { host: "ip4-localhost"; port: "8080"; keepalive: true; }
); );
protocols: protocols:
( (
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; }, { name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "localhost"; port: "9001"; }, { name: "socks5"; host: "ip4-localhost"; port: "9001"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; } { name: "anyprot"; host: "ip4-localhost"; port: "9099"; }
); );
on_timeout: "ssh"; on_timeout: "ssh";
+44
View File
@@ -0,0 +1,44 @@
# 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
@@ -0,0 +1,44 @@
# 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
@@ -0,0 +1,44 @@
# 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
@@ -0,0 +1,44 @@
# 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";
+39 -7
View File
@@ -30,14 +30,16 @@
/* Removes cnx from probing list */ /* Removes cnx from probing list */
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx) static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
{ {
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing); if (!gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing)) {
fd_info->num_probing--; fd_info->num_probing--;
}
} }
static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx) static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
{ {
gap_set(fd_info->probing_list, fd_info->num_probing, cnx); if (!gap_set(fd_info->probing_list, fd_info->num_probing, cnx)) {
fd_info->num_probing++; fd_info->num_probing++;
}
} }
/* shovels data from active fd to the other /* shovels data from active fd to the other
@@ -53,7 +55,6 @@ 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); print_message(msg_fd, "activity on fd%d\n", read_q->fd);
switch(fd2fd(write_q, read_q)) { switch(fd2fd(write_q, read_q)) {
case -1:
case FD_CNXCLOSED: case FD_CNXCLOSED:
tidy_connection(cnx, fd_info); tidy_connection(cnx, fd_info);
break; break;
@@ -296,6 +297,37 @@ 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 */ /* Forks a shoveler process for one protocol */
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) { void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
pid_t pid; pid_t pid;
@@ -307,8 +339,8 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
switch (pid = fork()) { switch (pid = fork()) {
case 0: /* child */ case 0: /* child */
/* TODO: close all file descriptors except 2 */ close_listen_endpoints(fd_info);
/* free(cnx); */ tidy_other_connections(fd_info, cnx);
connect_proxy(cnx); connect_proxy(cnx);
exit(0); exit(0);
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno)); case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
-4
View File
@@ -46,10 +46,6 @@ int probe_client_protocol(struct connection *cnx)
if (n > 0) { if (n > 0) {
defer_write(&cnx->q[1], buffer, n); 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; size_t pp_len = 0;
if (cnx->endpoint->endpoint_cfg->proxyprotocol) { if (cnx->endpoint->endpoint_cfg->proxyprotocol) {
pp_len = pp_header_len(cnx->q[1].begin_deferred_data, pp_len = pp_header_len(cnx->q[1].begin_deferred_data,
+1 -1
View File
@@ -277,7 +277,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
for (i = 0; i < list_len; i++) { for (i = 0; i < list_len; i++) {
item = &list[i]; item = &list[i];
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item); print_message(msg_probe_info, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
if(!fnmatch(*item, name_nullterminated, 0)) { if(!fnmatch(*item, name_nullterminated, 0)) {
free(name_nullterminated); free(name_nullterminated);
return 1; return 1;
+8 -10
View File
@@ -239,12 +239,11 @@ static int nonblocking_socket(struct sslhcfg_protocols_item* proto)
} }
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0); int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
CHECK_RES_RETURN(out, "udp:socket:socket", -1);
res = set_nonblock(out); res = set_nonblock(out);
if (res == -1) { CHECK_RES_RETURN(out, "udp:socket:nonblock", -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; return out;
} }
@@ -273,10 +272,9 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
addrlen = sizeof(src_addr); addrlen = sizeof(src_addr);
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen); len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
if (len < 0) { if ((len == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return NULL;
perror("recvfrom"); CHECK_RES_DIE(len, "udp_listener/c2s/recvfrom");
return NULL;
}
target = known_source(fd_info->hash_sources, &src_addr, addrlen); target = known_source(fd_info->hash_sources, &src_addr, addrlen);
addrinfo.ai_addr = (struct sockaddr*) &src_addr; addrinfo.ai_addr = (struct sockaddr*) &src_addr;
addrinfo.ai_addrlen = addrlen; addrinfo.ai_addrlen = addrlen;
@@ -337,7 +335,7 @@ void udp_s2c_forward(struct connection* cnx)
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL); res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return; if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
CHECK_RES_DIE(res, "udp_listener/recvfrom"); CHECK_RES_DIE(res, "udp_listener/s2c/recvfrom");
res = sendto(cnx->local_endpoint, data, res, 0, res = sendto(cnx->local_endpoint, data, res, 0,
(struct sockaddr*)&cnx->client_addr, cnx->addrlen); (struct sockaddr*)&cnx->client_addr, cnx->addrlen);
mark_active(cnx); mark_active(cnx);