mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-25 01:06:20 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40c616e94c | ||
|
|
555717e345 | ||
|
|
02573eb44b | ||
|
|
d166b8977c | ||
|
|
4c3b52dda5 | ||
|
|
c981ae9853 | ||
|
|
8a0de7b628 | ||
|
|
5154630fe0 | ||
|
|
63d5ecddca | ||
|
|
e412811ff1 | ||
|
|
7e3f723699 | ||
|
|
a1db2e8a92 | ||
|
|
72c743e1e1 | ||
|
|
5cba44f5fa | ||
|
|
9a36854ed3 | ||
|
|
b19f8a6046 | ||
|
|
bb685f8467 | ||
|
|
f418ae6128 | ||
|
|
a6df18527c | ||
|
|
e690cb5622 | ||
|
|
64c3e0ed1e | ||
|
|
b971f3edcd | ||
|
|
d2ec01c072 | ||
|
|
fb8fe57bd8 | ||
|
|
79c8af6ed1 | ||
|
|
c4e7261a51 | ||
|
|
7e2bb7f01f | ||
|
|
4cf3749e73 | ||
|
|
8d124e1085 | ||
|
|
a6c5e07d69 | ||
|
|
aa17061e26 | ||
|
|
9d10989d55 | ||
|
|
8b604a3db7 | ||
|
|
5168fe081a | ||
|
|
7b923f793e | ||
|
|
a4db163a69 | ||
|
|
b36486bb6d | ||
|
|
d16d132f0e | ||
|
|
3fffce323d | ||
|
|
83478818df | ||
|
|
750e828d49 | ||
|
|
82aeedefcd | ||
|
|
f6fe735171 | ||
|
|
78827d75fe | ||
|
|
7228c0ebc3 | ||
|
|
da194f15f0 | ||
|
|
d23a537d62 | ||
|
|
aa14090bcc | ||
|
|
006706a901 | ||
|
|
f9831df8bc | ||
|
|
c78a50c1d7 | ||
|
|
92e8a3e256 | ||
|
|
cd664574f1 | ||
|
|
449fabba51 | ||
|
|
cd5d75fed9 | ||
|
|
50f5af394b | ||
|
|
8ac93c3e9d | ||
|
|
43bd660df2 | ||
|
|
35036c94c7 | ||
|
|
21d00bd29d | ||
|
|
32b065d895 | ||
|
|
cd7afaa00d | ||
|
|
53ae8bb913 | ||
|
|
97a67500ea | ||
|
|
33d73dd514 | ||
|
|
63b503e27f | ||
|
|
2e0d6b6e8e | ||
|
|
4625883b7f | ||
|
|
07ceb99280 | ||
|
|
5fa03ec9a3 | ||
|
|
9ce9b5cd82 | ||
|
|
97810cf0b2 | ||
|
|
f8684fc9d6 | ||
|
|
5def70dff5 | ||
|
|
478e1fc92f | ||
|
|
96b1aa86d8 | ||
|
|
b1517f27a8 | ||
|
|
454a261c95 | ||
|
|
9c3274359a | ||
|
|
a71de786d6 | ||
|
|
dd167d13a3 | ||
|
|
759e68c8d2 | ||
|
|
78bc954769 | ||
|
|
3f5c81d2f6 | ||
|
|
ff810d41b0 | ||
|
|
d922086f53 | ||
|
|
1e0578c082 | ||
|
|
87577ae5f6 | ||
|
|
875fa488c9 | ||
|
|
91d148f66c | ||
|
|
66f4b18121 | ||
|
|
bccb78e1a3 | ||
|
|
6f497b6c8b | ||
|
|
4e6145576e | ||
|
|
58ed185316 | ||
|
|
122649ce3c | ||
|
|
31b096a0f4 | ||
|
|
adaf407b8a | ||
|
|
4e72800f2e | ||
|
|
18eeaa579a | ||
|
|
80f2d758a4 | ||
|
|
9798bdcaa0 | ||
|
|
93df6975ed | ||
|
|
44a78bc515 | ||
|
|
711c11c820 | ||
|
|
ae117097ea | ||
|
|
0a23ca133e | ||
|
|
207d482189 | ||
|
|
d4d9dbb8e7 | ||
|
|
8ddff5e388 | ||
|
|
a80e2ceb27 | ||
|
|
e28fa91b0f | ||
|
|
17313100b5 | ||
|
|
4cd3ab8958 | ||
|
|
5ec1f7eb98 | ||
|
|
5b21375087 | ||
|
|
d57a155bf4 | ||
|
|
ec033efbbc | ||
|
|
3389000ff3 | ||
|
|
25abd765cb | ||
|
|
2cdd60dd18 | ||
|
|
b0aeeff465 | ||
|
|
c9eff6e38d | ||
|
|
0cde3d794a | ||
|
|
ed48d3964f | ||
|
|
7b0d486d3d | ||
|
|
0e118a109c | ||
|
|
9955cc6560 | ||
|
|
caa62875c1 | ||
|
|
c8fce0a02f | ||
|
|
4277d27063 | ||
|
|
16bf1a6aca | ||
|
|
4f0f5017bc | ||
|
|
70b31a48d9 | ||
|
|
66caf8a31b | ||
|
|
4d3cc9c925 | ||
|
|
2e11001087 | ||
|
|
6ea7d48f86 | ||
|
|
3fb1201b3f | ||
|
|
e9e7ada069 | ||
|
|
e6cbbe9511 | ||
|
|
e5f16b93ce | ||
|
|
673c40954e | ||
|
|
f7b6f669a4 | ||
|
|
dbad46a358 | ||
|
|
098a55fd1d | ||
|
|
5e27806545 | ||
|
|
317c08604b | ||
|
|
3013658b20 | ||
|
|
a704c7f7f5 | ||
|
|
1a3341c2a4 | ||
|
|
4a6bbda60d | ||
|
|
fa848f2ae9 | ||
|
|
6aa19d080a | ||
|
|
a43385b8db | ||
|
|
1261a5f4c7 | ||
|
|
40da147efd | ||
|
|
e4936454c5 | ||
|
|
63f9c4a582 | ||
|
|
0e45107797 | ||
|
|
4584e719e1 | ||
|
|
75e426c80f | ||
|
|
e40d43bea5 | ||
|
|
2196146224 | ||
|
|
2f822741b3 | ||
|
|
760def3444 | ||
|
|
4b885b4a2c | ||
|
|
ce23f202b7 | ||
|
|
82b8ba547e | ||
|
|
8e38d56167 | ||
|
|
ea3b6794e1 | ||
|
|
e42f165ef5 | ||
|
|
37d2756703 | ||
|
|
ec25ab56e8 | ||
|
|
7fb65ad0ac | ||
|
|
c43882c85f | ||
|
|
4a4c571116 | ||
|
|
c049885758 | ||
|
|
de0ec959d9 | ||
|
|
e7df8eeaa1 | ||
|
|
8af87ebbad | ||
|
|
bf57d63c3a | ||
|
|
862e33cfec | ||
|
|
24e7f46a43 | ||
|
|
bf2053eb79 | ||
|
|
1ad450a444 | ||
|
|
300e1916c3 | ||
|
|
e678428334 | ||
|
|
edc42ca13b | ||
|
|
c41ec489e0 | ||
|
|
79f49f4481 | ||
|
|
8e130882fc | ||
|
|
030ef64b99 | ||
|
|
cf4f4cbebe | ||
|
|
905ac95ca1 | ||
|
|
da3b51e056 | ||
|
|
308b54aa8a | ||
|
|
987643878f | ||
|
|
46e741e9c2 | ||
|
|
c3d019284d | ||
|
|
4c570e8d57 | ||
|
|
0a00220b42 | ||
|
|
a0f9fc164e | ||
|
|
517e4ad5b4 | ||
|
|
b258b0e0f7 | ||
|
|
02f6c6999d | ||
|
|
41a914b350 | ||
|
|
a487ad4cbe | ||
|
|
21ed247acb | ||
|
|
dfa5d3cedb | ||
|
|
74ce2acdc5 | ||
|
|
0e3242c115 | ||
|
|
5715a9f0bd | ||
|
|
de474e1d07 | ||
|
|
cb199ed3c6 | ||
|
|
7485afe397 | ||
|
|
417722e3c1 | ||
|
|
9fca586735 | ||
|
|
0613d412a2 | ||
|
|
b2ee07655a | ||
|
|
465ecdda5b | ||
|
|
3edf6a9d22 | ||
|
|
7a88a50c32 | ||
|
|
e7a5bbc4e1 | ||
|
|
40557c58ad | ||
|
|
f0e1aaf82c | ||
|
|
f51781664c | ||
|
|
5948270b95 | ||
|
|
758be2a8ab | ||
|
|
654ff0b5b0 | ||
|
|
15a5073559 | ||
|
|
c2b6bf246c | ||
|
|
6366d50b89 | ||
|
|
5b93e4ab55 | ||
|
|
1c63b06cc8 | ||
|
|
71c617050d | ||
|
|
0409775a1a | ||
|
|
b72baa0622 | ||
|
|
790d639ad0 | ||
|
|
e884fc616d | ||
|
|
7e63dedca3 | ||
|
|
49d4080afd | ||
|
|
be4e126f3a | ||
|
|
34bcc2bbc8 | ||
|
|
90975fd6c3 | ||
|
|
f91f16d753 | ||
|
|
dae8101a50 | ||
|
|
68fc10aed0 | ||
|
|
49c136691c | ||
|
|
adb27aa4a3 | ||
|
|
9ff9723278 | ||
|
|
52a9356c35 | ||
|
|
ac3b43d4bd | ||
|
|
368f286ce5 | ||
|
|
05a835ff1f | ||
|
|
0a3d5874b4 | ||
|
|
a42d4f8049 | ||
|
|
1e33455fe7 | ||
|
|
a3d9df87cb | ||
|
|
247d60d8e6 | ||
|
|
7d820049a2 | ||
|
|
09597bfa42 | ||
|
|
41e3b0cd1d | ||
|
|
76d0351452 | ||
|
|
fccaa5fa9f | ||
|
|
f077101835 | ||
|
|
f3230b4a94 | ||
|
|
c12f7a1ade | ||
|
|
ebeabb6c18 | ||
|
|
8a1dae8c22 | ||
|
|
07719f55b8 | ||
|
|
6c94bf71b8 | ||
|
|
0d9e004d32 | ||
|
|
0c388cb367 | ||
|
|
9f99f296b1 | ||
|
|
8b6e06e6c7 | ||
|
|
5cdff8c558 | ||
|
|
72438dc1ae | ||
|
|
e67d6ff905 | ||
|
|
0af37a1bed | ||
|
|
08ce89b6e2 | ||
|
|
99d4a6d4a2 | ||
|
|
2c93a015ea | ||
|
|
95a6577cda |
No files matched your search
@@ -1,4 +1,86 @@
|
||||
v2.0:
|
||||
New sslh-ev: this is functionaly 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.
|
||||
Probes specified in the `protocols`
|
||||
configuration entry are tried on incoming packets,
|
||||
TCP or UDP, and forwarded based on the input
|
||||
protocol (an incoming TCP connection will be
|
||||
forwarded as TCP, and same with UDP).
|
||||
This has been tested with DNS as shown in udp.cfg:
|
||||
incoming packets that contain my domain name are
|
||||
assumed to be a DNS request and forwarded
|
||||
accordingly. Note this could cause problems if
|
||||
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 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.
|
||||
|
||||
Added probes for UDP protocols QUICK and Teamspeak.
|
||||
|
||||
Added probes for syslog protocol.
|
||||
|
||||
sslh-select refactored to change linear searches
|
||||
through connections to linear searches through
|
||||
fd_set.
|
||||
|
||||
Fixed a libconfig call to support libconfig 1.7.3.
|
||||
|
||||
Added symbol to support libconfig 1.4.9, still in
|
||||
use in CentOS7.
|
||||
|
||||
Warn about unknown settings in the configuration
|
||||
file.
|
||||
|
||||
Added per-protocol `transparent` option. sslh-fork
|
||||
drops the capability after creating the server-side
|
||||
transparent socket. Transparent now uses CAP_NET_RAW
|
||||
instead of CAP_NET_ADMIN.
|
||||
|
||||
Removed compile-time option to use POSIX regex. Now
|
||||
regex must be PCRE2 (Perl-Compatible). This was in
|
||||
fact the case since v1.21, as PCRE are used to parse
|
||||
the config file.
|
||||
|
||||
v1.21: 11JUL2020
|
||||
WARNING:
|
||||
Moved configuration and command-line management to
|
||||
use conf2struct. Changes are:
|
||||
* `--ssl` and using `name: 'ssl'` in config file is no longer supported, use `tls` instead.
|
||||
* command line option <-F|--config> no longer defaults to /etc/sslh.cfg, so you have to
|
||||
specify it explicitly.
|
||||
* command line option <-v|--verbose> takes a mandatory integer parameter
|
||||
|
||||
Added TCP_FASTOPEN support for client sockets (if
|
||||
tfo_ok is specified in their configuration) and for
|
||||
listenint socket, if all client protocols support it.
|
||||
@@ -8,12 +90,6 @@ v1.21: 11JUL2020
|
||||
than that many bytes have been received (mostly for
|
||||
regex)
|
||||
|
||||
Moved configuration and command-line management to
|
||||
use conf2struct. Changes are:
|
||||
* command line option <-F|--config> no longer defaults to /etc/sslh.cfg, so you have to
|
||||
specify it explicitly.
|
||||
* command line option <-v|--verbose> takes a mandatory integer parameter
|
||||
|
||||
Update Let's Encrypt entry in example.cfg for tls-alpn-01
|
||||
challenges; tls-sni-* challenges are now deprecated.
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
FROM alpine:latest as build
|
||||
|
||||
ADD . /sslh
|
||||
|
||||
RUN \
|
||||
apk add \
|
||||
gcc \
|
||||
libconfig-dev \
|
||||
make \
|
||||
musl-dev \
|
||||
pcre2-dev \
|
||||
perl && \
|
||||
cd /sslh && \
|
||||
make sslh-select && \
|
||||
strip sslh-select
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=build /sslh/sslh-select /sslh
|
||||
|
||||
RUN apk --no-cache add libconfig pcre2
|
||||
|
||||
ENTRYPOINT [ "/sslh", "--foreground"]
|
||||
@@ -1,9 +1,11 @@
|
||||
# Configuration
|
||||
|
||||
VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBPCRE=1 # Use libpcre? (needed for regex on musl)
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
USELIBCAP= # Use libcap?
|
||||
USESYSTEMD= # Make use of systemd socket activation
|
||||
@@ -18,15 +20,24 @@ 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
|
||||
CFLAGS ?=-Wall -g $(CFLAGS_COV)
|
||||
AR ?= ar
|
||||
CFLAGS ?=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
LIBS=
|
||||
OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o
|
||||
LIBS=-lm -lpcre2-8
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o
|
||||
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)
|
||||
|
||||
CONDITIONAL_TARGETS=
|
||||
|
||||
@@ -39,11 +50,6 @@ ifneq ($(strip $(ENABLE_REGEX)),)
|
||||
CPPFLAGS+=-DENABLE_REGEX
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBPCRE)),)
|
||||
CPPFLAGS+=-DLIBPCRE
|
||||
LIBS:=$(LIBS) -lpcreposix
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBCONFIG)),)
|
||||
LIBS:=$(LIBS) -lconfig
|
||||
CPPFLAGS+=-DLIBCONFIG
|
||||
@@ -68,32 +74,40 @@ endif
|
||||
|
||||
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||
|
||||
.c.o: *.h version.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
|
||||
%.o: %.c %.h version.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
$(OBJS_A): $(OBJS)
|
||||
$(AR) rcs $(OBJS_A) $(OBJS)
|
||||
|
||||
version.h:
|
||||
./genver.sh >version.h
|
||||
|
||||
sslh: sslh-fork sslh-select
|
||||
sslh: sslh-fork sslh-select sslh-ev
|
||||
|
||||
$(OBJS): 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-conf.c: sslhconf.cfg
|
||||
|
||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||
conf2struct sslhconf.cfg
|
||||
|
||||
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
|
||||
#strip sslh-fork
|
||||
sslh-fork: version.h Makefile $(FORK_OBJS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
|
||||
|
||||
sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
|
||||
#strip sslh-select
|
||||
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
|
||||
|
||||
systemd-sslh-generator: systemd-sslh-generator.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
||||
|
||||
echosrv: version.h $(OBJS) echosrv.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
|
||||
echosrv-conf.c echosrv-conf.h: echosrv.cfg
|
||||
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)
|
||||
|
||||
$(MAN): sslh.pod Makefile
|
||||
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
|
||||
@@ -104,6 +118,15 @@ release:
|
||||
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
|
||||
|
||||
# Build docker image
|
||||
docker:
|
||||
docker image build -t "sslh:${VERSION}" .
|
||||
docker image tag "sslh:${VERSION}" sslh:latest
|
||||
|
||||
docker-clean:
|
||||
yes | docker image rm "sslh:${VERSION}" sslh:latest
|
||||
yes | docker image prune
|
||||
|
||||
# generic install: install binary and man page
|
||||
install: sslh $(MAN)
|
||||
mkdir -p $(DESTDIR)/$(BINDIR)
|
||||
@@ -122,10 +145,10 @@ uninstall:
|
||||
update-rc.d sslh remove
|
||||
|
||||
distclean: clean
|
||||
rm -f tags sslh-conf.c sslh-conf.h cscope.*
|
||||
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
||||
|
||||
clean:
|
||||
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
rm -f sslh-fork sslh-select sslh-ev echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
|
||||
tags:
|
||||
ctags --globals -T *.[ch]
|
||||
|
||||
@@ -20,9 +20,9 @@ address.
|
||||
|
||||
`sslh` has the bells and whistles expected from a mature
|
||||
daemon: privilege and capabilities dropping, inetd support,
|
||||
systemd support, transparent proxying,
|
||||
chroot, logging, IPv4 and IPv6, a fork-based and a
|
||||
select-based model, and more.
|
||||
systemd support, transparent proxying, chroot, logging,
|
||||
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
|
||||
model, and more.
|
||||
|
||||
Install
|
||||
=======
|
||||
@@ -37,11 +37,56 @@ Please refer to the [configuration guide](doc/config.md).
|
||||
|
||||
|
||||
|
||||
Docker image
|
||||
------------
|
||||
|
||||
How to use
|
||||
|
||||
---
|
||||
|
||||
Build docker image
|
||||
|
||||
make docker
|
||||
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--rm \
|
||||
-it \
|
||||
sslh:latest \
|
||||
--listen=0.0.0.0:443 \
|
||||
--ssh=hostname:22 \
|
||||
--tls=hostname:443
|
||||
```
|
||||
|
||||
docker-compose example
|
||||
|
||||
```
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sslh:
|
||||
image: sslh:latest
|
||||
hostname: sslh
|
||||
ports:
|
||||
- 443:443
|
||||
command: --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
|
||||
depends_on:
|
||||
- nginx
|
||||
- openvpn
|
||||
|
||||
nginx:
|
||||
image: nginx
|
||||
|
||||
openvpn:
|
||||
image: openvpn
|
||||
```
|
||||
|
||||
Comments? Questions?
|
||||
====================
|
||||
|
||||
You can subscribe to the `sslh` mailing list here:
|
||||
<http://rutschle.net/cgi-bin/mailman/listinfo/sslh>
|
||||
<https://lists.rutschle.net/mailman/listinfo/sslh>
|
||||
|
||||
This mailing list should be used for discussion, feature
|
||||
requests, and will be the preferred channel for announcements.
|
||||
|
||||
+3578
-2590
File diff suppressed because it is too large.
Load diff
+198
-230
@@ -1,4 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* argtable3: Declares the main interfaces of the library
|
||||
*
|
||||
* This file is part of the argtable3 library.
|
||||
*
|
||||
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
|
||||
@@ -31,273 +33,239 @@
|
||||
#ifndef ARGTABLE3
|
||||
#define ARGTABLE3
|
||||
|
||||
#include <stdio.h> /* FILE */
|
||||
#include <time.h> /* struct tm */
|
||||
#include <stdio.h> /* FILE */
|
||||
#include <time.h> /* struct tm */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ARG_REX_ICASE 1
|
||||
|
||||
#define ARG_DSTR_SIZE 200
|
||||
#define ARG_CMD_NAME_LEN 100
|
||||
#define ARG_CMD_DESCRIPTION_LEN 256
|
||||
|
||||
#ifndef ARG_REPLACE_GETOPT
|
||||
#define ARG_REPLACE_GETOPT 1 /* use the embedded getopt as the system getopt(3) */
|
||||
#endif /* ARG_REPLACE_GETOPT */
|
||||
|
||||
/* bit masks for arg_hdr.flag */
|
||||
enum
|
||||
{
|
||||
ARG_TERMINATOR=0x1,
|
||||
ARG_HASVALUE=0x2,
|
||||
ARG_HASOPTVALUE=0x4
|
||||
};
|
||||
enum { ARG_TERMINATOR = 0x1, ARG_HASVALUE = 0x2, ARG_HASOPTVALUE = 0x4 };
|
||||
|
||||
typedef void (arg_resetfn)(void *parent);
|
||||
typedef int (arg_scanfn)(void *parent, const char *argval);
|
||||
typedef int (arg_checkfn)(void *parent);
|
||||
typedef void (arg_errorfn)(void *parent, FILE *fp, int error, const char *argval, const char *progname);
|
||||
#if defined(_WIN32)
|
||||
#if defined(argtable3_EXPORTS)
|
||||
#define ARG_EXTERN __declspec(dllexport)
|
||||
#elif defined(argtable3_IMPORTS)
|
||||
#define ARG_EXTERN __declspec(dllimport)
|
||||
#else
|
||||
#define ARG_EXTERN
|
||||
#endif
|
||||
#else
|
||||
#define ARG_EXTERN
|
||||
#endif
|
||||
|
||||
typedef struct _internal_arg_dstr* arg_dstr_t;
|
||||
typedef void* arg_cmd_itr_t;
|
||||
|
||||
typedef void(arg_resetfn)(void* parent);
|
||||
typedef int(arg_scanfn)(void* parent, const char* argval);
|
||||
typedef int(arg_checkfn)(void* parent);
|
||||
typedef void(arg_errorfn)(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname);
|
||||
typedef void(arg_dstr_freefn)(char* buf);
|
||||
typedef int(arg_cmdfn)(int argc, char* argv[], arg_dstr_t res);
|
||||
typedef int(arg_comparefn)(const void* k1, const void* k2);
|
||||
|
||||
/*
|
||||
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
|
||||
* The argtable library requires each arg_xxx struct to have an arg_hdr
|
||||
* struct as its first data member.
|
||||
* The argtable library functions then use this data to identify the
|
||||
* properties of the command line option, such as its option tags,
|
||||
* datatype string, and glossary strings, and so on.
|
||||
* Moreover, the arg_hdr struct contains pointers to custom functions that
|
||||
* are provided by each arg_xxx struct which perform the tasks of parsing
|
||||
* 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 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.
|
||||
*/
|
||||
struct arg_hdr
|
||||
{
|
||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||
const char *shortopts; /* String defining the short 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 */
|
||||
int maxcount; /* Maximum number of occurences if this option accepted */
|
||||
void *parent; /* Pointer to parent arg_xxx struct */
|
||||
arg_resetfn *resetfn; /* Pointer to parent arg_xxx reset function */
|
||||
arg_scanfn *scanfn; /* Pointer to parent arg_xxx scan function */
|
||||
arg_checkfn *checkfn; /* Pointer to parent arg_xxx check function */
|
||||
arg_errorfn *errorfn; /* Pointer to parent arg_xxx error function */
|
||||
void *priv; /* Pointer to private header data for use by arg_xxx functions */
|
||||
};
|
||||
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
|
||||
* The argtable library requires each arg_xxx struct to have an arg_hdr
|
||||
* struct as its first data member.
|
||||
* The argtable library functions then use this data to identify the
|
||||
* properties of the command line option, such as its option tags,
|
||||
* datatype string, and glossary strings, and so on.
|
||||
* Moreover, the arg_hdr struct contains pointers to custom functions that
|
||||
* are provided by each arg_xxx struct which perform the tasks of parsing
|
||||
* 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 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.
|
||||
*/
|
||||
typedef struct arg_hdr {
|
||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||
const char* shortopts; /* String defining the short 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 */
|
||||
int maxcount; /* Maximum number of occurences if this option accepted */
|
||||
void* parent; /* Pointer to parent arg_xxx struct */
|
||||
arg_resetfn* resetfn; /* Pointer to parent arg_xxx reset function */
|
||||
arg_scanfn* scanfn; /* Pointer to parent arg_xxx scan function */
|
||||
arg_checkfn* checkfn; /* Pointer to parent arg_xxx check function */
|
||||
arg_errorfn* errorfn; /* Pointer to parent arg_xxx error function */
|
||||
void* priv; /* Pointer to private header data for use by arg_xxx functions */
|
||||
} arg_hdr_t;
|
||||
|
||||
struct arg_rem
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
};
|
||||
typedef struct arg_rem {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
} arg_rem_t;
|
||||
|
||||
struct arg_lit
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
};
|
||||
typedef struct arg_lit {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
} arg_lit_t;
|
||||
|
||||
struct arg_int
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
int *ival; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_int {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
int* ival; /* Array of parsed argument values */
|
||||
} arg_int_t;
|
||||
|
||||
struct arg_dbl
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
double *dval; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_dbl {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
double* dval; /* Array of parsed argument values */
|
||||
} arg_dbl_t;
|
||||
|
||||
struct arg_str
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char **sval; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_str {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char** sval; /* Array of parsed argument values */
|
||||
} arg_str_t;
|
||||
|
||||
struct arg_rex
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char **sval; /* Array of parsed argument values */
|
||||
};
|
||||
typedef struct arg_rex {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char** sval; /* Array of parsed argument values */
|
||||
} arg_rex_t;
|
||||
|
||||
struct arg_file
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args*/
|
||||
const char **filename; /* Array of parsed filenames (eg: /home/foo.bar) */
|
||||
const char **basename; /* Array of parsed basenames (eg: foo.bar) */
|
||||
const char **extension; /* Array of parsed extensions (eg: .bar) */
|
||||
};
|
||||
typedef struct arg_file {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args*/
|
||||
const char** filename; /* Array of parsed filenames (eg: /home/foo.bar) */
|
||||
const char** basename; /* Array of parsed basenames (eg: foo.bar) */
|
||||
const char** extension; /* Array of parsed extensions (eg: .bar) */
|
||||
} arg_file_t;
|
||||
|
||||
struct arg_date
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
const char *format; /* strptime format string used to parse the date */
|
||||
int count; /* Number of matching command line args */
|
||||
struct tm *tmval; /* Array of parsed time values */
|
||||
};
|
||||
typedef struct arg_date {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
const char* format; /* strptime format string used to parse the date */
|
||||
int count; /* Number of matching command line args */
|
||||
struct tm* tmval; /* Array of parsed time values */
|
||||
} arg_date_t;
|
||||
|
||||
enum {ARG_ELIMIT=1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG};
|
||||
struct arg_end
|
||||
{
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of errors encountered */
|
||||
int *error; /* Array of error codes */
|
||||
void **parent; /* Array of pointers to offending arg_xxx struct */
|
||||
const char **argval; /* Array of pointers to offending argv[] string */
|
||||
};
|
||||
enum { ARG_ELIMIT = 1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG };
|
||||
typedef struct arg_end {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of errors encountered */
|
||||
int* error; /* Array of error codes */
|
||||
void** parent; /* Array of pointers to offending arg_xxx struct */
|
||||
const char** argval; /* Array of pointers to offending argv[] string */
|
||||
} arg_end_t;
|
||||
|
||||
typedef struct arg_cmd_info {
|
||||
char name[ARG_CMD_NAME_LEN];
|
||||
char description[ARG_CMD_DESCRIPTION_LEN];
|
||||
arg_cmdfn* proc;
|
||||
} arg_cmd_info_t;
|
||||
|
||||
/**** arg_xxx constructor functions *********************************/
|
||||
|
||||
struct arg_rem* arg_rem(const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, const char* glossary);
|
||||
|
||||
struct arg_lit* arg_lit0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* glossary);
|
||||
struct arg_lit* arg_lit1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char *glossary);
|
||||
struct arg_lit* arg_litn(const char* shortopts,
|
||||
const char* longopts,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_key* arg_key0(const char* keyword,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
struct arg_key* arg_key1(const char* keyword,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
struct arg_key* arg_keyn(const char* keyword,
|
||||
int flags,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_int* arg_int0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_int* arg_int1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_int* arg_intn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char *datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_dbl* arg_dbl0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_dbl* arg_dbl1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_dbl* arg_dbln(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char *datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_str* arg_str0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_str* arg_str1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_str* arg_strn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rexn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
|
||||
struct arg_rex* arg_rex0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
struct arg_rex* arg_rex1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int flags,
|
||||
const char *glossary);
|
||||
struct arg_rex* arg_rexn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
int flags,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_file* arg_file0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_file* arg_file1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_file* arg_filen(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
ARG_EXTERN struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
struct arg_date* arg_date0(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* format,
|
||||
const char* datatype,
|
||||
const char* glossary);
|
||||
struct arg_date* arg_date1(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* format,
|
||||
const char* datatype,
|
||||
const char *glossary);
|
||||
struct arg_date* arg_daten(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* format,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
const char *glossary);
|
||||
|
||||
struct arg_end* arg_end(int maxerrors);
|
||||
ARG_EXTERN struct arg_end* arg_end(int maxerrors);
|
||||
|
||||
#define ARG_DSTR_STATIC ((arg_dstr_freefn*)0)
|
||||
#define ARG_DSTR_VOLATILE ((arg_dstr_freefn*)1)
|
||||
#define ARG_DSTR_DYNAMIC ((arg_dstr_freefn*)3)
|
||||
|
||||
/**** other functions *******************************************/
|
||||
int arg_nullcheck(void **argtable);
|
||||
int arg_parse(int argc, char **argv, void **argtable);
|
||||
void arg_print_option(FILE *fp, const char *shortopts, const char *longopts, const char *datatype, const char *suffix);
|
||||
void arg_print_syntax(FILE *fp, void **argtable, const char *suffix);
|
||||
void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix);
|
||||
void arg_print_glossary(FILE *fp, void **argtable, const char *format);
|
||||
void arg_print_glossary_gnu(FILE *fp, void **argtable);
|
||||
void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
|
||||
void arg_freetable(void **argtable, size_t n);
|
||||
ARG_EXTERN int arg_nullcheck(void** argtable);
|
||||
ARG_EXTERN int arg_parse(int argc, char** argv, void** argtable);
|
||||
ARG_EXTERN void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntax(FILE* fp, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_glossary(FILE* fp, void** argtable, const char* format);
|
||||
ARG_EXTERN void arg_print_glossary_gnu(FILE* fp, void** argtable);
|
||||
ARG_EXTERN void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
|
||||
ARG_EXTERN void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format);
|
||||
ARG_EXTERN void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable);
|
||||
ARG_EXTERN void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname);
|
||||
ARG_EXTERN void arg_freetable(void** argtable, size_t n);
|
||||
|
||||
ARG_EXTERN arg_dstr_t arg_dstr_create(void);
|
||||
ARG_EXTERN void arg_dstr_destroy(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_reset(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_free(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc);
|
||||
ARG_EXTERN void arg_dstr_cat(arg_dstr_t ds, const char* str);
|
||||
ARG_EXTERN void arg_dstr_catc(arg_dstr_t ds, char c);
|
||||
ARG_EXTERN void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...);
|
||||
ARG_EXTERN char* arg_dstr_cstr(arg_dstr_t ds);
|
||||
|
||||
ARG_EXTERN void arg_cmd_init(void);
|
||||
ARG_EXTERN void arg_cmd_uninit(void);
|
||||
ARG_EXTERN void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description);
|
||||
ARG_EXTERN void arg_cmd_unregister(const char* name);
|
||||
ARG_EXTERN int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res);
|
||||
ARG_EXTERN unsigned int arg_cmd_count(void);
|
||||
ARG_EXTERN arg_cmd_info_t* arg_cmd_info(const char* name);
|
||||
ARG_EXTERN arg_cmd_itr_t arg_cmd_itr_create(void);
|
||||
ARG_EXTERN void arg_cmd_itr_destroy(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN int arg_cmd_itr_advance(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN char* arg_cmd_itr_key(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k);
|
||||
ARG_EXTERN void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn);
|
||||
ARG_EXTERN void arg_make_get_help_msg(arg_dstr_t res);
|
||||
ARG_EXTERN void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable);
|
||||
ARG_EXTERN void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end);
|
||||
ARG_EXTERN int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode);
|
||||
ARG_EXTERN void arg_set_module_name(const char* name);
|
||||
ARG_EXTERN void arg_set_module_version(int major, int minor, int patch, const char* tag);
|
||||
|
||||
/**** deprecated functions, for back-compatibility only ********/
|
||||
void arg_free(void **argtable);
|
||||
ARG_EXTERN void arg_free(void** argtable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# This is a basic configuration file that should provide
|
||||
# sensible values for "standard" setup.
|
||||
|
||||
verbose: 0;
|
||||
foreground: false;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
# You will find extensive examples with explanations in
|
||||
# example.cfg
|
||||
|
||||
timeout: 2;
|
||||
user: "nobody";
|
||||
pidfile: "/var/run/sslh.pid";
|
||||
chroot: "/var/empty";
|
||||
|
||||
|
||||
# Change hostname with your external address name.
|
||||
# Change hostname with your external address name, or the IP
|
||||
# of the interface that receives connections
|
||||
listen:
|
||||
(
|
||||
{ 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; },
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
collection.c: management of a collection of connections, for 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 "common.h"
|
||||
#include "collection.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "gap.h"
|
||||
|
||||
/* Info to keep track of all connections */
|
||||
struct cnx_collection {
|
||||
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
||||
};
|
||||
|
||||
/* Allocates and initialises a new collection of connections with at least
|
||||
* `len` elements. */
|
||||
cnx_collection* collection_init(int len)
|
||||
{
|
||||
cnx_collection* collection;
|
||||
|
||||
collection = malloc(sizeof(*collection));
|
||||
CHECK_ALLOC(collection, "collection_init(collection)");
|
||||
|
||||
memset(collection, 0, sizeof(*collection));
|
||||
|
||||
collection->fd2cnx = gap_init(len);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/* Caveat: might not work, as has never been used */
|
||||
void collection_destroy(cnx_collection* collection)
|
||||
{
|
||||
/* Caveat 2: no code to free connections yet */
|
||||
gap_destroy(collection->fd2cnx);
|
||||
free(collection);
|
||||
}
|
||||
|
||||
/* Points the file descriptor to the specified connection index */
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
|
||||
{
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocates a connection and inits it with specified file descriptor */
|
||||
struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collection, int fd)
|
||||
{
|
||||
struct connection* cnx = malloc(sizeof(*cnx));
|
||||
|
||||
if (!cnx) return NULL;
|
||||
|
||||
init_cnx(cnx);
|
||||
cnx->type = SOCK_STREAM;
|
||||
cnx->q[0].fd = fd;
|
||||
cnx->state = ST_PROBING;
|
||||
cnx->probe_timeout = time(NULL) + cfg.timeout;
|
||||
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
|
||||
return cnx;
|
||||
}
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx)
|
||||
{
|
||||
if (cnx->q[0].fd != -1)
|
||||
gap_set(collection->fd2cnx, cnx->q[0].fd, NULL);
|
||||
if (cnx->q[1].fd != -1)
|
||||
gap_set(collection->fd2cnx, cnx->q[1].fd, NULL);
|
||||
free(cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns the connection that contains the file descriptor */
|
||||
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd)
|
||||
{
|
||||
return gap_get(collection->fd2cnx, fd);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef COLLECTION_H
|
||||
#define COLLECTION_H
|
||||
|
||||
typedef struct cnx_collection cnx_collection;
|
||||
|
||||
|
||||
cnx_collection* collection_init(int len);
|
||||
void collection_destroy(cnx_collection* collection);
|
||||
|
||||
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
|
||||
|
||||
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd);
|
||||
|
||||
#endif
|
||||
@@ -4,7 +4,6 @@
|
||||
* No code here should assume whether sockets are blocking or not.
|
||||
**/
|
||||
|
||||
#define SYSLOG_NAMES
|
||||
#define _GNU_SOURCE
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
@@ -16,6 +15,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
@@ -42,8 +42,6 @@ struct sslhcfg_item cfg;
|
||||
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
|
||||
|
||||
|
||||
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;
|
||||
@@ -60,7 +58,7 @@ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall
|
||||
char buf[NI_MAXHOST];
|
||||
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s:%s: %s\n",
|
||||
print_message(msg_system_error, "%s:%s: %s\n",
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
syscall,
|
||||
strerror(errno));
|
||||
@@ -70,14 +68,14 @@ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall
|
||||
}
|
||||
}
|
||||
|
||||
int get_fd_sockets(int *sockfd[])
|
||||
int get_fd_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
int sd = 0;
|
||||
|
||||
#ifdef SYSTEMD
|
||||
sd = sd_listen_fds(0);
|
||||
if (sd < 0) {
|
||||
fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd));
|
||||
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
|
||||
exit(1);
|
||||
}
|
||||
if (sd > 0) {
|
||||
@@ -85,7 +83,8 @@ int get_fd_sockets(int *sockfd[])
|
||||
*sockfd = malloc(sd * sizeof(*sockfd[0]));
|
||||
CHECK_ALLOC(*sockfd, "malloc");
|
||||
for (i = 0; i < sd; i++) {
|
||||
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
|
||||
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
|
||||
(*sockfd)[i].type = SOCK_STREAM;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -111,81 +110,93 @@ int make_listen_tfo(int s)
|
||||
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
|
||||
}
|
||||
|
||||
/* Starts listening sockets on specified addresses.
|
||||
* IN: addr[], num_addr
|
||||
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
|
||||
* Returns number of addresses bound
|
||||
* Bound file descriptors are returned in newly-allocated *sockfd pointer
|
||||
*/
|
||||
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
|
||||
/* 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)
|
||||
{
|
||||
struct sockaddr_storage *saddr;
|
||||
struct addrinfo *addr;
|
||||
int i, res, one;
|
||||
int num_addr = 0;
|
||||
int sd_socks = 0;
|
||||
struct sockaddr_storage *saddr;
|
||||
int sockfd, one, res;
|
||||
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
|
||||
if (sd_socks > 0) {
|
||||
return sd_socks;
|
||||
}
|
||||
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
check_res_dump(CR_DIE, sockfd, addr, "socket");
|
||||
|
||||
for (addr = addr_list; addr; addr = addr->ai_next)
|
||||
num_addr++;
|
||||
one = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
|
||||
if (num_addr == 0) {
|
||||
fprintf(stderr, "FATAL: No available addresses.\n");
|
||||
exit(1);
|
||||
}
|
||||
res = make_listen_tfo(sockfd);
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)");
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "listening to %d addresses\n", num_addr);
|
||||
if (keepalive) {
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
|
||||
}
|
||||
|
||||
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
|
||||
CHECK_ALLOC(*sockfd, "malloc");
|
||||
if (IP_FREEBIND) {
|
||||
res = setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
|
||||
}
|
||||
|
||||
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
|
||||
if (!addr) {
|
||||
fprintf(stderr, "FATAL: Inconsistent listen number. This should not happen.\n");
|
||||
exit(1);
|
||||
}
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
|
||||
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
|
||||
check_res_dump(CR_DIE, (*sockfd)[i], addr, "socket");
|
||||
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(CR_DIE, res, addr, "bind");
|
||||
|
||||
one = 1;
|
||||
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
if (!udp) {
|
||||
res = listen (sockfd, 50);
|
||||
check_res_dump(CR_DIE, res, addr, "listen");
|
||||
}
|
||||
|
||||
res = make_listen_tfo((*sockfd)[i]);
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)");
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
if (addr->ai_flags & SO_KEEPALIVE) {
|
||||
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
|
||||
}
|
||||
/* Starts listening sockets on specified addresses.
|
||||
* OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects
|
||||
* Returns number of addresses bound
|
||||
*/
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int i, res;
|
||||
int num_addr = 0, keepalive = 0, udp = 0;
|
||||
int sd_socks = 0;
|
||||
|
||||
if (IP_FREEBIND) {
|
||||
res = setsockopt((*sockfd)[i], IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
|
||||
}
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt((*sockfd)[i], IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
if (sd_socks > 0) {
|
||||
return sd_socks;
|
||||
}
|
||||
|
||||
res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(CR_DIE, res, addr, "bind");
|
||||
*sockfd = NULL;
|
||||
|
||||
res = listen ((*sockfd)[i], 50);
|
||||
check_res_dump(CR_DIE, res, addr, "listen");
|
||||
print_message(msg_config, "Listening to:\n");
|
||||
|
||||
}
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
keepalive = cfg.listen[i].keepalive;
|
||||
udp = cfg.listen[i].is_udp;
|
||||
|
||||
return num_addr;
|
||||
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
||||
cfg.listen[i].keepalive ? "keepalive" : "",
|
||||
cfg.listen[i].is_udp ? "udp" : "");
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
|
||||
@@ -272,16 +283,34 @@ int bind_peer(int fd, int fd_from)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make the file descriptor non-block */
|
||||
int set_nonblock(int fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
flags = fcntl(fd, F_SETFL, flags);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
/* 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)
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct addrinfo *a, from;
|
||||
struct sockaddr_storage ss;
|
||||
char buf[NI_MAXHOST];
|
||||
int fd, res, one;
|
||||
int transparent = cnx->proto->transparent || cfg.transparent;
|
||||
|
||||
memset(&from, 0, sizeof(from));
|
||||
from.ai_addr = (struct sockaddr*)&ss;
|
||||
@@ -290,48 +319,52 @@ int connect_addr(struct connection *cnx, int fd_from)
|
||||
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, make sure both connections use the same address family */
|
||||
if (cfg.transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
continue;
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "connecting to %s family %d len %d\n",
|
||||
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
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) {
|
||||
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
|
||||
print_message(msg_connections_error, "forward to %s failed:socket: %s\n",
|
||||
cnx->proto->name, strerror(errno));
|
||||
} else {
|
||||
one = 1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one));
|
||||
/* no need to check return value; if it's not supported, that's okay */
|
||||
|
||||
if (cfg.transparent) {
|
||||
if (blocking == NON_BLOCKING) {
|
||||
set_nonblock(fd);
|
||||
}
|
||||
|
||||
if (transparent) {
|
||||
res = bind_peer(fd, fd_from);
|
||||
CHECK_RES_RETURN(res, "bind_peer", res);
|
||||
}
|
||||
res = connect(fd, a->ai_addr, a->ai_addrlen);
|
||||
if (res == -1) {
|
||||
switch (errno) {
|
||||
case EINPROGRESS:
|
||||
/* Can't be done yet, or TFO already done */
|
||||
break;
|
||||
|
||||
default:
|
||||
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
|
||||
cnx->proto->name, strerror(errno));
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
if (cnx->proto->keepalive) {
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res);
|
||||
}
|
||||
return fd;
|
||||
/* 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",
|
||||
cnx->proto->name, strerror(errno));
|
||||
close(fd);
|
||||
continue; /* Try the next address */
|
||||
}
|
||||
if (cnx->proto->keepalive) {
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@@ -342,9 +375,8 @@ 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");
|
||||
|
||||
@@ -365,8 +397,7 @@ int flush_deferred(struct queue *q)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
|
||||
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
|
||||
|
||||
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
||||
if (n == -1)
|
||||
@@ -398,9 +429,12 @@ void init_cnx(struct connection *cnx)
|
||||
|
||||
void dump_connection(struct connection *cnx)
|
||||
{
|
||||
printf("state: %d\n", cnx->state);
|
||||
printf("fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||
printf("fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -425,8 +459,6 @@ 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:
|
||||
@@ -451,7 +483,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
|
||||
case ECONNRESET:
|
||||
case EPIPE:
|
||||
/* remove end closed -- drop the connection */
|
||||
/* remote end closed -- drop the connection */
|
||||
return FD_CNXCLOSED;
|
||||
}
|
||||
} else if (size_w < size_r) {
|
||||
@@ -477,7 +509,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
|
||||
|
||||
if (res) {
|
||||
log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
|
||||
print_message(msg_system_error, "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),
|
||||
@@ -485,7 +517,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
/* should not fail but... */
|
||||
if (res) {
|
||||
log_message(LOG_ERR, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
|
||||
print_message(msg_system_error, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
|
||||
strcpy(host, "?");
|
||||
strcpy(serv, "?");
|
||||
}
|
||||
@@ -514,7 +546,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
if (host[0] == '[') {
|
||||
end = strrchr(host, ']');
|
||||
if (!end) {
|
||||
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
|
||||
print_message(msg_config_error, "%s: no closing bracket in IPv6 address?\n", host);
|
||||
return -1;
|
||||
}
|
||||
host++; /* skip first bracket */
|
||||
@@ -523,7 +555,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
if (res)
|
||||
log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
print_message(msg_system_error, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -539,7 +571,7 @@ void resolve_name(struct addrinfo **out, char* fullname)
|
||||
/* Find port */
|
||||
char *sep = strrchr(fullname, ':');
|
||||
if (!sep) { /* No separator: parameter is just a port */
|
||||
fprintf(stderr, "%s: names must be fully specified as hostname:port\n", fullname);
|
||||
print_message(msg_config_error, "%s: names must be fully specified as hostname:port\n", fullname);
|
||||
exit(1);
|
||||
}
|
||||
serv = sep+1;
|
||||
@@ -549,30 +581,13 @@ void resolve_name(struct addrinfo **out, char* fullname)
|
||||
|
||||
res = resolve_split_name(out, host, serv);
|
||||
if (res) {
|
||||
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
|
||||
print_message(msg_config_error, "%s `%s'\n", gai_strerror(res), fullname);
|
||||
if (res == EAI_SERVICE)
|
||||
fprintf(stderr, "(Check you have specified all ports)\n");
|
||||
print_message(msg_config_error, "(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)
|
||||
@@ -607,30 +622,6 @@ 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)
|
||||
{
|
||||
@@ -674,8 +665,7 @@ 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) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
||||
print_message(msg_system_error, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
||||
strcpy(addr_str, STRING_UNKNOWN);
|
||||
}
|
||||
/* extract peer name */
|
||||
@@ -683,15 +673,12 @@ 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) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
||||
print_message(msg_system_error, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
@@ -726,35 +713,6 @@ 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) {
|
||||
@@ -768,16 +726,33 @@ void set_keepcaps(int val) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* set needed capabilities for effective and permitted, clear rest */
|
||||
void set_capabilities(void) {
|
||||
/* Returns true if anything requires transparent proxying. */
|
||||
static int use_transparent(void)
|
||||
{
|
||||
#ifdef LIBCAP
|
||||
if (cfg.transparent)
|
||||
return 1;
|
||||
|
||||
for (int i = 0; i < cfg.protocols_len; i++)
|
||||
if (cfg.protocols[i].transparent)
|
||||
return 1;
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set needed capabilities for effective and permitted, clear rest
|
||||
* IN: cap_net_admin: set to 1 to set CAP_NET_RAW
|
||||
* */
|
||||
void set_capabilities(int cap_net_admin) {
|
||||
#ifdef LIBCAP
|
||||
int res;
|
||||
cap_t caps;
|
||||
cap_value_t cap_list[10];
|
||||
int ncap = 0;
|
||||
|
||||
if (cfg.transparent)
|
||||
cap_list[ncap++] = CAP_NET_ADMIN;
|
||||
if (cap_net_admin)
|
||||
cap_list[ncap++] = CAP_NET_RAW;
|
||||
|
||||
caps = cap_init();
|
||||
|
||||
@@ -815,16 +790,14 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
if (user_name) {
|
||||
pw = getpwnam(user_name);
|
||||
if (!pw) {
|
||||
fprintf(stderr, "%s: not found\n", user_name);
|
||||
print_message(msg_config_error, "%s: not found\n", user_name);
|
||||
exit(2);
|
||||
}
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "turning into %s\n", user_name);
|
||||
print_message(msg_config, "turning into %s\n", user_name);
|
||||
}
|
||||
|
||||
if (chroot_path) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "chrooting into %s\n", chroot_path);
|
||||
print_message(msg_config, "chrooting into %s\n", chroot_path);
|
||||
|
||||
res = chroot(chroot_path);
|
||||
CHECK_RES_DIE(res, "chroot");
|
||||
@@ -845,7 +818,7 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
res = setuid(pw->pw_uid);
|
||||
CHECK_RES_DIE(res, "setuid");
|
||||
|
||||
set_capabilities();
|
||||
set_capabilities(use_transparent());
|
||||
set_keepcaps(0);
|
||||
}
|
||||
}
|
||||
@@ -854,14 +827,24 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
void write_pid_file(const char* pidfile)
|
||||
{
|
||||
FILE *f;
|
||||
int res;
|
||||
|
||||
f = fopen(pidfile, "w");
|
||||
if (!f) {
|
||||
perror(pidfile);
|
||||
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
|
||||
exit(3);
|
||||
}
|
||||
|
||||
fprintf(f, "%d\n", getpid());
|
||||
fclose(f);
|
||||
res = fprintf(f, "%d\n", getpid());
|
||||
if (res < 0) {
|
||||
print_message(msg_system_error, "write_pid_file:fprintf:%s", strerror(errno));
|
||||
exit(3);
|
||||
}
|
||||
|
||||
res = fclose(f);
|
||||
if (res == EOF) {
|
||||
print_message(msg_system_error, "write_pid_file:fclose:%s", strerror(errno));
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,22 +35,25 @@
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
#define CHECK_RES_DIE(res, str) \
|
||||
if (res == -1) { \
|
||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
||||
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define CHECK_RES_RETURN(res, str, ret) \
|
||||
if (res == -1) { \
|
||||
log_message(LOG_CRIT, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
#define CHECK_ALLOC(a, str) \
|
||||
if (!a) { \
|
||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
||||
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
@@ -80,9 +83,6 @@ 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 {
|
||||
@@ -92,15 +92,45 @@ 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 */
|
||||
|
||||
/* SOCK_STREAM */
|
||||
enum connection_state state;
|
||||
time_t probe_timeout;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
|
||||
/* q[0]: queue for external connection (client);
|
||||
* q[1]: queue for internal connection (httpd or sshd);
|
||||
* */
|
||||
struct queue q[2];
|
||||
|
||||
/* SOCK_DGRAM */
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
struct listen_endpoint {
|
||||
int socketfd; /* file descriptor of listening socket */
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
};
|
||||
|
||||
#define FD_CNXCLOSED 0
|
||||
@@ -114,10 +144,16 @@ struct connection_desc {
|
||||
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
NON_BLOCKING = 0,
|
||||
BLOCKING = 1
|
||||
} connect_blocking;
|
||||
|
||||
|
||||
/* common.c */
|
||||
void init_cnx(struct connection *cnx);
|
||||
int connect_addr(struct connection *cnx, int fd_from);
|
||||
int set_nonblock(int fd);
|
||||
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);
|
||||
@@ -128,24 +164,23 @@ int check_access_rights(int in_socket, const char* service);
|
||||
void setup_signals(void);
|
||||
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(int *sockfd[], struct addrinfo *addr_list);
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[]);
|
||||
|
||||
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* USAGE_STRING;
|
||||
extern const char* server_type;
|
||||
|
||||
/* sslh-fork.c */
|
||||
void start_shoveler(int);
|
||||
|
||||
void main_loop(int *listen_sockets, int num_addr_listen);
|
||||
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||
|
||||
#endif
|
||||
+127
@@ -40,3 +40,130 @@ to:
|
||||
netstat -lpt
|
||||
```
|
||||
|
||||
I get a segmentation fault!
|
||||
===========================
|
||||
|
||||
Well, it's not yours (fault): a segfault is always a bug in
|
||||
the programme. Usually standard use cases are well tested,
|
||||
so it may be related to something unusual in your
|
||||
configuration, or even something wrong, but it should still
|
||||
never result in a segfault.
|
||||
|
||||
Thankfully, when they are deterministic, segfaults are
|
||||
usually fairly easy to fix if you're willing to run a few
|
||||
diagnostics to help the developer.
|
||||
|
||||
First, make sure you have debug symbols:
|
||||
```
|
||||
$ file sslh-select
|
||||
sslh-select: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a758ac75ff11f1ace577705b4d6627e301940b59, with debug_info, not stripped
|
||||
```
|
||||
|
||||
Note `with debug_info, not stripped` at the end. If you
|
||||
don't have that, your distribution stripped the binary: you
|
||||
will need to get the source code and compile it yourself
|
||||
(that way, you will also get the latest version).
|
||||
|
||||
Install `valgrind` and run `sslh` under it:
|
||||
|
||||
```
|
||||
valgrind --leak-check=full ./sslh-fork -v 2 -f -F yourconfig.cfg
|
||||
```
|
||||
|
||||
Report the full output to the mailing list or github.
|
||||
Valgrind is very powerful and gives precise hints of what is
|
||||
wrong and why. For example on `sslh` issue
|
||||
(#273)[https://github.com/yrutschle/sslh/issues/273]:
|
||||
|
||||
```
|
||||
sudo valgrind --leak-check=full ./sslh-fork -v 2 -f -F /etc/sslh.cfg
|
||||
==20037== Memcheck, a memory error detector
|
||||
==20037== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
|
||||
==20037== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
|
||||
==20037== Command: ./sslh-fork -v 2 -f -F /etc/sslh.cfg
|
||||
==20037==
|
||||
sslh-fork v1.21b-1-g2c93a01-dirty started
|
||||
--20037-- WARNING: unhandled arm-linux syscall: 403
|
||||
--20037-- You may be able to write your own handler.
|
||||
--20037-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
|
||||
--20037-- Nevertheless we consider this a bug. Please report
|
||||
--20037-- it at http://valgrind.org/support/bug_reports.html.
|
||||
==20040== Conditional jump or move depends on uninitialised value(s)
|
||||
==20040== at 0x112A3C: parse_tls_header (tls.c:162)
|
||||
==20040== by 0x111CEF: is_tls_protocol (probe.c:214)
|
||||
==20040== by 0x11239F: probe_client_protocol (probe.c:366)
|
||||
==20040== by 0x10A8F7: start_shoveler (sslh-fork.c:98)
|
||||
==20040== by 0x10AE9B: main_loop (sslh-fork.c:200)
|
||||
==20040== by 0x1114FB: main (sslh-main.c:322)
|
||||
==20040==
|
||||
```
|
||||
|
||||
Here we see that something wrong is happening at `tls.c`
|
||||
line 162, and it's linked to an uninitialised value.
|
||||
|
||||
Using sslh for virtual hosting
|
||||
==============================
|
||||
|
||||
Virtual hosting refers to having several domain names behind
|
||||
a single IP address. All Web servers handle this, but
|
||||
sometimes it can be useful to do it with `sslh`.
|
||||
|
||||
TLS virtual hosting with SNI
|
||||
----------------------------
|
||||
|
||||
For TLS, this is done very simply using Server Name
|
||||
Indication, SNI for short, which is a TLS extension whereby
|
||||
the client indicates the name of the server it wishes to
|
||||
connect to. This can be a very powerful way to separate
|
||||
several TLS-based services hosted behind the same port:
|
||||
simply name each service with its own hostname. For example,
|
||||
we could define `mail.rutschle.net`, `im.rutschle.net`,
|
||||
`www.rutschle.net`, all of which point to the same IP
|
||||
address. `sslh` uses the `sni_hostnames` setting of the
|
||||
TLS probe to do this, e.g.:
|
||||
|
||||
```
|
||||
protocols: (
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "993";
|
||||
sni_hostnames: [ "mail.rutschle.net" ];
|
||||
},
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "xmpp-client";
|
||||
sni_hostnames: [ "im.rutschle.net" ];
|
||||
},
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "4443";
|
||||
sni_hostnames: [ "www.rutschle.net" ];
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
HTTP virtual hosting with regex
|
||||
-------------------------------
|
||||
|
||||
If you wish to serve several Web domains over HTTP through
|
||||
`sslh`, you can do this simply by using regular expressions
|
||||
on the Host specification part of the HTTP query.
|
||||
|
||||
The following example forwards connections to `host_A.acme`
|
||||
to 192.168.0.2, and connections to `host_B.acme` to
|
||||
192.168.0.3.
|
||||
|
||||
```
|
||||
protocols: (
|
||||
{ name: "regex";
|
||||
host: "192.168.0.2";
|
||||
port: "80";
|
||||
regex_patterns:
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_A.acme"] },
|
||||
{ name: "regex";
|
||||
host: "192.168.0.3";
|
||||
port: "80";
|
||||
regex_patterns:
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] }
|
||||
);
|
||||
```
|
||||
+25
-15
@@ -7,7 +7,7 @@ Dependencies
|
||||
`sslh` uses:
|
||||
|
||||
* [libconfig](http://www.hyperrealm.com/libconfig/). For
|
||||
Debian this is contained in package `libconfig8-dev`. You
|
||||
Debian this is contained in package `libconfig-dev`. You
|
||||
can compile with or without it using USELIBCONFIG in the
|
||||
Makefile.
|
||||
|
||||
@@ -29,6 +29,13 @@ Makefile.
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at
|
||||
compile-time.
|
||||
|
||||
* libpcre2, in package `libpcre-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.
|
||||
|
||||
|
||||
For OpenSUSE, these are contained in packages libconfig9 and
|
||||
libconfig-dev in repository
|
||||
@@ -42,7 +49,11 @@ For Fedora, you'll need packages `libconfig` and
|
||||
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.3) to your path.
|
||||
(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
|
||||
-----------
|
||||
@@ -71,8 +82,8 @@ of the Makefile:
|
||||
Binaries
|
||||
--------
|
||||
|
||||
The Makefile produces two different executables: `sslh-fork`
|
||||
and `sslh-select`:
|
||||
The Makefile produces three different executables: `sslh-fork`,
|
||||
`sslh-select` and `sslh-ev`:
|
||||
|
||||
* `sslh-fork` forks a new process for each incoming connection.
|
||||
It is well-tested and very reliable, but incurs the overhead
|
||||
@@ -81,18 +92,17 @@ 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 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.
|
||||
|
||||
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.
|
||||
* `sslh-select` uses only one thread, which monitors all
|
||||
connections at once. It only incurs a 16 byte overhead per
|
||||
connection. Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely. If you are going
|
||||
to use `sslh` on a "medium" setup (a few hundreds of
|
||||
connections), or if you are on a system where forking is
|
||||
expensive (e.g. Windows), `sslh-select` will be better.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as
|
||||
a backend. This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ with launchctl or simply reboot.
|
||||
<string>0.0.0.0:443</string>
|
||||
<string>--ssh</string>
|
||||
<string>localhost:22</string>
|
||||
<string>--ssl</string>
|
||||
<string>--tls</string>
|
||||
<string>localhost:443</string>
|
||||
</array>
|
||||
<key>QueueDirectories</key>
|
||||
|
||||
+27
-163
@@ -65,13 +65,13 @@ 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 connexions
|
||||
we're listening to for incoming connections
|
||||
* `-l` summons `sslh` in inetd mode.
|
||||
|
||||
* sslh options:
|
||||
* `-i` for inetd mode
|
||||
* `--http` to forward HTTP connexions to port 80,
|
||||
and SSH connexions to port 22.
|
||||
* `--http` to forward HTTP connections to port 80,
|
||||
and SSH connections to port 22.
|
||||
|
||||
Capabilities support
|
||||
--------------------
|
||||
@@ -84,177 +84,27 @@ processes.
|
||||
Alternatively, you may use filesystem capabilities instead
|
||||
of starting sslh as root and asking it to drop privileges.
|
||||
You will need `CAP_NET_BIND_SERVICE` for listening on port 443
|
||||
and `CAP_NET_ADMIN` for transparent proxying (see
|
||||
and `CAP_NET_RAW` for transparent proxying (see
|
||||
`capabilities(7)`).
|
||||
|
||||
You can use the `setcap(8)` utility to give these capabilities
|
||||
to the executable:
|
||||
|
||||
sudo setcap cap_net_bind_service,cap_net_admin+pe sslh-select
|
||||
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
|
||||
|
||||
Then you can run sslh-select as an unpriviledged user, e.g.:
|
||||
|
||||
sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443
|
||||
|
||||
Caveat: `CAP_NET_ADMIN` does give sslh too many rights, e.g.
|
||||
configuring the interface. If you're not going to use
|
||||
transparent proxying, just don't use it (or use the libcap method).
|
||||
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
|
||||
|
||||
Transparent proxy support
|
||||
-------------------------
|
||||
|
||||
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).
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
This makes it easier to use the server's logs, and potential
|
||||
IP-based banning ability.
|
||||
|
||||
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_ADMIN` 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 --ssl 192.168.0.1:4443
|
||||
|
||||
This will not work:
|
||||
|
||||
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 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.
|
||||
Set up can get complicated, so it has its own [document](tproxy.md).
|
||||
|
||||
Systemd Socket Activation
|
||||
-------------------------
|
||||
@@ -284,9 +134,9 @@ Example service unit:
|
||||
PartOf=sslh.socket
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443
|
||||
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --tls 127.0.0.1:443
|
||||
KillMode=process
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=full
|
||||
@@ -335,3 +185,17 @@ many connections to ssh from the same IP address...)
|
||||
|
||||
See example files in scripts/fail2ban.
|
||||
|
||||
UDP
|
||||
---
|
||||
|
||||
`sslh` can perform demultiplexing on UDP packets as well.
|
||||
This does not work with `sslh-fork` (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 overriden with `udp_timeout`. This allows to
|
||||
process both single-datagram protocols such as DNS, and
|
||||
connection-based protocols such as QUIC.
|
||||
+178
-20
@@ -1,17 +1,181 @@
|
||||
Transparent Proxy to Two Hosts
|
||||
==============================
|
||||
# Transparent proxy
|
||||
|
||||
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).
|
||||
|
||||
This document shows recipes that may help to do that.
|
||||
|
||||
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. 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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## 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)
|
||||
@@ -20,8 +184,7 @@ Two Raspberry Pis on my home LAN:
|
||||
|
||||

|
||||
|
||||
`sslh` build
|
||||
------------
|
||||
### `sslh` build
|
||||
|
||||
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
|
||||
|
||||
@@ -47,8 +210,7 @@ MAN=sslh.8.gz # man page name
|
||||
# itself
|
||||
```
|
||||
|
||||
systemd setup
|
||||
-------------
|
||||
### systemd setup
|
||||
|
||||
Create an sslh systemd service file...
|
||||
```
|
||||
@@ -83,8 +245,7 @@ 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
|
||||
```
|
||||
@@ -123,8 +284,7 @@ protocols:
|
||||
);
|
||||
```
|
||||
|
||||
Configure `stunnel`
|
||||
-------------------
|
||||
### Configure `stunnel`
|
||||
|
||||
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
|
||||
```
|
||||
@@ -151,8 +311,7 @@ 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..
|
||||
@@ -194,8 +353,7 @@ 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
|
||||
@@ -235,8 +393,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)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
# TODO: c2s does not warn if udp: 1 (instead of 'true')
|
||||
|
||||
udp: true;
|
||||
|
||||
prefix: "hello";
|
||||
|
||||
listen: "localhost:9000";
|
||||
|
||||
listen-host: "localhost";
|
||||
listen-port: "9000";
|
||||
|
||||
+1307
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,66 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Apr 30 09:55:03 2022.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 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_ECHOCFG_H
|
||||
#define C2S_ECHOCFG_H
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
struct echocfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
};
|
||||
|
||||
struct echocfg_item {
|
||||
int udp;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
struct echocfg_listen_item* listen;
|
||||
};
|
||||
|
||||
int echocfg_parse_file(
|
||||
const char* filename,
|
||||
struct echocfg_item* echocfg,
|
||||
const char** errmsg);
|
||||
|
||||
void echocfg_fprint(
|
||||
FILE* out,
|
||||
struct echocfg_item *echocfg,
|
||||
int depth);
|
||||
|
||||
int echocfg_cl_parse(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct echocfg_item *echocfg);
|
||||
|
||||
#endif
|
||||
@@ -27,8 +27,13 @@
|
||||
#include <syslog.h>
|
||||
#include <libgen.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define cfg sslhcfg
|
||||
#include "common.h"
|
||||
#undef cfg
|
||||
|
||||
#include "echosrv-conf.h"
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
@@ -36,60 +41,21 @@
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
const char* USAGE_STRING =
|
||||
"echosrv\n" \
|
||||
"usage:\n" \
|
||||
"\techosrv [-v] --listen <address:port> [--prefix <prefix>]\n"
|
||||
"-v: verbose\n" \
|
||||
"--listen: address to listen on. Can be specified multiple times.\n" \
|
||||
"--prefix: add specified prefix before every line echoed.\n"
|
||||
"";
|
||||
struct echocfg_item cfg;
|
||||
|
||||
const char* server_type = "echsrv"; /* keep setup_syslog happy */
|
||||
|
||||
/*
|
||||
* Settings that depend on the command line.
|
||||
*/
|
||||
char* prefix = "";
|
||||
int port;
|
||||
|
||||
int verbose, numeric;
|
||||
|
||||
void parse_cmdline(int argc, char* argv[])
|
||||
void check_res_dump(int res, struct addrinfo *addr, char* syscall)
|
||||
{
|
||||
int c;
|
||||
struct option options[] = {
|
||||
{ "verbose", no_argument, &verbose, 1 },
|
||||
{ "numeric", no_argument, &numeric, 1 },
|
||||
{ "listen", required_argument, 0, 'l' },
|
||||
{ "prefix", required_argument, 0, 'p' },
|
||||
};
|
||||
struct addrinfo **a;
|
||||
char buf[NI_MAXHOST];
|
||||
|
||||
while ((c = getopt_long_only(argc, argv, "l:p:", options, NULL)) != -1) {
|
||||
if (c == 0) continue;
|
||||
if (res == -1) {
|
||||
if (addr)
|
||||
fprintf(stderr, "error %s:%s: %s\n",
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
syscall,
|
||||
strerror(errno));
|
||||
else
|
||||
fprintf(stderr, "Dying just because\n");
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'l':
|
||||
/* find the end of the listen list */
|
||||
for (a = &addr_listen; *a; a = &((*a)->ai_next));
|
||||
/* append the specified addresses */
|
||||
resolve_name(a, optarg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
prefix = optarg;
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "%s", USAGE_STRING);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (!addr_listen) {
|
||||
fprintf(stderr, "No listening port specified\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -99,19 +65,28 @@ void start_echo(int fd)
|
||||
int res;
|
||||
char buffer[1 << 20];
|
||||
int ret, prefix_len;
|
||||
int first = 1;
|
||||
|
||||
prefix_len = strlen(prefix);
|
||||
prefix_len = strlen(cfg.prefix);
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
strcpy(buffer, prefix);
|
||||
strcpy(buffer, cfg.prefix);
|
||||
|
||||
while (1) {
|
||||
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
|
||||
if (ret == -1) {
|
||||
if (ret <= 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
res = write(fd, buffer, ret + prefix_len);
|
||||
if (first) {
|
||||
res = write(fd, buffer, ret + prefix_len);
|
||||
first = 0;
|
||||
if (write(1, buffer, ret + prefix_len) < 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
res = write(fd, buffer + prefix_len, ret);
|
||||
}
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
return;
|
||||
@@ -119,30 +94,232 @@ void start_echo(int fd)
|
||||
}
|
||||
}
|
||||
|
||||
void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
/* TCP echo server: accepts connections to an endpoint, forks an echo for each
|
||||
* connection, forever. Prefix is added at start of response stream */
|
||||
void tcp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
int in_socket, i;
|
||||
while (1) {
|
||||
int in_socket = accept(listen_socket->socketfd, 0, 0);
|
||||
if (in_socket == -1) {
|
||||
perror("tcp_echo:accept");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!fork())
|
||||
{
|
||||
close(listen_socket->socketfd);
|
||||
start_echo(in_socket);
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
waitpid(-1, NULL, WNOHANG);
|
||||
}
|
||||
}
|
||||
|
||||
void print_udp_xchange(int sockfd, struct sockaddr* addr, socklen_t addrlen)
|
||||
{
|
||||
struct addrinfo src_addrinfo, to_addrinfo;
|
||||
char str_addr[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
char str_addr2[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
struct sockaddr_storage ss;
|
||||
|
||||
src_addrinfo.ai_addr = (struct sockaddr*)&ss;
|
||||
src_addrinfo.ai_addrlen = sizeof(ss);
|
||||
getsockname(sockfd, src_addrinfo.ai_addr, &src_addrinfo.ai_addrlen);
|
||||
|
||||
to_addrinfo.ai_addr = addr;
|
||||
to_addrinfo.ai_addrlen = sizeof(*addr);
|
||||
|
||||
fprintf(stderr, "UDP local %s remote %s\n",
|
||||
sprintaddr(str_addr, sizeof(str_addr), &src_addrinfo),
|
||||
sprintaddr(str_addr2, sizeof(str_addr2), &to_addrinfo)
|
||||
);
|
||||
}
|
||||
|
||||
/* UDP echo server: receive packets, return them, forever.
|
||||
* Prefix is added at each packet */
|
||||
void udp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
char data[65536];
|
||||
struct sockaddr src_addr;
|
||||
socklen_t addrlen;
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
size_t prefix_len = strlen(cfg.prefix);
|
||||
memcpy(data, cfg.prefix, prefix_len);
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(src_addr);
|
||||
size_t len = recvfrom(listen_socket->socketfd,
|
||||
data + prefix_len,
|
||||
sizeof(data) - prefix_len,
|
||||
0,
|
||||
&src_addr,
|
||||
&addrlen);
|
||||
|
||||
if (len < 0) {
|
||||
perror("recvfrom");
|
||||
}
|
||||
*(data + prefix_len + len) = 0;
|
||||
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
|
||||
|
||||
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
|
||||
|
||||
int res = sendto(listen_socket->socketfd,
|
||||
data,
|
||||
len + prefix_len,
|
||||
0,
|
||||
&src_addr,
|
||||
addrlen);
|
||||
if (res < 0) {
|
||||
perror("sendto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (!fork()) {
|
||||
while (1)
|
||||
{
|
||||
in_socket = accept(listen_sockets[i], 0, 0);
|
||||
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
|
||||
if (!fork())
|
||||
{
|
||||
close(listen_sockets[i]);
|
||||
start_echo(in_socket);
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
if (cfg.udp) {
|
||||
udp_echo(&listen_sockets[i]);
|
||||
} else {
|
||||
tcp_echo(&listen_sockets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/* Following is a number of utility functions copied from common.c: linking
|
||||
* against common.o directly means echosrv has to work with sslh config struct,
|
||||
* which makes it all too awkward */
|
||||
|
||||
/* simplified from common.c */
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
{
|
||||
char host[NI_MAXHOST], serv[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
0 );
|
||||
|
||||
if (res) {
|
||||
/* Name resolution failed: do it numerically instead */
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
/* should not fail but... */
|
||||
if (res) {
|
||||
strcpy(host, "?");
|
||||
strcpy(serv, "?");
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buf, size, "%s:%s", host, serv);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/* simplified from common.c */
|
||||
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
{
|
||||
struct sockaddr_storage *saddr;
|
||||
int sockfd, one, res;
|
||||
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
|
||||
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
check_res_dump(sockfd, addr, "socket");
|
||||
|
||||
one = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
|
||||
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(res, addr, "bind");
|
||||
|
||||
if (!udp) {
|
||||
res = listen (sockfd, 50);
|
||||
check_res_dump(res, addr, "listen");
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* simplified from common.c */
|
||||
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
{
|
||||
struct addrinfo hint;
|
||||
char *end;
|
||||
int res;
|
||||
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
hint.ai_family = PF_UNSPEC;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
|
||||
* around IP address */
|
||||
if (host[0] == '[') {
|
||||
end = strrchr(host, ']');
|
||||
if (!end) {
|
||||
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
|
||||
return -1;
|
||||
}
|
||||
host++; /* skip first bracket */
|
||||
*end = 0; /* remove last bracket */
|
||||
}
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
|
||||
if (res)
|
||||
fprintf(stderr, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
return res;
|
||||
}
|
||||
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int i, res;
|
||||
int num_addr = 0, keepalive = 0, udp = 0;
|
||||
|
||||
*sockfd = NULL;
|
||||
|
||||
fprintf(stderr, "Listening to:\n");
|
||||
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
udp = cfg.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));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
fprintf(stderr, "%d:\t%s\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr));
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
@@ -150,11 +327,15 @@ int main(int argc, char *argv[])
|
||||
extern int optind;
|
||||
int num_addr_listen;
|
||||
|
||||
int *listen_sockets;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
parse_cmdline(argc, argv);
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
if (echocfg_cl_parse(argc, argv, &cfg))
|
||||
exit(1);
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
|
||||
echocfg_fprint(stdout, &cfg, 0);
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
main_loop(listen_sockets, num_addr_listen);
|
||||
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# conf2struct for echosrv
|
||||
|
||||
header: "echosrv-conf.h";
|
||||
parser: "echosrv-conf.c";
|
||||
|
||||
printer: true;
|
||||
|
||||
conffile_option: ("F", "config");
|
||||
|
||||
config: {
|
||||
name: "echocfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{name: "udp", type: "bool"; default: false; },
|
||||
{name: "prefix", type: "string"; },
|
||||
{ name: "listen",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
cl_groups: (
|
||||
{ name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port";
|
||||
short: "p"; argdesc: "<host:port>";
|
||||
list: "listen";
|
||||
# no override, this just adds to the list (and thus can be specified several times)
|
||||
targets: (
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" }
|
||||
);
|
||||
}
|
||||
)
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Nov 7 09:19:26 2020.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2019 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef C2S_SSLHCFG_H
|
||||
#define C2S_SSLHCFG_H
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "probe.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int keepalive;
|
||||
};
|
||||
|
||||
struct sslhcfg_protocols_item {
|
||||
char* name;
|
||||
char* host;
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
int fork;
|
||||
int tfo_ok;
|
||||
int log_level;
|
||||
int keepalive;
|
||||
size_t sni_hostnames_len;
|
||||
char** sni_hostnames;
|
||||
size_t alpn_protocols_len;
|
||||
char** alpn_protocols;
|
||||
size_t regex_patterns_len;
|
||||
char** regex_patterns;
|
||||
int minlength_is_present;
|
||||
int minlength;
|
||||
T_PROBE* probe;
|
||||
struct addrinfo* saddr;
|
||||
void* data;
|
||||
};
|
||||
|
||||
struct sslhcfg_item {
|
||||
char* prefix;
|
||||
int verbose;
|
||||
int foreground;
|
||||
int inetd;
|
||||
int numeric;
|
||||
int transparent;
|
||||
int timeout;
|
||||
int user_is_present;
|
||||
char* user;
|
||||
int pidfile_is_present;
|
||||
char* pidfile;
|
||||
int chroot_is_present;
|
||||
char* chroot;
|
||||
char* syslog_facility;
|
||||
char* on_timeout;
|
||||
size_t listen_len;
|
||||
struct sslhcfg_listen_item* listen;
|
||||
size_t protocols_len;
|
||||
struct sslhcfg_protocols_item* protocols;
|
||||
};
|
||||
|
||||
int sslhcfg_parse_file(
|
||||
const char* filename,
|
||||
struct sslhcfg_item* sslhcfg,
|
||||
const char** errmsg);
|
||||
|
||||
void sslhcfg_fprint(
|
||||
FILE* out,
|
||||
struct sslhcfg_item *sslhcfg,
|
||||
int depth);
|
||||
|
||||
int sslhcfg_cl_parse(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct sslhcfg_item *sslhcfg);
|
||||
|
||||
#endif
|
||||
+41
-2
@@ -3,7 +3,6 @@
|
||||
# not be used as a starting point for a working
|
||||
# configuration. Instead use basic.cfg.
|
||||
|
||||
verbose: 0;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
@@ -13,6 +12,30 @@ 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.
|
||||
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
|
||||
|
||||
|
||||
# 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)
|
||||
@@ -25,7 +48,8 @@ syslog_facility: "auth";
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; },
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; }
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; },
|
||||
{ host: "thelonious"; is_udp: true; port: "443" }
|
||||
);
|
||||
|
||||
# List of protocols
|
||||
@@ -43,6 +67,11 @@ 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
|
||||
#
|
||||
# Probe-specific options:
|
||||
# (sslh will try each probe in order they are declared, and
|
||||
@@ -89,6 +118,16 @@ protocols:
|
||||
# catch anything else TLS
|
||||
{ name: "tls"; host: "localhost"; port: "443"; tfo_ok: true },
|
||||
|
||||
# Forward UDP
|
||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "123";
|
||||
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"; },
|
||||
# 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" ]; },
|
||||
|
||||
# Regex examples -- better use the built-in probes for real-world use!
|
||||
# OpenVPN
|
||||
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
gap.c: gap, a simple, dynamically-growing array
|
||||
of pointers that never shrinks
|
||||
|
||||
# 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 <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sslh-conf.h"
|
||||
#include "gap.h"
|
||||
|
||||
|
||||
/* Allocate one page-worth of elements */
|
||||
static int gap_len_alloc(int elem_size)
|
||||
{
|
||||
return getpagesize() / elem_size;
|
||||
}
|
||||
|
||||
/* Creates a new gap at least `len` big, all pointers are initialised at NULL */
|
||||
gap_array* gap_init(int len)
|
||||
{
|
||||
gap_array* gap = malloc(sizeof(*gap));
|
||||
if (!gap) return NULL;
|
||||
memset(gap, 0, sizeof(*gap));
|
||||
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
gap->len = gap_len_alloc(elem_size);
|
||||
if (gap->len < len) gap->len = len;
|
||||
gap->array = malloc(gap->len * elem_size);
|
||||
if (!gap->array) return NULL;
|
||||
|
||||
for (int i = 0; i < gap->len; i++)
|
||||
gap->array[i] = NULL;
|
||||
|
||||
return gap;
|
||||
}
|
||||
|
||||
int gap_extend(gap_array* gap)
|
||||
{
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
int new_length = gap->len + gap_len_alloc(elem_size);
|
||||
void** new = realloc(gap->array, new_length * elem_size);
|
||||
if (!new) return -1;
|
||||
|
||||
gap->array = new;
|
||||
|
||||
for (int i = gap->len; i < new_length; i++) {
|
||||
gap->array[i] = NULL;
|
||||
}
|
||||
|
||||
gap->len = new_length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gap_destroy(gap_array* gap)
|
||||
{
|
||||
free(gap->array);
|
||||
free(gap);
|
||||
}
|
||||
|
||||
|
||||
/* In gap, find element pointing to ptr, then shift the rest of the array that
|
||||
* is considered len elements long.
|
||||
* A poor man's list, if you will. Currently only used to remove probing
|
||||
* connections, so it only copies a few pointers at most.
|
||||
* Returns -1 if ptr was not found */
|
||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
||||
{
|
||||
int start, i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (gap->array[i] == ptr)
|
||||
break;
|
||||
|
||||
if (i < len)
|
||||
start = i;
|
||||
else
|
||||
return -1;
|
||||
|
||||
for (i = start; i < len; i++) {
|
||||
gap->array[i] = gap->array[i+1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef GAP_H
|
||||
#define GAP_H
|
||||
|
||||
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_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)
|
||||
{
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -0,0 +1,28 @@
|
||||
#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
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
CFLAGS=-DHASH_TESTING -O2 -Wall
|
||||
OBJ=../hash.o ../gap.o htest.o
|
||||
|
||||
htest: $(OBJ)
|
||||
$(CC) -o htest $(OBJ)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Basic delete
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 10 ac
|
||||
a 20 ba
|
||||
a 21 bb
|
||||
|
||||
d 21 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
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>
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
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>
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,10 @@
|
||||
# delete in a discontinuous block
|
||||
|
||||
a 10 aa
|
||||
a 11 ab
|
||||
a 12 ac
|
||||
a 14 ad
|
||||
a 10 bc
|
||||
|
||||
d 11 ab
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
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>
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=0>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,39 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
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>
|
||||
@@ -0,0 +1,8 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:30:ba
|
||||
1:30:bb
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,8 @@
|
||||
# delete before wrap
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ad
|
||||
|
||||
# shift ac and ad
|
||||
d 30 ab
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=3>
|
||||
0:30:ad
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,109 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Basic insertions
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 10 ac
|
||||
a 20 ba
|
||||
a 21 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
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>
|
||||
@@ -0,0 +1,8 @@
|
||||
# insert and bubble with single empty space
|
||||
|
||||
a 10 aa
|
||||
a 11 ab
|
||||
a 12 ac
|
||||
a 14 ad
|
||||
a 10 bc
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
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>
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,7 @@
|
||||
# wrap-around and insert above floor
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ad
|
||||
a 0 ba
|
||||
a 0 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#! /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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# This runs all the tests.
|
||||
|
||||
# Tests scripts are in *.tst files.
|
||||
# Corresponding output is put in *.out.
|
||||
# Reference output is put in *.ref.
|
||||
# Any discrepency 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;
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
# 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;
|
||||
|
||||
va_start(ap, str);
|
||||
|
||||
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd)
|
||||
vfprintf(stderr, str, 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;
|
||||
get_connection_desc(desc, cnx);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#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 */
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
# probe.c: Code for probing protocols
|
||||
#
|
||||
# Copyright (C) 2007-2019 Yves Rutschle
|
||||
# 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
|
||||
@@ -22,26 +22,28 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#ifdef ENABLE_REGEX
|
||||
#ifdef LIBPCRE
|
||||
#include <pcreposix.h>
|
||||
#else
|
||||
#include <regex.h>
|
||||
#endif
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
|
||||
static int is_ssh_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_openvpn_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_tinc_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_xmpp_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_http_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_tls_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_adb_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_socks5_protocol(const char *p, int len, struct sslhcfg_protocols_item*);
|
||||
static int is_true(const char *p, int len, struct sslhcfg_protocols_item* proto) { return 1; }
|
||||
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*);
|
||||
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
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
|
||||
*/
|
||||
@@ -49,13 +51,16 @@ 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 },
|
||||
{ "tls", is_tls_protocol },
|
||||
{ "ssl", is_tls_protocol },
|
||||
{ "adb", is_adb_protocol },
|
||||
{ "socks5", is_socks5_protocol },
|
||||
{ "syslog", is_syslog_protocol },
|
||||
{ "teamspeak", is_teamspeak_protocol },
|
||||
{ "msrdp", is_msrdp_protocol },
|
||||
{ "anyprot", is_true }
|
||||
};
|
||||
|
||||
@@ -83,39 +88,44 @@ struct sslhcfg_protocols_item* timeout_protocol(void)
|
||||
|
||||
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
|
||||
#define HEXDUMP_COLS 16
|
||||
void hexdump(const char *mem, unsigned int len)
|
||||
void hexdump(msg_info msg_info, 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)
|
||||
fprintf(stderr, "0x%06x: ", i);
|
||||
c += sprintf(&str[c], "0x%06x: ", i);
|
||||
|
||||
/* print hex data */
|
||||
if(i < len)
|
||||
fprintf(stderr, "%02x ", 0xFF & mem[i]);
|
||||
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]);
|
||||
else /* end of block, just aligning for ASCII dump */
|
||||
fprintf(stderr, " ");
|
||||
c+= sprintf(&str[c], " ");
|
||||
|
||||
/* 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 */
|
||||
fputc(' ', stderr);
|
||||
str[c++] = ' ';
|
||||
else if(isprint(mem[j])) /* printable char */
|
||||
fputc(0xFF & mem[j], stderr);
|
||||
str[c++] = 0xFF & mem[j];
|
||||
else /* other char */
|
||||
fputc('.', stderr);
|
||||
str[c++] = '.';
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
str[c++] = '\n';
|
||||
str[c++] = 0;
|
||||
print_message(msg_info, "%s", str);
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an SSH connection? */
|
||||
static int is_ssh_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 4)
|
||||
return PROBE_AGAIN;
|
||||
@@ -133,22 +143,73 @@ static int is_ssh_protocol(const char *p, int len, struct sslhcfg_protocols_item
|
||||
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
|
||||
* and OpenVPN ssl.c, ssl.h and options.c
|
||||
*/
|
||||
static int is_openvpn_protocol (const char*p,int len, struct sslhcfg_protocols_item* proto)
|
||||
#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 (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
if (proto->is_udp == 0)
|
||||
{
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
packet_len = ntohs(*(uint16_t*)p);
|
||||
return packet_len == len - 2;
|
||||
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))
|
||||
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))
|
||||
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;
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of a tinc connections?
|
||||
* Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf
|
||||
* First connection starts with "0 " in 1.0.15)
|
||||
* */
|
||||
static int is_tinc_protocol( const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_tinc_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
@@ -160,7 +221,7 @@ static int is_tinc_protocol( const char *p, int len, struct sslhcfg_protocols_it
|
||||
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
|
||||
* clients, just checking first frame containing "jabber" in xml entity)
|
||||
* */
|
||||
static int is_xmpp_protocol( const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_xmpp_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (memmem(p, len, "jabber", 6))
|
||||
return PROBE_MATCH;
|
||||
@@ -183,7 +244,7 @@ static int probe_http_method(const char *p, int len, const char *opt)
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an HTTP connection? */
|
||||
static int is_http_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res;
|
||||
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
|
||||
@@ -209,7 +270,7 @@ static int is_http_protocol(const char *p, int len, struct sslhcfg_protocols_ite
|
||||
}
|
||||
|
||||
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
|
||||
static int is_tls_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
switch (parse_tls_header(proto->data, p, len)) {
|
||||
case TLS_MATCH: return PROBE_MATCH;
|
||||
@@ -229,7 +290,7 @@ static int probe_adb_cnxn_message(const char *p)
|
||||
return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5);
|
||||
}
|
||||
|
||||
static int is_adb_protocol(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
/* amessage.data_length is not being checked, under the assumption that
|
||||
* a packet >= 30 bytes will have "something" in the payload field.
|
||||
@@ -268,7 +329,7 @@ static int is_adb_protocol(const char *p, int len, struct sslhcfg_protocols_item
|
||||
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
|
||||
}
|
||||
|
||||
static int is_socks5_protocol(const char *p_in, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
unsigned char* p = (unsigned char*)p_in;
|
||||
int i;
|
||||
@@ -301,83 +362,125 @@ static int is_socks5_protocol(const char *p_in, int len, struct sslhcfg_protocol
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
static int regex_probe(const char *p, int len, struct sslhcfg_protocols_item* proto)
|
||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res, i, j;
|
||||
|
||||
res = sscanf(p, "<%d>", &i);
|
||||
if (res == 1) return 1;
|
||||
|
||||
res = sscanf(p, "%d <%d>", &i, &j);
|
||||
if (res == 2) return 1;
|
||||
|
||||
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
|
||||
regex_t **probe = proto->data;
|
||||
regmatch_t pos = { 0, len };
|
||||
pcre2_code**probe = (pcre2_code**)proto->data;
|
||||
pcre2_match_data* matches;
|
||||
|
||||
for (; *probe && regexec(*probe, p, 0, &pos, REG_STARTEND); probe++)
|
||||
/* try them all */;
|
||||
matches = pcre2_match_data_create(1, NULL);
|
||||
|
||||
return (*probe != NULL);
|
||||
for (; *probe; probe++) {
|
||||
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL);
|
||||
if (res >= 0) return 1;
|
||||
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
/* Should never happen as we check when loading config file */
|
||||
fprintf(stderr, "FATAL: regex probe called but not built in\n");
|
||||
print_message(msg_int_error, "FATAL: regex probe called but not built in\n");
|
||||
exit(5);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
/* 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
|
||||
)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
struct sslhcfg_protocols_item* p;
|
||||
int i, n, res, again = 0;
|
||||
int i, res, again = 0;
|
||||
|
||||
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). */
|
||||
print_message(msg_packets, "hexdump of incoming packet:\n");
|
||||
hexdump(msg_packets, buf, len);
|
||||
|
||||
if (n > 0) {
|
||||
if (cfg.verbose > 1) {
|
||||
fprintf(stderr, "hexdump of incoming packet:\n");
|
||||
hexdump(buffer, n);
|
||||
}
|
||||
defer_write(&cnx->q[1], buffer, n);
|
||||
}
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
*proto_out = NULL;
|
||||
for (i = 0; i < proto_len; i++) {
|
||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||
p = &cfg.protocols[i];
|
||||
p = proto_in[i];
|
||||
|
||||
if (! p->probe) continue;
|
||||
|
||||
if (cfg.verbose) fprintf(stderr, "probing for %s\n", p->name);
|
||||
print_message(msg_probe_info, "probing for %s\n", p->name);
|
||||
|
||||
/* Don't probe last protocol if it is anyprot (and store last protocol) */
|
||||
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||
if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||
break;
|
||||
|
||||
if (p->minlength_is_present && (cnx->q[1].deferred_data_size < p->minlength )) {
|
||||
fprintf(stderr, "input too short, %d bytes but need %d\n", cnx->q[1].deferred_data_size , p->minlength);
|
||||
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);
|
||||
again++;
|
||||
continue;
|
||||
}
|
||||
|
||||
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
|
||||
if (cfg.verbose) fprintf(stderr, "probed for %s: %s\n", p->name, probe_str[res]);
|
||||
res = p->probe(buf, len, p);
|
||||
print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]);
|
||||
|
||||
if (res == PROBE_MATCH) {
|
||||
cnx->proto = p;
|
||||
*proto_out = p;
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
if (res == PROBE_AGAIN)
|
||||
again++;
|
||||
}
|
||||
if ((again && (n > 0)) || ((n == -1) && (errno == EAGAIN)))
|
||||
if (again)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
/* Everything failed: match the last one */
|
||||
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
|
||||
|
||||
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);
|
||||
}
|
||||
*proto_out = proto_in[proto_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "tls.h"
|
||||
#include "log.h"
|
||||
|
||||
typedef enum {
|
||||
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
||||
@@ -13,7 +14,7 @@ typedef enum {
|
||||
} probe_result;
|
||||
|
||||
struct sslhcfg_protocols_item;
|
||||
typedef int T_PROBE(const char*, int, struct sslhcfg_protocols_item*);
|
||||
typedef int T_PROBE(const char*, ssize_t, struct sslhcfg_protocols_item*);
|
||||
|
||||
struct protocol_probe_desc {
|
||||
const char* name;
|
||||
@@ -47,6 +48,13 @@ 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
|
||||
);
|
||||
|
||||
/* set the protocol to connect to in case of timeout */
|
||||
void set_ontimeout(const char* name);
|
||||
|
||||
@@ -56,6 +64,6 @@ void set_ontimeout(const char* name);
|
||||
*/
|
||||
struct sslhcfg_protocols_item* timeout_protocol(void);
|
||||
|
||||
void hexdump(const char*, unsigned int);
|
||||
void hexdump(msg_info, const char*, unsigned int);
|
||||
|
||||
#endif
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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
@@ -0,0 +1,41 @@
|
||||
#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
|
||||
@@ -9,7 +9,7 @@
|
||||
# is needed in order to run as sslh user
|
||||
#
|
||||
#SSLH_USER=sslh
|
||||
#setcap cap_net_bind_service,cap_net_admin=+ep $SSLH
|
||||
#setcap cap_net_bind_service,cap_net_raw=+ep $SSLH
|
||||
|
||||
#
|
||||
# Configuration file for sslh
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^.+ sslh\[.+\]: connection from <HOST>:.+ to .+ forwarded
|
||||
failregex = ^.+ sslh\[.+\]: ssh:connection from <HOST>:.+ to .+ forwarded
|
||||
from .+ to .+:ssh\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
@@ -8,7 +8,7 @@ ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
|
||||
+845
-82
File diff suppressed because it is too large.
Load diff
+27
-4
@@ -1,8 +1,8 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sun Jul 19 16:59:51 2020.
|
||||
* on Sun Sep 11 21:43:25 2022.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2019 Yves Rutschle
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -30,7 +30,9 @@
|
||||
|
||||
#ifndef C2S_SSLHCFG_H
|
||||
#define C2S_SSLHCFG_H
|
||||
#include <libconfig.h>
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "probe.h"
|
||||
@@ -41,6 +43,7 @@
|
||||
struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int is_udp;
|
||||
int keepalive;
|
||||
};
|
||||
|
||||
@@ -50,8 +53,12 @@ struct sslhcfg_protocols_item {
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
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;
|
||||
@@ -65,15 +72,28 @@ struct sslhcfg_protocols_item {
|
||||
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;
|
||||
@@ -81,7 +101,10 @@ 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;
|
||||
struct sslhcfg_listen_item* listen;
|
||||
size_t protocols_len;
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
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 */
|
||||
|
||||
int max_fd; /* legacy to be removed, still required for UDP */
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (fd > w->max_fd) w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (fd > w->max_fd) w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
+64
-44
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
sslh-fork: forking server
|
||||
|
||||
# Copyright (C) 2007-2012 Yves Rutschle
|
||||
# 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
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "tcp-probe.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
@@ -29,8 +32,6 @@
|
||||
|
||||
const char* server_type = "sslh-fork";
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
/* shovels data from one fd to the other and vice-versa
|
||||
returns after one socket closed
|
||||
*/
|
||||
@@ -57,9 +58,8 @@ int shovel(struct connection *cnx)
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (FD_ISSET(cnx->q[i].fd, &fds)) {
|
||||
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
||||
if (!res) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||
if (res == FD_CNXCLOSED) {
|
||||
print_message(msg_fd, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,7 @@ void start_shoveler(int in_socket)
|
||||
} else {
|
||||
/* Timed out: it's necessarily SSH */
|
||||
cnx.proto = timeout_protocol();
|
||||
if (cfg.verbose)
|
||||
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->name);
|
||||
print_message(msg_fd, "timed out, connect to %s\n", cnx.proto->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -111,9 +110,11 @@ void start_shoveler(int in_socket)
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(&cnx, in_socket);
|
||||
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);
|
||||
@@ -127,13 +128,12 @@ void start_shoveler(int in_socket)
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "connection closed down\n");
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int *listener_pid;
|
||||
static pid_t *listener_pid;
|
||||
static int listener_pid_number = 0;
|
||||
|
||||
void stop_listeners(int sig)
|
||||
@@ -145,7 +145,7 @@ void stop_listeners(int sig)
|
||||
}
|
||||
}
|
||||
|
||||
void set_listen_procname(int listen_socket)
|
||||
void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
int res;
|
||||
@@ -155,7 +155,7 @@ void set_listen_procname(int listen_socket)
|
||||
|
||||
addr.ai_addr = (struct sockaddr*)&ss;
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
res = getsockname(listen_socket, addr.ai_addr, &addr.ai_addrlen);
|
||||
res = getsockname(listen_socket->socketfd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res != -1) {
|
||||
sprintaddr(listen_addr, sizeof(listen_addr), &addr);
|
||||
setproctitle("listener %s", listen_addr);
|
||||
@@ -163,50 +163,69 @@ void set_listen_procname(int listen_socket)
|
||||
#endif
|
||||
}
|
||||
|
||||
void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
|
||||
/* TCP listener: connections, fork a child for each new connection
|
||||
* IN:
|
||||
* endpoint: array of listening endpoint objects
|
||||
* num_endpoints: size of endpoint array
|
||||
* active_endpoint: which endpoint is this listener working on
|
||||
* Does not return
|
||||
* */
|
||||
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
|
||||
{
|
||||
int in_socket, i, res;
|
||||
int i, in_socket;
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
print_message(msg_fd, "accepted fd %d\n", in_socket);
|
||||
|
||||
switch(fork()) {
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
|
||||
case 0: /* In child process */
|
||||
/* Shoveler processes don't need to hog file descriptors */
|
||||
for (i = 0; i < num_endpoints; i++)
|
||||
close(endpoint[i].socketfd);
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
|
||||
default: /* In parent process */
|
||||
break;
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
int i, res;
|
||||
struct sigaction action;
|
||||
|
||||
listener_pid_number = 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: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
/* Log if fork() fails for some reason */
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
// We're in the child, we have work to do
|
||||
/* We're in the child, we have work to do */
|
||||
case 0:
|
||||
/* Listening process just accepts a connection, forks, and goes
|
||||
* back to listening */
|
||||
set_listen_procname(listen_sockets[i]);
|
||||
while (1)
|
||||
{
|
||||
in_socket = accept(listen_sockets[i], 0, 0);
|
||||
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
|
||||
switch(fork()) {
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
|
||||
/* In child process */
|
||||
case 0:
|
||||
for (i = 0; i < num_addr_listen; ++i)
|
||||
close(listen_sockets[i]);
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
|
||||
/* In parent process */
|
||||
default: break;
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
set_listen_procname(&listen_sockets[i]);
|
||||
if (listen_sockets[i].type == SOCK_DGRAM)
|
||||
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
|
||||
else
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
break;
|
||||
// We're in the parent, we don't need to do anything
|
||||
|
||||
/* We're in the parent, we don't need to do anything */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -220,6 +239,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
res = sigaction(SIGTERM, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
|
||||
+108
-139
@@ -26,11 +26,8 @@
|
||||
#include <libconfig.h>
|
||||
#endif
|
||||
#ifdef ENABLE_REGEX
|
||||
#ifdef LIBPCRE
|
||||
#include <pcreposix.h>
|
||||
#else
|
||||
#include <regex.h>
|
||||
#endif
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
@@ -39,29 +36,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
|
||||
const char* USAGE_STRING =
|
||||
"sslh " VERSION "\n" \
|
||||
"usage:\n" \
|
||||
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
|
||||
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <addr> [-p <addr> ...] \n" \
|
||||
"%s\n\n" /* Dynamically built list of builtin protocols */ \
|
||||
"\t[--on-timeout <addr>]\n" \
|
||||
"-v: verbose\n" \
|
||||
"-V: version\n" \
|
||||
"-f: foreground\n" \
|
||||
"-n: numeric output\n" \
|
||||
"-u: specify under which user to run\n" \
|
||||
"-C: specify under which chroot path to run\n" \
|
||||
"--transparent: behave as a transparent proxy\n" \
|
||||
"-F: use configuration file (warning: no space between -F and file name!)\n" \
|
||||
"--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \
|
||||
"-t: seconds to wait before connecting to --on-timeout address.\n" \
|
||||
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
|
||||
"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
|
||||
"-P: PID file.\n" \
|
||||
"-i: Run as a inetd service.\n" \
|
||||
"";
|
||||
#include "log.h"
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
@@ -76,7 +51,7 @@ static void printcaps(void) {
|
||||
|
||||
desc = cap_to_text(caps, &len);
|
||||
|
||||
fprintf(stderr, "capabilities: %s\n", desc);
|
||||
print_message(msg_config, "capabilities: %s\n", desc);
|
||||
|
||||
cap_free(caps);
|
||||
cap_free(desc);
|
||||
@@ -86,95 +61,41 @@ static void printcaps(void) {
|
||||
static void printsettings(void)
|
||||
{
|
||||
char buf[NI_MAXHOST];
|
||||
struct addrinfo *a;
|
||||
int i;
|
||||
struct sslhcfg_protocols_item *p;
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
fprintf(stderr,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%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" : "");
|
||||
print_message(msg_config,
|
||||
"%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" : ""
|
||||
);
|
||||
}
|
||||
fprintf(stderr, "listening on:\n");
|
||||
for (a = addr_listen; a; a = a->ai_next) {
|
||||
fprintf(stderr,
|
||||
"\t%s\t[%s]\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
a->ai_flags & SO_KEEPALIVE ? "keepalive" : "");
|
||||
}
|
||||
fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout,
|
||||
timeout_protocol()->name);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/* To be removed in v1.21 */
|
||||
const char* ssl_err_msg = "Usage of 'ssl' setting is deprecated and will be removed in v1.21. Please use 'tls' instead\n";
|
||||
void ssl_to_tls(char* setting)
|
||||
{
|
||||
if (!strcmp(setting, "ssl")) {
|
||||
strcpy(setting, "tls"); /* legacy configuration */
|
||||
log_message(LOG_INFO, ssl_err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Turn 'ssl' command line option to 'tls'. To be removed in v1.21 */
|
||||
void cmd_ssl_to_tls(int argc, char* argv[])
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--ssl")) {
|
||||
strcpy(argv[i], "--tls");
|
||||
/* foreground option not parsed yet, syslog not open, just print on
|
||||
* stderr and hope for the best */
|
||||
fprintf(stderr, "%s", ssl_err_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Extract configuration on addresses and ports on which to listen.
|
||||
* out: newly allocated list of addrinfo to listen to
|
||||
*/
|
||||
#ifdef LIBCONFIG
|
||||
static int config_resolve_listen(struct addrinfo **listen)
|
||||
{
|
||||
int i, res;
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
res = resolve_split_name(listen, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) return res;
|
||||
|
||||
/* getaddrinfo returned a list of addresses corresponding to the
|
||||
* specification; move the pointer to the end of that list before
|
||||
* processing the next specification, while setting flags for
|
||||
* start_listen_sockets() through ai_flags (which is not meant for
|
||||
* that, but is only used as hint in getaddrinfo, so it's OK) */
|
||||
for (; *listen; listen = &((*listen)->ai_next)) {
|
||||
if (cfg.listen[i].keepalive)
|
||||
(*listen)->ai_flags = SO_KEEPALIVE;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifdef LIBCONFIG
|
||||
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
#ifdef ENABLE_REGEX
|
||||
{
|
||||
int num_patterns, i, res;
|
||||
regex_t** pattern_list;
|
||||
size_t errsize;
|
||||
char* err;
|
||||
int num_patterns, i, error;
|
||||
pcre2_code** pattern_list;
|
||||
PCRE2_SIZE error_offset;
|
||||
PCRE2_UCHAR8 err_str[120];
|
||||
|
||||
num_patterns = p->regex_patterns_len;
|
||||
|
||||
@@ -183,19 +104,21 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
p->data = (void*)pattern_list;
|
||||
|
||||
for (i = 0; i < num_patterns; i++) {
|
||||
pattern_list[i] = malloc(sizeof(*(pattern_list[i])));
|
||||
CHECK_ALLOC(pattern_list[i], "malloc");
|
||||
res = regcomp(pattern_list[i], p->regex_patterns[i], REG_EXTENDED);
|
||||
if (res) {
|
||||
err = malloc(errsize = regerror(res, pattern_list[i], NULL, 0));
|
||||
CHECK_ALLOC(err, "malloc");
|
||||
regerror(res, pattern_list[i], err, errsize);
|
||||
fprintf(stderr, "%s:%s\n", p->regex_patterns[i], err);
|
||||
free(err);
|
||||
pattern_list[i] = pcre2_compile((PCRE2_SPTR8)p->regex_patterns[i],
|
||||
PCRE2_ZERO_TERMINATED, 0,
|
||||
&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",
|
||||
p->regex_patterns[i], error, err_str, error_offset);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* For each protocol in the configuration, resolve address and set up protocol
|
||||
@@ -206,14 +129,19 @@ static void config_protocols()
|
||||
int i;
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
||||
if (resolve_split_name(&(p->saddr), p->host, p->port)) {
|
||||
fprintf(stderr, "cannot resolve %s:%s\n", p->host, p->port);
|
||||
exit(1);
|
||||
|
||||
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);
|
||||
exit(4);
|
||||
}
|
||||
|
||||
p->probe = get_probe(p->name);
|
||||
if (!p->probe) {
|
||||
fprintf(stderr, "%s: probe unknown\n", p->name);
|
||||
print_message(msg_config_error, "%s: probe unknown\n", p->name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -232,23 +160,55 @@ 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) {
|
||||
if (!cfg->protocols_len) {
|
||||
fprintf(stderr, "At least one target protocol must be specified.\n");
|
||||
exit(2);
|
||||
}
|
||||
void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
/* If compiling with systemd socket support no need to require listen address */
|
||||
#ifndef SYSTEMD
|
||||
if (!addr_listen && !cfg->inetd) {
|
||||
fprintf(stderr, "No listening address specified; use at least one -p option\n");
|
||||
if (!cfg->listen_len && !cfg->inetd) {
|
||||
print_message(msg_config_error, "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")) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +218,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int res, num_addr_listen;
|
||||
int *listen_sockets;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
#ifdef LIBBSD
|
||||
setproctitle_init(argc, argv, envp);
|
||||
@@ -267,28 +227,29 @@ int main(int argc, char *argv[], char* envp[])
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||
if (res) exit(6);
|
||||
if (cfg.verbose > 3)
|
||||
sslhcfg_fprint(stderr, &cfg, 0);
|
||||
res = config_resolve_listen(&addr_listen);
|
||||
if (res) exit(4);
|
||||
|
||||
if (cfg.version) {
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
config_protocols();
|
||||
config_sanity_check(&cfg);
|
||||
|
||||
if (cfg.inetd)
|
||||
{
|
||||
cfg.verbose = 0;
|
||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||
start_shoveler(0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (cfg.verbose)
|
||||
printsettings();
|
||||
printsettings();
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
#ifdef SYSTEMD
|
||||
if (num_addr_listen < 1) {
|
||||
fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n");
|
||||
print_message(msg_config_error, "No listening sockets found, restart sockets or specify addresses in config\n");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
@@ -311,13 +272,21 @@ 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);
|
||||
|
||||
if (cfg.verbose)
|
||||
printcaps();
|
||||
printcaps();
|
||||
|
||||
print_message(msg_config, "%s %s started\n", server_type, VERSION);
|
||||
|
||||
main_loop(listen_sockets, num_addr_listen);
|
||||
|
||||
close_logfile();
|
||||
|
||||
free(listen_sockets);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+115
-373
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
sslh-select: mono-processus server
|
||||
|
||||
# Copyright (C) 2007-2010 Yves Rutschle
|
||||
# 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
|
||||
@@ -20,274 +20,100 @@
|
||||
|
||||
*/
|
||||
|
||||
/* Why use select(2) rather than poll(2)?
|
||||
* No real reason except that's how it was written at first. This article:
|
||||
* 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. */
|
||||
|
||||
#define __LINUX__
|
||||
|
||||
#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"
|
||||
|
||||
const char* server_type = "sslh-select";
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
/* watcher type for a select() loop */
|
||||
struct watchers {
|
||||
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
|
||||
int max_fd; /* Highest fd number to pass to select() */
|
||||
};
|
||||
|
||||
/* cnx_num_alloc is the number of connection to allocate at once (at start-up,
|
||||
* and then every time we get too many simultaneous connections: e.g. start
|
||||
* with 100 slots, then if we get more than 100 connections allocate another
|
||||
* 100 slots, and so on). We never free up connection structures. We try to
|
||||
* allocate as many structures at once as will fit in one page (which is 102
|
||||
* in sslh 1.9 on Linux on x86)
|
||||
*/
|
||||
static long cnx_num_alloc;
|
||||
|
||||
/* Make the file descriptor non-block */
|
||||
int set_nonblock(int fd)
|
||||
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
int num_addr_listen)
|
||||
{
|
||||
int flags;
|
||||
*w = malloc(sizeof(**w));
|
||||
CHECK_ALLOC(*w, "malloc");
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
memset(*w, 0, sizeof(**w));
|
||||
FD_ZERO(&(*w)->fds_r);
|
||||
FD_ZERO(&(*w)->fds_w);
|
||||
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
flags = fcntl(fd, F_SETFL, flags);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
|
||||
{
|
||||
int i;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
watchers_add_read(*w, listen_sockets[i].socketfd);
|
||||
set_nonblock(listen_sockets[i].socketfd);
|
||||
}
|
||||
init_cnx(cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void watchers_add_read(watchers* w, int fd)
|
||||
{
|
||||
FD_SET(fd, &w->fds_r);
|
||||
if (fd > 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 */
|
||||
int fd_is_in_range(int fd) {
|
||||
static int fd_out_of_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;
|
||||
print_message(msg_system_error, "too many open file descriptor to monitor them all -- dropping connection\n");
|
||||
return 1;
|
||||
}
|
||||
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 */
|
||||
int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size)
|
||||
{
|
||||
int in_socket, free, i, res;
|
||||
struct connection *new;
|
||||
|
||||
in_socket = accept(listen_socket, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", -1);
|
||||
|
||||
if (!fd_is_in_range(in_socket)) {
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = set_nonblock(in_socket);
|
||||
if (res == -1) {
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Find an empty slot */
|
||||
for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) {
|
||||
/* nothing */
|
||||
}
|
||||
if (free >= *cnx_size) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "buying more slots from the slot machine.\n");
|
||||
new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0]));
|
||||
if (!new) {
|
||||
log_message(LOG_ERR, "unable to realloc -- dropping connection\n");
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
*cnx = new;
|
||||
*cnx_size += cnx_num_alloc;
|
||||
for (i = free; i < *cnx_size; i++) {
|
||||
init_cnx(&(*cnx)[i]);
|
||||
}
|
||||
}
|
||||
(*cnx)[free].q[0].fd = in_socket;
|
||||
(*cnx)[free].state = ST_PROBING;
|
||||
(*cnx)[free].probe_timeout = time(NULL) + cfg.timeout;
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free);
|
||||
|
||||
return in_socket;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Connect queue 1 of connection to SSL; returns new file descriptor */
|
||||
int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
|
||||
{
|
||||
struct queue *q = &cnx->q[1];
|
||||
|
||||
q->fd = connect_addr(cnx, cnx->q[0].fd);
|
||||
if ((q->fd != -1) && fd_is_in_range(q->fd)) {
|
||||
log_connection(NULL, cnx);
|
||||
set_nonblock(q->fd);
|
||||
flush_deferred(q);
|
||||
if (q->deferred_data) {
|
||||
FD_SET(q->fd, fds_w);
|
||||
FD_CLR(cnx->q[0].fd, fds_r);
|
||||
}
|
||||
FD_SET(q->fd, fds_r);
|
||||
return q->fd;
|
||||
} else {
|
||||
tidy_connection(cnx, fds_r, fds_w);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* shovels data from active fd to the other
|
||||
returns after one socket closed or operation would block
|
||||
*/
|
||||
void shovel(struct connection *cnx, int active_fd,
|
||||
fd_set *fds_r, fd_set *fds_w)
|
||||
{
|
||||
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, fds_r, fds_w);
|
||||
break;
|
||||
|
||||
case FD_STALLED:
|
||||
FD_SET(write_q->fd, fds_w);
|
||||
FD_CLR(read_q->fd, fds_r);
|
||||
break;
|
||||
|
||||
default: /* Nothing */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* shovels data from one fd to the other and vice-versa
|
||||
returns after one socket closed
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
/* returns true if specified fd is initialised and present in fd_set */
|
||||
int is_fd_active(int fd, fd_set* set)
|
||||
{
|
||||
if (fd == -1) return 0;
|
||||
return FD_ISSET(fd, set);
|
||||
}
|
||||
|
||||
/* Main loop: the idea is as follow:
|
||||
* - fds_r and fds_w contain the file descriptors to monitor in read and write
|
||||
* - When a file descriptor goes off, process it: read from it, write the data
|
||||
* to its corresponding pair.
|
||||
* - When a file descriptor blocks when writing, remove the read fd from fds_r,
|
||||
* move the data to a deferred buffer, and add the write fd to fds_w. Defered
|
||||
* move the data to a deferred buffer, and add the write fd to fds_w. Deferred
|
||||
* buffer is allocated dynamically.
|
||||
* - When we can write to a file descriptor that has deferred data, we try to
|
||||
* write as much as we can. Once all data is written, remove the fd from fds_w
|
||||
@@ -296,166 +122,82 @@ int is_fd_active(int fd, fd_set* set)
|
||||
* That way, each pair of file descriptor (read from one, write to the other)
|
||||
* is monitored either for read or for write, but never for both.
|
||||
*/
|
||||
void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
|
||||
struct loop_info fd_info = {0};
|
||||
fd_set readfds, writefds; /* working read and write fd sets */
|
||||
struct timeval tv;
|
||||
int max_fd, i, j, res;
|
||||
int in_socket = 0;
|
||||
struct connection *cnx;
|
||||
int num_cnx; /* Number of connections in *cnx */
|
||||
int num_probing = 0; /* Number of connections currently probing
|
||||
* We use this to know if we need to time out of
|
||||
* select() */
|
||||
int i, res;
|
||||
|
||||
FD_ZERO(&fds_r);
|
||||
FD_ZERO(&fds_w);
|
||||
fd_info.num_probing = 0;
|
||||
fd_info.probing_list = gap_init(0);
|
||||
udp_init(&fd_info);
|
||||
tcp_init();
|
||||
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
FD_SET(listen_sockets[i], &fds_r);
|
||||
set_nonblock(listen_sockets[i]);
|
||||
}
|
||||
max_fd = listen_sockets[num_addr_listen-1] + 1;
|
||||
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
|
||||
cnx_num_alloc = getpagesize() / sizeof(struct connection);
|
||||
|
||||
num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
|
||||
cnx = malloc(num_cnx * sizeof(struct connection));
|
||||
CHECK_ALLOC(cnx, "malloc");
|
||||
for (i = 0; i < num_cnx; i++)
|
||||
init_cnx(&cnx[i]);
|
||||
fd_info.collection = collection_init(fd_info.watchers->max_fd);
|
||||
|
||||
while (1)
|
||||
{
|
||||
memset(&tv, 0, sizeof(tv));
|
||||
tv.tv_sec = cfg.timeout;
|
||||
|
||||
memcpy(&readfds, &fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fds_w, sizeof(writefds));
|
||||
memcpy(&readfds, &fd_info.watchers->fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fd_info.watchers->fds_w, sizeof(writefds));
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing);
|
||||
res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL);
|
||||
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
|
||||
|
||||
/* Check main socket for new connections */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (FD_ISSET(listen_sockets[i], &readfds)) {
|
||||
in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx);
|
||||
if (in_socket > 0) {
|
||||
num_probing++;
|
||||
FD_SET(in_socket, &fds_r);
|
||||
if (in_socket >= max_fd)
|
||||
max_fd = in_socket + 1;;
|
||||
}
|
||||
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
|
||||
struct connection* new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]);
|
||||
|
||||
if (fd_out_of_range(new_cnx->q[0].fd))
|
||||
tidy_connection(new_cnx, &fd_info);
|
||||
|
||||
/* don't also process it as a read socket */
|
||||
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all sockets for write activity */
|
||||
for (i = 0; i < num_cnx; i++) {
|
||||
if (cnx[i].q[0].fd != -1) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
if (is_fd_active(cnx[i].q[j].fd, &writefds)) {
|
||||
res = flush_deferred(&cnx[i].q[j]);
|
||||
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
||||
if (cnx[i].state == ST_PROBING) num_probing--;
|
||||
tidy_connection(&cnx[i], &fds_r, &fds_w);
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "closed slot %d\n", i);
|
||||
} else {
|
||||
/* If no deferred data is left, stop monitoring the fd
|
||||
* for write, and restart monitoring the other one for reads*/
|
||||
if (!cnx[i].q[j].deferred_data_size) {
|
||||
FD_CLR(cnx[i].q[j].fd, &fds_w);
|
||||
FD_SET(cnx[i].q[1-j].fd, &fds_r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
cnx_write_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check sockets in probing state for timeouts */
|
||||
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=%0xp\n", cnx);
|
||||
if (cnx)
|
||||
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx);
|
||||
exit(1);
|
||||
}
|
||||
if (cnx->probe_timeout < time(NULL)) {
|
||||
print_message(msg_fd, "timeout slot %d\n", i);
|
||||
probing_read_process(cnx, &fd_info);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all sockets for read activity */
|
||||
for (i = 0; i < num_cnx; i++) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
if (is_fd_active(cnx[i].q[j].fd, &readfds) ||
|
||||
((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "processing fd%d slot %d\n", j, i);
|
||||
|
||||
switch (cnx[i].state) {
|
||||
|
||||
case ST_PROBING:
|
||||
if (j == 1) {
|
||||
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
|
||||
dump_connection(&cnx[i]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* If timed out it's SSH, otherwise the client sent
|
||||
* data so probe the protocol */
|
||||
if ((cnx[i].probe_timeout < time(NULL))) {
|
||||
cnx[i].proto = timeout_protocol();
|
||||
if (cfg.verbose)
|
||||
log_message(LOG_INFO,
|
||||
"timed out, connect to %s\n",
|
||||
cnx[i].proto->name);
|
||||
} else {
|
||||
res = probe_client_protocol(&cnx[i]);
|
||||
if (res == PROBE_AGAIN)
|
||||
continue;
|
||||
}
|
||||
|
||||
num_probing--;
|
||||
cnx[i].state = ST_SHOVELING;
|
||||
|
||||
/* libwrap check if required for this protocol */
|
||||
if (cnx[i].proto->service &&
|
||||
check_access_rights(in_socket, cnx[i].proto->service)) {
|
||||
tidy_connection(&cnx[i], &fds_r, &fds_w);
|
||||
res = -1;
|
||||
} else if (cnx[i].proto->fork) {
|
||||
struct connection *pcnx_i = &cnx[i];
|
||||
struct connection cnx_i = *pcnx_i;
|
||||
switch (fork()) {
|
||||
case 0: /* child */
|
||||
for (i = 0; i < num_addr_listen; i++)
|
||||
close(listen_sockets[i]);
|
||||
for (i = 0; i < num_cnx; i++)
|
||||
if (&cnx[i] != pcnx_i)
|
||||
for (j = 0; j < 2; j++)
|
||||
if (cnx[i].q[j].fd != -1)
|
||||
close(cnx[i].q[j].fd);
|
||||
free(cnx);
|
||||
connect_proxy(&cnx_i);
|
||||
exit(0);
|
||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
default: /* parent */
|
||||
break;
|
||||
}
|
||||
tidy_connection(&cnx[i], &fds_r, &fds_w);
|
||||
res = -1;
|
||||
} else {
|
||||
res = connect_queue(&cnx[i], &fds_r, &fds_w);
|
||||
}
|
||||
|
||||
if (res >= max_fd)
|
||||
max_fd = res + 1;;
|
||||
break;
|
||||
|
||||
case ST_SHOVELING:
|
||||
shovel(&cnx[i], j, &fds_r, &fds_w);
|
||||
break;
|
||||
|
||||
default: /* illegal */
|
||||
log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
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 readfs does
|
||||
* not know about that */
|
||||
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.watchers->fds_r)) {
|
||||
cnx_read_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,7 +205,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
|
||||
|
||||
|
||||
void start_shoveler(int listen_socket) {
|
||||
fprintf(stderr, "inetd mode is not supported in select mode\n");
|
||||
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<--ssl> I<target address for SSL>] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
|
||||
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -114,8 +114,6 @@ IP address on which the Internet connection ends up).
|
||||
This can be specified several times to bind B<sslh> to
|
||||
several addresses.
|
||||
|
||||
=item B<--ssl> I<target address>
|
||||
|
||||
=item B<--tls> I<target address>
|
||||
|
||||
Interface and port on which to forward SSL connection,
|
||||
|
||||
+78
-3
@@ -25,7 +25,34 @@ config: {
|
||||
name : "sslhcfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "verbose"; type: "int"; default: 0; short: "v"; },
|
||||
{ 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: "version"; type: "bool"; default: false;
|
||||
short: "V";
|
||||
description: "Print version information and exit"; },
|
||||
{ name: "foreground"; type: "bool"; default: false;
|
||||
short: "f";
|
||||
description: "Run in foreground instead of as a daemon"; },
|
||||
@@ -40,6 +67,8 @@ 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"; },
|
||||
@@ -51,16 +80,21 @@ 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"; },
|
||||
|
||||
{ name: "prefix"; type: "string"; default: ""; description: "Reserved for testing" }, # For echosrv only, not sslh
|
||||
|
||||
{ name: "listen",
|
||||
no_cl_accessors: true; # disable generation of individual cl options for each group element (we create a specific --listen option further below)
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "keepalive"; type: "bool"; default: false; }
|
||||
)
|
||||
},
|
||||
@@ -73,9 +107,15 @@ config: {
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "service"; type: "string"; optional: true; },
|
||||
{ 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;
|
||||
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",
|
||||
@@ -95,7 +135,8 @@ config: {
|
||||
# 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: "data"; type: "runtime"; c_type: "void*" },
|
||||
{ name: "timeouts"; type: "runtime"; c_type: "dl_list" }
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -162,7 +203,19 @@ cl_groups: (
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "openvpn" },
|
||||
{ path: "name"; value: "tinc" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ 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 },
|
||||
@@ -213,6 +266,28 @@ cl_groups: (
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "syslog"; pattern: "(.+):(\w+)"; description: "Set up syslog target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "syslog" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ 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";
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "common.h"
|
||||
|
||||
|
||||
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||
|
||||
static char* resolve_listen(const char *hostname, const char *port) {
|
||||
/* Need room in the strcat for \0 and :
|
||||
* the format in the socket unit file is hostname:port */
|
||||
|
||||
@@ -21,22 +21,23 @@ my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
|
||||
# Which tests do we run
|
||||
my $SSH_SHY_CNX = 1;
|
||||
my $SSH_SHY_CNX = 0;
|
||||
my $PROBES_NOFRAG = 1;
|
||||
my $PROBES_AGAIN = 1;
|
||||
my $SSL_MIX_SSH = 1;
|
||||
my $SSH_MIX_SSL = 1;
|
||||
my $DROP_CNX = 1;
|
||||
|
||||
# Robustness tests. These are mostly to achieve full test
|
||||
# coverage, but do not necessarily result in an actual test
|
||||
# (e.g. some tests need to be run with valgrind to check all
|
||||
# memory management code).
|
||||
my $RB_CNX_NOSERVER = 1;
|
||||
my $RB_PARAM_NOHOST = 1;
|
||||
my $RB_WRONG_USERNAME = 1;
|
||||
my $RB_OPEN_PID_FILE = 1;
|
||||
my $RB_RESOLVE_ADDRESS = 1;
|
||||
my $RB_CL_PARAMS = 1;
|
||||
my $RB_CNX_NOSERVER = 0;
|
||||
my $RB_PARAM_NOHOST = 0;
|
||||
my $RB_WRONG_USERNAME = 0;
|
||||
my $RB_OPEN_PID_FILE = 0;
|
||||
my $RB_RESOLVE_ADDRESS = 0;
|
||||
my $RB_CL_PARAMS = 0;
|
||||
|
||||
`lcov --directory . --zerocounters`;
|
||||
|
||||
@@ -126,9 +127,6 @@ sub test_probes {
|
||||
'http' => {
|
||||
data => "GET index.html HTTP/1.1",
|
||||
no_frag => 1 },
|
||||
'ssl' => {
|
||||
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello ssl alone"
|
||||
},
|
||||
'tls' => {
|
||||
# Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`)
|
||||
data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn",
|
||||
@@ -140,6 +138,7 @@ sub test_probes {
|
||||
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone"
|
||||
},
|
||||
'openvpn' => { data => "\x00\x00" },
|
||||
'syslog' => { data => "<42> My syslog message" },
|
||||
'tinc' => { data => "0 hello" },
|
||||
'xmpp' => {data => "I should get a real jabber connection initialisation here" },
|
||||
'adb' => { data => "CNXN....................host:..." },
|
||||
@@ -208,7 +207,8 @@ foreach my $s (@{$conf->fetch_array("protocols")}) {
|
||||
}
|
||||
|
||||
|
||||
my @binaries = ('sslh-select', 'sslh-fork');
|
||||
#my @binaries = ('sslh-select', 'sslh-fork');
|
||||
my @binaries = ('sslh-select');
|
||||
|
||||
for my $binary (@binaries) {
|
||||
warn "Testing $binary\n";
|
||||
@@ -217,13 +217,14 @@ for my $binary (@binaries) {
|
||||
my ($sslh_pid, $valgrind);
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $cmd = "./$binary -v 4 -f -u $user -Ftest.cfg";
|
||||
verbose_exec $cmd;
|
||||
my $cmd = "./$binary -v 4 -f -u $user -F test.cfg";
|
||||
#$valgrind = 1;
|
||||
#exec "valgrind --leak-check=full ./$binary -v 3 -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
|
||||
#$cmd = "valgrind --leak-check=full $cmd";
|
||||
verbose_exec $cmd;
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
sleep 1; # Give everyone some time to start
|
||||
sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds
|
||||
|
||||
|
||||
@@ -285,6 +286,19 @@ for my $binary (@binaries) {
|
||||
}
|
||||
}
|
||||
|
||||
# Test: Drop connection without writing anything
|
||||
if ($DROP_CNX) {
|
||||
print "***Test: Connect but don't write anything\n";
|
||||
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if ($cnx_h) {
|
||||
close $cnx_h;
|
||||
my_is(1, "$binary: Connect and write nothing");
|
||||
# The goal of the test is to check sslh doesn't
|
||||
# crash
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($PROBES_NOFRAG) {
|
||||
test_probes(no_frag => 1, binary => $binary);
|
||||
|
||||
@@ -14,114 +14,264 @@
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
use Data::Dumper;
|
||||
use Conf::Libconfig;
|
||||
|
||||
## BEGIN TEST CONFIG
|
||||
|
||||
# Do we test sslh-select or sslh-fork?
|
||||
my $sslh_binary = "./sslh-select";
|
||||
|
||||
# How many clients to we start for each protocol?
|
||||
my $NUM_CNX = 30;
|
||||
# How many total clients to we start? Each client will pick
|
||||
# a new protocol among what's in test.cfg.
|
||||
my $NUM_CNX = 16;
|
||||
|
||||
# Delay between starting new processes when starting up. If
|
||||
# you start 200 processes in under a second, things go wrong
|
||||
# and it's not sslh's fault (typically the echosrv won't be
|
||||
# forking fast enough).
|
||||
my $start_time_delay = 1;
|
||||
|
||||
# If you test 4 protocols, you'll start $NUM_CNX * 4 clients
|
||||
# (e.g. 40), starting one every $start_time_delay seconds.
|
||||
my $start_time_delay = .5;
|
||||
|
||||
# Max times we repeat the test string: allows to test for
|
||||
# large messages.
|
||||
my $block_rpt = 4096;
|
||||
my $block_rpt = 5;
|
||||
|
||||
# Probability to stop a client after a message (e.g. with
|
||||
# .01 a client will send an average of 100 messages before
|
||||
# disconnecting).
|
||||
my $stop_client_probability = .001;
|
||||
|
||||
# What protocols we test, and on what ports
|
||||
# Just comment out protocols you don't want to use.
|
||||
my %protocols = (
|
||||
"ssh" => { address => "localhost:9001", client => client("ssh") },
|
||||
"ssl" => { address => "localhost:9002", client => client("ssl") },
|
||||
"openvpn" => {address => "localhost:9003", client => client("openvpn") },
|
||||
"tinc" => {address => "localhost:9004", client => client("tinc") },
|
||||
);
|
||||
my $stop_client_probability = .0001;
|
||||
|
||||
##END CONFIG
|
||||
|
||||
my $conf = new Conf::Libconfig;
|
||||
$conf->read_file("test.cfg");
|
||||
|
||||
|
||||
# Pick one address for TCP and one for UDP
|
||||
my @listen = @{$conf->fetch_array("listen")};
|
||||
my ($sslh_tcp_address, $sslh_udp_address);
|
||||
|
||||
foreach my $l (@listen) {
|
||||
if ($l->{is_udp}) {
|
||||
$sslh_udp_address //= "$l->{host}:$l->{port}";
|
||||
} else {
|
||||
$sslh_tcp_address //= "$l->{host}:$l->{port}";
|
||||
}
|
||||
last if defined $sslh_tcp_address and defined $sslh_udp_address;
|
||||
}
|
||||
|
||||
|
||||
# code snippets to connect to each protocol
|
||||
my %connect_params = (
|
||||
regex => {
|
||||
is_udp => 1,
|
||||
sleep => 0,
|
||||
test_data => "foo bar",
|
||||
resp_len => 12,
|
||||
},
|
||||
ssh => {
|
||||
sleep => 20, # So it times out 50% of connections
|
||||
test_data => "SSH-2.0 hello",
|
||||
resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
|
||||
},
|
||||
tinc => {
|
||||
sleep => 0,
|
||||
test_data => "0 ",
|
||||
resp_len => 8, # length "tinc: 0 " => 10
|
||||
},
|
||||
openvpn => {
|
||||
sleep => 0,
|
||||
test_data => "\x00\x00",
|
||||
resp_len => 11, # length "openvpn: \x0\x0" => 11
|
||||
},
|
||||
);
|
||||
|
||||
sub connect_service {
|
||||
my ($cnx, $service) = @_;
|
||||
|
||||
my $params = $connect_params{$service};
|
||||
|
||||
sleep rand $params->{sleep};
|
||||
my $test_data = $params->{test_data};
|
||||
syswrite $cnx, $test_data;
|
||||
sleep 1;
|
||||
sysread $cnx, my $r, $params->{resp_len};
|
||||
|
||||
my $expected = "$service: $test_data";
|
||||
return ($r eq $expected);
|
||||
}
|
||||
|
||||
# We use ports 9000, 9001 and 9002 -- hope that won't clash
|
||||
# with anything...
|
||||
my $sslh_address = "localhost:9000";
|
||||
my $pidfile = "/tmp/sslh_test.pid";
|
||||
|
||||
sub client {
|
||||
my ($service) = @_;
|
||||
my ($protocol, $client_id, $fd_out) = @_;
|
||||
|
||||
return sub {
|
||||
while (1) {
|
||||
my $cnx = new IO::Socket::INET(PeerHost => $sslh_address);
|
||||
my $test_data = "$service testing " x int(rand($block_rpt)+1) . "\n";
|
||||
my $service = $protocol->{name};
|
||||
|
||||
sleep 5 if $service eq "ssh";
|
||||
if ($service eq "openvpn") {
|
||||
syswrite $cnx, "\x00\x0F\x38\n";
|
||||
my $msg;
|
||||
sysread $cnx, $msg, 14; # length "openvpn: \x0\xF\x38\n" => 14
|
||||
}
|
||||
if ($service eq "tinc") {
|
||||
syswrite $cnx, "0 \n";
|
||||
my $msg;
|
||||
sysread $cnx, $msg, 10; # length "tinc: 0 \n" => 10
|
||||
}
|
||||
while (1) {
|
||||
print $cnx $test_data;
|
||||
my $r = <$cnx>;
|
||||
($? = 1, die "$service got [$r]\n") if ($r ne "$service: $test_data");
|
||||
last if rand(1) < $stop_client_probability;
|
||||
}
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
while (1) {
|
||||
my $r;
|
||||
#warn "$client_id: connect $sslh_tcp_address\n";
|
||||
my $cnx = new IO::Socket::INET(PeerHost => $sslh_tcp_address);
|
||||
die "$@\n" if (!$cnx);
|
||||
|
||||
foreach my $p (keys %protocols) {
|
||||
if (!fork) {
|
||||
exec "./echosrv --listen $protocols{$p}->{address} --prefix '$p: '";
|
||||
}
|
||||
}
|
||||
my $cnt = 0;
|
||||
|
||||
# Start sslh with the right plumbing
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $prots = join " ", map "--$_ $protocols{$_}->{address}", keys %protocols;
|
||||
my $cmd = "$sslh_binary -f -t 3 -u $user --listen $sslh_address $prots -P $pidfile";
|
||||
print "$cmd\n";
|
||||
exec $cmd;
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
sleep 2; # valgrind can be heavy -- wait 5 seconds
|
||||
#warn "$client_id: connecting $service\n";
|
||||
|
||||
|
||||
for (1 .. $NUM_CNX) {
|
||||
foreach my $p (keys %protocols) {
|
||||
if (!fork) {
|
||||
warn "starting $p\n";
|
||||
&{$protocols{$p}->{client}};
|
||||
if (not connect_service($cnx, $service)) {
|
||||
print $fd_out "$client_id\t0\tC\n";
|
||||
warn "$client_id: connecting failed\n";
|
||||
exit;
|
||||
}
|
||||
# Give a little time so we don't overrun the
|
||||
# listen(2) backlog.
|
||||
select undef, undef, undef, $start_time_delay;
|
||||
|
||||
#warn "$client_id: shoveling $service\n";
|
||||
|
||||
while (1) {
|
||||
my $test_data = "$service $cnt" x int(rand($block_rpt)+1) . "\n";
|
||||
print $cnx $test_data;
|
||||
$r = <$cnx>;
|
||||
my $expected= "$test_data";
|
||||
my $r_l = length $r;
|
||||
my $e_l = length $expected;
|
||||
$fd_out->autoflush;
|
||||
my $error = "";
|
||||
$error = "E" if $r ne $expected;
|
||||
print $fd_out ("$client_id\t$r_l\t$error\n");
|
||||
($? = 1, die "$service got [$r] expected [$expected]\n") if ($r ne $expected);
|
||||
if (rand(1) < $stop_client_probability) {
|
||||
print $fd_out ("$client_id\t$r_l\tD\n");
|
||||
last;
|
||||
}
|
||||
$cnt++;
|
||||
}
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
# For now, a simple regex client
|
||||
sub udp_client {
|
||||
my ($protocol, $client_id, $fd_out) = @_;
|
||||
|
||||
warn "UDP client starts\n";
|
||||
|
||||
while (1) {
|
||||
my $cnx = new IO::Socket::INET(Proto => 'udp', PeerHost => $sslh_udp_address);
|
||||
# my $cnx; socket $cnx, PF_INET, SOCK_DGRAM, 0 or die "socket: $!\n";
|
||||
die "$@\n" if (!$cnx);
|
||||
my $cnt = 0;
|
||||
|
||||
while (1) {
|
||||
my $test_data = "foo udp $cnt"x int(rand($block_rpt)+1). "\n";
|
||||
|
||||
my $ipaddr = inet_aton("localhost");
|
||||
my $portaddr = sockaddr_in(8086, $ipaddr);
|
||||
my $res = send($cnx, $test_data, 0, $portaddr);
|
||||
if ($res != length($test_data)) {
|
||||
die "cannot sendto: $!";
|
||||
}
|
||||
|
||||
my $expected= "$protocol->{name}: $test_data";
|
||||
|
||||
my $r;
|
||||
defined(recv($cnx, $r, length $expected, 0)) or die "recv: $!\n";
|
||||
|
||||
my $r_l = length $r;
|
||||
my $e_l = length $expected;
|
||||
$fd_out->autoflush;
|
||||
my $error = "";
|
||||
$error = "M" if $r ne $expected;
|
||||
print $fd_out ("$client_id\t$r_l\t$error\n");
|
||||
($? = 1, die "udp got [$r] expected [$expected]\n") if ($r ne $expected);
|
||||
if (rand(1) < $stop_client_probability) {
|
||||
print $fd_out ("$client_id\t$r_l\tD\n");
|
||||
last;
|
||||
}
|
||||
$cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (@{$conf->fetch_array("protocols")}) {
|
||||
if (!fork) {
|
||||
my $udp = $p->{is_udp} ? "--udp" : "";
|
||||
my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null";
|
||||
warn "$cmd\n";
|
||||
exec $cmd;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
warn "Don't forget to run sslh -F test.cfg!\n";
|
||||
|
||||
sleep 2; # Let echosrv's and sslh start
|
||||
|
||||
my ($c_in, $c_out);
|
||||
pipe $c_in, $c_out;
|
||||
|
||||
my @protocols = @{$conf->fetch_array("protocols")};
|
||||
|
||||
if (!fork) {
|
||||
# Process that starts all the clients
|
||||
for my $client_num (1 .. $NUM_CNX) {
|
||||
if (!fork) {
|
||||
my @supported_protocols = keys %connect_params;
|
||||
my $p_name = $supported_protocols[rand @supported_protocols];
|
||||
my @p = grep { $_->{name} eq $p_name } @protocols;
|
||||
my $p = shift @p;
|
||||
if ($p->{is_udp}) {
|
||||
udp_client($p, "$p->{name}$client_num", $c_out);
|
||||
} else {
|
||||
client($p, "$p->{name}$client_num", $c_out);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
# Give a little time so we don't overrun the
|
||||
# listen(2) backlog.
|
||||
select undef, undef, undef, $start_time_delay;
|
||||
}
|
||||
exit;
|
||||
} else {
|
||||
my %data;
|
||||
|
||||
# The condition here selects between pretty output or
|
||||
# raw output
|
||||
if (1) {
|
||||
my $CLEAR_LINE = "\033[2K";
|
||||
my $CURSOR_HOME = "\033[1;1H";
|
||||
my $CLEAR_SCREEN = "\033[2J";
|
||||
|
||||
# Process that retrieves client output to pretty print
|
||||
print $CLEAR_SCREEN; # Clear screen
|
||||
while (<$c_in>) {
|
||||
chop;
|
||||
my ($client_id, $r_l, $error, @rest) = split /\t/, $_;
|
||||
|
||||
$data{$client_id} = [ 0, ""] if not exists $data{$client_id};
|
||||
my ($curr_rcv) = ${$data{$client_id}}[0] + $r_l;;
|
||||
$error //= "";
|
||||
my ($curr_error) = "${$data{$client_id}}[1]$error";
|
||||
# Consolidate into... roman numerals!
|
||||
$curr_error =~ s/D{10}/X/;
|
||||
$curr_error =~ s/X{10}/C/;
|
||||
$curr_error =~ s/C{10}/M/;
|
||||
|
||||
$data{$client_id} = [ $r_l + $curr_rcv, "$curr_error$error" ];
|
||||
|
||||
|
||||
$client_id =~ /(\d+)/;
|
||||
my $i = $1;
|
||||
# print $CURSOR_HOME;
|
||||
print "\033[$i;1H$CLEAR_LINE$client_id\t$curr_rcv\t$curr_error\n";
|
||||
#foreach my $i (sort keys %data) {
|
||||
# ($r_l, $error) = @{$data{$i}};
|
||||
# print "$CLEAR_LINE$i\t$r_l\t$error\n";
|
||||
}
|
||||
} else {
|
||||
# Just print the client outputs
|
||||
while (<$c_in>) {
|
||||
print;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn "waiting\n";
|
||||
wait;
|
||||
warn "finished waiting\n";
|
||||
|
||||
|
||||
`killall echosrv`;
|
||||
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
tcp-listener.c: handles demultiplexing TCP protocols
|
||||
|
||||
# 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
|
||||
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include "tcp-listener.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
/* Removes cnx from probing list */
|
||||
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||
{
|
||||
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
|
||||
fd_info->num_probing--;
|
||||
}
|
||||
|
||||
static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||
{
|
||||
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
|
||||
fd_info->num_probing++;
|
||||
}
|
||||
|
||||
/* 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 loop_info* fd_info)
|
||||
{
|
||||
struct queue *read_q, *write_q;
|
||||
|
||||
read_q = &cnx->q[active_fd];
|
||||
write_q = &cnx->q[1-active_fd];
|
||||
|
||||
print_message(msg_fd, "activity on fd%d\n", read_q->fd);
|
||||
|
||||
switch(fd2fd(write_q, read_q)) {
|
||||
case -1:
|
||||
case FD_CNXCLOSED:
|
||||
tidy_connection(cnx, fd_info);
|
||||
break;
|
||||
|
||||
case FD_STALLED:
|
||||
watchers_add_write(fd_info->watchers, write_q->fd);
|
||||
watchers_del_read(fd_info->watchers, read_q->fd);
|
||||
break;
|
||||
|
||||
default: /* Nothing */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Returns the queue index that contains the specified file descriptor */
|
||||
static int active_queue(struct connection* cnx, int fd)
|
||||
{
|
||||
if (cnx->q[0].fd == fd) return 0;
|
||||
if (cnx->q[1].fd == fd) return 1;
|
||||
|
||||
print_message(msg_int_error, "file descriptor %d not found in connection object\n", fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Process a TCP read event on the specified file descriptor */
|
||||
void tcp_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);
|
||||
|
||||
/* connection can get tidied if there is an error on the other file
|
||||
* descriptor -- then cnx is NULL */
|
||||
if (!cnx) return;
|
||||
|
||||
/* 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) {
|
||||
print_message(msg_int_error, "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 */
|
||||
print_message(msg_int_error, "Illegal connection state %d\n", cnx->state);
|
||||
dump_connection(cnx);
|
||||
exit(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 */
|
||||
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info)
|
||||
{
|
||||
int in_socket, res;
|
||||
|
||||
print_message(msg_fd, "accepting from %d\n", listen_socket);
|
||||
|
||||
in_socket = accept(listen_socket, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", NULL);
|
||||
|
||||
res = set_nonblock(in_socket);
|
||||
if (res == -1) {
|
||||
close(in_socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct connection* cnx = collection_alloc_cnx_from_fd(fd_info->collection, in_socket);
|
||||
if (!cnx) {
|
||||
close(in_socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
add_probing_cnx(fd_info, cnx);
|
||||
return cnx;
|
||||
}
|
||||
|
||||
|
||||
/* Connect queue 1 of connection to SSL; returns new file descriptor */
|
||||
static int connect_queue(struct connection* cnx,
|
||||
struct loop_info* fd_info)
|
||||
{
|
||||
struct queue *q = &cnx->q[1];
|
||||
|
||||
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||
if (q->fd != -1) {
|
||||
log_connection(NULL, cnx);
|
||||
flush_deferred(q);
|
||||
if (q->deferred_data) {
|
||||
/*
|
||||
FD_SET(q->fd, &fd_info->watchers->fds_w);
|
||||
FD_CLR(cnx->q[0].fd, &fd_info->watchers->fds_r); */
|
||||
watchers_add_write(fd_info->watchers, q->fd);
|
||||
watchers_del_read(fd_info->watchers, cnx->q[0].fd);
|
||||
}
|
||||
/* FD_SET(q->fd, &fd_info->watchers->fds_r); */
|
||||
watchers_add_read(fd_info->watchers, q->fd);
|
||||
collection_add_fd(fd_info->collection, cnx, q->fd);
|
||||
return q->fd;
|
||||
} else {
|
||||
tidy_connection(cnx, fd_info);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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))) {
|
||||
print_message(msg_fd, "%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) {
|
||||
print_message(msg_fd, "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);
|
||||
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Process read activity on a socket in probe state
|
||||
* IN/OUT cnx: connection data, updated if connected
|
||||
* IN/OUT info: updated if connected
|
||||
* */
|
||||
void probing_read_process(struct connection* cnx,
|
||||
struct loop_info* fd_info)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* If timed out it's SSH, otherwise the client sent
|
||||
* data so probe the protocol */
|
||||
if ((cnx->probe_timeout < time(NULL))) {
|
||||
cnx->proto = timeout_protocol();
|
||||
print_message(msg_fd, "timed out, connect to %s\n", cnx->proto->name);
|
||||
} else {
|
||||
res = probe_client_protocol(cnx);
|
||||
if (res == PROBE_AGAIN)
|
||||
return;
|
||||
}
|
||||
|
||||
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: print_message(msg_system_error, "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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Process a connection that is active in write (this is TCP only, as
|
||||
* UDP sockets are never "full" or deferred or whatever) */
|
||||
void cnx_write_process(struct loop_info* fd_info, int fd)
|
||||
{
|
||||
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd);
|
||||
|
||||
/* connection can get tidied if there is an error on the other file
|
||||
* descriptor -- then cnx is NULL */
|
||||
if (!cnx) return;
|
||||
|
||||
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) {
|
||||
watchers_del_write(fd_info->watchers, cnx->q[queue].fd);
|
||||
watchers_add_read(fd_info->watchers, cnx->q[1-queue].fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef TCP_LISTENER_H
|
||||
#define TCP_LISTENER_H
|
||||
|
||||
#include "processes.h"
|
||||
#include "collection.h"
|
||||
#include "tcp-probe.h"
|
||||
|
||||
void tcp_read_process(struct loop_info* fd_info, int fd);
|
||||
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info);
|
||||
void probing_read_process(struct connection* cnx, struct loop_info* fd_info);
|
||||
void cnx_write_process(struct loop_info* fd_info, int fd);
|
||||
|
||||
#endif
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
# tcp-probe.c: TCP code that is common to the sslh-fork and sslh-[ev|select]
|
||||
#
|
||||
# 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
|
||||
*/
|
||||
|
||||
|
||||
#include "probe.h"
|
||||
|
||||
static struct sslhcfg_protocols_item** tcp_protocols;
|
||||
static int tcp_protocols_len = 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,
|
||||
tcp_protocols, tcp_protocols_len,
|
||||
&cnx->proto
|
||||
);
|
||||
}
|
||||
|
||||
/* read() returned an error, so just connect to the last protocol to die */
|
||||
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
|
||||
static void tcp_protocol_list_init(void)
|
||||
{
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
|
||||
if (!p->is_udp) {
|
||||
tcp_protocols_len++;
|
||||
tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols));
|
||||
tcp_protocols[tcp_protocols_len-1] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Configuration sanity check for TCP:
|
||||
* - If there is a listening socket, there must be at least one target
|
||||
*/
|
||||
static void tcp_sanity_check(void)
|
||||
{
|
||||
int tcp_present = 0;
|
||||
|
||||
for (int i = 0; i < cfg.listen_len; i++) {
|
||||
struct sslhcfg_listen_item* p = &cfg.listen[i];
|
||||
if (!p->is_udp) {
|
||||
tcp_present = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tcp_present && !tcp_protocols_len) {
|
||||
print_message(msg_config_error, "At least one TCP target protocol must be specified.\n");
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
void tcp_init(void)
|
||||
{
|
||||
tcp_protocol_list_init();
|
||||
tcp_sanity_check();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TCP_PROBE_H
|
||||
#define TCP_PROBE_H
|
||||
|
||||
void tcp_init(void);
|
||||
|
||||
#endif
|
||||
@@ -1,39 +1,63 @@
|
||||
# Configuration file for testing (use both by sslh under
|
||||
# test and the test script `t`)
|
||||
|
||||
verbose: 2;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
numeric: true;
|
||||
transparent: false;
|
||||
timeout: 10; # Probe test writes slowly
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: both
|
||||
# Defaults should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 1; # print configuration at startup
|
||||
verbose-config-error: 1; # print configuration errors
|
||||
verbose-connections: 1; # trace established incoming address to forward address
|
||||
verbose-connections-error: 1; # connection errors
|
||||
verbose-connections-try: 1; # connection attempts towards targets
|
||||
verbose-fd: 0; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 1; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 0; # what's happening during the probe process
|
||||
verbose-probe-error: 1; # failures and problems during probing
|
||||
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "localhost"; port: "8080"; keepalive: true; },
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; }
|
||||
{ host: "localhost"; port: "8081"; keepalive: true; },
|
||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
|
||||
);
|
||||
|
||||
|
||||
# Tester beware: when using fork, the forked process loses
|
||||
# track of buffers of other, concurrent connections. Memory
|
||||
# leak tools thus complain each time a forked process stops.
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; },
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "http"; host: "localhost"; port: "9002"; },
|
||||
{ name: "tinc"; host: "localhost"; port: "9003"; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "9004"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "9009"; },
|
||||
{ name: "adb"; host: "localhost"; port: "9010"; },
|
||||
{ name: "syslog"; host: "localhost"; port: "9013"; },
|
||||
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
|
||||
udp_timeout: 30;
|
||||
regex_patterns: [ "^foo" ];
|
||||
},
|
||||
{ name: "regex"; host: "localhost"; port: "9011";
|
||||
regex_patterns: [ "^foo", "^bar" ];
|
||||
minlength: 4;
|
||||
test_patterns: (
|
||||
test_patterns: ( # this is used by the test script, not by sslh
|
||||
{ pattern: "foo"; result: "ssh"; }, # After timeout
|
||||
{ pattern: "fooo"; result: "regex"; },
|
||||
{ pattern: "bar"; result: "ssh"; },
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <fnmatch.h> /* fnmatch() */
|
||||
#include "tls.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "log.h"
|
||||
|
||||
#define TLS_HEADER_LEN 5
|
||||
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
|
||||
@@ -82,14 +83,14 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
|
||||
tls_content_type = data[0];
|
||||
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||
if (cfg.verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
|
||||
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
|
||||
return TLS_EPROTOCOL;
|
||||
}
|
||||
|
||||
tls_version_major = data[1];
|
||||
tls_version_minor = data[2];
|
||||
if (tls_version_major < 3) {
|
||||
if (cfg.verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
|
||||
print_message(msg_probe_error, "Received SSL %d.%d handshake which cannot be parsed.\n",
|
||||
tls_version_major, tls_version_minor);
|
||||
|
||||
return TLS_EVERSION;
|
||||
@@ -111,7 +112,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
return TLS_EPROTOCOL;
|
||||
}
|
||||
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
|
||||
if (cfg.verbose) fprintf(stderr, "Not a client hello\n");
|
||||
print_message(msg_probe_error, "Not a client hello\n");
|
||||
|
||||
return TLS_EPROTOCOL;
|
||||
}
|
||||
@@ -144,7 +145,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
pos += 1 + len;
|
||||
|
||||
if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
|
||||
if (cfg.verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n");
|
||||
print_message(msg_probe_error, "Received SSL 3.0 handshake without extensions\n");
|
||||
return TLS_EVERSION;
|
||||
}
|
||||
|
||||
@@ -228,7 +229,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
|
||||
return TLS_ENOEXT;
|
||||
}
|
||||
default:
|
||||
if (cfg.verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
|
||||
print_message(msg_probe_error, "Unknown server name extension name type: %d\n",
|
||||
data[pos]);
|
||||
}
|
||||
pos += 3 + len;
|
||||
@@ -254,7 +255,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
|
||||
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
|
||||
return len;
|
||||
} else if (len > 0) {
|
||||
if (cfg.verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
||||
print_message(msg_probe_error, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
||||
}
|
||||
pos += 1 + len;
|
||||
}
|
||||
@@ -276,7 +277,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
|
||||
|
||||
for (i = 0; i < list_len; i++) {
|
||||
item = &list[i];
|
||||
if (cfg.verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
||||
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
||||
if(!fnmatch(*item, name_nullterminated, 0)) {
|
||||
free(name_nullterminated);
|
||||
return 1;
|
||||
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
udp-listener.c: handles demultiplexing UDP protocols
|
||||
|
||||
# Copyright (C) 2020-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
|
||||
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "udp-listener.h"
|
||||
|
||||
/* Incoming connections are of course all received on a single socket. Create a
|
||||
* hash that associates (incoming sockaddr) => struct connection*, so finding
|
||||
* the connection related to an incoming packet is fast.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
|
||||
{
|
||||
struct sockaddr* addr1 = &cnx1->client_addr;
|
||||
socklen_t addrlen1 = cnx1->addrlen;
|
||||
|
||||
struct sockaddr* addr2 = &cnx2->client_addr;
|
||||
socklen_t addrlen2 = cnx2->addrlen;
|
||||
|
||||
if (addrlen1 != addrlen2) return -1;
|
||||
|
||||
return memcmp(addr1, addr2, addrlen1);
|
||||
}
|
||||
|
||||
/* From an IP address, create something that's useable as a hash key.
|
||||
* Currently:
|
||||
* lowest bytes of remote port */
|
||||
static int hash_make_key(hash_item new)
|
||||
{
|
||||
struct sockaddr* addr = &new->client_addr;
|
||||
//socklen_t addrlen = new->addrlen;
|
||||
struct sockaddr_in* addr4;
|
||||
struct sockaddr_in6* addr6;
|
||||
int out;
|
||||
|
||||
switch (addr->sa_family) {
|
||||
case AF_INET:
|
||||
addr4 = (struct sockaddr_in*)addr;
|
||||
out = addr4->sin_port;
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
addr6 = (struct sockaddr_in6*)addr;
|
||||
out = addr6->sin6_port;
|
||||
break;
|
||||
|
||||
default: /* Just use the first bytes, skipping the address family */
|
||||
out = ((char*)addr)[2];
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static struct sslhcfg_protocols_item** udp_protocols;
|
||||
static int udp_protocols_len = 0;
|
||||
|
||||
static void udp_protocol_list_init(void)
|
||||
{
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
|
||||
if (p->is_udp) {
|
||||
udp_protocols_len++;
|
||||
udp_protocols = realloc(udp_protocols, udp_protocols_len * sizeof(*udp_protocols));
|
||||
udp_protocols[udp_protocols_len-1] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Configuration sanity check for UDP:
|
||||
* - If there is a listening addres, there must be at least one target
|
||||
*/
|
||||
static void udp_sanity_check(void)
|
||||
{
|
||||
int udp_present = 0;
|
||||
|
||||
for (int i = 0; i < cfg.listen_len; i++) {
|
||||
struct sslhcfg_listen_item* p = &cfg.listen[i];
|
||||
if (p->is_udp) {
|
||||
udp_present = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (udp_present && !udp_protocols_len) {
|
||||
print_message(msg_config_error, "At least one UDP target protocol must be specified.\n");
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Init the UDP subsystem.
|
||||
* - Initialise the hash
|
||||
* - that's all, folks
|
||||
* */
|
||||
void udp_init(struct loop_info* fd_info)
|
||||
{
|
||||
fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp);
|
||||
|
||||
udp_protocol_list_init();
|
||||
udp_sanity_check();
|
||||
}
|
||||
|
||||
|
||||
/* Find if the specified source has been seen before.
|
||||
* If yes, returns file descriptor of connection
|
||||
* If not, returns -1
|
||||
* */
|
||||
static int known_source(hash* h, struct sockaddr* addr, socklen_t addrlen)
|
||||
{
|
||||
struct connection search;
|
||||
search.client_addr = *addr;
|
||||
search.addrlen = addrlen;
|
||||
|
||||
struct connection* cnx = hash_find(h, &search);
|
||||
if (!cnx) return -1;
|
||||
return cnx->q[0].fd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int new_source(hash* h, struct connection* new)
|
||||
{
|
||||
return hash_insert(h, new);
|
||||
}
|
||||
|
||||
|
||||
/* Double linked list utilities: push element at tail of list */
|
||||
static void list_push(dl_list* list, struct connection* cnx)
|
||||
{
|
||||
cnx->timeout_next = NULL;
|
||||
|
||||
if (!list->head) {
|
||||
cnx->timeout_prev = NULL;
|
||||
list->head = cnx;
|
||||
}
|
||||
|
||||
if (list->tail) {
|
||||
list->tail->timeout_next = cnx;
|
||||
cnx->timeout_prev = list->tail;
|
||||
}
|
||||
|
||||
list->tail = cnx;
|
||||
}
|
||||
|
||||
/* Double linked list utilities: remove element */
|
||||
static void list_remove(dl_list* list, struct connection* cnx)
|
||||
{
|
||||
if (list->head == cnx) list->head = cnx->timeout_next;
|
||||
if (list->tail == cnx) list->tail = cnx->timeout_prev;
|
||||
|
||||
if (cnx->timeout_prev)
|
||||
cnx->timeout_prev->timeout_next = cnx->timeout_next;
|
||||
|
||||
if (cnx->timeout_next)
|
||||
cnx->timeout_next->timeout_prev = cnx->timeout_prev;
|
||||
}
|
||||
|
||||
/* Timeouts are managed with one list for each protocol. Whenever a connection
|
||||
* is active, it gets moved to the end of the list. Each call will pop the
|
||||
* first elements that have timed out and free their resources.
|
||||
*
|
||||
* This gets called every time a UDP packet is received from the outside, i.e.
|
||||
* every time we might need to free up resources. If no packets come in, we
|
||||
* don't time out anything, as we don't need the resources.
|
||||
* */
|
||||
void udp_timeouts(struct loop_info* fd_info)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
struct connection *cnx = cfg.protocols[i].timeouts.head;
|
||||
while (cnx && (now - cnx->last_active > cfg.protocols[i].udp_timeout)) {
|
||||
print_message(msg_fd, "timed out UDP %d\n", cnx->target_sock);
|
||||
tidy_connection(cnx, fd_info);
|
||||
|
||||
cnx = cfg.protocols[i].timeouts.head;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void udp_tidy(struct connection* cnx, struct loop_info* fd_info)
|
||||
{
|
||||
close(cnx->target_sock);
|
||||
hash_remove(fd_info->hash_sources, cnx);
|
||||
list_remove(&cnx->proto->timeouts, cnx);
|
||||
}
|
||||
|
||||
/* Mark the connection was active */
|
||||
static void mark_active(struct connection* cnx)
|
||||
{
|
||||
cnx->last_active = time(NULL);
|
||||
|
||||
dl_list* list = &cnx->proto->timeouts;
|
||||
|
||||
list_remove(list, cnx);
|
||||
list_push(list, cnx);
|
||||
}
|
||||
|
||||
|
||||
/* Process UDP coming from outside (client towards server)
|
||||
* If it's a new source, probe; otherwise, forward to previous target
|
||||
* Returns: newly allocate connections, for new connections
|
||||
* NULL otherwise
|
||||
* */
|
||||
struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
{
|
||||
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
struct sockaddr src_addr;
|
||||
struct addrinfo addrinfo;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
cnx_collection* collection = fd_info->collection;
|
||||
struct connection* cnx;
|
||||
ssize_t len;
|
||||
socklen_t addrlen;
|
||||
int res, target, out = -1;
|
||||
char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
|
||||
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
|
||||
malloc/free overhead for each packet, when really 64K is not that much */
|
||||
|
||||
|
||||
udp_timeouts(fd_info);
|
||||
|
||||
addrlen = sizeof(src_addr);
|
||||
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
|
||||
if (len < 0) {
|
||||
perror("recvfrom");
|
||||
return NULL;
|
||||
}
|
||||
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
|
||||
addrinfo.ai_addr = &src_addr;
|
||||
addrinfo.ai_addrlen = addrlen;
|
||||
print_message(msg_probe_info, "received %ld UDP from %d:%s\n",
|
||||
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||
|
||||
if (target == -1) {
|
||||
res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
|
||||
/* First version: if we can't work out the protocol from the first
|
||||
* packet, drop it. Conceivably, we could store several packets to
|
||||
* run probes on packet sets */
|
||||
print_message(msg_probe_info, "UDP probed: %d\n", res);
|
||||
if (res != PROBE_MATCH) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||
res = set_nonblock(out);
|
||||
CHECK_RES_RETURN(res, "udp:socket:nonblock", NULL);
|
||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
|
||||
if (!cnx) return NULL;
|
||||
target = out;
|
||||
cnx->target_sock = out;
|
||||
cnx->proto = proto;
|
||||
cnx->type = SOCK_DGRAM;
|
||||
cnx->client_addr = src_addr;
|
||||
cnx->addrlen = addrlen;
|
||||
cnx->local_endpoint = sockfd;
|
||||
|
||||
res = new_source(fd_info->hash_sources, cnx);
|
||||
if (res == -1) {
|
||||
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increaѕe udp_max_connections");
|
||||
collection_remove_cnx(collection, cnx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
cnx = collection_get_cnx_from_fd(collection, target);
|
||||
|
||||
/* at this point src is the UDP connection */
|
||||
res = sendto(cnx->target_sock, data, len, 0,
|
||||
cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen);
|
||||
mark_active(cnx);
|
||||
print_message(msg_fd, "sending %d to %s\n",
|
||||
res, sprintaddr(data, sizeof(data), cnx->proto->saddr));
|
||||
|
||||
return cnx;
|
||||
}
|
||||
|
||||
void udp_s2c_forward(struct connection* cnx)
|
||||
{
|
||||
int sockfd = cnx->target_sock;
|
||||
char data[65536];
|
||||
int res;
|
||||
|
||||
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
||||
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
|
||||
CHECK_RES_DIE(res, "udp_listener/recvfrom");
|
||||
res = sendto(cnx->local_endpoint, data, res, 0,
|
||||
&cnx->client_addr, cnx->addrlen);
|
||||
mark_active(cnx);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef UDPLISTENER_H
|
||||
#define UDPLISTENER_H
|
||||
|
||||
|
||||
#include "collection.h"
|
||||
#include "processes.h"
|
||||
#include "common.h"
|
||||
|
||||
/* UDP listener: upon incoming packet, find where it should go
|
||||
* This is run in its own process and never returns.
|
||||
*/
|
||||
void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint);
|
||||
|
||||
|
||||
/* Process UDP coming from outside (client towards server)
|
||||
* If it's a new source, probe; otherwise, forward to previous target
|
||||
* Returns: newly allocate connections, for new connections
|
||||
* -1 otherwise
|
||||
* */
|
||||
struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info);
|
||||
|
||||
/* Process UDP coming from inside (server towards client) */
|
||||
void udp_s2c_forward(struct connection* cnx);
|
||||
|
||||
|
||||
void udp_init(struct loop_info* fd_info);
|
||||
void udp_tidy(struct connection* cnx, struct loop_info* fd_info);
|
||||
|
||||
#endif /* UDPLISTENER_H */
|
||||
@@ -0,0 +1,31 @@
|
||||
# Example for UDP protocols
|
||||
|
||||
# Listen sockets get a `is_udp : true` option
|
||||
# Protocol entries work exactly the same as TCP, there is no
|
||||
# difference. Currently, if mix up TCP and UDP probes, sslh will
|
||||
# try them in order and forward a UDP packet to a TCP probe
|
||||
# that matches (but using UDP; meaning, sslh will happily
|
||||
# forward a UDP packet to udp:localhost:80 if the HTTP
|
||||
# probe is specified, and matches)
|
||||
|
||||
verbose: 3;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
timeout: 10;
|
||||
pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen: (
|
||||
{ host: "localhost"; is_udp: true; port: "4443"; }
|
||||
);
|
||||
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "regex"; regex_patterns: [ "rutschle.net" ]; host: "ns1.rutschle.net"; port: "53"; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "9099"; }
|
||||
);
|
||||
Reference in new issue
Block a user