mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-22 18:12:10 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a0425f4f | ||
|
|
34645a62b2 | ||
|
|
6d50082993 | ||
|
|
6219527ce4 | ||
|
|
0dd580f45b | ||
|
|
63e15bb55c | ||
|
|
6f3dcfb365 | ||
|
|
f8e031ecb9 | ||
|
|
fcfc348aee | ||
|
|
6905498d60 | ||
|
|
eea50a97fc | ||
|
|
387ef13f52 | ||
|
|
dcb2cc928a | ||
|
|
2106bd6527 | ||
|
|
12f748fe8e | ||
|
|
b6c8e116af | ||
|
|
4427a9570f | ||
|
|
3c237b3c9a | ||
|
|
2914fe7b5c | ||
|
|
829a7299e1 | ||
|
|
f2e86a3df7 | ||
|
|
f64f0c5ba5 | ||
|
|
6d1e95cc52 | ||
|
|
b9de23534e | ||
|
|
55872427f2 | ||
|
|
9e2e02d58e | ||
|
|
00e8a3b084 | ||
|
|
988f7135ef | ||
|
|
0baf23df95 | ||
|
|
81e2d0e2f4 | ||
|
|
b57b333a39 | ||
|
|
effa0f2615 | ||
|
|
8769d86c04 | ||
|
|
663e41e98c | ||
|
|
d97cda1fa5 | ||
|
|
c512fd5279 | ||
|
|
d4de0a8859 | ||
|
|
4f7ba84e9a | ||
|
|
e1619707f6 | ||
|
|
ad34bc5c5a | ||
|
|
2975ae88f2 | ||
|
|
2624877232 | ||
|
|
eb6a3c6724 | ||
|
|
0a0392e7ee | ||
|
|
3817f2d728 | ||
|
|
860631f910 | ||
|
|
51ba24c1b2 | ||
|
|
22987e7d94 | ||
|
|
02ea8e7ea4 | ||
|
|
691aa936cc | ||
|
|
78a1ed6899 | ||
|
|
eac34ec36f | ||
|
|
8aa97d7118 | ||
|
|
8ad8bfdb5d | ||
|
|
4c2e4e01f2 | ||
|
|
2ecb681277 | ||
|
|
54dc8374ab | ||
|
|
8c4253f145 | ||
|
|
4483e9c7e5 | ||
|
|
65c9c4ce95 | ||
|
|
5434dc59df | ||
|
|
20290bbfa6 | ||
|
|
1dfd70c20c | ||
|
|
feaf528a60 | ||
|
|
11da63cf4e | ||
|
|
28ea73301e | ||
|
|
cd9b85fd4f | ||
|
|
bd9ed060e4 | ||
|
|
071e7500a2 | ||
|
|
b7556c07bd | ||
|
|
6b9ec3a46e | ||
|
|
ad66e79f46 | ||
|
|
b12220e640 | ||
|
|
204305a88f | ||
|
|
0f96ed8adb | ||
|
|
43e75a0a8c | ||
|
|
ad1f5d68e9 | ||
|
|
ff8206f7c8 | ||
|
|
8298daf686 | ||
|
|
168477ea34 | ||
|
|
b5d4d4662b | ||
|
|
6e6d94752a | ||
|
|
b2bcfc26b2 | ||
|
|
8fb62445c4 | ||
|
|
c0cc45975c | ||
|
|
0fe9bd5a95 | ||
|
|
9522799421 | ||
|
|
fe25928e18 | ||
|
|
eccf7dbdc4 | ||
|
|
e0bcf282ff | ||
|
|
1f462ba166 | ||
|
|
0e7885bc9f | ||
|
|
5ad1ea1e25 | ||
|
|
4978641271 | ||
|
|
ef6f698d86 | ||
|
|
416a82fcc6 | ||
|
|
2f111b6b8d | ||
|
|
24c3bb07a0 | ||
|
|
951b708f61 | ||
|
|
5a0897c5cb | ||
|
|
7a6673a877 | ||
|
|
3ebdca5e96 | ||
|
|
22a8ba9ef5 | ||
|
|
dcfa3fa2db | ||
|
|
fabf0a121c | ||
|
|
3a1c31d8cb | ||
|
|
e527b8e588 | ||
|
|
a2b4da8483 | ||
|
|
710807fd3b | ||
|
|
2e9f23a2f4 | ||
|
|
bf082292c2 | ||
|
|
59d89e34f0 | ||
|
|
cac7f48fa7 | ||
|
|
9e6b4fae29 | ||
|
|
16ef412663 | ||
|
|
5f55f7d76a | ||
|
|
9243a6e369 | ||
|
|
686d1f7cb6 | ||
|
|
e7a9a37624 | ||
|
|
7d41760f9a | ||
|
|
4def95865c | ||
|
|
109052fdc7 | ||
|
|
d6bb000115 | ||
|
|
18a9a882f5 | ||
|
|
acdbb79d43 | ||
|
|
1fd072837b | ||
|
|
a34f34917a | ||
|
|
d6041c93c4 | ||
|
|
2e2701de55 | ||
|
|
70a9b97f81 | ||
|
|
72a4376248 | ||
|
|
316e9a1863 | ||
|
|
c892fc4b34 | ||
|
|
b619f5bf36 | ||
|
|
06b7d7ed14 | ||
|
|
a222ea2c99 | ||
|
|
eb84c6a55b | ||
|
|
93ab4f1e3a | ||
|
|
5e252bb3f6 | ||
|
|
d6265e2c50 | ||
|
|
e1229dca28 | ||
|
|
06e697e029 | ||
|
|
0bb3286a7d | ||
|
|
2fd9ea600a | ||
|
|
f4eea875e2 | ||
|
|
e8934f7a81 | ||
|
|
8271db2d9d | ||
|
|
995916c923 | ||
|
|
799d75413f | ||
|
|
8166be1a09 | ||
|
|
684374f353 | ||
|
|
de7351fd84 | ||
|
|
d2ca706f86 | ||
|
|
c859e341aa | ||
|
|
717fe8fae5 | ||
|
|
1ddf45bf52 | ||
|
|
ae7530e33f | ||
|
|
d0a016221c | ||
|
|
9286b55702 | ||
|
|
31c9e19abb | ||
|
|
ef8f3d1419 | ||
|
|
2759c223be | ||
|
|
b3c770898a | ||
|
|
fee8491a8e | ||
|
|
a80d79fd40 | ||
|
|
4b921be69d | ||
|
|
1799a81079 |
No files matched your search
@@ -7,6 +7,11 @@ libsslh.a
|
||||
sslh-fork
|
||||
sslh-select
|
||||
sslh-ev
|
||||
systemd-sslh-generator
|
||||
sslh.8.gz
|
||||
tags
|
||||
version.h
|
||||
/config.status
|
||||
/config.log
|
||||
/config.h
|
||||
/Makefile
|
||||
@@ -1,3 +1,102 @@
|
||||
ech branch:
|
||||
This is an experimental version and does not work.
|
||||
Do not use it unless you are experimenting with ECH
|
||||
and an experimental wolfSSL.
|
||||
|
||||
A lot of context is discussed here:
|
||||
https://github.com/wolfSSL/wolfssl/issues/10485
|
||||
|
||||
What it does:
|
||||
- It creates a public ECH configuration for the
|
||||
first public name (configure in the `listen`
|
||||
section of the configuration file). This gets
|
||||
printed in a file configured in `ech_conf_sink`
|
||||
(see ech_test.cfg example).
|
||||
- Upon incoming connections, it will call an
|
||||
imaginary wolfSSL_GetEchClientHelloInner().
|
||||
The resulting packet is hexdumped.
|
||||
- It will also use wolfSSL to decode the SNI and
|
||||
print it (but does not yet use it to forward).
|
||||
|
||||
Most things happen in tls_ech.c, in particular there
|
||||
is a stub of GetEchClientHelloInner() that needs to
|
||||
be removed when the function becomes available in
|
||||
wolfSSL.
|
||||
|
||||
TODO:
|
||||
Change configure script to check for appropriate
|
||||
wolfSSL version.
|
||||
Create ECH configurations for each public name
|
||||
Link decoded InnerHello to SNI extraction code
|
||||
....
|
||||
Error cases still to be worked out (see
|
||||
conversations on github)
|
||||
Profit!
|
||||
|
||||
|
||||
|
||||
v2.3.0:
|
||||
Added `max_connections` setting to `listen` and
|
||||
`protocol` configuration; see the
|
||||
[guide](doc/max_connections.md) for more
|
||||
information.
|
||||
|
||||
accept() is now disabled for some time when ulimit -n is reached.
|
||||
|
||||
Support proxyprotocol on incoming connections. See
|
||||
the [guide](doc/proxyprotocol.md) for information.
|
||||
|
||||
Fix proxyprotocol target field (Thanks to Github user 404-Not-Found)
|
||||
|
||||
Fix memory leak in regex probe.
|
||||
|
||||
v2.2.4:
|
||||
Fix CVE-2025-46806 for "Misaligned Memory Accesses in `is_openvpn_protocol()`"
|
||||
|
||||
Fix CVE-2025-46807 for "File Descriptor Exhaustion in sslh-select and sslh-ev"
|
||||
|
||||
Fix potential parsing of undefined data in syslog
|
||||
probe (no CVE assigned)
|
||||
|
||||
Thanks to Matthias Gerstner of the SUSE security
|
||||
team for the security review that found these
|
||||
defects!
|
||||
|
||||
v2.2.3:
|
||||
Reverse older commit: version.h cannot be included
|
||||
without breaking the build (everything recompiles
|
||||
every time) and the release archive creation (which
|
||||
relies on git tags).
|
||||
|
||||
v2.2.2:
|
||||
Fix potential vulnerability similar to CVE-2020-28935
|
||||
|
||||
v2.2.1:
|
||||
Added a boolean setting "is_unix" for listen and
|
||||
protocol entries. This will use the 'host' setting
|
||||
as a path name to a socket file, and connections
|
||||
(listening or connecting) will be performed on Unix
|
||||
socket instead of Internet sockets.
|
||||
|
||||
Support HAProxy's proxyprotocol on the backend
|
||||
server side.
|
||||
|
||||
Lots of documentation about a new, simpler way to
|
||||
perform transparent proxying.
|
||||
|
||||
New "verbose" option that overrides all other
|
||||
verbose settings.
|
||||
|
||||
v2.1.3:
|
||||
Fix Landlock access to /etc/hosts.deny and
|
||||
/etc/hosts.allow.
|
||||
|
||||
v2.1.2:
|
||||
Fix inetd mode.
|
||||
|
||||
v2.1.1:
|
||||
Various minor fixes.
|
||||
|
||||
v2.1.0:
|
||||
Support for the Landlock LSM. After initial setup,
|
||||
sslh gives up all local file access rights.
|
||||
|
||||
+9
-25
@@ -3,15 +3,14 @@ VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
# landlock is checked by `configure` and set in config.h
|
||||
|
||||
# uncomment the following line to disable landlock
|
||||
# override undefine HAVE_LANDLOCK
|
||||
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBEV=1 # Use libev?
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
USELIBCAP= # Use libcap?
|
||||
USESYSTEMD= # Make use of systemd socket activation
|
||||
USELIBBSD?= # Use libbsd (needed to update process name in `ps`)
|
||||
COV_TEST= # Perform test coverage?
|
||||
PREFIX?=/usr
|
||||
BINDIR?=$(PREFIX)/sbin
|
||||
@@ -23,7 +22,7 @@ MAN=sslh.8.gz # man page name
|
||||
# itself
|
||||
|
||||
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
||||
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
|
||||
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(COV_TEST)),)
|
||||
@@ -35,8 +34,8 @@ AR ?= ar
|
||||
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
|
||||
LIBS=-lm -lpcre2-8
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o
|
||||
LIBS=-lm -lpcre2-8 @LIBS@
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o tls_ech.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
|
||||
OBJS_A=libsslh.a
|
||||
FORK_OBJS=sslh-fork.o $(OBJS_A)
|
||||
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
|
||||
@@ -44,11 +43,6 @@ EV_OBJS=processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o $(OBJS_A)
|
||||
|
||||
CONDITIONAL_TARGETS=
|
||||
|
||||
ifneq ($(strip $(USELIBWRAP)),)
|
||||
LIBS:=$(LIBS) -lwrap
|
||||
CPPFLAGS+=-DLIBWRAP
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(ENABLE_REGEX)),)
|
||||
CPPFLAGS+=-DENABLE_REGEX
|
||||
endif
|
||||
@@ -58,22 +52,12 @@ ifneq ($(strip $(USELIBCONFIG)),)
|
||||
CPPFLAGS+=-DLIBCONFIG
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBCAP)),)
|
||||
LIBS:=$(LIBS) -lcap
|
||||
CPPFLAGS+=-DLIBCAP
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USESYSTEMD)),)
|
||||
LIBS:=$(LIBS) -lsystemd
|
||||
CPPFLAGS+=-DSYSTEMD
|
||||
CONDITIONAL_TARGETS+=systemd-sslh-generator
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBBSD)),)
|
||||
LIBS:=$(LIBS) -lbsd
|
||||
CPPFLAGS+=-DLIBBSD
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBEV)),)
|
||||
CONDITIONAL_TARGETS+=sslh-ev
|
||||
endif
|
||||
@@ -126,7 +110,7 @@ $(MAN): sslh.pod Makefile
|
||||
# Create release: export clean tree and tag current
|
||||
# configuration
|
||||
release:
|
||||
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
git archive $(VERSION) --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
|
||||
|
||||
# Build docker image
|
||||
@@ -161,8 +145,8 @@ distclean: clean
|
||||
clean:
|
||||
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
|
||||
tags:
|
||||
ctags --globals -T *.[ch]
|
||||
tags: *.c *.h
|
||||
ctags *.[ch]
|
||||
|
||||
cscope:
|
||||
-find . -name "*.[chS]" >cscope.files
|
||||
|
||||
@@ -20,34 +20,72 @@ address.
|
||||
|
||||
`sslh` has the bells and whistles expected from a mature
|
||||
daemon: privilege and capabilities dropping, inetd support,
|
||||
systemd support, transparent proxying, chroot, logging,
|
||||
IPv4 and IPv6, TCP and UDP, a fork-based, a select-based
|
||||
model, and yet another based on libev for larger
|
||||
installations.
|
||||
systemd support, transparent proxying, support for HAProxy's
|
||||
proxyprotocol, chroot, logging, IPv4 and IPv6, TCP and UDP,
|
||||
a fork-based, a select-based model, and yet another based on
|
||||
libev for larger installations.
|
||||
|
||||
Install
|
||||
=======
|
||||
|
||||
Please refer to the [install guide](doc/INSTALL.md).
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
Matthias Gerstner from OpenSUSE has performed a code review
|
||||
of `sslh` from a security point of view, which revealed a
|
||||
number of problems, including two CVE. His findings have
|
||||
already been taken partly into account for the more critical
|
||||
ones. The [full
|
||||
review](https://security.opensuse.org/2025/06/13/sslh-denial-of-service-vulnerabilities.html)
|
||||
is well worth reading if you are using `sslh` in production.
|
||||
|
||||
Part of the securing your installation involves configuring
|
||||
connection limits. This is described in [this
|
||||
guide](doc/max_connections.md).
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Please refer to the [configuration guide](doc/config.md).
|
||||
|
||||
Transparent proxying
|
||||
--------------------
|
||||
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
This makes it easier to use the server's logs, and potential
|
||||
IP-based banning ability.
|
||||
|
||||
Configuration of transparent proxying is highly dependent on
|
||||
your network environment and infrastructure setup. There is
|
||||
no known generic approach, and if you do not find directions
|
||||
for your exact setup, you will probably need an extensive
|
||||
knowledge of network management and iptables setup".
|
||||
The same result can be achieved more easily by using
|
||||
`proxyprotocol` if the backend server supports it. This is a
|
||||
simple setting to add to the `sslh` protocol configuration,
|
||||
usually with an equivalently simple setting to add in
|
||||
the backend server configuration, so try that first. This is
|
||||
explained in a [separate document](doc/proxyprotocol.md).
|
||||
|
||||
This means services behind `sslh` (Apache, `sshd` and so on)
|
||||
will see the external IP and ports as if the external world
|
||||
connected directly to them. This simplifies IP-based access
|
||||
control (or makes it possible at all), and makes it possible
|
||||
to use IP-based banning tools such as `fail2ban`.
|
||||
|
||||
There are two methods. One uses additional virtual network
|
||||
interfaces. The principle and basic setup is described
|
||||
[here](doc/simple_transparent_proxy.md), with further
|
||||
scenarios described [there](doc/scenarios-for-simple-transparent-proxy.md).
|
||||
|
||||
There is also a guide to use [podman](doc/podman.md).
|
||||
|
||||
Another method uses iptable packet marking features, and is
|
||||
highly dependent on your network environment and
|
||||
infrastructure setup. There is no known generic approach,
|
||||
and if you do not find directions for your exact setup, you
|
||||
will probably need an extensive knowledge of network
|
||||
management and iptables setup".
|
||||
|
||||
It is described in its own [document](doc/tproxy.md).
|
||||
In most cases, you will be better off following the first
|
||||
method.
|
||||
|
||||
|
||||
Docker image
|
||||
|
||||
@@ -11,9 +11,13 @@ pidfile: "/var/run/sslh.pid";
|
||||
|
||||
# Change hostname with your external address name, or the IP
|
||||
# of the interface that receives connections
|
||||
# Default is to bind all interfaces. httpd can be started
|
||||
# first to bind on localhost, in which case sslh will bind
|
||||
# only other interfaces.
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; }
|
||||
{ host: "0.0.0.0"; port: "443"; },
|
||||
{ host: "[::]"; port: "443"; }
|
||||
);
|
||||
|
||||
|
||||
@@ -24,8 +28,6 @@ protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "1194"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "5222"; },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "443"; }
|
||||
);
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
#include <sys/types.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "proxyprotocol.h"
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
#ifndef SA_NOCLDWAIT
|
||||
#define SA_NOCLDWAIT 0
|
||||
#if HAVE_LIBCAP
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
/* Make use of systemd socket activation
|
||||
@@ -30,7 +31,7 @@
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
#ifdef HAVE_LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -42,7 +43,7 @@ struct sslhcfg_item cfg;
|
||||
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
|
||||
|
||||
|
||||
#ifdef LIBWRAP
|
||||
#ifdef HAVE_LIBWRAP
|
||||
#include <tcpd.h>
|
||||
int allow_severity =0, deny_severity = 0;
|
||||
#endif
|
||||
@@ -80,7 +81,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
|
||||
}
|
||||
if (sd > 0) {
|
||||
int i;
|
||||
*sockfd = malloc(sd * sizeof(*sockfd[0]));
|
||||
*sockfd = calloc(sd, sizeof(*sockfd[0]));
|
||||
CHECK_ALLOC(*sockfd, "malloc");
|
||||
for (i = 0; i < sd; i++) {
|
||||
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
|
||||
@@ -155,17 +156,91 @@ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
void print_inet_listen_info(int fd, const char* addr,
|
||||
struct sslhcfg_listen_item* cfg)
|
||||
{
|
||||
print_message(msg_config, "%d:\t%s\t[%s] [%s] [max_cnx: %d]\n",
|
||||
fd,
|
||||
addr,
|
||||
cfg->keepalive ? "keepalive" : "",
|
||||
cfg->is_udp ? "udp" : "",
|
||||
cfg->max_connections);
|
||||
}
|
||||
|
||||
|
||||
/* Start listening internet sockets for configuration entry 'index'
|
||||
* OUT: *sockfd[]: pointer to array of listen_endpoint object; we append new
|
||||
* endpoints to that array
|
||||
* IN: num_addr: how many entries are in sockfd[]
|
||||
* *cfg: configuration data for the endpoint we are adding
|
||||
* Return: new value of num_addr
|
||||
* */
|
||||
static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int res;
|
||||
|
||||
res = resolve_split_name(&start_addr, cfg->host, cfg->port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, cfg->keepalive, cfg->is_udp);
|
||||
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
(*sockfd)[num_addr-1].family = AF_INET;
|
||||
(*sockfd)[num_addr-1].endpoint_cfg = cfg;
|
||||
print_inet_listen_info((*sockfd)[num_addr-1].socketfd,
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
cfg);
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
/* Same, but for UNIX sockets */
|
||||
static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg)
|
||||
{
|
||||
int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
CHECK_RES_DIE(fd, "socket(AF_UNIX)");
|
||||
|
||||
int res = unlink(cfg->host);
|
||||
if ((res == -1) && (errno != ENOENT)) {
|
||||
print_message(msg_config_error, "unlink unix socket `%s':%d:%s\n", cfg->host, errno, strerror(errno));
|
||||
exit(4);
|
||||
}
|
||||
|
||||
struct sockaddr_un sun;
|
||||
sun.sun_family = AF_UNIX;
|
||||
strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1);
|
||||
printf("binding [%s]\n", sun.sun_path);
|
||||
res = bind(fd, (struct sockaddr*)&sun, sizeof(sun));
|
||||
CHECK_RES_DIE(res, "bind(AF_UNIX)");
|
||||
|
||||
res = listen(fd, 50);
|
||||
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
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].endpoint_cfg = cfg;
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
|
||||
/* Starts listening sockets on specified addresses.
|
||||
* OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects
|
||||
* Returns number of addresses bound
|
||||
*/
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int i, res;
|
||||
int num_addr = 0, keepalive = 0, udp = 0;
|
||||
int sd_socks = 0;
|
||||
int i;
|
||||
int num_addr = 0, sd_socks = 0;
|
||||
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
|
||||
@@ -178,22 +253,11 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
print_message(msg_config, "Listening to:\n");
|
||||
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
keepalive = cfg.listen[i].keepalive;
|
||||
udp = cfg.listen[i].is_udp;
|
||||
|
||||
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
||||
cfg.listen[i].keepalive ? "keepalive" : "",
|
||||
cfg.listen[i].is_udp ? "udp" : "");
|
||||
if (cfg.listen[i].is_unix) {
|
||||
num_addr = start_listen_unix(sockfd, num_addr, &cfg.listen[i]);
|
||||
} else {
|
||||
num_addr = start_listen_inet(sockfd, num_addr, &cfg.listen[i]);
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
@@ -243,12 +307,17 @@ int is_same_machine(struct addrinfo* from)
|
||||
|
||||
/* Transparent proxying: bind the peer address of fd to the peer address of
|
||||
* fd_from */
|
||||
#define IP_TRANSPARENT 19
|
||||
#ifndef IP_TRANSPARENT
|
||||
#define IP_TRANSPARENT 19
|
||||
#endif
|
||||
#ifndef IP_BIND_ADDRESS_NO_PORT
|
||||
#define IP_BIND_ADDRESS_NO_PORT 24
|
||||
#endif
|
||||
int bind_peer(int fd, int fd_from)
|
||||
{
|
||||
struct addrinfo from;
|
||||
struct sockaddr_storage ss;
|
||||
int res, trans = 1;
|
||||
int res, enable = 1, disable = 0;
|
||||
|
||||
memset(&from, 0, sizeof(from));
|
||||
from.ai_addr = (struct sockaddr*)&ss;
|
||||
@@ -264,15 +333,17 @@ int bind_peer(int fd, int fd_from)
|
||||
return 0;
|
||||
|
||||
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &enable, sizeof(enable));
|
||||
CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT");
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
||||
CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR");
|
||||
#else
|
||||
if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans));
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(enable));
|
||||
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res);
|
||||
#ifdef IPV6_BINDANY
|
||||
} else { /* IPv6 */
|
||||
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans));
|
||||
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &enable, sizeof(enable));
|
||||
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res);
|
||||
#endif /* IPV6_BINDANY */
|
||||
}
|
||||
@@ -284,19 +355,30 @@ int bind_peer(int fd, int fd_from)
|
||||
"bind", errno, strerror(errno));
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is more than one transparent mode proxy going on, such as
|
||||
* using sslh as the target of stunnel also in transparent mode, then
|
||||
* the (ip,port) combination will already be bound for the previous application.
|
||||
* In that case, the best we can do is bind with a different port.
|
||||
* This does mean the local server can't use the ident protocol as the port will
|
||||
* have changed, but most people won't care.
|
||||
* Also note that stunnel uses the same logic for the same situation.
|
||||
*/
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(disable));
|
||||
CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR");
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, &enable, sizeof(enable));
|
||||
CHECK_RES_RETURN(res, "setsockopt IP_BIND_ADDRESS_NO_PORT", res);
|
||||
((struct sockaddr_in *)from.ai_addr)->sin_port = 0;
|
||||
res = bind(fd, from.ai_addr, from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "bind", res);
|
||||
/*
|
||||
* There was a serious problem, when daisy-chaining programs using the same
|
||||
* ip transparent mechanism, as sslh uses. stunnel was mentioned in a previous
|
||||
* comment. This problem should now be solved through the two methods, getting
|
||||
* a connection established:
|
||||
* In the first try, SO_REUSEADDR is set to socket, which will allow the same
|
||||
* IP-address:port tuple, as it is used by another application. The check for
|
||||
* inconsistency with other connections (same 4-value-tupel) is done at the
|
||||
* moment, when the connection gets established.
|
||||
* If that fails, SO_REUSEADDR gets removed and IP_BIND_ADDRESS_NO_PORT get set.
|
||||
* This will search for a free port, which will not collide with current
|
||||
* connections. Read more in this excellent blog-post:
|
||||
* https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections
|
||||
* The problem will still appear, if the another application in the daisy-chain
|
||||
* does not use similar mechanisms. In that case you must either pull this
|
||||
* application at the beginning of the chain, or get it fixed.
|
||||
*/
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -319,11 +401,8 @@ int set_nonblock(int fd)
|
||||
}
|
||||
|
||||
|
||||
/* Connect to first address that works and returns a file descriptor, or -1 if
|
||||
* none work.
|
||||
* If transparent proxying is on, use fd_from peer address on external address
|
||||
* of new file descriptor. */
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
/* Connects to INET/INET6 domain sockets and return a fd */
|
||||
static int connect_inet(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct addrinfo *a, from;
|
||||
struct sockaddr_storage ss;
|
||||
@@ -342,10 +421,10 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
resolve_split_name(&(cnx->proto->saddr), cnx->proto->host,
|
||||
cnx->proto->port);
|
||||
}
|
||||
|
||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||
/* When transparent, make sure both connections use the same address family */
|
||||
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
/* When transparent or using proxyprotocol, make sure both
|
||||
* connections use the same address family (e.g. IP4 on both sides) */
|
||||
if ((transparent || cnx->proto->proxyprotocol_is_present) && (a->ai_family != from.ai_addr->sa_family))
|
||||
continue;
|
||||
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
@@ -390,8 +469,65 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Connects to AF_UNIX domain sockets */
|
||||
static int connect_unix(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
struct sockaddr_un* sun = (struct sockaddr_un*)&ss;
|
||||
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
sun->sun_family = AF_UNIX;
|
||||
strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1);
|
||||
|
||||
int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun));
|
||||
CHECK_RES_RETURN(res, "connect", res);
|
||||
|
||||
if (blocking == NON_BLOCKING) {
|
||||
set_nonblock(fd);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connect to the first backend server address that works and updates the *cnx
|
||||
* object accordingly (in cnx->q[1].fd). Set that to -1 in case of failure.
|
||||
*
|
||||
* If transparent proxying is on, use fd_from peer address on external address
|
||||
* of new file descriptor.
|
||||
* If proxyprotocol is used, write header on new backend server connection
|
||||
* */
|
||||
void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (cnx->proto->is_unix) {
|
||||
fd = connect_unix(cnx, fd_from, blocking);
|
||||
} else {
|
||||
fd = connect_inet(cnx, fd_from, blocking);
|
||||
}
|
||||
cnx->q[1].fd = fd;
|
||||
|
||||
if (cnx->proto->proxyprotocol_is_present) {
|
||||
if (cnx->proto->proxyprotocol) {
|
||||
pp_write_header(cnx->proto->proxyprotocol, cnx);
|
||||
/* If pp_write_header() fails, it already logs a message and there is
|
||||
* nothing much we can do. The server side will probably close the
|
||||
* connection */
|
||||
} else {
|
||||
/* proxyprotocol == 0 means we remove a proxyprotocol header from
|
||||
* the client-side, and log PP information (as it will be lost
|
||||
* after sslh) */
|
||||
int pp_len = pp_log_connection(cnx->q[1].begin_deferred_data,
|
||||
cnx->q[1].deferred_data_size);
|
||||
if (pp_len > 0) {
|
||||
defer_skip(&cnx->q[1], pp_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Store some data to write to the queue later */
|
||||
int defer_write(struct queue *q, void* data, int data_size)
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size)
|
||||
{
|
||||
char *p;
|
||||
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
|
||||
@@ -403,25 +539,53 @@ int defer_write(struct queue *q, void* data, int data_size)
|
||||
q->begin_deferred_data = p;
|
||||
q->deferred_data = p + data_offset;
|
||||
p += data_offset + q->deferred_data_size;
|
||||
q->deferred_data_size += data_size;
|
||||
q->deferred_data_size += (int)data_size;
|
||||
memcpy(p, data, data_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* skip (remove) some data from the head of the queue */
|
||||
void defer_skip(struct queue *q, ssize_t data_size)
|
||||
{
|
||||
q->deferred_data += data_size;
|
||||
q->deferred_data_size -= data_size;
|
||||
}
|
||||
|
||||
/* Store some data to write *before* what's already in the queue */
|
||||
int defer_write_before(struct queue *q, void* data, ssize_t data_size)
|
||||
{
|
||||
char *p;
|
||||
|
||||
print_message(msg_fd, "writing deferred to beginning on fd %d\n", q->fd);
|
||||
p = malloc(q->deferred_data_size + data_size);
|
||||
CHECK_ALLOC(p, "malloc");
|
||||
|
||||
memcpy(p, data, data_size);
|
||||
memcpy(p + data_size, q->deferred_data, q->deferred_data_size);
|
||||
|
||||
free(q->begin_deferred_data);
|
||||
|
||||
q->begin_deferred_data = p;
|
||||
q->deferred_data = p;
|
||||
q->deferred_data_size += (int)data_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* tries to flush some of the data for specified queue
|
||||
* Upon success, the number of bytes written is returned.
|
||||
* Upon failure, -1 returned (e.g. connexion closed)
|
||||
* */
|
||||
int flush_deferred(struct queue *q)
|
||||
{
|
||||
int n;
|
||||
ssize_t n;
|
||||
|
||||
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
|
||||
|
||||
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
||||
if (n == -1)
|
||||
return n;
|
||||
return (int)n;
|
||||
|
||||
if (n == q->deferred_data_size) {
|
||||
/* All has been written -- release the memory */
|
||||
@@ -432,10 +596,10 @@ int flush_deferred(struct queue *q)
|
||||
} else {
|
||||
/* There is data left */
|
||||
q->deferred_data += n;
|
||||
q->deferred_data_size -= n;
|
||||
q->deferred_data_size -= (int)n;
|
||||
}
|
||||
|
||||
return n;
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
|
||||
@@ -458,6 +622,37 @@ void dump_connection(struct connection *cnx)
|
||||
}
|
||||
|
||||
|
||||
/* *cnx must have its proto field probed already.
|
||||
* If required, increment the connection count for this protocol.
|
||||
* Returns 1 if connection count is exceeded, 0 otherwise */
|
||||
int inc_proto_connections(struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
proto->num_connections++;
|
||||
if (proto->max_connections_is_present) {
|
||||
print_message(msg_connections, "Proto %s +1: %d/%d cnx\n",
|
||||
proto->name,
|
||||
proto->num_connections,
|
||||
proto->max_connections);
|
||||
if (proto->num_connections > proto->max_connections) {
|
||||
print_message(msg_connections_error, "%s: too many connections, dropping\n", proto->name);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dec_proto_connections(struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (proto) {
|
||||
proto->num_connections--;
|
||||
print_message(msg_connections, "Proto %s -1: %d/%d cnx\n",
|
||||
proto->name,
|
||||
proto->num_connections,
|
||||
proto->max_connections);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* moves data from one fd to other
|
||||
*
|
||||
@@ -470,7 +665,8 @@ void dump_connection(struct connection *cnx)
|
||||
int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
int target, from, size_r, size_w;
|
||||
int target, from;
|
||||
ssize_t size_r, size_w;
|
||||
|
||||
target = target_q->fd;
|
||||
from = from_q->fd;
|
||||
@@ -515,7 +711,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
|
||||
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
|
||||
|
||||
return size_w;
|
||||
return (int)size_w;
|
||||
}
|
||||
|
||||
/* returns a string that prints the IP and port of the sockaddr */
|
||||
@@ -524,6 +720,9 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
char host[NI_MAXHOST], serv[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
memset(host, 0, sizeof(host));
|
||||
memset(serv, 0, sizeof(serv));
|
||||
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
@@ -647,7 +846,7 @@ int get_connection_desc(struct connection_desc* desc, const struct connection *c
|
||||
|
||||
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
#ifdef HAVE_LIBBSD
|
||||
struct connection_desc d;
|
||||
|
||||
if (!desc) {
|
||||
@@ -672,7 +871,7 @@ void set_proctitle_shovel(struct connection_desc* desc, const struct connection
|
||||
*/
|
||||
int check_access_rights(int in_socket, const char* service)
|
||||
{
|
||||
#ifdef LIBWRAP
|
||||
#ifdef HAVE_LIBWRAP
|
||||
union {
|
||||
struct sockaddr saddr;
|
||||
struct sockaddr_storage ss;
|
||||
@@ -699,7 +898,7 @@ int check_access_rights(int in_socket, const char* service)
|
||||
}
|
||||
}
|
||||
|
||||
if (!hosts_ctl(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);
|
||||
close(in_socket);
|
||||
return -1;
|
||||
@@ -708,16 +907,23 @@ int check_access_rights(int in_socket, const char* service)
|
||||
return 0;
|
||||
}
|
||||
|
||||
volatile sig_atomic_t received_sigchld;
|
||||
|
||||
void sig_sigchld(int sig)
|
||||
{
|
||||
received_sigchld = 1;
|
||||
}
|
||||
|
||||
|
||||
void setup_signals(void)
|
||||
{
|
||||
int res;
|
||||
struct sigaction action;
|
||||
|
||||
/* Request no SIGCHLD is sent upon termination of
|
||||
* the children */
|
||||
/* Set SIGCHLD to sig_sigchld() */
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = NULL;
|
||||
action.sa_flags = SA_NOCLDWAIT;
|
||||
action.sa_handler = sig_sigchld;
|
||||
action.sa_flags = SA_NOCLDSTOP;
|
||||
res = sigaction(SIGCHLD, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
@@ -732,13 +938,12 @@ void setup_signals(void)
|
||||
action.sa_handler = SIG_IGN;
|
||||
res = sigaction(SIGPIPE, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Ask OS to keep capabilities over a setuid(nonzero) */
|
||||
void set_keepcaps(int val) {
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
int res;
|
||||
res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0);
|
||||
if (res) {
|
||||
@@ -751,7 +956,7 @@ void set_keepcaps(int val) {
|
||||
/* Returns true if anything requires transparent proxying. */
|
||||
static int use_transparent(void)
|
||||
{
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
if (cfg.transparent)
|
||||
return 1;
|
||||
|
||||
@@ -767,7 +972,7 @@ static int use_transparent(void)
|
||||
* IN: cap_net_admin: set to 1 to set CAP_NET_RAW
|
||||
* */
|
||||
void set_capabilities(int cap_net_admin) {
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
int res;
|
||||
cap_t caps;
|
||||
cap_value_t cap_list[10];
|
||||
@@ -845,27 +1050,48 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef O_NOFOLLOW
|
||||
#define O_NOFOLLOW 0
|
||||
#endif
|
||||
|
||||
/* Writes my PID */
|
||||
void write_pid_file(const char* pidfile)
|
||||
{
|
||||
FILE *f;
|
||||
int res;
|
||||
int fd;
|
||||
char pidbuf[32];
|
||||
size_t len, written = 0;
|
||||
ssize_t res;
|
||||
|
||||
f = fopen(pidfile, "w");
|
||||
if (!f) {
|
||||
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
|
||||
exit(3);
|
||||
/* Format PID as string */
|
||||
len = snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid());
|
||||
if (len >= sizeof(pidbuf)) {
|
||||
print_message(msg_system_error, "write_pid_file: PID string too long\n");
|
||||
return;
|
||||
}
|
||||
|
||||
res = fprintf(f, "%d\n", getpid());
|
||||
if (res < 0) {
|
||||
print_message(msg_system_error, "write_pid_file:fprintf:%s", strerror(errno));
|
||||
exit(3);
|
||||
/* Open file with O_NOFOLLOW to prevent symlink attacks (Similar to CVE-2020-28935) */
|
||||
fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW ,0644);
|
||||
|
||||
if (fd == -1) {
|
||||
print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
res = fclose(f);
|
||||
if (res == EOF) {
|
||||
print_message(msg_system_error, "write_pid_file:fclose:%s", strerror(errno));
|
||||
exit(3);
|
||||
/* Write PID to file with proper error handling */
|
||||
while (written < len) {
|
||||
res = write(fd, pidbuf + written, len - written);
|
||||
if (res == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
print_message(msg_system_error, "write_pid_file: write: %s\n", strerror(errno));
|
||||
break;
|
||||
}
|
||||
written += res;
|
||||
}
|
||||
|
||||
/* Close file */
|
||||
if (close(fd) == -1) {
|
||||
print_message(msg_system_error, "write_pid_file: close: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,13 @@
|
||||
#define FD_SETSIZE 4096
|
||||
#endif
|
||||
|
||||
/* Cygwin does not define some symbols
|
||||
* */
|
||||
#ifndef SA_NOCLDWAIT
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
@@ -34,8 +41,14 @@
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
#include "version.h"
|
||||
|
||||
#undef MAX
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
@@ -104,6 +117,7 @@ struct connection {
|
||||
struct sslhcfg_protocols_item* proto; /* Where to connect to */
|
||||
|
||||
/* SOCK_STREAM */
|
||||
struct listen_endpoint* endpoint; /* Client-facing listen fd */
|
||||
enum connection_state state;
|
||||
time_t probe_timeout;
|
||||
|
||||
@@ -132,6 +146,9 @@ struct connection {
|
||||
struct listen_endpoint {
|
||||
int socketfd; /* file descriptor of listening socket */
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
int family; /* AF_INET | AF_UNIX */
|
||||
int num_connections; /* How many active connections on this endpoint */
|
||||
struct sslhcfg_listen_item* endpoint_cfg; /* the configuration item that corresponds to this endpoint */
|
||||
};
|
||||
|
||||
#define FD_CNXCLOSED 0
|
||||
@@ -154,7 +171,7 @@ typedef enum {
|
||||
/* common.c */
|
||||
void init_cnx(struct connection *cnx);
|
||||
int set_nonblock(int fd);
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
|
||||
void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
|
||||
int fd2fd(struct queue *target, struct queue *from);
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
|
||||
void resolve_name(struct addrinfo **out, char* fullname);
|
||||
@@ -168,17 +185,25 @@ void drop_privileges(const char* user_name, const char* chroot_path);
|
||||
void set_capabilities(int cap_net_admin);
|
||||
void write_pid_file(const char* pidfile);
|
||||
void dump_connection(struct connection *cnx);
|
||||
int inc_proto_connections(struct sslhcfg_protocols_item* cnx);
|
||||
void dec_proto_connections(struct sslhcfg_protocols_item* cnx);
|
||||
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[]);
|
||||
|
||||
int defer_write(struct queue *q, void* data, int data_size);
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size);
|
||||
void defer_skip(struct queue *q, ssize_t data_size);
|
||||
int defer_write_before(struct queue *q, void* data, ssize_t data_size);
|
||||
int flush_deferred(struct queue *q);
|
||||
|
||||
extern struct sslhcfg_item cfg;
|
||||
extern struct addrinfo *addr_listen;
|
||||
extern const char* server_type;
|
||||
|
||||
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
|
||||
extern int hosts_ctl();
|
||||
#endif
|
||||
|
||||
/* sslh-fork.c */
|
||||
void start_shoveler(int);
|
||||
|
||||
@@ -188,4 +213,7 @@ void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||
void setup_landlock(void);
|
||||
|
||||
|
||||
/* sslh-fork.c or processes.c, depending on the model */
|
||||
void setup_sigchld(void);
|
||||
|
||||
#endif
|
||||
+15
@@ -3,7 +3,22 @@
|
||||
#ifndef CONFIG_H
|
||||
/* Template for config.h, filled by `configure`. */
|
||||
|
||||
/* Libwrap, to support host_ctl, /etc/allow and /etc/deny */
|
||||
#undef HAVE_LIBWRAP
|
||||
|
||||
/* Landlock sandboxing Linux LSM */
|
||||
#undef HAVE_LANDLOCK
|
||||
|
||||
/* Support for Proxy-protocol using libproxyprotocol */
|
||||
#undef HAVE_PROXYPROTOCOL
|
||||
|
||||
/* libcap support, to use Linux capabilities */
|
||||
#undef HAVE_LIBCAP
|
||||
|
||||
/* libbsd, to change process name */
|
||||
#undef HAVE_LIBBSD
|
||||
|
||||
/* wolfssl, to support Encrypted Client Hello (ECH) */
|
||||
#undef HAVE_WOLFSSL
|
||||
|
||||
#endif
|
||||
@@ -1445,6 +1445,53 @@ fi
|
||||
|
||||
} # ac_fn_c_try_compile
|
||||
|
||||
# ac_fn_c_try_link LINENO
|
||||
# -----------------------
|
||||
# Try to link conftest.$ac_ext, and return whether this succeeded.
|
||||
ac_fn_c_try_link ()
|
||||
{
|
||||
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext
|
||||
if { { ac_try="$ac_link"
|
||||
case "(($ac_try" in
|
||||
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
|
||||
*) ac_try_echo=$ac_try;;
|
||||
esac
|
||||
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
|
||||
printf "%s\n" "$ac_try_echo"; } >&5
|
||||
(eval "$ac_link") 2>conftest.err
|
||||
ac_status=$?
|
||||
if test -s conftest.err; then
|
||||
grep -v '^ *+' conftest.err >conftest.er1
|
||||
cat conftest.er1 >&5
|
||||
mv -f conftest.er1 conftest.err
|
||||
fi
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; } && {
|
||||
test -z "$ac_c_werror_flag" ||
|
||||
test ! -s conftest.err
|
||||
} && test -s conftest$ac_exeext && {
|
||||
test "$cross_compiling" = yes ||
|
||||
test -x conftest$ac_exeext
|
||||
}
|
||||
then :
|
||||
ac_retval=0
|
||||
else $as_nop
|
||||
printf "%s\n" "$as_me: failed program was:" >&5
|
||||
sed 's/^/| /' conftest.$ac_ext >&5
|
||||
|
||||
ac_retval=1
|
||||
fi
|
||||
# Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
|
||||
# created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
|
||||
# interfere with the next link command; also delete a directory that is
|
||||
# left behind by Apple's compiler. We do this before executing the actions.
|
||||
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
|
||||
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
|
||||
as_fn_set_status $ac_retval
|
||||
|
||||
} # ac_fn_c_try_link
|
||||
|
||||
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
|
||||
# -------------------------------------------------------
|
||||
# Tests whether HEADER exists and can be compiled using the include files in
|
||||
@@ -3159,6 +3206,171 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
|
||||
ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
||||
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for hosts_ctl in -lwrap" >&5
|
||||
printf %s "checking for hosts_ctl in -lwrap... " >&6; }
|
||||
if test ${ac_cv_lib_wrap_hosts_ctl+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lwrap $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
char hosts_ctl ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return hosts_ctl ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_wrap_hosts_ctl=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_wrap_hosts_ctl=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.beam \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wrap_hosts_ctl" >&5
|
||||
printf "%s\n" "$ac_cv_lib_wrap_hosts_ctl" >&6; }
|
||||
if test "x$ac_cv_lib_wrap_hosts_ctl" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LIBWRAP 1" >>confdefs.h
|
||||
LIBS="$LIBS -lwrap"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for cap_get_proc in -lcap" >&5
|
||||
printf %s "checking for cap_get_proc in -lcap... " >&6; }
|
||||
if test ${ac_cv_lib_cap_cap_get_proc+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lcap $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
char cap_get_proc ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return cap_get_proc ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_cap_cap_get_proc=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_cap_cap_get_proc=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.beam \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cap_cap_get_proc" >&5
|
||||
printf "%s\n" "$ac_cv_lib_cap_cap_get_proc" >&6; }
|
||||
if test "x$ac_cv_lib_cap_cap_get_proc" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LIBCAP 1" >>confdefs.h
|
||||
LIBS="$LIBS -lcap"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle in -lbsd" >&5
|
||||
printf %s "checking for setproctitle in -lbsd... " >&6; }
|
||||
if test ${ac_cv_lib_bsd_setproctitle+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lbsd $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
char setproctitle ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return setproctitle ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_bsd_setproctitle=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_bsd_setproctitle=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.beam \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_setproctitle" >&5
|
||||
printf "%s\n" "$ac_cv_lib_bsd_setproctitle" >&6; }
|
||||
if test "x$ac_cv_lib_bsd_setproctitle" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LIBBSD 1" >>confdefs.h
|
||||
LIBS="$LIBS -lbsd"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wolfSSL_Init in -lwolfssl" >&5
|
||||
printf %s "checking for wolfSSL_Init in -lwolfssl... " >&6; }
|
||||
if test ${ac_cv_lib_wolfssl_wolfSSL_Init+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lwolfssl $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
char wolfSSL_Init ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return wolfSSL_Init ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_wolfssl_wolfSSL_Init=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_wolfssl_wolfSSL_Init=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.beam \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wolfssl_wolfSSL_Init" >&5
|
||||
printf "%s\n" "$ac_cv_lib_wolfssl_wolfSSL_Init" >&6; }
|
||||
if test "x$ac_cv_lib_wolfssl_wolfSSL_Init" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_WOLFSSL 1" >>confdefs.h
|
||||
LIBS="$LIBS -lwolfssl"
|
||||
fi
|
||||
|
||||
|
||||
ac_header= ac_cache=
|
||||
for ac_item in $ac_header_c_list
|
||||
do
|
||||
@@ -3199,6 +3411,21 @@ then :
|
||||
fi
|
||||
|
||||
done
|
||||
for ac_header in proxy_protocol.h
|
||||
do :
|
||||
ac_fn_c_check_header_compile "$LINENO" "proxy_protocol.h" "ac_cv_header_proxy_protocol_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_proxy_protocol_h" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_PROXY_PROTOCOL_H 1" >>confdefs.h
|
||||
printf "%s\n" "#define HAVE_PROXYPROTOCOL 1" >>confdefs.h
|
||||
LIBS="$LIBS -lproxyprotocol"
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
LIBS="$LIBS"
|
||||
|
||||
|
||||
cat >confcache <<\_ACEOF
|
||||
# This file is a shell script that caches the results of configure
|
||||
# tests run on this system so they can be shared between configure
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
|
||||
dnl Use autoconf to generate the `configure` script from this and Makefile.in
|
||||
dnl This is awkardly adapted from https://github.com/edrosten/autoconf_tutorial
|
||||
dnl (all mistakes mine)
|
||||
|
||||
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" ], [])
|
||||
|
||||
dnl TODO: check that wolfssl contains wolfSSL_GetEchClientHelloInner instead of just existing
|
||||
AC_CHECK_LIB([wolfssl], [wolfSSL_Init], [AC_DEFINE(HAVE_WOLFSSL) LIBS="$LIBS -lwolfssl" ], [])
|
||||
|
||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
||||
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
|
||||
|
||||
LIBS="$LIBS"
|
||||
AC_SUBST([LIBS])
|
||||
|
||||
AC_OUTPUT
|
||||
@@ -0,0 +1,331 @@
|
||||
# Daisy-Chaining-Transparency #
|
||||
|
||||
This documentation goes a level deeper, what happens in the operating system with IP-addresses, and why some combinations of programs are failing, when they use the same transparency method.
|
||||
|
||||
There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.
|
||||
|
||||
Unfortunately you will see a lot of errors popping out: **Address already in use**
|
||||
|
||||
[This article from Cloudflare blog](https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections) explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem.
|
||||
|
||||
Let us look to the following example: We have sslh (S) accepting connections from a client (C) and forwarding one of those connections to a man-in-the-middle (M), which finally forwards this connection to sshd. If everything works perfectly, we would like to see those connections.
|
||||
|
||||

|
||||
|
||||
But unfortunately we are receiving in many constellations errors, when M tries to open its connection to our final target sshd, here called T.
|
||||
|
||||
Let us look more close, why that is happening. We need for this two terminal windows on the same server.
|
||||
|
||||
### First example, uncooperative applications ###
|
||||
|
||||
As the problem has nothing to do with transparency itself, but only of reuse of same IP-addresses and ports, we avoid the overhead of the additional capablities, to keep the example easy and clear.
|
||||
|
||||
In the first terminal we are starting python3 and entering the following three lines:
|
||||
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
>>>
|
||||
```
|
||||
|
||||
Now we are going to the second terminal window, and trying just the same:
|
||||
|
||||
```
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
OSError: [Errno 98] Address already in use
|
||||
sock.bind(("192.168.255.254", 12346)) ##this however works!!
|
||||
```
|
||||
|
||||
Here we are getting the error, which caused many of us hours of research.
|
||||
The problem is caused by the fact, that the kernel does not know at the moment of the bind()-call, how we want to use the socket. If we use this as a server socket, and will do a listen()-call as next, this will not work, as for server sockets, the two-value tuple ADDRESS:PORT needs to be unique only to the process, tied to this socket.
|
||||
|
||||
That is the reason, that there are port ranges, reserved for servers, where the administrator is responsible not to assign the same port to two applications.
|
||||
|
||||
But as server ports are coming from a range, which will not be used for client connections, a server can be sure, that if it is started at any time in the future, no outgoing client has used its port for an outbound connection.
|
||||
|
||||
Clients are usually using ports from a so called [ephemeral port range](https://en.wikipedia.org/wiki/Ephemeral_port).
|
||||
|
||||
However, for clients each connection is valid, as long as one value in the four-tuples describing the connection is different. In our example above, the two values from the destination are different, so this connection could be established (in theory) without conflicts.
|
||||
|
||||
To make that happen, you need to deploy a special socket option to this socket, to explain, that we "know the risks" and we will reuse the ip-port combination.
|
||||
|
||||
And, as we see from the second bind() in the second example, the error message: _**Address already in use**_ really means: _**Address:Port already in use**_
|
||||
|
||||
### Taking Care, part I ###
|
||||
|
||||
Ok, now we are entering our two terminals again, pressing Ctrl-D to finish python, and start a new session like this in both terminals:
|
||||
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
>>>
|
||||
```
|
||||
|
||||
And we will see, that the problem is solved, when both applications are taking care.
|
||||
|
||||
### Taking Care, part II ###
|
||||
|
||||
Ok, now we are going back to our terminals, pressing again Ctrl-D to finish python, and start new sessions like this:
|
||||
|
||||
In the first terminal we repeat the input from our first example, without the setsockopt() call:
|
||||
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
>>>
|
||||
```
|
||||
|
||||
in the second terminal, we enter our modified cooperative example:
|
||||
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
OSError: [Errno 98] Address already in use
|
||||
>>>
|
||||
```
|
||||
|
||||
Oops, here is our error again. And this is the reason, why I called this method "cooperative". As the first application has bound to that IP:PORT combination, without telling, that "it knows the risk", the kernel denies us, to use this combinations, as we may break the already active application. The first application is not "cooperative" :-(
|
||||
|
||||
Ok, but the kernel gives as some more possibilities: As we now get connections from a uncoperative application, we can no longer use the Client-IP-Port combination. We need to use a conflict free port for the client IP, to succeed. So lets get back to terminal 2 and continue after the error with the following commands:
|
||||
|
||||
```
|
||||
user@host:~$ python3
|
||||
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import socket
|
||||
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
>>> sock.bind(("192.168.255.254", 12345))
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
OSError: [Errno 98] Address already in use
|
||||
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
|
||||
>>> sock.setsockopt(socket.SOL_IP, socket.IP_BIND_ADDRESS_NO_PORT, 1)
|
||||
>>> sock.bind(("192.168.255.254", 0))
|
||||
>>>
|
||||
```
|
||||
|
||||
This last example makes it neccessary, that you use a recent version of the python3 socket library, as older versions, have the option _IP_BIND_ADDRESS_NO_PORT_ not yet defined.
|
||||
|
||||
With his behaviour, we are telling the kernel, that the kernel should assign us a conflict free port address at latest possible moment, while calling connect().
|
||||
|
||||
From `man ip`:
|
||||
|
||||
```
|
||||
Inform the kernel to not reserve an ephemeral port when using bind(2)
|
||||
with a port number of 0. The port will later be automatically chosen
|
||||
at connect(2) time, in a way that allows sharing a source port as
|
||||
long as the 4-tuple is unique.
|
||||
```
|
||||
|
||||
Ok, now with those two actions an application is really ready for cooperative working in ip-transparent chains.
|
||||
|
||||
If you are running in problems, with any kind of application, where you are redirecting transparent traffic to from sslh, check the following:
|
||||
|
||||
- Are you using a recent version of sslh, having the described feature enabled
|
||||
- are you using the most recent version of the other application
|
||||
If you can confirm both checks, tell the maintainers of the other application, about possible fixes, and send them a link to this article.
|
||||
|
||||
## Practical Use Of Daisy-Chaining: Proxytunnel Endpoint ##
|
||||
|
||||
One reasons, why we want to combine two programs is related to the core functionality of sslh. You wish to hide your ssh connection behind a https port.
|
||||
|
||||
But now you would like, to reach this port via the [proxy-tunnel application](https://github.com/proxytunnel/proxytunnel), though an restrictive http(s) proxy. This is in many cases, one of the few methods, to escape from restricted private networks, like in companies, schools and universities. Unfortunately, many of those proxy-servers will check, that the protocol leaving the proxy is really tls. Therefore you need and endpoint in your system, which will terminate the tls-connection and forward the encapsulated ssh stream to the sshd. Sslh can't do tls termination, as this is not a core job of tls. One of the solutions tried here is stunnel. Stunnel can do transparency like sslh, but unfortunately belongs to the uncooperative ip-transparent programs. At the time of writing this article, you can use stunnel as the first-in-chain, and a very recent sslh as second in chain. But nginx (or openresty) is capable of this, however it prefers (at least in the tested versions), mostly the second way just selecting a new random port and not (always) preserving the original source port, what makes debugging of events much easier.
|
||||
|
||||
|
||||
```
|
||||
stream {
|
||||
ssl_preread on;
|
||||
|
||||
map $ssl_preread_server_name $name {
|
||||
default master.yourdomain.top;
|
||||
t1.yourdomain.top t1.yourdomain.top;
|
||||
t2.yourdomain.top t2.yourdomain.top;
|
||||
cryptic.foo.bar location.selfsigned.cert;
|
||||
}
|
||||
|
||||
## $destination port :443 is assumed, beeing as real
|
||||
## webserver. Either anothe nginx http-server or apache
|
||||
## or anything other ...
|
||||
map $ssl_preread_server_name $dest {
|
||||
default 192.168.255.254:443;
|
||||
t1.yourdomain.top 192.168.255.254:443;
|
||||
t2.yourdomain.top 192.168.255.254:444;
|
||||
cryptic.foo.notexist 192.168.255.254:445;
|
||||
}
|
||||
|
||||
|
||||
## this is the server, to handle incoming tcp streams
|
||||
## and dispatching them transparent to $dest
|
||||
## 192.168.255.254:1443 is the address, where
|
||||
## the front facing sslh sends traffic to
|
||||
server {
|
||||
listen 192.168.255.254:1443;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_timeout 3m;
|
||||
proxy_bind $remote_addr transparent;
|
||||
proxy_pass $dest;
|
||||
ssl_preread on;
|
||||
}
|
||||
|
||||
## this is a basic endpoint for proxy-tunnel connections
|
||||
server {
|
||||
listen 192.168.255.254:444 ssl;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
|
||||
## give facl to the nginx user, see later in article
|
||||
ssl_certificate /etc/letsencrypt/live/$name/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/$name/privkey.pem;
|
||||
ssl_dhparam /etc/nginx/dhparam-nginx.pem;
|
||||
ssl_session_cache shared:MySSL:10m;
|
||||
ssl_session_tickets off;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_timeout 3m;
|
||||
proxy_bind $remote_addr transparent;
|
||||
proxy_pass 192.168.255.254:22 ;
|
||||
}
|
||||
|
||||
|
||||
## this is a fancy destination, using some tricks, to fool
|
||||
## some proxies.
|
||||
server {
|
||||
listen 192.168.255.254:445 ssl;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
|
||||
## this points to a directory, containg a self signed certificate
|
||||
## created with CN and SAN to an not officially existing
|
||||
## domain, e.g. cryptic.foo.notexists
|
||||
ssl_certificate /etc/nginx/$name/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/$name/privkey.pem;
|
||||
ssl_dhparam /etc/nginx/dhparam-nginx.pem;
|
||||
ssl_session_cache shared:MySSL:10m;
|
||||
ssl_session_tickets off;
|
||||
## additional trick: requiring client certificate authentication
|
||||
ssl_client_certificate /etc/nginx/public.cert ;
|
||||
ssl_verify_client on ;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_timeout 3m;
|
||||
proxy_bind $remote_addr transparent;
|
||||
proxy_pass 192.168.255.254:22;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This nginx stream server shows a combination of different possibilities, how to establish an endpoint for proxytunnel or similar programs, to hide your ssh connection. Remember: Hiding ssh tunneled in tls does not neccessarly raise the security of your connection. In theory a combination of two methods may also lower the security. In addition there are more drawbacks: Such a connection needs more cpu on both ends for doing double crypto, and the throughput through he connection drops, as the ratio payload/header increases. the single ssh-packets are smaller, than usual.
|
||||
So it is not recommended for doing scp or sftp through such connections.
|
||||
|
||||
### Nginx Configration Explained ###
|
||||
|
||||
However, there are situations, where this is the only way, to reach your server.
|
||||
|
||||
So we have now this nginx instance helping us out.
|
||||
|
||||
The server stanza listening on _192.168.255.254:1443_ is the main process, accepting incoming connections forwarded from sslh. It prereads tls SNI names, to get destination information. When in the nginx-configuration a variable is used, which is set by an map action, the mapping takes place just in that moment, when the variable needs to be expanded.
|
||||
|
||||
So we have either default values, in case there is no SNI, or values for individual SNI.
|
||||
|
||||
The connection stream is than forwarded ip-transparent to a given destination, which is itself another nginx instance, defined in the configuration file.
|
||||
|
||||
#### The Standard TLS-Termination ####
|
||||
|
||||
The server stanza listening on _192.168.255.254:444_ is a very basic TLS terminating endpoint. In most cases, a proxy is happy with a destination like this. Here nginx just terminates the TLS connection and shovels the incoming packets over to sshd in ip-transparent way. The proxytunnel configuration for this target will look like:
|
||||
|
||||
```
|
||||
host t2.yourdomain.top
|
||||
ProxyCommand /path/to/proxytunnel -e -p PROXY-IP:3128 -d t2.yourdomain.top:443
|
||||
ServerAliveInterval 30
|
||||
```
|
||||
|
||||
If you have no proxytunnel on your system, you can also use openssl. In this case the configuration looks like:
|
||||
|
||||
```
|
||||
host t2-alias
|
||||
ProxyCommand openssl s_client -proxy PROXY-IP:3128 -connect t2.yourdomain.top:443
|
||||
ServerAliveInterval 30
|
||||
```
|
||||
|
||||
`ssh ts-alias` will use this configuration.
|
||||
|
||||
One point from the above configuration needs further explanation, as we just refer to our common letsencrypt store, for certificates.
|
||||
|
||||
##### Excurse To File ACLs #####
|
||||
|
||||
We are using here our common letsencrypt certificate store, as copying certificates after renewal is a pain in the as and prone to errors.
|
||||
|
||||
A small script makes sure, that all applications can read the certificates:
|
||||
|
||||
```
|
||||
for i in Debian-exim dovecot mail www-data nginx ; do
|
||||
setfacl -Rdm u:$i:rX ./archive/
|
||||
setfacl -Rdm u:$i:rX ./live/
|
||||
setfacl -Rm u:$i:rX ./archive/
|
||||
setfacl -Rm u:$i:rX ./live/
|
||||
for file in $( find ./live ./archive -type l,f ) ; do
|
||||
echo -e "$i $file set"
|
||||
setfacl -m u:$i:r $file
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
This script needs only to be run, when a new application user needs access.
|
||||
The first two lines are making sure (watch the **d**), that the given options
|
||||
on those directories will be the default for newly created files below.
|
||||
The uppercase **X** means, that **x**-access will only be granted to directories or files, having already **x** set for others.
|
||||
|
||||
#### The Tricky TLS Termination ####
|
||||
|
||||
The server stanza listening on _192.168.255.254:445_ is a more tricky TLS terminating endpoint. Some proxies are trying to inspect the destination, before letting you go. Some of them you can fool, others not.
|
||||
|
||||
One trick can be to use an phantasy domain name with a self signed certificate, no dns server will ever resolve. As you are using this name as SNI name in your proxy-tunnel connection, it will work. This can be also a way, to hide your sshd, if someone, who knows you are using sslh tries to find your sshd. As long, as this person does not get access to proxies you are using, this may help in some situations.
|
||||
|
||||
Another trick is, requiring a client certificate for authentication. This is in each case the much better approach, as you can combine this also with official connected domain names. Without the client certificate no sshd!
|
||||
|
||||
The client certificate does not increase the above mentioned ratio between payload and headers, as this is only used while establishing the TLS connection.
|
||||
|
||||
It prevents however a proxy, doing a parallel sneak to your destination, to figure out what is behind. I have seen proxies letting you connect with this method, others are denying access.
|
||||
|
||||
To access this endpoint, the proxytunnel configuration inside `~/.ssh/config`
|
||||
will look like:
|
||||
|
||||
```
|
||||
host cryptic.foo.notexists
|
||||
ProxyCommand /path/to/proxytunnel -e -c ~/public.cert -k ~/private.pem -C ~/myOwnCA.cert -p PROXY-IP:3128 -o cryptic.foo.notexists -d t1.yourdomain.top:443
|
||||
ServerAliveInterval 30
|
||||
```
|
||||
|
||||
`-C` gives the certfile to your certificate used for selfsigning the server certificate. You can also set `-z` for not verifying the certificate, but that is not recommended! `t1.yourdomain.top` represents a valid domain name, where the listening sslh can be reached. This name is not taken for SNI, because **-o** sets our hidden SNI name. Whenever you enter `ssh cryptic.foo.notexists` you will get connected to your server!
|
||||
|
||||
The best recommendation however is: Avoid self-signed certs, if you are not really sure what you are doing.
|
||||
|
||||
+24
-9
@@ -12,19 +12,20 @@ GitHub.
|
||||
Getting more info
|
||||
=================
|
||||
|
||||
In general, if something doesn't work, you'll want to run
|
||||
`sslh` with lots of logging, and the logging directly in
|
||||
the terminal (Otherwise, logs are sent to `syslog`, and
|
||||
usually end up in `/var/log/auth.log`). You will achieve
|
||||
this by running `sslh` in foreground with verbose:
|
||||
There are several `verbose` options that each enable a set
|
||||
of messages, each related to some event type. See
|
||||
`example.cfg` for a list of them.
|
||||
|
||||
If something doesn't work, you'll want to run `sslh` with
|
||||
lots of logging, and the logging directly in the terminal
|
||||
(Otherwise, logs are sent to `syslog`, and usually end up in
|
||||
`/var/log/auth.log`). There is a general `--verbose` option
|
||||
that will allow you to enable all messages:
|
||||
|
||||
```
|
||||
sslh -v 1 -f -F myconfig.cfg
|
||||
sslh -v 3 -f -F myconfig.cfg
|
||||
```
|
||||
|
||||
Higher values of `verbose` produce more information. 1 is
|
||||
usually sufficient. 2 will also print incoming packets used
|
||||
for probing.
|
||||
|
||||
forward to [PROBE] failed:connect: Connection refused
|
||||
=====================================================
|
||||
@@ -167,3 +168,17 @@ protocols: (
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] }
|
||||
);
|
||||
```
|
||||
|
||||
I get an error "Too many open files"
|
||||
====================================
|
||||
|
||||
`sslh` used up all the file descriptors it can (1024 by default in Linux),
|
||||
which means it cannot create new connections. On some
|
||||
systems, you can increase the process limit with `ulimit -n
|
||||
2048` (for example). Maybe more usefully, you can limit the
|
||||
connection usage based on protocols or listening address, as
|
||||
described in the [max_connections](max_connections.md)
|
||||
guide.
|
||||
|
||||
|
||||
|
||||
+104
-86
@@ -4,9 +4,10 @@ Pre-built binaries
|
||||
Docker images of `master` and of the tagged versions are
|
||||
available directly from [Github](https://github.com/yrutschle/sslh/pkgs/container/sslh).
|
||||
|
||||
Windows binaries for Cygwin are graciouly produced by
|
||||
Windows binaries for Cygwin are graciously produced by
|
||||
nono303 on his [repository](https://github.com/nono303/sslh).
|
||||
|
||||
|
||||
Compile and install
|
||||
===================
|
||||
|
||||
@@ -15,139 +16,156 @@ Dependencies
|
||||
|
||||
`sslh` uses:
|
||||
|
||||
* [libconfig](http://www.hyperrealm.com/libconfig/). For
|
||||
Debian this is contained in package `libconfig-dev`. You
|
||||
can compile with or without it using USELIBCONFIG in the
|
||||
Makefile.
|
||||
* [libconfig](http://www.hyperrealm.com/libconfig/).
|
||||
For Debian this is contained in package `libconfig-dev`.
|
||||
You can compile with or without it using USELIBCONFIG in the Makefile.
|
||||
|
||||
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
|
||||
For Debian, this is contained in packages
|
||||
`libwrap0-dev`. You
|
||||
can compile with or without it using USELIBWRAP in the
|
||||
Makefile.
|
||||
For Debian, this is contained in packages `libwrap0-dev`.
|
||||
Presence of libwrap is checked by the configure script.
|
||||
|
||||
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`. You
|
||||
can compile with or without it using USESYSTEMD in the
|
||||
Makefile.
|
||||
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
|
||||
You can compile with or without it using USESYSTEMD in the Makefile.
|
||||
|
||||
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`. You can compile with or without it using USELIBCAP in the Makefile
|
||||
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
|
||||
Presence of libcap is checked by the configure script.
|
||||
|
||||
* libbsd, to enable to change the process name (as shown in
|
||||
`ps`, so each forked process shows what protocol and what
|
||||
connection it is serving),
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at
|
||||
compile-time.
|
||||
* [libconfig++-dev](https://packages.debian.org/bookworm/libconfig++-dev), in package `lìbconfig++-dev`
|
||||
|
||||
* libbsd, to enable to change the process name (as shown in `ps`,
|
||||
so each forked process shows what protocol and what connection it is serving),
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
|
||||
Presence of libbsd is checked by the configure script.
|
||||
|
||||
* libpcre2, in package `libpcre-dev`. You can compile
|
||||
with or without it using ENABLE_REGEX in the Makefile.
|
||||
* libpcre2, in package `libpcre2-dev`.
|
||||
You can compile with or without it using ENABLE_REGEX in the Makefile.
|
||||
|
||||
* libev-dev, in package `libev-dev`. If you build a binary
|
||||
specifically and do not build `sslh-ev`, you don't need
|
||||
this.
|
||||
* libev-dev, in package `libev-dev`.
|
||||
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
|
||||
|
||||
* [libproxyprotocol](https://github.com/kosmas-valianos/libproxyprotocol.git)
|
||||
to support HAProxy's [ProxyProtocol](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt).
|
||||
As this is not part of the distribution packages, set
|
||||
C_INCLUDE_PATH, LD_LIBRARY_PATH, and LIBRARY_PATH to the appropriate
|
||||
values:
|
||||
```
|
||||
export C_INCLUDE_PATH=/home/user/src/libproxyprotocol/src
|
||||
export LD_LIBRARY_PATH=/home/user/src/libproxyprotocol/libs
|
||||
export LIBRARY_PATH=/home/user/src/libproxyprotocol/libs
|
||||
```
|
||||
|
||||
For OpenSUSE, these are contained in packages libconfig9 and
|
||||
libconfig-dev in repository
|
||||
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
|
||||
|
||||
For Fedora, you'll need packages `libconfig` and
|
||||
`libconfig-devel`:
|
||||
For Fedora, you'll need packages `libconfig` and `libconfig-devel`:
|
||||
|
||||
yum install libconfig libconfig-devel
|
||||
yum install libconfig libconfig-devel
|
||||
|
||||
If you want to rebuild `sslh-conf.c` (after a `make
|
||||
distclean` for example), you will also need to add
|
||||
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
If you want to rebuild `sslh-conf.c` (after a `make distclean` for example),
|
||||
you will also need to add [conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
(v1.5) to your path.
|
||||
|
||||
|
||||
The test scripts are written in Perl, and will require
|
||||
IO::Socket::INET6 (libio-socket-inet6-perl in Debian).
|
||||
`IO::Socket::INET6` (`libio-socket-inet6-perl` in Debian).
|
||||
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
First you have to run `./configure` in the _**./sslh**_ directory. After this,
|
||||
the Makefile is created, and you can do your configuration changes in the Makefile.
|
||||
After each run of ./configure, those changes are gone and the Makefile is recreated.
|
||||
|
||||
After this, the Makefile should work:
|
||||
There are a couple of configuration options at the beginning of the Makefile:
|
||||
|
||||
make install
|
||||
* `# override undefine HAVE_LANDLOCK` if you uncomment this line, sslh will be compiled
|
||||
without landlock. This works with gcc versions < 12. Otherwise, if your system has
|
||||
linux/landlock.h in the include path, the configure script creates a _**config.h**_ file,
|
||||
which defines HAVE_LANDLOCK. It is not enough, to set this to 0, you must delete it,
|
||||
when you don't wish to have landlock in your binary.
|
||||
|
||||
* `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`),
|
||||
you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian).
|
||||
|
||||
There are a couple of configuration options at the beginning
|
||||
of the Makefile:
|
||||
* `USELIBCONFIG` compiles support for the configuration file.
|
||||
You will need `libconfig` headers to compile (`libconfig8-dev` in Debian).
|
||||
|
||||
* `USELIBWRAP` compiles support for host access control (see
|
||||
`hosts_access(3)`), you will need `libwrap` headers and
|
||||
library to compile (`libwrap0-dev` in Debian).
|
||||
* `USESYSTEMD` compiles support for using systemd socket activation.
|
||||
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
|
||||
|
||||
* `USELIBCONFIG` compiles support for the configuration
|
||||
file. You will need `libconfig` headers to compile
|
||||
(`libconfig8-dev` in Debian).
|
||||
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
|
||||
|
||||
* `USESYSTEMD` compiles support for using systemd socket activation.
|
||||
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
|
||||
* `USELIBCAP` compiles support for libcap, which allows to inherit capabilities to
|
||||
daughter-processes, which run as restricted users. You need this, when you wish to
|
||||
make sure, that the --user= parameter can be used, without setting capabilities etc.
|
||||
to your binaries, to make this work.
|
||||
|
||||
Now you can do either a plain `make` to create the binaries, or you can do an
|
||||
`make install` to create the binaries and install them.
|
||||
|
||||
* `USELIBBSD` compiles support for updating the process name (as shown
|
||||
by `ps`).
|
||||
|
||||
Generating the configuration parser
|
||||
-----------------------------------
|
||||
|
||||
The configuration file and command line parser is generated
|
||||
by `conf2struct`, from `sslhconf.cfg`, which generates
|
||||
`sslh-conf.c` and `sslh-conf.h`. The resulting files are
|
||||
included in the source so `sslh` can be built without
|
||||
`conf2struct` installed.
|
||||
The configuration file and command line parser is generated by `conf2struct`,
|
||||
from `sslhconf.cfg`, which generates `sslh-conf.c` and `sslh-conf.h`.
|
||||
The resulting files are included in the source
|
||||
so `sslh` can be built without `conf2struct` installed.
|
||||
|
||||
Further, to prevent build issues,
|
||||
`sslh-conf.[ch]` has no dependency to `sslhconf.cfg` in the Makefile.
|
||||
In the event of adding configuration settings,
|
||||
they need to be regenerated using `make c2s`.
|
||||
|
||||
Further, to prevent build issues, `sslh-conf.[ch]` has no
|
||||
dependency to `sslhconf.cfg` in the Makefile. In the event
|
||||
of adding configuration settings, they need to be
|
||||
regenerated using `make c2s`.
|
||||
|
||||
Binaries
|
||||
--------
|
||||
|
||||
The Makefile produces three different executables: `sslh-fork`,
|
||||
`sslh-select` and `sslh-ev`:
|
||||
The Makefile produces three different executables:
|
||||
`sslh-fork`, `sslh-select` and `sslh-ev`:
|
||||
|
||||
* `sslh-fork` forks a new process for each incoming connection.
|
||||
It is well-tested and very reliable, but incurs the overhead
|
||||
of many processes.
|
||||
If you are going to use `sslh` for a "small" setup (less than
|
||||
a dozen ssh connections and a low-traffic https server) then
|
||||
`sslh-fork` is probably more suited for you.
|
||||
It is well-tested and very reliable, but incurs the overhead of many processes.
|
||||
If you are going to use `sslh` for a "small" setup
|
||||
(less than a dozen ssh connections and a low-traffic https server)
|
||||
then `sslh-fork` is probably more suited for you.
|
||||
|
||||
* `sslh-select` uses only one thread, which monitors all
|
||||
connections at once. It only incurs a 16 byte overhead per
|
||||
connection. Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely. If you are going
|
||||
to use `sslh` on a "medium" setup (a few hundreds of
|
||||
connections), or if you are on a system where forking is
|
||||
expensive (e.g. Windows), `sslh-select` will be better.
|
||||
* `sslh-select` uses only one thread, which monitors all connections at once.
|
||||
It only incurs a 16 byte overhead per connection.
|
||||
Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely.
|
||||
If you are going to use `sslh` on a "medium" setup (a few hundreds of connections),
|
||||
or if you are on a system where forking is expensive (e.g. Windows),
|
||||
`sslh-select` will be better.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as a backend.
|
||||
This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as
|
||||
a backend. This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
* In general:
|
||||
|
||||
make
|
||||
cp sslh-fork /usr/local/sbin/sslh
|
||||
cp basic.cfg /etc/sslh.cfg
|
||||
vi /etc/sslh.cfg
|
||||
|
||||
```sh
|
||||
./configure
|
||||
make
|
||||
cp sslh-fork /usr/local/sbin/sslh
|
||||
cp basic.cfg /etc/sslh.cfg
|
||||
vi /etc/sslh.cfg
|
||||
```
|
||||
* For Debian:
|
||||
|
||||
cp scripts/etc.init.d.sslh /etc/init.d/sslh
|
||||
|
||||
```sh
|
||||
cp scripts/etc.init.d.sslh /etc/init.d/sslh
|
||||
```
|
||||
* For CentOS:
|
||||
```sh
|
||||
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
|
||||
```
|
||||
|
||||
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
|
||||
|
||||
|
||||
You might need to create links in /etc/rc<x>.d so that the server
|
||||
You might need to create links in `/etc/rc<x>.d` so that the server
|
||||
start automatically at boot-up, e.g. under Debian:
|
||||
|
||||
update-rc.d sslh defaults
|
||||
```sh
|
||||
update-rc.d sslh defaults
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ should be avoided as it is very inefficient on Windows, but
|
||||
The following script downloads the latest cygwin, the latest version of sslh, and then compiles and copies the binaries with dependancies to an output folder.
|
||||
|
||||
It may be needed to correct it from time to time, but it works. I use it in a virtual machine.
|
||||
Just retrieve WGET.EXE from [https://eternallybored.org/misc/wget/](url) or git binaries.
|
||||
Just retrieve WGET.EXE from https://eternallybored.org/misc/wget/ or git binaries.
|
||||
|
||||
Copy the 3 files
|
||||
|
||||
|
||||
+16
-4
@@ -76,10 +76,10 @@ Configuration goes like this on the server side, using `stunnel3`:
|
||||
Capabilities support
|
||||
--------------------
|
||||
|
||||
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
|
||||
make use of POSIX capabilities; this will save the required
|
||||
capabilities needed for transparent proxying for unprivileged
|
||||
processes.
|
||||
On Linux (only?), you can compile sslh with `USELIBCAP=1` set
|
||||
in the Makefile to make use of POSIX capabilities; this will
|
||||
save the required capabilities needed for transparent proxying
|
||||
for unprivileged processes.
|
||||
|
||||
Alternatively, you may use filesystem capabilities instead
|
||||
of starting sslh as root and asking it to drop privileges.
|
||||
@@ -102,6 +102,9 @@ Transparent proxy support
|
||||
Transparent proxying is described in its own
|
||||
[document](tproxy.md).
|
||||
|
||||
It might be easier to configure `sslh` to use Proxyprotocol
|
||||
if the backend server supports it.
|
||||
|
||||
Systemd Socket Activation
|
||||
-------------------------
|
||||
If compiled with `USESYSTEMD` then it is possible to activate
|
||||
@@ -197,3 +200,12 @@ process both single-datagram protocols such as DNS, and
|
||||
connection-based protocols such as QUIC.
|
||||
|
||||
An example for supporting QUIC is shown in `example.cfg`.
|
||||
|
||||
|
||||
Limiting file descriptor consumption
|
||||
------------------------------------
|
||||
|
||||
There are various mechanisms to limit the number of
|
||||
concurrent connections, which allows to limit the usage of
|
||||
file descriptor. This is described in a separate
|
||||
[guide](max_connections.md).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
File diff suppressed because it is too large.
Load diff
|
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,64 @@
|
||||
Limiting the number of connections
|
||||
----------------------------------
|
||||
|
||||
|
||||
As a network-facing service, `sslh` can be attacked by very
|
||||
simple denial-of-service attacks by creating a lot of
|
||||
concurrent connections. Protecting against such attacks is
|
||||
complicated from the server side. This attack will cause
|
||||
`sslh` to create a large number of file handles, which can
|
||||
cause exgagerated resource consumption.
|
||||
|
||||
In particular, when `sslh` runs into its file descriptor
|
||||
limit (as defined with `ulimit -n`), bad things happen: It
|
||||
becomes impossible to accept() new sockets efficiently
|
||||
(refer to libev's documentation, ["The special problem of
|
||||
accept()ing when you can't"](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#The_special_problem_of_accept_ing_wh)). Currently, when `sslh-ev` or
|
||||
`sslh-select` reaches its limit of file descriptors, it just
|
||||
stops accepting new connections altogether for a few
|
||||
seconds, hoping for the situation to get better soon. It is
|
||||
better to configure `sslh` to limit connections so the
|
||||
`ulimit` is never reached.
|
||||
|
||||
Keep in mind that limiting the number of connections means
|
||||
that in case of an attack, the server resources are
|
||||
protected, but this might be at the expense of serving
|
||||
legitimate connections. In particular, in some cases this
|
||||
might mean SSH is no longer available.
|
||||
|
||||
There are several mechanisms to limit the number of
|
||||
connections:
|
||||
|
||||
- Use `ulimit` (see bash(3) or your shell's man page) to
|
||||
limit the number of file descriptors. (but then accepting
|
||||
new connections might fail for a while)
|
||||
|
||||
Then, `sslh` provides several mechanisms to limit the number
|
||||
of concurrent connections, which in turns limits the number
|
||||
of file descriptors used.
|
||||
|
||||
Essentially there are two ways to do this:
|
||||
|
||||
- you can set `max_connections` per protocol, and `sslh`
|
||||
will drop connections after probing if the count is
|
||||
exceeded. This should help in keeping SSH connections
|
||||
available even if an attacker is stuffing other protocols.
|
||||
|
||||
- you can set `max_connections` for each `listen` entry. So
|
||||
the `sslh` process for each port will limit the number of
|
||||
concurrent connections to that port. This is similar to the
|
||||
`udp_max_connections` setting, but for TCP.
|
||||
|
||||
`sslh-select` and `sslh-ev` support both limit types.
|
||||
|
||||
`sslh-fork` has an explicit design goal to be as simple as
|
||||
possible, which makes it impossible to implement limits per
|
||||
protocol (because protocol probing is performed after the
|
||||
process has forked). Limits will only work for `listen`
|
||||
entries.
|
||||
|
||||
As of sslh 2.2.5, this is an experimental feature and not
|
||||
all use cases have been tested. If 2.2.5 does not exist yet,
|
||||
you must be on the Git's head, so it is even more
|
||||
experimtental! Do not hesitate to send feedback if you
|
||||
notice inconsistent behaviour.
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
Here is an example of deployment of sslh in transparent mode
|
||||
using ansible and podman, [submitted by Github user
|
||||
olegstepura](https://github.com/yrutschle/sslh/issues/448)
|
||||
|
||||
```yaml
|
||||
# ansible podman task
|
||||
- name: "Create sslh-co pod"
|
||||
containers.podman.podman_pod:
|
||||
name: sslh-co # read as "sslh and company"
|
||||
state: started
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:444"
|
||||
# ... (more ports if needed)
|
||||
network:
|
||||
- '{{ containers.config.network }}' # other services from this network can access containers in this network for example prometheus can read caddy metrics at sslh-co:2020, also caddy itself can connect to other services to act as a reverse proxy
|
||||
|
||||
- name: "Create the sslh container"
|
||||
containers.podman.podman_container:
|
||||
name: sslh
|
||||
image: "yrutschle/sslh:latest"
|
||||
pod: sslh-co
|
||||
capabilities:
|
||||
- NET_RAW
|
||||
- NET_BIND_SERVICE
|
||||
- NET_ADMIN
|
||||
sysctl:
|
||||
net.ipv4.conf.default.route_localnet: 1
|
||||
net.ipv4.conf.all.route_localnet: 1
|
||||
expose:
|
||||
- 444
|
||||
volume:
|
||||
# ... (make sure to mount config as you like)
|
||||
command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
|
||||
state: started
|
||||
# ⚠️ Important: Never add a `network:` here, otherwise this setup will NOT work.
|
||||
# Adding a network disconnects the sslh container from the pod's shared localhost.
|
||||
|
||||
- name: "Create the caddy container"
|
||||
containers.podman.podman_container:
|
||||
name: caddy
|
||||
image: "lucaslorentz/caddy-docker-proxy:alpine" # regular caddy or nginx image will also work
|
||||
pod: sslh-co
|
||||
expose:
|
||||
- 80
|
||||
- 443
|
||||
- 2020 # metrics, since caddy-docker-proxy uses :2019 internally
|
||||
volume:
|
||||
# ... (mount your configs and other stuff here)
|
||||
- "/var/run/podman/podman.sock:/var/run/docker.sock"
|
||||
state: started
|
||||
notify: podman restart sslh
|
||||
|
||||
- name: "Create the SSH proxy to host container"
|
||||
containers.podman.podman_container:
|
||||
name: ssh-proxy
|
||||
image: "alpine/socat:latest"
|
||||
pod: sslh-co
|
||||
expose:
|
||||
- 222
|
||||
command: TCP-LISTEN:222,fork TCP:host.containers.internal:22
|
||||
state: started
|
||||
```
|
||||
|
||||
```ini
|
||||
# sslh config
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
transparent: true;
|
||||
timeout: 5;
|
||||
|
||||
listen:
|
||||
(
|
||||
{ host: "0.0.0.0"; port: "444"; keepalive: true; },
|
||||
);
|
||||
|
||||
protocols:
|
||||
(
|
||||
{
|
||||
name: "ssh";
|
||||
service: "ssh";
|
||||
host: "localhost";
|
||||
port: "222";
|
||||
fork: true;
|
||||
},
|
||||
{
|
||||
name: "http";
|
||||
host: "localhost";
|
||||
port: "80";
|
||||
},
|
||||
{
|
||||
name: "tls";
|
||||
host: "localhost";
|
||||
port: "443";
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
I omitted Caddy configs here since they're irrelevant, and some unrelated container configs were also dropped for clarity.
|
||||
|
||||
In the example above, `sslh`, `caddy`, and `ssh-proxy` are **three containers inside the same Podman pod**, all sharing the same **pod-local `localhost`**.
|
||||
|
||||
`sslh` listens on port **444** instead of **443**, because `caddy` already binds to port `443`, and reconfiguring Caddy is more complex due to its Let's Encrypt integration (Caddy itself "thinks" it’s bound to your host interface).
|
||||
|
||||
#### Port mapping scheme
|
||||
All containers **share the same `localhost`** inside the pod:
|
||||
```
|
||||
host 443 → pod 444 (sslh) → pod 443 (caddy)
|
||||
```
|
||||
|
||||
|
||||
- **Podman** maps the host’s `443` → pod’s `444`.
|
||||
- **SSHL** listens on `localhost:444` inside the pod and forwards TLS traffic to `caddy` on `localhost:443`.
|
||||
- **Caddy** listens on `localhost:443` and reverse-proxies to other services in the private network.
|
||||
|
||||
This ensures that **reverse-proxied services** receive the correct client IP in the `X-Forwarded-For` header.
|
||||
|
||||
---
|
||||
|
||||
### Important note
|
||||
|
||||
⚠️ Do **not** set a `network:` parameter for the `sslh` container when it belongs to a Podman pod.
|
||||
Setting a custom network **forces Podman to detach the container from the pod’s shared network namespace** and creates a **separate `localhost`** for that container, breaking transparent routing.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
|
||||
state: started
|
||||
# ⚠️ Never add a `network:` here!
|
||||
# Adding a network disconnects the sslh container from the pod's shared localhost,
|
||||
# causing transparent routing to fail.
|
||||
```
|
||||
|
||||
### Key takeaways
|
||||
|
||||
- `net.ipv4.conf.default.route_localnet` and iptables rules are applied **inside the `sslh` container only**, not on the host.
|
||||
- The `sslh` **init** script manages all required `iptables` and routing rules inside of the container.
|
||||
- **Transparent mode restrictions**:
|
||||
- Any service `sslh` proxies traffic to **must** be inside the same pod and bound to its **pod-local `localhost`**.
|
||||
- Container ports must not conflict within the pod.
|
||||
- Ports should be published **at the pod level**, **not per-container**.
|
||||
- Containers inside this pod **must not** be attached to any custom networks.
|
||||
- Because of transparent routing, `sslh` cannot directly proxy SSH to the host IP.
|
||||
A workaround is to run a lightweight **socat** container in the same pod:
|
||||
`TCP-LISTEN:222 → TCP:host.containers.internal:22`
|
||||
- Caddy’s `reverse_proxy` **to other containers** in the **custom network attached to the pod** still works properly.
|
||||
- `socat` can also connect to both the **host** and **other containers** inside the custom network without issues.
|
||||
|
||||
### Tested setup
|
||||
|
||||
- Confirmed working on **Debian Trixie**
|
||||
- Using **Podman 5.4.2**
|
||||
- Pinned `podman` and related dependencies (`conmon`, `slirp4netns`, `aardvark-dns`, etc.) from **Debian testing**,
|
||||
since **stable** often ships outdated versions that can break this setup.
|
||||
@@ -0,0 +1,120 @@
|
||||
Using Proxyprotocol with sslh
|
||||
=============================
|
||||
|
||||
HAProxy defined the [Proxy protocol](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/proxying-essentials/client-ip-preservation/enable-proxy-protocol/), where a proxy adds external connection information directly inside the TCP connection, for the backend server to get. This achieves the same goal as transparent proxying, while requiring no networking knowledge and no administration rights.
|
||||
|
||||
Instead, the protocol needs to be supported in the servers.
|
||||
Many do, e.g. Apache (with the `RemoteIPProxyProtocol` setting).
|
||||
|
||||
`sslh` supports this protocol both on the client and the
|
||||
server side.
|
||||
|
||||
Proxyprotocol on the server side
|
||||
--------------------------------
|
||||
|
||||
Presumably that's the most common setup, with `sslh` being
|
||||
the client-facing proxy in front of the backend server.
|
||||
Support is enabled by adding the `proxyprotocol: [1|2]`
|
||||
setting in the target protocol:
|
||||
|
||||
```
|
||||
listen:
|
||||
(
|
||||
{ host: "localhost"; port: "443"; }
|
||||
);
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "2222"; },
|
||||
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
|
||||
);
|
||||
);
|
||||
```
|
||||
|
||||
Here `sslh` listens on port 443. It forwards `ssh`
|
||||
connections to port 2222 without touching them (which means sshd does not see the external data) (Apparently openssh does not as of september 2025 support Proxyprotocol).
|
||||
It forwards `tls` to port 8080, adding a proxy-protocol v2
|
||||
header which allows Apache (configured with RemoteIPProxyProtocol) to log the external IP and port.
|
||||
|
||||
|
||||
```
|
||||
203.0.113.4:443 :2222
|
||||
Client -----------------> sslh -------------> sshd
|
||||
198.51.100.42:2342 | logs: 127.0.0.1:<local port>
|
||||
|
|
||||
| :8080
|
||||
\--pp v2-------> apache
|
||||
logs: 198.51.100.42:2342
|
||||
|
||||
```
|
||||
|
||||
The `proxyprotocol` setting can be set to 1 or 2, which
|
||||
selects the appropriate protocol version. Version 2 being
|
||||
more efficient, this is what you should use unless you have
|
||||
good reasons to use version 1 (e.g. your backend server only
|
||||
supports v1, an unlikely scenario).
|
||||
|
||||
|
||||
Proxyprotocol on the client side
|
||||
--------------------------------
|
||||
|
||||
`sslh` also supports Proxy-protocol on incoming connections.
|
||||
These are so-called "client side", but you should not enable
|
||||
proxy-protocol on an Internet-facing server. Instead, this
|
||||
can be useful if `sslh` is located behind another proxy that
|
||||
adds proxy-protocol headers, e.g. HAProxy.
|
||||
|
||||
This is enabled by adding `proxyprotocol: true` to a
|
||||
`listen` statement:
|
||||
|
||||
```
|
||||
listen:
|
||||
(
|
||||
{ host: "localhost"; port: "443"; proxyprotocol: true; }
|
||||
);
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
|
||||
{ name: "tls"; host: "localhost"; port: "8080"; }
|
||||
);
|
||||
);
|
||||
```
|
||||
|
||||
In that case, `sslh` expects to find a proxyprotocol header
|
||||
on all incoming connections. It ignores the header to
|
||||
perform its usual protocol detection.
|
||||
|
||||
If the target protocol
|
||||
specified nothing, the connection is passed untouched, i.e.
|
||||
the proxyprotocol header is forwarded as usual: here, Apache
|
||||
on 8080 will receive the proxyprotocol header.
|
||||
|
||||
If the target protocol specifies `proxyprotocol: 0`, then
|
||||
`sslh` removes the proxyprotocol header, and logs an
|
||||
additional message with the connection information extracted
|
||||
from the header. Here, `sslh` logs the external information
|
||||
for ssh connections, removes the header, and forwards the
|
||||
connection to sshd. `sshd` does not support proxyprotocol,
|
||||
and logs a connection coming from the local IP address.
|
||||
|
||||
|
||||
```
|
||||
203.0.113.4:443 :2222
|
||||
Client ---------> HaProxy ----pp v2---------> sslh -------------> sshd
|
||||
198.51.100.42:2342 | logs: 127.0.0.1:<local port>
|
||||
| sslh logs: 198.51.100.42:2342 to localhost:2222
|
||||
|
|
||||
| :8080
|
||||
\--pp v2-------> apache
|
||||
logs: 198.51.100.42:2342
|
||||
sslh logs: local connection
|
||||
```
|
||||
|
||||
`sslh` always logs the 'real' network information, here the
|
||||
local addresses, and only logs the proxyprotocol information
|
||||
when it removes the header. It is not necessary to log the
|
||||
proxyprotocol information when the target server will
|
||||
receive it (as it is then the target server's job to log the
|
||||
external IP address).
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Three Scenarios for the simple transparent proxy setup #
|
||||
|
||||

|
||||
|
||||
## Introduction ##
|
||||
The first example is the configuration, which was described in the previous document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture.
|
||||
The connections have two different endings, showing the direction of the opening connection (SYN flag) and the answer connection (SYN-ACK flags). This is important, as the traffic in the transparent proxy setup flows somewhat unexpected.
|
||||
|
||||
## Example 1 ##
|
||||
The first example shows the setup, which is described in the [previous document](./simple_transparent_proxy.md). You see the Client connecting to sslh (red connection). When sslh accepts this connection, the SYN-ACK packet is send back to the client, which sends the first data packet(s) together with the ACK for the SYN-ACK. So the bidirectional tcp connection is fully open.
|
||||
Sslh opens now the blue connection to sshd and needs for that elevation rights, as it uses the clients IP address as its own address for opening this connection. Now things are becoming complicated: Sshd send back the first packet with SYN-ACK flags (green line), addressed to the clients IP (dotted line). As already described, that would go wrong, so our routing trick makes this packet beeing deflected back to sslh, so this tcp connection is also opened. But we have here now an asymetric behaviour, that the read and write pathes of the tcp connection are going different routes.
|
||||
The sslh process shuffles now all the bytes coming from sshd from the green line to the red line, vice versa for the packets from the client.
|
||||
|
||||
## Example 2 ##
|
||||
In this example sshd is running on another server. No matter, if this is docker, kvm, virtualbox or another physical host, connected with an ethernet cable. Here we need no dummy interface, so we need another way, to configure our routing deflection. The principle is the same: We need to force packets coming back from sshd going to sslh and not directly back to the client.
|
||||
In this case its your decision, where those rules will be tied in, options are:
|
||||
|
||||
* the startscript of sslh
|
||||
* the docker or kvm configuration
|
||||
* the configuration of the outgoing interface
|
||||
|
||||
Its two lines you need:
|
||||
```
|
||||
ip rule add from SSHD_ADDRESS/32 table sslh
|
||||
ip route add local 0.0.0.0/0 dev lo table sslh
|
||||
```
|
||||
On the sshd host, we need no additional rules, as all traffic is coming back to our sslh host, because this is in this setting the default gateway. The only thing, we need to do: Assign a unique IP address only for sshd and all other services, you wish to hide behind sslh and host on this device.
|
||||
|
||||
There are two ways, how you can add multiple ip addresses to one device. The new _**ip addr add**_ supports multiple add statements to one and the same interface name. So you can just duplicate the interface stancas in the _**/etc/network/interfaces**_ configuration. The problem with this method is, that some older managment tools, like ifconfig are unable to show the additional addresses. So when you are used to some older tools, you may configure sub-interfaces like eth0:1.
|
||||
However my recommendation is, migrate to new tools, get used to it, as old tools don't show you the whole configuration!
|
||||
|
||||
## Example 3 ##
|
||||
This is now the extended version of the previous example. The target host has another path back to the client, as there is a default route to another interface. Now we need **TWO** routing deflections, one on the sslh host, like in scenario 2, and one on the sshd target host.
|
||||
The routing setup on the target host looks like:
|
||||
* Add an routing table name for the deflection table in _**/etc/iproute2/rt_tables**_
|
||||
* Find a location, where you will hook the two routing rules in
|
||||
```
|
||||
ip rule add from SSHD_ADDRESS/32 table sslh_routeback
|
||||
ip route add default via SERVER1_ETH1_IP dev eth0 table sslh_routeback
|
||||
|
||||
```
|
||||
This is setting up a default route for all traffic, originating from the ip address sshd (or any other service) is using, back to the host, hosting sslh. On that host, those packets will be deflected again with the same rule from scenario 2.
|
||||
|
||||
Be aware, that scenario 3 can look very different and the picture above shows only one of those setups. Each configuration, where packets from the target system can find their way back, without beeing forcibly routed through the sslh hosting system, belongs into this category. This are e.g. virtual machines or containers, having interfaces in the same network, like the sslh hosting system. Even, when they look in some drawings embedded in their host, as soon, as they have network interfaces, allowing a direct connection to the client, it is scenario 3!
|
||||
|
||||
## Modifications ##
|
||||
Now you can think about many modifications, but the tools will be the same, for all other thinkable scenarios. You must always make sure, that packets from foreign hosts, will find their way back to the sslh host. So if the chain consists of three or four servers, all need the deflection rules.
|
||||
|
||||
## Important Finding On Routing ##
|
||||
When I went ahead and wrote in my first drawings the warning, that the kernel in scenario 2 and 3 needs to have forwarding in place, I finally tested, that this is not true. **Both scenarios are working without kernel forwarding beeing activated!**
|
||||
The background: The deflecting routing table cames into the game, before the kernel has to made the decision, that packets with non local ip addresses in source and destination must be forwarded. After the routing rule deliveres the packet to sslh and sslh rewrites the source ip, the packet is treated as local, and can pass the system.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Transparent Proxy Configuration Using IP Routing#
|
||||
This documentation is another explanation of the transparent proxy with the goal, beeing secure and minimalistic. Besides this documentation will explain, how and why this configuration works.
|
||||
The explanation will only describe the connection to sshd, so the target sshd can be replace with any other target service, sslh supports.
|
||||
|
||||
## Introduction in the data flow ##
|
||||
This chapter can be skipped, if you just like to configure things fast.
|
||||
This chapter is a little excurse to the dataflow. First point of all is something, which you will unfortunately not see in the nice routing diagrams for iptables or netfilter-tables (nft) like: [Iptables at wikipedia](https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Netfilter-packet-flow.svg/2560px-Netfilter-packet-flow.svg.png), [Netfilter Flow](https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Netfilter_hooks_into_Linux_networking_packet_flows).
|
||||
|
||||
Packets from local application talking to other local applications are routed through the loopback-interface. They leave postrouting to lo and reentering from there prerouting, without passing ingress/egress.
|
||||
This has nothing to do with the "**route_localnet = 1**" trick, which only makes, that the the local ip range 10.0.0.0/8 gets routed!
|
||||
As you can read in many articles, this is nothing you should do, as you may bring your system at risk, because it allows to leak packets from outside to applications, which feel themselves secure, by using those unroutable addresses.
|
||||
|
||||
#### A Simple Simulation ####
|
||||
You can prove this behaviour with a simple test:
|
||||
```
|
||||
# In one terminal start socat as a local echo server
|
||||
# this is simulating sshd
|
||||
|
||||
socat TCP4-LISTEN:2000,bind=SERVER_IP,fork EXEC:cat
|
||||
|
||||
# In the next terminal start another instance of socat,
|
||||
# simulating sslh
|
||||
|
||||
socat TCP-LISTEN:3000,bind=SERVER_IP,fork TCP:SERVER_IP:2000
|
||||
|
||||
# In another terminal you can watch the traffic on lo
|
||||
|
||||
tcpdump -i lo port 2000
|
||||
|
||||
# In the last terminal talk to the echo server
|
||||
telnet SERVER_IP 2000
|
||||
```
|
||||
You will see your traffic on lo, but not on eth0, if you retry the tcpdump there.
|
||||
|
||||
If you setup sslh as non transparent proxy, it will just work, as what we have seen.
|
||||
|
||||
#### Going Transparent ####
|
||||
|
||||
In case of transparent proxy however, sslh uses some tricks, to reuse the clients IP on its outgoing interface to sshd. It opens the interface in raw mode, so it either needs to be started as root and drop privileges after binding, or you will need to give some capabilities to the sslh binary (cap_net_bind_service,cap_net_raw+ep), if you will start it as restricted user.
|
||||
In this setup we continue, with dropping priviledges.
|
||||
|
||||
Doing so, you can send packets to the sshd, listening on another interface, but, the answer packets from sshd will get routed back to the client. This however will not work, as the client would refuse those packets, because they don't belong to a tcp session, the client opened. In most cases those packets would even not reach the client, as source ip addresses from private address space, will be blocked by most internet routers and connection providers.
|
||||
|
||||
So its mandatory, to use some tricks, to get those packets back to sslh. All configurations, I have seen so far, are using two components for that. They bind sshd to lo, and than they introduce some firewall rules, to mark packets, originating from the sshd port on lo, so that those packets can be routed in a next step -based on that marking- back to sslh.
|
||||
|
||||
##### Drawbacks From Using loopback #####
|
||||
This idea has some serious drawbacks: First, you need to allow routing of the local address space, 127.0.0.0/8, with kernel configurations. Search for the string "net.ipv4.conf.default.route_localnet" and you will find lot of articles, why you should not do this.
|
||||
By allowing this, you need additional firewall rules, dropping martian packets, which otherwise would get routed to the internet from other applications, running on lo, not aware, that their traffic could be routed. You need further firewall rules, blocking incoming packets to loopback addresses, as otherwise some applications (especially udp) could be the goal of some bad traffic.
|
||||
|
||||
##### Using A Dedicated Interface #####
|
||||
So this configuration makes use of a own interface, just for the services, where sslh should hide the traffic for. We use a interface of the dummy kernel module, which was designed just for this case. It is an interface, beeing there, having no cable connection or whatsever, but applications can bind to it. We assign to this interface just a /32 private address, as this interface is not part of any network.
|
||||
|
||||
Doing so, we can avoid all the hassle with marking certain packets, coming from the single applications, sslh has to hide, as we now just route ALL traffic from this specific interface by its ip to sslh.
|
||||
We need one routing rule and one routing table, this covers as many targets sslh will serve on this interface, without adding additional rules for adding apache, openvpn and others.
|
||||
|
||||
We need no firewall rules, preventing martians, as this single routing rule will deadroute all traffic from this interface, if sslh is not catching it up.
|
||||
|
||||
We only need firewall protection for this specific ip address, when we have activated ip forwarding on that system. If the system is no router and needs no forwarding, there is no protection needed.
|
||||
|
||||
## Finally The Configuration ##
|
||||
|
||||
As described, we need as a first step a dedicated interface, just for the services, sslh should hide. Its possible, to generate individual interfaces for different configurations, however, that makes things again more complex and has no advantages seen so far.
|
||||
|
||||
### Named Routing Table ###
|
||||
As we configure the needed routing rules in the interface configuration, we need to define a name for the sslh routing table first.
|
||||
Using named routing tables helps, understanding the routing configuration, as a name indicates, why this routing table is configured.
|
||||
To do so go to _**/etc/iproute2/rt_tables**_ and add a line
|
||||
|
||||
```
|
||||
111 sslh
|
||||
```
|
||||
With newer versions of iproute2 the /etc/iproute2 directory with the embedded templates got no longer installed. The cause maybe, that the example names, which were not used in any configuration, generated confusion. However, once you need those files, generate them and they will be honoured. You still can use just numbers for your routing table. But doing this, and having more than one routing table, you need a list, which numer belongs to which configuration.
|
||||
And seeing in the output from `ip route list table all ` the tables names instead just numbers is worth creating the file.
|
||||
|
||||
### Dummy Interface ###
|
||||
Now we configure our dedicated interface.
|
||||
In the file _**/etc/network/interfaces**_, we place this entry:
|
||||
```
|
||||
auto dummy0
|
||||
iface dummy0 inet static
|
||||
address 192.168.255.254/32
|
||||
pre-up modprobe dummy
|
||||
## Attention! with kernels, not automatically creating a dummy0
|
||||
## interface after module loading the following line should be:
|
||||
## pre-up modprobe dummy; if [ ! -e /sys/class/net/dummy0 ]; then ip link add dummy0 type dummy ; fi
|
||||
post-up ip rule add from 192.168.255.254 table sslh
|
||||
post-up ip route add local 0.0.0.0/0 dev dummy0 table sslh
|
||||
pre-down ip route del local 0.0.0.0/0 dev dummy0 table sslh
|
||||
pre-down ip rule del from 192.168.255.254 table sslh
|
||||
```
|
||||
As long, as your system has no other interfaces with private address-space, or is routing such addresses, you can continue with the given example. Otherwise you need to select a conflict free address.
|
||||
If you are updating a older current configuration, make sure, that you have no longer insecure localnet routing in place:
|
||||
```
|
||||
sysctl net.ipv4.conf.default.route_localnet
|
||||
sysctl net.ipv4.conf.all.route_localnet
|
||||
```
|
||||
should both report "0"!
|
||||
|
||||
|
||||
### Explanation Of The Routing Rules ###
|
||||
The two routing rules in the dummy0 interface configuration are the key for this configuration.
|
||||
|
||||
The first line is an routing rule entry, routing everything coming from the dummy0 ip source address to a special routing table _**sslh**_.
|
||||
|
||||
The next line generates this table implicitly, by inserting a single rule, routing everything from that ip address to dummy0.
|
||||
|
||||
Opposite to other firewall based configurations, we have those rules now tied to the dummy0 device, dedicated to the hidden services.
|
||||
When this interface comes up, the routing rules are making sure, that no martian packets can leave the system, by some processes using this IP address. When the interface goes down, we delete those rules.
|
||||
Also the startup script needs lo longer special treatment for the transparent mode.
|
||||
|
||||
|
||||
#### SSLH Default Configuration ####
|
||||
And finally you need to configute _**/etc/default/sslh**_ with the right settings for all the services, sslh should work for.
|
||||
```
|
||||
DAEMON_OPTS="--user sslh --listen SERVER_IP:443 --transparent \
|
||||
--ssh 192.168.255.254:22 --tls 192.168.255.254:443 \
|
||||
--pidfile /var/run/sslh/sslh.pid"
|
||||
```
|
||||
|
||||
#### Systemd ####
|
||||
This setup is now startup agnostic. As we don't need special treatment in the startup script, the sysV based init scripts will just work, like the systemd scripts. Nothing needs to be modified, when going transparent.
|
||||
|
||||
#### Remote Setups ####
|
||||
This concept can also be adapted for several setups, where the sshd (or any other target service) is running in a container, kvm-virtual machine, etc.
|
||||
Precondition is, that the target system is the next hop and uses the sslh-hosting system as default gateway. In addition you need to bind an additional ip-address, solely used for sshd on the corresponding interface.
|
||||
Than you can adapt the routing rule, routing traffic coming back from this ip to the sslh-routing-table.
|
||||
Its also possible, to forward to an next hop system, which has its own default gateway back, bypassing the sslh-host.
|
||||
In this case, you need to add a special route back to the sslh host, for all traffic with the sshd source ip address. This can be done similar to the two rules described above:
|
||||
```
|
||||
# first define a name for the table in /etc/iproute2/rt_tables e.g. sslh-routeback
|
||||
ip rule add from IPADRESS-OF-SERVIE table sslh-routeback
|
||||
ip route add default via IPADDRESS-OF_SSLH-HOST dev eth0 table sslh-routeback
|
||||
```
|
||||
The details are depending on your network settings. Als long, as the forward chain to the hidden service passes systems under your control, you can add backroutes on each system in that route. Precondition: The used ip address produces no conflict on those systems.
|
||||
|
||||
[I added a second document](./scenarios-for-simple-transparent-proxy.md), describing three possible scenarios in detail. Those three scenarios should cover all setups related to transparent proxying.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
File diff suppressed because it is too large.
Load diff
|
After Width: | Height: | Size: 158 KiB |
+6
-10
@@ -1,15 +1,11 @@
|
||||
# Transparent proxy
|
||||
# Transparent proxy using packet marking
|
||||
|
||||
On Linux and FreeBSD you can use the `--transparent` option to
|
||||
request transparent proxying. This means services behind `sslh`
|
||||
(Apache, `sshd` and so on) will see the external IP and ports
|
||||
as if the external world connected directly to them. This
|
||||
simplifies IP-based access control (or makes it possible at
|
||||
all).
|
||||
Before reading further, make sure you try to set up
|
||||
transparent proxying using [IP routing](simple_transparent_proxy.md).
|
||||
It is conceptually easier to understand, cleaner, and more
|
||||
portable.
|
||||
|
||||
This document shows recipes that may help to do that.
|
||||
|
||||
Note that getting this to work is very tricky and
|
||||
Using this method is very tricky and
|
||||
detail-dependant: depending on whether the target server and
|
||||
sslh are on the same machine, different machines, or
|
||||
different dockers, and tool versions, all seem to change the
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# sslh configuration for saaz:
|
||||
# ssh to localhost
|
||||
# https to nextcloud
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
user: "yves";
|
||||
pidfile: "/tmp/sslh.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Where to write the ECH configurations
|
||||
ech_conf_sink: "/tmp/ech";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||
|
||||
|
||||
verbose: 1;
|
||||
|
||||
|
||||
# Change hostname with your external address name.
|
||||
listen:
|
||||
(
|
||||
{ host: "inet"; port: "4443"; },
|
||||
{ host: "chaton"; port: "4445"; }
|
||||
);
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
{ name: "tls"; sni_hostnames: [
|
||||
"inet.rutschle.net", "lists.rutschle.net"
|
||||
];
|
||||
host: "localhost"; port: "4453"; },
|
||||
{ name: "tls"; sni_hostnames: [
|
||||
"chaton.rutschle.net"
|
||||
];
|
||||
host: "localhost"; port: "4455"; }
|
||||
);
|
||||
|
||||
|
||||
on-timeout: "ssh";
|
||||
+49
-26
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Apr 30 09:55:03 2022.
|
||||
* on Wed Sep 10 15:49:49 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -36,6 +36,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include "echosrv-conf.h"
|
||||
#include "argtable3.h"
|
||||
#ifdef LIBPCRE
|
||||
@@ -216,6 +218,19 @@ char* config_error_text(config_t* c) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static jmp_buf c2s_asprintf_fail;
|
||||
|
||||
static int c2s_asprintf(char **restrict strp, const char *restrict fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
int res = vasprintf(strp, fmt, ap);
|
||||
va_end(ap);
|
||||
if (res == -1) longjmp(c2s_asprintf_fail, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* This is the same as config_setting_lookup_string() except
|
||||
it allocates a new string which belongs to the caller */
|
||||
static int myconfig_setting_lookup_stringcpy(
|
||||
@@ -226,7 +241,7 @@ static int myconfig_setting_lookup_stringcpy(
|
||||
const char* str;
|
||||
*value = NULL;
|
||||
if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
|
||||
asprintf(value, "%s", str);
|
||||
c2s_asprintf(value, "%s", str);
|
||||
return CONFIG_TRUE;
|
||||
} else {
|
||||
return CONFIG_FALSE;
|
||||
@@ -310,7 +325,7 @@ static int settingcpy(config_type type, void* target, const config_setting_t* se
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
c2s_asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -353,7 +368,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -365,7 +380,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy the value of a string argument to arbitrary memory
|
||||
/* Copy the value of a string argument to arbitary memory
|
||||
* location that must be large enough, converting on the way
|
||||
* (i.e. CFG_INT gets atoi() and so on) */
|
||||
/* 0: success
|
||||
@@ -765,7 +780,7 @@ static int read_block_setval(void* target,
|
||||
/* setting is present in cfg, look it up */
|
||||
if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) {
|
||||
TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type]));
|
||||
asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
desc->name, type2str[desc->type]);
|
||||
return 0;
|
||||
}
|
||||
@@ -822,7 +837,7 @@ static int read_block(config_setting_t* cfg, void* target, struct config_desc* d
|
||||
set = read_block_setval(target, cfg, desc, errmsg);
|
||||
|
||||
if (!set && desc->mandatory) {
|
||||
asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -862,7 +877,7 @@ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, con
|
||||
if (pmatch[pmatch_cnt].rm_so == -1) {
|
||||
/* This should not happen as regexec() did
|
||||
* match before, unless there is a
|
||||
* discrepancy between the regex and the
|
||||
* discrepency between the regex and the
|
||||
* number of backreferences */
|
||||
return 0;
|
||||
}
|
||||
@@ -980,13 +995,13 @@ static int regcompmatch_posix( regmatch_t* pmatch,
|
||||
int errlen = regerror(res, &preg, NULL, 0);
|
||||
regerr = malloc(errlen);
|
||||
regerror(res, &preg, regerr, errlen);
|
||||
asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
free(regerr);
|
||||
return 0;
|
||||
}
|
||||
res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0);
|
||||
if (res) {
|
||||
asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
c2s_asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
arg_cl->hdr.longopts,
|
||||
arg->regex);
|
||||
return 0;
|
||||
@@ -1012,7 +1027,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);
|
||||
if (!pcre) {
|
||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
arg->regex, error, err_str, error_offset);
|
||||
return 0;
|
||||
}
|
||||
@@ -1022,7 +1037,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
0, 0, matches, NULL);
|
||||
if (res < 0) {
|
||||
pcre2_get_error_message(res, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
arg_cl->sval[arg_index], arg->regex, res, err_str);
|
||||
return 0;
|
||||
}
|
||||
@@ -1111,13 +1126,13 @@ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg)
|
||||
/* Read config file */
|
||||
if (config_read_file(c, filename) == CONFIG_FALSE) {
|
||||
if (config_error_line(c) != 0) {
|
||||
asprintf(errmsg, "%s:%d:%s",
|
||||
c2s_asprintf(errmsg, "%s:%d:%s",
|
||||
filename,
|
||||
config_error_line(c),
|
||||
config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -1128,23 +1143,23 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
{
|
||||
switch(config_setting_type(s)) {
|
||||
case CONFIG_TYPE_INT:
|
||||
asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_BOOL:
|
||||
asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_INT64:
|
||||
asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_FLOAT:
|
||||
asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_STRING:
|
||||
asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
break;
|
||||
|
||||
default: /* This means a bug */
|
||||
@@ -1155,7 +1170,7 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
|
||||
/* Typesets all the settings in a configuration as a
|
||||
* newly-allocated string. The string management is caller's
|
||||
* responsibility.
|
||||
* responsability.
|
||||
* Returns the number of scalars in the configuration */
|
||||
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
||||
{
|
||||
@@ -1172,9 +1187,9 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
if(config_setting_is_list(parent) ||
|
||||
config_setting_is_array(parent)) {
|
||||
asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
} else {
|
||||
asprintf(&subpath, "%s/%s", path, name);
|
||||
c2s_asprintf(&subpath, "%s/%s", path, name);
|
||||
}
|
||||
|
||||
if (config_setting_is_scalar(child)) {
|
||||
@@ -1182,12 +1197,12 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
/* Add value to the output string */
|
||||
if (*strp) {
|
||||
asprintf(&old, "%s", *strp);
|
||||
c2s_asprintf(&old, "%s", *strp);
|
||||
free(*strp);
|
||||
} else {
|
||||
asprintf(&old, "%s", "");
|
||||
c2s_asprintf(&old, "%s", "");
|
||||
}
|
||||
asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
free(value);
|
||||
free(old);
|
||||
|
||||
@@ -1224,6 +1239,14 @@ int echocfg_cl_parse(int argc, char* argv[], struct echocfg_item* cfg)
|
||||
|
||||
};
|
||||
|
||||
/* Set up failure handler in case asprintf() runs out of
|
||||
* memory */
|
||||
;
|
||||
if (setjmp(c2s_asprintf_fail)) {
|
||||
fprintf(stderr, "asprintf: probably out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse command line */
|
||||
nerrors = arg_parse(argc, argv, argtable);
|
||||
if (nerrors) {
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Apr 30 09:55:03 2022.
|
||||
* on Wed Sep 10 15:49:49 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <pwd.h>
|
||||
#include <syslog.h>
|
||||
#include <libgen.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define cfg sslhcfg
|
||||
@@ -62,9 +61,10 @@ void check_res_dump(int res, struct addrinfo *addr, char* syscall)
|
||||
|
||||
void start_echo(int fd)
|
||||
{
|
||||
int res;
|
||||
ssize_t res;
|
||||
char buffer[1 << 20];
|
||||
int ret, prefix_len;
|
||||
ssize_t ret;
|
||||
size_t prefix_len;
|
||||
int first = 1;
|
||||
|
||||
prefix_len = strlen(cfg.prefix);
|
||||
@@ -151,7 +151,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(src_addr);
|
||||
size_t len = recvfrom(listen_socket->socketfd,
|
||||
ssize_t len = recvfrom(listen_socket->socketfd,
|
||||
data + prefix_len,
|
||||
sizeof(data) - prefix_len,
|
||||
0,
|
||||
@@ -162,11 +162,11 @@ void udp_echo(struct listen_endpoint* listen_socket)
|
||||
perror("recvfrom");
|
||||
}
|
||||
*(data + prefix_len + len) = 0;
|
||||
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
|
||||
fprintf(stderr, "%zd %s\n", len, data + prefix_len);
|
||||
|
||||
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
|
||||
|
||||
int res = sendto(listen_socket->socketfd,
|
||||
ssize_t res = sendto(listen_socket->socketfd,
|
||||
data,
|
||||
len + prefix_len,
|
||||
0,
|
||||
@@ -322,9 +322,6 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int num_addr_listen;
|
||||
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
+37
-5
@@ -16,6 +16,7 @@ chroot: "/var/empty";
|
||||
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||
# Defaults are indicated here, and should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
# Each option relates to a different set of messages.
|
||||
verbose-config: 0; # print configuration at startup
|
||||
verbose-config-error: 3; # print configuration errors
|
||||
verbose-connections: 3; # trace established incoming address to forward address
|
||||
@@ -28,6 +29,9 @@ verbose-probe-error: 3; # failures and problems during probing
|
||||
verbose-system-error: 3; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 3; # internal errors, the kind that should never happen
|
||||
|
||||
# This one is special and overrides all previous options if
|
||||
# set, as a quick way to get "as much as possible"
|
||||
#verbose: 3;
|
||||
|
||||
# Specify a path to the logfile.
|
||||
#logfile: "/var/log/sslh.log"
|
||||
@@ -45,11 +49,21 @@ syslog_facility: "auth";
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
# host: name of the interface to listen to
|
||||
# port: name of the service to listen to
|
||||
# is_udp: listen in the UDP domain (default is TCP)
|
||||
# is_unix: listen in the UNIX socket domain (default is TCP)
|
||||
# keepalive: set TCP_KEEPALIVE
|
||||
# max_connections: drop incoming connections after this count
|
||||
# proxyprotocol: [true|false]: this endpoint expects
|
||||
# proxyprotocol headers on incoming connections. Refer to
|
||||
# doc/proxyprotocol.md
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; },
|
||||
{ host: "thelonious"; port: "443"; max_connections: 5; },
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; },
|
||||
{ host: "thelonious"; is_udp: true; port: "443" }
|
||||
{ host: "thelonious"; is_udp: true; port: "443"; },
|
||||
{ host: "/tmp/unix_socket"; is_unix: true; port: ""; }
|
||||
);
|
||||
|
||||
# List of protocols
|
||||
@@ -65,20 +79,32 @@ listen:
|
||||
# keepalive: Should TCP keepalive be on or off for that
|
||||
# connection (default is off)
|
||||
# fork: Should a new process be forked for this protocol?
|
||||
# (only useful for sslh-select)
|
||||
# (only useful for sslh-select and sslh-ev)
|
||||
# max_connections: further connections for this protocol
|
||||
# will be rejected once the limit is reached. Does not
|
||||
# work with sslh-fork.
|
||||
# tfo_ok: Set to true if the server supports TCP FAST OPEN
|
||||
# resolve_on_forward: Set to true if server address should be resolved on
|
||||
# (every) newly incoming connection (again)
|
||||
# transparent: Set to true to proxy this protocol
|
||||
# transparently (server sees the remote client IP
|
||||
# address). Same as the global option, but per-protocol
|
||||
# is_unix: [true|false] connect to a UNIX socket. The host
|
||||
# field becomes the pathname to the socket, and the port
|
||||
# field is unused (but necessary).
|
||||
# proxyprotocol: <1|2>; When connecting to the backend
|
||||
# server, a proxyprotocol header of the specified
|
||||
# version will be added, containing the client's
|
||||
# connection information.
|
||||
#
|
||||
# Probe-specific options:
|
||||
# (sslh will try each probe in order they are declared, and
|
||||
# connect to the first that matches.)
|
||||
#
|
||||
# tls:
|
||||
# sni_hostnames: list of FQDN for that target
|
||||
# sni_hostnames: list of FQDN for that target. Each name can
|
||||
# include wildcard following glob(7) rules.
|
||||
|
||||
# alpn_protocols: list of ALPN protocols for that target, see:
|
||||
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||
#
|
||||
@@ -94,12 +120,18 @@ listen:
|
||||
# that target.
|
||||
#
|
||||
# You can specify several of 'regex' and 'tls'.
|
||||
#
|
||||
# If you want to filter on incoming IP addresses, you can
|
||||
# use libwrap which will use /etc/hosts.allow and
|
||||
# /etc/hosts.deny.
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22";
|
||||
keepalive: true; fork: true; tfo_ok: true },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
|
||||
# UNIX socket to a local NGINX. The socket name is in 'host'; 'port' is necessary but not used.
|
||||
{ name: "http"; is_unix: true; host: "/tmp/nginx.sock"; port: ""; },
|
||||
|
||||
# match BOTH ALPN/SNI
|
||||
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true },
|
||||
|
||||
@@ -59,6 +59,11 @@ gap_array* gap_init(int len)
|
||||
return gap;
|
||||
}
|
||||
|
||||
void gap_set_hardlimit(gap_array* gap, int index)
|
||||
{
|
||||
gap->hardlimit = index;
|
||||
}
|
||||
|
||||
int gap_extend(gap_array* gap)
|
||||
{
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
@@ -88,7 +93,11 @@ void gap_destroy(gap_array* gap)
|
||||
* is considered len elements long.
|
||||
* A poor man's list, if you will. Currently only used to remove probing
|
||||
* connections, so it only copies a few pointers at most.
|
||||
* Returns -1 if ptr was not found */
|
||||
* Returns:
|
||||
* -1 if ptr was not found
|
||||
* -2 if `len` is inconsistent with gap->len (i.e. trying to shift beyond the
|
||||
* end of the array)
|
||||
* */
|
||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
||||
{
|
||||
int start, i;
|
||||
@@ -102,6 +111,9 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
||||
else
|
||||
return -1;
|
||||
|
||||
if (gap->len < len)
|
||||
return -2;
|
||||
|
||||
for (i = start; i < len - 1; i++) {
|
||||
gap->array[i] = gap->array[i+1];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
typedef struct gap_array gap_array;
|
||||
|
||||
gap_array* gap_init(int len);
|
||||
void gap_set_hardlimit(gap_array* ga, int index);
|
||||
static void* gap_get(gap_array* gap, int index);
|
||||
static int gap_set(gap_array* gap, int index, void* ptr);
|
||||
void gap_destroy(gap_array* gap);
|
||||
@@ -13,7 +14,8 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len);
|
||||
/* Private declarations to allow inlining.
|
||||
* Don't assume my implementation. */
|
||||
typedef struct gap_array {
|
||||
int len; /* Number of elements in array */
|
||||
int len; /* Number of elements in array (corresponds to the number of pages allocated) */
|
||||
int hardlimit; /* Maximum index allowed after which sets will fail; 0 means no limit */
|
||||
void** array;
|
||||
} gap_array;
|
||||
|
||||
@@ -21,6 +23,9 @@ int gap_extend(gap_array* gap);
|
||||
|
||||
static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, void* ptr)
|
||||
{
|
||||
if (gap->hardlimit && (index > gap->hardlimit))
|
||||
return -1;
|
||||
|
||||
while (index >= gap->len) {
|
||||
int res = gap_extend(gap);
|
||||
if (res == -1) return -1;
|
||||
@@ -37,6 +42,8 @@ static inline void* __attribute__((unused)) gap_get(gap_array* gap, int index)
|
||||
* gap_get()'s job. This will do for now */
|
||||
if (index >= gap->len) return NULL;
|
||||
|
||||
if (gap->hardlimit && (index > gap->hardlimit)) return NULL;
|
||||
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
|
||||
@@ -37,13 +37,35 @@
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if HASH_DEBUG
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include "gap.h"
|
||||
|
||||
#if 0
|
||||
#define DEBUG 1
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
typedef void* hash_item;
|
||||
|
||||
typedef struct hash hash;
|
||||
#include "hash.h"
|
||||
|
||||
static void* const FREE = NULL;
|
||||
|
||||
|
||||
/* Define DEBUG adds two functions:
|
||||
* hash_dump() that prints the hash to a file as text
|
||||
* hash_item_print() takes a pointer to a function that prints a hash item
|
||||
* See test suite in hashtest/ for an example
|
||||
* */
|
||||
#if 0
|
||||
#define HASH_DEBUG 1
|
||||
#endif
|
||||
|
||||
struct hash {
|
||||
int hash_size; /* Max number of items in the hash */
|
||||
int item_cnt; /* Number of items in the hash */
|
||||
@@ -51,9 +73,11 @@ struct hash {
|
||||
|
||||
hash_make_key_fn hash_make_key;
|
||||
hash_cmp_item_fn cmp_item;
|
||||
#ifdef HASH_DEBUG
|
||||
void (*hash_item_print)(FILE* out, hash_item item);
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef struct hash hash;
|
||||
|
||||
|
||||
static int hash_make_key(hash* h, hash_item item)
|
||||
@@ -131,6 +155,10 @@ static int distance(int current_index, hash* h, hash_item item)
|
||||
return current_index - wanted_index + h->hash_size;
|
||||
}
|
||||
|
||||
int hash_insertable(hash* h)
|
||||
{
|
||||
return (h->item_cnt < h->hash_size);
|
||||
}
|
||||
|
||||
int hash_insert(hash* h, hash_item new)
|
||||
{
|
||||
@@ -146,7 +174,7 @@ int hash_insert(hash* h, hash_item new)
|
||||
if (distance(index, h, curr_item) < distance(index, h, new)) {
|
||||
gap_set(h->data, index, new);
|
||||
#if DEBUG
|
||||
fprintf(stderr, "intermediate insert [%s] at %d\n", &new->client_addr, index);
|
||||
fprintf(stderr, "intermediate insert at %d\n", index);
|
||||
#endif
|
||||
new = curr_item;
|
||||
}
|
||||
@@ -188,17 +216,16 @@ int hash_remove(hash* h, hash_item item)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HASH_TESTING
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#define STR_LENGTH 16
|
||||
struct hash_item {
|
||||
int wanted_index;
|
||||
char str[STR_LENGTH];
|
||||
};
|
||||
/******************************************************************************/
|
||||
#if HASH_DEBUG
|
||||
void hash_item_print(hash* h, void fn(FILE*, hash_item))
|
||||
{
|
||||
h->hash_item_print = fn;
|
||||
}
|
||||
|
||||
|
||||
void hash_dump(hash* h, char* filename)
|
||||
{
|
||||
char str[STR_LENGTH];
|
||||
FILE* out = fopen(filename, "w");
|
||||
|
||||
if (!out) {
|
||||
@@ -209,14 +236,13 @@ void hash_dump(hash* h, char* filename)
|
||||
fprintf(out, "<hash elem=%d>\n", h->item_cnt);
|
||||
for (int i = 0; i < h->hash_size; i++) {
|
||||
hash_item item = gap_get(h->data, i);
|
||||
int idx = 0;
|
||||
|
||||
memset(str, 0, STR_LENGTH);
|
||||
if (item) {
|
||||
idx = hash_make_key(h, item);
|
||||
memcpy(str, ((struct hash_item*)item)->str, STR_LENGTH);
|
||||
}
|
||||
fprintf(out, "\t%d:%d:%s\n", i, idx, str);
|
||||
fprintf(out, "\t%d:", i);
|
||||
if (item)
|
||||
h->hash_item_print(out, item);
|
||||
else
|
||||
fprintf(out, "<empty>");
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, "</hash>\n");
|
||||
fclose(out);
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
/* You will need to typedef a pointer type to hash_item before including this
|
||||
* .h */
|
||||
|
||||
typedef struct hash hash;
|
||||
|
||||
/* Function that returns a key (index) for a given item. The key must be always
|
||||
* the same for an item. It doesn't need to be bounded (hash.c masks it for you) */
|
||||
typedef int (*hash_make_key_fn)(hash_item item);
|
||||
@@ -15,6 +13,7 @@ typedef int (*hash_cmp_item_fn)(hash_item item1, hash_item item2);
|
||||
|
||||
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item);
|
||||
|
||||
int hash_insertable(hash* h); /* Is it possible to insert: any space left? */
|
||||
int hash_insert(hash* h, hash_item new);
|
||||
int hash_remove(hash* h, hash_item item);
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
|
||||
CFLAGS=-DHASH_TESTING -O2 -Wall
|
||||
CFLAGS=-DHASH_DEBUG -O2 -Wall
|
||||
OBJ=../hash.o ../gap.o htest.o
|
||||
|
||||
htest: $(OBJ)
|
||||
|
||||
+28
-28
@@ -1,34 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:10:aa
|
||||
11:10:ab
|
||||
12:10:ac
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:20:ba
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:10:aa
|
||||
11:10:ab
|
||||
12:12:ac
|
||||
13:13:ad
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:30:ad
|
||||
1:0:
|
||||
1:<empty>
|
||||
2:2:ba
|
||||
3:2:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:10:aa
|
||||
11:10:bc
|
||||
12:12:ac
|
||||
13:0:
|
||||
13:<empty>
|
||||
14:14:ad
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=0>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -20,7 +20,7 @@
|
||||
18:18:bi
|
||||
19:19:bj
|
||||
20:20:ca
|
||||
21:0:
|
||||
21:<empty>
|
||||
22:22:cd
|
||||
23:23:ce
|
||||
24:24:cf
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:10:ab
|
||||
11:0:
|
||||
11:<empty>
|
||||
12:12:ac
|
||||
13:13:ad
|
||||
14:14:ae
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:30:ba
|
||||
1:30:bb
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:30:ac
|
||||
1:1:ad
|
||||
2:0:
|
||||
2:<empty>
|
||||
3:3:ae
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=3>
|
||||
0:30:ad
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:31:ad
|
||||
1:0:
|
||||
1:<empty>
|
||||
2:2:ba
|
||||
3:3:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
Binary file not shown.
@@ -55,6 +55,11 @@ static void htest_next_key(FILE* f, char* action, int* key, char str[STR_LENGTH]
|
||||
if (res == EOF) exit(0);
|
||||
}
|
||||
|
||||
static void item_print(FILE* out, hash_item item)
|
||||
{
|
||||
fprintf(out, "%d:%s", item->wanted_index, item->str);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
hash* h = hash_init(HASH_SIZE, &hash_make_key, &cmp_item);
|
||||
@@ -63,6 +68,8 @@ int main(int argc, char* argv[])
|
||||
int line = 0;
|
||||
FILE* f;
|
||||
|
||||
hash_item_print(h, item_print);
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: htest <script file> <dump file>\n");
|
||||
exit(1);
|
||||
|
||||
+27
-27
@@ -1,34 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:10:aa
|
||||
11:10:ab
|
||||
12:10:ac
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:20:ba
|
||||
21:21:bb
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -1,34 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
0:<empty>
|
||||
1:<empty>
|
||||
2:<empty>
|
||||
3:<empty>
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:10:aa
|
||||
11:10:bc
|
||||
12:11:ab
|
||||
13:12:ac
|
||||
14:14:ad
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:<empty>
|
||||
31:<empty>
|
||||
</hash>
|
||||
@@ -3,32 +3,32 @@
|
||||
1:30:ad
|
||||
2:2:ba
|
||||
3:2:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -3,32 +3,32 @@
|
||||
1:30:ad
|
||||
2:0:ba
|
||||
3:0:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
4:<empty>
|
||||
5:<empty>
|
||||
6:<empty>
|
||||
7:<empty>
|
||||
8:<empty>
|
||||
9:<empty>
|
||||
10:<empty>
|
||||
11:<empty>
|
||||
12:<empty>
|
||||
13:<empty>
|
||||
14:<empty>
|
||||
15:<empty>
|
||||
16:<empty>
|
||||
17:<empty>
|
||||
18:<empty>
|
||||
19:<empty>
|
||||
20:<empty>
|
||||
21:<empty>
|
||||
22:<empty>
|
||||
23:<empty>
|
||||
24:<empty>
|
||||
25:<empty>
|
||||
26:<empty>
|
||||
27:<empty>
|
||||
28:<empty>
|
||||
29:<empty>
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
+38
-8
@@ -90,10 +90,44 @@ static int add_path_ro(int ruleset_fd, ll_obj_type otype, const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// close helper handle
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int add_libs(int ruleset_fd)
|
||||
{
|
||||
/* Access to libraries, to be able to fork */
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/lib");
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/usr/lib");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/ld.so.cache"); /* To avoid searching all libs... */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_resolv(int ruleset_fd)
|
||||
{
|
||||
/* Files to resolve names (required when dynamic resolution is used) */
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/resolv.conf");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/nsswitch.conf");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_libwrap(int ruleset_fd)
|
||||
{
|
||||
/* Files for libwrap */
|
||||
#ifdef LIBWRAP
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup_landlock(void)
|
||||
{
|
||||
__u64 restrict_rules =
|
||||
@@ -124,15 +158,11 @@ void setup_landlock(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Access to libraries, to be able to fork */
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/lib");
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/usr/lib");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/ld.so.cache"); /* To avoid searching all libs... */
|
||||
|
||||
/* Files to resolve names (required when dynamic resolution is used) */
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/resolv.conf");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/nsswitch.conf");
|
||||
/* Add all the paths we need */
|
||||
add_libs(ruleset_fd);
|
||||
add_resolv(ruleset_fd);
|
||||
add_libwrap(ruleset_fd);
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
|
||||
print_message(msg_config_error, "Landlock: Failed to restrict privileges");
|
||||
|
||||
@@ -21,10 +21,7 @@
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#ifdef ENABLE_REGEX
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
#include <regex.h>
|
||||
#include <ctype.h>
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
@@ -146,6 +143,7 @@ static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
|
||||
#define OVPN_OPCODE_MASK 0xF8
|
||||
#define OVPN_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3)
|
||||
#define OVPN_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3)
|
||||
#define OVPN_CONTROL_HARD_RESET_CLIENT_V3 (0x0A << 3)
|
||||
#define OVPN_HMAC_128 16
|
||||
#define OVPN_HMAC_160 20
|
||||
#define OVPN_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
|
||||
@@ -165,7 +163,9 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
|
||||
return PROBE_NEXT;
|
||||
|
||||
if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 &&
|
||||
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2)
|
||||
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2 &&
|
||||
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V3
|
||||
)
|
||||
return PROBE_NEXT;
|
||||
|
||||
/* The detection pattern above may not be reliable enough.
|
||||
@@ -176,13 +176,19 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128) + sizeof(uint32_t))
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))) <= 5u)
|
||||
uint32_t i;
|
||||
/* OVPN_HMAC_128 is unaligned, which requires special care e.g. on ARM */
|
||||
memcpy(&i, (p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128)), sizeof(i));
|
||||
i = ntohl(i);
|
||||
if (i <= 5u)
|
||||
return PROBE_MATCH;
|
||||
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160) + sizeof(uint32_t))
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u)
|
||||
memcpy(&i, (p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160)), sizeof(i));
|
||||
i = ntohl(i);
|
||||
if (i <= 5u)
|
||||
return PROBE_MATCH;
|
||||
|
||||
return PROBE_NEXT;
|
||||
@@ -323,7 +329,7 @@ static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
|
||||
if (len < min_data_packet_size + sizeof(empty_message))
|
||||
return PROBE_AGAIN;
|
||||
|
||||
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
|
||||
if (memcmp(&p[0], empty_message, sizeof(empty_message)) != 0)
|
||||
return PROBE_NEXT;
|
||||
|
||||
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
|
||||
@@ -362,17 +368,38 @@ static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_prot
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
/* ******************
|
||||
* is_syslog_protocol
|
||||
* */
|
||||
static regex_t syslog_preg;
|
||||
static int configured_syslog_regex = 0;
|
||||
|
||||
static void config_syslog_regex(void)
|
||||
{
|
||||
/* two patterns for syslog messages:
|
||||
* <12> My message
|
||||
* 15 <12> My message
|
||||
* 12 is 'priority', 1 to 3 digits (RFC4234)
|
||||
* 15 is 'message length', a TCP-only option (RFC6587)
|
||||
*/
|
||||
int res = regcomp(&syslog_preg, "^([0-9]{1,3} )?<[0-9]{1,3}>", REG_EXTENDED);
|
||||
if (res) {
|
||||
print_message(msg_system_error, "regcomp");
|
||||
exit(1);
|
||||
}
|
||||
configured_syslog_regex = 1;
|
||||
}
|
||||
|
||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res, i, j;
|
||||
char buf[len+1];
|
||||
|
||||
res = sscanf(p, "<%d>", &i);
|
||||
if (res == 1) return 1;
|
||||
if (!configured_syslog_regex) config_syslog_regex();
|
||||
|
||||
res = sscanf(p, "%d <%d>", &i, &j);
|
||||
if (res == 2) return 1;
|
||||
strncpy(buf, p, len);
|
||||
buf[len] = 0;
|
||||
|
||||
return 0;
|
||||
return (regexec(&syslog_preg, buf, (size_t)0, NULL, 0) == 0);
|
||||
}
|
||||
|
||||
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
@@ -396,16 +423,18 @@ static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocol
|
||||
return packet_len == len;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_REGEX
|
||||
pcre2_match_data* probe_regex_matches;
|
||||
#endif
|
||||
|
||||
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
#ifdef ENABLE_REGEX
|
||||
pcre2_code**probe = (pcre2_code**)proto->data;
|
||||
pcre2_match_data* matches;
|
||||
|
||||
matches = pcre2_match_data_create(1, NULL);
|
||||
|
||||
for (; *probe; probe++) {
|
||||
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL);
|
||||
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, probe_regex_matches, NULL);
|
||||
if (res >= 0) return 1;
|
||||
|
||||
}
|
||||
@@ -423,8 +452,8 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item
|
||||
* proto_out: protocol that matched
|
||||
*
|
||||
* Returns
|
||||
* PROBE_AGAIN if not enough data, and set *proto to NULL
|
||||
* PROBE_MATCH if protocol is identified, in which case *proto is set to
|
||||
* PROBE_AGAIN if not enough data, and set *proto_out to NULL
|
||||
* PROBE_MATCH if protocol is identified, in which case *proto_out is set to
|
||||
* point to the appropriate protocol
|
||||
* */
|
||||
int probe_buffer(char* buf, int len,
|
||||
@@ -436,9 +465,6 @@ int probe_buffer(char* buf, int len,
|
||||
struct sslhcfg_protocols_item* p;
|
||||
int i, res, again = 0;
|
||||
|
||||
print_message(msg_packets, "hexdump of incoming packet:\n");
|
||||
hexdump(msg_packets, buf, len);
|
||||
|
||||
*proto_out = NULL;
|
||||
for (i = 0; i < proto_len; i++) {
|
||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common.h"
|
||||
#include "tls.h"
|
||||
#include "log.h"
|
||||
#include "proxyprotocol.h"
|
||||
|
||||
typedef enum {
|
||||
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
||||
@@ -66,4 +67,11 @@ struct sslhcfg_protocols_item* timeout_protocol(void);
|
||||
|
||||
void hexdump(msg_info, const char*, unsigned int);
|
||||
|
||||
#ifdef ENABLE_REGEX
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
extern pcre2_match_data* probe_regex_matches;
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
+105
-4
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Processes that are common to sslh-ev and sslh-select
|
||||
|
||||
# Copyright (C) 2021 Yves Rutschle
|
||||
# Copyright (C) 2021-2025 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
@@ -26,6 +26,14 @@
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
/* Struct to keep track of an association of a forked PID to its
|
||||
* listening socket and its protocol */
|
||||
struct pid2proto {
|
||||
pid_t pid;
|
||||
struct listen_endpoint* endpoint;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
};
|
||||
|
||||
|
||||
int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
|
||||
{
|
||||
@@ -38,9 +46,9 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
|
||||
watchers_del_read(fd_info->watchers, cnx->q[i].fd);
|
||||
watchers_del_write(fd_info->watchers, cnx->q[i].fd);
|
||||
close(cnx->q[i].fd);
|
||||
if (cnx->q[i].deferred_data)
|
||||
free(cnx->q[i].deferred_data);
|
||||
}
|
||||
if (cnx->q[i].begin_deferred_data)
|
||||
free(cnx->q[i].begin_deferred_data);
|
||||
}
|
||||
|
||||
if (cnx->type == SOCK_DGRAM)
|
||||
@@ -49,6 +57,9 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
|
||||
if (gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing) != -1)
|
||||
fd_info->num_probing--;
|
||||
|
||||
dec_proto_connections(cnx->proto);
|
||||
dec_listen_connections(cnx->endpoint);
|
||||
|
||||
collection_remove_cnx(fd_info->collection, cnx);
|
||||
return 0;
|
||||
}
|
||||
@@ -88,9 +99,11 @@ struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_e
|
||||
int type = listen_socket->type;
|
||||
struct connection* cnx;
|
||||
|
||||
udp_timeouts(fd_info);
|
||||
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
cnx = accept_new_connection(fd, fd_info);
|
||||
cnx = accept_new_connection(listen_socket, fd_info);
|
||||
if (!cnx) return NULL;
|
||||
|
||||
break;
|
||||
@@ -112,4 +125,92 @@ struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_e
|
||||
|
||||
|
||||
|
||||
typedef struct pid2proto* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
static int pid_make_key(hash_item item)
|
||||
{
|
||||
return item->pid;
|
||||
}
|
||||
|
||||
static int pid_cmp(hash_item item1, hash_item item2)
|
||||
{
|
||||
return item1->pid != item2->pid;
|
||||
}
|
||||
|
||||
/* When a child has died, update the counts appropriately */
|
||||
void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
|
||||
struct pid2proto p2p = { .pid = pid };
|
||||
struct pid2proto* found = hash_find(loop->pid2proto, &p2p);
|
||||
dec_proto_connections(found->proto);
|
||||
dec_listen_connections(found->endpoint);
|
||||
|
||||
hash_remove(loop->pid2proto, found);
|
||||
free(found);
|
||||
}
|
||||
|
||||
|
||||
typedef struct pid2proto* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
int has_space_to_fork(struct loop_info* fd_info)
|
||||
{
|
||||
return hash_insertable(fd_info->pid2proto);
|
||||
}
|
||||
|
||||
|
||||
void remember_child_data(struct loop_info* fd_info,
|
||||
struct connection* cnx, pid_t pid)
|
||||
{
|
||||
struct pid2proto* pid2proto = malloc(sizeof(*pid2proto));
|
||||
pid2proto->pid = pid;
|
||||
pid2proto->proto = cnx->proto;
|
||||
pid2proto->endpoint = cnx->endpoint;
|
||||
if (hash_insert(fd_info->pid2proto, pid2proto)) {
|
||||
print_message(msg_int_error, "hash_insert for pid %d failed\n", pid);
|
||||
/* This should not happen as called is supposed to check that space is
|
||||
* available in the hash */
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static int max_forking_connections(void)
|
||||
{
|
||||
int max_cnx = 0;
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
if (cfg.protocols[i].fork) {
|
||||
if (cfg.protocols[i].max_connections_is_present)
|
||||
max_cnx += cfg.protocols[i].max_connections;
|
||||
else max_cnx += 512; /* A magic number that's hopefully high enough */
|
||||
}
|
||||
}
|
||||
return max_cnx;
|
||||
}
|
||||
|
||||
static int next_power_of_two(int n)
|
||||
{
|
||||
n |= (n >> 16);
|
||||
n |= (n >> 4);
|
||||
n |= (n >> 2);
|
||||
n |= (n >> 1);
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
void loop_init(struct loop_info* loop, int num_addr_listen)
|
||||
{
|
||||
memset(loop, 0, sizeof(*loop));
|
||||
loop->collection = collection_init(0);
|
||||
loop->probing_list = gap_init(0);
|
||||
CHECK_ALLOC(loop->probing_list, "gap_init");
|
||||
|
||||
udp_init(loop);
|
||||
tcp_init();
|
||||
|
||||
loop->num_addr_listen = num_addr_listen;
|
||||
|
||||
int max_forks = next_power_of_two(max_forking_connections());
|
||||
loop->pid2proto = hash_init(2 * max_forks, pid_make_key, pid_cmp);
|
||||
CHECK_ALLOC(loop->pid2proto, "hash_init");
|
||||
}
|
||||
|
||||
|
||||
+12
-3
@@ -5,13 +5,12 @@
|
||||
#include "collection.h"
|
||||
#include "gap.h"
|
||||
|
||||
typedef struct connection* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
/* Provided by event loop, sslh-ev or sslh-select, for implementation-dependant
|
||||
* data */
|
||||
typedef struct watchers watchers;
|
||||
|
||||
typedef void hash;
|
||||
|
||||
/* Global state for a loop */
|
||||
struct loop_info {
|
||||
int num_probing; /* Number of connections currently probing
|
||||
@@ -21,7 +20,10 @@ struct loop_info {
|
||||
|
||||
hash* hash_sources; /* UDP remote sources previously encountered */
|
||||
|
||||
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
|
||||
|
||||
watchers* watchers;
|
||||
int num_addr_listen; /* How many listen endpoints do we have here */
|
||||
|
||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||
};
|
||||
@@ -30,7 +32,12 @@ void cnx_read_process(struct loop_info* fd_info, int fd);
|
||||
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket);
|
||||
|
||||
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
|
||||
void loop_init(struct loop_info* loop, int num_addr_listen);
|
||||
|
||||
void remember_child_data(struct loop_info* fd_info,
|
||||
struct connection* cnx, pid_t pid);
|
||||
void decrease_forked_connection(struct loop_info* loop, pid_t pid);
|
||||
int has_space_to_fork(struct loop_info* loop);
|
||||
|
||||
/* These must be declared in the loop handler, sslh-ev or sslh-select */
|
||||
void watchers_add_read(watchers* w, int fd);
|
||||
@@ -38,4 +45,6 @@ void watchers_del_read(watchers* w, int fd);
|
||||
void watchers_add_write(watchers* w, int fd);
|
||||
void watchers_del_write(watchers* w, int fd);
|
||||
|
||||
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
|
||||
|
||||
#endif
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
# proxyprotocol: Support for HAProxy's proxyprotocol
|
||||
#
|
||||
# Copyright (C) 2025 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_PROXYPROTOCOL
|
||||
|
||||
#include <proxy_protocol.h>
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
/* Converts socket family to libproxyprotocol family */
|
||||
static int family_to_pp(int af_family)
|
||||
{
|
||||
switch (af_family) {
|
||||
case AF_INET:
|
||||
return ADDR_FAMILY_INET;
|
||||
|
||||
case AF_INET6:
|
||||
return ADDR_FAMILY_INET6;
|
||||
|
||||
case AF_UNIX:
|
||||
return ADDR_FAMILY_UNIX;
|
||||
|
||||
default:
|
||||
print_message(msg_int_error, "Unknown internal socket family %d\n", af_family);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
typedef char libpp_addr[108]; /* This is hardcoded in libproxyprotocol/proxy_protocol.h */
|
||||
|
||||
|
||||
typedef enum {
|
||||
PEER,
|
||||
SOCK
|
||||
} sockpeer_t;
|
||||
|
||||
/* Fills *addr, *host and *serv with the connection information corresponding
|
||||
* to fd. sockpeer indicates if if is filled with the remote, or local, part of
|
||||
* the socket.
|
||||
* *host is the IP address as string and *serv is the service (port)
|
||||
* */
|
||||
static int get_info(int fd, sockpeer_t sockpeer, struct addrinfo* addr, libpp_addr* host, uint16_t* serv)
|
||||
{
|
||||
char serv_str[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
if (sockpeer == PEER) {
|
||||
res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "getpeername", -1);
|
||||
} else {
|
||||
res = getsockname(fd, addr->ai_addr, &addr->ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "getsockname", -1);
|
||||
}
|
||||
|
||||
res = getnameinfo(addr->ai_addr, addr->ai_addrlen,
|
||||
(char*)host, sizeof(*host),
|
||||
serv_str, sizeof(serv_str),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV );
|
||||
if (res != 0) {
|
||||
print_message(msg_system_error, "getnameinfo: %s\n", gai_strerror(res));
|
||||
return -1;
|
||||
}
|
||||
|
||||
*serv = atoi(serv_str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int pp_write_header(int pp_version, struct connection* cnx)
|
||||
{
|
||||
pp_info_t pp_info_in_v1 = {
|
||||
.transport_protocol = TRANSPORT_PROTOCOL_STREAM,
|
||||
};
|
||||
uint16_t pp1_hdr_len;
|
||||
int32_t error;
|
||||
|
||||
struct sockaddr_storage ss;
|
||||
struct addrinfo addr;
|
||||
int res;
|
||||
|
||||
addr.ai_addr = (struct sockaddr*)&ss;
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
|
||||
res = get_info(cnx->q[0].fd, PEER,
|
||||
&addr,
|
||||
&pp_info_in_v1.src_addr,
|
||||
&pp_info_in_v1.src_port);
|
||||
if (res == -1) return -1;
|
||||
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
|
||||
|
||||
res = get_info(cnx->q[1].fd, SOCK,
|
||||
&addr,
|
||||
&pp_info_in_v1.dst_addr,
|
||||
&pp_info_in_v1.dst_port
|
||||
);
|
||||
if (res == -1) return -1;
|
||||
|
||||
uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
|
||||
|
||||
if (!pp1_hdr) {
|
||||
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error));
|
||||
return -1;
|
||||
}
|
||||
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
|
||||
|
||||
pp_info_clear(&pp_info_in_v1);
|
||||
free(pp1_hdr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Logs the connection data in the pp header and return header length */
|
||||
int pp_log_connection(char* buffer, ssize_t buffer_len)
|
||||
{
|
||||
pp_info_t pp_info;
|
||||
|
||||
int header_len = pp_parse_hdr((uint8_t*)buffer, buffer_len, &pp_info);
|
||||
|
||||
if (header_len > 0) {
|
||||
print_message(msg_connections, "client had proxyprotocol info: %s:%d to %s:%d not forwarded to server\n",
|
||||
pp_info.src_addr, pp_info.src_port,
|
||||
pp_info.dst_addr, pp_info.dst_port);
|
||||
} else {
|
||||
print_message(msg_connections, "no proxyprotocol data found on client side, forwarding as is\n");
|
||||
header_len = 0;
|
||||
}
|
||||
|
||||
return header_len;
|
||||
}
|
||||
|
||||
int pp_header_len(char* buffer, int buffer_len)
|
||||
{
|
||||
pp_info_t pp_info;
|
||||
|
||||
int header_len = pp_parse_hdr((uint8_t*)buffer, buffer_len, &pp_info);
|
||||
|
||||
print_message(msg_probe_info, "proxyprotocol header %d bytes found\n", header_len);
|
||||
|
||||
if (header_len < 0) header_len = 0;
|
||||
|
||||
return header_len;
|
||||
}
|
||||
|
||||
|
||||
#endif /* HAVE_PROXYPROTOCOL */
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef PROXYPROTOCOL_H
|
||||
#define PROXYPROTOCOL_H
|
||||
|
||||
|
||||
#if HAVE_PROXYPROTOCOL
|
||||
|
||||
|
||||
int pp_write_header(int pp_version, struct connection* cnx);
|
||||
int pp_log_connection(char* buffer, ssize_t buffer_len);
|
||||
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_header_len(char*, int) { return 0; }
|
||||
|
||||
#endif /* HAVE_PROXYPROTOCOL */
|
||||
|
||||
#endif /* PROXYPROTOCOL_H */
|
||||
@@ -0,0 +1,186 @@
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: sslh
|
||||
# Required-Start: $remote_fs $syslog $network
|
||||
# Required-Stop: $remote_fs $syslog $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: ssl/ssh multiplexer
|
||||
# Description: sslh lets one accept both HTTPS and SSH connections on the
|
||||
# same port. It makes it possible to connect to an SSH server
|
||||
# on port 443 (e.g. from inside a corporate firewall) while
|
||||
# still serving HTTPS on that port.
|
||||
### END INIT INFO
|
||||
|
||||
# Original Author: Guillaume Delacour <gui@iroqwa.org>
|
||||
# modified and optimized for current sslh-fork
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="ssl/ssh multiplexer"
|
||||
NAME=sslh
|
||||
DAEMON=/usr/sbin/$NAME
|
||||
DAEMON_OPTS=""
|
||||
PIDFILE=/var/run/sslh/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
RUN=yes
|
||||
|
||||
|
||||
# If you want to use a configuration file, put -F/path/to/sslh.cfg
|
||||
# into /etc/default/sslh DAEMON_OPTS
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Exit if the package is not installed
|
||||
if [ -x "$DAEMON" ]
|
||||
then
|
||||
echo "Can not start \"$DAEMON\", path not available"
|
||||
log_failure_msg "Can not start \"$DAEMON\", path not available"
|
||||
fi
|
||||
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
|
||||
# Use this if you want the user to explicitly set 'RUN' in
|
||||
# /etc/default/
|
||||
if [ "$RUN" != "yes" ]
|
||||
then
|
||||
echo "$NAME disabled, please adjust the configuration to your needs "
|
||||
log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it."
|
||||
return 2
|
||||
fi
|
||||
|
||||
# sslh write the pid as sslh user
|
||||
if [ ! -d /var/run/sslh/ ]
|
||||
then
|
||||
mkdir -p /var/run/sslh
|
||||
chown sslh:sslh /var/run/sslh
|
||||
fi
|
||||
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_OPTS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/45/KILL/5 --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# As long, as the started sslh is sslh-fork, don't kill the still existing
|
||||
# connections. You may need the following construct for sslh-ev and sslh-select,
|
||||
# as sslh has currently no function reloading its configuration.
|
||||
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
#start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
#[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
# don't activate this, as this kills only the leading process
|
||||
# of sslh-fork, and the spawned worker stays connected listening.
|
||||
# After that, the Owner of the PID from PIDFILE is gone, the
|
||||
# listening connection is still blocked
|
||||
# sslh can't reload its configuration as of Aug 2024
|
||||
#do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
# start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
# return 0
|
||||
#}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
# check if sslh is launched via inetd
|
||||
if [ -f /etc/inetd.conf ] && [ $(egrep -q "^https.*/usr/sbin/sslh" /etc/inetd.conf|wc -l) -ne 0 ]
|
||||
then
|
||||
echo "sslh is started from inetd."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer (select mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/default/sslh
|
||||
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
MountFlags=private
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
MemoryDenyWriteExecute=true
|
||||
DynamicUser=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,10 +1,10 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer
|
||||
Description=SSL/SSH multiplexer (fork mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/conf.d/sslh
|
||||
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
|
||||
EnvironmentFile=/etc/default/sslh
|
||||
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
+269
-59
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
* on Sat Jul 18 17:34:37 2026.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -36,6 +36,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include "sslh-conf.h"
|
||||
#include "argtable3.h"
|
||||
#ifdef LIBPCRE
|
||||
@@ -216,6 +218,19 @@ char* config_error_text(config_t* c) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static jmp_buf c2s_asprintf_fail;
|
||||
|
||||
static int c2s_asprintf(char **restrict strp, const char *restrict fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
int res = vasprintf(strp, fmt, ap);
|
||||
va_end(ap);
|
||||
if (res == -1) longjmp(c2s_asprintf_fail, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* This is the same as config_setting_lookup_string() except
|
||||
it allocates a new string which belongs to the caller */
|
||||
static int myconfig_setting_lookup_stringcpy(
|
||||
@@ -226,7 +241,7 @@ static int myconfig_setting_lookup_stringcpy(
|
||||
const char* str;
|
||||
*value = NULL;
|
||||
if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
|
||||
asprintf(value, "%s", str);
|
||||
c2s_asprintf(value, "%s", str);
|
||||
return CONFIG_TRUE;
|
||||
} else {
|
||||
return CONFIG_FALSE;
|
||||
@@ -310,7 +325,7 @@ static int settingcpy(config_type type, void* target, const config_setting_t* se
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
c2s_asprintf(&str, "%s", config_setting_get_string(setting));
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -353,7 +368,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
break;
|
||||
|
||||
case CFG_STRING:
|
||||
asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
|
||||
val.def_string = str;
|
||||
*(char**)target = val.def_string;
|
||||
break;
|
||||
@@ -443,6 +458,7 @@ struct compound_cl_arg {
|
||||
|
||||
|
||||
struct arg_file* sslhcfg_conffile;
|
||||
struct arg_int* sslhcfg_verbose;
|
||||
struct arg_int* sslhcfg_verbose_config;
|
||||
struct arg_int* sslhcfg_verbose_config_error;
|
||||
struct arg_int* sslhcfg_verbose_connections;
|
||||
@@ -466,6 +482,8 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_chroot;
|
||||
struct arg_str* sslhcfg_syslog_facility;
|
||||
struct arg_str* sslhcfg_logfile;
|
||||
struct arg_str* sslhcfg_ech_conf_sink;
|
||||
struct arg_str* sslhcfg_ech_public_name;
|
||||
struct arg_str* sslhcfg_on_timeout;
|
||||
struct arg_str* sslhcfg_prefix;
|
||||
struct arg_str* sslhcfg_listen;
|
||||
@@ -484,7 +502,7 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_anyprot;
|
||||
struct arg_end* sslhcfg_end;
|
||||
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg_protocols[] = {
|
||||
|
||||
|
||||
@@ -552,6 +570,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_unix",
|
||||
/* type */ CFG_BOOL,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_protocols_item, is_unix),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_udp",
|
||||
/* type */ CFG_BOOL,
|
||||
@@ -680,6 +714,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "max_connections",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_protocols_item, max_connections),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_protocols_item, max_connections_is_present),
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "sni_hostnames",
|
||||
/* type */ CFG_ARRAY,
|
||||
@@ -743,9 +793,25 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "proxyprotocol",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_protocols_item, proxyprotocol),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_protocols_item, proxyprotocol_is_present),
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg_listen[] = {
|
||||
|
||||
|
||||
@@ -781,6 +847,22 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "max_connections",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_listen_item, max_connections),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_listen_item, max_connections_is_present),
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_udp",
|
||||
/* type */ CFG_BOOL,
|
||||
@@ -797,6 +879,22 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "is_unix",
|
||||
/* type */ CFG_BOOL,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_listen_item, is_unix),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "keepalive",
|
||||
/* type */ CFG_BOOL,
|
||||
@@ -812,12 +910,44 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "proxyprotocol",
|
||||
/* type */ CFG_BOOL,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ NULL,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_listen_item, proxyprotocol),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_bool = 0
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg[] = {
|
||||
|
||||
|
||||
{
|
||||
/* name */ "verbose",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_verbose,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, verbose),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 0,
|
||||
/* default_val*/ .default_val.def_int = 0
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "verbose_config",
|
||||
/* type */ CFG_INT,
|
||||
@@ -1186,6 +1316,38 @@ static struct config_desc table_sslhcfg[] = {
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "ech_conf_sink",
|
||||
/* type */ CFG_STRING,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_ech_conf_sink,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, ech_conf_sink),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_item, ech_conf_sink_is_present),
|
||||
/* size */ sizeof(char*),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "ech_public_name",
|
||||
/* type */ CFG_STRING,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_ech_public_name,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, ech_public_name),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_item, ech_public_name_is_present),
|
||||
/* size */ sizeof(char*),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "on_timeout",
|
||||
/* type */ CFG_STRING,
|
||||
@@ -1255,7 +1417,7 @@ static struct compound_cl_target sslhcfg_anyprot_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1263,7 +1425,7 @@ static struct compound_cl_target sslhcfg_msrdp_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "msrdp" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1271,7 +1433,7 @@ static struct compound_cl_target sslhcfg_syslog_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1279,7 +1441,7 @@ static struct compound_cl_target sslhcfg_socks5_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1287,7 +1449,7 @@ static struct compound_cl_target sslhcfg_adb_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1295,7 +1457,7 @@ static struct compound_cl_target sslhcfg_http_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "http" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1303,7 +1465,7 @@ static struct compound_cl_target sslhcfg_xmpp_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1311,8 +1473,8 @@ static struct compound_cl_target sslhcfg_wireguard_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "wireguard" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1320,8 +1482,8 @@ static struct compound_cl_target sslhcfg_tinc_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1329,8 +1491,8 @@ static struct compound_cl_target sslhcfg_openvpn_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1338,8 +1500,8 @@ static struct compound_cl_target sslhcfg_ssl_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1347,8 +1509,8 @@ static struct compound_cl_target sslhcfg_tls_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1356,9 +1518,9 @@ static struct compound_cl_target sslhcfg_ssh_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "ssh" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[6], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -1372,7 +1534,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: listen */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_listen,
|
||||
.base_entry = & table_sslhcfg [25],
|
||||
.base_entry = & table_sslhcfg [28],
|
||||
.targets = sslhcfg_listen_targets,
|
||||
|
||||
|
||||
@@ -1384,7 +1546,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssh */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssh,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_ssh_targets,
|
||||
|
||||
|
||||
@@ -1396,7 +1558,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tls */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tls,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_tls_targets,
|
||||
|
||||
|
||||
@@ -1408,7 +1570,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssl */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssl,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_ssl_targets,
|
||||
|
||||
|
||||
@@ -1420,7 +1582,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: openvpn */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_openvpn,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_openvpn_targets,
|
||||
|
||||
|
||||
@@ -1432,7 +1594,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tinc */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tinc,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_tinc_targets,
|
||||
|
||||
|
||||
@@ -1444,7 +1606,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: wireguard */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_wireguard,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_wireguard_targets,
|
||||
|
||||
|
||||
@@ -1456,7 +1618,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: xmpp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_xmpp,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_xmpp_targets,
|
||||
|
||||
|
||||
@@ -1468,7 +1630,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: http */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_http,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_http_targets,
|
||||
|
||||
|
||||
@@ -1480,7 +1642,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: adb */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_adb,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_adb_targets,
|
||||
|
||||
|
||||
@@ -1492,7 +1654,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: socks5 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_socks5,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_socks5_targets,
|
||||
|
||||
|
||||
@@ -1504,7 +1666,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: syslog */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_syslog,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_syslog_targets,
|
||||
|
||||
|
||||
@@ -1516,7 +1678,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: msrdp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_msrdp,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_msrdp_targets,
|
||||
|
||||
|
||||
@@ -1528,7 +1690,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: anyprot */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_anyprot,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_anyprot_targets,
|
||||
|
||||
|
||||
@@ -1743,7 +1905,7 @@ static int read_block_setval(void* target,
|
||||
/* setting is present in cfg, look it up */
|
||||
if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) {
|
||||
TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type]));
|
||||
asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
|
||||
desc->name, type2str[desc->type]);
|
||||
return 0;
|
||||
}
|
||||
@@ -1800,7 +1962,7 @@ static int read_block(config_setting_t* cfg, void* target, struct config_desc* d
|
||||
set = read_block_setval(target, cfg, desc, errmsg);
|
||||
|
||||
if (!set && desc->mandatory) {
|
||||
asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1958,13 +2120,13 @@ static int regcompmatch_posix( regmatch_t* pmatch,
|
||||
int errlen = regerror(res, &preg, NULL, 0);
|
||||
regerr = malloc(errlen);
|
||||
regerror(res, &preg, regerr, errlen);
|
||||
asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
|
||||
free(regerr);
|
||||
return 0;
|
||||
}
|
||||
res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0);
|
||||
if (res) {
|
||||
asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
c2s_asprintf(errmsg, "--%s %s: Illegal argument",
|
||||
arg_cl->hdr.longopts,
|
||||
arg->regex);
|
||||
return 0;
|
||||
@@ -1990,7 +2152,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);
|
||||
if (!pcre) {
|
||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
|
||||
arg->regex, error, err_str, error_offset);
|
||||
return 0;
|
||||
}
|
||||
@@ -2000,7 +2162,7 @@ static int regcompmatch_pcre2( regmatch_t* pmatch,
|
||||
0, 0, matches, NULL);
|
||||
if (res < 0) {
|
||||
pcre2_get_error_message(res, err_str, sizeof(err_str));
|
||||
asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
|
||||
arg_cl->sval[arg_index], arg->regex, res, err_str);
|
||||
return 0;
|
||||
}
|
||||
@@ -2089,13 +2251,13 @@ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg)
|
||||
/* Read config file */
|
||||
if (config_read_file(c, filename) == CONFIG_FALSE) {
|
||||
if (config_error_line(c) != 0) {
|
||||
asprintf(errmsg, "%s:%d:%s",
|
||||
c2s_asprintf(errmsg, "%s:%d:%s",
|
||||
filename,
|
||||
config_error_line(c),
|
||||
config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -2106,23 +2268,23 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
{
|
||||
switch(config_setting_type(s)) {
|
||||
case CONFIG_TYPE_INT:
|
||||
asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_BOOL:
|
||||
asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_INT64:
|
||||
asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_FLOAT:
|
||||
asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
|
||||
break;
|
||||
|
||||
case CONFIG_TYPE_STRING:
|
||||
asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
|
||||
break;
|
||||
|
||||
default: /* This means a bug */
|
||||
@@ -2150,9 +2312,9 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
if(config_setting_is_list(parent) ||
|
||||
config_setting_is_array(parent)) {
|
||||
asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
|
||||
} else {
|
||||
asprintf(&subpath, "%s/%s", path, name);
|
||||
c2s_asprintf(&subpath, "%s/%s", path, name);
|
||||
}
|
||||
|
||||
if (config_setting_is_scalar(child)) {
|
||||
@@ -2160,12 +2322,12 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
|
||||
|
||||
/* Add value to the output string */
|
||||
if (*strp) {
|
||||
asprintf(&old, "%s", *strp);
|
||||
c2s_asprintf(&old, "%s", *strp);
|
||||
free(*strp);
|
||||
} else {
|
||||
asprintf(&old, "%s", "");
|
||||
c2s_asprintf(&old, "%s", "");
|
||||
}
|
||||
asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
|
||||
free(value);
|
||||
free(old);
|
||||
|
||||
@@ -2193,6 +2355,7 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
#ifdef LIBCONFIG
|
||||
sslhcfg_conffile = arg_filen("F", "config", "<file>", 0, 1, "Specify configuration file"),
|
||||
#endif
|
||||
sslhcfg_verbose = arg_intn("v", "verbose", "<n>", 0, 1, "Override all verbosness options"),
|
||||
sslhcfg_verbose_config = arg_intn(NULL, "verbose-config", "<n>", 0, 1, "Print configuration at startup"),
|
||||
sslhcfg_verbose_config_error = arg_intn(NULL, "verbose-config-error", "<n>", 0, 1, "Print configuration errors"),
|
||||
sslhcfg_verbose_connections = arg_intn(NULL, "verbose-connections", "<n>", 0, 1, "Trace established incoming address to forward address"),
|
||||
@@ -2216,6 +2379,8 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
|
||||
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
|
||||
sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
|
||||
sslhcfg_ech_conf_sink = arg_strn(NULL, "ech-conf-sink", "<str>", 0, 1, "File where to write ECH configurations"),
|
||||
sslhcfg_ech_public_name = arg_strn(NULL, "ech-public-name", "<str>", 0, 1, "Public name for ECH client-facing proxy (usually, sslh's address)"),
|
||||
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
|
||||
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
|
||||
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
|
||||
@@ -2236,6 +2401,14 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
|
||||
};
|
||||
|
||||
/* Set up failure handler in case asprintf() runs out of
|
||||
* memory */
|
||||
;
|
||||
if (setjmp(c2s_asprintf_fail)) {
|
||||
fprintf(stderr, "asprintf: probably out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse command line */
|
||||
nerrors = arg_parse(argc, argv, argtable);
|
||||
if (nerrors) {
|
||||
@@ -2305,6 +2478,9 @@ static void sslhcfg_protocols_fprint(
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_unix: %d", sslhcfg_protocols->is_unix);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_udp: %d", sslhcfg_protocols->is_udp);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
@@ -2329,6 +2505,11 @@ static void sslhcfg_protocols_fprint(
|
||||
fprintf(out, "keepalive: %d", sslhcfg_protocols->keepalive);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "max_connections: %d", sslhcfg_protocols->max_connections);
|
||||
if (! sslhcfg_protocols->max_connections_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "sni_hostnames [%zu]:\n", sslhcfg_protocols->sni_hostnames_len);
|
||||
for (i = 0; i < sslhcfg_protocols->sni_hostnames_len; i++) {
|
||||
indent(out, depth+1);
|
||||
@@ -2351,6 +2532,11 @@ static void sslhcfg_protocols_fprint(
|
||||
if (! sslhcfg_protocols->minlength_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "proxyprotocol: %d", sslhcfg_protocols->proxyprotocol);
|
||||
if (! sslhcfg_protocols->proxyprotocol_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
|
||||
static void sslhcfg_listen_fprint(
|
||||
@@ -2366,11 +2552,22 @@ static void sslhcfg_listen_fprint(
|
||||
fprintf(out, "port: %s", sslhcfg_listen->port);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "max_connections: %d", sslhcfg_listen->max_connections);
|
||||
if (! sslhcfg_listen->max_connections_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_udp: %d", sslhcfg_listen->is_udp);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "is_unix: %d", sslhcfg_listen->is_unix);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "keepalive: %d", sslhcfg_listen->keepalive);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "proxyprotocol: %d", sslhcfg_listen->proxyprotocol);
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
|
||||
void sslhcfg_fprint(
|
||||
@@ -2379,6 +2576,9 @@ void sslhcfg_fprint(
|
||||
int depth)
|
||||
{
|
||||
int i;
|
||||
indent(out, depth);
|
||||
fprintf(out, "verbose: %d", sslhcfg->verbose);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "verbose_config: %d", sslhcfg->verbose_config);
|
||||
fprintf(out, "\n");
|
||||
@@ -2457,6 +2657,16 @@ void sslhcfg_fprint(
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "ech_conf_sink: %s", sslhcfg->ech_conf_sink);
|
||||
if (! sslhcfg->ech_conf_sink_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "ech_public_name: %s", sslhcfg->ech_public_name);
|
||||
if (! sslhcfg->ech_public_name_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
|
||||
+18
-2
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
* on Sat Jul 18 17:34:37 2026.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -38,13 +38,18 @@
|
||||
#include "probe.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int max_connections_is_present;
|
||||
int max_connections;
|
||||
int is_udp;
|
||||
int is_unix;
|
||||
int keepalive;
|
||||
int proxyprotocol;
|
||||
};
|
||||
|
||||
struct sslhcfg_protocols_item {
|
||||
@@ -53,6 +58,7 @@ struct sslhcfg_protocols_item {
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
int is_unix;
|
||||
int is_udp;
|
||||
int udp_timeout;
|
||||
int fork;
|
||||
@@ -61,6 +67,8 @@ struct sslhcfg_protocols_item {
|
||||
int resolve_on_forward;
|
||||
int log_level;
|
||||
int keepalive;
|
||||
int max_connections_is_present;
|
||||
int max_connections;
|
||||
size_t sni_hostnames_len;
|
||||
char** sni_hostnames;
|
||||
size_t alpn_protocols_len;
|
||||
@@ -69,13 +77,17 @@ struct sslhcfg_protocols_item {
|
||||
char** regex_patterns;
|
||||
int minlength_is_present;
|
||||
int minlength;
|
||||
int proxyprotocol_is_present;
|
||||
int proxyprotocol;
|
||||
T_PROBE* probe;
|
||||
struct addrinfo* saddr;
|
||||
void* data;
|
||||
unsigned int num_connections;
|
||||
dl_list timeouts;
|
||||
};
|
||||
|
||||
struct sslhcfg_item {
|
||||
int verbose;
|
||||
int verbose_config;
|
||||
int verbose_config_error;
|
||||
int verbose_connections;
|
||||
@@ -103,6 +115,10 @@ struct sslhcfg_item {
|
||||
char* syslog_facility;
|
||||
int logfile_is_present;
|
||||
char* logfile;
|
||||
int ech_conf_sink_is_present;
|
||||
char* ech_conf_sink;
|
||||
int ech_public_name_is_present;
|
||||
char* ech_public_name;
|
||||
char* on_timeout;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
|
||||
@@ -45,6 +45,28 @@ static void cnx_read_cb(EV_P_ ev_io *w, int revents);
|
||||
static void cnx_write_cb(EV_P_ ev_io *w, int wevents);
|
||||
static void cnx_accept_cb(EV_P_ ev_io *w, int revents);
|
||||
|
||||
/*******************************************************************************/
|
||||
/* Helper functions */
|
||||
|
||||
/* Enable all accept() watchers */
|
||||
static void start_accept_watchers(watchers* w, int num_addr_listen)
|
||||
{
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
ev_io* io = gap_get(w->ev_ior, i);
|
||||
ev_io_start(EV_A_ io);
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable all accept() watchers */
|
||||
static void stop_accept_watchers(watchers* w, int num_addr_listen)
|
||||
{
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
ev_io* io = gap_get(w->ev_ior, i);
|
||||
ev_io_stop(EV_A_ io);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
|
||||
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
int num_addr_listen)
|
||||
@@ -60,11 +82,11 @@ static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
ev_io* io = malloc(sizeof(*io));
|
||||
|
||||
ev_io_init(io, &cnx_accept_cb, listen_sockets[i].socketfd, EV_READ);
|
||||
ev_io_start(EV_A_ io);
|
||||
gap_set((*w)->ev_ior, i, io);
|
||||
gap_set((*w)->fd2ls, listen_sockets[i].socketfd, &listen_sockets[i]);
|
||||
set_nonblock(listen_sockets[i].socketfd);
|
||||
}
|
||||
start_accept_watchers(*w, num_addr_listen);
|
||||
}
|
||||
|
||||
void watchers_add_read(watchers* w, int fd)
|
||||
@@ -122,22 +144,76 @@ static void cnx_write_cb(EV_P_ ev_io *w, int wevents)
|
||||
cnx_write_process(info, w->fd);
|
||||
}
|
||||
|
||||
/* Timer expired: destroy timer, restart the accept watchers */
|
||||
static void restart_accept_cb( EV_P_ ev_timer *timer, int revents)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
|
||||
ev_timer_stop(EV_A_ timer);
|
||||
free(timer);
|
||||
|
||||
start_accept_watchers(info->watchers, info->num_addr_listen);
|
||||
}
|
||||
|
||||
/* Stop the accept() io watchers, start a timer
|
||||
*/
|
||||
static void temporary_stop_accept_watchers(ev_io* io)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
|
||||
ev_timer* timer = malloc(sizeof(*timer));
|
||||
stop_accept_watchers(info->watchers, info->num_addr_listen);
|
||||
ev_timer_init(timer, &restart_accept_cb, 1, 0);
|
||||
ev_timer_start(EV_A_ 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").
|
||||
* When that happens, the strategy is: Disable all listening sockets, start a 1
|
||||
* second timer, enable all listening sockets. This is terrible, but apparently
|
||||
* the best we can do */
|
||||
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
|
||||
if (!cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd))) {
|
||||
switch (errno) {
|
||||
case EMFILE:
|
||||
case ENFILE: /* Not sure ENFILE should be included here */
|
||||
/* cancel accepting for a while */
|
||||
temporary_stop_accept_watchers(w);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sigchld_cb(EV_P_ ev_child *w, int revents)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
ev_child_stop(EV_A_ w);
|
||||
|
||||
decrease_forked_connection(info, w->pid);
|
||||
free(w);
|
||||
}
|
||||
|
||||
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid)
|
||||
{
|
||||
ev_child* cw = malloc(sizeof(*cw));
|
||||
|
||||
ev_child_init(cw, sigchld_cb, pid, 0);
|
||||
ev_child_start(EV_DEFAULT_ cw);
|
||||
}
|
||||
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
struct loop_info ev_info = {0};
|
||||
struct loop_info ev_info;
|
||||
loop = EV_DEFAULT;
|
||||
|
||||
ev_info.collection = collection_init(0);
|
||||
ev_info.probing_list = gap_init(0);
|
||||
udp_init(&ev_info);
|
||||
tcp_init();
|
||||
loop_init(&ev_info, num_addr_listen);
|
||||
|
||||
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
|
||||
ev_set_userdata(EV_A_ &ev_info);
|
||||
|
||||
+56
-16
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
sslh-fork: forking server
|
||||
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
# Copyright (C) 2007-2025 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "tcp-probe.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef LIBBSD
|
||||
#if HAVE_LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -74,7 +74,6 @@ void start_shoveler(int in_socket)
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
int res = PROBE_AGAIN;
|
||||
int out_socket;
|
||||
struct connection cnx;
|
||||
struct connection_desc desc;
|
||||
|
||||
@@ -110,13 +109,11 @@ void start_shoveler(int in_socket)
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(&cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
connect_addr(&cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(cnx.q[1].fd, "connect");
|
||||
|
||||
set_capabilities(0);
|
||||
|
||||
cnx.q[1].fd = out_socket;
|
||||
|
||||
get_connection_desc(&desc, &cnx);
|
||||
log_connection(&desc, &cnx);
|
||||
set_proctitle_shovel(&desc, &cnx);
|
||||
@@ -126,7 +123,7 @@ void start_shoveler(int in_socket)
|
||||
shovel(&cnx);
|
||||
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
close(cnx.q[1].fd);
|
||||
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
@@ -147,7 +144,7 @@ void stop_listeners(int sig)
|
||||
|
||||
void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
#if HAVE_LIBBSD
|
||||
int res;
|
||||
struct addrinfo addr;
|
||||
struct sockaddr_storage ss;
|
||||
@@ -163,6 +160,35 @@ void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void mask_sigchld(void)
|
||||
{
|
||||
struct sigaction action;
|
||||
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = NULL;
|
||||
action.sa_flags = SA_NOCLDWAIT;
|
||||
int res = sigaction(SIGCHLD, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
}
|
||||
|
||||
|
||||
extern volatile sig_atomic_t received_sigchld;
|
||||
/* EINTR, so we probably received a signal; check if it's SIGCHLD */
|
||||
static void process_signals(struct listen_endpoint* endpoint)
|
||||
{
|
||||
int chld;
|
||||
if (received_sigchld) {
|
||||
received_sigchld = 0;
|
||||
do {
|
||||
chld = waitpid(-1, NULL, WNOHANG);
|
||||
CHECK_RES_RETURN(chld, "waitpid", );
|
||||
if (chld) {
|
||||
endpoint->num_connections--;
|
||||
print_message(msg_fd, "child died, %d concurrent connections remaining\n", endpoint->num_connections);
|
||||
}
|
||||
} while (chld);
|
||||
}
|
||||
}
|
||||
|
||||
/* At least MacOS does not know these two options, so define them to something
|
||||
* equivalent for our use case */
|
||||
@@ -184,11 +210,10 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", /*void*/ );
|
||||
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));
|
||||
switch(in_socket) {
|
||||
switch(errno) {
|
||||
case ENETDOWN: /* accept(2) cites all these errnos as "you should retry" */
|
||||
case EPROTO:
|
||||
case ENOPROTOOPT:
|
||||
@@ -200,11 +225,24 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
||||
case ECONNABORTED:
|
||||
continue;
|
||||
|
||||
case EINTR:
|
||||
process_signals(endpoint);
|
||||
continue;
|
||||
|
||||
default: /* Otherwise, it's something wrong in our parameters, we fail */
|
||||
return;
|
||||
}
|
||||
}
|
||||
print_message(msg_fd, "accepted fd %d\n", in_socket);
|
||||
endpoint->num_connections++;
|
||||
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, endpoint->num_connections);
|
||||
if ((endpoint->endpoint_cfg->max_connections_is_present)
|
||||
&& (endpoint->num_connections > endpoint->endpoint_cfg->max_connections))
|
||||
{
|
||||
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
|
||||
endpoint->num_connections--;
|
||||
close(in_socket);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(fork()) {
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
@@ -247,15 +285,17 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
case 0:
|
||||
set_listen_procname(&listen_sockets[i]);
|
||||
if (listen_sockets[i].type == SOCK_DGRAM)
|
||||
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
|
||||
print_message(msg_config_error, "UDP not supported in sslh-fork\n");
|
||||
else
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
/* We're in the parent, we don't need to do anything */
|
||||
/* We're in the parent, which does nothing but wait to be killed by
|
||||
* SIGTERM, which it will send to its children */
|
||||
default:
|
||||
mask_sigchld();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -272,7 +312,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/* The actual main is in common.c: it's the same for both version of
|
||||
/* The actual main() is in sslh_main.c: it's the same for all versions of
|
||||
* the server
|
||||
*/
|
||||
|
||||
+89
-21
@@ -30,19 +30,24 @@
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "tcp-probe.h"
|
||||
|
||||
#if HAVE_LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBCAP
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
|
||||
static void printcaps(void) {
|
||||
#ifdef LIBCAP
|
||||
#if HAVE_LIBCAP
|
||||
cap_t caps;
|
||||
char* desc;
|
||||
ssize_t len;
|
||||
@@ -66,23 +71,32 @@ static void printsettings(void)
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
strcpy(buf, "resolve on forward");
|
||||
if (!p->resolve_on_forward) {
|
||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||
int len = strlen(buf);
|
||||
sprintf(buf+len, " family %d %d",
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family);
|
||||
if (p->is_unix) {
|
||||
sprintf(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);
|
||||
} else {
|
||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||
size_t len = strlen(buf);
|
||||
sprintf(buf+len, " family %d %d",
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family);
|
||||
}
|
||||
}
|
||||
print_message(msg_config,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
|
||||
"%s addr: %s. max_cnx: %d libwrap service: %s log_level: %d [%s] [%s] [%s] pp:%d\n",
|
||||
p->name,
|
||||
buf,
|
||||
p->max_connections,
|
||||
p->service,
|
||||
p->log_level,
|
||||
p->keepalive ? "keepalive" : "",
|
||||
p->fork ? "fork" : "",
|
||||
p->transparent ? "transparent" : ""
|
||||
p->transparent ? "transparent" : "",
|
||||
p->proxyprotocol_is_present ? p->proxyprotocol : -1
|
||||
);
|
||||
}
|
||||
print_message(msg_config,
|
||||
@@ -98,7 +112,8 @@ static void printsettings(void)
|
||||
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
#ifdef ENABLE_REGEX
|
||||
{
|
||||
int num_patterns, i, error;
|
||||
size_t num_patterns, i;
|
||||
int error;
|
||||
pcre2_code** pattern_list;
|
||||
PCRE2_SIZE error_offset;
|
||||
PCRE2_UCHAR8 err_str[120];
|
||||
@@ -120,6 +135,7 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
probe_regex_matches = pcre2_match_data_create(1, NULL);
|
||||
}
|
||||
#else
|
||||
{
|
||||
@@ -127,6 +143,36 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Perform some fixups on configuration after reading it.
|
||||
* if verbose is present, override all other verbose options
|
||||
*/
|
||||
void config_finish(struct sslhcfg_item* cfg)
|
||||
{
|
||||
if (cfg->verbose) {
|
||||
cfg->verbose_config = cfg->verbose;
|
||||
cfg->verbose_config_error = cfg->verbose;
|
||||
cfg->verbose_connections = cfg->verbose;
|
||||
cfg->verbose_connections_try = cfg->verbose;
|
||||
cfg->verbose_connections_error = cfg->verbose;
|
||||
cfg->verbose_fd = cfg->verbose;
|
||||
cfg->verbose_packets = cfg->verbose;
|
||||
cfg->verbose_probe_info = cfg->verbose;
|
||||
cfg->verbose_probe_error = cfg->verbose;
|
||||
cfg->verbose_system_error = cfg->verbose;
|
||||
cfg->verbose_int_error = cfg->verbose;
|
||||
}
|
||||
}
|
||||
|
||||
/* Checks that the UNIX socket specified exists and is accessible
|
||||
* Dies otherwise
|
||||
*/
|
||||
static void check_access_unix_socket(struct sslhcfg_protocols_item* p)
|
||||
{
|
||||
/* TODO */
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* For each protocol in the configuration, resolve address and set up protocol
|
||||
* options if required
|
||||
*/
|
||||
@@ -136,7 +182,9 @@ static void config_protocols()
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
||||
|
||||
if (
|
||||
if (p->is_unix) {
|
||||
check_access_unix_socket(p);
|
||||
} else if (
|
||||
!p->resolve_on_forward &&
|
||||
resolve_split_name(&(p->saddr), p->host, p->port)
|
||||
) {
|
||||
@@ -186,7 +234,7 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
#endif
|
||||
|
||||
for (i = 0; i < cfg->protocols_len; ++i) {
|
||||
if (strcmp(cfg->protocols[i].name, "tls")) {
|
||||
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
|
||||
if (cfg->protocols[i].sni_hostnames_len) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Config option sni_hostnames is only applicable for tls\n",
|
||||
@@ -217,22 +265,38 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
}
|
||||
}
|
||||
|
||||
/* Connect stdin, stdout, stderr to /dev/null. It is better to keep them around
|
||||
* so they do not get re-used by socket descriptors, and accidently used by
|
||||
* some library code.
|
||||
*/
|
||||
void close_std(void)
|
||||
{
|
||||
int newfd;
|
||||
|
||||
if ((newfd = open("/dev/null", O_RDWR))) {
|
||||
dup2 (newfd, STDIN_FILENO);
|
||||
dup2 (newfd, STDOUT_FILENO);
|
||||
dup2 (newfd, STDERR_FILENO);
|
||||
/* close the helper handle, as this is now unnecessary */
|
||||
close(newfd);
|
||||
} else {
|
||||
print_message(msg_system_error, "Error closing standard filehandles for background daemon\n");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[], char* envp[])
|
||||
{
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int res, num_addr_listen;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
#ifdef LIBBSD
|
||||
#if HAVE_LIBBSD
|
||||
setproctitle_init(argc, argv, envp);
|
||||
#endif
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||
if (res) exit(6);
|
||||
config_finish(&cfg);
|
||||
|
||||
if (cfg.version) {
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
@@ -245,6 +309,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
if (cfg.inetd)
|
||||
{
|
||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||
tcp_init();
|
||||
start_shoveler(0);
|
||||
exit(0);
|
||||
}
|
||||
@@ -262,6 +327,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
|
||||
if (!cfg.foreground) {
|
||||
if (fork() > 0) exit(0); /* Detach */
|
||||
close_std();
|
||||
|
||||
/* New session -- become group leader */
|
||||
if (getuid() == 0) {
|
||||
@@ -281,6 +347,8 @@ int main(int argc, char *argv[], char* envp[])
|
||||
/* Open log file for writing */
|
||||
setup_logfile();
|
||||
|
||||
ech_init(listen_sockets, num_addr_listen);
|
||||
|
||||
if (cfg.user || cfg.chroot)
|
||||
drop_privileges(cfg.user, cfg.chroot);
|
||||
setup_landlock();
|
||||
|
||||
+103
-15
@@ -67,7 +67,7 @@ static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
void watchers_add_read(watchers* w, int fd)
|
||||
{
|
||||
FD_SET(fd, &w->fds_r);
|
||||
if (fd > w->max_fd)
|
||||
if (fd + 1 > w->max_fd)
|
||||
w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
@@ -88,9 +88,83 @@ void watchers_del_write(watchers* w, int fd)
|
||||
FD_CLR(fd, &w->fds_w);
|
||||
}
|
||||
|
||||
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid)
|
||||
{
|
||||
/* Nothing to do, watcher is processed in the event loop when select() is
|
||||
* interrupted by the signal */
|
||||
}
|
||||
|
||||
/* /end watchers */
|
||||
|
||||
|
||||
/* Enable all accept() watchers */
|
||||
static void start_accept_watchers(watchers* w, struct listen_endpoint* listen_sockets, int num_addr_listen)
|
||||
{
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
watchers_add_read(w, listen_sockets[i].socketfd);
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable all accept() watchers */
|
||||
static void stop_accept_watchers(watchers* w, struct listen_endpoint* listen_sockets, int num_addr_listen)
|
||||
{
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
watchers_del_read(w, listen_sockets[i].socketfd);
|
||||
}
|
||||
}
|
||||
|
||||
static void temporary_stop_accept_watchers(
|
||||
watchers* w,
|
||||
struct listen_endpoint* listen_sockets,
|
||||
int num_addr_listen)
|
||||
{
|
||||
stop_accept_watchers(w, listen_sockets, num_addr_listen);
|
||||
alarm(2);
|
||||
}
|
||||
|
||||
/* Check if SIGCHLD was received, find which PID and reap appropriately */
|
||||
extern volatile sig_atomic_t received_sigchld;
|
||||
static void sigchld_process(struct loop_info* loop)
|
||||
{
|
||||
int chld;
|
||||
if (received_sigchld) {
|
||||
received_sigchld = 0;
|
||||
do {
|
||||
chld = waitpid(-1, NULL, WNOHANG);
|
||||
if ((chld == -1) && (errno == ECHILD)) return;
|
||||
CHECK_RES_RETURN(chld, "waitpid", );
|
||||
if (chld) {
|
||||
decrease_forked_connection(loop, chld);
|
||||
}
|
||||
} while (chld);
|
||||
}
|
||||
}
|
||||
|
||||
volatile sig_atomic_t received_sigalrm;
|
||||
void sig_sigalrm(int sig)
|
||||
{
|
||||
received_sigalrm = 1;
|
||||
}
|
||||
|
||||
static void setup_sigalrm(void)
|
||||
{
|
||||
int res;
|
||||
struct sigaction action;
|
||||
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = sig_sigalrm;
|
||||
res = sigaction(SIGALRM, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
}
|
||||
|
||||
|
||||
static void sigalrm_process(watchers* w, struct listen_endpoint* listen_sockets, int num_addr_listen)
|
||||
{
|
||||
if (received_sigalrm) {
|
||||
received_sigalrm = 0;
|
||||
start_accept_watchers(w, listen_sockets, num_addr_listen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
|
||||
@@ -124,15 +198,13 @@ static int fd_out_of_range(int fd) {
|
||||
*/
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
struct loop_info fd_info = {0};
|
||||
struct loop_info fd_info;
|
||||
fd_set readfds, writefds; /* working read and write fd sets */
|
||||
struct timeval tv;
|
||||
int i, res;
|
||||
|
||||
fd_info.num_probing = 0;
|
||||
fd_info.probing_list = gap_init(0);
|
||||
udp_init(&fd_info);
|
||||
tcp_init();
|
||||
setup_sigalrm();
|
||||
loop_init(&fd_info, num_addr_listen);
|
||||
|
||||
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
|
||||
@@ -148,21 +220,33 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
|
||||
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
|
||||
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
if ((res == -1) && ((errno != EAGAIN) && (errno != EINTR)))
|
||||
print_message(msg_system_error, "%s:%d:%d:%s\n",
|
||||
__FILE__, __LINE__, errno, strerror(errno)); \
|
||||
|
||||
/* Check main socket for new connections */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
|
||||
struct connection* new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]);
|
||||
|
||||
if (fd_out_of_range(new_cnx->q[0].fd))
|
||||
tidy_connection(new_cnx, &fd_info);
|
||||
|
||||
/* don't also process it as a read socket */
|
||||
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
||||
|
||||
struct connection* new_cnx;
|
||||
while ((new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]))) {
|
||||
if (fd_out_of_range(new_cnx->q[0].fd))
|
||||
tidy_connection(new_cnx, &fd_info);
|
||||
}
|
||||
/* if accept() returns with ENFILE, disable listen for a second */
|
||||
switch (errno) {
|
||||
case EMFILE:
|
||||
case ENFILE: /* Not sure ENFILE should be included here */
|
||||
temporary_stop_accept_watchers(fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +284,10 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
cnx_read_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Process if signals occured */
|
||||
sigchld_process(&fd_info);
|
||||
sigalrm_process(fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +298,7 @@ void start_shoveler(int listen_socket) {
|
||||
}
|
||||
|
||||
|
||||
/* The actual main is in common.c: it's the same for both version of
|
||||
/* The actual main is in sslh-main.c: it's the same for all versions of
|
||||
* the server
|
||||
*/
|
||||
|
||||
|
||||
@@ -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>] [-v] [-i] [-V] [-f] [-n]
|
||||
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [B<-v> I<n>] [-i] [-V] [-f] [-n]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -14,7 +14,7 @@ B<sslh> accepts connections on specified ports, and forwards
|
||||
them further based on tests performed on the first data
|
||||
packet sent by the remote client.
|
||||
|
||||
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
|
||||
Probes for HTTP, TLS, SSH, OpenVPN, tinc, XMPP 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 several services on port 443 (e.g. to
|
||||
@@ -102,8 +102,10 @@ clients wait for the server to send its banner.
|
||||
|
||||
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<iptables> settings.
|
||||
Refer to the README for more information.
|
||||
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>
|
||||
for more information.
|
||||
|
||||
=item B<-p> I<listening address>, B<--listen> I<listening address>
|
||||
|
||||
@@ -123,10 +125,11 @@ Note that you can set B<sslh> to listen on I<ext_ip:443> and
|
||||
B<httpd> to listen on I<localhost:443>: this allows clients
|
||||
inside your network to just connect directly to B<httpd>.
|
||||
|
||||
Also, B<sslh> probes for SSLv3 (or TLSv1) handshake and will
|
||||
Also, B<sslh> probes for TLS handshakes and will
|
||||
reject connections from clients requesting SSLv2. This is
|
||||
compliant with RFC6176 which prohibits the usage of SSLv2. If
|
||||
you wish to accept SSLv2, use B<--anyprot> instead.
|
||||
compliant with RFC6176 which prohibits the usage of SSLv2.
|
||||
If you wish to accept SSLv2, use B<--anyprot> instead.
|
||||
|
||||
|
||||
=item B<--ssh> I<target address>
|
||||
|
||||
@@ -164,9 +167,10 @@ specified on the command line, this should be specified
|
||||
last. If no default is specified, B<sslh> will forward
|
||||
unknown protocols to the first protocol specified.
|
||||
|
||||
=item B<-v>, B<--verbose>
|
||||
=item B<-v>, B<--verbose> I<n>
|
||||
|
||||
Increase verboseness.
|
||||
Override all verboseness. Refer to B<example.cfg> for all
|
||||
verbose sub-options.
|
||||
|
||||
=item B<-n>, B<--numeric>
|
||||
|
||||
@@ -233,8 +237,8 @@ detailed explanation of the variables used by B<sslh>.
|
||||
=head1 SEE ALSO
|
||||
|
||||
The latest version is available from
|
||||
L<http://www.rutschle.net/tech/sslh>, and can be tracked
|
||||
from L<http://freecode.com/projects/sslh>.
|
||||
L<https://github.com/yrutschle/sslh>. There you can find a more
|
||||
detailed and recent documentation.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
|
||||
+14
-1
@@ -18,6 +18,7 @@ includes: (
|
||||
"probe.h",
|
||||
" <sys/types.h>",
|
||||
" <sys/socket.h>",
|
||||
" <wolfssl/ssl.h>",
|
||||
" <netdb.h>"
|
||||
);
|
||||
|
||||
@@ -25,6 +26,9 @@ config: {
|
||||
name : "sslhcfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "verbose"; type: "int" default: 0;
|
||||
short: "v";
|
||||
description: "Override all verbosness options" },
|
||||
{ name: "verbose-config"; type: "int"; default: 0;
|
||||
description: "Print configuration at startup" },
|
||||
{ name: "verbose-config-error"; type: "int"; default: 3;
|
||||
@@ -82,6 +86,8 @@ config: {
|
||||
description: "Facility to syslog to"; },
|
||||
{ name: "logfile"; type: "string"; optional: true;
|
||||
description: "Log messages to a file" },
|
||||
{ name: "ech_conf_sink"; type: "string"; optional: true;
|
||||
description: "File where to write ECH configurations" },
|
||||
|
||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||
description: "Target to connect to when timing out"; },
|
||||
@@ -94,8 +100,11 @@ config: {
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "max_connections"; type: "int"; optional: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "keepalive"; type: "bool"; default: false; }
|
||||
{ name: "is_unix"; type: "bool"; default: false },
|
||||
{ name: "keepalive"; type: "bool"; default: false; },
|
||||
{ name: "proxyprotocol"; type: "bool"; default: false; }
|
||||
)
|
||||
},
|
||||
|
||||
@@ -107,6 +116,7 @@ config: {
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "service"; type: "string"; optional: true; },
|
||||
{ name: "is_unix"; type: "bool"; default: false },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "udp_timeout"; type: "int"; default: 60 },
|
||||
{ name: "fork"; type: "bool"; default: false },
|
||||
@@ -118,6 +128,7 @@ config: {
|
||||
description: "Set to true if server address should be resolved on (every) newly incoming connection (again)" },
|
||||
{ name: "log_level"; type: "int"; default: 1 },
|
||||
{ name: "keepalive"; type: "bool"; default: false },
|
||||
{ name: "max_connections"; type: "int"; optional: true },
|
||||
{ name: "sni_hostnames",
|
||||
type: "array",
|
||||
element_type: "string"
|
||||
@@ -131,11 +142,13 @@ config: {
|
||||
element_type: "string"
|
||||
},
|
||||
{ name: "minlength"; type: "int"; optional: true },
|
||||
{ name: "proxyprotocol"; type: "int"; optional: true },
|
||||
|
||||
# Runtime data
|
||||
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
|
||||
{ name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" },
|
||||
{ name: "data"; type: "runtime"; c_type: "void*" },
|
||||
{ name: "num_connections"; type: "runtime"; c_type: "unsigned int" },
|
||||
{ name: "timeouts"; type: "runtime"; c_type: "dl_list" }
|
||||
)
|
||||
}
|
||||
|
||||
+251
-89
@@ -1,39 +1,72 @@
|
||||
#include <libconfig.h>
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include "common.h"
|
||||
|
||||
|
||||
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||
|
||||
static char* resolve_listen(const char *hostname, const char *port) {
|
||||
/* Need room in the strcat for \0 and :
|
||||
* the format in the socket unit file is hostname:port */
|
||||
char *conn = malloc(strlen(hostname)+strlen(port)+2);
|
||||
CHECK_ALLOC(conn, "malloc");
|
||||
typedef struct FileList FileList;
|
||||
|
||||
struct FileList {
|
||||
char *name;
|
||||
struct FileList *prev;
|
||||
};
|
||||
|
||||
static void free_file_list(FileList *fl) {
|
||||
while (fl != NULL) {
|
||||
FileList *prev = fl->prev;
|
||||
free(fl->name);
|
||||
free(fl);
|
||||
fl = prev;
|
||||
}
|
||||
}
|
||||
|
||||
static FILE *err_log;
|
||||
static bool systemd_invoked = false;
|
||||
static const char
|
||||
*socket_ext = ".socket",
|
||||
*dropin_ext = ".conf",
|
||||
*service_ext = ".service.d/",
|
||||
*fork_unit_file = "/sslh@",
|
||||
*select_unit_file = "/sslh-select@",
|
||||
*fork_unit = "# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Requires=sslh@%s.socket\n"
|
||||
"Conflicts=sslh-select@%s.service\n"
|
||||
"PartOf=sslh@%s.socket\n",
|
||||
*select_unit = "# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Requires=sslh@%s.socket\n"
|
||||
"Conflicts=sslh@%s.service\n"
|
||||
"PartOf=sslh@%s.socket\n";
|
||||
|
||||
static char *resolve_listen(const char *hostname, const char *port) {
|
||||
char *conn = calloc(1, strlen(hostname) + strlen(port) + 2);
|
||||
CHECK_ALLOC(conn, "malloc")
|
||||
strcpy(conn, hostname);
|
||||
strcat(conn, ":");
|
||||
strcat(conn, port);
|
||||
|
||||
return conn;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int get_listen_from_conf(const char *filename, char **listen[]) {
|
||||
config_t config;
|
||||
config_setting_t *setting, *addr;
|
||||
const char *hostname, *port;
|
||||
int len = 0;
|
||||
|
||||
/* look up the listen stanzas in the config file so these
|
||||
* can be used in the socket file generated */
|
||||
config_init(&config);
|
||||
if (config_read_file(&config, filename) == CONFIG_FALSE) {
|
||||
/* we don't care if file is missing, skip it */
|
||||
if (config_error_line(&config) != 0) {
|
||||
fprintf(stderr, "%s:%d:%s\n",
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: %s%d%s\n",
|
||||
filename,
|
||||
config_error_line(&config),
|
||||
config_error_text(&config));
|
||||
@@ -42,118 +75,247 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
|
||||
} else {
|
||||
setting = config_lookup(&config, "listen");
|
||||
if (setting) {
|
||||
int i;
|
||||
len = config_setting_length(setting);
|
||||
*listen = malloc(len * sizeof(**listen));
|
||||
CHECK_ALLOC(*listen, "malloc");
|
||||
for (i = 0; i < len; i++) {
|
||||
CHECK_ALLOC(*listen, "malloc")
|
||||
for (int i = 0; i < len; i++) {
|
||||
addr = config_setting_get_elem(setting, i);
|
||||
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
|
||||
config_setting_lookup_string(addr, "port", &port))) {
|
||||
fprintf(stderr,
|
||||
"line %d:Incomplete specification (hostname and port required)\n",
|
||||
if (!(config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n",
|
||||
config_setting_source_line(addr));
|
||||
return -1;
|
||||
} else {
|
||||
char *resolved_listen = resolve_listen(hostname, port);
|
||||
|
||||
(*listen)[i] = malloc(strlen(resolved_listen));
|
||||
CHECK_ALLOC((*listen)[i], "malloc");
|
||||
strcpy((*listen)[i], resolved_listen);
|
||||
free(resolved_listen);
|
||||
(*listen)[i] = resolve_listen(hostname, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
|
||||
int i;
|
||||
|
||||
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
|
||||
fprintf(socket,
|
||||
"# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Before=sslh.service\n"
|
||||
"Before=sslh@%s.service\n"
|
||||
"SourcePath=%s\n"
|
||||
"PartOf=sslh@%s.service\n"
|
||||
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
|
||||
"[Socket]\n"
|
||||
"FreeBind=true\n",
|
||||
source);
|
||||
|
||||
for (i = 0; i < num_addr; i++) {
|
||||
cfg,
|
||||
source,
|
||||
cfg);
|
||||
for (int i = 0; i < num_addr; i++) {
|
||||
fprintf(socket, "ListenStream=%s\n", listen[i]);
|
||||
}
|
||||
fprintf(socket,
|
||||
"\n[Install]\n"
|
||||
"WantedBy=sockets.target\n");
|
||||
}
|
||||
|
||||
static int write_unit_dropin(const char *runtime_unit_dir, const bool is_fork, const char *cfg) {
|
||||
const char
|
||||
*unit_file = is_fork ? fork_unit_file : select_unit_file,
|
||||
*unit_string = is_fork ? fork_unit : select_unit;
|
||||
FILE *dropin_fd = stdout;
|
||||
if (systemd_invoked) {
|
||||
//Systemd drop-in configuration for the base select service sslh@%I.service
|
||||
const size_t runtime_len = strlen(runtime_unit_dir);
|
||||
size_t len = strlen(unit_file) + strlen(cfg) + strlen(service_ext) + strlen(dropin_ext);
|
||||
char dropin_dir[runtime_len + len + 1];
|
||||
strcpy(dropin_dir, runtime_unit_dir);
|
||||
strcat(dropin_dir, unit_file);
|
||||
strcat(dropin_dir, cfg);
|
||||
strcat(dropin_dir, service_ext);
|
||||
|
||||
if (mkdir(dropin_dir, S_IRWXU | S_IRWXG | S_IROTH)) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not create directory '%s' to generate drop-in configuration: %s\n",
|
||||
dropin_dir,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
len = len + strlen(cfg) + strlen(dropin_ext);
|
||||
char dropin_path[len + 1];
|
||||
strcpy(dropin_path, dropin_dir);
|
||||
strcat(dropin_path, cfg);
|
||||
strcat(dropin_path, dropin_ext);
|
||||
dropin_fd = fopen(dropin_path, "w");
|
||||
if (!dropin_fd) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open '%s' to generate drop-in configuration: %s\n",
|
||||
dropin_path,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
fprintf(dropin_fd, unit_string, cfg, cfg, cfg);
|
||||
if (systemd_invoked) {
|
||||
fclose(dropin_fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gen_sslh_config(char *runtime_unit_dir) {
|
||||
char *sslh_conf;
|
||||
int num_addr;
|
||||
FILE *config;
|
||||
int status = 0;
|
||||
const char *config_dir = "/etc/sslh/";
|
||||
char **listen;
|
||||
FILE *runtime_conf_fd = stdout;
|
||||
const char *unit_file;
|
||||
DIR *d = opendir(config_dir);
|
||||
FileList *fa = NULL;
|
||||
|
||||
/* There are two default locations so check both with first given preference */
|
||||
sslh_conf = "/etc/sslh.cfg";
|
||||
|
||||
config = fopen(sslh_conf, "r");
|
||||
if (config == NULL) {
|
||||
sslh_conf="/etc/sslh/sslh.cfg";
|
||||
config = fopen(sslh_conf, "r");
|
||||
if (config == NULL) {
|
||||
return -1;
|
||||
if (d) {
|
||||
struct dirent *dir;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
|
||||
continue;
|
||||
}
|
||||
FileList *lfa = malloc(sizeof(FileList));
|
||||
CHECK_ALLOC(lfa, "malloc")
|
||||
lfa->name = malloc(strlen(dir->d_name) + 1);
|
||||
CHECK_ALLOC(lfa->name, "malloc")
|
||||
strcpy(lfa->name, dir->d_name);
|
||||
lfa->prev = NULL;
|
||||
if (fa) {
|
||||
lfa->prev = fa;
|
||||
}
|
||||
fa = lfa;
|
||||
}
|
||||
closedir(d);
|
||||
} else {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Configuration directory '/etc/sslh/' does not exist! No units generated.\n");
|
||||
//Config directory /etc/sslh/ does not exist
|
||||
return 0;
|
||||
}
|
||||
|
||||
fclose(config);
|
||||
|
||||
num_addr = get_listen_from_conf(sslh_conf, &listen);
|
||||
if (num_addr < 0)
|
||||
return -1;
|
||||
|
||||
/* If this is run by systemd directly write to the location told to
|
||||
* otherwise write to standard out so that it's trivial to check what
|
||||
* will be written */
|
||||
if (runtime_unit_dir && *runtime_unit_dir) {
|
||||
unit_file = "/sslh.socket";
|
||||
size_t uf_len = strlen(unit_file);
|
||||
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
|
||||
char *runtime_conf = malloc(runtime_len);
|
||||
CHECK_ALLOC(runtime_conf, "malloc");
|
||||
strcpy(runtime_conf, runtime_unit_dir);
|
||||
strcat(runtime_conf, unit_file);
|
||||
runtime_conf_fd = fopen(runtime_conf, "w");
|
||||
free(runtime_conf);
|
||||
if (!fa) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Configuration directory '/etc/sslh/' is empty! No units generated.\n");
|
||||
//No configuration files in /etc/sslh/
|
||||
return 0;
|
||||
}
|
||||
FileList *fa_ref = fa;
|
||||
// size_t num_listen_addresses = 0;
|
||||
// char **listen_addresses = NULL;
|
||||
//Process all config files
|
||||
do {
|
||||
char *end = strstr(fa->name, ".cfg");
|
||||
if (!end) {
|
||||
continue;
|
||||
}
|
||||
//Current sslh config name
|
||||
const size_t end_count = end - fa->name;
|
||||
char config_name[end_count + 1];
|
||||
memcpy(config_name, fa->name, end_count);
|
||||
config_name[end_count] = '\0';
|
||||
|
||||
|
||||
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
|
||||
//Full path to current sslh config
|
||||
char full_path[strlen(config_dir) + strlen(fa->name) + 1];
|
||||
strcpy(full_path, config_dir);
|
||||
strcat(full_path, fa->name);
|
||||
FILE *config = fopen(full_path, "r");
|
||||
if (!config) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open config file '%s': %s\n",
|
||||
full_path,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
} else {
|
||||
fclose(config);
|
||||
int num_addr = get_listen_from_conf(full_path, &listen);
|
||||
if (num_addr <= 0) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: sslh config '%s' contains no valid listen configurations!\n",
|
||||
fa->name);
|
||||
status |= -1;
|
||||
continue;
|
||||
}
|
||||
FILE *socket_fd = stdout;
|
||||
if (systemd_invoked) {
|
||||
//Systemd socket for the current sslh config
|
||||
const size_t len = strlen(runtime_unit_dir) + strlen(fork_unit_file) + strlen(config_name) + strlen(socket_ext);
|
||||
char socket_path[len + 1];
|
||||
strcpy(socket_path, runtime_unit_dir);
|
||||
strcat(socket_path, fork_unit_file);
|
||||
strcat(socket_path, config_name);
|
||||
strcat(socket_path, socket_ext);
|
||||
socket_fd = fopen(socket_path, "w");
|
||||
if (!socket_fd) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open '%s' to generate socket configuration: %s\n",
|
||||
socket_path,
|
||||
strerror(errno));
|
||||
status |= errno;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Write socket unit
|
||||
write_socket_unit(socket_fd, listen, num_addr, config_name, full_path);
|
||||
//Write forking drop-in config
|
||||
status |= write_unit_dropin(runtime_unit_dir, false, config_name);
|
||||
//Write select drop-in config
|
||||
status |= write_unit_dropin(runtime_unit_dir, true, config_name);
|
||||
if (systemd_invoked) {
|
||||
fclose(socket_fd);
|
||||
}
|
||||
// if (listen_addresses) {
|
||||
// //Check for overlapping addresses
|
||||
// for (size_t i = 0; i < num_listen_addresses; i++) {
|
||||
// for (size_t j = 0; j < num_addr; j++) {
|
||||
// if (strcmp(*(listen_addresses + i), *(listen + j)) == 0) {
|
||||
// fprintf(err_log, "systemd-sslh-generator: Overlapping listen addresses across sslh configurations!");
|
||||
// return -1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// char *listen_addresses_copy[num_listen_addresses + num_addr];
|
||||
// if (listen_addresses) {
|
||||
// //Copy previous configurations' listen addresses into temp
|
||||
// memcpy(listen_addresses_copy, listen_addresses, num_listen_addresses);
|
||||
// //Free global list of listen addresses
|
||||
// free(listen_addresses);
|
||||
// }
|
||||
// //Copy listen addresses from current configuration into temp
|
||||
// memcpy(listen_addresses_copy + num_listen_addresses, listen, num_addr);
|
||||
// num_listen_addresses += num_addr;
|
||||
// listen_addresses = malloc(num_listen_addresses * sizeof(char *));
|
||||
// CHECK_ALLOC(listen_addresses, "malloc")
|
||||
// //Append all addresses to global list
|
||||
// memcpy(listen_addresses, listen_addresses_copy, num_listen_addresses);
|
||||
//Free all allocated listen strings
|
||||
for (size_t i = 0; i < num_addr; i++) {
|
||||
free(*(listen + i));
|
||||
}
|
||||
}
|
||||
} while((fa = fa->prev));
|
||||
free_file_list(fa_ref);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
int r = 0;
|
||||
int k;
|
||||
char *runtime_unit_dest = "";
|
||||
|
||||
if (argc > 1 && (argc != 4) ) {
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc == 1 || argc == 4) {
|
||||
systemd_invoked = argc == 4;
|
||||
if (systemd_invoked) {
|
||||
err_log = fopen("/dev/kmsg", "w");
|
||||
if (!err_log) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
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");
|
||||
}
|
||||
return r < 0 ? -1 : 0;
|
||||
} else {
|
||||
printf("This program takes three or no arguments.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argc > 1)
|
||||
runtime_unit_dest = argv[1];
|
||||
|
||||
k = gen_sslh_config(runtime_unit_dest);
|
||||
if (k < 0)
|
||||
r = k;
|
||||
|
||||
return r < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# Test suite for sslh
|
||||
|
||||
# Uses Conf::Libconfig to read sslh config file: install
|
||||
# with:
|
||||
# cpan Conf::Libconfig
|
||||
|
||||
# Usage:
|
||||
# ./run # run all tests
|
||||
# ./run -l # list all tests
|
||||
# ./run 1 3 5 # run specified tests
|
||||
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
use Test::More qw/no_plan/;
|
||||
use Conf::Libconfig 1.0.3;
|
||||
use Getopt::Long;
|
||||
|
||||
my ($coverage, $list_tests);
|
||||
GetOptions(
|
||||
'cover' => \$coverage,
|
||||
'list' => \$list_tests,
|
||||
);
|
||||
|
||||
|
||||
use Data::Dumper;
|
||||
|
||||
################################################################################
|
||||
# Global setup
|
||||
################################################################################
|
||||
my $no_listen = 8083; # Port on which no-one listens
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
|
||||
################################################################################
|
||||
# Helper functions
|
||||
################################################################################
|
||||
sub get_conf {
|
||||
my ($filename) = @_;
|
||||
my $conf = new Conf::Libconfig;
|
||||
$conf->read_file($filename) or die "$filename: $!";
|
||||
return $conf;
|
||||
}
|
||||
|
||||
# We want to keep track of tests to print a report at the
|
||||
# end, so we centralise all calls to Test::More::is here
|
||||
my $cnt = 1; # test counter
|
||||
my @results;
|
||||
sub my_is {
|
||||
my ($a, $b, $desc) = @_;
|
||||
|
||||
my $res = is($a, $b, $desc);
|
||||
push @results, [$cnt++, $desc, $res];
|
||||
}
|
||||
|
||||
sub my_like {
|
||||
my ($a, $b, $desc) = @_;
|
||||
|
||||
my $res = like($a, $b, $desc);
|
||||
push @results, [$cnt++, $desc, $res];
|
||||
}
|
||||
|
||||
sub verbose_exec
|
||||
{
|
||||
my ($cmd) = @_;
|
||||
|
||||
warn "$cmd\n";
|
||||
if (!fork) {
|
||||
exec $cmd;
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a 'host:port' address targetting ssh from the configuration file
|
||||
sub ssh_address {
|
||||
my ($conf) = @_;
|
||||
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->value("protocols")})[0];
|
||||
return $ssh_conf->{host} . ":" . $ssh_conf->{port};
|
||||
}
|
||||
|
||||
|
||||
# Returns the last TLS target from the configuration file
|
||||
sub ssl_address {
|
||||
my ($conf) = @_;
|
||||
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->value ("protocols")})[-1];
|
||||
return $ssl_conf->{host} . ":" . $ssl_conf->{port};
|
||||
}
|
||||
|
||||
# For SNI/ALPN, build a protocol name as such:
|
||||
# tls:sni1,sni2,...;alpn1,alpn2,...
|
||||
# input: a protocol entry from Libconfig
|
||||
sub make_sni_alpn_name {
|
||||
my ($prot) = @_;
|
||||
|
||||
return "tls:" . (join ",", @{$prot->{sni_hostnames} // []})
|
||||
. ";" . (join ",", @{$prot->{alpn_protocols} // [] });
|
||||
}
|
||||
|
||||
# Start an echoserver for each service
|
||||
my ($started_echosrvs);
|
||||
sub start_echosrv
|
||||
{
|
||||
my ($conf) = @_;
|
||||
|
||||
return if defined $started_echosrvs;
|
||||
|
||||
foreach my $s (@{$conf->value("protocols")}) {
|
||||
my $prefix = $s->{name};
|
||||
|
||||
$prefix =~ s/^ssl/tls/;
|
||||
|
||||
if ($s->{sni_hostnames} or $s->{alpn_protocols}) {
|
||||
$prefix = make_sni_alpn_name($s);
|
||||
}
|
||||
|
||||
verbose_exec "../echosrv --listen $s->{host}:$s->{port} --prefix '$prefix: '";
|
||||
}
|
||||
$started_echosrvs = 1;
|
||||
}
|
||||
|
||||
sub stop_echosrv
|
||||
{
|
||||
`killall echosrv` if $started_echosrvs;
|
||||
undef $started_echosrvs;
|
||||
}
|
||||
|
||||
|
||||
# Runs sslh, restart it if configuration has changed
|
||||
my ($sslh_pid, $sslh_conf);
|
||||
sub start_sslh
|
||||
{
|
||||
my ($exe, $conf) = @_;
|
||||
stop_sslh() if (defined $sslh_conf and ($conf ne $sslh_conf));
|
||||
return if defined $sslh_pid;
|
||||
$sslh_conf = $conf;
|
||||
my ($valgrind);
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $cmd = "../$exe -F $conf";
|
||||
print "****CMDLINE [$cmd]\n";
|
||||
#$valgrind = 1;
|
||||
#$cmd = "valgrind --leak-check=full $cmd";
|
||||
verbose_exec $cmd;
|
||||
exit 0;
|
||||
}
|
||||
sleep 1;
|
||||
warn "spawned $sslh_pid\n";
|
||||
sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds
|
||||
}
|
||||
|
||||
#stop sslh
|
||||
sub stop_sslh
|
||||
{
|
||||
if (defined $sslh_pid) {
|
||||
kill TERM => $sslh_pid or warn "kill process: $!\n";
|
||||
`killall sslh-ev sslh-select sslh-fork`;
|
||||
undef $sslh_pid;
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Test methods
|
||||
################################################################################
|
||||
|
||||
# runs a test that ends in sslh exiting early
|
||||
# $cmdline: which command line to run
|
||||
# $test: test object
|
||||
# Returns: sslh exit code
|
||||
sub run_ending_test
|
||||
{
|
||||
my ($exe, $params, $test) = @_;
|
||||
my $conf = get_conf($test->{cfg});
|
||||
my $ssh_address = ssh_address($conf);
|
||||
my $ssl_address = ssl_address($conf);
|
||||
my $pidfile = $conf->lookup_value("pidfile");
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $cmdline = "../$exe $params";
|
||||
warn "eval `$cmdline`\n";
|
||||
$cmdline = eval "\"$cmdline\"";
|
||||
warn "running `$cmdline`\n";
|
||||
exec "$cmdline";
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, $test->{exit}, "$exe: $test->{desc}");
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Runs one test for one probe. Start echosrv's if required.
|
||||
# Connect to port specified in the test, otherwise to the
|
||||
# first port in the sslh configuration.
|
||||
# run sslh, connect, write the test pattern, read the result,
|
||||
# check it connected to the right echosrv, check the data
|
||||
# was transfered ok.
|
||||
sub run_test_probe
|
||||
{
|
||||
my ($exe, $test) = @_;
|
||||
|
||||
my $conf = get_conf($test->{cfg});
|
||||
start_echosrv($conf);
|
||||
start_sslh($exe, $test->{cfg});
|
||||
|
||||
my $expected = $test->{expected};
|
||||
|
||||
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
|
||||
print "test_probe [$expected] $sslh_port\n";
|
||||
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "t: $!\n" unless $cnx;
|
||||
return unless $cnx;
|
||||
|
||||
syswrite $cnx, $test->{data};
|
||||
|
||||
my $data;
|
||||
my $n = sysread $cnx, $data, 1024;
|
||||
$data =~ /^(.*?): /;
|
||||
my $prefix = $1;
|
||||
$data =~ s/$prefix: //g;
|
||||
print "Received $n bytes: protocol $prefix data [$data]\n";
|
||||
close $cnx;
|
||||
|
||||
my_is($prefix, $expected, "$exe:$test->{desc}:probe");
|
||||
my $data_expected = $test->{data_expected} // "^$test->{data}\$";
|
||||
$data_expected =~ s/INPUT_DATA/$test->{data}/;
|
||||
my_like($data, qr/$data_expected/s, "$exe:$test->{desc}:shoveled");
|
||||
}
|
||||
|
||||
|
||||
################################################################################
|
||||
# Test bodies
|
||||
# Functions here each perform one test, and will be called with:
|
||||
# - exe: path the executable to test
|
||||
# - test: test object
|
||||
################################################################################
|
||||
|
||||
sub test_no_host
|
||||
{
|
||||
my ($exe, $test) = @_;
|
||||
run_ending_test(
|
||||
$exe,
|
||||
"-v 3 -f -u $user --listen $no_listen --ssh \$ssh_address --tls \$ssl_address -P \$pidfile",
|
||||
$test,
|
||||
);
|
||||
}
|
||||
|
||||
sub test_wrong_user
|
||||
{
|
||||
my ($exe, $test) = @_;
|
||||
run_ending_test(
|
||||
$exe,
|
||||
"-F $test->{cfg} -u ${user}_doesnt_exist --listen localhost:$no_listen --ssh \$ssh_address --tls \$ssl_address -P \$pidfile",
|
||||
$test,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
# Each test is decribed with an entry containing the
|
||||
# following fields:
|
||||
#
|
||||
# * `run`: which test method to use
|
||||
#
|
||||
# * `expected`: protocol prompt added by the echosrv (should be the protocol name)
|
||||
#
|
||||
# * `data`: what is sent to sslh, as a client
|
||||
#
|
||||
# * `data_expected` is a regex for the expected return (which can be
|
||||
# different when using Proxyprotocol). INPUT_DATA gets
|
||||
# replaced with $_->{data}, i.e. data sent originally,
|
||||
# before matching. Use `data` if not specified.
|
||||
#
|
||||
|
||||
my @tests = (
|
||||
|
||||
{
|
||||
desc => "No hostname in address",
|
||||
run => \&test_no_host,
|
||||
cfg => "test1.cfg",
|
||||
exit => 6,
|
||||
},
|
||||
{
|
||||
desc => "Changing to non-existant username",
|
||||
run => \&test_wrong_user,
|
||||
cfg => "test1.cfg",
|
||||
exit => 2,
|
||||
},
|
||||
|
||||
{
|
||||
desc => "ssh probe",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test1.cfg",
|
||||
data => "SSH-2.0 Tester banner\n", # Sent to echosrv
|
||||
expected => "ssh", # prefix expected from echosrv
|
||||
},
|
||||
|
||||
{
|
||||
desc => "socks5 probe",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test1.cfg",
|
||||
data => "\x05\x04\x01\x02\x03\x04",
|
||||
expected => "socks5", # prefix expected from echosrv
|
||||
},
|
||||
|
||||
# test_pp1.cfg has proxyprotocol on ssh but not socks5.
|
||||
# Check ssh has pp
|
||||
{
|
||||
desc => "Server-side ProxyProtocol active",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp1.cfg",
|
||||
data => "SSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
|
||||
},
|
||||
|
||||
# check socks5 does not
|
||||
{
|
||||
desc => "Server-side ProxyProtocol not active",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp1.cfg",
|
||||
data => "\x05\x04\x01\x02\x03\x04",
|
||||
expected => "socks5",
|
||||
},
|
||||
|
||||
# test_pp2.cfg has proxyprotocol on port 8080
|
||||
{
|
||||
desc => "Client-side ProxyProtocol active",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp2.cfg",
|
||||
data => "PROXY TCP4 127.0.0.1 127.0.0.1 8080 8081\x0D\x0ASSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0ASSH-2.0 hello",
|
||||
},
|
||||
|
||||
# test_pp2.cfg has no proxyprotocol on port 8081, so ssh
|
||||
# should pass untouched
|
||||
{
|
||||
desc => "Client-side ProxyProtocol not active",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp2.cfg",
|
||||
port => 8081,
|
||||
data => "SSH-2.0 hello",
|
||||
expected => "ssh",
|
||||
},
|
||||
|
||||
# test_pp2.cfg has proxyprotocol on port 8080 but
|
||||
# removes it towards "regex_remove_pp" tarbet
|
||||
# TODO: Log the addresses!
|
||||
{
|
||||
desc => "Client-side ProxyProtocol active but removed",
|
||||
run => \&run_test_probe,
|
||||
cfg => "test_pp2.cfg",
|
||||
data => "PROXY TCP4 127.0.0.1 127.0.0.1 8080 8081\x0D\x0Aregex_remove_pp hello",
|
||||
expected => "regex",
|
||||
data_expected => "^regex_remove_pp hello",
|
||||
},
|
||||
|
||||
);
|
||||
|
||||
|
||||
# Number tests
|
||||
my $i = 1;
|
||||
map { $_->{'index'} = $i++ } @tests;
|
||||
|
||||
# Only keep selected tests
|
||||
@tests = @tests[ map { $_ - 1 } @ARGV ] if @ARGV;
|
||||
|
||||
################################################################################
|
||||
#####List all tests
|
||||
format test_list_top =
|
||||
ID | Description
|
||||
----+-------------------------------------------------------------------------
|
||||
.
|
||||
format test_list =
|
||||
@>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
$_->{'index'}, $_->{'desc'};
|
||||
.
|
||||
if ($list_tests) {
|
||||
format_name STDOUT "test_list";
|
||||
format_top_name STDOUT "test_list_top";
|
||||
map { write; } @tests;
|
||||
exit 0;
|
||||
}
|
||||
#####/list
|
||||
|
||||
################################################################################
|
||||
# Run selected tests
|
||||
foreach my $test (@tests) {
|
||||
warn "Running test: $test->{desc}\n";
|
||||
my $binary = 'sslh-ev';
|
||||
my $code = ($test->{run})->($binary, $test);
|
||||
}
|
||||
|
||||
stop_echosrv();
|
||||
stop_sslh();
|
||||
|
||||
done_testing();
|
||||
|
||||
format test_results_top =
|
||||
ID | Description | Status
|
||||
----+-------------------------------------------------------------------+-------
|
||||
.
|
||||
|
||||
format test_results =
|
||||
@>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @>>
|
||||
$_->[0], $_->[1], $_->[2] ? "OK" : "NOK"
|
||||
.
|
||||
|
||||
format_name STDOUT "test_results";
|
||||
format_top_name STDOUT "test_results_top";
|
||||
map { write; } @results;
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
# Configuration file for testing (use both by sslh under
|
||||
# test and the test script `t`)
|
||||
|
||||
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: 0; # 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: "localhost"; port: "8080"; keepalive: true; proxyprotocol: true; },
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; },
|
||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; },
|
||||
{ host: "/tmp/sslh.sock"; is_unix: true; port: ""; }
|
||||
);
|
||||
|
||||
|
||||
# Tester beware: when using fork, the forked process loses
|
||||
# track of buffers of other, concurrent connections. Memory
|
||||
# leak tools thus complain each time a forked process stops.
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
{ name: "tinc"; host: "localhost"; port: "9003"; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "9004"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "9009"; },
|
||||
{ name: "adb"; host: "localhost"; port: "9010"; },
|
||||
{ name: "syslog"; host: "localhost"; port: "9013"; },
|
||||
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
|
||||
udp_timeout: 30;
|
||||
regex_patterns: [ "^foo" ];
|
||||
resolve_on_forward: true;
|
||||
},
|
||||
{ name: "regex"; host: "localhost"; port: "9011";
|
||||
regex_patterns: [ "^foo", "^bar" ];
|
||||
minlength: 4;
|
||||
test_patterns: ( # this is used by the test script, not by sslh
|
||||
{ pattern: "foo"; result: "ssh"; }, # After timeout
|
||||
{ pattern: "fooo"; result: "regex"; },
|
||||
{ pattern: "bar"; result: "ssh"; },
|
||||
{ pattern: "barr"; result: "regex"; },
|
||||
{ pattern: "barrrr"; result: "regex"; }
|
||||
);
|
||||
},
|
||||
{ name: "tls"; host: "localhost"; port: "9021"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni1" ]; },
|
||||
{ name: "tls"; host: "localhost"; port: "9022"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni2", "sni3" ]; },
|
||||
{ name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; },
|
||||
{ name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; },
|
||||
{ name: "tls"; host: "localhost"; port: "9025"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Testing for server-side ProxyProtocol
|
||||
|
||||
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: 0; # 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: "localhost"; port: "8080"; keepalive: true; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Testing for client-side ProxyProtocol
|
||||
|
||||
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: 0; # 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: "localhost"; port: "8080"; keepalive: true; proxyprotocol: true; },
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; proxyprotocol: false; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "regex"; host: "localhost"; port: "9002";
|
||||
regex_patterns: [ "^regex_remove_pp" ]; proxyprotocol: 0; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
on_timeout: "ssh";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
use Test::More qw/no_plan/;
|
||||
use Conf::Libconfig;
|
||||
use Conf::Libconfig 1.0.3;
|
||||
|
||||
my $conf = new Conf::Libconfig;
|
||||
$conf->read_file("test.cfg");
|
||||
@@ -17,7 +17,7 @@ $conf->read_file("test.cfg");
|
||||
|
||||
my $no_listen = 8083; # Port on which no-one listens
|
||||
my $pidfile = $conf->lookup_value("pidfile");
|
||||
my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
|
||||
my $sslh_port = $conf->value("listen")->[0]->{port};
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
|
||||
# Which tests do we run
|
||||
@@ -84,8 +84,9 @@ sub make_sni_alpn_name {
|
||||
sub test_probe {
|
||||
my (%opts) = @_;
|
||||
|
||||
print "test_probe [$opts{expected}] $sslh_port\n";
|
||||
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx;
|
||||
warn "t: $!\n" unless $cnx;
|
||||
return unless $cnx;
|
||||
|
||||
my $pattern = $opts{data};
|
||||
@@ -119,7 +120,7 @@ sub test_probe {
|
||||
sub test_probes {
|
||||
my (%in_opts) = @_;
|
||||
|
||||
my @probes = @{$conf->fetch_array("protocols")};
|
||||
my @probes = @{$conf->value("protocols")};
|
||||
foreach my $p (@probes) {
|
||||
my %protocols = (
|
||||
'ssh' => { data => "SSH-2.0 tester" },
|
||||
@@ -194,7 +195,7 @@ sub test_probes {
|
||||
|
||||
|
||||
# Start an echoserver for each service
|
||||
foreach my $s (@{$conf->fetch_array("protocols")}) {
|
||||
foreach my $s (@{$conf->value("protocols")}) {
|
||||
my $prefix = $s->{name};
|
||||
|
||||
$prefix =~ s/^ssl/tls/; # To remove in 1.21
|
||||
@@ -217,7 +218,7 @@ for my $binary (@binaries) {
|
||||
my ($sslh_pid, $valgrind);
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $cmd = "./$binary -v 4 -f -u $user -F test.cfg";
|
||||
my $cmd = "./$binary -u $user -F test.cfg";
|
||||
#$valgrind = 1;
|
||||
#$cmd = "valgrind --leak-check=full $cmd";
|
||||
verbose_exec $cmd;
|
||||
@@ -339,11 +340,11 @@ if ($RB_CNX_NOSERVER) {
|
||||
}
|
||||
|
||||
|
||||
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0];
|
||||
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->value("protocols")})[0];
|
||||
my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port};
|
||||
|
||||
# Use the last TLS echoserv (no SNI/ALPN)
|
||||
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1];
|
||||
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->value ("protocols")})[-1];
|
||||
my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port};
|
||||
|
||||
|
||||
+87
-29
@@ -25,6 +25,8 @@
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
|
||||
/* Removes cnx from probing list */
|
||||
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||
{
|
||||
@@ -118,16 +120,52 @@ void tcp_read_process(struct loop_info* fd_info,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* *cnx must have its endpoint field filled;
|
||||
* Increment the connection count for the listen endpoint.
|
||||
* Return 1 if connection count is exceeded, 0 otherwise */
|
||||
static int inc_listen_connections(struct listen_endpoint* endpoint)
|
||||
{
|
||||
endpoint->num_connections++;
|
||||
if (endpoint->endpoint_cfg->max_connections_is_present) {
|
||||
int num_cnx = endpoint->num_connections;
|
||||
int max_cnx = endpoint->endpoint_cfg->max_connections;
|
||||
|
||||
print_message(msg_connections, "Endpoint %d +1: %d/%d cnx\n",
|
||||
endpoint->socketfd, num_cnx, max_cnx);
|
||||
if (num_cnx > max_cnx) {
|
||||
print_message(msg_connections_error, "Endpoint %d: too many connections, dropping\n", endpoint->socketfd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dec_listen_connections(struct listen_endpoint* endpoint)
|
||||
{
|
||||
if (endpoint) {
|
||||
endpoint->num_connections--;
|
||||
print_message(msg_connections, "Endpoint %d -1: %d/%d cnx\n",
|
||||
endpoint->socketfd,
|
||||
endpoint->num_connections,
|
||||
endpoint->endpoint_cfg->max_connections);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Accepts a connection from the main socket and assigns it to an empty slot.
|
||||
* If no slots are available, allocate another few. If that fails, drop the
|
||||
* connexion */
|
||||
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info)
|
||||
struct connection* accept_new_connection(struct listen_endpoint* endpoint, struct loop_info* fd_info)
|
||||
{
|
||||
int in_socket, res;
|
||||
int listen_socket = endpoint->socketfd;
|
||||
|
||||
print_message(msg_fd, "accepting from %d\n", listen_socket);
|
||||
|
||||
in_socket = accept(listen_socket, 0, 0);
|
||||
if ((in_socket == -1) && (errno == EAGAIN)) return NULL; /* Do not log if we're just retring next iteration */
|
||||
CHECK_RES_RETURN(in_socket, "accept", NULL);
|
||||
|
||||
res = set_nonblock(in_socket);
|
||||
@@ -141,19 +179,23 @@ struct connection* accept_new_connection(int listen_socket, struct loop_info* fd
|
||||
close(in_socket);
|
||||
return NULL;
|
||||
}
|
||||
cnx->endpoint = endpoint;
|
||||
if (inc_listen_connections(endpoint)) {
|
||||
tidy_connection(cnx, fd_info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
add_probing_cnx(fd_info, cnx);
|
||||
return cnx;
|
||||
}
|
||||
|
||||
|
||||
/* Connect queue 1 of connection to SSL; returns new file descriptor */
|
||||
static int connect_queue(struct connection* cnx,
|
||||
struct loop_info* fd_info)
|
||||
{
|
||||
struct queue *q = &cnx->q[1];
|
||||
|
||||
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||
connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||
if (q->fd != -1) {
|
||||
log_connection(NULL, cnx);
|
||||
flush_deferred(q);
|
||||
@@ -227,7 +269,6 @@ static void shovel_single(struct connection *cnx)
|
||||
static void connect_proxy(struct connection *cnx)
|
||||
{
|
||||
int in_socket;
|
||||
int out_socket;
|
||||
|
||||
/* Minimize the file descriptor value to help select() */
|
||||
in_socket = dup(cnx->q[0].fd);
|
||||
@@ -238,24 +279,47 @@ static void connect_proxy(struct connection *cnx)
|
||||
cnx->q[0].fd = in_socket;
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
|
||||
cnx->q[1].fd = out_socket;
|
||||
/* Connect the backend server socket */
|
||||
connect_addr(cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(cnx->q[1].fd, "connect");
|
||||
|
||||
log_connection(NULL, cnx);
|
||||
|
||||
shovel_single(cnx);
|
||||
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
close(cnx->q[1].fd);
|
||||
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
/* Forks a shoveler process for one protocol */
|
||||
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
||||
pid_t pid;
|
||||
|
||||
if (!has_space_to_fork(fd_info)) {
|
||||
print_message(msg_connections_error, "forking for %s: out of hash space\n", cnx->proto->name);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pid = fork()) {
|
||||
case 0: /* child */
|
||||
/* TODO: close all file descriptors except 2 */
|
||||
/* free(cnx); */
|
||||
connect_proxy(cnx);
|
||||
exit(0);
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
default: /* parent */
|
||||
remember_child_data(fd_info, cnx, pid);
|
||||
watcher_sigchld(fd_info, cnx, pid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process read activity on a socket in probe state
|
||||
* IN/OUT cnx: connection data, updated if connected
|
||||
* IN/OUT info: updated if connected
|
||||
@@ -263,43 +327,37 @@ static void connect_proxy(struct connection *cnx)
|
||||
void probing_read_process(struct connection* cnx,
|
||||
struct loop_info* fd_info)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* If timed out it's SSH, otherwise the client sent
|
||||
* data so probe the protocol */
|
||||
if ((cnx->probe_timeout < time(NULL))) {
|
||||
cnx->proto = timeout_protocol();
|
||||
print_message(msg_fd, "timed out, connect to %s\n", cnx->proto->name);
|
||||
} else {
|
||||
res = probe_client_protocol(cnx);
|
||||
if (res == PROBE_AGAIN)
|
||||
return;
|
||||
if (probe_client_protocol(cnx) == PROBE_AGAIN)
|
||||
return; /* Not enough data, wait for more */
|
||||
}
|
||||
|
||||
remove_probing_cnx(fd_info, cnx);
|
||||
cnx->state = ST_SHOVELING;
|
||||
|
||||
if (inc_proto_connections(cnx->proto)) {
|
||||
tidy_connection(cnx, fd_info);
|
||||
return;
|
||||
}
|
||||
|
||||
/* libwrap check if required for this protocol */
|
||||
if (cnx->proto->service &&
|
||||
check_access_rights(cnx->q[0].fd, cnx->proto->service)) {
|
||||
tidy_connection(cnx, fd_info);
|
||||
res = -1;
|
||||
} else if (cnx->proto->fork) {
|
||||
switch (fork()) {
|
||||
case 0: /* child */
|
||||
/* TODO: close all file descriptors except 2 */
|
||||
/* free(cnx); */
|
||||
connect_proxy(cnx);
|
||||
exit(0);
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
default: /* parent */
|
||||
break;
|
||||
}
|
||||
fork_shoveling_process(fd_info, cnx);
|
||||
/* Free file descriptor (used only in child), but do not reduce connection
|
||||
* counts */
|
||||
cnx->proto = NULL;
|
||||
cnx->endpoint = NULL;
|
||||
tidy_connection(cnx, fd_info);
|
||||
res = -1;
|
||||
} else {
|
||||
res = connect_queue(cnx, fd_info);
|
||||
connect_queue(cnx, fd_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -6,8 +6,9 @@
|
||||
#include "tcp-probe.h"
|
||||
|
||||
void tcp_read_process(struct loop_info* fd_info, int fd);
|
||||
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info);
|
||||
struct connection* accept_new_connection(struct listen_endpoint* endpoint, struct loop_info* fd_info);
|
||||
void probing_read_process(struct connection* cnx, struct loop_info* fd_info);
|
||||
void cnx_write_process(struct loop_info* fd_info, int fd);
|
||||
|
||||
void dec_listen_connections(struct listen_endpoint*);
|
||||
#endif
|
||||
+16
-5
@@ -45,8 +45,19 @@ int probe_client_protocol(struct connection *cnx)
|
||||
|
||||
if (n > 0) {
|
||||
defer_write(&cnx->q[1], buffer, n);
|
||||
return probe_buffer(cnx->q[1].begin_deferred_data,
|
||||
cnx->q[1].deferred_data_size,
|
||||
|
||||
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,
|
||||
cnx->q[1].deferred_data_size);
|
||||
}
|
||||
|
||||
return probe_buffer(cnx->q[1].begin_deferred_data + pp_len,
|
||||
cnx->q[1].deferred_data_size - pp_len,
|
||||
tcp_protocols, tcp_protocols_len,
|
||||
&cnx->proto
|
||||
);
|
||||
@@ -60,13 +71,13 @@ int probe_client_protocol(struct connection *cnx)
|
||||
|
||||
static void tcp_protocol_list_init(void)
|
||||
{
|
||||
tcp_protocols = calloc(cfg.protocols_len, sizeof(tcp_protocols));
|
||||
CHECK_ALLOC(tcp_protocols, "tcp_protocols");
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
|
||||
if (!p->is_udp) {
|
||||
tcp_protocols[tcp_protocols_len] = p;
|
||||
tcp_protocols_len++;
|
||||
tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols));
|
||||
CHECK_ALLOC(tcp_protocols, "realloc");
|
||||
tcp_protocols[tcp_protocols_len-1] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ 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: 0; # file descriptor activity, open/close/whatnot
|
||||
verbose-fd: 1; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 1; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 0; # what's happening during the probe process
|
||||
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
|
||||
@@ -30,9 +30,11 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "localhost"; port: "8080"; keepalive: true; },
|
||||
{ host: "localhost"; port: "8080"; keepalive: true;
|
||||
max_connections: 2; proxyprotocol: true; },
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; },
|
||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
|
||||
# { host: "ip4-localhost"; is_udp: true; port: "8086"; },
|
||||
{ host: "/tmp/sslh.sock"; is_unix: true; port: ""; }
|
||||
);
|
||||
|
||||
|
||||
@@ -42,19 +44,20 @@ listen:
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; max_connections: 4; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "http"; host: "localhost"; port: "9002"; },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
{ name: "tinc"; host: "localhost"; port: "9003"; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "9004"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "9009"; },
|
||||
{ name: "adb"; host: "localhost"; port: "9010"; },
|
||||
{ name: "syslog"; host: "localhost"; port: "9013"; },
|
||||
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
|
||||
udp_timeout: 30;
|
||||
regex_patterns: [ "^foo" ];
|
||||
resolve_on_forward: true;
|
||||
},
|
||||
# { name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
|
||||
# max_connections: 1;
|
||||
# udp_timeout: 30;
|
||||
# regex_patterns: [ "^foo" ];
|
||||
# resolve_on_forward: true;
|
||||
# },
|
||||
{ name: "regex"; host: "localhost"; port: "9011";
|
||||
regex_patterns: [ "^foo", "^bar" ];
|
||||
minlength: 4;
|
||||
@@ -71,6 +74,7 @@ protocols:
|
||||
{ name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; },
|
||||
{ name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; },
|
||||
{ name: "tls"; host: "localhost"; port: "9025"; },
|
||||
# { name: "anyprot"; host: "ip4-localhost"; is_udp: true; port: "9999"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
CFLAGS=-DHASH_TESTING -O2 -Wall
|
||||
OBJ=../gap.o gtest.o
|
||||
|
||||
gtest: $(OBJ)
|
||||
$(CC) -o gtest $(OBJ)
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/* Wee testing program from the gap code, this is very similar to the hash
|
||||
* testing code.
|
||||
* Instead of pointers, we use integers
|
||||
*
|
||||
* script language:
|
||||
* a 5 12 # adds value 12 at index 5
|
||||
* d 512 42 # remove value 42, 512 is the length of the array
|
||||
* h 16 0 # set hard limit to 16 (value is not used)
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
#include "../gap.h"
|
||||
|
||||
static void gtest_next_line(FILE* f, char* action, int* index, void** value)
|
||||
{
|
||||
|
||||
int res = 0;
|
||||
while ((res != 3) && (res != EOF))
|
||||
res = fscanf(f, "%c %d %p\n", action, index, value);
|
||||
if (res == EOF) exit(0);
|
||||
}
|
||||
|
||||
static void gap_dump(gap_array* gap, char* filename)
|
||||
{
|
||||
int i;
|
||||
FILE* out = fopen(filename, "w");
|
||||
|
||||
if (!out) {
|
||||
perror(filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fprintf(out, "<gap len=%d hardlimit=%d>\n", gap->len, gap->hardlimit);
|
||||
for (i = 0; i < gap->len; i++) {
|
||||
void* value = gap_get(gap, i);
|
||||
fprintf(out, "[%d] = %p\n", i, value);
|
||||
}
|
||||
fprintf(out, "</gap>\n");
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
gap_array* gap = gap_init(0);
|
||||
char action;
|
||||
int line = 0, index;
|
||||
void* value;
|
||||
FILE* f;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: gtest <script file> <dump file>\n");
|
||||
exit(1);
|
||||
}
|
||||
char* script_file = argv[1];
|
||||
char* dump_file = argv[2];
|
||||
f = fopen(argv[1], "r");
|
||||
if (!f) {
|
||||
perror(script_file);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
action = ' ';
|
||||
|
||||
line++;
|
||||
gtest_next_line(f, &action, &index, &value);
|
||||
fprintf(stderr, "action %d: %c %d %p\n", line, action, index, value);
|
||||
|
||||
switch (action) {
|
||||
case 'a': /* add */
|
||||
fprintf(stderr, "[%d] = %p\n", index, value);
|
||||
gap_set(gap, index, value);
|
||||
break;
|
||||
|
||||
case 'd': /* del */
|
||||
fprintf(stderr, "removing %p\n", value);
|
||||
int res = gap_remove_ptr(gap, (void*)value, index);
|
||||
fprintf(stderr, "remove: %d\n", res);
|
||||
break;
|
||||
|
||||
case 'h': /* hardlimit */
|
||||
fprintf(stderr, "setting hard limit %d\n", index);
|
||||
gap_set_hardlimit(gap, index);
|
||||
|
||||
case 'g': /* get */
|
||||
fprintf(stderr, "searching\n");
|
||||
value = gap_get(gap, index);
|
||||
fprintf(stderr, "got [%d]= %p\n", index, value);
|
||||
break;
|
||||
}
|
||||
gap_dump(gap, dump_file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
# Test hard limit
|
||||
|
||||
h 15 0 # set hard limit
|
||||
a 5 42
|
||||
a 6 43
|
||||
a 15 44
|
||||
a 16 45
|
||||
a 17 46
|
||||
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
<gap len=512 hardlimit=15>
|
||||
[0] = (nil)
|
||||
[1] = (nil)
|
||||
[2] = (nil)
|
||||
[3] = (nil)
|
||||
[4] = (nil)
|
||||
[5] = 0x42
|
||||
[6] = 0x43
|
||||
[7] = (nil)
|
||||
[8] = (nil)
|
||||
[9] = (nil)
|
||||
[10] = (nil)
|
||||
[11] = (nil)
|
||||
[12] = (nil)
|
||||
[13] = (nil)
|
||||
[14] = (nil)
|
||||
[15] = 0x44
|
||||
[16] = (nil)
|
||||
[17] = (nil)
|
||||
[18] = (nil)
|
||||
[19] = (nil)
|
||||
[20] = (nil)
|
||||
[21] = (nil)
|
||||
[22] = (nil)
|
||||
[23] = (nil)
|
||||
[24] = (nil)
|
||||
[25] = (nil)
|
||||
[26] = (nil)
|
||||
[27] = (nil)
|
||||
[28] = (nil)
|
||||
[29] = (nil)
|
||||
[30] = (nil)
|
||||
[31] = (nil)
|
||||
[32] = (nil)
|
||||
[33] = (nil)
|
||||
[34] = (nil)
|
||||
[35] = (nil)
|
||||
[36] = (nil)
|
||||
[37] = (nil)
|
||||
[38] = (nil)
|
||||
[39] = (nil)
|
||||
[40] = (nil)
|
||||
[41] = (nil)
|
||||
[42] = (nil)
|
||||
[43] = (nil)
|
||||
[44] = (nil)
|
||||
[45] = (nil)
|
||||
[46] = (nil)
|
||||
[47] = (nil)
|
||||
[48] = (nil)
|
||||
[49] = (nil)
|
||||
[50] = (nil)
|
||||
[51] = (nil)
|
||||
[52] = (nil)
|
||||
[53] = (nil)
|
||||
[54] = (nil)
|
||||
[55] = (nil)
|
||||
[56] = (nil)
|
||||
[57] = (nil)
|
||||
[58] = (nil)
|
||||
[59] = (nil)
|
||||
[60] = (nil)
|
||||
[61] = (nil)
|
||||
[62] = (nil)
|
||||
[63] = (nil)
|
||||
[64] = (nil)
|
||||
[65] = (nil)
|
||||
[66] = (nil)
|
||||
[67] = (nil)
|
||||
[68] = (nil)
|
||||
[69] = (nil)
|
||||
[70] = (nil)
|
||||
[71] = (nil)
|
||||
[72] = (nil)
|
||||
[73] = (nil)
|
||||
[74] = (nil)
|
||||
[75] = (nil)
|
||||
[76] = (nil)
|
||||
[77] = (nil)
|
||||
[78] = (nil)
|
||||
[79] = (nil)
|
||||
[80] = (nil)
|
||||
[81] = (nil)
|
||||
[82] = (nil)
|
||||
[83] = (nil)
|
||||
[84] = (nil)
|
||||
[85] = (nil)
|
||||
[86] = (nil)
|
||||
[87] = (nil)
|
||||
[88] = (nil)
|
||||
[89] = (nil)
|
||||
[90] = (nil)
|
||||
[91] = (nil)
|
||||
[92] = (nil)
|
||||
[93] = (nil)
|
||||
[94] = (nil)
|
||||
[95] = (nil)
|
||||
[96] = (nil)
|
||||
[97] = (nil)
|
||||
[98] = (nil)
|
||||
[99] = (nil)
|
||||
[100] = (nil)
|
||||
[101] = (nil)
|
||||
[102] = (nil)
|
||||
[103] = (nil)
|
||||
[104] = (nil)
|
||||
[105] = (nil)
|
||||
[106] = (nil)
|
||||
[107] = (nil)
|
||||
[108] = (nil)
|
||||
[109] = (nil)
|
||||
[110] = (nil)
|
||||
[111] = (nil)
|
||||
[112] = (nil)
|
||||
[113] = (nil)
|
||||
[114] = (nil)
|
||||
[115] = (nil)
|
||||
[116] = (nil)
|
||||
[117] = (nil)
|
||||
[118] = (nil)
|
||||
[119] = (nil)
|
||||
[120] = (nil)
|
||||
[121] = (nil)
|
||||
[122] = (nil)
|
||||
[123] = (nil)
|
||||
[124] = (nil)
|
||||
[125] = (nil)
|
||||
[126] = (nil)
|
||||
[127] = (nil)
|
||||
[128] = (nil)
|
||||
[129] = (nil)
|
||||
[130] = (nil)
|
||||
[131] = (nil)
|
||||
[132] = (nil)
|
||||
[133] = (nil)
|
||||
[134] = (nil)
|
||||
[135] = (nil)
|
||||
[136] = (nil)
|
||||
[137] = (nil)
|
||||
[138] = (nil)
|
||||
[139] = (nil)
|
||||
[140] = (nil)
|
||||
[141] = (nil)
|
||||
[142] = (nil)
|
||||
[143] = (nil)
|
||||
[144] = (nil)
|
||||
[145] = (nil)
|
||||
[146] = (nil)
|
||||
[147] = (nil)
|
||||
[148] = (nil)
|
||||
[149] = (nil)
|
||||
[150] = (nil)
|
||||
[151] = (nil)
|
||||
[152] = (nil)
|
||||
[153] = (nil)
|
||||
[154] = (nil)
|
||||
[155] = (nil)
|
||||
[156] = (nil)
|
||||
[157] = (nil)
|
||||
[158] = (nil)
|
||||
[159] = (nil)
|
||||
[160] = (nil)
|
||||
[161] = (nil)
|
||||
[162] = (nil)
|
||||
[163] = (nil)
|
||||
[164] = (nil)
|
||||
[165] = (nil)
|
||||
[166] = (nil)
|
||||
[167] = (nil)
|
||||
[168] = (nil)
|
||||
[169] = (nil)
|
||||
[170] = (nil)
|
||||
[171] = (nil)
|
||||
[172] = (nil)
|
||||
[173] = (nil)
|
||||
[174] = (nil)
|
||||
[175] = (nil)
|
||||
[176] = (nil)
|
||||
[177] = (nil)
|
||||
[178] = (nil)
|
||||
[179] = (nil)
|
||||
[180] = (nil)
|
||||
[181] = (nil)
|
||||
[182] = (nil)
|
||||
[183] = (nil)
|
||||
[184] = (nil)
|
||||
[185] = (nil)
|
||||
[186] = (nil)
|
||||
[187] = (nil)
|
||||
[188] = (nil)
|
||||
[189] = (nil)
|
||||
[190] = (nil)
|
||||
[191] = (nil)
|
||||
[192] = (nil)
|
||||
[193] = (nil)
|
||||
[194] = (nil)
|
||||
[195] = (nil)
|
||||
[196] = (nil)
|
||||
[197] = (nil)
|
||||
[198] = (nil)
|
||||
[199] = (nil)
|
||||
[200] = (nil)
|
||||
[201] = (nil)
|
||||
[202] = (nil)
|
||||
[203] = (nil)
|
||||
[204] = (nil)
|
||||
[205] = (nil)
|
||||
[206] = (nil)
|
||||
[207] = (nil)
|
||||
[208] = (nil)
|
||||
[209] = (nil)
|
||||
[210] = (nil)
|
||||
[211] = (nil)
|
||||
[212] = (nil)
|
||||
[213] = (nil)
|
||||
[214] = (nil)
|
||||
[215] = (nil)
|
||||
[216] = (nil)
|
||||
[217] = (nil)
|
||||
[218] = (nil)
|
||||
[219] = (nil)
|
||||
[220] = (nil)
|
||||
[221] = (nil)
|
||||
[222] = (nil)
|
||||
[223] = (nil)
|
||||
[224] = (nil)
|
||||
[225] = (nil)
|
||||
[226] = (nil)
|
||||
[227] = (nil)
|
||||
[228] = (nil)
|
||||
[229] = (nil)
|
||||
[230] = (nil)
|
||||
[231] = (nil)
|
||||
[232] = (nil)
|
||||
[233] = (nil)
|
||||
[234] = (nil)
|
||||
[235] = (nil)
|
||||
[236] = (nil)
|
||||
[237] = (nil)
|
||||
[238] = (nil)
|
||||
[239] = (nil)
|
||||
[240] = (nil)
|
||||
[241] = (nil)
|
||||
[242] = (nil)
|
||||
[243] = (nil)
|
||||
[244] = (nil)
|
||||
[245] = (nil)
|
||||
[246] = (nil)
|
||||
[247] = (nil)
|
||||
[248] = (nil)
|
||||
[249] = (nil)
|
||||
[250] = (nil)
|
||||
[251] = (nil)
|
||||
[252] = (nil)
|
||||
[253] = (nil)
|
||||
[254] = (nil)
|
||||
[255] = (nil)
|
||||
[256] = (nil)
|
||||
[257] = (nil)
|
||||
[258] = (nil)
|
||||
[259] = (nil)
|
||||
[260] = (nil)
|
||||
[261] = (nil)
|
||||
[262] = (nil)
|
||||
[263] = (nil)
|
||||
[264] = (nil)
|
||||
[265] = (nil)
|
||||
[266] = (nil)
|
||||
[267] = (nil)
|
||||
[268] = (nil)
|
||||
[269] = (nil)
|
||||
[270] = (nil)
|
||||
[271] = (nil)
|
||||
[272] = (nil)
|
||||
[273] = (nil)
|
||||
[274] = (nil)
|
||||
[275] = (nil)
|
||||
[276] = (nil)
|
||||
[277] = (nil)
|
||||
[278] = (nil)
|
||||
[279] = (nil)
|
||||
[280] = (nil)
|
||||
[281] = (nil)
|
||||
[282] = (nil)
|
||||
[283] = (nil)
|
||||
[284] = (nil)
|
||||
[285] = (nil)
|
||||
[286] = (nil)
|
||||
[287] = (nil)
|
||||
[288] = (nil)
|
||||
[289] = (nil)
|
||||
[290] = (nil)
|
||||
[291] = (nil)
|
||||
[292] = (nil)
|
||||
[293] = (nil)
|
||||
[294] = (nil)
|
||||
[295] = (nil)
|
||||
[296] = (nil)
|
||||
[297] = (nil)
|
||||
[298] = (nil)
|
||||
[299] = (nil)
|
||||
[300] = (nil)
|
||||
[301] = (nil)
|
||||
[302] = (nil)
|
||||
[303] = (nil)
|
||||
[304] = (nil)
|
||||
[305] = (nil)
|
||||
[306] = (nil)
|
||||
[307] = (nil)
|
||||
[308] = (nil)
|
||||
[309] = (nil)
|
||||
[310] = (nil)
|
||||
[311] = (nil)
|
||||
[312] = (nil)
|
||||
[313] = (nil)
|
||||
[314] = (nil)
|
||||
[315] = (nil)
|
||||
[316] = (nil)
|
||||
[317] = (nil)
|
||||
[318] = (nil)
|
||||
[319] = (nil)
|
||||
[320] = (nil)
|
||||
[321] = (nil)
|
||||
[322] = (nil)
|
||||
[323] = (nil)
|
||||
[324] = (nil)
|
||||
[325] = (nil)
|
||||
[326] = (nil)
|
||||
[327] = (nil)
|
||||
[328] = (nil)
|
||||
[329] = (nil)
|
||||
[330] = (nil)
|
||||
[331] = (nil)
|
||||
[332] = (nil)
|
||||
[333] = (nil)
|
||||
[334] = (nil)
|
||||
[335] = (nil)
|
||||
[336] = (nil)
|
||||
[337] = (nil)
|
||||
[338] = (nil)
|
||||
[339] = (nil)
|
||||
[340] = (nil)
|
||||
[341] = (nil)
|
||||
[342] = (nil)
|
||||
[343] = (nil)
|
||||
[344] = (nil)
|
||||
[345] = (nil)
|
||||
[346] = (nil)
|
||||
[347] = (nil)
|
||||
[348] = (nil)
|
||||
[349] = (nil)
|
||||
[350] = (nil)
|
||||
[351] = (nil)
|
||||
[352] = (nil)
|
||||
[353] = (nil)
|
||||
[354] = (nil)
|
||||
[355] = (nil)
|
||||
[356] = (nil)
|
||||
[357] = (nil)
|
||||
[358] = (nil)
|
||||
[359] = (nil)
|
||||
[360] = (nil)
|
||||
[361] = (nil)
|
||||
[362] = (nil)
|
||||
[363] = (nil)
|
||||
[364] = (nil)
|
||||
[365] = (nil)
|
||||
[366] = (nil)
|
||||
[367] = (nil)
|
||||
[368] = (nil)
|
||||
[369] = (nil)
|
||||
[370] = (nil)
|
||||
[371] = (nil)
|
||||
[372] = (nil)
|
||||
[373] = (nil)
|
||||
[374] = (nil)
|
||||
[375] = (nil)
|
||||
[376] = (nil)
|
||||
[377] = (nil)
|
||||
[378] = (nil)
|
||||
[379] = (nil)
|
||||
[380] = (nil)
|
||||
[381] = (nil)
|
||||
[382] = (nil)
|
||||
[383] = (nil)
|
||||
[384] = (nil)
|
||||
[385] = (nil)
|
||||
[386] = (nil)
|
||||
[387] = (nil)
|
||||
[388] = (nil)
|
||||
[389] = (nil)
|
||||
[390] = (nil)
|
||||
[391] = (nil)
|
||||
[392] = (nil)
|
||||
[393] = (nil)
|
||||
[394] = (nil)
|
||||
[395] = (nil)
|
||||
[396] = (nil)
|
||||
[397] = (nil)
|
||||
[398] = (nil)
|
||||
[399] = (nil)
|
||||
[400] = (nil)
|
||||
[401] = (nil)
|
||||
[402] = (nil)
|
||||
[403] = (nil)
|
||||
[404] = (nil)
|
||||
[405] = (nil)
|
||||
[406] = (nil)
|
||||
[407] = (nil)
|
||||
[408] = (nil)
|
||||
[409] = (nil)
|
||||
[410] = (nil)
|
||||
[411] = (nil)
|
||||
[412] = (nil)
|
||||
[413] = (nil)
|
||||
[414] = (nil)
|
||||
[415] = (nil)
|
||||
[416] = (nil)
|
||||
[417] = (nil)
|
||||
[418] = (nil)
|
||||
[419] = (nil)
|
||||
[420] = (nil)
|
||||
[421] = (nil)
|
||||
[422] = (nil)
|
||||
[423] = (nil)
|
||||
[424] = (nil)
|
||||
[425] = (nil)
|
||||
[426] = (nil)
|
||||
[427] = (nil)
|
||||
[428] = (nil)
|
||||
[429] = (nil)
|
||||
[430] = (nil)
|
||||
[431] = (nil)
|
||||
[432] = (nil)
|
||||
[433] = (nil)
|
||||
[434] = (nil)
|
||||
[435] = (nil)
|
||||
[436] = (nil)
|
||||
[437] = (nil)
|
||||
[438] = (nil)
|
||||
[439] = (nil)
|
||||
[440] = (nil)
|
||||
[441] = (nil)
|
||||
[442] = (nil)
|
||||
[443] = (nil)
|
||||
[444] = (nil)
|
||||
[445] = (nil)
|
||||
[446] = (nil)
|
||||
[447] = (nil)
|
||||
[448] = (nil)
|
||||
[449] = (nil)
|
||||
[450] = (nil)
|
||||
[451] = (nil)
|
||||
[452] = (nil)
|
||||
[453] = (nil)
|
||||
[454] = (nil)
|
||||
[455] = (nil)
|
||||
[456] = (nil)
|
||||
[457] = (nil)
|
||||
[458] = (nil)
|
||||
[459] = (nil)
|
||||
[460] = (nil)
|
||||
[461] = (nil)
|
||||
[462] = (nil)
|
||||
[463] = (nil)
|
||||
[464] = (nil)
|
||||
[465] = (nil)
|
||||
[466] = (nil)
|
||||
[467] = (nil)
|
||||
[468] = (nil)
|
||||
[469] = (nil)
|
||||
[470] = (nil)
|
||||
[471] = (nil)
|
||||
[472] = (nil)
|
||||
[473] = (nil)
|
||||
[474] = (nil)
|
||||
[475] = (nil)
|
||||
[476] = (nil)
|
||||
[477] = (nil)
|
||||
[478] = (nil)
|
||||
[479] = (nil)
|
||||
[480] = (nil)
|
||||
[481] = (nil)
|
||||
[482] = (nil)
|
||||
[483] = (nil)
|
||||
[484] = (nil)
|
||||
[485] = (nil)
|
||||
[486] = (nil)
|
||||
[487] = (nil)
|
||||
[488] = (nil)
|
||||
[489] = (nil)
|
||||
[490] = (nil)
|
||||
[491] = (nil)
|
||||
[492] = (nil)
|
||||
[493] = (nil)
|
||||
[494] = (nil)
|
||||
[495] = (nil)
|
||||
[496] = (nil)
|
||||
[497] = (nil)
|
||||
[498] = (nil)
|
||||
[499] = (nil)
|
||||
[500] = (nil)
|
||||
[501] = (nil)
|
||||
[502] = (nil)
|
||||
[503] = (nil)
|
||||
[504] = (nil)
|
||||
[505] = (nil)
|
||||
[506] = (nil)
|
||||
[507] = (nil)
|
||||
[508] = (nil)
|
||||
[509] = (nil)
|
||||
[510] = (nil)
|
||||
[511] = (nil)
|
||||
</gap>
|
||||
@@ -0,0 +1,5 @@
|
||||
# Insertions after the first page, to extend the gap
|
||||
|
||||
a 5 42
|
||||
a 1024 43
|
||||
a 1028 44
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,6 @@
|
||||
# Simple insertions in the first page
|
||||
|
||||
a 5 42
|
||||
a 10 43
|
||||
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
<gap len=512 hardlimit=0>
|
||||
[0] = (nil)
|
||||
[1] = (nil)
|
||||
[2] = (nil)
|
||||
[3] = (nil)
|
||||
[4] = (nil)
|
||||
[5] = 0x42
|
||||
[6] = (nil)
|
||||
[7] = (nil)
|
||||
[8] = (nil)
|
||||
[9] = (nil)
|
||||
[10] = 0x43
|
||||
[11] = (nil)
|
||||
[12] = (nil)
|
||||
[13] = (nil)
|
||||
[14] = (nil)
|
||||
[15] = (nil)
|
||||
[16] = (nil)
|
||||
[17] = (nil)
|
||||
[18] = (nil)
|
||||
[19] = (nil)
|
||||
[20] = (nil)
|
||||
[21] = (nil)
|
||||
[22] = (nil)
|
||||
[23] = (nil)
|
||||
[24] = (nil)
|
||||
[25] = (nil)
|
||||
[26] = (nil)
|
||||
[27] = (nil)
|
||||
[28] = (nil)
|
||||
[29] = (nil)
|
||||
[30] = (nil)
|
||||
[31] = (nil)
|
||||
[32] = (nil)
|
||||
[33] = (nil)
|
||||
[34] = (nil)
|
||||
[35] = (nil)
|
||||
[36] = (nil)
|
||||
[37] = (nil)
|
||||
[38] = (nil)
|
||||
[39] = (nil)
|
||||
[40] = (nil)
|
||||
[41] = (nil)
|
||||
[42] = (nil)
|
||||
[43] = (nil)
|
||||
[44] = (nil)
|
||||
[45] = (nil)
|
||||
[46] = (nil)
|
||||
[47] = (nil)
|
||||
[48] = (nil)
|
||||
[49] = (nil)
|
||||
[50] = (nil)
|
||||
[51] = (nil)
|
||||
[52] = (nil)
|
||||
[53] = (nil)
|
||||
[54] = (nil)
|
||||
[55] = (nil)
|
||||
[56] = (nil)
|
||||
[57] = (nil)
|
||||
[58] = (nil)
|
||||
[59] = (nil)
|
||||
[60] = (nil)
|
||||
[61] = (nil)
|
||||
[62] = (nil)
|
||||
[63] = (nil)
|
||||
[64] = (nil)
|
||||
[65] = (nil)
|
||||
[66] = (nil)
|
||||
[67] = (nil)
|
||||
[68] = (nil)
|
||||
[69] = (nil)
|
||||
[70] = (nil)
|
||||
[71] = (nil)
|
||||
[72] = (nil)
|
||||
[73] = (nil)
|
||||
[74] = (nil)
|
||||
[75] = (nil)
|
||||
[76] = (nil)
|
||||
[77] = (nil)
|
||||
[78] = (nil)
|
||||
[79] = (nil)
|
||||
[80] = (nil)
|
||||
[81] = (nil)
|
||||
[82] = (nil)
|
||||
[83] = (nil)
|
||||
[84] = (nil)
|
||||
[85] = (nil)
|
||||
[86] = (nil)
|
||||
[87] = (nil)
|
||||
[88] = (nil)
|
||||
[89] = (nil)
|
||||
[90] = (nil)
|
||||
[91] = (nil)
|
||||
[92] = (nil)
|
||||
[93] = (nil)
|
||||
[94] = (nil)
|
||||
[95] = (nil)
|
||||
[96] = (nil)
|
||||
[97] = (nil)
|
||||
[98] = (nil)
|
||||
[99] = (nil)
|
||||
[100] = (nil)
|
||||
[101] = (nil)
|
||||
[102] = (nil)
|
||||
[103] = (nil)
|
||||
[104] = (nil)
|
||||
[105] = (nil)
|
||||
[106] = (nil)
|
||||
[107] = (nil)
|
||||
[108] = (nil)
|
||||
[109] = (nil)
|
||||
[110] = (nil)
|
||||
[111] = (nil)
|
||||
[112] = (nil)
|
||||
[113] = (nil)
|
||||
[114] = (nil)
|
||||
[115] = (nil)
|
||||
[116] = (nil)
|
||||
[117] = (nil)
|
||||
[118] = (nil)
|
||||
[119] = (nil)
|
||||
[120] = (nil)
|
||||
[121] = (nil)
|
||||
[122] = (nil)
|
||||
[123] = (nil)
|
||||
[124] = (nil)
|
||||
[125] = (nil)
|
||||
[126] = (nil)
|
||||
[127] = (nil)
|
||||
[128] = (nil)
|
||||
[129] = (nil)
|
||||
[130] = (nil)
|
||||
[131] = (nil)
|
||||
[132] = (nil)
|
||||
[133] = (nil)
|
||||
[134] = (nil)
|
||||
[135] = (nil)
|
||||
[136] = (nil)
|
||||
[137] = (nil)
|
||||
[138] = (nil)
|
||||
[139] = (nil)
|
||||
[140] = (nil)
|
||||
[141] = (nil)
|
||||
[142] = (nil)
|
||||
[143] = (nil)
|
||||
[144] = (nil)
|
||||
[145] = (nil)
|
||||
[146] = (nil)
|
||||
[147] = (nil)
|
||||
[148] = (nil)
|
||||
[149] = (nil)
|
||||
[150] = (nil)
|
||||
[151] = (nil)
|
||||
[152] = (nil)
|
||||
[153] = (nil)
|
||||
[154] = (nil)
|
||||
[155] = (nil)
|
||||
[156] = (nil)
|
||||
[157] = (nil)
|
||||
[158] = (nil)
|
||||
[159] = (nil)
|
||||
[160] = (nil)
|
||||
[161] = (nil)
|
||||
[162] = (nil)
|
||||
[163] = (nil)
|
||||
[164] = (nil)
|
||||
[165] = (nil)
|
||||
[166] = (nil)
|
||||
[167] = (nil)
|
||||
[168] = (nil)
|
||||
[169] = (nil)
|
||||
[170] = (nil)
|
||||
[171] = (nil)
|
||||
[172] = (nil)
|
||||
[173] = (nil)
|
||||
[174] = (nil)
|
||||
[175] = (nil)
|
||||
[176] = (nil)
|
||||
[177] = (nil)
|
||||
[178] = (nil)
|
||||
[179] = (nil)
|
||||
[180] = (nil)
|
||||
[181] = (nil)
|
||||
[182] = (nil)
|
||||
[183] = (nil)
|
||||
[184] = (nil)
|
||||
[185] = (nil)
|
||||
[186] = (nil)
|
||||
[187] = (nil)
|
||||
[188] = (nil)
|
||||
[189] = (nil)
|
||||
[190] = (nil)
|
||||
[191] = (nil)
|
||||
[192] = (nil)
|
||||
[193] = (nil)
|
||||
[194] = (nil)
|
||||
[195] = (nil)
|
||||
[196] = (nil)
|
||||
[197] = (nil)
|
||||
[198] = (nil)
|
||||
[199] = (nil)
|
||||
[200] = (nil)
|
||||
[201] = (nil)
|
||||
[202] = (nil)
|
||||
[203] = (nil)
|
||||
[204] = (nil)
|
||||
[205] = (nil)
|
||||
[206] = (nil)
|
||||
[207] = (nil)
|
||||
[208] = (nil)
|
||||
[209] = (nil)
|
||||
[210] = (nil)
|
||||
[211] = (nil)
|
||||
[212] = (nil)
|
||||
[213] = (nil)
|
||||
[214] = (nil)
|
||||
[215] = (nil)
|
||||
[216] = (nil)
|
||||
[217] = (nil)
|
||||
[218] = (nil)
|
||||
[219] = (nil)
|
||||
[220] = (nil)
|
||||
[221] = (nil)
|
||||
[222] = (nil)
|
||||
[223] = (nil)
|
||||
[224] = (nil)
|
||||
[225] = (nil)
|
||||
[226] = (nil)
|
||||
[227] = (nil)
|
||||
[228] = (nil)
|
||||
[229] = (nil)
|
||||
[230] = (nil)
|
||||
[231] = (nil)
|
||||
[232] = (nil)
|
||||
[233] = (nil)
|
||||
[234] = (nil)
|
||||
[235] = (nil)
|
||||
[236] = (nil)
|
||||
[237] = (nil)
|
||||
[238] = (nil)
|
||||
[239] = (nil)
|
||||
[240] = (nil)
|
||||
[241] = (nil)
|
||||
[242] = (nil)
|
||||
[243] = (nil)
|
||||
[244] = (nil)
|
||||
[245] = (nil)
|
||||
[246] = (nil)
|
||||
[247] = (nil)
|
||||
[248] = (nil)
|
||||
[249] = (nil)
|
||||
[250] = (nil)
|
||||
[251] = (nil)
|
||||
[252] = (nil)
|
||||
[253] = (nil)
|
||||
[254] = (nil)
|
||||
[255] = (nil)
|
||||
[256] = (nil)
|
||||
[257] = (nil)
|
||||
[258] = (nil)
|
||||
[259] = (nil)
|
||||
[260] = (nil)
|
||||
[261] = (nil)
|
||||
[262] = (nil)
|
||||
[263] = (nil)
|
||||
[264] = (nil)
|
||||
[265] = (nil)
|
||||
[266] = (nil)
|
||||
[267] = (nil)
|
||||
[268] = (nil)
|
||||
[269] = (nil)
|
||||
[270] = (nil)
|
||||
[271] = (nil)
|
||||
[272] = (nil)
|
||||
[273] = (nil)
|
||||
[274] = (nil)
|
||||
[275] = (nil)
|
||||
[276] = (nil)
|
||||
[277] = (nil)
|
||||
[278] = (nil)
|
||||
[279] = (nil)
|
||||
[280] = (nil)
|
||||
[281] = (nil)
|
||||
[282] = (nil)
|
||||
[283] = (nil)
|
||||
[284] = (nil)
|
||||
[285] = (nil)
|
||||
[286] = (nil)
|
||||
[287] = (nil)
|
||||
[288] = (nil)
|
||||
[289] = (nil)
|
||||
[290] = (nil)
|
||||
[291] = (nil)
|
||||
[292] = (nil)
|
||||
[293] = (nil)
|
||||
[294] = (nil)
|
||||
[295] = (nil)
|
||||
[296] = (nil)
|
||||
[297] = (nil)
|
||||
[298] = (nil)
|
||||
[299] = (nil)
|
||||
[300] = (nil)
|
||||
[301] = (nil)
|
||||
[302] = (nil)
|
||||
[303] = (nil)
|
||||
[304] = (nil)
|
||||
[305] = (nil)
|
||||
[306] = (nil)
|
||||
[307] = (nil)
|
||||
[308] = (nil)
|
||||
[309] = (nil)
|
||||
[310] = (nil)
|
||||
[311] = (nil)
|
||||
[312] = (nil)
|
||||
[313] = (nil)
|
||||
[314] = (nil)
|
||||
[315] = (nil)
|
||||
[316] = (nil)
|
||||
[317] = (nil)
|
||||
[318] = (nil)
|
||||
[319] = (nil)
|
||||
[320] = (nil)
|
||||
[321] = (nil)
|
||||
[322] = (nil)
|
||||
[323] = (nil)
|
||||
[324] = (nil)
|
||||
[325] = (nil)
|
||||
[326] = (nil)
|
||||
[327] = (nil)
|
||||
[328] = (nil)
|
||||
[329] = (nil)
|
||||
[330] = (nil)
|
||||
[331] = (nil)
|
||||
[332] = (nil)
|
||||
[333] = (nil)
|
||||
[334] = (nil)
|
||||
[335] = (nil)
|
||||
[336] = (nil)
|
||||
[337] = (nil)
|
||||
[338] = (nil)
|
||||
[339] = (nil)
|
||||
[340] = (nil)
|
||||
[341] = (nil)
|
||||
[342] = (nil)
|
||||
[343] = (nil)
|
||||
[344] = (nil)
|
||||
[345] = (nil)
|
||||
[346] = (nil)
|
||||
[347] = (nil)
|
||||
[348] = (nil)
|
||||
[349] = (nil)
|
||||
[350] = (nil)
|
||||
[351] = (nil)
|
||||
[352] = (nil)
|
||||
[353] = (nil)
|
||||
[354] = (nil)
|
||||
[355] = (nil)
|
||||
[356] = (nil)
|
||||
[357] = (nil)
|
||||
[358] = (nil)
|
||||
[359] = (nil)
|
||||
[360] = (nil)
|
||||
[361] = (nil)
|
||||
[362] = (nil)
|
||||
[363] = (nil)
|
||||
[364] = (nil)
|
||||
[365] = (nil)
|
||||
[366] = (nil)
|
||||
[367] = (nil)
|
||||
[368] = (nil)
|
||||
[369] = (nil)
|
||||
[370] = (nil)
|
||||
[371] = (nil)
|
||||
[372] = (nil)
|
||||
[373] = (nil)
|
||||
[374] = (nil)
|
||||
[375] = (nil)
|
||||
[376] = (nil)
|
||||
[377] = (nil)
|
||||
[378] = (nil)
|
||||
[379] = (nil)
|
||||
[380] = (nil)
|
||||
[381] = (nil)
|
||||
[382] = (nil)
|
||||
[383] = (nil)
|
||||
[384] = (nil)
|
||||
[385] = (nil)
|
||||
[386] = (nil)
|
||||
[387] = (nil)
|
||||
[388] = (nil)
|
||||
[389] = (nil)
|
||||
[390] = (nil)
|
||||
[391] = (nil)
|
||||
[392] = (nil)
|
||||
[393] = (nil)
|
||||
[394] = (nil)
|
||||
[395] = (nil)
|
||||
[396] = (nil)
|
||||
[397] = (nil)
|
||||
[398] = (nil)
|
||||
[399] = (nil)
|
||||
[400] = (nil)
|
||||
[401] = (nil)
|
||||
[402] = (nil)
|
||||
[403] = (nil)
|
||||
[404] = (nil)
|
||||
[405] = (nil)
|
||||
[406] = (nil)
|
||||
[407] = (nil)
|
||||
[408] = (nil)
|
||||
[409] = (nil)
|
||||
[410] = (nil)
|
||||
[411] = (nil)
|
||||
[412] = (nil)
|
||||
[413] = (nil)
|
||||
[414] = (nil)
|
||||
[415] = (nil)
|
||||
[416] = (nil)
|
||||
[417] = (nil)
|
||||
[418] = (nil)
|
||||
[419] = (nil)
|
||||
[420] = (nil)
|
||||
[421] = (nil)
|
||||
[422] = (nil)
|
||||
[423] = (nil)
|
||||
[424] = (nil)
|
||||
[425] = (nil)
|
||||
[426] = (nil)
|
||||
[427] = (nil)
|
||||
[428] = (nil)
|
||||
[429] = (nil)
|
||||
[430] = (nil)
|
||||
[431] = (nil)
|
||||
[432] = (nil)
|
||||
[433] = (nil)
|
||||
[434] = (nil)
|
||||
[435] = (nil)
|
||||
[436] = (nil)
|
||||
[437] = (nil)
|
||||
[438] = (nil)
|
||||
[439] = (nil)
|
||||
[440] = (nil)
|
||||
[441] = (nil)
|
||||
[442] = (nil)
|
||||
[443] = (nil)
|
||||
[444] = (nil)
|
||||
[445] = (nil)
|
||||
[446] = (nil)
|
||||
[447] = (nil)
|
||||
[448] = (nil)
|
||||
[449] = (nil)
|
||||
[450] = (nil)
|
||||
[451] = (nil)
|
||||
[452] = (nil)
|
||||
[453] = (nil)
|
||||
[454] = (nil)
|
||||
[455] = (nil)
|
||||
[456] = (nil)
|
||||
[457] = (nil)
|
||||
[458] = (nil)
|
||||
[459] = (nil)
|
||||
[460] = (nil)
|
||||
[461] = (nil)
|
||||
[462] = (nil)
|
||||
[463] = (nil)
|
||||
[464] = (nil)
|
||||
[465] = (nil)
|
||||
[466] = (nil)
|
||||
[467] = (nil)
|
||||
[468] = (nil)
|
||||
[469] = (nil)
|
||||
[470] = (nil)
|
||||
[471] = (nil)
|
||||
[472] = (nil)
|
||||
[473] = (nil)
|
||||
[474] = (nil)
|
||||
[475] = (nil)
|
||||
[476] = (nil)
|
||||
[477] = (nil)
|
||||
[478] = (nil)
|
||||
[479] = (nil)
|
||||
[480] = (nil)
|
||||
[481] = (nil)
|
||||
[482] = (nil)
|
||||
[483] = (nil)
|
||||
[484] = (nil)
|
||||
[485] = (nil)
|
||||
[486] = (nil)
|
||||
[487] = (nil)
|
||||
[488] = (nil)
|
||||
[489] = (nil)
|
||||
[490] = (nil)
|
||||
[491] = (nil)
|
||||
[492] = (nil)
|
||||
[493] = (nil)
|
||||
[494] = (nil)
|
||||
[495] = (nil)
|
||||
[496] = (nil)
|
||||
[497] = (nil)
|
||||
[498] = (nil)
|
||||
[499] = (nil)
|
||||
[500] = (nil)
|
||||
[501] = (nil)
|
||||
[502] = (nil)
|
||||
[503] = (nil)
|
||||
[504] = (nil)
|
||||
[505] = (nil)
|
||||
[506] = (nil)
|
||||
[507] = (nil)
|
||||
[508] = (nil)
|
||||
[509] = (nil)
|
||||
[510] = (nil)
|
||||
[511] = (nil)
|
||||
</gap>
|
||||
@@ -0,0 +1,10 @@
|
||||
# Remove values
|
||||
|
||||
a 5 42
|
||||
a 10 43
|
||||
a 13 44
|
||||
a 510 45
|
||||
a 511 46
|
||||
|
||||
d 512 43
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
<gap len=512 hardlimit=0>
|
||||
[0] = (nil)
|
||||
[1] = (nil)
|
||||
[2] = (nil)
|
||||
[3] = (nil)
|
||||
[4] = (nil)
|
||||
[5] = 0x42
|
||||
[6] = (nil)
|
||||
[7] = (nil)
|
||||
[8] = (nil)
|
||||
[9] = (nil)
|
||||
[10] = (nil)
|
||||
[11] = (nil)
|
||||
[12] = 0x44
|
||||
[13] = (nil)
|
||||
[14] = (nil)
|
||||
[15] = (nil)
|
||||
[16] = (nil)
|
||||
[17] = (nil)
|
||||
[18] = (nil)
|
||||
[19] = (nil)
|
||||
[20] = (nil)
|
||||
[21] = (nil)
|
||||
[22] = (nil)
|
||||
[23] = (nil)
|
||||
[24] = (nil)
|
||||
[25] = (nil)
|
||||
[26] = (nil)
|
||||
[27] = (nil)
|
||||
[28] = (nil)
|
||||
[29] = (nil)
|
||||
[30] = (nil)
|
||||
[31] = (nil)
|
||||
[32] = (nil)
|
||||
[33] = (nil)
|
||||
[34] = (nil)
|
||||
[35] = (nil)
|
||||
[36] = (nil)
|
||||
[37] = (nil)
|
||||
[38] = (nil)
|
||||
[39] = (nil)
|
||||
[40] = (nil)
|
||||
[41] = (nil)
|
||||
[42] = (nil)
|
||||
[43] = (nil)
|
||||
[44] = (nil)
|
||||
[45] = (nil)
|
||||
[46] = (nil)
|
||||
[47] = (nil)
|
||||
[48] = (nil)
|
||||
[49] = (nil)
|
||||
[50] = (nil)
|
||||
[51] = (nil)
|
||||
[52] = (nil)
|
||||
[53] = (nil)
|
||||
[54] = (nil)
|
||||
[55] = (nil)
|
||||
[56] = (nil)
|
||||
[57] = (nil)
|
||||
[58] = (nil)
|
||||
[59] = (nil)
|
||||
[60] = (nil)
|
||||
[61] = (nil)
|
||||
[62] = (nil)
|
||||
[63] = (nil)
|
||||
[64] = (nil)
|
||||
[65] = (nil)
|
||||
[66] = (nil)
|
||||
[67] = (nil)
|
||||
[68] = (nil)
|
||||
[69] = (nil)
|
||||
[70] = (nil)
|
||||
[71] = (nil)
|
||||
[72] = (nil)
|
||||
[73] = (nil)
|
||||
[74] = (nil)
|
||||
[75] = (nil)
|
||||
[76] = (nil)
|
||||
[77] = (nil)
|
||||
[78] = (nil)
|
||||
[79] = (nil)
|
||||
[80] = (nil)
|
||||
[81] = (nil)
|
||||
[82] = (nil)
|
||||
[83] = (nil)
|
||||
[84] = (nil)
|
||||
[85] = (nil)
|
||||
[86] = (nil)
|
||||
[87] = (nil)
|
||||
[88] = (nil)
|
||||
[89] = (nil)
|
||||
[90] = (nil)
|
||||
[91] = (nil)
|
||||
[92] = (nil)
|
||||
[93] = (nil)
|
||||
[94] = (nil)
|
||||
[95] = (nil)
|
||||
[96] = (nil)
|
||||
[97] = (nil)
|
||||
[98] = (nil)
|
||||
[99] = (nil)
|
||||
[100] = (nil)
|
||||
[101] = (nil)
|
||||
[102] = (nil)
|
||||
[103] = (nil)
|
||||
[104] = (nil)
|
||||
[105] = (nil)
|
||||
[106] = (nil)
|
||||
[107] = (nil)
|
||||
[108] = (nil)
|
||||
[109] = (nil)
|
||||
[110] = (nil)
|
||||
[111] = (nil)
|
||||
[112] = (nil)
|
||||
[113] = (nil)
|
||||
[114] = (nil)
|
||||
[115] = (nil)
|
||||
[116] = (nil)
|
||||
[117] = (nil)
|
||||
[118] = (nil)
|
||||
[119] = (nil)
|
||||
[120] = (nil)
|
||||
[121] = (nil)
|
||||
[122] = (nil)
|
||||
[123] = (nil)
|
||||
[124] = (nil)
|
||||
[125] = (nil)
|
||||
[126] = (nil)
|
||||
[127] = (nil)
|
||||
[128] = (nil)
|
||||
[129] = (nil)
|
||||
[130] = (nil)
|
||||
[131] = (nil)
|
||||
[132] = (nil)
|
||||
[133] = (nil)
|
||||
[134] = (nil)
|
||||
[135] = (nil)
|
||||
[136] = (nil)
|
||||
[137] = (nil)
|
||||
[138] = (nil)
|
||||
[139] = (nil)
|
||||
[140] = (nil)
|
||||
[141] = (nil)
|
||||
[142] = (nil)
|
||||
[143] = (nil)
|
||||
[144] = (nil)
|
||||
[145] = (nil)
|
||||
[146] = (nil)
|
||||
[147] = (nil)
|
||||
[148] = (nil)
|
||||
[149] = (nil)
|
||||
[150] = (nil)
|
||||
[151] = (nil)
|
||||
[152] = (nil)
|
||||
[153] = (nil)
|
||||
[154] = (nil)
|
||||
[155] = (nil)
|
||||
[156] = (nil)
|
||||
[157] = (nil)
|
||||
[158] = (nil)
|
||||
[159] = (nil)
|
||||
[160] = (nil)
|
||||
[161] = (nil)
|
||||
[162] = (nil)
|
||||
[163] = (nil)
|
||||
[164] = (nil)
|
||||
[165] = (nil)
|
||||
[166] = (nil)
|
||||
[167] = (nil)
|
||||
[168] = (nil)
|
||||
[169] = (nil)
|
||||
[170] = (nil)
|
||||
[171] = (nil)
|
||||
[172] = (nil)
|
||||
[173] = (nil)
|
||||
[174] = (nil)
|
||||
[175] = (nil)
|
||||
[176] = (nil)
|
||||
[177] = (nil)
|
||||
[178] = (nil)
|
||||
[179] = (nil)
|
||||
[180] = (nil)
|
||||
[181] = (nil)
|
||||
[182] = (nil)
|
||||
[183] = (nil)
|
||||
[184] = (nil)
|
||||
[185] = (nil)
|
||||
[186] = (nil)
|
||||
[187] = (nil)
|
||||
[188] = (nil)
|
||||
[189] = (nil)
|
||||
[190] = (nil)
|
||||
[191] = (nil)
|
||||
[192] = (nil)
|
||||
[193] = (nil)
|
||||
[194] = (nil)
|
||||
[195] = (nil)
|
||||
[196] = (nil)
|
||||
[197] = (nil)
|
||||
[198] = (nil)
|
||||
[199] = (nil)
|
||||
[200] = (nil)
|
||||
[201] = (nil)
|
||||
[202] = (nil)
|
||||
[203] = (nil)
|
||||
[204] = (nil)
|
||||
[205] = (nil)
|
||||
[206] = (nil)
|
||||
[207] = (nil)
|
||||
[208] = (nil)
|
||||
[209] = (nil)
|
||||
[210] = (nil)
|
||||
[211] = (nil)
|
||||
[212] = (nil)
|
||||
[213] = (nil)
|
||||
[214] = (nil)
|
||||
[215] = (nil)
|
||||
[216] = (nil)
|
||||
[217] = (nil)
|
||||
[218] = (nil)
|
||||
[219] = (nil)
|
||||
[220] = (nil)
|
||||
[221] = (nil)
|
||||
[222] = (nil)
|
||||
[223] = (nil)
|
||||
[224] = (nil)
|
||||
[225] = (nil)
|
||||
[226] = (nil)
|
||||
[227] = (nil)
|
||||
[228] = (nil)
|
||||
[229] = (nil)
|
||||
[230] = (nil)
|
||||
[231] = (nil)
|
||||
[232] = (nil)
|
||||
[233] = (nil)
|
||||
[234] = (nil)
|
||||
[235] = (nil)
|
||||
[236] = (nil)
|
||||
[237] = (nil)
|
||||
[238] = (nil)
|
||||
[239] = (nil)
|
||||
[240] = (nil)
|
||||
[241] = (nil)
|
||||
[242] = (nil)
|
||||
[243] = (nil)
|
||||
[244] = (nil)
|
||||
[245] = (nil)
|
||||
[246] = (nil)
|
||||
[247] = (nil)
|
||||
[248] = (nil)
|
||||
[249] = (nil)
|
||||
[250] = (nil)
|
||||
[251] = (nil)
|
||||
[252] = (nil)
|
||||
[253] = (nil)
|
||||
[254] = (nil)
|
||||
[255] = (nil)
|
||||
[256] = (nil)
|
||||
[257] = (nil)
|
||||
[258] = (nil)
|
||||
[259] = (nil)
|
||||
[260] = (nil)
|
||||
[261] = (nil)
|
||||
[262] = (nil)
|
||||
[263] = (nil)
|
||||
[264] = (nil)
|
||||
[265] = (nil)
|
||||
[266] = (nil)
|
||||
[267] = (nil)
|
||||
[268] = (nil)
|
||||
[269] = (nil)
|
||||
[270] = (nil)
|
||||
[271] = (nil)
|
||||
[272] = (nil)
|
||||
[273] = (nil)
|
||||
[274] = (nil)
|
||||
[275] = (nil)
|
||||
[276] = (nil)
|
||||
[277] = (nil)
|
||||
[278] = (nil)
|
||||
[279] = (nil)
|
||||
[280] = (nil)
|
||||
[281] = (nil)
|
||||
[282] = (nil)
|
||||
[283] = (nil)
|
||||
[284] = (nil)
|
||||
[285] = (nil)
|
||||
[286] = (nil)
|
||||
[287] = (nil)
|
||||
[288] = (nil)
|
||||
[289] = (nil)
|
||||
[290] = (nil)
|
||||
[291] = (nil)
|
||||
[292] = (nil)
|
||||
[293] = (nil)
|
||||
[294] = (nil)
|
||||
[295] = (nil)
|
||||
[296] = (nil)
|
||||
[297] = (nil)
|
||||
[298] = (nil)
|
||||
[299] = (nil)
|
||||
[300] = (nil)
|
||||
[301] = (nil)
|
||||
[302] = (nil)
|
||||
[303] = (nil)
|
||||
[304] = (nil)
|
||||
[305] = (nil)
|
||||
[306] = (nil)
|
||||
[307] = (nil)
|
||||
[308] = (nil)
|
||||
[309] = (nil)
|
||||
[310] = (nil)
|
||||
[311] = (nil)
|
||||
[312] = (nil)
|
||||
[313] = (nil)
|
||||
[314] = (nil)
|
||||
[315] = (nil)
|
||||
[316] = (nil)
|
||||
[317] = (nil)
|
||||
[318] = (nil)
|
||||
[319] = (nil)
|
||||
[320] = (nil)
|
||||
[321] = (nil)
|
||||
[322] = (nil)
|
||||
[323] = (nil)
|
||||
[324] = (nil)
|
||||
[325] = (nil)
|
||||
[326] = (nil)
|
||||
[327] = (nil)
|
||||
[328] = (nil)
|
||||
[329] = (nil)
|
||||
[330] = (nil)
|
||||
[331] = (nil)
|
||||
[332] = (nil)
|
||||
[333] = (nil)
|
||||
[334] = (nil)
|
||||
[335] = (nil)
|
||||
[336] = (nil)
|
||||
[337] = (nil)
|
||||
[338] = (nil)
|
||||
[339] = (nil)
|
||||
[340] = (nil)
|
||||
[341] = (nil)
|
||||
[342] = (nil)
|
||||
[343] = (nil)
|
||||
[344] = (nil)
|
||||
[345] = (nil)
|
||||
[346] = (nil)
|
||||
[347] = (nil)
|
||||
[348] = (nil)
|
||||
[349] = (nil)
|
||||
[350] = (nil)
|
||||
[351] = (nil)
|
||||
[352] = (nil)
|
||||
[353] = (nil)
|
||||
[354] = (nil)
|
||||
[355] = (nil)
|
||||
[356] = (nil)
|
||||
[357] = (nil)
|
||||
[358] = (nil)
|
||||
[359] = (nil)
|
||||
[360] = (nil)
|
||||
[361] = (nil)
|
||||
[362] = (nil)
|
||||
[363] = (nil)
|
||||
[364] = (nil)
|
||||
[365] = (nil)
|
||||
[366] = (nil)
|
||||
[367] = (nil)
|
||||
[368] = (nil)
|
||||
[369] = (nil)
|
||||
[370] = (nil)
|
||||
[371] = (nil)
|
||||
[372] = (nil)
|
||||
[373] = (nil)
|
||||
[374] = (nil)
|
||||
[375] = (nil)
|
||||
[376] = (nil)
|
||||
[377] = (nil)
|
||||
[378] = (nil)
|
||||
[379] = (nil)
|
||||
[380] = (nil)
|
||||
[381] = (nil)
|
||||
[382] = (nil)
|
||||
[383] = (nil)
|
||||
[384] = (nil)
|
||||
[385] = (nil)
|
||||
[386] = (nil)
|
||||
[387] = (nil)
|
||||
[388] = (nil)
|
||||
[389] = (nil)
|
||||
[390] = (nil)
|
||||
[391] = (nil)
|
||||
[392] = (nil)
|
||||
[393] = (nil)
|
||||
[394] = (nil)
|
||||
[395] = (nil)
|
||||
[396] = (nil)
|
||||
[397] = (nil)
|
||||
[398] = (nil)
|
||||
[399] = (nil)
|
||||
[400] = (nil)
|
||||
[401] = (nil)
|
||||
[402] = (nil)
|
||||
[403] = (nil)
|
||||
[404] = (nil)
|
||||
[405] = (nil)
|
||||
[406] = (nil)
|
||||
[407] = (nil)
|
||||
[408] = (nil)
|
||||
[409] = (nil)
|
||||
[410] = (nil)
|
||||
[411] = (nil)
|
||||
[412] = (nil)
|
||||
[413] = (nil)
|
||||
[414] = (nil)
|
||||
[415] = (nil)
|
||||
[416] = (nil)
|
||||
[417] = (nil)
|
||||
[418] = (nil)
|
||||
[419] = (nil)
|
||||
[420] = (nil)
|
||||
[421] = (nil)
|
||||
[422] = (nil)
|
||||
[423] = (nil)
|
||||
[424] = (nil)
|
||||
[425] = (nil)
|
||||
[426] = (nil)
|
||||
[427] = (nil)
|
||||
[428] = (nil)
|
||||
[429] = (nil)
|
||||
[430] = (nil)
|
||||
[431] = (nil)
|
||||
[432] = (nil)
|
||||
[433] = (nil)
|
||||
[434] = (nil)
|
||||
[435] = (nil)
|
||||
[436] = (nil)
|
||||
[437] = (nil)
|
||||
[438] = (nil)
|
||||
[439] = (nil)
|
||||
[440] = (nil)
|
||||
[441] = (nil)
|
||||
[442] = (nil)
|
||||
[443] = (nil)
|
||||
[444] = (nil)
|
||||
[445] = (nil)
|
||||
[446] = (nil)
|
||||
[447] = (nil)
|
||||
[448] = (nil)
|
||||
[449] = (nil)
|
||||
[450] = (nil)
|
||||
[451] = (nil)
|
||||
[452] = (nil)
|
||||
[453] = (nil)
|
||||
[454] = (nil)
|
||||
[455] = (nil)
|
||||
[456] = (nil)
|
||||
[457] = (nil)
|
||||
[458] = (nil)
|
||||
[459] = (nil)
|
||||
[460] = (nil)
|
||||
[461] = (nil)
|
||||
[462] = (nil)
|
||||
[463] = (nil)
|
||||
[464] = (nil)
|
||||
[465] = (nil)
|
||||
[466] = (nil)
|
||||
[467] = (nil)
|
||||
[468] = (nil)
|
||||
[469] = (nil)
|
||||
[470] = (nil)
|
||||
[471] = (nil)
|
||||
[472] = (nil)
|
||||
[473] = (nil)
|
||||
[474] = (nil)
|
||||
[475] = (nil)
|
||||
[476] = (nil)
|
||||
[477] = (nil)
|
||||
[478] = (nil)
|
||||
[479] = (nil)
|
||||
[480] = (nil)
|
||||
[481] = (nil)
|
||||
[482] = (nil)
|
||||
[483] = (nil)
|
||||
[484] = (nil)
|
||||
[485] = (nil)
|
||||
[486] = (nil)
|
||||
[487] = (nil)
|
||||
[488] = (nil)
|
||||
[489] = (nil)
|
||||
[490] = (nil)
|
||||
[491] = (nil)
|
||||
[492] = (nil)
|
||||
[493] = (nil)
|
||||
[494] = (nil)
|
||||
[495] = (nil)
|
||||
[496] = (nil)
|
||||
[497] = (nil)
|
||||
[498] = (nil)
|
||||
[499] = (nil)
|
||||
[500] = (nil)
|
||||
[501] = (nil)
|
||||
[502] = (nil)
|
||||
[503] = (nil)
|
||||
[504] = (nil)
|
||||
[505] = (nil)
|
||||
[506] = (nil)
|
||||
[507] = (nil)
|
||||
[508] = (nil)
|
||||
[509] = 0x45
|
||||
[510] = 0x46
|
||||
[511] = 0x46
|
||||
</gap>
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# This runs all the tests.
|
||||
|
||||
# Tests scripts are in *.tst files.
|
||||
# Corresponding output is put in *.out.
|
||||
# Reference output is put in *.ref.
|
||||
# Any discrepancy will be reported!
|
||||
|
||||
use strict;
|
||||
|
||||
my @res;
|
||||
foreach my $fn (`ls *.tst`) {
|
||||
chomp $fn;
|
||||
my $cmd = "./gtest $fn $fn.out";
|
||||
print "$cmd\n";
|
||||
`$cmd`;
|
||||
my $res = system("diff -u $fn.ref $fn.out");
|
||||
push @res, [$fn, ($res == 0 ? "OK" : "*KO*")];
|
||||
}
|
||||
|
||||
format =
|
||||
@<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>
|
||||
$_->[0], $_->[1]
|
||||
.
|
||||
|
||||
|
||||
#format_name STDOUT test_result;
|
||||
map { write; } @res;
|
||||
|
||||
@@ -61,6 +61,7 @@ static int parse_server_name_extension(const struct TLSProtocol *, const char *,
|
||||
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
|
||||
static int has_match(const char**, size_t, const char*, size_t);
|
||||
|
||||
|
||||
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
|
||||
* hello handshake, returning a status code
|
||||
*
|
||||
@@ -81,6 +82,11 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
if (data_len < TLS_HEADER_LEN)
|
||||
return TLS_ELENGTH;
|
||||
|
||||
/* If if it is an ECH, decrypt and match */
|
||||
ech_match_sni(data, data_len);
|
||||
|
||||
|
||||
|
||||
tls_content_type = data[0];
|
||||
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
|
||||
@@ -224,7 +230,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
|
||||
switch (data[pos]) { /* name type */
|
||||
case 0x00: /* host_name */
|
||||
if(has_match(tls_data->sni_hostname_list, tls_data->sni_list_len, data + pos + 3, len)) {
|
||||
return len;
|
||||
return (int)len;
|
||||
} else {
|
||||
return TLS_ENOEXT;
|
||||
}
|
||||
@@ -253,7 +259,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
|
||||
return TLS_EPROTOCOL;
|
||||
|
||||
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
|
||||
return len;
|
||||
return (int)len;
|
||||
} else if (len > 0) {
|
||||
print_message(msg_probe_error, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
||||
}
|
||||
@@ -301,11 +307,11 @@ struct TLSProtocol *
|
||||
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, const char** list, size_t list_len) {
|
||||
if (alpn) {
|
||||
tls_data->alpn_protocol_list = list;
|
||||
tls_data->alpn_list_len = list_len;
|
||||
tls_data->alpn_list_len = (int)list_len;
|
||||
tls_data->match_mode.tls_match_alpn = 1;
|
||||
} else {
|
||||
tls_data->sni_hostname_list = list;
|
||||
tls_data->sni_list_len = list_len;
|
||||
tls_data->sni_list_len = (int)list_len;
|
||||
tls_data->match_mode.tls_match_sni = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,5 +44,6 @@ struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, s
|
||||
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
|
||||
#define TLS_EPROTOCOL -5 /* Protocol error */
|
||||
|
||||
void ech_match_sni(const char* buffer_in, size_t buffer_len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
# tls_ech.c: support for RFC9849 TLS Encrypted Client Hello (ECH)
|
||||
#
|
||||
# Copyright (C) 2026 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#define WOLFSSL_USE_OPTION_H
|
||||
#include <wolfssl/options.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <wolfssl/wolfcrypt/random.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "tls.h"
|
||||
|
||||
WOLFSSL_CTX* wolfssl_ctx;
|
||||
|
||||
|
||||
// Hard-coded server certificate (PEM format)
|
||||
// from snakeoil -- these serves no purpose beyond initialising the context
|
||||
static const char* SERVER_CERT_PEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICyjCCAbKgAwIBAgIUZrCzD4tEIu1r9EhkOn96raiupe4wDQYJKoZIhvcNAQEL\n"
|
||||
"BQAwDzENMAsGA1UEAwwEc2FhejAeFw0yMTA1MjgyMDM0MjRaFw0zMTA1MjYyMDM0\n"
|
||||
"MjRaMA8xDTALBgNVBAMMBHNhYXowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
|
||||
"AoIBAQC5s1UhjcmsrRnSdJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6J\n"
|
||||
"MAL1MbPtX0pfR+yBILWUdn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoe\n"
|
||||
"gD9FE3lE895GIhEv7Jd9bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bK\n"
|
||||
"fJzuOxi7oIeINLEiR0Z4hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2\n"
|
||||
"QFMByfkEmAGp9Br+2tquRUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd\n"
|
||||
"6G6TLenPbQPEaac5UgGZ7rJvL9idAgMBAAGjHjAcMAkGA1UdEwQCMAAwDwYDVR0R\n"
|
||||
"BAgwBoIEc2FhejANBgkqhkiG9w0BAQsFAAOCAQEAZPasi1Hu7q4S9wJsbIzcGj7m\n"
|
||||
"ZCAxNdm3AINeV3mxfhToeqtXSoa2sujCEgwCuiYi0OE8wmseLO8e1xIsRc/NEpOl\n"
|
||||
"h9u5OupV2Xbf7nuKzPl31YQ7kv+90mnixizf6CFpHXAdU3WEZ33+fMtS+lV/ZBYo\n"
|
||||
"Nh9GGNJ7Grv5KIKu/b2cR403pRF1hGrjWABrIDZO3ZCtNgRp/My1Qr+/04rTs8VF\n"
|
||||
"WyDshOe0u6Xo//M3sCMbx5+62BopCMmAu2TxiPew0OTM8eDa3IBbc2kLGxp8c4J+\n"
|
||||
"EYpAKTZbkhs/6SleNMC4BOEmZQ1tk6D0N0u4Y0b1IKEjgx7HqwZ6mIdmbU1lZQ==\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static const char* SERVER_KEY_PEM =
|
||||
"-----BEGIN PRIVATE KEY-----\n"
|
||||
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5s1UhjcmsrRnS\n"
|
||||
"dJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6JMAL1MbPtX0pfR+yBILWU\n"
|
||||
"dn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoegD9FE3lE895GIhEv7Jd9\n"
|
||||
"bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bKfJzuOxi7oIeINLEiR0Z4\n"
|
||||
"hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2QFMByfkEmAGp9Br+2tqu\n"
|
||||
"RUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd6G6TLenPbQPEaac5UgGZ\n"
|
||||
"7rJvL9idAgMBAAECggEAbXNQctViD596/7rryhze+KztsI/UNsVU6uH3T+LN1XxU\n"
|
||||
"jurnRVNFV4JU3DDrHV9tIDQw3pXCxJzlyRpKHDWGNgTZU2fI6sZGuX+4w1Apd8ss\n"
|
||||
"3Y5mEkGYyaslF/MY13rR2B0wXf/IR4nYA/ssiaBjLcGqHHnQOVseHNgtdXFPXW9n\n"
|
||||
"5IXJ2PSeaFW0Z13vbTQm9Cx2kCBKL4J3TymhkuSW3DUHxmlDfWUK14lkyXCZIZ3i\n"
|
||||
"TbXQAXhwTrz3wqbG++iEP4MZM0Ka7/+WXqrsxA/yCirb+HnMewxwBKzGvkym2I2H\n"
|
||||
"3BkjDn9exDgTePD7i0w22UYCF5uezDB9YOiPVZ634QKBgQDyKIgxD7XMMJezeg0Y\n"
|
||||
"PaG+Eig/hE27mm2pyTQSd6MMZ1HpbDUpLQ/GO3Npy+bKS0/5eefNT4PWVcmpRL4W\n"
|
||||
"blNIfixkcZq1a6wax9Nhk89jXZdhBinO8gfXFOiANFDS7B4fEXeTrfhmuryqUw9m\n"
|
||||
"L6GJ0jk/H6fs6ads15y+O7A6tQKBgQDEUKlZg4qXxNMNKq7l+v7R6IH8VrNLt0I/\n"
|
||||
"f4fhpW3IhmPJCRuOFOS0UpDPYjTeu14i3EFcpe0D+DVoQiggBTAh3I/qMHvRko5g\n"
|
||||
"DT83YHp2/ec0AY0ocwnLwye224t6GF7S8R8vhG1FkIBk6DTJgWFZLDtY+dekZugz\n"
|
||||
"IHvgtxSPSQKBgHWD7fPBJ/xbaIMUq94jqqZOsXGBhyePncBTgA2mOV3/leStOm8t\n"
|
||||
"CwasOyoQZYOuYLU0z6T2/Ye6Qg7+6TCBgbEgafKknuuDwRWN+6rSzEXwWVIgZ5Gi\n"
|
||||
"KuPZparxuHdjVorFMz5borxXys7tV//DBaWYe2eCuT9jdHiBefNni/IxAoGAWHTn\n"
|
||||
"+deVqVEcsAAdkq8IBtk3SNZgL6vBhA8Y0QnTb9luOPWLnve3HbFeYrOjkwDmJ9sK\n"
|
||||
"8I4rP/ClT/cPUW5FA9z4U9PI1uOsl4cghvlH9Tnu5bYVPranIVMsH+7I6Bj3ESFo\n"
|
||||
"peaLvh6gW9dtgaZ0kNOnYi4hhoh/9Bmc9+JGt1ECgYAbGSQLa21L4Hx3NvMparVQ\n"
|
||||
"bGjN26igvqKmhBaKrH8YolOlhEw/mCwTlg9zI7jrhdO0VClPrB8IVBoL+6HvrT21\n"
|
||||
"LJM31J3ddqME9/5fQgKMQsD60kRDD6iV/iK2S6TOUjxJn1f6MuLmGSOHZYcbdBPZ\n"
|
||||
"DyBKgbz/xGV6katjXvg74A==\n"
|
||||
"-----END PRIVATE KEY-----\n";
|
||||
|
||||
|
||||
|
||||
#if 1
|
||||
/* Will be provided by wolfSSL */
|
||||
int wolfSSL_GetEchClientHelloInner(
|
||||
WOLFSSL* ssl, /* Connected to an existing context with the ECH configs. Stores hpke state */
|
||||
const byte* buffer_in, /* The full ClientHelloOuter */
|
||||
word32 buffer_len, /* How many bytes in the buffer */
|
||||
byte* buffer_out,
|
||||
word32* buffer_out_len
|
||||
)
|
||||
{
|
||||
char* inner = "some clienthelloinner";
|
||||
|
||||
*buffer_out_len = strlen(inner);
|
||||
memcpy(buffer_out, inner, *buffer_out_len);
|
||||
print_message(msg_fd, "GetEchClientHelloInner set\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void ech_get_client_hello_inner(WOLFSSL_CTX* ctx, const byte* data, size_t datalen)
|
||||
{
|
||||
WOLFSSL* ssl = NULL;
|
||||
|
||||
printf("wolfSSL_new(%p)\n", ctx);
|
||||
ssl = wolfSSL_new(ctx);
|
||||
if (ssl == NULL) {
|
||||
fprintf(stderr, "Failed to create WOLFSSL object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable certificate verification on this connection
|
||||
wolfSSL_set_verify(ssl, WOLFSSL_VERIFY_NONE, NULL);
|
||||
|
||||
byte client_hello_inner[1024];
|
||||
word32 ch_len = sizeof(client_hello_inner);;
|
||||
|
||||
int res = wolfSSL_GetEchClientHelloInner(
|
||||
ssl,
|
||||
data, /* The full ClientHelloOuter */
|
||||
datalen, /* How many bytes in the buffer */
|
||||
(byte*)&client_hello_inner,
|
||||
&ch_len
|
||||
);
|
||||
client_hello_inner[ch_len] = 0;
|
||||
if (!res) {
|
||||
print_message(msg_fd, "GetEchClientHelloInner: Innerlen: %d\n", ch_len);
|
||||
hexdump(msg_fd, (char*)client_hello_inner, ch_len);
|
||||
print_message(msg_fd, "/GetEchClientHelloInner\n");
|
||||
}
|
||||
|
||||
|
||||
byte sni_data[1024];
|
||||
unsigned int sni_len = sizeof(sni_data);
|
||||
memset(sni_data, 'A', sni_len);
|
||||
|
||||
printf("SNI_GetFromBuffer\n");
|
||||
wolfSSL_SNI_GetFromBuffer(data, datalen, 0, (byte*)&sni_data, &sni_len);
|
||||
sni_data[sni_len] = 0;
|
||||
printf("sni_len = %d\n", sni_len);
|
||||
printf("sni: %s\n", sni_data);
|
||||
}
|
||||
|
||||
/* See if the context matches */
|
||||
void ech_match_sni(const char* buffer_in, size_t buffer_len)
|
||||
{
|
||||
ech_get_client_hello_inner(wolfssl_ctx, (byte*)buffer_in, buffer_len);
|
||||
}
|
||||
|
||||
|
||||
/* Appends a configuration to the DNS configuration file */
|
||||
static void append_conf(const char* sni, const char* ech)
|
||||
{
|
||||
FILE* f = fopen(cfg.ech_conf_sink, "a");
|
||||
|
||||
if (!f) {
|
||||
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
int res = fprintf(f, "%s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\"\n", sni, ech);
|
||||
printf("append: %s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\" : %d\n", sni, ech, res);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
/* Erases the configuration file */
|
||||
static void reset_conf(void)
|
||||
{
|
||||
FILE* f = fopen(cfg.ech_conf_sink, "w");
|
||||
|
||||
if (!f) {
|
||||
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
printf("erased and opend %s\n", cfg.ech_conf_sink);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Generate a keypair for a config; later this will be moved to a separate
|
||||
* process, and sslh will just read it */
|
||||
static void ech_genkey(WOLFSSL_CTX* ctx, const char* sni)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("ech_genkey for ctx %p\n", ctx);
|
||||
|
||||
print_message(msg_config, "Generating ECH config for outer SNI: %s\n", sni);
|
||||
ret = wolfSSL_CTX_GenerateEchConfig(ctx, sni, 0, 0, 0);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
print_message(msg_system_error, "Failed to generate ECH config: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
byte echConfig[512];
|
||||
word32 echConfigLen = sizeof(echConfig);
|
||||
ret = wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
print_message(msg_system_error, "Failed to get ECH config: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char out[512];
|
||||
word32 outLen = sizeof(out);
|
||||
if (Base64_Encode_NoNl(echConfig, echConfigLen, (byte*)out, &outLen) != 0) {
|
||||
print_message(msg_system_error, "Failed to encode to base64: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
append_conf(sni, out);
|
||||
}
|
||||
|
||||
/* For each public names, create a keypair and configuration.
|
||||
* (Later, this will load the configurations from some place else */
|
||||
static void ech_create_configs(WOLFSSL_CTX** ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
reset_conf();
|
||||
|
||||
*ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
|
||||
if (!*ctx) {
|
||||
print_message(msg_system_error, "Failed to create WOLFSSL_CTX\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load certificate from memory
|
||||
ret = wolfSSL_CTX_use_certificate_buffer(*ctx,
|
||||
(const unsigned char*)SERVER_CERT_PEM,
|
||||
strlen(SERVER_CERT_PEM),
|
||||
WOLFSSL_FILETYPE_PEM);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to load certificate: %d\n", ret);
|
||||
fprintf(stderr, "Please replace SERVER_CERT_PEM with a valid certificate\n");
|
||||
wolfSSL_CTX_free(*ctx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load private key from memory
|
||||
ret = wolfSSL_CTX_use_PrivateKey_buffer(*ctx,
|
||||
(const unsigned char*)SERVER_KEY_PEM,
|
||||
strlen(SERVER_KEY_PEM),
|
||||
WOLFSSL_FILETYPE_PEM);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to load private key: %d\n", ret);
|
||||
fprintf(stderr, "Please replace SERVER_KEY_PEM with a valid private key\n");
|
||||
wolfSSL_CTX_free(*ctx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Certificate and key loaded successfully\n");
|
||||
|
||||
ech_genkey(*ctx, cfg.listen[0].host); /* For now this only works on the first host */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Initialises the ECH subsystem: create wolfSSL context, create ECH configs,
|
||||
* etc */
|
||||
|
||||
void ech_init()
|
||||
{
|
||||
wolfSSL_Init();
|
||||
|
||||
ech_create_configs(&wolfssl_ctx);
|
||||
}
|
||||
+14
-5
@@ -27,12 +27,16 @@
|
||||
#include "sslh-conf.h"
|
||||
#include "udp-listener.h"
|
||||
|
||||
|
||||
/* Incoming connections are of course all received on a single socket. Create a
|
||||
* hash that associates (incoming sockaddr) => struct connection*, so finding
|
||||
* the connection related to an incoming packet is fast.
|
||||
*/
|
||||
|
||||
|
||||
typedef struct connection* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
|
||||
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
|
||||
{
|
||||
@@ -120,6 +124,7 @@ static void udp_sanity_check(void)
|
||||
void udp_init(struct loop_info* fd_info)
|
||||
{
|
||||
fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp);
|
||||
CHECK_ALLOC(fd_info->hash_sources, "hash_init");
|
||||
|
||||
udp_protocol_list_init();
|
||||
udp_sanity_check();
|
||||
@@ -259,14 +264,13 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
struct connection* cnx;
|
||||
ssize_t len;
|
||||
socklen_t addrlen;
|
||||
int res, target, out = -1;
|
||||
ssize_t res;
|
||||
int target, out = -1;
|
||||
char data[65536]; /* Theoretical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
|
||||
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
|
||||
malloc/free overhead for each packet, when really 64K is not that much */
|
||||
|
||||
|
||||
udp_timeouts(fd_info);
|
||||
|
||||
addrlen = sizeof(src_addr);
|
||||
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
|
||||
if (len < 0) {
|
||||
@@ -280,7 +284,7 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||
|
||||
if (target == -1) {
|
||||
res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
|
||||
res = probe_buffer(data, (int)len, udp_protocols, udp_protocols_len, &proto);
|
||||
/* First version: if we can't work out the protocol from the first
|
||||
* packet, drop it. Conceivably, we could store several packets to
|
||||
* run probes on packet sets */
|
||||
@@ -301,6 +305,11 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
cnx->addrlen = addrlen;
|
||||
cnx->local_endpoint = sockfd;
|
||||
|
||||
if (inc_proto_connections(cnx->proto)) {
|
||||
tidy_connection(cnx, fd_info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = new_source(fd_info->hash_sources, cnx);
|
||||
if (res == -1) {
|
||||
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increase udp_max_connections");
|
||||
@@ -324,7 +333,7 @@ void udp_s2c_forward(struct connection* cnx)
|
||||
{
|
||||
int sockfd = cnx->target_sock;
|
||||
char data[65536];
|
||||
int res;
|
||||
ssize_t res;
|
||||
|
||||
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
||||
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
|
||||
|
||||
@@ -25,5 +25,6 @@ void udp_s2c_forward(struct connection* cnx);
|
||||
|
||||
void udp_init(struct loop_info* fd_info);
|
||||
void udp_tidy(struct connection* cnx, struct loop_info* fd_info);
|
||||
void udp_timeouts(struct loop_info* fd_info);
|
||||
|
||||
#endif /* UDPLISTENER_H */
|
||||
Reference in new issue
Block a user