Compare commits

..
28 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
23 changed files with 203 additions and 179 deletions

No files matched your search

+12
View File
@@ -1,3 +1,15 @@
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.
+1 -7
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,
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, WireGuard,
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,8 +112,6 @@ docker run \
docker-compose example
```yaml
version: "3"
services:
sslh:
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**_
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
@@ -178,8 +174,6 @@ 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 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
to sshd for a set period of time:
* sslh listens on 443 and only directs to httpd
+17 -4
View File
@@ -74,6 +74,13 @@ 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));
@@ -86,6 +93,7 @@ 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
@@ -203,6 +211,8 @@ 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)");
@@ -212,10 +222,10 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
exit(4);
}
struct sockaddr_un sun;
memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
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));
CHECK_RES_DIE(res, "bind(AF_UNIX)");
@@ -226,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]));
(*sockfd)[num_addr-1].socketfd = fd;
(*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;
return num_addr;
@@ -475,10 +485,13 @@ 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;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
memset(&ss, 0, sizeof(ss));
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);
+1 -1
View File
@@ -200,7 +200,7 @@ extern struct addrinfo *addr_listen;
extern const char* server_type;
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
extern int hosts_ctl();
extern int hosts_ctl(char*, char*, char*, char*);
#endif
/* sslh-fork.c */
+10 -5
View File
@@ -7,12 +7,17 @@ AC_INIT
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_FILES([Makefile])
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_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_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
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"])
LIBS="$LIBS"
AC_SUBST([LIBS])
+11 -2
View File
@@ -68,9 +68,13 @@ 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));
strcpy(buffer, cfg.prefix);
memcpy(buffer, cfg.prefix, prefix_len);
while (1) {
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));
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,
sizeof(data) - prefix_len - 1,
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
@@ -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")
# 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
+4 -1
View File
@@ -1,6 +1,9 @@
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)
$(CC) -o htest $(OBJ)
+1
View File
@@ -29,6 +29,7 @@ struct hash_item {
};
typedef struct hash_item* hash_item;
typedef struct hash hash;
#include "../hash.h"
+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 */
msg_info msg_int_error = {
LOG_CRIT,
&cfg.verbose_system_error
&cfg.verbose_int_error
};
/* 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;
/* 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);
@@ -125,6 +131,8 @@ 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" */
+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)
{
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 */
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
{
/* print offset */
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 */
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 */
c+= sprintf(&str[c], " ");
c += snprintf(&str[c], sizeof(str) - c, " ");
/* print ASCII dump */
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)
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;
} else {
if (len < 1)
@@ -205,7 +208,9 @@ 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)
if (ntohl(*(uint32_t*)p) != 0x01000000)
uint32_t type;
memcpy(&type, p, sizeof(type));
if (ntohl(type) != 0x01000000)
return PROBE_NEXT;
return PROBE_MATCH;
@@ -407,20 +412,25 @@ static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_prot
if (len < 8)
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)
{
char version;
char packet_len;
uint16_t packet_len;
if (len < 7)
return PROBE_NEXT;
version=*p;
if (version!=0x03)
return 0;
packet_len = ntohs(*(uint16_t*)(p+2));
return packet_len == len;
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;
}
#ifdef ENABLE_REGEX
@@ -465,6 +475,9 @@ 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"};
+4
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) {
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);
+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 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; }
#endif /* HAVE_PROXYPROTOCOL */
+46 -1
View File
@@ -26,6 +26,7 @@
#include "log.h"
#include "udp-listener.h"
#include "tcp-listener.h"
#include "sslh-conf.h"
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);
}
/* 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").
@@ -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)
{
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) {
case EMFILE:
case ENFILE: /* Not sure ENFILE should be included here */
@@ -212,6 +255,8 @@ 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);
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
+10 -8
View File
@@ -224,9 +224,10 @@ 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(endpoint[active_endpoint].socketfd, 0, 0);
in_socket = accept(self->socketfd, 0, 0);
if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__FILE__, __LINE__, "accept", errno, strerror(errno));
@@ -243,33 +244,34 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
continue;
case EINTR:
process_signals(endpoint);
process_signals(self);
continue;
default: /* Otherwise, it's something wrong in our parameters, we fail */
return;
}
}
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))
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))
{
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
endpoint->num_connections--;
self->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, endpoint);
start_shoveler(in_socket, self);
exit(0);
default: /* In parent process */
+24 -3
View File
@@ -30,6 +30,8 @@
#include <pcre2.h>
#endif
#include <sys/un.h>
#include "common.h"
#include "probe.h"
#include "log.h"
@@ -72,16 +74,16 @@ static void printsettings(void)
for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i];
if (p->is_unix) {
sprintf(buf, "unix socket: %s", p->host);
snprintf(buf, sizeof(buf), "unix socket: %s", p->host);
} else {
if (p->resolve_on_forward) {
strcpy(buf, "resolve on forward: ");
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 {
sprintaddr(buf, sizeof(buf), p->saddr);
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_addr->sa_family);
}
@@ -277,6 +279,21 @@ static void config_sanity_libwrap(const struct sslhcfg_protocols_item* prot)
#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)
{
size_t i;
@@ -291,6 +308,8 @@ void config_sanity_check(struct sslhcfg_item* cfg)
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) {
@@ -312,6 +331,8 @@ 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) {
+8 -3
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<--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
@@ -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 2s.
protocol (which should usually be SSH). Default is 5s.
=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/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.
=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,
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 -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 (systemd_invoked) {
fclose(err_log);
}
if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
}
if (systemd_invoked) {
fclose(err_log);
}
return r < 0 ? -1 : 0;
} else {
printf("This program takes three or no arguments.\n");
+6 -5
View File
@@ -30,14 +30,16 @@
/* Removes cnx from probing list */
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);
fd_info->num_probing--;
if (!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)
{
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
fd_info->num_probing++;
if (!gap_set(fd_info->probing_list, fd_info->num_probing, cnx)) {
fd_info->num_probing++;
}
}
/* 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);
switch(fd2fd(write_q, read_q)) {
case -1:
case FD_CNXCLOSED:
tidy_connection(cnx, fd_info);
break;
-4
View File
@@ -46,10 +46,6 @@ 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,
+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);
CHECK_RES_RETURN(out, "udp:socket:socket", -1);
res = set_nonblock(out);
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;
}
CHECK_RES_RETURN(out, "udp:socket:nonblock", -1);
return out;
}
@@ -273,10 +272,9 @@ 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 < 0) {
perror("recvfrom");
return NULL;
}
if ((len == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return NULL;
CHECK_RES_DIE(len, "udp_listener/c2s/recvfrom");
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
addrinfo.ai_addr = (struct sockaddr*) &src_addr;
addrinfo.ai_addrlen = addrlen;
@@ -337,7 +335,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/recvfrom");
CHECK_RES_DIE(res, "udp_listener/s2c/recvfrom");
res = sendto(cnx->local_endpoint, data, res, 0,
(struct sockaddr*)&cnx->client_addr, cnx->addrlen);
mark_active(cnx);