Compare commits

..
2 Commits
109 changed files with 1469 additions and 16624 deletions

No files matched your search

-63
View File
@@ -1,63 +0,0 @@
name: Create and publish Container image
on:
push:
branches:
- master
tags:
- 'v*'
pull_request:
branches:
- master
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
context: .
file: Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-7
View File
@@ -3,15 +3,8 @@
*.o
cscope.*
echosrv
libsslh.a
sslh-fork
sslh-select
sslh-ev
systemd-sslh-generator
sslh.8.gz
tags
version.h
/config.status
/config.log
/config.h
/Makefile
+18 -84
View File
@@ -1,82 +1,16 @@
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
v1.23.x:
Obsolete. Please use v2.0 or above. This is a branch
where no new features will be introduced, only
bugfixes to versions below 1.22.
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.
v1.23.1:
Check accept() for failure in sslh-fork. Aborted
connections otherwise result in crashes.
Support HAProxy's proxyprotocol on the backend
server side.
v1.23.0:
Reintroduce --ssl as alias to --tls.
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.
Reintroduced --ssl as an alias to --tls.
Introduce autoconf to adapt to landlock presence.
Close connexion without error message if remote
client forcefully closes connexion, for Windows.
v2.0.1:
Fix resolve_on_forward setting, which would crash
sslh reliably.
v2.0.0:
v2.0:
New sslh-ev: this is functionally equivalent to
sslh-select (mono-process, only forks for specified
protocols), but based on libev, which should make it
scalable to large numbers of connections.
New log system: instead of --verbose with arbitrary
levels, there are now several message classes. Each
message class can be set to go to stderr, syslog, or
both. Classes are documented in example.cfg.
UDP connections are now managed in a hash to avoid
linear searches. The downside is that the number of
UDP connections is a hard limit, configurable with
the 'udp_max_connections', which defaults to 1024.
Timeouts are managed with lists.
inetd merges stderr output to what is sent to the
client, which is a security issue as it might give
information to an attacker. When inetd is activated,
stderr is forcibly closed.
New protocol-level option `resolve_on_forward`,
requests that target names are resolved at each
connection instead of at startup. Useful for dynamic
DNS situations. (Paul Schroeder/milkpirate)
New probe for MSRDP (akappner).
v1.22: 17AUG2021
sslh-select now supports UDP protocols.
@@ -92,7 +26,7 @@ v1.22: 17AUG2021
combined with incoming TLS with SNI. UDP clients
and servers need to agree on the IPv4/IPv6 they use:
use the same protocol on all sides! Often, this
means explicitly using 'ip4-localhost'.
means explicitely using 'ip4-localhost'.
UDP sender-receiver pairs (connections, so to speak)
are kept for 60s, which can be changed with
`udp_timeout` in the configuration.
@@ -134,7 +68,7 @@ v1.21: 11JUL2020
Added TCP_FASTOPEN support for client sockets (if
tfo_ok is specified in their configuration) and for
listening socket, if all client protocols support it.
listenint socket, if all client protocols support it.
(Craig Andrews)
Added 'minlength' option to skip a probe if less
@@ -160,8 +94,8 @@ v1.20: 20NOV2018
Before, probes were tried in order, repeating on the
same probe as long it returned PROBE_AGAIN before
moving to the next one. This means a probe which
requires a lot of data (i.e. return PROBE_AGAIN for
a long time) could prevent successful matches from
requires a lot of data (i.e. returne PROBE_AGAIN for
a long time) could prevent sucessful matches from
subsequent probes. The configuration file needed to
take that into account.
@@ -222,7 +156,7 @@ v1.18: 29MAR2016
v1.17: 09MAR2015
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
Transparent proxy support for FreeBSD.
Transparant proxy support for FreeBSD.
(Ruben van Staveren)
Using -F with no argument will try
@@ -251,7 +185,7 @@ v1.16: 11FEB2014
Libcap support: Keep only CAP_NET_ADMIN if started
as root with transparent proxying and dropping
privileges (enable USELIBCAP in Makefile). This
priviledges (enable USELIBCAP in Makefile). This
avoids having to mess with filesystem capabilities.
(Sebastian Schmidt/yath)
@@ -260,7 +194,7 @@ v1.16: 11FEB2014
actual errors if connections are dropped before
getting to getpeername).
Set IP_FREEBIND if available to bind to addresses
Set IP_FREEDBIND if available to bind to addresses
that don't yet exist.
v1.15: 27JUL2013
@@ -345,7 +279,7 @@ v1.11: 21APR2012
--user isn't specified, just run as current user.
No longer create PID file by default, it should be
explicitly set with --pidfile.
explicitely set with --pidfile.
No longer log to syslog if in foreground. Logs are
instead output to stderr.
@@ -436,7 +370,7 @@ v1.8: 15JUL2011
v1.7: 01FEB2010
Added CentOS init.d script (Andre Krajnik).
Fixed default ssl address inconsistency, now
Fixed default ssl address inconsistancy, now
defaults to "localhost:443" and fixed documentation
accordingly (pointed by Markus Schalke).
+17 -32
View File
@@ -1,38 +1,23 @@
ARG ALPINE_VERSION="latest"
ARG TARGET_ARCH="library"
FROM alpine:latest as build
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
ADD . /sslh
WORKDIR /sslh
RUN \
apk add \
gcc \
libconfig-dev \
make \
musl-dev \
pcre-dev \
perl && \
cd /sslh && \
make sslh-select && \
strip sslh-select
RUN apk add --no-cache \
'gcc' \
'libconfig-dev' \
'make' \
'musl-dev' \
'pcre2-dev' \
'perl' \
;
FROM alpine:latest
COPY . /sslh
COPY --from=build /sslh/sslh-select /sslh
RUN ./configure && make sslh-select && strip sslh-select
RUN apk --no-cache add libconfig pcre
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
RUN apk add --no-cache \
'libconfig' \
'pcre2' \
'iptables' \
'ip6tables' \
'libcap' \
&& \
adduser -s '/bin/sh' -S -D sslh && \
setcap cap_net_bind_service,cap_net_raw+ep /usr/local/bin/sslh
COPY "./container-entrypoint.sh" "/init"
ENTRYPOINT [ "/init" ]
# required for updating iptables
USER root:root
ENTRYPOINT [ "/sslh", "--foreground"]
+35 -45
View File
@@ -3,14 +3,12 @@ VERSION=$(shell ./genver.sh -r)
# Configuration -- you probably need to `make clean` if you
# change any of these
# 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
@@ -21,28 +19,23 @@ MAN=sslh.8.gz # man page name
# End of configuration -- the rest should take care of
# itself
ifneq ($(strip $(ENABLE_SANITIZER)),)
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
endif
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC ?= gcc
AR ?= ar
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV)
LIBS=-lm -lpcre2-8 @LIBS@
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
OBJS_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)
EV_OBJS=processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o $(OBJS_A)
LIBS=-lm -lpcre2-8
OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o udp-listener.o collection.o gap.o
CONDITIONAL_TARGETS=
ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap
CPPFLAGS+=-DLIBWRAP
endif
ifneq ($(strip $(ENABLE_REGEX)),)
CPPFLAGS+=-DENABLE_REGEX
endif
@@ -52,65 +45,62 @@ 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 $(USELIBEV)),)
CONDITIONAL_TARGETS+=sslh-ev
ifneq ($(strip $(USELIBBSD)),)
LIBS:=$(LIBS) -lbsd
CPPFLAGS+=-DLIBBSD
endif
all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
%.o: %.c %.h version.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
$(OBJS_A): $(OBJS)
$(AR) rcs $(OBJS_A) $(OBJS)
.c.o: *.h version.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
version.h:
./genver.sh >version.h
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
sslh: sslh-fork sslh-select
c2s:
conf2struct sslhconf.cfg
conf2struct echosrv.cfg
$(OBJS): version.h common.h collection.h sslh-conf.h gap.h
sslh-conf.c sslh-conf.h: sslhconf.cfg
$(warning "sslhconf.cfg is more recent than sslh-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
conf2struct sslhconf.cfg
sslh-fork: version.h Makefile $(FORK_OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
#strip sslh-fork
sslh-select: version.h $(SELECT_OBJS) Makefile
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select $(SELECT_OBJS) $(LIBS)
sslh-ev: version.h $(EV_OBJS) Makefile
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-ev $(EV_OBJS) $(LIBS) -lev
sslh-select: version.h $(OBJS) sslh-select.o Makefile
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
systemd-sslh-generator: systemd-sslh-generator.o
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
echosrv-conf.c echosrv-conf.h: echosrv.cfg
$(warning "echosrv.cfg is more recent than echosrv-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
conf2struct echosrv.cfg
echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
landlock.o: config.h
$(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
# Create release: export clean tree and tag current
# configuration
release:
git archive $(VERSION) --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
# Build docker image
@@ -123,7 +113,7 @@ docker-clean:
yes | docker image prune
# generic install: install binary and man page
install: sslh-fork $(MAN)
install: sslh $(MAN)
mkdir -p $(DESTDIR)/$(BINDIR)
mkdir -p $(DESTDIR)/$(MANDIR)
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
@@ -143,7 +133,7 @@ distclean: clean
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
clean:
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags --globals -T *.[ch]
+18 -128
View File
@@ -11,7 +11,7 @@ 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 connect to SSH from
inside a corporate firewall, which almost never block port
443) while still serving HTTPS on that port.
443) while still serving HTTPS on that port.
Hence `sslh` acts as a protocol demultiplexer, or a
switchboard. With the SNI and ALPN probe, it makes a good
@@ -20,10 +20,9 @@ address.
`sslh` has the bells and whistles expected from a mature
daemon: privilege and capabilities dropping, inetd support,
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.
systemd support, transparent proxying, chroot, logging,
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
model, and more.
Install
=======
@@ -36,40 +35,6 @@ 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.
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 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).
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
@@ -79,120 +44,45 @@ How to use
---
Build docker image
make docker
```bash
docker run \
--cap-add CAP_NET_RAW \
--cap-add CAP_NET_BIND_SERVICE \
docker container run \
--rm \
-it \
ghcr.io/yrutschle/sslh:latest \
--foreground \
--listen=0.0.0.0:443 \
--ssh=hostname:22 \
--tls=hostname:443
--tlshostname:443 \
sslh:latest
```
docker-compose example
```yaml
```
---
version: "3"
services:
sslh:
image: ghcr.io/yrutschle/sslh:latest
image: sslh:latest
hostname: sslh
ports:
- 443:443
command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
- 443:443/tcp
command: --listen=0.0.0.0:443 --tlshostname:443 --openvpn=openvpn:1194
depends_on:
- nginx
- openvpn
nginx:
image: nginx
openvpn:
image: openvpn
```
Transparent mode 1: using sslh container for networking
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
container_name: sslh
environment:
- TZ=${TZ}
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
sysctls:
- net.ipv4.conf.default.route_localnet=1
- net.ipv4.conf.all.route_localnet=1
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
ports:
- 443:443 #sslh
- 80:80 #nginx
- 8443:8443 #nginx
- 1194:1194 #openvpn
extra_hosts:
- localbox:host-gateway
restart: unless-stopped
nginx:
image: nginx:latest
.....
network_mode: service:sslh #set nginx container to use sslh networking.
# ^^^ This is required. This makes nginx reachable by sslh via localhost
openvpn:
image: openvpn:latest
.....
network_mode: service:sslh #set openvpn container to use sslh networking
```
Transparent mode 2: using host networking
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
container_name: sslh
environment:
- TZ=${TZ}
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
# must be set manually
#sysctls:
# - net.ipv4.conf.default.route_localnet=1
# - net.ipv4.conf.all.route_localnet=1
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
network_mode: host
restart: unless-stopped
nginx:
image: nginx:latest
.....
ports:
- 8443:8443 # bind to docker host on port 8443
hostname: nginx
openvpn:
image: openvpn:latest
.....
ports:
- 1194:1194 # bind to docker host on port 1194
hostname: openvpn
```
Comments? Questions?
+13 -13
View File
@@ -2876,9 +2876,9 @@ static void arg_file_resetfn(struct arg_file* parent) {
static const char* arg_basename(const char* filename) {
const char *result = NULL, *result1, *result2;
/* Find the last occurrence of other file separator character. */
/* Two alternative file separator chars are supported as legal */
/* file separators but not both together in the same filename. */
/* Find the last occurrence of eother file separator character. */
/* Two alternative file separator chars are supported as legal */
/* file separators but not both together in the same filename. */
result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL);
result2 = (filename ? strrchr(filename, FILESEPARATOR2) : NULL);
@@ -2927,7 +2927,7 @@ static int arg_file_scanfn(struct arg_file* parent, const char* argval) {
} else if (!argval) {
/* a valid argument with no argument value was given. */
/* This happens when an optional argument value was invoked. */
/* leave parent argument value unaltered but still count the argument. */
/* leave parent arguiment value unaltered but still count the argument. */
parent->count++;
} else {
parent->filename[parent->count] = argval;
@@ -3173,7 +3173,7 @@ static int arg_int_scanfn(struct arg_int* parent, const char* argval) {
} else if (!argval) {
/* a valid argument with no argument value was given. */
/* This happens when an optional argument value was invoked. */
/* leave parent argument value unaltered but still count the argument. */
/* leave parent arguiment value unaltered but still count the argument. */
parent->count++;
} else {
long int val;
@@ -3813,8 +3813,8 @@ static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_O
#endif
#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */
#define OP_OR (MAX_CHAR + 2)
#define OP_EXPR (MAX_CHAR + 3) /* parenthesis () */
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parenthesis (?:) */
#define OP_EXPR (MAX_CHAR + 3) /* parentesis () */
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */
#define OP_DOT (MAX_CHAR + 5)
#define OP_CLASS (MAX_CHAR + 6)
#define OP_CCLASS (MAX_CHAR + 7)
@@ -5313,7 +5313,7 @@ static void arg_parse_untagged(int argc, char** argv, struct arg_hdr** table, st
}
}
/* if a tentative error still remains at this point then register it as a proper error */
/* if a tenative error still remains at this point then register it as a proper error */
if (errorlast) {
arg_register_error(endtable, parentlast, errorlast, optarglast);
optind++;
@@ -5384,7 +5384,7 @@ int arg_parse(int argc, char** argv, void** argtable) {
/*
Fill in the local copy of argv[]. We need a local copy
because getopt rearranges argv[] which adversely affects
subsequent parsing attempts.
susbsequent parsing attempts.
*/
for (i = 0; i < argc; i++)
argvcopy[i] = argv[i];
@@ -5451,7 +5451,7 @@ static void arg_cat_option(char* dest, size_t ndest, const char* shortopts, cons
if (shortopts) {
char option[3];
/* note: option array[] is initialized dynamically here to satisfy */
/* note: option array[] is initialiazed dynamically here to satisfy */
/* a deficiency in the watcom compiler wrt static array initializers. */
option[0] = '-';
option[1] = shortopts[0];
@@ -5509,7 +5509,7 @@ static void arg_cat_optionv(char* dest, size_t ndest, const char* shortopts, con
/* "-a|-b|-c" */
char shortopt[3];
/* note: shortopt array[] is initialized dynamically here to satisfy */
/* note: shortopt array[] is initialiazed dynamically here to satisfy */
/* a deficiency in the watcom compiler wrt static array initializers. */
shortopt[0] = '-';
shortopt[1] = *c;
@@ -5881,7 +5881,7 @@ static void arg_print_formatted_ds(arg_dstr_t ds, const unsigned lmargin, const
* Prints the glossary in strict GNU format.
* Differences to arg_print_glossary() are:
* - wraps lines after 80 chars
* - indents lines without shortopts
* - indents lines without shortops
* - does not accept formatstrings
*
* Contributed by Uli Fouquet
@@ -5956,7 +5956,7 @@ int arg_nullcheck(void** argtable) {
* that entry were still allocated ok. Those subsequent allocations will not be
* deallocated by arg_free().
* Despite the unlikeliness of the problem occurring, and the even unlikelier event
* that it has any deleterious effect, it is fixed regardless by replacing arg_free()
* that it has any deliterious effect, it is fixed regardless by replacing arg_free()
* with the newer arg_freetable() function.
* We still keep arg_free() for backwards compatibility.
*/
+2 -2
View File
@@ -87,7 +87,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
* that particular arg_xxx arguments, performing post-parse checks, and
* reporting errors.
* These functions are private to the individual arg_xxx source code
* and are the pointer to them are initialised by that arg_xxx struct's
* and are the pointer to them are initiliased by that arg_xxx struct's
* constructor function. The user could alter them after construction
* if desired, but the original intention is for them to be set by the
* constructor and left unaltered.
@@ -95,7 +95,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
typedef struct arg_hdr {
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
const char* shortopts; /* String defining the short options */
const char* longopts; /* String defining the long options */
const char* longopts; /* String defiing the long options */
const char* datatype; /* Description of the argument data type */
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
int mincount; /* Minimum number of occurences of this option accepted */
+10 -14
View File
@@ -1,33 +1,29 @@
# This is a basic configuration file that should provide
# sensible values for "standard" setup.
# You will find extensive examples with explanations in
# example.cfg
verbose: 0;
foreground: false;
inetd: false;
numeric: false;
transparent: false;
timeout: 2;
user: "nobody";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# 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.
# Change hostname with your external address name.
listen:
(
{ host: "0.0.0.0"; port: "443"; },
{ host: "[::]"; port: "443"; }
{ host: "thelonious"; port: "443"; }
);
# Change to the protocols you want to forward to. The
# defaults here are sensible for services running on
# localhost
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"; }
);
+171 -286
View File
@@ -4,6 +4,7 @@
* No code here should assume whether sockets are blocking or not.
**/
#define SYSLOG_NAMES
#define _GNU_SOURCE
#include <stddef.h>
#include <stdarg.h>
@@ -12,18 +13,10 @@
#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"
#if HAVE_LIBCAP
#include <sys/capability.h>
#include <sys/prctl.h>
#endif
/* Added to make the code compilable under CYGWIN
* */
@@ -37,7 +30,7 @@
#include <systemd/sd-daemon.h>
#endif
#ifdef HAVE_LIBBSD
#ifdef LIBBSD
#include <bsd/unistd.h>
#endif
@@ -49,7 +42,9 @@ struct sslhcfg_item cfg;
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
#ifdef HAVE_LIBWRAP
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
#ifdef LIBWRAP
#include <tcpd.h>
int allow_severity =0, deny_severity = 0;
#endif
@@ -65,7 +60,7 @@ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall
char buf[NI_MAXHOST];
if (res == -1) {
print_message(msg_system_error, "%s:%s: %s\n",
fprintf(stderr, "%s:%s: %s\n",
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
@@ -82,7 +77,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
#ifdef SYSTEMD
sd = sd_listen_fds(0);
if (sd < 0) {
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd));
exit(1);
}
if (sd > 0) {
@@ -117,7 +112,7 @@ int make_listen_tfo(int s)
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
}
/* Starts listening on a single address
/* Starts listening on a single address
* Returns a socket filehandle, or dies with message in case of major error */
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
{
@@ -162,76 +157,17 @@ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
return sockfd;
}
/* 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]));
(*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;
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
cfg->keepalive ? "keepalive" : "",
cfg->is_udp ? "udp" : "");
}
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]));
(*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;
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[])
{
int i;
int num_addr = 0, sd_socks = 0;
struct addrinfo *addr, *start_addr;
char buf[NI_MAXHOST];
int i, res;
int num_addr = 0, keepalive = 0, udp = 0;
int sd_socks = 0;
sd_socks = get_fd_sockets(sockfd);
@@ -241,14 +177,26 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
*sockfd = NULL;
print_message(msg_config, "Listening to:\n");
if (cfg.verbose) fprintf(stderr, "Listening to:\n");
for (i = 0; i < cfg.listen_len; i++) {
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]);
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;
if (cfg.verbose)
fprintf(stderr, "%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" : "");
}
freeaddrinfo(start_addr);
}
return num_addr;
@@ -298,17 +246,12 @@ int is_same_machine(struct addrinfo* from)
/* Transparent proxying: bind the peer address of fd to the peer address of
* fd_from */
#ifndef IP_TRANSPARENT
#define IP_TRANSPARENT 19
#endif
#ifndef IP_BIND_ADDRESS_NO_PORT
#define IP_BIND_ADDRESS_NO_PORT 24
#endif
#define IP_TRANSPARENT 19
int bind_peer(int fd, int fd_from)
{
struct addrinfo from;
struct sockaddr_storage ss;
int res, enable = 1, disable = 0;
int res, trans = 1;
memset(&from, 0, sizeof(from));
from.ai_addr = (struct sockaddr*)&ss;
@@ -322,55 +265,23 @@ int bind_peer(int fd, int fd_from)
/* if the destination is the same machine, there's no need to do bind */
if (is_same_machine(&from))
return 0;
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &enable, sizeof(enable));
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
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, &enable, sizeof(enable));
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans));
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res);
#ifdef IPV6_BINDANY
} else { /* IPv6 */
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &enable, sizeof(enable));
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans));
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res);
#endif /* IPV6_BINDANY */
}
#endif /* IP_TRANSPARENT / IP_BINDANY */
res = bind(fd, from.ai_addr, from.ai_addrlen);
if (res == -1) {
if (errno != EADDRINUSE) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__,
"bind", errno, strerror(errno));
return res;
}
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.
*/
}
CHECK_RES_RETURN(res, "bind", res);
return 0;
}
@@ -392,8 +303,11 @@ int set_nonblock(int fd)
}
/* Connects to INET/INET6 domain sockets and return a fd */
static int connect_inet(struct connection *cnx, int fd_from, connect_blocking blocking)
/* 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)
{
struct addrinfo *a, from;
struct sockaddr_storage ss;
@@ -408,23 +322,19 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername", res);
if (cnx->proto->resolve_on_forward) {
resolve_split_name(&(cnx->proto->saddr), cnx->proto->host,
cnx->proto->port);
}
for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* 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))
/* When transparent, make sure both connections use the same address family */
if (transparent && a->ai_family != from.ai_addr->sa_family)
continue;
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
if (cfg.verbose)
fprintf(stderr, "connecting to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a),
a->ai_addr->sa_family, a->ai_addrlen);
/* XXX Needs to match ai_family from fd_from when being transparent! */
fd = socket(a->ai_family, SOCK_STREAM, 0);
if (fd == -1) {
print_message(msg_connections_error, "forward to %s failed:socket: %s\n",
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
cnx->proto->name, strerror(errno));
} else {
one = 1;
@@ -437,7 +347,6 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
if (transparent) {
res = bind_peer(fd, fd_from);
if (res == -1) close(fd);
CHECK_RES_RETURN(res, "bind_peer", res);
}
res = connect(fd, a->ai_addr, a->ai_addrlen);
@@ -445,7 +354,7 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
/* EINPROGRESS indicates it might take time. If it eventually
* fails, it'll be caught as a failed read */
if ((res == -1) && (errno != EINPROGRESS)) {
print_message(msg_connections_error, "forward to %s failed:connect: %s\n",
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
cnx->proto->name, strerror(errno));
close(fd);
continue; /* Try the next address */
@@ -460,105 +369,40 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
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) {
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 */
}
}
/* Store some data to write to the queue later */
int defer_write(struct queue *q, void* data, ssize_t data_size)
int defer_write(struct queue *q, void* data, int data_size)
{
char *p;
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
if (cfg.verbose)
fprintf(stderr, "**** writing deferred on fd %d\n", q->fd);
print_message(msg_fd, "writing deferred on fd %d\n", q->fd);
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
CHECK_ALLOC(p, "realloc");
q->begin_deferred_data = p;
q->deferred_data = p + data_offset;
p += data_offset + q->deferred_data_size;
q->deferred_data_size += (int)data_size;
q->deferred_data_size += data_size;
memcpy(p, data, data_size);
return 0;
}
/* 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)
{
ssize_t n;
int n;
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
if (cfg.verbose)
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
n = write(q->fd, q->deferred_data, q->deferred_data_size);
if (n == -1)
return (int)n;
return n;
if (n == q->deferred_data_size) {
/* All has been written -- release the memory */
@@ -569,10 +413,10 @@ int flush_deferred(struct queue *q)
} else {
/* There is data left */
q->deferred_data += n;
q->deferred_data_size -= (int)n;
q->deferred_data_size -= n;
}
return (int)n;
return n;
}
@@ -586,12 +430,11 @@ void init_cnx(struct connection *cnx)
void dump_connection(struct connection *cnx)
{
print_message(msg_int_error, "type: %s\n", cnx->type == SOCK_DGRAM ? "UDP" : "TCP");
print_message(msg_int_error, "state: %d\n", cnx->state);
print_message(msg_int_error, "0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
hexdump(msg_int_error, cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
print_message(msg_int_error, "1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
hexdump(msg_int_error, cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
printf("state: %d\n", cnx->state);
printf("0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
hexdump(cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
printf("1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
hexdump(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
}
@@ -607,8 +450,7 @@ void dump_connection(struct connection *cnx)
int fd2fd(struct queue *target_q, struct queue *from_q)
{
char buffer[BUFSIZ];
int target, from;
ssize_t size_r, size_w;
int target, from, size_r, size_w;
target = target_q->fd;
from = from_q->fd;
@@ -617,10 +459,11 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
if (size_r == -1) {
switch (errno) {
case EAGAIN:
if (cfg.verbose)
fprintf(stderr, "reading 0 from %d\n", from);
return FD_NODATA;
case ECONNRESET:
case ENOTSOCK:
case EPIPE:
return FD_CNXCLOSED;
}
@@ -653,7 +496,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
return (int)size_w;
return size_w;
}
/* returns a string that prints the IP and port of the sockaddr */
@@ -662,16 +505,13 @@ 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),
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
if (res) {
print_message(msg_system_error, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
/* Name resolution failed: do it numerically instead */
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
@@ -679,7 +519,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
NI_NUMERICHOST | NI_NUMERICSERV);
/* should not fail but... */
if (res) {
print_message(msg_system_error, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
log_message(LOG_ERR, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
strcpy(host, "?");
strcpy(serv, "?");
}
@@ -691,8 +531,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
}
/* Turns a hostname and port (or service) into a list of struct addrinfo
* On success, returns 0
* On failure, returns -1 or one of getaddrinfo() codes
* returns 0 on success, -1 otherwise and logs error
*/
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
{
@@ -709,7 +548,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
if (host[0] == '[') {
end = strrchr(host, ']');
if (!end) {
print_message(msg_config_error, "%s: no closing bracket in IPv6 address?\n", host);
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
return -1;
}
host++; /* skip first bracket */
@@ -718,7 +557,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
res = getaddrinfo(host, serv, &hint, out);
if (res)
print_message(msg_system_error, "resolve_split_name: %s `%s:%s'\n", gai_strerror(res), host, serv);
log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv);
return res;
}
@@ -734,7 +573,7 @@ void resolve_name(struct addrinfo **out, char* fullname)
/* Find port */
char *sep = strrchr(fullname, ':');
if (!sep) { /* No separator: parameter is just a port */
print_message(msg_config_error, "%s: names must be fully specified as hostname:port\n", fullname);
fprintf(stderr, "%s: names must be fully specified as hostname:port\n", fullname);
exit(1);
}
serv = sep+1;
@@ -744,13 +583,30 @@ void resolve_name(struct addrinfo **out, char* fullname)
res = resolve_split_name(out, host, serv);
if (res) {
print_message(msg_config_error, "%s `%s'\n", gai_strerror(res), fullname);
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
if (res == EAI_SERVICE)
print_message(msg_config_error, "(Check you have specified all ports)\n");
fprintf(stderr, "(Check you have specified all ports)\n");
exit(4);
}
}
/* Log to syslog or stderr if foreground */
void log_message(int type, const char* msg, ...)
{
va_list ap;
va_start(ap, msg);
if (cfg.foreground)
vfprintf(stderr, msg, ap);
va_end(ap);
if (do_syslog) {
va_start(ap, msg);
vsyslog(type, msg, ap);
va_end(ap);
}
}
/* Fills a connection description; returns 0 on failure */
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx)
@@ -785,10 +641,34 @@ int get_connection_desc(struct connection_desc* desc, const struct connection *c
return 1;
}
/* syslogs who connected to where
* desc: string description of the connection. if NULL, log_connection will
* manage on its own
* cnx: connection descriptor
* */
void log_connection(struct connection_desc* desc, const struct connection *cnx)
{
struct connection_desc d;
if (cnx->proto->log_level < 1)
return;
if (!desc) {
desc = &d;
get_connection_desc(desc, cnx);
}
log_message(LOG_INFO, "%s:connection from %s to %s forwarded from %s to %s\n",
cnx->proto->name,
desc->peer,
desc->service,
desc->local,
desc->target);
}
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
{
#ifdef HAVE_LIBBSD
#ifdef LIBBSD
struct connection_desc d;
if (!desc) {
@@ -813,7 +693,7 @@ void set_proctitle_shovel(struct connection_desc* desc, const struct connection
*/
int check_access_rights(int in_socket, const char* service)
{
#ifdef HAVE_LIBWRAP
#ifdef LIBWRAP
union {
struct sockaddr saddr;
struct sockaddr_storage ss;
@@ -828,7 +708,8 @@ int check_access_rights(int in_socket, const char* service)
/* extract peer address */
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
if (res) {
print_message(msg_system_error, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
if (cfg.verbose)
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
strcpy(addr_str, STRING_UNKNOWN);
}
/* extract peer name */
@@ -836,12 +717,15 @@ int check_access_rights(int in_socket, const char* service)
if (!cfg.numeric) {
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
if (res) {
print_message(msg_system_error, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
if (cfg.verbose)
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
}
}
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
if (cfg.verbose)
fprintf(stderr, "access denied\n");
log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str);
close(in_socket);
return -1;
}
@@ -876,10 +760,39 @@ void setup_signals(void)
}
/* Open syslog connection with appropriate banner;
* banner is made up of basename(bin_name)+"[pid]" */
void setup_syslog(const char* bin_name) {
char *name1, *name2;
int res, fn;
if (!strcmp(cfg.syslog_facility, "none")) {
do_syslog = 0;
return;
}
name1 = strdup(bin_name);
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
break;
if (facilitynames[fn].c_val == -1) {
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
exit(1);
}
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
free(name1);
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
log_message(LOG_INFO, "%s %s started\n", server_type, VERSION);
}
/* Ask OS to keep capabilities over a setuid(nonzero) */
void set_keepcaps(int val) {
#if HAVE_LIBCAP
#ifdef LIBCAP
int res;
res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0);
if (res) {
@@ -892,7 +805,7 @@ void set_keepcaps(int val) {
/* Returns true if anything requires transparent proxying. */
static int use_transparent(void)
{
#if HAVE_LIBCAP
#ifdef LIBCAP
if (cfg.transparent)
return 1;
@@ -908,7 +821,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) {
#if HAVE_LIBCAP
#ifdef LIBCAP
int res;
cap_t caps;
cap_value_t cap_list[10];
@@ -953,14 +866,16 @@ void drop_privileges(const char* user_name, const char* chroot_path)
if (user_name) {
pw = getpwnam(user_name);
if (!pw) {
print_message(msg_config_error, "%s: not found\n", user_name);
fprintf(stderr, "%s: not found\n", user_name);
exit(2);
}
print_message(msg_config, "turning into %s\n", user_name);
if (cfg.verbose)
fprintf(stderr, "turning into %s\n", user_name);
}
if (chroot_path) {
print_message(msg_config, "chrooting into %s\n", chroot_path);
if (cfg.verbose)
fprintf(stderr, "chrooting into %s\n", chroot_path);
res = chroot(chroot_path);
CHECK_RES_DIE(res, "chroot");
@@ -986,48 +901,18 @@ 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)
{
int fd;
char pidbuf[32];
size_t len, written = 0;
ssize_t res;
FILE *f;
/* 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;
f = fopen(pidfile, "w");
if (!f) {
perror(pidfile);
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;
}
/* 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));
}
fprintf(f, "%d\n", getpid());
fclose(f);
}
+10 -31
View File
@@ -5,7 +5,6 @@
* enough for the macros to adapt (http://support.microsoft.com/kb/111855)
*/
#ifdef __CYGWIN__
#undef FD_SETSIZE
#define FD_SETSIZE 4096
#endif
@@ -34,11 +33,6 @@
#include <sys/capability.h>
#endif
#ifdef __APPLE__
#include <AvailabilityMacros.h>
#endif
#include "config.h"
#include "version.h"
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
@@ -46,20 +40,20 @@
#define CHECK_RES_DIE(res, str) \
if (res == -1) { \
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
perror(str); \
exit(1); \
}
#define CHECK_RES_RETURN(res, str, ret) \
if (res == -1) { \
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
log_message(LOG_CRIT, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
return ret; \
}
#define CHECK_ALLOC(a, str) \
if (!a) { \
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
perror(str); \
exit(1); \
}
@@ -89,6 +83,9 @@ enum connection_state {
ST_SHOVELING /* Connexion is established */
};
/* this is used to pass protocols through the command-line parameter parsing */
#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
/* A 'queue' is composed of a file descriptor (which can be read from or
* written to), and a queue for deferred write data */
struct queue {
@@ -98,12 +95,6 @@ struct queue {
int deferred_data_size;
};
/* Double linked list for timeout management */
typedef struct {
struct connection* head;
struct connection* tail;
} dl_list;
struct connection {
int type; /* SOCK_DGRAM | SOCK_STREAM */
struct sslhcfg_protocols_item* proto; /* Where to connect to */
@@ -118,16 +109,13 @@ struct connection {
struct queue q[2];
/* SOCK_DGRAM */
struct sockaddr_storage client_addr; /* Contains the remote client address */
struct sockaddr client_addr; /* Contains the remote client address */
socklen_t addrlen;
int local_endpoint; /* Contains the local address */
time_t last_active;
/* double linked list of timeouts */
struct connection *timeout_prev, *timeout_next;
/* We need one local socket for each target server, so we know where to
* forward server responses */
int target_sock;
@@ -137,7 +125,6 @@ struct connection {
struct listen_endpoint {
int socketfd; /* file descriptor of listening socket */
int type; /* SOCK_DGRAM | SOCK_STREAM */
int family; /* AF_INET | AF_UNIX */
};
#define FD_CNXCLOSED 0
@@ -160,7 +147,7 @@ typedef enum {
/* common.c */
void init_cnx(struct connection *cnx);
int set_nonblock(int fd);
void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
int 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);
@@ -173,30 +160,22 @@ void setup_syslog(const char* bin_name);
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 log_message(int type, const char* msg, ...);
void dump_connection(struct connection *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, ssize_t data_size);
int defer_write_before(struct queue *q, void* data, ssize_t data_size);
int defer_write(struct queue *q, void* data, int 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);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
/* landlock.c */
void setup_landlock(void);
#endif
-21
View File
@@ -1,21 +0,0 @@
#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
#endif
Vendored
-4655
View File
File diff suppressed because it is too large. Load diff
-20
View File
@@ -1,20 +0,0 @@
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" ], [])
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
-97
View File
@@ -1,97 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: GPL2-or-later
#
# Copyright (C) 2023 Olliver Schinagl <oliver@schinagl.nl>
#
# A beginning user should be able to docker run image bash (or sh) without
# needing to learn about --entrypoint
# https://github.com/docker-library/official-images#consistency
set -eu
bin='sslh'
# run command if it is not starting with a "-" and is an executable in PATH
if [ "${#}" -le 0 ] || \
[ "${1#-}" != "${1}" ] || \
[ -d "${1}" ] || \
! command -v "${1}" > '/dev/null' 2>&1; then
entrypoint='true'
fi
unconfigure_iptables() {
echo "Received SIG TERM/INT/KILL. Removing iptables / routing changes"
set +e # Don't exit if got error
set -x
iptables -t raw -D PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -D POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
iptables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
iptables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip rule del fwmark 0x1 lookup 100
ip route del local 0.0.0.0/0 dev lo table 100
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
ip6tables -t raw -D PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -D POSTROUTING ! -o lo -s ::1/128 -j DROP
ip6tables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
ip6tables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip -6 rule del fwmark 0x1 lookup 100
ip -6 route del local ::/0 dev lo table 100
fi
set -e
set +x
}
configure_iptables() {
echo "Configuring iptables and routing..."
set +e # Don't exit if got error
set -x
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
fi
set -e
set +x
}
for _args in "${@}" ; do
if [ "${_args:-}" = '--transparent' ] ; then
echo '--transparent flag is set'
configure_iptables
trap unconfigure_iptables TERM INT KILL
break
fi
done
# Drop privileges and run as sslh user
sslh_cmd="${entrypoint:+${bin}} ${@}"
echo "Executing with user 'sslh': ${sslh_cmd}"
exec su - sslh -c "${sslh_cmd}" &
wait "${!}"
exit 0
@@ -1,283 +0,0 @@
# 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.<br>
Unfortunately you will see a lot of errors popping out: **Address already in use**<br>
[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.
![Dataflow-Of-Daisy-Chain](./detailed-ip-transparency.png)
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.<br>
### 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" :-(<br>
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 reommendation however is: Avoid self signed certs, if you are not really sure, what you are doing.
+10 -11
View File
@@ -7,25 +7,24 @@ doesn't work, report how what was suggested here went.
It's also worth reading [how to ask
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
before posting on the mailing list or opening an issue in
GitHub.
Github.
Getting more info
=================
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:
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:
```
sslh -v 3 -f -F myconfig.cfg
sslh -v 1 -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
=====================================================
+68 -119
View File
@@ -1,13 +1,3 @@
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 graciously produced by
nono303 on his [repository](https://github.com/nono303/sslh).
Compile and install
===================
@@ -16,156 +6,115 @@ 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 `libconfig8-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`.
Presence of libwrap is checked by the configure script.
For Debian, this is contained in packages
`libwrap0-dev`. You
can compile with or without it using USELIBWRAP 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.
* [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`.
Presence of libcap is checked by the configure script.
* [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
* [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.
* 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.
* 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.
* [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).
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.
There are a couple of configuration options at the beginning of the Makefile:
After this, the Makefile should work:
* `# 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).
make install
* `USELIBCONFIG` compiles support for the configuration file.
You will need `libconfig` headers to compile (`libconfig8-dev` in Debian).
There are a couple of configuration options at the beginning
of the Makefile:
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
* `USELIBWRAP` compiles support for host access control (see
`hosts_access(3)`), you will need `libwrap` headers and
library to compile (`libwrap0-dev` in Debian).
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
* `USELIBCONFIG` compiles support for the configuration
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `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.
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.
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`.
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
* `USELIBBSD` compiles support for updating the process name (as shown
by `ps`).
Binaries
--------
The Makefile produces three different executables:
`sslh-fork`, `sslh-select` and `sslh-ev`:
The Makefile produces two different executables: `sslh-fork`
and `sslh-select`:
* `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 is more recent and less tested, but 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 thousand ssh
connections, and another few thousand ssl connections),
`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.
If you have a very large site (tens of thousands of connections),
you'll need a vapourware version that would use libevent or
something like that.
Installation
------------
* In general:
```sh
./configure
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
```
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
* For Debian:
```sh
cp scripts/etc.init.d.sslh /etc/init.d/sslh
```
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
start automatically at boot-up, e.g. under Debian:
```sh
update-rc.d sslh defaults
```
update-rc.d sslh defaults
+1 -1
View File
@@ -33,7 +33,7 @@ with launchctl or simply reboot.
<string>0.0.0.0:443</string>
<string>--ssh</string>
<string>localhost:22</string>
<string>--tls</string>
<string>--ssl</string>
<string>localhost:443</string>
</array>
<key>QueueDirectories</key>
-72
View File
@@ -1,72 +0,0 @@
It is possible to run `sslh` on Windows. The `fork` model
should be avoided as it is very inefficient on Windows, but
`sslh-select` and `sslh-ev` both work with good performance
(prefer the latter, however).
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/ or git binaries.
Copy the 3 files
GO.cmd
wget.exe
compile.sh
to C root folder, then execute **GO.cmd** with administrative rights.
with **GO.cmd**
@ECHO OFF
CD /D "%~dp0"
NET SESSION >NUL 2>&1
IF %ERRORLEVEL% NEQ 0 (
ECHO Permission denied. This script must be run as an Administrator.
ECHO:
GOTO FIN
) ELSE (
ECHO Running as Administrator.
TIMEOUT /T 2 >NUL
wget --no-check-certificate https://www.cygwin.com/setup-x86_64.exe
IF NOT EXIST setup-x86_64.exe GOTO FIN
MKDIR C:\Z
setup-x86_64.exe -l C:\Z -s ftp://ftp.funet.fi/pub/mirrors/sourceware.org/pub/cygwin/ -q -P make -P git -P gcc-g++ -P autoconf -P automake -P libtool -P libpcre-devel -P libpcre2-devel -P bison -P libev-devel
MKDIR C:\cygwin64\home\user
COPY COMPILE.SH C:\cygwin64\home\user
START C:\cygwin64\bin\mintty.exe /bin/bash --login -i ~/compile.sh
START EXPLORER C:\zzSORTIE
)
:FIN
PAUSE
EXIT
and **compile.sh**
# SAVE FILE TO UNIX FORMAT
# COPY IT IN C cygwin64 home user
git clone https://github.com/hyperrealm/libconfig.git
cd libconfig
autoreconf -fi
./configure
make
make install
cd ..
cp /usr/local/lib/libconfig.* /usr/lib
git clone https://github.com/yrutschle/sslh.git
cd sslh
make
cd ..
mkdir /cygdrive/c/zzSORTIE
cp ./sslh/sslh*.exe /cygdrive/c/zzSORTIE
cp /usr/local/bin/cygconfig-11.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygwin1.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygpcreposix-0.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygpcre-1.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygev-4.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygpcre2-8-0.dll /cygdrive/c/zzSORTIE
This method was contributed by lerenardo on [github](https://github.com/yrutschle/sslh/issues/196#issuecomment-1692805639).
+162 -17
View File
@@ -65,21 +65,21 @@ Configuration goes like this on the server side, using `stunnel3`:
* `-f` for foreground/debugging
* `-p` for specifying the key and certificate
* `-d` for specifying which interface and port
we're listening to for incoming connections
we're listening to for incoming connexions
* `-l` summons `sslh` in inetd mode.
* sslh options:
* `-i` for inetd mode
* `--http` to forward HTTP connections to port 80,
and SSH connections to port 22.
* `--http` to forward HTTP connexions to port 80,
and SSH connexions to port 22.
Capabilities support
--------------------
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.
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.
Alternatively, you may use filesystem capabilities instead
of starting sslh as root and asking it to drop privileges.
@@ -92,18 +92,165 @@ to the executable:
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
Then you can run sslh-select as an unprivileged user, e.g.:
Then you can run sslh-select as an unpriviledged user, e.g.:
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
Transparent proxy support
-------------------------
Transparent proxying is described in its own
[document](tproxy.md).
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).
It might be easier to configure `sslh` to use Proxyprotocol
if the backend server supports it.
You can refer to Sean Warn'ѕ [tutorial](tproxy.md) for a
different set-up which enables transparent proxying between
two different machines. The following may only work if
`sslh` and the final servers are on the same machine.
Note that getting this to work 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
required network configuration somewhat. If it doesn't work,
it's almost certain that the problem is not linked to `sslh`
but to the network setup that surrounds it.
Linux:
`sslh` needs extended rights to perform this: you'll need to
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
or run it as root (but don't do that).
The firewalling tables also need to be adjusted as follows.
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
this scheme -- let me know if you manage that:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
Tranparent proxying with IPv6 is similarly set up as follows:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
# Not sure if this is needed for ipv6 though
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
Explanation:
To be able to use `localhost` as destination in your sslh config along with transparent proxying
you have to allow routing of loopback addresses as done above.
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
showing how packets will traverse the iptables chains.
Note:
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
These rules will allow you to connect directly to ssh on port
22 (or to any other service behind sslh) as well as through sslh on port 443.
Also remember that iptables configuration and ip routes and
rules won't be necessarily persisted after you reboot. Make
sure to save them properly. For example in CentOS7, you would
do `iptables-save > /etc/sysconfig/iptables`, and add both
`ip` commands to your `/etc/rc.local`.
FreeBSD:
Given you have no firewall defined yet, you can use the following configuration
to have ipfw properly redirect traffic back to sslh
/etc/rc.conf
firewall_enable="YES"
firewall_type="open"
firewall_logif="YES"
firewall_coscripts="/etc/ipfw/sslh.rules"
/etc/ipfw/sslh.rules
#! /bin/sh
# ssl
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
# ssh
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
# xmpp
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
# openvpn (running on other internal system)
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
General notes:
This will only work if `sslh` does not use any loopback
addresses (no `127.0.0.1` or `localhost`), you'll need to use
explicit IP addresses (or names):
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
Systemd Socket Activation
-------------------------
@@ -166,7 +313,7 @@ This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overridden by a /etc/systemd/system/sslh.socket file.
overriden by a /etc/systemd/system/sslh.socket file.
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
@@ -188,15 +335,13 @@ UDP
---
`sslh` can perform demultiplexing on UDP packets as well.
This does not work with `sslh-fork` (it is not possible to
This only works with `sslh-select` (it is not possible to
support UDP with a forking model). Specify a listening
address and target protocols with `is_udp: true`. `sslh`
will wait for incoming UDP packets, run the probes in the
usual fashion, and forward packets to the appropriate
target. `sslh` will then remember the association between
remote host to target server for 60 seconds by default,
which can be overridden with `udp_timeout`. This allows to
which can be overriden with `udp_timeout`. This allows to
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`.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because it is too large. Load diff

Before

Width:  |  Height:  |  Size: 59 KiB

@@ -1,51 +0,0 @@
# Three Scenarios for the simple transparent proxy setup #
![Simple Transparent Proxy Examples](./sslh-examples-v3.png)
## 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.
-135
View File
@@ -1,135 +0,0 @@
# 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.

Before

Width:  |  Height:  |  Size: 233 KiB

File diff suppressed because it is too large. Load diff

Before

Width:  |  Height:  |  Size: 158 KiB

+20 -174
View File
@@ -1,177 +1,17 @@
# Transparent proxy using packet marking
Before reading further, make sure you try to set up
transparent proxying using [IP routing](doc/simple_transparent_proxy.md).
It is conceptually easier to understand, cleaner, and more
portable.
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
required network configuration somewhat. If it doesn't work,
it's almost certain that the problem is not linked to `sslh`
but to the network setup that surrounds it. If in trouble,
it might be worth trying to set up the network rules
with a simpler server than `sslh`, such as
[`socat`](http://www.dest-unreach.org/socat/)
Users have tried to do at least the following:
* `sslh` and the target servers run on the same host (see [below](#transparent-proxy-to-one-host))
* `sslh` runs on a host, and the target servers run on LXC or dockers running on the same host. No known working setup.
* `sslh` runs on a host, and the target servers run on different hosts on the same local network(see [below](#transparent-proxy-to-two-hosts))
* `sslh` runs on a host, and the target servers run on a different host on a different network (there is a [usecase](https://github.com/yrutschle/sslh/issues/295) for this). No known working setup, and it's unclear it is possible.
## Transparent proxy to one host
### Linux
`sslh` needs extended rights to perform this: you'll need to
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
or run it as root (but don't do that).
The firewalling tables also need to be adjusted as follows.
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
this scheme -- let me know if you manage that:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
Transparent proxying with IPv6 is similarly set up as follows:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
# Not sure if this is needed for ipv6 though
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
Explanation:
To be able to use `localhost` as destination in your sslh config along with transparent proxying
you have to allow routing of loopback addresses as done above.
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
showing how packets will traverse the iptables chains.
Note:
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
These rules will allow you to connect directly to ssh on port
22 (or to any other service behind sslh) as well as through sslh on port 443.
Also remember that iptables configuration and ip routes and
rules won't be necessarily persisted after you reboot. Make
sure to save them properly. For example in CentOS7, you would
do `iptables-save > /etc/sysconfig/iptables`, and add both
`ip` commands to your `/etc/rc.local`.
### FreeBSD
Given you have no firewall defined yet, you can use the following configuration
to have ipfw properly redirect traffic back to sslh
/etc/rc.conf
firewall_enable="YES"
firewall_type="open"
firewall_logif="YES"
firewall_coscripts="/etc/ipfw/sslh.rules"
/etc/ipfw/sslh.rules
#! /bin/sh
# ssl
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
# ssh
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
# xmpp
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
# openvpn (running on other internal system)
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
General notes:
This will only work if `sslh` does not use any loopback
addresses (no `127.0.0.1` or `localhost`), you'll need to use
explicit IP addresses (or names):
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
## Transparent Proxy to Two Hosts
Transparent Proxy to Two Hosts
==============================
Tutorial by Sean Warner. 19 June 2019 20:35
### Aim
Aim
---
* Show that `sslh` can transparently proxy requests from the internet to services on two separate hosts that are both on the same LAN.
* The IP address of the client initiating the request is what the destination should see… and not the IP address of the host that `sslh` is running on, which is what happens when `sslh` is not running in transparent mode.
* The solution here only works for my very specific use-case but hopefully others can adapt it to suits their needs.
### Overview of my Network
Overview of my Network
----------------------
Two Raspberry Pis on my home LAN:
* Pi A: 192.168.1.124 – `sslh` (Port 4433), Apache2 web server for https (port 443), `stunnel` (port 4480) to decrypt ssh traffic and forward to SSH server (also on Pi A at Port 1022)
@@ -180,7 +20,8 @@ Two Raspberry Pis on my home LAN:
![Architecture](tproxy.svg)
### `sslh` build
`sslh` build
------------
 
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
@@ -206,7 +47,8 @@ MAN=sslh.8.gz         # man page name
# itself
```
 
### systemd setup
systemd setup
-------------
Create an sslh systemd service file...
```
@@ -241,7 +83,8 @@ Start it again to test…
# systemctl start sslh
```
 
### Configure `sslh`
Configure `sslh`
----------------
First stop `sslh` then open the config file and replace with below, save and start `sslh` again
```
@@ -280,7 +123,8 @@ protocols:
);
```
 
### Configure `stunnel`
Configure `stunnel`
-------------------
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
```
@@ -307,7 +151,8 @@ connect = 192.168.1.124:1022
TIMEOUTclose = 0
```
 
### Configure iptables for Pi A
Configure iptables for Pi A
--------------------------
The `_add.sh` script creates the rules, the `_rm.sh` script removes the rules.
They will be lost if you reboot but there are ways to make them load again on start-up..
@@ -349,7 +194,8 @@ Now run the "add" script on Pi A!
# piA_tproxy_rm.sh
```
## Configure iptables for Pi B
Configure iptables for Pi B
--------------------------
```
# nano /usr/local/sbin/piB_tproxy_add.sh
@@ -389,8 +235,8 @@ Now run the "add" script on Pi B!
# piB_tproxy_rm.sh
```
 
### Testing
Testing
-------
* Getting to sshd on PiA
I did this test using 4G from my phone (outside the LAN)
-12
View File
@@ -1,12 +0,0 @@
# TODO: c2s does not warn if udp: 1 (instead of 'true')
udp: true;
prefix: "hello";
listen: "localhost:9000";
listen-host: "localhost";
listen-port: "9000";
+23 -46
View File
@@ -1,8 +1,8 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:59 2025.
* on Fri Aug 13 18:03:20 2021.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
# Copyright (C) 2018-2021 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -36,8 +36,6 @@
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <stdarg.h>
#include <setjmp.h>
#include "echosrv-conf.h"
#include "argtable3.h"
#ifdef LIBPCRE
@@ -218,19 +216,6 @@ 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(
@@ -241,7 +226,7 @@ static int myconfig_setting_lookup_stringcpy(
const char* str;
*value = NULL;
if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
c2s_asprintf(value, "%s", str);
asprintf(value, "%s", str);
return CONFIG_TRUE;
} else {
return CONFIG_FALSE;
@@ -325,7 +310,7 @@ static int settingcpy(config_type type, void* target, const config_setting_t* se
break;
case CFG_STRING:
c2s_asprintf(&str, "%s", config_setting_get_string(setting));
asprintf(&str, "%s", config_setting_get_string(setting));
val.def_string = str;
*(char**)target = val.def_string;
break;
@@ -368,7 +353,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
break;
case CFG_STRING:
c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
val.def_string = str;
*(char**)target = val.def_string;
break;
@@ -780,7 +765,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]));
c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
desc->name, type2str[desc->type]);
return 0;
}
@@ -837,7 +822,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) {
c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
return 0;
}
@@ -995,13 +980,13 @@ static int regcompmatch_posix( regmatch_t* pmatch,
int errlen = regerror(res, &preg, NULL, 0);
regerr = malloc(errlen);
regerror(res, &preg, regerr, errlen);
c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
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) {
c2s_asprintf(errmsg, "--%s %s: Illegal argument",
asprintf(errmsg, "--%s %s: Illegal argument",
arg_cl->hdr.longopts,
arg->regex);
return 0;
@@ -1027,7 +1012,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));
c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
arg->regex, error, err_str, error_offset);
return 0;
}
@@ -1037,7 +1022,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));
c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
arg_cl->sval[arg_index], arg->regex, res, err_str);
return 0;
}
@@ -1126,13 +1111,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) {
c2s_asprintf(errmsg, "%s:%d:%s",
asprintf(errmsg, "%s:%d:%s",
filename,
config_error_line(c),
config_error_text(c));
return 0;
}
c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
asprintf(errmsg, "%s:%s", filename, config_error_text(c));
return 0;
}
return 1;
@@ -1143,23 +1128,23 @@ static void scalar_to_string(char** strp, config_setting_t* s)
{
switch(config_setting_type(s)) {
case CONFIG_TYPE_INT:
c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
asprintf(strp, "%d\n", config_setting_get_int(s));
break;
case CONFIG_TYPE_BOOL:
c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
break;
case CONFIG_TYPE_INT64:
c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
asprintf(strp, "%lld\n", config_setting_get_int64(s));
break;
case CONFIG_TYPE_FLOAT:
c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
asprintf(strp, "%lf\n", config_setting_get_float(s));
break;
case CONFIG_TYPE_STRING:
c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
asprintf(strp, "%s\n", config_setting_get_string(s));
break;
default: /* This means a bug */
@@ -1187,9 +1172,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)) {
c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
} else {
c2s_asprintf(&subpath, "%s/%s", path, name);
asprintf(&subpath, "%s/%s", path, name);
}
if (config_setting_is_scalar(child)) {
@@ -1197,12 +1182,12 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
/* Add value to the output string */
if (*strp) {
c2s_asprintf(&old, "%s", *strp);
asprintf(&old, "%s", *strp);
free(*strp);
} else {
c2s_asprintf(&old, "%s", "");
asprintf(&old, "%s", "");
}
c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
asprintf(strp, "%s%s:%s", old, subpath, value);
free(value);
free(old);
@@ -1239,14 +1224,6 @@ 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
View File
@@ -1,8 +1,8 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:59 2025.
* on Fri Aug 13 18:03:20 2021.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
# Copyright (C) 2018-2021 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
+7 -12
View File
@@ -1,6 +1,6 @@
/* echosrv: a simple line echo server with optional prefix adding.
*
* echosrv --listen localhost6:1234 --prefix "ssl: "
* echsrv --listen localhost6:1234 --prefix "ssl: "
*
* This will bind to 1234, and echo every line pre-pending "ssl: ". This is
* used for testing: we create several such servers with different prefixes,
@@ -62,10 +62,9 @@ void check_res_dump(int res, struct addrinfo *addr, char* syscall)
void start_echo(int fd)
{
ssize_t res;
int res;
char buffer[1 << 20];
ssize_t ret;
size_t prefix_len;
int ret, prefix_len;
int first = 1;
prefix_len = strlen(cfg.prefix);
@@ -101,10 +100,7 @@ void tcp_echo(struct listen_endpoint* listen_socket)
{
while (1) {
int in_socket = accept(listen_socket->socketfd, 0, 0);
if (in_socket == -1) {
perror("tcp_echo:accept");
exit(1);
}
CHECK_RES_DIE(in_socket, "accept");
if (!fork())
{
@@ -113,7 +109,6 @@ void tcp_echo(struct listen_endpoint* listen_socket)
exit(0);
}
close(in_socket);
waitpid(-1, NULL, WNOHANG);
}
}
@@ -152,7 +147,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
while (1) {
addrlen = sizeof(src_addr);
ssize_t len = recvfrom(listen_socket->socketfd,
size_t len = recvfrom(listen_socket->socketfd,
data + prefix_len,
sizeof(data) - prefix_len,
0,
@@ -163,11 +158,11 @@ void udp_echo(struct listen_endpoint* listen_socket)
perror("recvfrom");
}
*(data + prefix_len + len) = 0;
fprintf(stderr, "%zd %s\n", len, data + prefix_len);
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
ssize_t res = sendto(listen_socket->socketfd,
int res = sendto(listen_socket->socketfd,
data,
len + prefix_len,
0,
-109
View File
@@ -1,109 +0,0 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sat Nov 7 09:19:26 2020.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2019 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef C2S_SSLHCFG_H
#define C2S_SSLHCFG_H
#ifdef LIBCONFIG
# include <libconfig.h>
#endif
#include "probe.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
struct sslhcfg_listen_item {
char* host;
char* port;
int keepalive;
};
struct sslhcfg_protocols_item {
char* name;
char* host;
char* port;
int service_is_present;
char* service;
int fork;
int tfo_ok;
int log_level;
int keepalive;
size_t sni_hostnames_len;
char** sni_hostnames;
size_t alpn_protocols_len;
char** alpn_protocols;
size_t regex_patterns_len;
char** regex_patterns;
int minlength_is_present;
int minlength;
T_PROBE* probe;
struct addrinfo* saddr;
void* data;
};
struct sslhcfg_item {
char* prefix;
int verbose;
int foreground;
int inetd;
int numeric;
int transparent;
int timeout;
int user_is_present;
char* user;
int pidfile_is_present;
char* pidfile;
int chroot_is_present;
char* chroot;
char* syslog_facility;
char* on_timeout;
size_t listen_len;
struct sslhcfg_listen_item* listen;
size_t protocols_len;
struct sslhcfg_protocols_item* protocols;
};
int sslhcfg_parse_file(
const char* filename,
struct sslhcfg_item* sslhcfg,
const char** errmsg);
void sslhcfg_fprint(
FILE* out,
struct sslhcfg_item *sslhcfg,
int depth);
int sslhcfg_cl_parse(
int argc,
char* argv[],
struct sslhcfg_item *sslhcfg);
#endif
+5 -50
View File
@@ -3,6 +3,7 @@
# not be used as a starting point for a working
# configuration. Instead use basic.cfg.
verbose: 0;
foreground: true;
inetd: false;
numeric: false;
@@ -12,34 +13,6 @@ user: "nobody";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# Logging configuration
# 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
verbose-connections-error: 3; # connection errors
verbose-connections-try: 0; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-packets: 0; # hexdump packets on which probing is done
verbose-probe-info: 0; # what's happening during the probe process
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"
# Specify the number of concurrent UDP connection that can
# be managed (default 1024)
udp_max_connections: 16;
# Specify which syslog facility to use (names for your
# system are usually defined in /usr/include/*/sys/syslog.h
# or equivalent)
@@ -53,8 +26,7 @@ listen:
(
{ host: "thelonious"; port: "443"; },
{ host: "thelonious"; port: "8080"; keepalive: true; },
{ host: "thelonious"; is_udp: true; port: "443"; },
{ host: "/tmp/unix_socket"; is_unix: true; port: ""; }
{ host: "thelonious"; is_udp: true; port: "443" }
);
# List of protocols
@@ -72,27 +44,16 @@ listen:
# fork: Should a new process be forked for this protocol?
# (only useful for sslh-select)
# 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. Each name can
# include wildcard following glob(7) rules.
# sni_hostnames: list of FQDN for that target
# alpn_protocols: list of ALPN protocols for that target, see:
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
#
@@ -108,18 +69,12 @@ 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 },
# 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: ""; },
{ name: "http"; host: "localhost"; port: "80"; },
# match BOTH ALPN/SNI
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true },
@@ -143,7 +98,7 @@ protocols:
udp_timeout: 20; # Time after which the "connection" is forgotten
regex_patterns: [ "hello" ]; },
# Forward Teamspeak3 (Voice only)
{ name: "teamspeak"; host: "localhost"; is_udp: true; port: "9987"; },
{ name: "regex"; host: "localhost"; is_udp: true; port: "9987"; regex_patterns: [ "TS3INIT1" ]; },
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
# Remember that the regex needs to be adjusted for every supported QUIC version.
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
+24 -6
View File
@@ -31,6 +31,11 @@
#include "gap.h"
typedef struct gap_array {
int len; /* Number of elements in array */
void** array;
} gap_array;
/* Allocate one page-worth of elements */
static int gap_len_alloc(int elem_size)
{
@@ -48,10 +53,7 @@ gap_array* gap_init(int len)
gap->len = gap_len_alloc(elem_size);
if (gap->len < len) gap->len = len;
gap->array = malloc(gap->len * elem_size);
if (!gap->array) {
free(gap);
return NULL;
}
if (!gap->array) return NULL;
for (int i = 0; i < gap->len; i++)
gap->array[i] = NULL;
@@ -59,7 +61,12 @@ gap_array* gap_init(int len)
return gap;
}
int gap_extend(gap_array* gap)
void* gap_get(gap_array* gap, int index)
{
return gap->array[index];
}
static int gap_extend(gap_array* gap)
{
int elem_size = sizeof(gap->array[0]);
int new_length = gap->len + gap_len_alloc(elem_size);
@@ -77,6 +84,17 @@ int gap_extend(gap_array* gap)
return 0;
}
int gap_set(gap_array* gap, int index, void* ptr)
{
while (index >= gap->len) {
int res = gap_extend(gap);
if (res == -1) return -1;
}
gap->array[index] = ptr;
return 0;
}
void gap_destroy(gap_array* gap)
{
free(gap->array);
@@ -102,7 +120,7 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len)
else
return -1;
for (i = start; i < len - 1; i++) {
for (i = start; i < len; i++) {
gap->array[i] = gap->array[i+1];
}
+2 -32
View File
@@ -4,40 +4,10 @@
typedef struct gap_array gap_array;
gap_array* gap_init(int len);
static void* gap_get(gap_array* gap, int index);
static int gap_set(gap_array* gap, int index, void* ptr);
void* gap_get(gap_array* gap, int index);
int gap_set(gap_array* gap, int index, void* ptr);
void gap_destroy(gap_array* gap);
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 */
void** array;
} gap_array;
int gap_extend(gap_array* gap);
static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, void* ptr)
{
while (index >= gap->len) {
int res = gap_extend(gap);
if (res == -1) return -1;
}
gap->array[index] = ptr;
return 0;
}
static inline void* __attribute__((unused)) gap_get(gap_array* gap, int index)
{
/* sslh-ev routinely reads before it writes. It's not clear if it should be
* its job to check the length (and add a gap_getlen()), or if it should be
* gap_get()'s job. This will do for now */
if (index >= gap->len) return NULL;
return gap->array[index];
}
#endif
+5 -5
View File
@@ -10,14 +10,14 @@ fi
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
# If we don't have git, we can't work out what
# version this is. It must have been downloaded as a
# zip file.
# zip file.
# If downloaded from the release page, the directory
# has the version number.
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
if [ "x$release" = "x" ]; then
# If downloaded from the head, GitHub creates the
# If downloaded from the head, Github creates the
# zip file with all files dated from the last
# change: use the Makefile's modification time as a
# release number
@@ -28,7 +28,7 @@ fi
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
# generate the version info based on the tag
release=`(git describe --tags || git --describe || git describe --all --long) \
2>/dev/null | tr -s '/' '-' | tr -d '\n'`
2>/dev/null | tr -d '\n'`
# Are there uncommitted changes?
git update-index --refresh --unmerged > /dev/null
-224
View File
@@ -1,224 +0,0 @@
/*
* a fixed-sized hash
*
# Copyright (C) 2022 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
#
# */
/* * The hash is open-addressing, linear search, robin-hood insertion, with
* backward shift deletion. References:
* https://codecapsule.com/2013/11/11/robin-hood-hashing/
* https://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
* This means items are reordered upon insertion and deletion, and the hash
* is well-ordered at all times with no tombstones.
*
* Each pointer is either:
* - to a connection struct
* - FREE (NULL) if not allocated
*
* */
#include <stdlib.h>
#include <stddef.h>
#include "gap.h"
typedef void* hash_item;
#include "hash.h"
static void* const FREE = NULL;
struct hash {
int hash_size; /* Max number of items in the hash */
int item_cnt; /* Number of items in the hash */
gap_array* data;
hash_make_key_fn hash_make_key;
hash_cmp_item_fn cmp_item;
};
typedef struct hash hash;
static int hash_make_key(hash* h, hash_item item)
{
return h->hash_make_key(item) % h->hash_size;
}
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item)
{
hash* h = malloc(sizeof(*h));
if (!h) return NULL;
h->hash_size = hash_size;
h->item_cnt = 0;
h->data = gap_init(hash_size);
h->hash_make_key = make_key;
h->cmp_item = cmp_item;
return h;
}
/* Return the index following i in h */
static int hash_next_index(hash* h, int i)
{
return (i + 1) % h->hash_size;
}
/* Returns the index in h of specified address, -1 if not found
* item is an item object that must return the target wanted index and for
* which comparison with the searched object will succeed.
* */
static int hash_find_index(hash* h, hash_item item)
{
hash_item cnx;
int index = hash_make_key(h, item);
int cnt = 0;
cnx = gap_get(h->data, index);
#ifdef DEBUG
fprintf(stderr, "searching %d\n", index);
#endif
while (cnx != FREE) {
if (cnt++ > h->hash_size) return -1;
if (!h->cmp_item(cnx, item))
break;
index = hash_next_index(h, index);
cnx = gap_get(h->data, index);
#ifdef DEBUG
fprintf(stderr, "searching %d\n", index);
#endif
}
if (cnx == FREE) return -1;
return index;
}
hash_item hash_find(hash* h, hash_item item)
{
int index = hash_find_index(h, item);
if (index == -1) return NULL;
hash_item out = gap_get(h->data, index);
return out;
}
/* Returns DIB: distance to initial bucket */
static int distance(int current_index, hash* h, hash_item item)
{
int wanted_index = hash_make_key(h, item);
if (wanted_index <= current_index)
return current_index - wanted_index;
else
return current_index - wanted_index + h->hash_size;
}
int hash_insert(hash* h, hash_item new)
{
int bubble_wanted_index = hash_make_key(h, new);
int index = bubble_wanted_index;
gap_array* hash = h->data;
if (h->item_cnt == h->hash_size)
return -1;
hash_item curr_item = gap_get(hash, index);
while (curr_item) {
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);
#endif
new = curr_item;
}
index = hash_next_index(h, index);
curr_item = gap_get(hash, index);
}
#if DEBUG
fprintf(stderr, "final insert at %d\n", index);
#endif
gap_set(hash, index, new);
h->item_cnt++;
return 0;
}
/* Remove cnx from the hash */
int hash_remove(hash* h, hash_item item)
{
gap_array* hash = h->data;
int index = hash_find_index(h, item);
if (index == -1) return -1; /* Tried to remove something that isn't there */
while (1) {
int next_index = hash_next_index(h, index);
hash_item next = gap_get(h->data, next_index);
if ((next == FREE) || (distance(next_index, h, next) == 0)) {
h->item_cnt--;
gap_set(hash, index, FREE);
return 0;
}
gap_set(hash, index, next);
index = hash_next_index(h, index);;
}
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];
};
void hash_dump(hash* h, char* filename)
{
char str[STR_LENGTH];
FILE* out = fopen(filename, "w");
if (!out) {
perror(filename);
exit(1);
}
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, "</hash>\n");
fclose(out);
}
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef HASH_H
#define HASH_H
/* 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);
/* Function that compares two items: returns 0 if they are the same */
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_insert(hash* h, hash_item new);
int hash_remove(hash* h, hash_item item);
/* Returns the hash item that matches specification (meaning the
* comparison function returns true for cmp(x, item), or NULL if not found */
hash_item hash_find(hash* h, hash_item item);
void hash_dump(hash* h, char* filename); /* For development only */
#endif
-7
View File
@@ -1,7 +0,0 @@
CFLAGS=-DHASH_TESTING -O2 -Wall
OBJ=../hash.o ../gap.o htest.o
htest: $(OBJ)
$(CC) -o htest $(OBJ)
-8
View File
@@ -1,8 +0,0 @@
# Basic delete
a 10 aa
a 10 ab
a 10 ac
a 20 ba
a 21 bb
d 21 bb
-34
View File
@@ -1,34 +0,0 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:10:aa
11:10:ab
12:10:ac
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
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:
</hash>
-9
View File
@@ -1,9 +0,0 @@
# Delete inside a block with nothing after
a 10 aa
a 10 ab
a 12 ac
a 13 ad
a 14 ae
d 14 ae
-34
View File
@@ -1,34 +0,0 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
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:
</hash>
-9
View File
@@ -1,9 +0,0 @@
# wrap-around and delete below floor
a 2 ba
a 30 aa
a 30 ab
a 30 ac
a 30 ad
a 2 bb
d 30 ab
-34
View File
@@ -1,34 +0,0 @@
<hash elem=5>
0:30:ad
1:0:
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:
30:30:aa
31:30:ac
</hash>
-10
View File
@@ -1,10 +0,0 @@
# delete in a discontinuous block
a 10 aa
a 11 ab
a 12 ac
a 14 ad
a 10 bc
d 11 ab
-34
View File
@@ -1,34 +0,0 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:10:aa
11:10:bc
12:12:ac
13:0:
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:
</hash>
-11
View File
@@ -1,11 +0,0 @@
# Delete an unexisting element. And on an empty hash
a 10 aa
d 10 ab
d 12 bc
# Empty for real
d 10 aa
d 10 aa
-34
View File
@@ -1,34 +0,0 @@
<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:
</hash>
-39
View File
@@ -1,39 +0,0 @@
# delete on a full hash
# First, fill the hash :-)
a 0 aa
a 1 ab
a 2 ac
a 3 ad
a 4 ae
a 5 af
a 6 ag
a 7 ah
a 8 ai
a 9 af
a 10 ba
a 11 bb
a 12 bc
a 13 bd
a 14 be
a 15 bf
a 16 bg
a 17 bh
a 18 bi
a 19 bj
a 20 ca
a 21 cb
a 22 cd
a 23 ce
a 24 cf
a 25 cg
a 26 ch
a 27 ci
a 28 cj
a 29 ck
a 30 da
a 31 db
d 21 cb
-34
View File
@@ -1,34 +0,0 @@
<hash elem=31>
0:0:aa
1:1:ab
2:2:ac
3:3:ad
4:4:ae
5:5:af
6:6:ag
7:7:ah
8:8:ai
9:9:af
10:10:ba
11:11:bb
12:12:bc
13:13:bd
14:14:be
15:15:bf
16:16:bg
17:17:bh
18:18:bi
19:19:bj
20:20:ca
21:0:
22:22:cd
23:23:ce
24:24:cf
25:25:cg
26:26:ch
27:27:ci
28:28:cj
29:29:ck
30:30:da
31:31:db
</hash>
-10
View File
@@ -1,10 +0,0 @@
# Delete inside a block with something discontinuous
a 10 aa
a 10 ab
a 12 ac
a 13 ad
a 14 ae
# ab shifts, ac and next doesn't
d 10 aa
-34
View File
@@ -1,34 +0,0 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:10:ab
11:0:
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:
</hash>
-8
View File
@@ -1,8 +0,0 @@
# Basic delete when wrapping, between wrap and floor
a 30 aa
a 30 ab
a 30 ac
a 30 ba
a 30 bb
d 30 ac
-34
View File
@@ -1,34 +0,0 @@
<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:
30:30:aa
31:30:ab
</hash>
-10
View File
@@ -1,10 +0,0 @@
# Delete inside a block with wrapping, with something after
a 30 aa
a 30 ab
a 30 ac
a 1 ad
a 3 ae
# shift ad but not ae
d 14 ae
-34
View File
@@ -1,34 +0,0 @@
<hash elem=5>
0:30:ac
1:1:ad
2:0:
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:
30:30:aa
31:30:ab
</hash>
-8
View File
@@ -1,8 +0,0 @@
# delete before wrap
a 30 aa
a 30 ab
a 30 ac
a 30 ad
# shift ac and ad
d 30 ab
-34
View File
@@ -1,34 +0,0 @@
<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:
30:30:aa
31:30:ac
</hash>
-11
View File
@@ -1,11 +0,0 @@
# Delete with wrapping in discontinuous group
a 30 aa
a 30 ab
a 30 ac
a 31 ad
a 2 ba
a 3 bb
# shift ac and ad but not ba and bb
d 30 ab
-34
View File
@@ -1,34 +0,0 @@
<hash elem=5>
0:31:ad
1:0:
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:
30:30:aa
31:30:ac
</hash>
BIN
View File
Binary file not shown.
-109
View File
@@ -1,109 +0,0 @@
/* Wee testing program from the hash code:
* htest <script> <dump>
*
* scripts are a list of operations:
* a $index $string
* => add an element at specified index
* d $index $string
* => remove an element
* s $index $string
* => prints the actual element index, if it's there
*
* The hash is dumped to the dump file at each iteration.
*/
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
/* tests have been written for a hash that holds 32 items */
#define HASH_SIZE 32
#define STR_LENGTH 16
struct hash_item {
int wanted_index;
char str[STR_LENGTH];
};
typedef struct hash_item* hash_item;
#include "../hash.h"
static int cmp_item(hash_item item1, hash_item item2)
{
return strcmp(item1->str, item2->str);
}
static int hash_make_key(hash_item item)
{
return item->wanted_index;
}
static void htest_next_key(FILE* f, char* action, int* key, char str[STR_LENGTH])
{
int res = 0;
while ((res != 3) && (res != EOF))
res = fscanf(f, "%c %d %s\n", action, key, str);
if (res == EOF) exit(0);
}
int main(int argc, char* argv[])
{
hash* h = hash_init(HASH_SIZE, &hash_make_key, &cmp_item);
char action;
hash_item item;
int line = 0;
FILE* f;
if (argc != 3) {
fprintf(stderr, "Usage: htest <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) {
item= malloc(sizeof(*item));
action = ' ';
line++;
htest_next_key(f, &action, &item->wanted_index, item->str);
fprintf(stderr, "action %d: %c %d %s\n", line, action, item->wanted_index, item->str);
switch (action) {
case 'a': /* add */
fprintf(stderr, "inserting [%s] at %d\n", item->str, item->wanted_index);
hash_insert(h, item);
break;
case 'd': /* del */
fprintf(stderr, "removing [%s] at %d\n", item->str, item->wanted_index);
hash_remove(h, item);
break;
case 's': /* search */
fprintf(stderr, "searching\n");
struct hash_item* found = hash_find(h, item);
fprintf(stderr, "searching %d[%s]: %p\n", item->wanted_index, item->str, found);
break;
case 'q': /* quit */
exit(1);
}
hash_dump(h, dump_file);
}
return 0;
}
-6
View File
@@ -1,6 +0,0 @@
# Basic insertions
a 10 aa
a 10 ab
a 10 ac
a 20 ba
a 21 bb
-34
View File
@@ -1,34 +0,0 @@
<hash elem=5>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:10:aa
11:10:ab
12:10:ac
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
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:
</hash>
-8
View File
@@ -1,8 +0,0 @@
# insert and bubble with single empty space
a 10 aa
a 11 ab
a 12 ac
a 14 ad
a 10 bc
-34
View File
@@ -1,34 +0,0 @@
<hash elem=5>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
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:
</hash>
-40
View File
@@ -1,40 +0,0 @@
# Insert on a full hash
# First, fill the hash :-)
a 0 aa
a 1 ab
a 2 ac
a 3 ad
a 4 ae
a 5 af
a 6 ag
a 7 ah
a 8 ai
a 9 af
a 10 ba
a 11 bb
a 12 bc
a 13 bd
a 14 be
a 15 bf
a 16 bg
a 17 bh
a 18 bi
a 19 bj
a 20 ca
a 21 cb
a 22 cd
a 23 ce
a 24 cf
a 25 cg
a 26 ch
a 27 ci
a 28 cj
a 29 ck
a 30 da
a 31 db
# it's full!
a 20 zz
a 31 za
-34
View File
@@ -1,34 +0,0 @@
<hash elem=32>
0:0:aa
1:1:ab
2:2:ac
3:3:ad
4:4:ae
5:5:af
6:6:ag
7:7:ah
8:8:ai
9:9:af
10:10:ba
11:11:bb
12:12:bc
13:13:bd
14:14:be
15:15:bf
16:16:bg
17:17:bh
18:18:bi
19:19:bj
20:20:ca
21:21:cb
22:22:cd
23:23:ce
24:24:cf
25:25:cg
26:26:ch
27:27:ci
28:28:cj
29:29:ck
30:30:da
31:31:db
</hash>
-7
View File
@@ -1,7 +0,0 @@
# wrap-around and insert at full floor
a 2 ba
a 30 aa
a 30 ab
a 30 ac
a 30 ad
a 2 bb
-34
View File
@@ -1,34 +0,0 @@
<hash elem=6>
0:30:ac
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:
30:30:aa
31:30:ab
</hash>
-7
View File
@@ -1,7 +0,0 @@
# wrap-around and insert above floor
a 30 aa
a 30 ab
a 30 ac
a 30 ad
a 0 ba
a 0 bb
-34
View File
@@ -1,34 +0,0 @@
<hash elem=6>
0:30:ac
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:
30:30:aa
31:30:ab
</hash>
-41
View File
@@ -1,41 +0,0 @@
#! /usr/bin/perl
# Creates a script of random accesses and deletes
use strict;
my $i = 0;
sub mkstr {
$i++;
return chr(ord('a') + ($i / 26) % 26) . chr(ord('a') + $i % 26);
}
my @elems;
sub add_elem {
my $val = int(rand(32));
my $str = mkstr($val);
push @elems, "$val $str";
print "a $val $str\n";
}
sub del_elem {
my $remove = splice(@elems, rand @elems, 1);
print "d $remove\n";
}
while (1) {
if (@elems < 5) {
add_elem;
} elsif (@elems > 28) {
del_elem;
} else {
if (rand() < .5) {
add_elem;
} else {
del_elem;
}
}
}
-30
View File
@@ -1,30 +0,0 @@
#! /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 = "./htest $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;
-186
View File
@@ -1,186 +0,0 @@
/*
* Setup a sandbox using the Landlock LSM, if available.
# Copyright (C) 2023 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"
#include "log.h"
#ifdef HAVE_LANDLOCK
#define _GNU_SOURCE
#include <linux/landlock.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
/* Ubuntu 22.04 does not have this symbol */
#ifndef LANDLOCK_ACCESS_FS_REFER
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
#endif
#ifndef landlock_create_ruleset
static inline int
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
const size_t size, const __u32 flags)
{
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
}
#endif
#ifndef landlock_add_rule
static inline int landlock_add_rule(const int ruleset_fd,
const enum landlock_rule_type rule_type,
const void *const rule_attr,
const __u32 flags)
{
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
flags);
}
#endif
#ifndef landlock_restrict_self
static inline int landlock_restrict_self(const int ruleset_fd,
const __u32 flags)
{
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
}
#endif
typedef enum {
LL_TREE,
LL_FILE
} ll_obj_type;
static int add_path_ro(int ruleset_fd, ll_obj_type otype, const char* path)
{
int fd = open(path, O_PATH | O_CLOEXEC);
if (fd < 0) {
print_message(msg_config_error, "Landlock: Failed to open %s: %s\n", path, strerror(errno));
return -1;
}
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = (otype == LL_TREE ? LANDLOCK_ACCESS_FS_READ_DIR : 0 ) |
LANDLOCK_ACCESS_FS_READ_FILE,
.parent_fd = fd,
};
int res = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
if (res) {
print_message(msg_config_error, "Landlock: Failed to update the ruleset with \"%s\": %s\n",
path, strerror(errno));
close(path_beneath.parent_fd);
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 =
LANDLOCK_ACCESS_FS_EXECUTE |
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_REMOVE_DIR |
LANDLOCK_ACCESS_FS_REMOVE_FILE |
LANDLOCK_ACCESS_FS_MAKE_CHAR |
LANDLOCK_ACCESS_FS_MAKE_DIR |
LANDLOCK_ACCESS_FS_MAKE_REG |
LANDLOCK_ACCESS_FS_MAKE_SOCK |
LANDLOCK_ACCESS_FS_MAKE_FIFO |
LANDLOCK_ACCESS_FS_MAKE_BLOCK |
LANDLOCK_ACCESS_FS_MAKE_SYM |
LANDLOCK_ACCESS_FS_REFER;
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = restrict_rules
};
/* ruleset_addr.handled_access_fs contains all rights that will be restricted
* unless explicitly added */
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
print_message(msg_config_error, "Landlock: Failed to create a ruleset");
return;
}
/* 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");
return;
}
if (landlock_restrict_self(ruleset_fd, 0)) {
print_message(msg_config_error, "Landlock: Failed to enforce ruleset");
return;
}
close(ruleset_fd);
print_message(msg_config, "Landlock: all restricted\n");
}
#else /* HAVE_LANDLOCK */
void setup_landlock(void)
{
print_message(msg_config, "Landlock: not built in\n");
return;
}
#endif /* HAVE_LANDLOCK */
-211
View File
@@ -1,211 +0,0 @@
/*
# log: processing of all outgoing messages
#
# Copyright (C) 2007-2021 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 SYSLOG_NAMES
#define _GNU_SOURCE
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include "sslh-conf.h"
#include "common.h"
#include "log.h"
msg_info msg_config = {
LOG_INFO,
&cfg.verbose_config
};
msg_info msg_config_error = {
LOG_ERR,
&cfg.verbose_config_error
};
msg_info msg_fd = {
LOG_DEBUG,
&cfg.verbose_fd
};
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
msg_info msg_int_error = {
LOG_CRIT,
&cfg.verbose_system_error
};
/* System errors: when the system around us fails us: memory allocation, fork, ... */
msg_info msg_system_error = {
LOG_ERR,
&cfg.verbose_system_error
};
msg_info msg_packets = {
LOG_INFO,
&cfg.verbose_packets
};
/* additional info when attempting outgoing connections */
msg_info msg_connections_try = {
LOG_DEBUG,
&cfg.verbose_connections_try
};
/* Connection information and failures (e.g. forbidden by policy) */
msg_info msg_connections = {
LOG_INFO,
&cfg.verbose_connections
};
/* Connection failures, e.g. target server not present */
msg_info msg_connections_error = {
LOG_ERR,
&cfg.verbose_connections_error
};
/* comment the probing process */
msg_info msg_probe_info = {
LOG_INFO,
&cfg.verbose_probe_info
};
/* probing errors, e.g. inconsistent data in connections */
msg_info msg_probe_error = {
LOG_ERR,
&cfg.verbose_probe_error
};
/* Bitmasks in verbose-* values */
#define MSG_STDOUT 1
#define MSG_SYSLOG 2
#define MSG_FILE 4
static FILE* logfile_fp = NULL;
/* Prints a message to stderr and/or syslog if appropriate */
void print_message(msg_info info, const char* str, ...)
{
va_list ap;
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
va_start(ap, str);
vfprintf(stderr, str, ap);
va_end(ap);
}
if (*info.verbose & MSG_SYSLOG) {
va_start(ap, str);
vsyslog(info.log_level, str, ap);
va_end(ap);
}
if (*info.verbose & MSG_FILE && logfile_fp != NULL) {
va_start(ap, str);
vfprintf(logfile_fp, str, ap);
fflush(logfile_fp);
va_end(ap);
}
}
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
/* Open syslog connection with appropriate banner;
* banner is made up of basename(bin_name)+"[pid]" */
void setup_syslog(const char* bin_name) {
char *name1, *name2;
int res, fn;
if (!strcmp(cfg.syslog_facility, "none")) {
do_syslog = 0;
return;
}
name1 = strdup(bin_name);
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
break;
if (facilitynames[fn].c_val == -1) {
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
exit(1);
}
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
free(name1);
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
}
void setup_logfile()
{
if (cfg.logfile == NULL)
{
return;
}
logfile_fp = fopen(cfg.logfile, "a");
if (logfile_fp == NULL)
{
fprintf(stderr, "Could not open logfile %s for writing: %s\n", cfg.logfile, strerror(errno));
exit(1);
}
}
void close_logfile()
{
if (logfile_fp != NULL)
{
fclose(logfile_fp);
logfile_fp = NULL;
}
}
/* syslogs who connected to where
* desc: string description of the connection. if NULL, log_connection will
* manage on its own
* cnx: connection descriptor
* */
void log_connection(struct connection_desc* desc, const struct connection *cnx)
{
struct connection_desc d;
if (cnx->proto->log_level < 1)
return;
if (!desc) {
desc = &d;
if (!get_connection_desc(desc, cnx)) {
print_message(msg_connections, "%s: lost incoming connection\n",
cnx->proto->name);
return;
}
}
print_message(msg_connections, "%s:connection from %s to %s forwarded from %s to %s\n",
cnx->proto->name,
desc->peer,
desc->service,
desc->local,
desc->target);
}
-36
View File
@@ -1,36 +0,0 @@
#ifndef LOG_H
#define LOG_H
#include "common.h"
void setup_syslog(const char* bin_name);
void setup_logfile();
void close_logfile();
void log_connection(struct connection_desc* desc, const struct connection *cnx);
typedef struct s_msg_info{
int log_level;
int *verbose;
} msg_info;
void print_message(msg_info info, const char* str, ...);
extern msg_info msg_config;
extern msg_info msg_config_error;
extern msg_info msg_fd;
extern msg_info msg_packets;
extern msg_info msg_int_error;
extern msg_info msg_system_error;
extern msg_info msg_connections_try;
extern msg_info msg_connections_error;
extern msg_info msg_connections;
extern msg_info msg_probe_info;
extern msg_info msg_probe_error;
#endif /* LOG_H */
+57 -125
View File
@@ -1,7 +1,7 @@
/*
# probe.c: Code for probing protocols
#
# Copyright (C) 2007-2021 Yves Rutschle
# Copyright (C) 2007-2019 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -27,13 +27,11 @@
#endif
#include <ctype.h>
#include "probe.h"
#include "log.h"
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
@@ -41,8 +39,6 @@ static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; }
/* Table of protocols that have a built-in probe
@@ -51,7 +47,6 @@ static struct protocol_probe_desc builtins[] = {
/* description probe */
{ "ssh", is_ssh_protocol},
{ "openvpn", is_openvpn_protocol },
{ "wireguard", is_wireguard_protocol },
{ "tinc", is_tinc_protocol },
{ "xmpp", is_xmpp_protocol },
{ "http", is_http_protocol },
@@ -59,8 +54,6 @@ static struct protocol_probe_desc builtins[] = {
{ "adb", is_adb_protocol },
{ "socks5", is_socks5_protocol },
{ "syslog", is_syslog_protocol },
{ "teamspeak", is_teamspeak_protocol },
{ "msrdp", is_msrdp_protocol },
{ "anyprot", is_true }
};
@@ -88,38 +81,33 @@ struct sslhcfg_protocols_item* timeout_protocol(void)
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
#define HEXDUMP_COLS 16
void hexdump(msg_info msg_info, const char *mem, unsigned int len)
void hexdump(const char *mem, unsigned int len)
{
unsigned int i, j;
char str[10 + HEXDUMP_COLS * 4 + 2];
int c = 0; /* index in str */
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
{
/* print offset */
if(i % HEXDUMP_COLS == 0)
c += sprintf(&str[c], "0x%06x: ", i);
fprintf(stderr, "0x%06x: ", i);
/* print hex data */
if(i < len)
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]);
fprintf(stderr, "%02x ", 0xFF & mem[i]);
else /* end of block, just aligning for ASCII dump */
c+= sprintf(&str[c], " ");
fprintf(stderr, " ");
/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
if(j >= len) /* end of block, not really printing */
str[c++] = ' ';
fputc(' ', stderr);
else if(isprint(mem[j])) /* printable char */
str[c++] = 0xFF & mem[j];
fputc(0xFF & mem[j], stderr);
else /* other char */
str[c++] = '.';
fputc('.', stderr);
}
str[c++] = '\n';
str[c++] = 0;
print_message(msg_info, "%s", str);
c = 0;
fputc('\n', stderr);
}
}
}
@@ -143,66 +131,15 @@ static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
* and OpenVPN ssl.c, ssl.h and options.c
*/
#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_HMAC_128 16
#define OVPN_HMAC_160 20
#define OVPN_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto)
{
int packet_len;
if (proto->is_udp == 0)
{
if (len < 2)
return PROBE_AGAIN;
if (len < 2)
return PROBE_AGAIN;
packet_len = ntohs(*(uint16_t*)p);
return packet_len == len - 2;
} else {
if (len < 1)
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)
return PROBE_NEXT;
/* The detection pattern above may not be reliable enough.
* Check the packet id: OpenVPN sents five initial packets
* whereas the packet id is increased with every transmitted datagram.
*/
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)
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)
return PROBE_MATCH;
return PROBE_NEXT;
}
}
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (proto->is_udp == 0)
return PROBE_NEXT;
// Handshake Init: 148 bytes
if (len != 148)
return PROBE_NEXT;
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
if (ntohl(*(uint32_t*)p) != 0x01000000)
return PROBE_NEXT;
return PROBE_MATCH;
packet_len = ntohs(*(uint16_t*)p);
return packet_len == len - 2;
}
/* Is the buffer the beginning of a tinc connections?
@@ -323,7 +260,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)) != 0)
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
return PROBE_NEXT;
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
@@ -375,27 +312,6 @@ static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protoco
return 0;
}
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (len < 8)
return PROBE_NEXT;
return !strncmp(p, "TS3INIT1", len);
}
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
char version;
char packet_len;
if (len < 7)
return PROBE_NEXT;
version=*p;
if (version!=0x03)
return 0;
packet_len = ntohs(*(uint16_t*)(p+2));
return packet_len == len;
}
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
#ifdef ENABLE_REGEX
@@ -412,58 +328,51 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item
return 0;
#else
/* Should never happen as we check when loading config file */
print_message(msg_int_error, "FATAL: regex probe called but not built in\n");
fprintf(stderr, "FATAL: regex probe called but not built in\n");
exit(5);
#endif
}
/* Run all the probes on a buffer
* buf, len: buffer to test on
* proto_in, proto_len: array of protocols to try
* 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
* point to the appropriate protocol
* */
int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item** proto_in,
int proto_len,
struct sslhcfg_protocols_item** proto_out
)
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
{
struct sslhcfg_protocols_item* p;
int i, res, again = 0;
print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, buf, len);
if (cfg.verbose > 1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buf, len);
}
*proto_out = NULL;
for (i = 0; i < proto_len; i++) {
*proto = NULL;
for (i = 0; i < cfg.protocols_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
p = proto_in[i];
p = &cfg.protocols[i];
if (! p->probe) continue;
print_message(msg_probe_info, "probing for %s\n", p->name);
if (cfg.verbose) fprintf(stderr, "probing for %s\n", p->name);
/* Don't probe last protocol if it is anyprot (and store last protocol) */
if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot")))
break;
if (p->minlength_is_present && (len < p->minlength )) {
print_message(msg_probe_info, "input too short, %d bytes but need %d\n",
len , p->minlength);
fprintf(stderr, "input too short, %d bytes but need %d\n", len , p->minlength);
again++;
continue;
}
res = p->probe(buf, len, p);
print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]);
if (cfg.verbose) fprintf(stderr, "probed for %s: %s\n", p->name, probe_str[res]);
if (res == PROBE_MATCH) {
*proto_out = p;
*proto = p;
return PROBE_MATCH;
}
if (res == PROBE_AGAIN)
@@ -473,14 +382,37 @@ int probe_buffer(char* buf, int len,
return PROBE_AGAIN;
/* Everything failed: match the last one */
*proto = &cfg.protocols[cfg.protocols_len-1];
return PROBE_MATCH;
}
if (proto_len == 0) {
/* This should be caught by configuration sanity checks, but just in
* case, die gracefully rather than segfaulting */
print_message(msg_int_error, "Received traffic on transport that has no target\n");
exit(0);
/*
* Read the beginning of data coming from the client connection and check if
* it's a known protocol.
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
* which case cnx->proto is set to the appropriate protocol.
*/
int probe_client_protocol(struct connection *cnx)
{
char buffer[BUFSIZ];
ssize_t n;
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
/* It's possible that read() returns an error, e.g. if the client
* disconnected between the previous call to select() and now. If that
* happens, we just connect to the default protocol so the caller of this
* function does not have to deal with a specific failure condition (the
* connection will just fail later normally). */
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,
&cnx->proto);
}
*proto_out = proto_in[proto_len-1];
/* read() returned an error, so just connect to the last protocol to die */
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
return PROBE_MATCH;
}
+3 -8
View File
@@ -5,7 +5,6 @@
#include "common.h"
#include "tls.h"
#include "log.h"
typedef enum {
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
@@ -48,12 +47,8 @@ void set_protocol_list(struct sslhcfg_protocols_item*);
*/
int probe_client_protocol(struct connection *cnx);
/* Probe on a buffer */
int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item** proto_in,
int proto_len,
struct sslhcfg_protocols_item** proto_out
);
/* Probe, but on a buffer */
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto);
/* set the protocol to connect to in case of timeout */
void set_ontimeout(const char* name);
@@ -64,6 +59,6 @@ void set_ontimeout(const char* name);
*/
struct sslhcfg_protocols_item* timeout_protocol(void);
void hexdump(msg_info, const char*, unsigned int);
void hexdump(const char*, unsigned int);
#endif
-115
View File
@@ -1,115 +0,0 @@
/*
Processes that are common to sslh-ev and sslh-select
# Copyright (C) 2021 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 "udp-listener.h"
#include "tcp-listener.h"
#include "processes.h"
#include "probe.h"
#include "log.h"
int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
{
int i;
for (i = 0; i < 2; i++) {
if (cnx->q[i].fd != -1) {
print_message(msg_fd, "closing fd %d\n", cnx->q[i].fd);
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->type == SOCK_DGRAM)
udp_tidy(cnx, fd_info);
if (gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing) != -1)
fd_info->num_probing--;
collection_remove_cnx(fd_info->collection, cnx);
return 0;
}
/* Process a connection that is active in read */
void cnx_read_process(struct loop_info* fd_info, int fd)
{
cnx_collection* collection = fd_info->collection;
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
switch (cnx->type) {
case SOCK_STREAM:
tcp_read_process(fd_info, fd);
break;
case SOCK_DGRAM:
udp_s2c_forward(cnx);
break;
default:
print_message(msg_int_error, "cnx_read_process: Illegal connection type %d\n", cnx->type);
dump_connection(cnx);
exit(1);
}
}
/* Process a connection that accepts a socket
* (For UDP, this means all traffic coming from remote clients)
* Returns new connection object, or NULL
* */
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket)
{
int fd = listen_socket->socketfd;
int type = listen_socket->type;
struct connection* cnx;
switch (type) {
case SOCK_STREAM:
cnx = accept_new_connection(fd, fd_info);
if (!cnx) return NULL;
break;
case SOCK_DGRAM:
cnx = udp_c2s_forward(fd, fd_info);
if (!cnx) return NULL;
break;
default:
print_message(msg_int_error, "Inconsistent cnx type: %d\n", type);
exit(1);
}
int new_fd = cnx->q[0].fd;
watchers_add_read(fd_info->watchers, new_fd);
return cnx;
}
-41
View File
@@ -1,41 +0,0 @@
#ifndef PROCESSES_H
#define PROCESSES_H
#include "common.h"
#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;
/* Global state for a loop */
struct loop_info {
int num_probing; /* Number of connections currently probing
* We use this to know if we need to time out of
* select() */
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
hash* hash_sources; /* UDP remote sources previously encountered */
watchers* watchers;
cnx_collection* collection; /* Collection of connections linked to this loop */
};
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);
/* These must be declared in the loop handler, sslh-ev or sslh-select */
void watchers_add_read(watchers* w, int fd);
void watchers_del_read(watchers* w, int fd);
void watchers_add_write(watchers* w, int fd);
void watchers_del_write(watchers* w, int fd);
#endif
-120
View File
@@ -1,120 +0,0 @@
/*
# 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 */
/* Fills *addr, *host and *serv with the connection information corresponding
* to fd. *host is the IP address as string and *serv is the service (port)
* */
static int get_info(int fd, struct addrinfo* addr, libpp_addr* host, uint16_t* serv)
{
char serv_str[NI_MAXSERV];
int res;
res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen);
CHECK_RES_RETURN(res, "getpeername", -1);
res = getnameinfo(addr->ai_addr, addr->ai_addrlen,
(char*)host, sizeof(*host),
serv_str, sizeof(serv_str),
NI_NUMERICHOST | NI_NUMERICSERV );
CHECK_RES_RETURN(res, "getnameinfo", -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,
&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,
&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;
}
#endif /* HAVE_PROXYPROTOCOL */
-15
View File
@@ -1,15 +0,0 @@
#ifndef PROXYPROTOCOL_H
#define PROXYPROTOCOL_H
#if HAVE_PROXYPROTOCOL
int pp_write_header(int pp_version, struct connection* cnx);
#else /* HAVE_PROXYPROTOCOL */
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
#endif /* HAVE_PROXYPROTOCOL */
#endif /* PROXYPROTOCOL_H */
@@ -1,186 +0,0 @@
#! /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
+1 -1
View File
@@ -18,7 +18,7 @@
#CONFIG=/etc/sslh.cfg
#
# Extra option to pass on command line
# Extra option to pass on comand line
# Those can supersede configuration file settings
#
#OPTIONS=
+2 -2
View File
@@ -8,7 +8,7 @@
# but many connection attempts from the same
# origin is reason enough to block.
#
# Version: 2014-03-28
# Verion: 2014-03-28
[INCLUDES]
@@ -16,7 +16,7 @@
[Definition]
failregex = ^.+ sslh\[.+\]: ssh:connection from <HOST>:.+ to .+ forwarded
failregex = ^.+ sslh\[.+\]: connection from <HOST>:.+ to .+ forwarded
from .+ to .+:ssh\s*$
ignoreregex =
-27
View File
@@ -1,27 +0,0 @@
[Unit]
Description=SSL/SSH multiplexer (select mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target
@@ -1,10 +1,10 @@
[Unit]
Description=SSL/SSH multiplexer (fork mode) for %I
Description=SSL/SSH multiplexer
After=network.target
[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
+55 -476
View File
@@ -1,8 +1,8 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:58 2025.
* on Tue Jan 2 21:42:30 2024.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
# Copyright (C) 2018-2021 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -36,8 +36,6 @@
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <stdarg.h>
#include <setjmp.h>
#include "sslh-conf.h"
#include "argtable3.h"
#ifdef LIBPCRE
@@ -218,19 +216,6 @@ 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(
@@ -241,7 +226,7 @@ static int myconfig_setting_lookup_stringcpy(
const char* str;
*value = NULL;
if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
c2s_asprintf(value, "%s", str);
asprintf(value, "%s", str);
return CONFIG_TRUE;
} else {
return CONFIG_FALSE;
@@ -325,7 +310,7 @@ static int settingcpy(config_type type, void* target, const config_setting_t* se
break;
case CFG_STRING:
c2s_asprintf(&str, "%s", config_setting_get_string(setting));
asprintf(&str, "%s", config_setting_get_string(setting));
val.def_string = str;
*(char**)target = val.def_string;
break;
@@ -368,7 +353,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
break;
case CFG_STRING:
c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
val.def_string = str;
*(char**)target = val.def_string;
break;
@@ -459,29 +444,16 @@ 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;
struct arg_int* sslhcfg_verbose_connections_try;
struct arg_int* sslhcfg_verbose_connections_error;
struct arg_int* sslhcfg_verbose_fd;
struct arg_int* sslhcfg_verbose_packets;
struct arg_int* sslhcfg_verbose_probe_info;
struct arg_int* sslhcfg_verbose_probe_error;
struct arg_int* sslhcfg_verbose_system_error;
struct arg_int* sslhcfg_verbose_int_error;
struct arg_lit* sslhcfg_version;
struct arg_lit* sslhcfg_foreground;
struct arg_lit* sslhcfg_inetd;
struct arg_lit* sslhcfg_numeric;
struct arg_lit* sslhcfg_transparent;
struct arg_int* sslhcfg_timeout;
struct arg_int* sslhcfg_udp_max_connections;
struct arg_str* sslhcfg_user;
struct arg_str* sslhcfg_pidfile;
struct arg_str* sslhcfg_chroot;
struct arg_str* sslhcfg_syslog_facility;
struct arg_str* sslhcfg_logfile;
struct arg_str* sslhcfg_on_timeout;
struct arg_str* sslhcfg_prefix;
struct arg_str* sslhcfg_listen;
@@ -490,17 +462,15 @@ struct arg_file* sslhcfg_conffile;
struct arg_str* sslhcfg_ssl;
struct arg_str* sslhcfg_openvpn;
struct arg_str* sslhcfg_tinc;
struct arg_str* sslhcfg_wireguard;
struct arg_str* sslhcfg_xmpp;
struct arg_str* sslhcfg_http;
struct arg_str* sslhcfg_adb;
struct arg_str* sslhcfg_socks5;
struct arg_str* sslhcfg_syslog;
struct arg_str* sslhcfg_msrdp;
struct arg_str* sslhcfg_anyprot;
struct arg_end* sslhcfg_end;
static struct config_desc table_sslhcfg_protocols[] = {
@@ -568,22 +538,6 @@ 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,
@@ -664,22 +618,6 @@ static struct config_desc table_sslhcfg_protocols[] = {
/* default_val*/ .default_val.def_bool = 0
},
{
/* name */ "resolve_on_forward",
/* type */ CFG_BOOL,
/* sub_group*/ NULL,
/* arg_cl */ NULL,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_protocols_item, resolve_on_forward),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_bool = 0
},
{
/* name */ "log_level",
/* type */ CFG_INT,
@@ -775,25 +713,9 @@ 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[] = {
@@ -845,22 +767,6 @@ 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,
@@ -878,7 +784,7 @@ static struct config_desc table_sslhcfg_listen[] = {
},
{ 0 }
};
static struct config_desc table_sslhcfg[] = {
@@ -898,182 +804,6 @@ static struct config_desc table_sslhcfg[] = {
/* default_val*/ .default_val.def_int = 0
},
{
/* name */ "verbose_config",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_config,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_config),
/* 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_error",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_config_error,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_config_error),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 3
},
{
/* name */ "verbose_connections",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_connections,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_connections),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 3
},
{
/* name */ "verbose_connections_try",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_connections_try,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_connections_try),
/* 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_connections_error",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_connections_error,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_connections_error),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 3
},
{
/* name */ "verbose_fd",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_fd,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_fd),
/* 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_packets",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_packets,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_packets),
/* 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_probe_info",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_probe_info,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_probe_info),
/* 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_probe_error",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_probe_error,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_probe_error),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 3
},
{
/* name */ "verbose_system_error",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_system_error,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_system_error),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 3
},
{
/* name */ "verbose_int_error",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_verbose_int_error,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, verbose_int_error),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 3
},
{
/* name */ "version",
/* type */ CFG_BOOL,
@@ -1170,22 +900,6 @@ static struct config_desc table_sslhcfg[] = {
/* default_val*/ .default_val.def_int = 5
},
{
/* name */ "udp_max_connections",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_udp_max_connections,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, udp_max_connections),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_int = 1024
},
{
/* name */ "user",
/* type */ CFG_STRING,
@@ -1250,22 +964,6 @@ static struct config_desc table_sslhcfg[] = {
/* default_val*/ .default_val.def_string = "auth"
},
{
/* name */ "logfile",
/* type */ CFG_STRING,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_logfile,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, logfile),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_item, logfile_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,
@@ -1335,15 +1033,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[11], 0, .value.def_int = 1 },
{ 0 }
};
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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ 0 }
};
@@ -1351,7 +1041,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ 0 }
};
@@ -1359,7 +1049,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ 0 }
};
@@ -1367,7 +1057,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ 0 }
};
@@ -1375,7 +1065,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ 0 }
};
@@ -1383,16 +1073,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[11], 0, .value.def_int = 1 },
{ 0 }
};
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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ 0 }
};
@@ -1400,8 +1081,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
{ 0 }
};
@@ -1409,8 +1090,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
{ 0 }
};
@@ -1418,8 +1099,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
{ 0 }
};
@@ -1427,8 +1108,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[11], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
{ 0 }
};
@@ -1436,9 +1117,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[9], 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 }
};
@@ -1452,7 +1133,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: listen */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_listen,
.base_entry = & table_sslhcfg [26],
.base_entry = & table_sslhcfg [13],
.targets = sslhcfg_listen_targets,
@@ -1464,7 +1145,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: ssh */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssh,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_ssh_targets,
@@ -1476,7 +1157,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: tls */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_tls,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_tls_targets,
@@ -1488,7 +1169,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: ssl */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssl,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_ssl_targets,
@@ -1500,7 +1181,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: openvpn */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_openvpn,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_openvpn_targets,
@@ -1512,7 +1193,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: tinc */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_tinc,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_tinc_targets,
@@ -1521,22 +1202,10 @@ static struct compound_cl_arg compound_cl_args[] = {
.override_const = "tinc",
},
{ /* arg: wireguard */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_wireguard,
.base_entry = & table_sslhcfg [27],
.targets = sslhcfg_wireguard_targets,
.override_desc = & table_sslhcfg_protocols [0],
.override_matchindex = 0,
.override_const = "wireguard",
},
{ /* arg: xmpp */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_xmpp,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_xmpp_targets,
@@ -1548,7 +1217,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: http */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_http,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_http_targets,
@@ -1560,7 +1229,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: adb */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_adb,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_adb_targets,
@@ -1572,7 +1241,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: socks5 */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_socks5,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_socks5_targets,
@@ -1584,7 +1253,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: syslog */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_syslog,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_syslog_targets,
@@ -1593,22 +1262,10 @@ static struct compound_cl_arg compound_cl_args[] = {
.override_const = "syslog",
},
{ /* arg: msrdp */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_msrdp,
.base_entry = & table_sslhcfg [27],
.targets = sslhcfg_msrdp_targets,
.override_desc = & table_sslhcfg_protocols [0],
.override_matchindex = 0,
.override_const = "msrdp",
},
{ /* arg: anyprot */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_anyprot,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [14],
.targets = sslhcfg_anyprot_targets,
@@ -1823,7 +1480,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]));
c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n",
desc->name, type2str[desc->type]);
return 0;
}
@@ -1880,7 +1537,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) {
c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
return 0;
}
@@ -2038,13 +1695,13 @@ static int regcompmatch_posix( regmatch_t* pmatch,
int errlen = regerror(res, &preg, NULL, 0);
regerr = malloc(errlen);
regerror(res, &preg, regerr, errlen);
c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
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) {
c2s_asprintf(errmsg, "--%s %s: Illegal argument",
asprintf(errmsg, "--%s %s: Illegal argument",
arg_cl->hdr.longopts,
arg->regex);
return 0;
@@ -2070,7 +1727,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));
c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
arg->regex, error, err_str, error_offset);
return 0;
}
@@ -2080,7 +1737,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));
c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n",
arg_cl->sval[arg_index], arg->regex, res, err_str);
return 0;
}
@@ -2169,13 +1826,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) {
c2s_asprintf(errmsg, "%s:%d:%s",
asprintf(errmsg, "%s:%d:%s",
filename,
config_error_line(c),
config_error_text(c));
return 0;
}
c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
asprintf(errmsg, "%s:%s", filename, config_error_text(c));
return 0;
}
return 1;
@@ -2186,23 +1843,23 @@ static void scalar_to_string(char** strp, config_setting_t* s)
{
switch(config_setting_type(s)) {
case CONFIG_TYPE_INT:
c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
asprintf(strp, "%d\n", config_setting_get_int(s));
break;
case CONFIG_TYPE_BOOL:
c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" );
break;
case CONFIG_TYPE_INT64:
c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
asprintf(strp, "%lld\n", config_setting_get_int64(s));
break;
case CONFIG_TYPE_FLOAT:
c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
asprintf(strp, "%lf\n", config_setting_get_float(s));
break;
case CONFIG_TYPE_STRING:
c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
asprintf(strp, "%s\n", config_setting_get_string(s));
break;
default: /* This means a bug */
@@ -2230,9 +1887,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)) {
c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
} else {
c2s_asprintf(&subpath, "%s/%s", path, name);
asprintf(&subpath, "%s/%s", path, name);
}
if (config_setting_is_scalar(child)) {
@@ -2240,12 +1897,12 @@ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp
/* Add value to the output string */
if (*strp) {
c2s_asprintf(&old, "%s", *strp);
asprintf(&old, "%s", *strp);
free(*strp);
} else {
c2s_asprintf(&old, "%s", "");
asprintf(&old, "%s", "");
}
c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
asprintf(strp, "%s%s:%s", old, subpath, value);
free(value);
free(old);
@@ -2273,30 +1930,17 @@ 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"),
sslhcfg_verbose_connections_try = arg_intn(NULL, "verbose-connections-try", "<n>", 0, 1, "Connection errors"),
sslhcfg_verbose_connections_error = arg_intn(NULL, "verbose-connections-error", "<n>", 0, 1, "Connection attempts towards targets"),
sslhcfg_verbose_fd = arg_intn(NULL, "verbose-fd", "<n>", 0, 1, "File descriptor activity, open/close/whatnot"),
sslhcfg_verbose_packets = arg_intn(NULL, "verbose-packets", "<n>", 0, 1, "Hexdump packets on which probing is done"),
sslhcfg_verbose_probe_info = arg_intn(NULL, "verbose-probe-info", "<n>", 0, 1, "Trace the probe process"),
sslhcfg_verbose_probe_error = arg_intn(NULL, "verbose-probe-error", "<n>", 0, 1, "Failures and problems during probing"),
sslhcfg_verbose_system_error = arg_intn(NULL, "verbose-system-error", "<n>", 0, 1, "System call failures"),
sslhcfg_verbose_int_error = arg_intn(NULL, "verbose-int-error", "<n>", 0, 1, "Internal errors that should never happen"),
sslhcfg_verbose = arg_intn("v", "verbose", "<n>", 0, 1, ""),
sslhcfg_version = arg_litn("V", "version", 0, 1, "Print version information and exit"),
sslhcfg_foreground = arg_litn("f", "foreground", 0, 1, "Run in foreground instead of as a daemon"),
sslhcfg_inetd = arg_litn("i", "inetd", 0, 1, "Run in inetd mode: use stdin/stdout instead of network listen"),
sslhcfg_numeric = arg_litn("n", "numeric", 0, 1, "Print IP addresses and ports as numbers"),
sslhcfg_transparent = arg_litn(NULL, "transparent", 0, 1, "Set up as a transparent proxy"),
sslhcfg_timeout = arg_intn("t", "timeout", "<n>", 0, 1, "Set up timeout before connecting to default target"),
sslhcfg_udp_max_connections = arg_intn(NULL, "udp-max-connections", "<n>", 0, 1, "Number of concurrent UDP connections"),
sslhcfg_user = arg_strn("u", "user", "<str>", 0, 1, "Username to change to after set-up"),
sslhcfg_pidfile = arg_strn("P", "pidfile", "<file>", 0, 1, "Path to file to store PID of current instance"),
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_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"),
@@ -2305,26 +1949,16 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
sslhcfg_ssl = arg_strn(NULL, "ssl", "<host:port>", 0, 10, "Set up TLS/SSL target"),
sslhcfg_openvpn = arg_strn(NULL, "openvpn", "<host:port>", 0, 10, "Set up OpenVPN target"),
sslhcfg_tinc = arg_strn(NULL, "tinc", "<host:port>", 0, 10, "Set up tinc target"),
sslhcfg_wireguard = arg_strn(NULL, "wireguard", "<host:port>", 0, 10, "Set up WireGuard target"),
sslhcfg_xmpp = arg_strn(NULL, "xmpp", "<host:port>", 0, 10, "Set up XMPP target"),
sslhcfg_http = arg_strn(NULL, "http", "<host:port>", 0, 10, "Set up HTTP (plain) target"),
sslhcfg_adb = arg_strn(NULL, "adb", "<host:port>", 0, 10, "Set up ADB (Android Debug) target"),
sslhcfg_socks5 = arg_strn(NULL, "socks5", "<host:port>", 0, 10, "Set up socks5 target"),
sslhcfg_syslog = arg_strn(NULL, "syslog", "<host:port>", 0, 10, "Set up syslog target"),
sslhcfg_msrdp = arg_strn(NULL, "msrdp", "<host:port>", 0, 10, "Set up msrdp target"),
sslhcfg_anyprot = arg_strn(NULL, "anyprot", "<host:port>", 0, 10, "Set up default target"),
sslhcfg_end = arg_end(10)
};
/* 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) {
@@ -2394,9 +2028,6 @@ 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);
@@ -2412,9 +2043,6 @@ static void sslhcfg_protocols_fprint(
fprintf(out, "transparent: %d", sslhcfg_protocols->transparent);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "resolve_on_forward: %d", sslhcfg_protocols->resolve_on_forward);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "log_level: %d", sslhcfg_protocols->log_level);
fprintf(out, "\n");
indent(out, depth);
@@ -2443,11 +2071,6 @@ 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(
@@ -2466,9 +2089,6 @@ static void sslhcfg_listen_fprint(
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");
}
@@ -2483,39 +2103,6 @@ void sslhcfg_fprint(
fprintf(out, "verbose: %d", sslhcfg->verbose);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_config: %d", sslhcfg->verbose_config);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_config_error: %d", sslhcfg->verbose_config_error);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_connections: %d", sslhcfg->verbose_connections);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_connections_try: %d", sslhcfg->verbose_connections_try);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_connections_error: %d", sslhcfg->verbose_connections_error);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_fd: %d", sslhcfg->verbose_fd);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_packets: %d", sslhcfg->verbose_packets);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_probe_info: %d", sslhcfg->verbose_probe_info);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_probe_error: %d", sslhcfg->verbose_probe_error);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_system_error: %d", sslhcfg->verbose_system_error);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "verbose_int_error: %d", sslhcfg->verbose_int_error);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "version: %d", sslhcfg->version);
fprintf(out, "\n");
indent(out, depth);
@@ -2534,9 +2121,6 @@ void sslhcfg_fprint(
fprintf(out, "timeout: %d", sslhcfg->timeout);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "udp_max_connections: %d", sslhcfg->udp_max_connections);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "user: %s", sslhcfg->user);
if (! sslhcfg->user_is_present)
fprintf(out, " <unset>");
@@ -2555,11 +2139,6 @@ void sslhcfg_fprint(
fprintf(out, "syslog_facility: %s", sslhcfg->syslog_facility);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "logfile: %s", sslhcfg->logfile);
if (! sslhcfg->logfile_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);
+2 -22
View File
@@ -1,8 +1,8 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:58 2025.
* on Tue Jan 2 21:42:30 2024.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
# Copyright (C) 2018-2021 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -44,7 +44,6 @@ struct sslhcfg_listen_item {
char* host;
char* port;
int is_udp;
int is_unix;
int keepalive;
};
@@ -54,13 +53,11 @@ struct sslhcfg_protocols_item {
char* port;
int service_is_present;
char* service;
int is_unix;
int is_udp;
int udp_timeout;
int fork;
int tfo_ok;
int transparent;
int resolve_on_forward;
int log_level;
int keepalive;
size_t sni_hostnames_len;
@@ -71,34 +68,19 @@ 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;
dl_list timeouts;
};
struct sslhcfg_item {
int verbose;
int verbose_config;
int verbose_config_error;
int verbose_connections;
int verbose_connections_try;
int verbose_connections_error;
int verbose_fd;
int verbose_packets;
int verbose_probe_info;
int verbose_probe_error;
int verbose_system_error;
int verbose_int_error;
int version;
int foreground;
int inetd;
int numeric;
int transparent;
int timeout;
int udp_max_connections;
int user_is_present;
char* user;
int pidfile_is_present;
@@ -106,8 +88,6 @@ struct sslhcfg_item {
int chroot_is_present;
char* chroot;
char* syslog_facility;
int logfile_is_present;
char* logfile;
char* on_timeout;
char* prefix;
size_t listen_len;
-153
View File
@@ -1,153 +0,0 @@
/*
sslh-ev: mono-processus server based on libev
# Copyright (C) 2021 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 <stdlib.h>
#include <ev.h>
#include "gap.h"
#include "log.h"
#include "udp-listener.h"
#include "tcp-listener.h"
const char* server_type = "sslh-ev";
static struct ev_loop* loop;
/* Libev watchers */
struct watchers {
/* one set of ev_io for read, one for write, indexed by file descriptor */
gap_array *ev_ior, *ev_iow;
struct listen_endpoint* listen_sockets;
gap_array* fd2ls; /* Array indexed by file descriptor, pointing to listen_sockets */
};
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);
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
int num_addr_listen)
{
*w = malloc(sizeof(**w));
(*w)->ev_ior = gap_init(num_addr_listen);
(*w)->ev_iow = gap_init(num_addr_listen);
(*w)->listen_sockets = listen_sockets;
(*w)->fd2ls = gap_init(0);
/* Create watchers for listen sockets */
for (int i = 0; i < num_addr_listen; i++) {
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);
}
}
void watchers_add_read(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_ior, fd);
if (!io) {
io = malloc(sizeof(*io));
ev_io_init(io, &cnx_read_cb, fd, EV_READ);
ev_io_set(io, fd, EV_READ);
gap_set(w->ev_ior, fd, io);
}
ev_io_start(loop, io);
}
void watchers_del_read(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_ior, fd);
if (io) ev_io_stop(EV_A_ io);
}
void watchers_add_write(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_iow, fd);
if (!io) {
io = malloc(sizeof(*io));
ev_io_init(io, &cnx_write_cb, fd, EV_WRITE);
ev_io_set(io, fd, EV_WRITE);
gap_set(w->ev_iow, fd, io);
}
ev_io_start(loop, io);
}
void watchers_del_write(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_iow, fd);
if (io) ev_io_stop(EV_A_ io);
}
/* /watchers */
#include "processes.h"
/* Libev callbacks */
static void cnx_read_cb(EV_P_ ev_io *w, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
cnx_read_process(info, w->fd);
}
static void cnx_write_cb(EV_P_ ev_io *w, int wevents)
{
struct loop_info* info = ev_userdata(EV_A);
cnx_write_process(info, w->fd);
}
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));
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
struct loop_info ev_info = {0};
loop = EV_DEFAULT;
ev_info.collection = collection_init(0);
ev_info.probing_list = gap_init(0);
udp_init(&ev_info);
tcp_init();
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info);
ev_run(EV_A_ 0);
}
void start_shoveler(int listen_socket) {
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
exit(1);
}
+21 -45
View File
@@ -23,10 +23,9 @@
#include "common.h"
#include "probe.h"
#include "sslh-conf.h"
#include "tcp-probe.h"
#include "log.h"
#include "udp-listener.h"
#if HAVE_LIBBSD
#ifdef LIBBSD
#include <bsd/unistd.h>
#endif
@@ -59,7 +58,8 @@ int shovel(struct connection *cnx)
if (FD_ISSET(cnx->q[i].fd, &fds)) {
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
if (res == FD_CNXCLOSED) {
print_message(msg_fd, "%s %s", i ? "client" : "server", "socket closed\n");
if (cfg.verbose)
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
return res;
}
}
@@ -74,6 +74,7 @@ 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;
@@ -98,7 +99,8 @@ void start_shoveler(int in_socket)
} else {
/* Timed out: it's necessarily SSH */
cnx.proto = timeout_protocol();
print_message(msg_fd, "timed out, connect to %s\n", cnx.proto->name);
if (cfg.verbose)
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->name);
break;
}
}
@@ -109,11 +111,13 @@ void start_shoveler(int in_socket)
}
/* Connect the target socket */
connect_addr(&cnx, in_socket, BLOCKING);
CHECK_RES_DIE(cnx.q[1].fd, "connect");
out_socket = connect_addr(&cnx, in_socket, BLOCKING);
CHECK_RES_DIE(out_socket, "connect");
set_capabilities(0);
cnx.q[1].fd = out_socket;
get_connection_desc(&desc, &cnx);
log_connection(&desc, &cnx);
set_proctitle_shovel(&desc, &cnx);
@@ -123,9 +127,10 @@ void start_shoveler(int in_socket)
shovel(&cnx);
close(in_socket);
close(cnx.q[1].fd);
close(out_socket);
print_message(msg_fd, "connection closed down\n");
if (cfg.verbose)
fprintf(stderr, "connection closed down\n");
exit(0);
}
@@ -144,7 +149,7 @@ void stop_listeners(int sig)
void set_listen_procname(struct listen_endpoint *listen_socket)
{
#if HAVE_LIBBSD
#ifdef LIBBSD
int res;
struct addrinfo addr;
struct sockaddr_storage ss;
@@ -161,13 +166,6 @@ void set_listen_procname(struct listen_endpoint *listen_socket)
}
/* At least MacOS does not know these two options, so define them to something
* equivalent for our use case */
#ifndef ENONET
#define ENONET EWOULDBLOCK
#endif
/* /MacOS kludge */
/* TCP listener: connections, fork a child for each new connection
* IN:
* endpoint: array of listening endpoint objects
@@ -181,29 +179,11 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
while (1) {
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__FILE__, __LINE__, "accept", errno, strerror(errno));
switch(in_socket) {
case ENETDOWN: /* accept(2) cites all these errnos as "you should retry" */
case EPROTO:
case ENOPROTOOPT:
case EHOSTDOWN:
case ENONET:
case EHOSTUNREACH:
case EOPNOTSUPP:
case ENETUNREACH:
case ECONNABORTED:
continue;
default: /* Otherwise, it's something wrong in our parameters, we fail */
return;
}
}
print_message(msg_fd, "accepted fd %d\n", in_socket);
if (in_socket == -1) continue;
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
switch(fork()) {
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
case 0: /* In child process */
@@ -230,24 +210,20 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
CHECK_ALLOC(listener_pid, "malloc");
tcp_init();
/* Start one process for each listening address */
for (i = 0; i < num_addr_listen; i++) {
listener_pid[i] = fork();
switch(listener_pid[i]) {
/* Log if fork() fails for some reason */
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
/* We're in the child, we have work to do */
case 0:
set_listen_procname(&listen_sockets[i]);
if (listen_sockets[i].type == SOCK_DGRAM)
print_message(msg_config_error, "UDP not supported in sslh-fork\n");
log_message(LOG_ERR, "UDP not (yet?) 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 */
@@ -268,7 +244,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
wait(NULL);
}
/* The actual main() is in sslh_main.c: it's the same for all versions of
/* The actual main is in common.c: it's the same for both version of
* the server
*/
+40 -158
View File
@@ -30,24 +30,18 @@
#include <pcre2.h>
#endif
#include "common.h"
#include "probe.h"
#include "log.h"
#include "tcp-probe.h"
#if HAVE_LIBBSD
#ifdef LIBBSD
#include <bsd/unistd.h>
#endif
#if HAVE_LIBCAP
#include <sys/capability.h>
#endif
#include "common.h"
#include "probe.h"
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
static void printcaps(void) {
#if HAVE_LIBCAP
#ifdef LIBCAP
cap_t caps;
char* desc;
ssize_t len;
@@ -56,7 +50,7 @@ static void printcaps(void) {
desc = cap_to_text(caps, &len);
print_message(msg_config, "capabilities: %s\n", desc);
fprintf(stderr, "capabilities: %s\n", desc);
cap_free(caps);
cap_free(desc);
@@ -65,50 +59,34 @@ static void printcaps(void) {
static void printsettings(void)
{
char buf[NI_MAXHOST + 256]; /* 256 > " family %d %d" for reasonable ints */
char buf[NI_MAXHOST];
int i;
struct sslhcfg_protocols_item *p;
for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i];
if (p->is_unix) {
sprintf(buf, "unix socket: %s", p->host);
} else {
strcpy(buf, "resolve on forward");
if (!p->resolve_on_forward) {
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",
p->name,
buf,
p->service,
p->log_level,
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "",
p->transparent ? "transparent" : ""
);
fprintf(stderr,
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n",
p->name,
sprintaddr(buf, sizeof(buf), p->saddr),
p->service,
p->log_level,
p->saddr->ai_family,
p->saddr->ai_addr->sa_family,
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "",
p->transparent ? "transparent" : ""
);
}
print_message(msg_config,
"timeout: %d\n"
"on-timeout: %s\n"
"UDP hash size: %d\n",
cfg.timeout,
timeout_protocol()->name,
cfg.udp_max_connections);
fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout,
timeout_protocol()->name);
}
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
#ifdef ENABLE_REGEX
{
size_t num_patterns, i;
int error;
int num_patterns, i, error;
pcre2_code** pattern_list;
PCRE2_SIZE error_offset;
PCRE2_UCHAR8 err_str[120];
@@ -125,7 +103,7 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
&error, &error_offset, NULL);
if (!pattern_list[i]) {
pcre2_get_error_message(error, err_str, sizeof(err_str));
print_message(msg_config_error, "compiling pattern /%s/:%d:%s at offset %ld\n",
fprintf(stderr, "compiling pattern /%s/:%d:%s at offset %ld\n",
p->regex_patterns[i], error, err_str, error_offset);
exit(1);
}
@@ -137,36 +115,6 @@ 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
*/
@@ -175,21 +123,14 @@ static void config_protocols()
int i;
for (i = 0; i < cfg.protocols_len; i++) {
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
if (p->is_unix) {
check_access_unix_socket(p);
} else if (
!p->resolve_on_forward &&
resolve_split_name(&(p->saddr), p->host, p->port)
) {
print_message(msg_config_error, "cannot resolve %s:%s\n",
p->host, p->port);
if (resolve_split_name(&(p->saddr), p->host, p->port)) {
fprintf(stderr, "cannot resolve %s:%s\n", p->host, p->port);
exit(4);
}
p->probe = get_probe(p->name);
if (!p->probe) {
print_message(msg_config_error, "%s: probe unknown\n", p->name);
fprintf(stderr, "%s: probe unknown\n", p->name);
exit(1);
}
@@ -208,75 +149,25 @@ static void config_protocols()
(const char**) cfg.protocols[i].alpn_protocols,
cfg.protocols[i].alpn_protocols_len);
}
p->timeouts.head = NULL;
p->timeouts.tail = NULL;
}
}
void config_sanity_check(struct sslhcfg_item* cfg)
{
size_t i;
void config_sanity_check(struct sslhcfg_item* cfg) {
if (!cfg->protocols_len) {
fprintf(stderr, "At least one target protocol must be specified.\n");
exit(2);
}
/* If compiling with systemd socket support no need to require listen address */
#ifndef SYSTEMD
if (!cfg->listen_len && !cfg->inetd) {
print_message(msg_config_error, "No listening address specified; use at least one -p option\n");
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
#endif
for (i = 0; i < cfg->protocols_len; ++i) {
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",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
if (cfg->protocols[i].alpn_protocols_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option alpn_protocols is only applicable for tls\n",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
}
if (cfg->protocols[i].is_udp) {
if (cfg->protocols[i].tfo_ok) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option tfo_ok is not applicable for udp connections\n",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
} else {
if (!strcmp(cfg->protocols[i].name, "wireguard")) {
print_message(msg_config_error, "Wireguard works only with UDP\n");
exit(1);
}
}
}
}
/* 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[])
{
@@ -286,14 +177,15 @@ int main(int argc, char *argv[], char* envp[])
int res, num_addr_listen;
struct listen_endpoint *listen_sockets;
#if HAVE_LIBBSD
#ifdef 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.verbose > 3)
sslhcfg_fprint(stderr, &cfg, 0);
if (cfg.version) {
printf("%s %s\n", server_type, VERSION);
@@ -305,26 +197,25 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd)
{
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
cfg.verbose = 0;
start_shoveler(0);
exit(0);
}
printsettings();
if (cfg.verbose)
printsettings();
num_addr_listen = start_listen_sockets(&listen_sockets);
#ifdef SYSTEMD
if (num_addr_listen < 1) {
print_message(msg_config_error, "No listening sockets found, restart sockets or specify addresses in config\n");
fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n");
exit(1);
}
#endif
if (!cfg.foreground) {
if (fork() > 0) exit(0); /* Detach */
close_std();
/* New session -- become group leader */
if (getuid() == 0) {
@@ -341,22 +232,13 @@ int main(int argc, char *argv[], char* envp[])
/* Open syslog connection before we drop privs/chroot */
setup_syslog(argv[0]);
/* Open log file for writing */
setup_logfile();
if (cfg.user || cfg.chroot)
drop_privileges(cfg.user, cfg.chroot);
setup_landlock();
printcaps();
print_message(msg_config, "%s %s started\n", server_type, VERSION);
if (cfg.verbose)
printcaps();
main_loop(listen_sockets, num_addr_listen);
close_logfile();
free(listen_sockets);
return 0;
}
+481 -82
View File
@@ -25,88 +25,483 @@
* https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few
* hundred file descriptors, both become very slow, so there is little
* incentive to move to poll() to support more than FD_SETSIZE (which is 1024
* on many Linux. To support large numbers of descriptors efficiently, either use sslh-fork
* or sslh-ev. */
* on many Linux. To support large numbers of descriptors, either use the fork
* version, or we'll have to write a new version based on libev. */
#define __LINUX__
#include <limits.h>
#include "common.h"
#include "probe.h"
#include "tcp-listener.h"
#include "udp-listener.h"
#include "collection.h"
#include "processes.h"
#include "gap.h"
#include "log.h"
static int debug = 0;
const char* server_type = "sslh-select";
/* watcher type for a select() loop */
struct watchers {
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
/* Global state for a select() loop */
struct select_info {
int max_fd; /* Highest fd number to pass to select() */
int num_probing; /* Number of connections currently probing
* We use this to know if we need to time out of
* select() */
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
cnx_collection* collection; /* Collection of connections linked to this loop */
time_t next_timeout; /* time at which next UDP connection times out */
};
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
int num_addr_listen)
static int tidy_connection(struct connection *cnx, struct select_info* fd_info)
{
*w = malloc(sizeof(**w));
CHECK_ALLOC(*w, "malloc");
int i;
fd_set* fds = &fd_info->fds_r;
fd_set* fds2 = &fd_info->fds_w;
memset(*w, 0, sizeof(**w));
FD_ZERO(&(*w)->fds_r);
FD_ZERO(&(*w)->fds_w);
for (i = 0; i < 2; i++) {
if (cnx->q[i].fd != -1) {
if (cfg.verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
for (int i = 0; i < num_addr_listen; i++) {
watchers_add_read(*w, listen_sockets[i].socketfd);
set_nonblock(listen_sockets[i].socketfd);
}
}
void watchers_add_read(watchers* w, int fd)
{
FD_SET(fd, &w->fds_r);
if (fd + 1 > w->max_fd)
w->max_fd = fd + 1;
}
void watchers_del_read(watchers* w, int fd)
{
FD_CLR(fd, &w->fds_r);
}
void watchers_add_write(watchers* w, int fd)
{
FD_SET(fd, &w->fds_w);
if (fd > w->max_fd)
w->max_fd = fd + 1;
}
void watchers_del_write(watchers* w, int fd)
{
FD_CLR(fd, &w->fds_w);
}
/* /end watchers */
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
* and FD_CLR. Need to drop connections if we go above that limit */
static int fd_out_of_range(int fd) {
if (fd >= FD_SETSIZE) {
print_message(msg_system_error, "too many open file descriptor to monitor them all -- dropping connection\n");
return 1;
FD_CLR(cnx->q[i].fd, fds);
FD_CLR(cnx->q[i].fd, fds2);
close(cnx->q[i].fd);
if (cnx->q[i].deferred_data)
free(cnx->q[i].deferred_data);
}
}
collection_remove_cnx(fd_info->collection, cnx);
return 0;
}
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
* and FD_CLR. Need to drop connections if we go above that limit */
static int fd_is_in_range(int fd) {
if (fd >= FD_SETSIZE) {
log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n");
return 0;
}
return 1;
}
/* 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 */
static struct connection* accept_new_connection(int listen_socket, struct cnx_collection *collection)
{
int in_socket, res;
if (cfg.verbose) fprintf(stderr, "accepting from %d\n", listen_socket);
in_socket = accept(listen_socket, 0, 0);
CHECK_RES_RETURN(in_socket, "accept", NULL);
if (!fd_is_in_range(in_socket)) {
close(in_socket);
return NULL;
}
res = set_nonblock(in_socket);
if (res == -1) {
close(in_socket);
return NULL;
}
struct connection* cnx = collection_alloc_cnx_from_fd(collection, in_socket);
if (!cnx) {
close(in_socket);
return NULL;
}
return cnx;
}
/* Connect queue 1 of connection to SSL; returns new file descriptor */
static int connect_queue(struct connection* cnx,
struct select_info* fd_info)
{
struct queue *q = &cnx->q[1];
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
if ((q->fd != -1) && fd_is_in_range(q->fd)) {
log_connection(NULL, cnx);
flush_deferred(q);
if (q->deferred_data) {
FD_SET(q->fd, &fd_info->fds_w);
FD_CLR(cnx->q[0].fd, &fd_info->fds_r);
}
FD_SET(q->fd, &fd_info->fds_r);
collection_add_fd(fd_info->collection, cnx, q->fd);
return q->fd;
} else {
tidy_connection(cnx, fd_info);
return -1;
}
}
/* shovels data from active fd to the other
returns after one socket closed or operation would block
*/
static void shovel(struct connection *cnx, int active_fd, struct select_info* fd_info)
{
struct queue *read_q, *write_q;
read_q = &cnx->q[active_fd];
write_q = &cnx->q[1-active_fd];
if (cfg.verbose)
fprintf(stderr, "activity on fd%d\n", read_q->fd);
switch(fd2fd(write_q, read_q)) {
case -1:
case FD_CNXCLOSED:
tidy_connection(cnx, fd_info);
break;
case FD_STALLED:
FD_SET(write_q->fd, &fd_info->fds_w);
FD_CLR(read_q->fd, &fd_info->fds_r);
break;
default: /* Nothing */
break;
}
}
/* shovels data from one fd to the other and vice-versa
returns after one socket closed
*/
static void shovel_single(struct connection *cnx)
{
fd_set fds_r, fds_w;
int res, i;
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
FD_ZERO(&fds_r);
FD_ZERO(&fds_w);
while (1) {
for (i = 0; i < 2; i++) {
if (cnx->q[i].deferred_data_size) {
FD_SET(cnx->q[i].fd, &fds_w);
FD_CLR(cnx->q[1-i].fd, &fds_r);
} else {
FD_CLR(cnx->q[i].fd, &fds_w);
FD_SET(cnx->q[1-i].fd, &fds_r);
}
}
res = select(
max_fd,
&fds_r,
&fds_w,
NULL,
NULL
);
CHECK_RES_DIE(res, "select");
for (i = 0; i < 2; i++) {
if (FD_ISSET(cnx->q[i].fd, &fds_w)) {
res = flush_deferred(&cnx->q[i]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (cfg.verbose)
fprintf(stderr, "%s socket closed\n", i ? "server" : "client");
return;
}
}
if (FD_ISSET(cnx->q[i].fd, &fds_r)) {
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
if (!res) {
if (cfg.verbose)
fprintf(stderr, "socket closed\n");
return;
}
}
}
}
}
/* Child process that makes internal connection and proxies
*/
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);
if (in_socket == -1) {
in_socket = cnx->q[0].fd;
} else {
close(cnx->q[0].fd);
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;
log_connection(NULL, cnx);
shovel_single(cnx);
close(in_socket);
close(out_socket);
if (cfg.verbose)
fprintf(stderr, "connection closed down\n");
exit(0);
}
/* Removes cnx from probing list */
static void remove_probing_cnx(struct select_info* fd_info, struct connection* cnx)
{
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
fd_info->num_probing--;
}
static void add_probing_cnx(struct select_info* fd_info, struct connection* cnx)
{
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
fd_info->num_probing++;
}
/* Process read activity on a socket in probe state
* IN/OUT cnx: connection data, updated if connected
* IN/OUT info: updated if connected
* */
static void probing_read_process(struct connection* cnx,
struct select_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();
if (cfg.verbose)
log_message(LOG_INFO,
"timed out, connect to %s\n",
cnx->proto->name);
} else {
res = probe_client_protocol(cnx);
if (res == PROBE_AGAIN)
return;
}
remove_probing_cnx(fd_info, cnx);
cnx->state = ST_SHOVELING;
/* 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: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
default: /* parent */
break;
}
tidy_connection(cnx, fd_info);
res = -1;
} else {
res = connect_queue(cnx, fd_info);
}
if (res >= fd_info->max_fd)
fd_info->max_fd = res + 1;;
}
/* Returns the queue index that contains the specified file descriptor */
int active_queue(struct connection* cnx, int fd)
{
if (cnx->q[0].fd == fd) return 0;
if (cnx->q[1].fd == fd) return 1;
log_message(LOG_ERR, "file descriptor %d not found in connection object\n", fd);
return -1;
}
/* Process a connection that is active in read */
static void tcp_read_process(struct select_info* fd_info,
int fd)
{
if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd);
cnx_collection* collection = fd_info->collection;
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
/* Determine active queue (0 or 1): if fd is that of q[1], active_q = 1,
* otherwise it's 0 */
int active_q = active_queue(cnx, fd);
switch (cnx->state) {
case ST_PROBING:
if (active_q == 1) {
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
dump_connection(cnx);
exit(1);
}
probing_read_process(cnx, fd_info);
break;
case ST_SHOVELING:
shovel(cnx, active_q, fd_info);
break;
default: /* illegal */
log_message(LOG_ERR, "Illegal connection state %d\n", cnx->state);
dump_connection(cnx);
exit(1);
}
}
static void cnx_read_process(struct select_info* fd_info, int fd)
{
cnx_collection* collection = fd_info->collection;
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
switch (cnx->type) {
case SOCK_STREAM:
tcp_read_process(fd_info, fd);
break;
case SOCK_DGRAM:
udp_s2c_forward(cnx);
break;
default:
log_message(LOG_ERR, "cnx_read_process: Illegal connection type %d\n", cnx->type);
dump_connection(cnx);
exit(1);
}
}
/* Process a connection that is active in write */
static void cnx_write_process(struct select_info* fd_info, int fd)
{
if (debug) fprintf(stderr, "cnx_write_process fd %d\n", fd);
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd);
int res;
int queue = active_queue(cnx, fd);
res = flush_deferred(&cnx->q[queue]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (cnx->state == ST_PROBING) remove_probing_cnx(fd_info, cnx);
tidy_connection(cnx, fd_info);
} else {
/* If no deferred data is left, stop monitoring the fd
* for write, and restart monitoring the other one for reads*/
if (!cnx->q[queue].deferred_data_size) {
FD_CLR(cnx->q[queue].fd, &fd_info->fds_w);
FD_SET(cnx->q[1-queue].fd, &fd_info->fds_r);
}
}
}
/* Process a connection that accepts a socket
* (For UDP, this means all traffic coming from remote clients)
* */
void cnx_accept_process(struct select_info* fd_info, struct listen_endpoint* listen_socket)
{
int fd = listen_socket->socketfd;
int type = listen_socket->type;
struct connection* cnx;
int new_fd;
if (debug) fprintf(stderr, "cnx_accept_process fd %d\n", fd);
switch (type) {
case SOCK_STREAM:
cnx = accept_new_connection(fd, fd_info->collection);
if (cnx) {
add_probing_cnx(fd_info, cnx);
new_fd = cnx->q[0].fd;
}
break;
case SOCK_DGRAM:
new_fd = udp_c2s_forward(fd, fd_info->collection, fd_info->max_fd);
fprintf(stderr, "new_fd %d\n", new_fd);
if (new_fd == -1)
return;
break;
default:
log_message(LOG_ERR, "Inconsistent cnx type: %d\n", type);
exit(1);
return;
}
FD_SET(new_fd, &fd_info->fds_r);
if (new_fd >= fd_info->max_fd)
fd_info->max_fd = new_fd + 1;
}
/* Check all connections to see if a UDP connections has timed out, then free
* it. At the same time, keep track of the closest, next timeout. Only do the
* search through connections if that timeout actually happened. If the
* connection that would have timed out has had activity, it doesn't matter: we
* go through connections to find the next timeout, which was needed anyway. */
static void udp_timeouts(struct select_info* fd_info)
{
time_t now = time(NULL);
if (now < fd_info->next_timeout) return;
time_t next_timeout = INT_MAX;
for (int i = 0; i < fd_info->max_fd; i++) {
/* if it's either in read or write set, there is a connection
* behind that file descriptor */
if (FD_ISSET(i, &fd_info->fds_r) || FD_ISSET(i, &fd_info->fds_w)) {
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, i);
if (cnx) {
time_t timeout = udp_timeout(cnx);
if (!timeout) continue; /* Not a UDP connection */
if (cnx && (timeout <= now)) {
if (cfg.verbose > 3)
fprintf(stderr, "timed out UDP %d\n", cnx->target_sock);
close(cnx->target_sock);
FD_CLR(i, &fd_info->fds_r);
FD_CLR(i, &fd_info->fds_w);
collection_remove_cnx(fd_info->collection, cnx);
} else {
if (timeout < next_timeout) next_timeout = timeout;
}
}
}
}
if (next_timeout != INT_MAX)
fd_info->next_timeout = next_timeout;
}
/* Main loop: the idea is as follow:
* - fds_r and fds_w contain the file descriptors to monitor in read and write
@@ -124,42 +519,48 @@ 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 select_info fd_info = {0};
fd_set readfds, writefds; /* working read and write fd sets */
struct timeval tv;
int i, res;
fd_info.num_probing = 0;
FD_ZERO(&fd_info.fds_r);
FD_ZERO(&fd_info.fds_w);
fd_info.probing_list = gap_init(0);
udp_init(&fd_info);
tcp_init();
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
for (i = 0; i < num_addr_listen; i++) {
FD_SET(listen_sockets[i].socketfd, &fd_info.fds_r);
set_nonblock(listen_sockets[i].socketfd);
}
fd_info.max_fd = listen_sockets[num_addr_listen-1].socketfd + 1;
fd_info.collection = collection_init(fd_info.watchers->max_fd);
fd_info.collection = collection_init(fd_info.max_fd);
while (1)
{
memset(&tv, 0, sizeof(tv));
tv.tv_sec = cfg.timeout;
memcpy(&readfds, &fd_info.watchers->fds_r, sizeof(readfds));
memcpy(&writefds, &fd_info.watchers->fds_w, sizeof(writefds));
memcpy(&readfds, &fd_info.fds_r, sizeof(readfds));
memcpy(&writefds, &fd_info.fds_w, sizeof(writefds));
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, &readfds, &writefds,
if (cfg.verbose)
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n",
fd_info.max_fd, fd_info.num_probing);
res = select(fd_info.max_fd, &readfds, &writefds,
NULL, fd_info.num_probing ? &tv : NULL);
if (res < 0)
perror("select");
/* UDP timeouts: clear out connections after some idle time */
udp_timeouts(&fd_info);
/* 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);
cnx_accept_process(&fd_info, &listen_sockets[i]);
/* don't also process it as a read socket */
FD_CLR(listen_sockets[i].socketfd, &readfds);
@@ -167,11 +568,8 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
}
/* Check all sockets for write activity */
for (i = 0; i < fd_info.watchers->max_fd; i++) {
/* Check if it's active AND currently monitored (if a connection
* died, it gets tidied, which closes both sockets, but writefs does
* not know about that */
if (FD_ISSET(i, &writefds) && FD_ISSET(i, &fd_info.watchers->fds_w)) {
for (i = 0; i < fd_info.max_fd; i++) {
if (FD_ISSET(i, &writefds)) {
cnx_write_process(&fd_info, i);
}
}
@@ -180,23 +578,24 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
for (i = 0; i < fd_info.num_probing; i++) {
struct connection* cnx = gap_get(fd_info.probing_list, i);
if (!cnx || cnx->state != ST_PROBING) {
print_message(msg_int_error, "Inconsistent probing: cnx=0x%p\n", cnx);
log_message(LOG_ERR, "Inconsistent probing: cnx=%0xp\n", cnx);
if (cnx)
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx->state);
log_message(LOG_ERR, "Inconsistent probing: state=%d\n", cnx);
exit(1);
}
if (cnx->probe_timeout < time(NULL)) {
print_message(msg_fd, "timeout slot %d\n", i);
if (cfg.verbose)
fprintf(stderr, "timeout slot %d\n", i);
probing_read_process(cnx, &fd_info);
}
}
/* Check all sockets for read activity */
for (i = 0; i < fd_info.watchers->max_fd; i++) {
for (i = 0; i < fd_info.max_fd; i++) {
/* Check if it's active AND currently monitored (if a connection
* died, it gets tidied, which closes both sockets, but readfs does
* not know about that */
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.watchers->fds_r)) {
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.fds_r)) {
cnx_read_process(&fd_info, i);
}
}
@@ -205,12 +604,12 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
void start_shoveler(int listen_socket) {
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
fprintf(stderr, "inetd mode is not supported in select mode\n");
exit(1);
}
/* The actual main is in sslh-main.c: it's the same for all versions of
/* The actual main is in common.c: it's the same for both version of
* the server
*/
+11 -15
View File
@@ -6,7 +6,7 @@
=head1 SYNOPSIS
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [B<-v> I<n>] [-i] [-V] [-f] [-n]
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--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]
=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, TLS, SSH, OpenVPN, tinc, XMPP are
Probes for HTTP, SSL, 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,10 +102,8 @@ 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<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.
This works on Linux only and involves B<iptables> settings.
Refer to the README for more information.
=item B<-p> I<listening address>, B<--listen> I<listening address>
@@ -125,11 +123,10 @@ 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 TLS handshakes and will
Also, B<sslh> probes for SSLv3 (or TLSv1) handshake 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>
@@ -167,10 +164,9 @@ 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> I<n>
=item B<-v>, B<--verbose>
Override all verboseness. Refer to B<example.cfg> for all
verbose sub-options.
Increase verboseness.
=item B<-n>, B<--numeric>
@@ -237,8 +233,8 @@ detailed explanation of the variables used by B<sslh>.
=head1 SEE ALSO
The latest version is available from
L<https://github.com/yrutschle/sslh>. There you can find a more
detailed and recent documentation.
L<http://www.rutschle.net/tech/sslh>, and can be tracked
from L<http://freecode.com/projects/sslh>.
=head1 AUTHOR
+3 -63
View File
@@ -25,34 +25,7 @@ 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;
description: "Print configuration errors" },
{ name: "verbose-connections"; type: "int"; default: 3;
description: "Trace established incoming address to forward address" },
{ name: "verbose-connections-try"; type: "int"; default: 0;
description: "Connection errors" },
{ name: "verbose-connections-error"; type: "int"; default: 3;
description: "Connection attempts towards targets" },
{ name: "verbose-fd"; type: "int"; default: 0;
description: "File descriptor activity, open/close/whatnot" },
{ name: "verbose-packets"; type: "int"; default: 0;
description: "Hexdump packets on which probing is done" },
{ name: "verbose-probe-info"; type: "int"; default: 0;
description: "Trace the probe process" },
{ name: "verbose-probe-error"; type: "int"; default: 3;
description: "Failures and problems during probing" },
{ name: "verbose-system-error"; type: "int"; default: 3;
description: "System call failures" },
{ name: "verbose-int-error"; type: "int"; default: 3;
description: "Internal errors that should never happen" },
{ name: "verbose"; type: "int"; default: 0; short: "v"; },
{ name: "version"; type: "bool"; default: false;
short: "V";
description: "Print version information and exit"; },
@@ -70,8 +43,6 @@ config: {
{ name: "timeout"; type: "int"; default: 5;
short: "t";
description: "Set up timeout before connecting to default target"; },
{ name: "udp_max_connections"; type: "int"; default: 1024;
description: "Number of concurrent UDP connections"; },
{ name: "user"; type: "string"; optional: true;
short: "u";
description: "Username to change to after set-up"; },
@@ -83,8 +54,6 @@ config: {
description: "Root to change to after set-up"; },
{ name: "syslog_facility"; type: "string"; default: "auth";
description: "Facility to syslog to"; },
{ name: "logfile"; type: "string"; optional: true;
description: "Log messages to a file" },
{ name: "on-timeout"; type: "string"; default: "ssh";
description: "Target to connect to when timing out"; },
@@ -98,7 +67,6 @@ config: {
{ name: "host"; type: "string"; var: true; },
{ name: "port"; type: "string"; var: true; },
{ name: "is_udp"; type: "bool"; default: false },
{ name: "is_unix"; type: "bool"; default: false },
{ name: "keepalive"; type: "bool"; default: false; }
)
},
@@ -111,16 +79,13 @@ 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 },
{ name: "tfo_ok"; type: "bool"; default: false;
description: "Set to true if this protocol supports TCP FAST OPEN" },
{ name: "transparent"; type: "bool"; default: false;
{ name: "transparent"; type: "bool"; default: false;
description: "Set to proxy this protocol transparently" },
{ name: "resolve_on_forward"; type: "bool"; default: false;
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: "sni_hostnames",
@@ -136,13 +101,11 @@ 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: "timeouts"; type: "runtime"; c_type: "dl_list" }
{ name: "data"; type: "runtime"; c_type: "void*" }
)
}
)
@@ -229,18 +192,6 @@ cl_groups: (
{ path: "tfo_ok"; value: 1 }
);
},
{ name: "wireguard"; pattern: "(.+):(\w+)"; description: "Set up WireGuard target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "wireguard" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 },
{ path: "tfo_ok"; value: 1 }
);
},
{ name: "xmpp"; pattern: "(.+):(\w+)"; description: "Set up XMPP target";
list: "protocols";
override: "name";
@@ -296,17 +247,6 @@ cl_groups: (
{ path: "log_level"; value: 1 }
);
},
{ name: "msrdp"; pattern: "(.+):(\w+)"; description: "Set up msrdp target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "msrdp" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
},
{ name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target";
list: "protocols";
override: "name";
+86 -254
View File
@@ -1,72 +1,37 @@
#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)
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")
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");
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(err_log,
"systemd-sslh-generator: %s%d%s\n",
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
@@ -75,247 +40,114 @@ 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 (int i = 0; i < len; i++) {
CHECK_ALLOC(*listen, "malloc");
for (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(err_log,
"systemd-sslh-generator: 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(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
(*listen)[i] = resolve_listen(hostname, port);
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
CHECK_ALLOC((*listen)[i], "malloc");
strcpy((*listen)[i], resolve_listen(hostname, port));
}
}
}
}
return len;
}
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
int i;
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh@%s.service\n"
"Before=sslh.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",
cfg,
source,
cfg);
for (int i = 0; i < num_addr; i++) {
source);
for (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) {
int status = 0;
const char *config_dir = "/etc/sslh/";
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
DIR *d = opendir(config_dir);
FileList *fa = NULL;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
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;
}
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';
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
//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));
}
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
}
} while((fa = fa->prev));
free_file_list(fa_ref);
return status;
}
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");
}
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
}
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 {
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}
+1 -1
View File
@@ -396,7 +396,7 @@ if ($RB_RESOLVE_ADDRESS) {
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v 3 -f -u $user --listen blahblah.nonexistent:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
exec "./sslh-select -v 3 -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff. Show more