Compare commits

...

626 Commits

Author SHA1 Message Date
Yves Rutschle
c0cc45975c clean up inline define 2025-04-15 22:27:27 +02:00
npt-1707
0fe9bd5a95 common.c: Fix that symlink does not interferer 2025-04-15 22:20:26 +02:00
Yves Rutschle
fe25928e18 Merge branch 'master' of github.com:yrutschle/sslh 2025-04-08 21:41:23 +02:00
Sergey Fedorov
eccf7dbdc4 common.h: add a declaration of hosts_ctl for macOS
Fixes: https://github.com/yrutschle/sslh/issues/492
2025-04-08 21:40:58 +02:00
Yves Rutschle
e0bcf282ff include config.h. unclear how this worked without that 2025-04-08 21:36:12 +02:00
Yves Rutschle
1f462ba166 update tag to v2.2.1 2025-04-06 11:59:51 +02:00
Yves Rutschle
0e7885bc9f fix compilation when libproxyprotocol is not present 2025-04-06 11:59:08 +02:00
Yves Rutschle
5ad1ea1e25 complete changelog for v2.3.0 2025-04-06 11:46:19 +02:00
Yves Rutschle
4978641271 add reference to new proxyprotocol to README 2025-04-06 11:38:30 +02:00
Yves Rutschle
ef6f698d86 document proxyprotocol 2025-04-03 21:30:34 +02:00
Yves Rutschle
416a82fcc6 code cleanup 2025-03-29 17:34:25 +01:00
Yves Rutschle
2f111b6b8d add missing proxyprotocol.o 2025-03-09 17:56:49 +00:00
Yves Rutschle
24c3bb07a0 add support for proxyprotocol v1 on backend server side 2025-03-09 18:49:17 +01:00
Yves Rutschle
951b708f61 clarify UDP will not be supported in sslh-fork in the future 2025-03-09 09:44:15 +01:00
Yves Rutschle
5a0897c5cb refactor: connect_addr() update the *cnx object upon connecting to backend server, instead of each caller doing it 2025-03-09 09:43:41 +01:00
Yves Rutschle
7a6673a877 merged proxyprotocol linking 2025-02-21 21:03:59 +01:00
yrutschle
3ebdca5e96 document move of libraries to autoconf detection 2025-02-19 09:28:49 +01:00
yrutschle
22a8ba9ef5 move libbsd support to autoconf detection 2025-02-19 09:25:59 +01:00
yrutschle
dcfa3fa2db move libcap support to autoconf detection 2025-02-19 09:01:01 +01:00
yrutschle
fabf0a121c actually include config.h 2025-02-19 08:59:17 +01:00
yrutschle
3a1c31d8cb move tcp wrapper support to autoconf detection 2025-02-18 10:09:12 +01:00
Yves Rutschle
e527b8e588 libproxyprotocol: add test and options to link the library if present 2025-02-01 10:12:04 +01:00
Yves Rutschle
a2b4da8483 fix #486: do not return prematurely when accept fails 2025-02-01 09:21:19 +01:00
Yves Rutschle
710807fd3b Fix possible file descriptor leak (fix #485) 2025-01-05 18:37:12 +01:00
Yves Rutschle
2e9f23a2f4 remove unix socket before binding 2024-12-23 17:25:40 +01:00
Yves Rutschle
bf082292c2 new is_unix field to create listen unix sockets 2024-12-22 23:54:14 +01:00
Yves Rutschle
59d89e34f0 refactor: move inet code to its own function 2024-12-22 21:58:51 +01:00
Yves Rutschle
cac7f48fa7 new is_unix field to connect to unix socket target servers 2024-12-22 16:17:47 +01:00
Yves Rutschle
9e6b4fae29 refactor: isolate inet connection 2024-12-22 00:06:54 +01:00
Yves Rutschle
16ef412663 use current tag when exporting an archive (fix #481) 2024-12-09 22:37:06 +01:00
Yves Rutschle
5f55f7d76a SOL_IP is Linux-specific, replace with portable IPPROTO_IP (fix #481) 2024-12-08 21:49:04 +01:00
Yves Rutschle
9243a6e369 check asprintf return value (fix #471) 2024-09-08 23:11:50 +02:00
Yves Rutschle
686d1f7cb6 refactor close stdin,out,err to own function and clarify the comment 2024-09-08 17:12:49 +02:00
ftasnetamot
e7a9a37624 changed SSL to TLS in sslh.pod initial description 2024-09-08 16:56:50 +02:00
ftasnetamot
7d41760f9a spellcheck correction, man-page, modified debian-sys-v-startscript 2024-09-08 16:56:50 +02:00
ftasnetamot
4def95865c still testing, as on i watched reusing fd 0 2024-09-08 16:56:50 +02:00
ftasnetamot
109052fdc7 Updated PR as discussion in issue 468 , currently ony for testing 2024-09-08 16:56:50 +02:00
ftasnetamot
d6bb000115 close std-filehandles when daemonize 2024-09-08 16:56:50 +02:00
ftasnetamot
18a9a882f5 Added some clarifications to scenario 3
added information about iproute 2
2024-08-15 23:07:26 +02:00
ftasnetamot
acdbb79d43 include version.h in repo
Many code checking editors, marking the common.h as erroneous, as
version.h is missing. This can lead to confusion.
Change in Makefile.in with fake empty dependency ensures, that version.h
is recreated at every "make"
Another change in make clean, makes sure, that at this point a stub
version.h is generated.

version.h removed from .gitignore
2024-08-15 23:06:46 +02:00
ftasnetamot
1fd072837b added forgotten link to cloudflare blog. Without this link, the wording
maked no sense.
2024-08-15 23:04:18 +02:00
ftasnetamot
a34f34917a Documentation, why Daisy-Chaining IP-Transparent
applications will fail. Discuss this in some examples.
2024-08-15 23:04:18 +02:00
ftasnetamot
d6041c93c4 added missing 3rd argument to CHECK_RES_RESULT 2024-08-15 23:02:54 +02:00
ftasnetamot
2e2701de55 Changed a CHECK_RES_DIE to CHECK_RES_RETURN, as the real problem will
occur at connect!
2024-08-15 23:02:54 +02:00
ftasnetamot
70a9b97f81 optimized setsockopt() options for better
cooperation between different transparent-ip applications.
See: https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections
for an explanation for those changes.
2024-08-15 23:02:54 +02:00
ftasnetamot
72a4376248 Enhance documentation about compiling;
make disabling landlock in Makefile possible, as single point of
configuration
2024-08-15 22:58:54 +02:00
Yves Rutschle
316e9a1863 fix documentation on verboseness (#452) 2024-07-10 15:41:49 +02:00
Yves Rutschle
c892fc4b34 add short for verbose; remove config debug printing 2024-07-10 15:37:34 +02:00
Yves Rutschle
b619f5bf36 add a verbose option that overrides all others 2024-07-10 15:31:55 +02:00
Yves Rutschle
06b7d7ed14 add reference to libwrap in example configuration (fix #418) 2024-06-26 19:19:03 +02:00
Yves Rutschle
a222ea2c99 update Changelog 2024-06-26 19:17:48 +02:00
yrutschle
eb84c6a55b add libwrap files to landlock read list (fix #450) 2024-06-17 23:00:16 +02:00
yrutschle
93ab4f1e3a refactor in smaller functions 2024-06-17 22:54:52 +02:00
Yves Rutschle
5e252bb3f6 add links to ip-routing transparent proxying 2024-06-12 09:40:33 +02:00
ftasnetamot
d6265e2c50 Added svg file
corrected link to the diagram in md file
2024-06-11 21:40:06 +02:00
ftasnetamot
e1229dca28 updated picture, as png export failed in workflow before :-( 2024-06-09 17:21:04 +02:00
ftasnetamot
06e697e029 removed the warning regards kernel forwarding,
as this is also not necessary.
Updated text and picture
2024-06-09 17:21:04 +02:00
ftasnetamot
0bb3286a7d corrected broken style of connection arrows
in the diagram showing the scenarios
2024-06-09 17:21:04 +02:00
ftasnetamot
2fd9ea600a Added another file, discussing three possible setup scenarios, which
should cover up all setups.

Changes to be committed:
  new file:   scenarios-for-simple-transparent-proxy.md
  modified:   simple_transparent_proxy.md
  new file:   sslh-examples-v1.png
2024-06-09 17:21:04 +02:00
ftasnetamot
f4eea875e2 drastically reduced the configuration by putting all the magic into the interface-configuration 2024-06-04 21:04:56 +02:00
ftasnetamot
e8934f7a81 some more scenarios, how the simple configuration can be used. 2024-06-04 21:04:56 +02:00
ftasnetamot
8271db2d9d Add the description of a simple transparent proxy without the need of iptables/nftables and loopback routing.
Explain how all that works.
2024-06-02 14:26:50 +02:00
Yves Rutschle
995916c923 update history 2024-05-25 16:22:47 +02:00
Sergey Ponomarev
799d75413f echosrv.c: fix format '%ld' expects argument of type 'long int', but argument 3 has type 'ssize_t' {aka 'int'}
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
2024-05-25 16:21:50 +02:00
Yves Rutschle
8166be1a09 Fix inetd mode (fix #399) 2024-05-16 21:30:11 +02:00
Yves Rutschle
684374f353 avoid useless strcpy (fix #440) 2024-05-11 17:01:48 +02:00
Yves Rutschle
de7351fd84 Make basic.cfg more useful (fix #438) 2024-04-21 18:20:54 +02:00
yrutschle
d2ca706f86 document use of glob for SNI matching (fix #373) 2024-04-13 23:38:32 +02:00
Sergey Ponomarev
c859e341aa .gitignore generated files by ./configure 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
717fe8fae5 INSTALL.md: reformat
Add ./configure step
Fix libpcre-dev to libpcre2-dev
2024-04-13 20:25:18 +02:00
Sergey Ponomarev
1ddf45bf52 tcp_protocol_list_init(): allocate once instead of realloc each time 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
ae7530e33f Fix Narrowing conversion from 'ssize_t' to signed type 'int' is implementation-defined 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
d0a016221c probe.c: Fix Function 'memcmp' is called without explicitly comparing result 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
9286b55702 echosrv.c: Fix Narrowing conversion from 'ssize_t' (aka 'long') to signed type 'int' is implementation-defined 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
31c9e19abb echosrv.c: fix Narrowing conversion from ssize_t to int 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
ef8f3d1419 INSTALL.md fix typo 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
2759c223be README.Windows.md fix link 2024-04-13 20:25:18 +02:00
Sergey Ponomarev
b3c770898a
Ignore opts (#436)
* Be less strict about args at startup

Ignore unknown options.

* Don't fail if pid file is accessible
2024-04-13 11:03:54 +02:00
yrutschle
fee8491a8e remove useless capabilities and use standard environment in systemd 2024-04-10 18:39:07 +02:00
Yves Rűtschlé
a80d79fd40 Merge multiple sslh systemd unit configurations 2024-04-10 18:07:28 +02:00
Yves Rűtschlé
4b921be69d Merge systemd unit multiple configurations 2024-04-10 18:06:56 +02:00
Yves Rutschle
1799a81079 fix off-by-one error that would sometime ignore the latest connection 2024-03-31 22:18:58 +02:00
Sergio C
5f1c1b1b61
Update README.md (#431)
Updated tproxy.md location
2024-03-24 08:33:34 +01:00
Yves Rutschle
58783af410 For MacOS, do not have multiple identicat cases in switch 2024-03-23 09:53:01 +01:00
Yves Rutschle
1957be1dc3 v.2.1.0 2024-03-22 16:41:50 +01:00
Yves Rűtschlé
ecca78bde7 for MacOS, define unknown symbols to equivalent values 2024-03-21 18:54:03 +01:00
Yves Rűtschlé
b94060ad76 undef FD_SETSIZE to avoid compiler warning upon redefine 2024-03-15 15:09:39 +01:00
Yves Rűtschlé
736b108a75 dont log error when remote client drops connection on Windows (fix #427) 2024-03-13 16:58:09 +01:00
Yves Rutschle
7ca567fcd9 clarify the difficulty of transparent proxying 2024-03-12 22:07:17 +01:00
Yves Rutschle
3117c15fbd when sslh-fork accept() fails with some errors, retry accept instead of dying 2024-01-11 21:32:39 +01:00
Yves Rutschle
e428fc505c prevent children from continuing the parent's work in case of failure 2024-01-11 21:24:19 +01:00
Yves Rutschle
4dfb4d300a add default LANDLOCK_ACCESS_FS_REFER define for Ubuntu (fix #420) 2024-01-06 11:56:57 +01:00
Yves Rutschle
39184b5622 enable landlock access to files to allow forking and name resolution 2024-01-05 13:21:55 +01:00
Yves Rutschle
940461de18 check name resolution error to avoid segfault 2024-01-05 11:15:53 +01:00
Yves Rutschle
6f949419d1 fix comment to reflect reality 2024-01-05 10:34:29 +01:00
Yves Rutschle
dab5df7409 clarify where the name resolution error comes from 2024-01-05 10:32:41 +01:00
Yves Rutschle
402ca5219b fix warnings on sockaddr storage 2024-01-04 23:22:44 +01:00
Yves Rutschle
046401148d add ./configure stage to docker build 2024-01-04 23:04:26 +01:00
Yves Rutschle
780e536aeb use autoconf to crete build scripts depending on landlock presence (fix #417) 2024-01-04 22:45:10 +01:00
Yves Rutschle
ed0ab12a16 reintroduce --ssl as alias to --tls 2024-01-02 21:38:23 +01:00
Yves Rutschle
b65f1e8b26 Merged Landlock feature 2023-12-09 14:13:07 +01:00
Konstantin
91b649daa0
Set image tag to use github in docker-compose example (#414)
Change image tag of the docker-compose example from ```sslh:latest``` to ```ghcr.io/yrutschle/sslh:latest```
2023-11-19 20:43:13 +01:00
Latchezar Tzvetkoff
7499c26e9e
Cleanup error checking logic in bind_peer() (#412)
Thanks for the cleanup!
2023-11-15 22:02:21 +01:00
Yves Rutschle
90a55b6f9d document Windows build and docker repositories 2023-11-15 21:49:44 +01:00
Yves Rutschle
1f66e2e093 add sslh-ev 2023-11-15 21:46:42 +01:00
Yves Rutschle
92d2326016
Merge pull request #408 from pcrow/master
Fix transparent mode in a multi-stage chain
2023-10-07 21:15:58 +02:00
Preston Crow
81eed9d56a Transparent mode in a multi-stage chain will fail after the first step
because the (ip,port) is already bound.  With this change, the bind is
retried with a different port to at least keep the same IP address, which
for most uses is all that is needed.  I've tested this on my own system
where sslh is used downstream from stunnel, with both in transparent mode.
2023-10-06 17:50:25 -04:00
Yves Rutschle
490a44723b fix install to build sslh-fork (fix #407) 2023-10-05 11:53:09 +02:00
Yves Rutschle
23fb1eba6f remove obsolete intermediate sslh target 2023-10-03 09:34:38 +02:00
Yves Rutschle
be66848e2d
Merge pull request #406 from ffontaine/master
Makefile: add USE_LIBEV
2023-10-03 09:32:42 +02:00
Fabrice Fontaine
3e93c1d43d Makefile: add USE_LIBEV
Add USE_LIBEV to avoid the following build failure without libev raised
since version 2.0 and
711c11c820:

sslh-ev.c:24:10: fatal error: ev.h: Aucun fichier ou dossier de ce type
   24 | #include <ev.h>
      |          ^~~~~~

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2023-10-01 18:53:43 +02:00
Yves Rutschle
1b26eb50a5 Changelog for resolve_on_forward fix 2023-09-24 09:25:52 +02:00
Yves Rutschle
e0f15a31b7 resolve name at connection time for UDP too 2023-09-12 21:39:51 +02:00
Yves Rutschle
c2551c011e update to Conf::Libconfig 1.0.3 API 2023-09-12 21:35:23 +02:00
Yves Rutschle
e2c3ed61a8 update to Conf::Libconfig 1.0.3 API 2023-09-12 21:35:10 +02:00
Yves Rutschle
1b0c6d0b8d add resolve_on_forward in tests 2023-09-06 18:01:52 +02:00
Yves Rutschle
0562eb4b07 fix resolve_on_forward use (fix #405) 2023-09-06 15:48:13 +02:00
Yves Rutschle
93600d1fb1
Merge pull request #299 from amake/patch-1
Allow supplying additional CFLAGS
2023-08-31 15:26:42 +02:00
Yves Rutschle
04f258e705
Merge branch 'master' into patch-1 2023-08-31 15:26:22 +02:00
Yves Rutschle
1fb888bbf2 remove magic numbers with correct sizeof 2023-08-31 15:18:22 +02:00
Yves Rutschle
1f0cab2aee
Merge pull request #342 from utoni/fix/ovpn-probe-read-overflow
fix possible buffer overflow
2023-08-31 15:15:50 +02:00
Yves Rutschle
ff9328fa6c Merge branch 'utoni-fix/cppcheck-complains' 2023-08-31 15:13:57 +02:00
Yves Rutschle
e941e8dd2e fix cppcheck complains 2023-08-31 15:10:53 +02:00
Yves Rűtschlé
8930ec395e Initial support for the landlock LSM 2023-08-29 17:20:51 +02:00
Yves Rutschle
54fe4b2f47
Merge pull request #401 from rnhmjoj/master
fix handling of IPv6 UDP connections
2023-08-26 21:32:22 +02:00
rnhmjoj
33129481cf
fix handling of IPv6 UDP connections
Problem:
IPv6 addresses are 4 bytes long and don't fit inside a `sockaddr`, so
`recvfrom` will truncate the address to the first half.
When generating a reply, the remaining half of the address is filled
with garbage and the packet is subsequently delivered to the wrong host,
if not immediately dropped.

Solution:
replace `sockaddr` with `sockaddr_storage`, the latter is guaranteed to
be large enough to hold an IPv6 address and pointers can be cast to
`sockaddr *` when needed.
2023-08-22 11:44:09 +02:00
Yves Rűtschlé
4cc0867753 remove dependency of sslh-conf.c to sslhconf.cfg (fix #283) 2023-08-13 10:38:39 +02:00
Yves Rutschle
4728730abc
Merge pull request #392 from klementng/docker/transparent-patch
Docker: ipv6 transparent patch
2023-08-10 08:21:39 +02:00
clement
9e7b4b751f sync and resolve merge conflict 2023-08-09 23:36:01 +08:00
Clement
b11f2620ab
Add clarification on --transparent mode for docker 2023-08-09 22:57:47 +08:00
Yves Rutschle
056c283145
Merge pull request #393 from oliv3r/dev/cleanup
container: Cleanup some style issues
2023-08-09 08:25:22 +02:00
Olliver Schinagl
00beb9595d
container: Cleanup some style issues
Commit 5635dc5142aa ("Enable --transparent mode for docker") made a
little bit of a mess of the Dockerfile and container-entrypoint.sh.

A few issues are, but not limited to; trailing whitespaces, incorrect
indentation, removed final newline, component sortability just to name a
few.

This MR fixes that and cleans up those files again.

One thing not touched was the enable/disablement of `set +e` to exit the
script on error. It is nicer/cleaner to solve this in a different way,
but that adds to much complexity.

While here, make the container architecture and alpine version
configurable, allowing us to build multi-arch images from the CI in the
future.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-08-08 09:01:51 +02:00
clement
27f3e9075a update examples 2023-08-08 08:11:08 +08:00
clement
3912330040 add proper ipv6 checking 2023-08-08 08:02:54 +08:00
Yves Rutschle
4cbff962db
Merge pull request #386 from oliv3r/dev/fix_version_tag
version: Do not put a slash in the version tag
2023-07-30 23:12:32 +02:00
Yves Rutschle
29c949e051
Merge pull request #389 from jsoref/spelling
Spelling
2023-07-30 23:09:03 +02:00
Yves Rutschle
e8a84e6c22
Merge pull request #388 from klementng/master
Enable --transparent mode for docker
2023-07-30 23:01:38 +02:00
Josh Soref
42476d3cdc spelling: version
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:13 -04:00
Josh Soref
f48f74c004 spelling: unprivileged
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:13 -04:00
Josh Soref
3af02d5c44 spelling: transparent
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:13 -04:00
Josh Soref
9e6d5cc13a spelling: theoretical
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
74fe57147b spelling: tentative
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
9acec69b9a spelling: successful
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
2a81ec0650 spelling: subsequent
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
596a4ee5b4 spelling: shortopts
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
dbfeb480fe spelling: return
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
6b8a203691 spelling: responsibility
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
95e351e150 spelling: privileges
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
4b4c585107 spelling: parenthesis
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
0217e842d2 spelling: overridden
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
877ef1d27c spelling: other
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
b1bfd5aee3 spelling: nonexistent
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
af38544316 spelling: listening
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
d4908178bf spelling: initialized
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
9deedec029 spelling: initialised
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
8ef3e8ddd8 spelling: increase
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
1eeba07396 spelling: inconsistency
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
2c48b8d83b spelling: functionally
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
44bac28718 spelling: freebind
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
f520c616cc spelling: explicitly
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
39de82ae13 spelling: echosrv
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
08bea0e15d spelling: discrepancy
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
cb52f3cdb4 spelling: deleterious
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
306164531f spelling: defining
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
40ddc4900a spelling: command
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
32aaacf4e7 spelling: argument
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
a6a73f4d32 spelling: arbitrary
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
3d959eb425 spelling: address
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:43:12 -04:00
Josh Soref
b2fd9d9daf spelling: github
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-07-30 01:15:36 -04:00
clement
8ff27e931f Change ordering for better layer caching 2023-07-22 06:46:00 +08:00
Clement
ecbc33ba29
Update github build link 2023-07-21 07:07:25 +08:00
clement
5635dc5142 Enable --transparent mode for docker 2023-07-21 06:58:18 +08:00
Yves Rutschle
bb76bc1d31
Merge pull request #382 from oliv3r/add_default_entrypoint
docker: Add proper entrypoint
2023-06-10 19:17:12 +02:00
Yves Rutschle
4a5ccb75b2
Merge pull request #384 from oliv3r/fix/parallel_builds
CI: Do not parallelized container builds
2023-06-10 19:15:39 +02:00
Olliver Schinagl
6672cc0f86
version: Do not put a slash in the version tag
Many systems do not like having a `/` in the version tag. In some cases,
we generate a version as `head/branch`, which even gets amplified if one
uses `dev/feature` as a branch name.

So lets drop these slashes to avoid potential issues.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-09 14:00:30 +02:00
Olliver Schinagl
9dd560493a
container: Drop privileges
A container is best served with the least amount of privileges. This
also ensures we don't have to drop anything later.

This does require running the container with elevated capabilities.

Note, that if for whatever reason, 'root' access within the container is
needed, this can easily be accomplished by running the container with
`docker run --user root:root sslh` for example.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-05 22:50:06 +02:00
Olliver Schinagl
db5ed29fa2
docker: Add proper entrypoint
As per docker guidelines [0] a container should always really have a
consistent entrypoint, without having to override it or do special
tricks.

The behavior should be _identical_ as before, but will no longer trigger
errors because sslh doesn't understand certain parameters (/bin/sh
for example being common). Further more, allows a proper entrypoint for
a CI to work easily with the container as well. Allowing for scenario's
such as `apk add git && sslh --foreground` in your sslh image for example.

E.g. `docker run sslh --help` works though with the default
`--foreground` a bit weirdly, as does `docker run sslh
/bin/sh` or `docker run sslh ls`.

[0]: https://github.com/docker-library/official-images#consistency

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-05 22:50:06 +02:00
Olliver Schinagl
295dba93b5
docker: Do not foreground by default, this should be a choice
It is weird that when invoking sslh, that it daemonizess in foreground by
default. This should always be a user choice, and if not, it should be a
program default.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-05 22:50:06 +02:00
Olliver Schinagl
f23da1fc36 CI: Do not parallelized container builds
Container builds that are parallelized must be 'merged' again
afterwards. Because that makes the pipeline far more complex for a quick
compile job, we might as well just run in sequentially.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-05 22:26:30 +02:00
Olliver Schinagl
20764074cb
docker: Improve caching layers
Docker is most efficient if you can 'order' the layers from
least-changing to most changing to improve on cache hits.

While here, change ADD to COPY as add is really intended to download
external packages, as well as installing sslh into a proper location.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-05 22:05:24 +02:00
Yves Rutschle
7b7c9231b0
Merge pull request #383 from oliv3r/dev/pipeline
docker: Automatically build and push container
2023-06-05 20:53:35 +02:00
Olliver Schinagl
397f672248
docker: Automatically build and push container
The current sslh container works fine, but needs to be created manually
and locally by the user. Instead, let the pipeline do the dirty work and
push it to this repo's own registry.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2023-06-05 17:09:50 +02:00
Yves Rutschle
3707c5b8a6 fix file descriptor leak if bind_peer fails 2023-05-13 23:18:07 +02:00
Yves Rutschle
5666a1bb9d die if fd is not in cnx, which should be impossible (current behaviour results in illegal array dereferencing, which is worse) 2023-05-13 23:11:03 +02:00
Yves Rutschle
7b9c7f0fb2 check accept() return value 2023-05-13 23:00:26 +02:00
Yves Rutschle
eec2446723 fix error messages 2023-05-13 22:57:08 +02:00
Yves Rutschle
d29c9524bd fix unbalance of va_start/va_end 2023-05-13 22:51:04 +02:00
Yves Rutschle
db4ae0ef9d fix potential memory leak if the second malloc fails 2023-05-13 22:40:53 +02:00
Yves Rutschle
ee48dae8c5 fix potential file descriptor leak if set_nonblock () fails 2023-05-13 22:36:21 +02:00
Yves Rutschle
842f6b0473 Add mention of QUIC example (fix #376) 2023-02-19 17:24:51 +01:00
Yves Rutschle
1f64a71cde fix out-of-bounds read in sslh-ev (fix #368) 2023-01-08 22:50:56 +01:00
Yves Rutschle
00fc8e5d95 fix off-by-one error 2023-01-08 22:50:10 +01:00
Yves Rutschle
b9602ab98b removed obsolete max_fd tracker 2023-01-08 22:32:17 +01:00
Yves Rutschle
486f8a0090 removed obsolete comments 2023-01-08 21:58:50 +01:00
Yves Rutschle
3a1ac6c8d7 add protocol name that was missing in previous commit 2023-01-08 17:45:50 +01:00
Yves Rutschle
9dc3e3ce56 connection loss before it is logged resulted in logging uninitialised characters 2023-01-08 17:31:05 +01:00
Yves Rűtschlé
40c616e94c downgrade TLS error to info (fix #367) 2022-11-20 18:26:20 +01:00
Yves Rűtschlé
555717e345 defensive programming in case connections get tidied while there is activity on both file descriptors (fix #355) 2022-11-09 17:48:14 +01:00
Yves Rűtschlé
02573eb44b only process write events if file descriptor has not been tidied (may fix #355) 2022-11-03 17:23:49 +01:00
Yves Rűtschlé
d166b8977c document test script dependency 2022-11-03 09:32:47 +01:00
Yves Rűtschlé
4c3b52dda5 Merge branch 'master' of https://github.com/yrutschle/sslh 2022-10-29 22:31:35 +02:00
Yves Rűtschlé
c981ae9853 update log format to actually catch ssh bruteforcing (fix #359) 2022-10-29 22:31:24 +02:00
Yves Rutschle
8a0de7b628
Merge pull request #362 from exussum12/patch-1
Fix typo
2022-10-21 18:08:45 +02:00
Yves Rutschle
5154630fe0
Merge pull request #365 from iamdoubz/master
Update make clean to remove ev builds
2022-10-21 18:07:11 +02:00
iamdoubz
63d5ecddca
Update make clean to remove ev builds 2022-10-20 12:04:02 -05:00
Scott Dutton
e412811ff1
Update config.md 2022-10-16 00:19:21 +01:00
Yves Rutschle
7e3f723699
Merge pull request #356 from utoni/fix/watcher-unitialised-value
Fixes unitialised memory access as seen in issue #355.
2022-09-22 21:10:12 +02:00
Toni Uhlig
a1db2e8a92
Fixes unitialised memory access as seen in issue #355.
==1391== Conditional jump or move depends on uninitialised value(s)
==1391==    at 0x10E92F: watchers_add_read (sslh-select.c:67)
==1391==    by 0x10E92F: watchers_init (sslh-select.c:59)
==1391==    by 0x10E92F: main_loop (sslh-select.c:134)
==1391==    by 0x10DB6D: main (sslh-main.c:285)

Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-09-22 11:47:03 +02:00
Yves Rűtschlé
72c743e1e1 acknowledges akappner 2022-09-11 22:15:14 +02:00
Toni Uhlig
61af0b2e09
fix cppcheck complains
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-09-11 22:12:33 +02:00
Yves Rűtschlé
5cba44f5fa Add built-in MSRDP support 2022-09-11 21:51:06 +02:00
Yves Rutschle
9a36854ed3
Merge pull request #353 from utoni/fix/possible-format-str-exploit
fix possible format str vuln
2022-09-11 21:15:51 +02:00
Toni Uhlig
b19f8a6046
fix possible format string exploit if packet dumping enabled
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-09-10 12:53:57 +02:00
Yves Rutschle
bb685f8467
Merge pull request #344 from utoni/improve/makefile-ar
Makefile generates libsslh.a useable for other projects e.g. fuzzer.
2022-09-07 19:11:44 +02:00
Yves Rűtschlé
f418ae6128 added wireguard command-line setting 2022-09-04 18:48:17 +02:00
Yves Rutschle
a6df18527c
Merge pull request #345 from utoni/add/wireguard
add wireguard probe
2022-09-04 18:30:09 +02:00
Yves Rűtschlé
e690cb5622 Merge branch 'master' of https://github.com/yrutschle/sslh 2022-09-04 15:28:55 +02:00
Yves Rűtschlé
64c3e0ed1e improve basic.cfg 2022-09-04 15:26:39 +02:00
Toni Uhlig
b971f3edcd
add wireguard probe
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-26 12:27:44 +02:00
Toni Uhlig
d2ec01c072
Makefile generates libsslh.a useable for other projects e.g. fuzzer.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-26 12:12:33 +02:00
Toni Uhlig
c7ddee0409
fix possible buffer overflow
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-19 20:17:59 +02:00
Yves Rutschle
fb8fe57bd8
Merge pull request #341 from utoni/add/teamspeak-probe
add teamspeak3 (voice only) probe
2022-08-19 18:03:12 +02:00
Yves Rutschle
79c8af6ed1
Merge pull request #340 from utoni/fix/sslh-select
changed `select(nfds, ...)` to `select(nfds + 1, ...)`, see `man 2 select`
2022-08-19 18:00:25 +02:00
Yves Rutschle
c4e7261a51
Merge pull request #339 from utoni/add/config-sanity-checks
add some config sanity checks, fixes #307
2022-08-19 17:53:08 +02:00
Yves Rutschle
7e2bb7f01f
Merge pull request #338 from utoni/add/openvpn-udp-probe
add openvpn udp probe
2022-08-19 17:50:39 +02:00
Toni Uhlig
4cf3749e73
add teamspeak3 (voice only) probe
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-14 10:42:58 +02:00
Toni Uhlig
8d124e1085
changed select(nfds, ...) to select(nfds + 1, ...), see man 3 select
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-13 23:40:46 +02:00
Toni Uhlig
a6c5e07d69
add some config sanity checks, fixes #307
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-13 22:18:26 +02:00
Toni Uhlig
aa17061e26
add openvpn udp probe
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-08-11 17:22:44 +02:00
Yves Rűtschlé
9d10989d55 reword documentation to account for sslh-ev 2022-07-24 17:59:11 +02:00
Yves Rűtschlé
8b604a3db7 defensive programming against inconsistent configuration (fix #336 more) 2022-07-24 17:50:13 +02:00
Yves Rűtschlé
5168fe081a add mention of sslh-ev 2022-07-21 18:24:36 +02:00
Yves Rűtschlé
7b923f793e fix packages names for Debian 11 2022-07-21 18:20:56 +02:00
yrutschle
a4db163a69 config sanity check that there is at least one target protocol for each family that we listen to (fix #336) 2022-07-10 21:16:41 +02:00
yrutschle
b36486bb6d prepare v2.0 2022-06-07 20:57:44 +02:00
yrutschle
d16d132f0e document udp_max_connections in example.cfg 2022-05-31 22:49:39 +02:00
yrutschle
3fffce323d fix dependencies for hash.o (fix #333) 2022-05-25 17:07:19 +02:00
yrutschle
83478818df update comment to mention sslh-ev 2022-05-22 22:33:27 +02:00
yrutschle
750e828d49 reinstate checking of FD_SETSIZE for sslh-select 2022-05-22 22:32:22 +02:00
yrutschle
82aeedefcd allow gap set and get to be inlined 2022-05-05 22:40:12 +02:00
yrutschle
f6fe735171 sort target protocols as TCP or UDP, so only appropriate probes are called by the listeners 2022-05-05 17:45:40 +02:00
yrutschle
78827d75fe provide simple output to stderr (fix #330) 2022-05-05 09:12:45 +02:00
yrutschle
7228c0ebc3 refactor: move TCP code to new object tcp-listener 2022-05-03 17:03:30 +02:00
yrutschle
da194f15f0 make local functions static 2022-05-02 23:01:28 +02:00
yrutschle
d23a537d62 regerenate c2s files 2022-04-30 09:56:08 +02:00
Yves Rutschle
aa14090bcc
Merge pull request #329 from utoni/add/log-to-file
Added support for logging to a file.
2022-04-30 09:53:07 +02:00
yrutschle
006706a901 typo 2022-04-30 09:38:30 +02:00
lns
f9831df8bc Added support for logging to a file.
* Added ASAN/LSAN/UBSAN support via Makefile
 * Fixed a memory leak

Signed-off-by: lns <matzeton@googlemail.com>
2022-04-28 15:19:18 +02:00
yrutschle
c78a50c1d7 remove obsolete code 2022-04-27 17:33:36 +02:00
yrutschle
92e8a3e256 manage timeout with lists instead of linear searches 2022-04-27 17:29:02 +02:00
yrutschle
cd664574f1 manage timeout with lists instead of linear searches 2022-04-27 17:28:13 +02:00
yrutschle
449fabba51 linked list sorted by timeout times 2022-04-24 18:35:09 +02:00
yrutschle
cd5d75fed9 fix off-by-one error that wont time out highest UDP file descriptor 2022-04-24 18:31:27 +02:00
yrutschle
50f5af394b remove obsolete declaration 2022-04-18 22:55:15 +02:00
yrutschle
8ac93c3e9d abstract connection activation 2022-04-18 21:25:07 +02:00
Yves Rutschle
43bd660df2
Merge pull request #328 from jerome992/master
fixed docker compilation error
2022-04-10 16:24:50 +02:00
yrutschle
35036c94c7 make UDP hash size configurable 2022-04-10 09:03:53 +02:00
yrutschle
21d00bd29d remove globals for hash size 2022-04-10 08:45:01 +02:00
Jerome
32b065d895 fixed docker compilation error 2022-04-09 22:09:40 +02:00
yrutschle
cd7afaa00d fix comment to reflect floorless algorithm 2022-04-09 13:27:30 +02:00
yrutschle
53ae8bb913 remove floor entirely 2022-04-09 13:22:55 +02:00
yrutschle
97a67500ea remove need for floor at insert 2022-04-08 21:10:20 +02:00
yrutschle
33d73dd514 invert distance counting 2022-04-08 21:09:45 +02:00
yrutschle
63b503e27f simplify remove with distance computation 2022-04-08 19:55:22 +02:00
yrutschle
2e0d6b6e8e removed obsolete variable 2022-04-08 18:40:39 +02:00
yrutschle
4625883b7f simplify insertion using distance instead of floors and wrapped 2022-04-08 18:38:29 +02:00
yrutschle
07ceb99280 remove unused variable 2022-04-02 19:04:49 +02:00
yrutschle
5fa03ec9a3 fix hash item type definition 2022-04-02 18:59:32 +02:00
yrutschle
9ce9b5cd82 Integrate hash for UDP 2022-04-02 18:48:24 +02:00
yrutschle
97810cf0b2 Merge branch 'master' of https://github.com/yrutschle/sslh 2022-04-02 15:10:51 +02:00
Yves Rutschle
f8684fc9d6 add resolve_on_forward to c2s files (fix #326) 2022-04-01 19:35:16 +02:00
Yves Rutschle
5def70dff5 fix dependencies so conf2struct gets called before sslh-conf.h is used 2022-04-01 19:34:25 +02:00
yrutschle
478e1fc92f abstract hash indexes 2022-03-31 22:08:56 +02:00
Yves Rutschle
96b1aa86d8
Merge pull request #325 from g1franc/patch-1
Update README.MacOSX
2022-03-31 15:27:00 +02:00
g1franc
b1517f27a8
Update README.MacOSX
Should be *tls* as argument here instead of old *ssl*
2022-03-31 00:06:48 +02:00
yrutschle
454a261c95 initial hash object with testing framework 2022-03-30 22:56:41 +02:00
yrutschle
9c3274359a document resolve_on_forward 2022-03-23 21:33:45 +01:00
Yves Rutschle
a71de786d6
Merge pull request #323 from milkpirate/feat/resolve-on-connect
Feat/resolve on connect
2022-03-23 21:26:25 +01:00
Yves Rutschle
dd167d13a3
Merge pull request #324 from beango1/patch-1
typo correction
2022-03-23 21:23:05 +01:00
beango1
759e68c8d2
typo correction 2022-03-23 10:22:44 -04:00
Paul Schroeder
78bc954769
review
Signed-off-by: Paul Schroeder <milkpirate@users.noreply.github.com>
2022-03-19 23:18:29 +01:00
Paul Schroeder
3f5c81d2f6
be more clearly
Signed-off-by: Paul Schroeder <milkpirate@users.noreply.github.com>
2022-03-18 18:03:58 +01:00
yrutschle
ff810d41b0 Merge branch 'master' of https://github.com/yrutschle/sslh 2022-03-18 18:03:29 +01:00
yrutschle
d922086f53 handle accept failure properly (fix #322) 2022-03-18 18:03:18 +01:00
yrutschle
1e0578c082 don't log to syslog when testing 2022-03-18 18:02:32 +01:00
Paul Schroeder
87577ae5f6
add functionality
Signed-off-by: Paul Schroeder <milkpirate@users.noreply.github.com>
2022-03-18 17:59:54 +01:00
Paul Schroeder
875fa488c9
add option and description
Signed-off-by: Paul Schroeder <milkpirate@users.noreply.github.com>
2022-03-18 17:59:29 +01:00
yrutschle
91d148f66c removed obsolete resolve_name function 2022-03-17 21:51:54 +01:00
yrutschle
66f4b18121 ensure conf2struct is called early in the make 2022-03-17 21:51:01 +01:00
Yves Rutschle
bccb78e1a3
Merge pull request #317 from jeffre/master
docs: corrections to docker usage
2022-01-23 16:26:55 +01:00
jeffre
6f497b6c8b docs: corrections to docker usage 2022-01-20 20:21:12 -07:00
yrutschle
4e6145576e warn systemd that sslh will fork (fix #314) 2021-12-23 21:56:31 +01:00
yrutschle
58ed185316 missing header dependency 2021-12-19 19:30:21 +01:00
yrutschle
122649ce3c fix log level for unknown ALPN (fix #313) 2021-12-12 09:30:07 +01:00
yrutschle
31b096a0f4 Merge branch 'master' of https://github.com/yrutschle/sslh 2021-12-09 21:33:37 +01:00
yrutschle
adaf407b8a added sslh-ev to install docs 2021-12-09 21:29:48 +01:00
Yves Rutschle
4e72800f2e
Merge pull request #311 from Belisarith/master
Correct wrong version of pcre in Dockerfile
2021-11-25 11:02:40 +01:00
Belisarith
18eeaa579a additional fix dockerfile, otherwise docker is not runnable 2021-11-23 12:57:16 +00:00
Belisarith
80f2d758a4 Correct wrong version of pcre in Dockerfile 2021-11-23 12:40:17 +00:00
yrutschle
9798bdcaa0 Merge branch 'master' of https://github.com/yrutschle/sslh 2021-11-20 17:00:34 +01:00
yrutschle
93df6975ed set appropriate log level for SNI matching 2021-11-20 17:00:24 +01:00
Yves Rutschle
44a78bc515 document dependency on libev 2021-11-14 18:58:59 +00:00
yrutschle
711c11c820 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. 2021-11-12 09:05:18 +01:00
yrutschle
ae117097ea sslh-ev in ChangeLog 2021-11-12 09:03:30 +01:00
yrutschle
0a23ca133e finalise UDP support for sslh-ev 2021-11-11 21:16:37 +01:00
yrutschle
207d482189 refactor: move udp timeout management to udp module 2021-11-09 18:27:52 +01:00
yrutschle
d4d9dbb8e7 remove dependancy to watcher type in UDP timeout 2021-11-09 18:12:02 +01:00
yrutschle
8ddff5e388 sslh-ev working for TCP 2021-11-07 16:13:44 +01:00
yrutschle
a80e2ceb27 remove obsolete declaration 2021-11-07 13:13:41 +01:00
yrutschle
e28fa91b0f reap children properly 2021-11-07 13:13:05 +01:00
yrutschle
17313100b5 print cnx type when dumping it 2021-11-01 18:48:26 +01:00
yrutschle
4cd3ab8958 moved watcher add to watcher init 2021-10-28 15:41:09 +02:00
yrutschle
5ec1f7eb98 added missing header 2021-10-28 15:40:40 +02:00
Yves Rutschle
5b21375087
Merge pull request #310 from bket/fix_tests
Fix (some) failing tests
2021-10-28 15:07:51 +02:00
Björn Ketelaars
d57a155bf4 Fix (some) failing tests
Found a couple of failing tests on Alpine Linux and OpenBSD. For the
tests to even run `ip4-localhost` has to be changed to an IP-address
(127.0.0.1). `ip4-localhost` is typically not part of `/etc/hosts`.

Output failing tests:

```
not ok 5
udp: 0
prefix: tls:
listen [1]:
    host: localhost
    port: 9025
flushing deferred data to fd 9
selecting... max_fd=11 num_probing=0
activity on fd8
closing fd 8
closing fd 9
selecting... max_fd=11 num_probing=0
#   Failed test at ./t line 59.
#          got: '1'
#     expected: 'sslh-select: Connect and write nothing'
```

```
not ok 22 - sslh-select:ssh: probe connected correctly
#   Failed test 'sslh-select:ssh: probe connected correctly'
#   at ./t line 59.
#          got: 'regex'
#     expected: 'ssh'
```

```
not ok 68 - sslh-select:ssh: probe connected correctly
#   Failed test 'sslh-select:ssh: probe connected correctly'
#   at ./t line 59.
#          got: 'regex'
#     expected: 'ssh'
```

Diff works around issues causing tests 22 and 68 to fail. As of yet, no
workaround for test 5 has been found.
2021-10-27 19:52:06 +02:00
yrutschle
ec033efbbc refactor more code from sslh-select to processes.c 2021-10-26 21:45:44 +02:00
yrutschle
3389000ff3 move action processing to its own file 2021-10-17 16:02:49 +02:00
yrutschle
25abd765cb refactor: abstract watchers from loop data 2021-10-11 22:40:46 +02:00
yrutschle
2cdd60dd18 make systemd-sslh-generator work again (fix #308 again) 2021-10-04 21:34:22 +02:00
yrutschle
b0aeeff465 Include log header before defining macros that depend on log levels. (fix #308) 2021-10-04 09:11:41 +02:00
yrutschle
c9eff6e38d removed obsolete declarations 2021-10-03 17:25:31 +02:00
yrutschle
0cde3d794a check return values (fix #61) 2021-10-02 21:27:31 +02:00
yrutschle
ed48d3964f removed obsolete prototye 2021-10-02 21:23:39 +02:00
yrutschle
7b0d486d3d removed obsolete check to verbose 2021-10-02 21:23:17 +02:00
yrutschle
0e118a109c Overhaul of the logging system: logs all have classes, and
each class can be configured independently to write to
stderr or syslog.
2021-10-02 15:40:32 +02:00
yrutschle
9955cc6560 describe verbose options 2021-10-02 15:38:22 +02:00
yrutschle
caa62875c1 remove --verbose option 2021-09-27 13:28:21 +02:00
yrutschle
c8fce0a02f make sure no error will go to stderr if in inetd (fix #303) 2021-09-27 13:21:16 +02:00
yrutschle
4277d27063 migrate last messages to new log system 2021-09-27 13:16:30 +02:00
yrutschle
16bf1a6aca make echosrv independant from common macros 2021-09-27 13:01:20 +02:00
yrutschle
4f0f5017bc remove obsolete prototype 2021-09-27 12:55:57 +02:00
yrutschle
70b31a48d9 migrate generic system call failure checks to new log system 2021-09-27 12:53:41 +02:00
yrutschle
66caf8a31b remove log_message 2021-09-27 12:51:37 +02:00
yrutschle
4d3cc9c925 migrate some more common.c to new log system 2021-09-27 12:46:51 +02:00
yrutschle
2e11001087 migrate UDP to new log system 2021-09-27 12:43:03 +02:00
yrutschle
6ea7d48f86 migrate tls.c and probe.c to new log system 2021-09-26 16:55:31 +02:00
yrutschle
3fb1201b3f merged transparent proxy setups together, clarifying what is not known to work 2021-09-26 16:28:06 +02:00
yrutschle
e9e7ada069 convert to hash-based titles 2021-09-26 16:13:23 +02:00
yrutschle
e6cbbe9511 migrate common.c to new logging system 2021-09-26 15:53:21 +02:00
yrutschle
e5f16b93ce hexdump writes to parametrable msg_info 2021-09-19 21:54:47 +02:00
yrutschle
673c40954e migrate sslh-fork to new log system 2021-09-19 20:29:43 +02:00
yrutschle
f7b6f669a4 sslh-select to use new log system 2021-09-19 20:24:46 +02:00
yrutschle
dbad46a358 remove obsolete debug code 2021-09-19 15:19:37 +02:00
yrutschle
098a55fd1d new logging system: now with message classes 2021-09-19 15:14:38 +02:00
yrutschle
5e27806545 new logging system: now with message classes 2021-09-19 15:13:04 +02:00
yrutschle
317c08604b move logging code to its own file 2021-09-15 21:51:11 +02:00
yrutschle
3013658b20 test to drop connection before writing anything (fix #285) 2021-08-28 16:33:20 +02:00
yrutschle
a704c7f7f5 fix #302 2021-08-28 16:03:58 +02:00
yrutschle
1a3341c2a4 be more defensive when allocating and extending gap 2021-08-24 20:07:28 +02:00
yrutschle
4a6bbda60d remove obsolete usage string and added lost version option 2021-08-24 14:10:14 +02:00
yrutschle
fa848f2ae9 do not timeout TCP connections (fix #300) 2021-08-24 13:38:18 +02:00
Aaron Madlon-Kay
a3640775bb
Allow supplying additional CFLAGS
Package managers may want to supply default CFLAGS that should be used *in addition* to project-specific ones.
2021-08-18 08:49:33 +09:00
Yves Rutschle
6aa19d080a updated version numbers for release 2021-08-17 21:55:18 +02:00
yrutschle
a43385b8db remove hardcoded quick50 and teamspeak. to be replaced with a generalised regex setup to come in next version 2021-08-13 18:03:59 +02:00
yrutschle
1261a5f4c7 updated some references to obsolete ssl option 2021-08-06 22:43:38 +02:00
yrutschle
40da147efd keep track of next UDP timeout to only go through all connections when that happens 2021-08-06 22:29:00 +02:00
yrutschle
e4936454c5 refactor: take time only once when computing UPD timeouts 2021-08-05 17:05:08 +02:00
yrutschle
63f9c4a582 added syslog probe (fixes #34) 2021-08-04 15:29:05 +02:00
yrutschle
0e45107797 advertise new UDP protocols 2021-08-01 22:02:42 +02:00
yrutschle
4584e719e1 added hardcoded probe for Teamspeak3 2021-08-01 22:01:59 +02:00
yrutschle
75e426c80f added hardcoded probe for QUICK50 2021-08-01 21:35:24 +02:00
yrutschle
e40d43bea5 removed obsolete ssl test pattern 2021-08-01 21:27:04 +02:00
yrutschle
2196146224 Merge branch 'master' of https://github.com/yrutschle/sslh 2021-08-01 21:25:39 +02:00
Yves Rutschle
2f822741b3
Merge pull request #297 from jeremiejig/master
update doc CAP_NET_RAW, remove CAP_SETUID/SETGID
2021-08-01 21:25:05 +02:00
Jeremiejig
760def3444 update doc CAP_NET_RAW, remove CAP_SETUID/SETGID
Update documentation CAP_NET_ADMIN -> CAP_NET_RAW.

Remove useless capabilities CAP_SETUID CAP_SETGID in service unit files.
There are only useful if not using either User=sslh nor DynamicUser=true
2021-08-01 21:08:52 +04:00
yrutschle
4b885b4a2c remove obsolete variables 2021-07-31 23:34:43 +02:00
yrutschle
ce23f202b7 use pcre2 api directly 2021-07-31 23:12:55 +02:00
Yves Rutschle
82b8ba547e
Merge pull request #205 from inztar/master
add Dockerfile
2021-07-31 16:28:42 +02:00
Yves Rutschle
8e38d56167
Merge pull request #294 from wltu/fix-unused-result
Fixed unused result issue with write()
2021-07-31 16:20:06 +02:00
Yves Rutschle
ea3b6794e1
Merge pull request #296 from lnslbrty/improved/example-config-udp
Added some example regex for UDP forward.
2021-07-31 16:16:15 +02:00
Toni Uhlig
e42f165ef5
Added some example regex for UDP forward.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2021-07-30 23:00:24 +02:00
yrutschle
37d2756703 move to PCRE for conf file 2021-07-23 22:36:20 +02:00
Willy Tu
ec25ab56e8 Fixed unused result issue with write()
Simply print an error message if the write result is < 0.
2021-07-23 13:21:14 -07:00
yrutschle
7fb65ad0ac refactor: remove sideeffects from udp_timedout 2021-07-18 21:50:40 +02:00
yrutschle
c43882c85f refactor: move timeout management to separate function 2021-07-18 21:36:46 +02:00
yrutschle
4a4c571116 document UDP support 2021-07-16 22:38:53 +02:00
yrutschle
c049885758 document UDP support 2021-07-16 22:37:41 +02:00
yrutschle
de0ec959d9 make udp timeout configurable per protocol 2021-07-08 14:13:04 +02:00
yrutschle
e7df8eeaa1 removed static known_source arrays, UDP connections are now managed dynamically 2021-07-07 19:58:08 +02:00
yrutschle
8af87ebbad report errors as E instead of M, and consolidate restarts as simplified roman numerals 2021-07-07 19:56:17 +02:00
yrutschle
bf57d63c3a fix message for fd closure on timeout 2021-07-02 08:22:09 +02:00
yrutschle
862e33cfec moved UDP support from sslh-fork to sslh-select 2021-07-01 22:44:35 +02:00
yrutschle
24e7f46a43 fix for libconfig 1.7.3 (#292) 2021-06-23 11:48:59 +02:00
yrutschle
bf2053eb79 fail gracefully if libconfig fails to find root element 2021-06-22 21:34:08 +02:00
yrutschle
1ad450a444 sslh-select sets O_NONBLOCK *before* calling connect, which prevents hanging on an unresposive server (fix #258) 2021-05-28 13:38:45 +02:00
yrutschle
300e1916c3 add recv and sendto addresses 2021-05-16 15:08:33 +02:00
yrutschle
e678428334 clarify function name 2021-05-15 17:57:33 +02:00
yrutschle
edc42ca13b clarify variable name 2021-05-15 17:39:34 +02:00
yrutschle
c41ec489e0 changed CAP_NET_ADMIN to CAP_NET_RAW as it's enough 2021-05-11 06:44:27 +02:00
yrutschle
79f49f4481 refactor t_load to use test.cfg as base 2021-05-09 16:40:38 +02:00
yrutschle
8e130882fc sslh-fork drops all capabilities after connecting to server side 2021-05-09 15:46:25 +02:00
yrutschle
030ef64b99 refactor: purified set_capabilities, told to keep or drop CAP_NET_ADMIN instead of deciding for itself 2021-05-09 15:39:14 +02:00
yrutschle
cf4f4cbebe typo 2021-05-08 22:33:45 +02:00
yrutschle
905ac95ca1 do not drop CAP_NET_ADMIN if any of the protocols require transparent proxying 2021-05-08 07:44:39 +02:00
yrutschle
da3b51e056 upgraded argtable3 to current last version to fix a parsing issue with command line options 2021-05-08 07:37:20 +02:00
yrutschle
308b54aa8a remove caveat about UDP refactor 2021-05-08 07:06:14 +02:00
yrutschle
987643878f when verbose, precise which listen sockets are udp 2021-05-01 16:15:34 +02:00
yrutschle
46e741e9c2 add UDP support to echosrv 2021-04-25 09:06:31 +02:00
yrutschle
c3d019284d made echosrv independant from common.o and with its own configuration 2021-04-24 10:31:41 +02:00
yrutschle
4c570e8d57 remove some debug messages 2021-04-21 08:48:31 +02:00
yrutschle
0a00220b42 justify use of select 2021-04-21 08:43:21 +02:00
yrutschle
a0f9fc164e changelog the sslh-select refactoring 2021-04-21 08:42:27 +02:00
yrutschle
517e4ad5b4 Merge branch 'master' of https://github.com/yrutschle/sslh 2021-04-19 09:39:05 +02:00
yrutschle
b258b0e0f7 manage probing sockets in a specific list instead of searching through all connections 2021-04-19 09:38:22 +02:00
yrutschle
02f6c6999d fix client/server protocol to indicate process death, and make process mortal again 2021-04-18 11:18:26 +02:00
yrutschle
41a914b350 alloc_cnx returns cnx pointer instead of simple error code 2021-04-17 21:29:38 +02:00
yrutschle
a487ad4cbe fix ssh test and add random delays to exercise probing code 2021-04-17 21:28:55 +02:00
yrutschle
21ed247acb removed obsolete function 2021-04-17 21:00:52 +02:00
yrutschle
dfa5d3cedb simplified some redundant parameters 2021-04-17 17:53:45 +02:00
yrutschle
74ce2acdc5 make t_load pretty print 2021-04-16 22:04:45 +02:00
yrutschle
0e3242c115 fix to work with test.cfg 2021-04-14 23:27:44 +02:00
yrutschle
5715a9f0bd stop managing connections as array; instead, allocate connections, and fd2cnx is the only pointer to it 2021-04-14 23:26:33 +02:00
yrutschle
de474e1d07 stop managing connections as array; instead, allocate connections, and fd2cnx is the only pointer to it 2021-04-14 23:26:01 +02:00
yrutschle
cb199ed3c6 remove redundant dependency 2021-04-11 09:38:36 +02:00
yrutschle
7485afe397 refactor accept to its own function 2021-04-10 08:26:22 +02:00
yrutschle
417722e3c1 remove obsolete definitions 2021-04-03 21:40:53 +02:00
yrutschle
9fca586735 remove over-verboseness 2021-04-03 21:39:43 +02:00
yrutschle
0613d412a2 refactor: write activity checked on select output rather than on each connection object 2021-04-03 21:34:47 +02:00
yrutschle
b2ee07655a typo 2021-04-03 21:27:44 +02:00
yrutschle
465ecdda5b fix busy loop bug 2021-04-03 21:27:35 +02:00
yrutschle
3edf6a9d22 refactor: move write process to its own function 2021-04-02 22:20:04 +02:00
yrutschle
7a88a50c32 refactor: remove redundant paramters 2021-04-02 16:02:23 +02:00
yrutschle
e7a5bbc4e1 removed some over-verboseness 2021-03-30 20:16:38 +02:00
yrutschle
40557c58ad working version with read activity checked on select output rather than on each connection object 2021-03-29 21:56:21 +02:00
yrutschle
f0e1aaf82c refurbished valgrind launcher 2021-03-29 21:54:54 +02:00
yrutschle
f51781664c add server-side file descriptor to collection 2021-03-28 12:22:36 +02:00
yrutschle
5948270b95 update API 2021-03-28 11:47:33 +02:00
yrutschle
758be2a8ab clarify API: allocate cnx in collection from a new fd, or add a fd to an existing connection in a collection 2021-03-28 11:40:40 +02:00
yrutschle
654ff0b5b0 rebuild when headers change 2021-03-28 11:39:49 +02:00
yrutschle
15a5073559 keep track of which connection contains what file descriptor 2021-03-23 22:58:10 +01:00
yrutschle
c2b6bf246c refactor: make collection part of select object 2021-03-23 21:43:07 +01:00
yrutschle
6366d50b89 refactor: use fd_info instead of passing fd_sets directly 2021-03-23 13:59:15 +01:00
yrutschle
5b93e4ab55 abstracted collection management to its own file 2021-03-21 16:42:37 +01:00
yrutschle
1c63b06cc8 refactor: move collection handling towards a foreach construct 2021-03-21 14:01:12 +01:00
yrutschle
71c617050d fix collection extension 2021-03-17 21:16:08 +01:00
yrutschle
0409775a1a fix t_load so it works again! 2021-03-17 18:48:27 +01:00
Yves Rutschle
b72baa0622
Merge pull request #280 from ffontaine/master
Makefile: fix static linking with pcre
2021-03-16 20:32:46 +01:00
yrutschle
790d639ad0 echosrv to prefix only once 2021-03-07 17:43:42 +01:00
yrutschle
e884fc616d add conditional inclusion for header 2021-03-03 18:20:45 +01:00
yrutschle
7e63dedca3 refactor: abstract connection list management to a new 'collection' type 2021-03-03 14:38:24 +01:00
yrutschle
49d4080afd refactor pre-increment to more idiomatic post-increment (fix #282) 2021-03-03 14:29:25 +01:00
yrutschle
be4e126f3a refactor: move all read process code to its own function 2021-02-28 18:19:18 +01:00
yrutschle
34bcc2bbc8 refactor: move probing actions to its own function 2021-02-28 10:46:17 +01:00
yrutschle
90975fd6c3 refactor: move all file descriptor info for select to its own struct 2021-02-28 10:12:13 +01:00
yrutschle
f91f16d753 make local functions static 2021-02-27 15:33:41 +01:00
yrutschle
dae8101a50 remove redundant macro definition 2021-02-27 15:20:15 +01:00
yrutschle
68fc10aed0 comment udp_listener prototype 2021-02-23 21:19:23 +01:00
yrutschle
49c136691c refactor: move UDP code to its own file 2021-02-23 21:17:39 +01:00
yrutschle
adb27aa4a3 add a per-protocol transparent proxy option 2021-02-21 21:03:01 +01:00
yrutschle
9ff9723278 precised size types to ssize_t instead of int 2021-02-08 22:50:53 +01:00
yrutschle
52a9356c35 fix typo 2021-01-09 15:34:09 +01:00
yrutschle
ac3b43d4bd document choice for UDP buffer management 2021-01-09 15:24:31 +01:00
yrutschle
368f286ce5 added FAQ for virtual hosting 2020-12-20 15:13:13 +01:00
yrutschle
05a835ff1f begin of release not for UDP 2020-12-06 15:50:08 +01:00
yrutschle
0a3d5874b4 free udp connections after some timeout 2020-12-05 22:59:38 +01:00
yrutschle
a42d4f8049 forward server responses back to sender 2020-12-04 16:31:52 +01:00
yrutschle
1e33455fe7 don't try to probe a connection that's finished (should fix #284) 2020-12-02 22:49:19 +01:00
yrutschle
a3d9df87cb fix typo and explicit a test 2020-12-02 22:48:17 +01:00
yrutschle
247d60d8e6 dump deferred data when dying from unexpected fd activity 2020-12-02 22:05:09 +01:00
yrutschle
7d820049a2 preliminary udp support: forwards incoming packets, no return yet 2020-11-29 15:51:04 +01:00
yrutschle
09597bfa42 refactor: make probe_buffer hexdump input 2020-11-29 10:42:58 +01:00
yrutschle
41e3b0cd1d warn about refactor 2020-11-29 10:19:50 +01:00
yrutschle
76d0351452 change listener_pid type to pid_t 2020-11-14 18:31:43 +01:00
yrutschle
fccaa5fa9f refactor: separate probe reading from socket from probe on buffer, so we can call probe on buffer independantly 2020-11-11 22:05:45 +01:00
yrutschle
f077101835 refactor: move tcp listener to its own function 2020-11-08 19:09:18 +01:00
yrutschle
f3230b4a94 abstract listening sockets so we have protocol information alongside the socket -- echosrv and sslh-select 2020-11-07 22:44:37 +01:00
yrutschle
c12f7a1ade abstract listening sockets so we have protocol information alongside the socket 2020-11-07 22:31:49 +01:00
yrutschle
ebeabb6c18 add is_udp option and listen to UDP ports 2020-11-07 21:29:09 +01:00
yrutschle
8a1dae8c22 refactor: move local address resolution into start_listen_socket 2020-11-07 18:55:04 +01:00
yrutschle
07719f55b8 refactor echosrv to use conf2struct 2020-11-07 09:29:26 +01:00
yrutschle
6c94bf71b8 refactor: move code to listen to a single address out of start_listen_sockets 2020-11-01 21:21:26 +01:00
Fabrice Fontaine
0d9e004d32 Makefile: fix static linking with pcre
Static build with pcre is broken since version 1.19b and
cb90cc97ae
because -lpcre has been replaced by -lpcreposix which will result in
the following static build failure:

/srv/storage/autobuild/run/instance-1/output-1/host/bin/mipsel-linux-gcc -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -Os   -static -static -o echosrv echosrv.o probe.o common.o tls.o  -lpcreposix -lconfig -lcap
/srv/storage/autobuild/run/instance-1/output-1/host/opt/ext-toolchain/bin/../lib/gcc/mipsel-buildroot-linux-uclibc/8.3.0/../../../../mipsel-buildroot-linux-uclibc/bin/ld: /srv/storage/autobuild/run/instance-1/output-1/host/mipsel-buildroot-linux-uclibc/sysroot/usr/lib/libpcreposix.a(libpcreposix_la-pcreposix.o): in function `regfree':
pcreposix.c:(.text+0x120): undefined reference to `pcre_free'

So append -lpcre after -lpcreposix

Fixes:
 - http://autobuild.buildroot.org/results/a601824fc0c205a6a940e0f9f079ce2c39840605

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2020-10-15 08:05:02 +02:00
yrutschle
0c388cb367 update mailing list address 2020-09-23 21:34:14 +02:00
yrutschle
9f99f296b1 warn about unknown settings in the configuration file 2020-08-29 18:22:42 +02:00
yrutschle
8b6e06e6c7 support for libconfig 1.4.9. Fix #275 2020-08-11 17:37:23 +02:00
yrutschle
5cdff8c558 document how to provide useful information in case of segfault 2020-08-04 17:27:09 +02:00
yrutschle
72438dc1ae dont try to use conffile count if libconfig is not present, fix #274 2020-08-04 16:37:49 +02:00
yrutschle
e67d6ff905 removed obsolete support for . Use instead 2020-07-30 09:45:50 +02:00
yrutschle
0af37a1bed initialise runtime data, which avoids using bad pointers in tls.c as in issue 273 2020-07-29 22:52:48 +02:00
yrutschle
08ce89b6e2 fix tinc configuration target 2020-07-29 22:31:02 +02:00
yrutschle
99d4a6d4a2 explicit obsoletion of ssl param 2020-07-29 22:23:36 +02:00
yrutschle
2c93a015ea make libconfig optionnal again 2020-07-24 16:51:06 +02:00
yrutschle
3315a727dc update required conf2struct version 2020-07-19 21:05:36 +02:00
yrutschle
8638199f13 test for command line parameters 2020-07-19 18:32:57 +02:00
yrutschle
d7cf82424b add --anyprot command line option (fix #272) 2020-07-18 17:27:32 +02:00
yrutschle
ac8563525e fix Changelog for -v 2020-07-18 17:25:39 +02:00
yrutschle
2f983625d0 documentation update 2020-07-18 16:54:23 +02:00
yrutschle
f6b11a424b reset config file option to --config 2020-07-17 21:58:23 +02:00
yrutschle
cf46cd5bb4
Merge pull request #271 from bket/config_init
Always initialize config_t structure, fixes #270
2020-07-15 21:15:49 +02:00
Björn Ketelaars
b473622698 Always initialize config_t structure, fixes #270
Without this change the config_t structure is ONLY initialized when it
meets a specific criterion. If this criterion is not met the config_t
structure is still used, which can cause a segmentation fault.

Fixes #270.
2020-07-14 06:57:43 +02:00
Yves Rutschle
de8e5725c2 automatically sign release 2020-07-11 21:39:37 +02:00
Yves Rutschle
0cc516bf51 v1.21 2020-07-11 21:30:26 +02:00
yrutschle
337bd34fb7 fix robustness tests 2020-07-10 23:27:07 +02:00
yrutschle
46d9796bd6 fail as soon as a listen address cannot be resolved 2020-07-10 23:12:03 +02:00
yrutschle
219163ac27 dont keep going if config parse failed 2020-07-10 22:49:41 +02:00
yrutschle
8ddbe59883 warn that transparent proxy is hard 2020-07-06 22:25:42 +02:00
yrutschle
c5b0932bad syslog_facility 'none' disables syslog 2020-06-19 23:39:25 +02:00
yrutschle
9e2cc93973 syslog connections when specifying probes on the command line 2020-06-19 23:38:33 +02:00
yrutschle
408c5741f8 rewind logged messages so it does not crash... 2020-06-19 23:01:25 +02:00
Yves Rutschle
c595aadb4d log to syslog even if in foreground 2020-05-28 10:13:54 +00:00
yrutschle
61191edf7c initialised FAQ 2020-04-10 21:45:04 +02:00
yrutschle
1009f556fc initialised FAQ 2020-04-10 21:43:46 +02:00
yrutschle
98807771e0 clarify error message for setsockopt IP_TRANSPARENT 2020-01-26 21:35:09 +01:00
yrutschle
4610e220bd update c2s version requirement 2020-01-26 21:20:48 +01:00
yrutschle
dfa764e2e8 config from new c2s: underscore and dashes are equivalent 2020-01-26 21:19:16 +01:00
yrutschle
10fe9c6e27 Merge branch 'master' of https://github.com/yrutschle/sslh 2020-01-26 21:13:04 +01:00
yrutschle
d1f782e1fe
Merge pull request #255 from jamesmacwhite/cfg-example-tls
Update SSL example to TLS
2020-01-02 21:12:18 +01:00
James White
9ed44ee09b
Update SSL example to TLS
Using will throw a deprecation warning message
2020-01-02 09:24:32 +00:00
yrutschle
e913eeeb88 explicit what sslhconf.cfg is for 2019-12-30 21:06:09 +01:00
yrutschle
9d1d9f850a added config parser generated by conf2struct, so sslh builds without conf2struct after cloning 2019-12-30 20:44:16 +01:00
yrutschle
588883eb42 Fix on-timeout setting so it is no longer ignored (issue #253) 2019-12-27 18:26:16 +01:00
yrutschle
bae16ab29e Merge branch 'master' of https://github.com/yrutschle/sslh 2019-11-15 22:20:48 +01:00
yrutschle
32851d2041 minor doc clarification for dependencies 2019-11-15 22:20:31 +01:00
yrutschle
49e8bff01b
Merge pull request #248 from muellerj/master
Fix warnings
2019-11-14 23:16:24 +01:00
Jonas Mueller
87aaa156e0 Add explicit casts to remove warnings
for incompatible-pointer-types-discards-qualifiers
2019-11-05 20:28:58 +01:00
Jonas Mueller
ef8233a839 Fix warnings for format-string-security 2019-11-05 20:11:44 +01:00
Jonas Mueller
125458df51 Fix warnings for return-type 2019-11-05 20:06:23 +01:00
yrutschle
d2cfa3c603
Merge pull request #242 from ideal/master
Fix remaining available size of buffer
2019-09-08 20:56:25 +02:00
yrutschle
891bcf9966 fix error message upon regex syntax error 2019-09-06 21:05:46 +02:00
yrutschle
544c2b6d2f
Merge pull request #240 from niobos/feature/descriptive-ps-name
Change process name to indicate task of process
2019-09-05 21:49:32 +02:00
Niobos
0380a4309f Change process name to indicate task of process 2019-09-05 16:40:59 +02:00
yrutschle
3aa245efa5 separate connection information creationg from log emission 2019-09-05 15:48:33 +02:00
ideal
bc28d6ce19 the remaining size of buffer should minus prefix_len 2019-09-04 23:02:13 +08:00
yrutschle
d78c810d84 specify conf2struct version 2019-08-30 22:25:38 +02:00
yrutschle
ed3cd40e23 adapt config format to conf2struct v1.0 2019-08-29 22:21:00 +02:00
yrutschle
12edc3dbca merged master 2019-08-16 10:06:31 +02:00
yrutschle
0931dfdf0b specify c2s commit to be used for building 2019-08-16 08:13:34 +02:00
yrutschle
0d5a2f6922
Merge pull request #236 from Jason-Cooke/patch-1
docs: fix typo
2019-07-09 21:50:49 +02:00
Jason Cooke
49e59734cb
docs: fix typo 2019-07-09 09:12:39 +12:00
Yves Rutschle
914dc98fb3 added transparent proxy figure 2019-07-08 20:18:57 +00:00
yrutschle
1dc0088b74
Merge pull request #235 from FlexMcMurphy/patch-3
Delete tproxy.svg
2019-07-08 22:12:31 +02:00
FlexMcMurphy
9416617456
Delete tproxy.svg
Delete this svg file and replace with fixed svg diagram with text boxes displaying correctly.
2019-07-05 23:52:24 +01:00
yrutschle
7e047e35b3 updated architecture figure to SVG 2019-07-03 22:45:04 +02:00
yrutschle
8ffcf5ab03
Merge pull request #233 from FlexMcMurphy/patch-1
Delete tproxy.jpg
2019-07-03 22:26:59 +02:00
yrutschle
a917149e14
Merge pull request #234 from FlexMcMurphy/patch-2
Update tproxy.md
2019-07-03 22:26:43 +02:00
FlexMcMurphy
08a64e99b4
Update tproxy.md
Minor formatting changes. Could also update tproxy.jpg to the svg version.
2019-06-30 12:28:53 +01:00
FlexMcMurphy
0ef57f1500
Delete tproxy.jpg 2019-06-30 11:47:42 +01:00
yrutschle
fde86a778d remove contents from README that was added in doc/ 2019-06-29 21:51:02 +02:00
yrutschle
5bd0235d38 split documentation into separate files in doc/ 2019-06-29 21:50:05 +02:00
yrutschle
177d337ac9 added new tproxy doc 2019-06-29 19:23:56 +02:00
yrutschle
8e203c897e added SOCKS5 to README 2019-06-24 21:39:18 +02:00
yrutschle
6e9f24153f
Merge pull request #232 from jmccrohan/master
Update Let's Encrypt entry in example.cfg
2019-06-17 20:57:06 +02:00
Jonathan McCrohan
f029b4098b Update Let's Encrypt entry in example.cfg
Update Let's Encrypt entry in example.cfg for tls-alpn-01 challenge.
Previous entry was based on tls-sni-01 challenge which is deprecated and
was disabled in March 2019.

Signed-off-by: Jonathan McCrohan <jmccrohan@gmail.com>
2019-06-16 23:26:33 +01:00
yrutschle
c1369910c9
Merge pull request #229 from rugbylug/evening_of_docs
Fix a typo
2019-05-20 21:28:53 +02:00
Ondřej Kuzník
d756f7d504 Fix a typo 2019-05-16 20:52:04 +01:00
yrutschle
6d99f780ef Merge branch 'master' of https://github.com/yrutschle/sslh 2019-05-13 15:21:36 +02:00
yrutschle
d5baed3f18 Fix fd2fd return value which should not be -1 on error 2019-05-13 15:21:22 +02:00
yrutschle
5684959dd6
Merge pull request #225 from ffontaine/master
fix version extraction when building in a larger git tree
2019-04-06 22:12:30 +02:00
Taras Inzyk
95a6577cda add Dockerfile 2019-04-06 17:26:08 +05:00
Fabrice Fontaine
b5d8b2d199 fix version extraction when building in a larger git tree
sslh uses host git to extract its own version number. In buildroot, this
is an issue since extracted information is conflicting with buildroot git
status if we use git as VCS for buildroot.

Since these git calls are legitimate only if git is used for the sslh
subtree only, this patch adds a check : a .git directory has to exist at
the root of the project to enable git-extracted version string.

Signed-off-by: David Bachelart <david.bachelart@bbright.com>
[yann.morin.1998@free.fr: fix troll character U+c2a0]
Signed-off-by: "Yann E. MORIN" <yann.morin.1998@free.fr>
[Retrieved from:
https://git.buildroot.net/buildroot/tree/package/sslh/0001-secure-version-while-building-sslh-in-a-larger-git-t.patch]
Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2019-04-05 23:05:25 +02:00
yrutschle
e528f519bc relax address matching rule so it matches more than just 'localhost' 2019-03-11 22:21:56 +01:00
yrutschle
b529069029 print a synthetic test report 2019-03-11 21:31:24 +01:00
yrutschle
67eb471c6f Merge branch 'tfo' 2019-03-10 10:12:33 +01:00
yrutschle
2705face30 TCP_FASTOPEN changelog 2019-03-10 10:11:28 +01:00
yrutschle
4e725e1520 added TFO for listening socket 2019-03-10 10:11:06 +01:00
yrutschle
b0c3c8fdbc manage TFO already done in connect call 2019-03-10 09:53:52 +01:00
yrutschle
15f733e572 add tfo_ok configuration setting 2019-03-10 09:46:06 +01:00
yrutschle
ff91f94315 Merge client TFO setting 2019-03-10 09:39:48 +01:00
Craig Andrews
0a880ea607
Use TCP Fast Open for client sockets
Set the TCP_FASTOPEN_CONNECT option on client sockets to signal desire to use TCP Fast Open.

See https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=19f6d3f3c8422d65b5e3d2162e30ef07c6e21ea2
2019-03-09 21:18:36 -05:00
yrutschle
e0312b4a9d make ssh fork when on command line as used to be the case before 2019-03-09 12:36:57 +01:00
yrutschle
5a213c9650 c2s fix: initialise root config structure even if no config file is specified 2019-03-09 11:36:24 +01:00
yrutschle
7baf7f724c Move config dump to stderr and verbose > 4 2019-02-08 22:45:16 +01:00
yrutschle
848b107f2b document conf2struct dependency 2019-02-03 21:13:37 +01:00
yrutschle
a7b4462c6e Added 'minlength' option 2019-02-03 14:45:14 +01:00
yrutschle
5b309a9b97 update c2f output 2019-01-27 18:53:36 +01:00
yrutschle
d2b64c7f38
Merge pull request #211 from licaon-kter/patch-1
Update verbose option in examples
2019-01-19 08:22:10 +01:00
Licaon_Kter
d647b4eb55
And here 2019-01-19 00:27:11 +00:00
Licaon_Kter
a584348a55
Update verbose option in examples 2019-01-19 00:26:06 +00:00
yrutschle
530acc7c72 Moved command line parsing to conf2struct 2019-01-12 21:33:44 +01:00
yrutschle
dbc0667ad3 removed obsolete declarations 2018-12-08 22:55:03 +01:00
yrutschle
e8654da78c Moved configuration file parsing to conf2struct, which
abstract all the tedious bits of reading the settings from
the file into a structure.
2018-12-07 08:41:40 +01:00
yrutschle
343b0a0fbf reactivate tests 2018-12-07 08:40:30 +01:00
yrutschle
ad0adfb0e1 re-integrate command line support 2018-12-07 08:32:36 +01:00
yrutschle
33ab9d535d code cleanup and adaptation of regex probe 2018-12-04 23:11:04 +01:00
yrutschle
d3d4fd657a moved config parse to c2s code 2018-12-03 11:02:20 +01:00
yrutschle
e2fddf17fc updated description to better match our maturity 2018-11-29 18:31:55 +01:00
yrutschle
e7ce929020 config file now read to struct with c2s; command line no longer works 2018-11-29 11:56:33 +01:00
yrutschle
7af31c45c9 fix tests so that fragmented tests actually work 2018-11-29 11:52:25 +01:00
Yves Rutschle
4ae2e62d25 v1.20 2018-11-20 22:58:41 +01:00
yrutschle
8ec9799ca0 fix ssl tests 2018-11-04 22:59:01 +01:00
yrutschle
80ad31aec0 refactoring: replace magic constants with symbols 2018-11-04 22:25:16 +01:00
yrutschle
d6c714166a only try to parse TLS extensions if settings are actually set 2018-09-30 21:35:45 +02:00
yrutschle
aa77922ffd turn ssl setting from command line to tls (ssl no longer exists) 2018-09-30 21:34:22 +02:00
yrutschle
2ee0088c5f turn ssl setting from configuration file to tls (ssl no longer exists) 2018-09-30 20:20:06 +02:00
yrutschle
f480eb6c7d refactoring: simpler TLS extension parsing algorithm 2018-09-28 13:58:33 +02:00
yrutschle
6431bb7e35 refactoring: changed magic numbers for use_alpn to named bitfields 2018-09-23 22:29:25 +02:00
yrutschle
6d6ea50066 Merge branch 'master' of https://github.com/yrutschle/sslh 2018-09-23 21:57:16 +02:00
yrutschle
71265a8477
Merge pull request #201 from astiob/probe-strlen
Fix incorrect strncmp length in HTTP method probing
2018-09-23 21:56:51 +02:00
yrutschle
0003680137 remove old tls and ssl targets, only use alpn/sni probe also for TLS with no extensions 2018-09-23 12:07:06 +02:00
Oleg Oshmyan
e8f0d3ea53 Fix HTTP method probing 2018-09-22 15:50:40 +03:00
yrutschle
a5d00568b5
Merge pull request #199 from rom1dep/sni_alpn_errmatching
tls: proposed fix for incorrect SNI/ALPN matching
2018-08-30 23:16:28 +02:00
yrutschle
ffe9971624 test suite for SNI/ALPN with multiple targets and all combinations covered 2018-08-30 19:50:53 +02:00
Yves Rutschle
1693436cc3 automatic test for SNI/ALPN (single target) 2018-08-29 19:18:23 +02:00
Romain DEP.
e42f670112 tls: proposed fix for incorrect SNI/ALPN matching 2018-08-21 22:36:01 +02:00
Yves Rutschle
60df92c2b2 prevent repeated reads on broken sockets 2018-08-14 23:05:49 +02:00
Yves Rutschle
8ad32816a6 last_p might be used uninitialised if last probe has no probe set (which I don't think might happen, but anyhow...) 2018-08-14 22:52:52 +02:00
Yves Rutschle
677e385fec new probing algorithm 2018-08-13 22:29:09 +02:00
Yves Rutschle
b6db83a701 also test probes when no fragmentation occurs 2018-08-12 21:45:42 +02:00
Yves Rutschle
3a17bd6832 removed obsolete tests 2018-08-12 21:35:42 +02:00
Yves Rutschle
94911c1c2a cleaner framework to test all probes methodically (not all tests work as the probe code needs to be reworked) 2018-08-11 23:13:24 +02:00
Yves Rutschle
d7889588da Merge branch 'master' of https://www.github.com/yrutschle/sslh 2018-08-04 22:34:20 +02:00
Yves Rutschle
ad2b595280 Test suite: some cleanup and use config file instead of command-line parameters 2018-08-04 22:33:02 +02:00
Yves Rutschle
9df1ab8404 fix man page on timeout default protocol 2018-07-31 19:54:46 +00:00
yrutschle
10fb0bce6f
Merge pull request #192 from candrews/patch-5
Include common.h in systemd-sslh-generator.c
2018-07-11 13:18:50 +02:00
Craig Andrews
eb53c45351
Include common.h in systemd-sslh-generator.c
Fixes https://github.com/yrutschle/sslh/issues/188
2018-07-06 12:30:28 -04:00
Yves Rutschle
a1cc399ae5 fix syslog facility name check 2018-07-03 12:22:03 +02:00
yrutschle
108a9780d8
Merge pull request #187 from msantos/segfault
Fix potential segfaults (unverified malloc() returns) and other robustness issues
2018-06-18 16:45:51 +02:00
Michael Santos
9228171eb0 config: exit if list element is invalid 2018-06-18 10:35:28 -04:00
Michael Santos
8ce2b2ea05 Check memory allocations succeed 2018-06-18 10:35:28 -04:00
Michael Santos
4c132e3c8d config: segfault parsing invalid sni/alpn
Check return value of config_setting_get_string_elem() for error
before passing the result to strlen():

~~~ segfault.conf
protocols:
(
 { name: "tls"; host: "localhost"; port: "8443";  sni_hostnames:  [ 0 ];
}
);
~~~
2018-06-18 10:35:28 -04:00
Michael Santos
cfd0163a5b main_loop: initialize in_socket
in_socket may be used uninitialized if no addresses are available.

~~~
sslh-select.c:415:8: warning: Function call argument is an uninitialized value
                            check_access_rights(in_socket, cnx[i].proto->service)) {
                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
2018-06-18 10:35:28 -04:00
Michael Santos
5cf591a254 Avoid segfault with malformed IPv6 address
A literal IPv6 address without a trailing bracket will result in a write
past the end of the address buffer:

~~~ segfault.conf
protocols:
(
 { name: "tls"; host: "["; port: "8443"; }
);
~~~

~~~
$ sslh-select -p 127.0.0.1:443 --foreground -F./segfault.conf
[: no closing bracket in IPv6 address?
Segmentation fault (core dumped)
~~~
2018-06-18 10:35:28 -04:00
Michael Santos
c179d9a57b start_listen_sockets: exit if no addresses
Do not allocate a 0 byte buffer if no addresses are available:

    common.c:122:14: warning: Call to 'malloc' has an allocation size of 0 bytes
       *sockfd = malloc(num_addr * sizeof(*sockfd[0]));
		 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2018-06-18 10:35:28 -04:00
yrutschle
336e8bb9d1
Merge pull request #186 from Revertron/patch-1
Fixed a typo
2018-06-14 16:36:13 +02:00
Roman
0ada00474b
Fixed a typo
Just a typo in usage text.
2018-06-14 14:31:50 +02:00
Yves Rutschle
95e8f5731c document magic constant in socks5 probe 2018-06-13 12:40:02 +00:00
Yves Rutschle
dfd9e14866 fix socks5 probe 2018-06-13 09:52:49 +02:00
yrutschle
552723cc5f
Merge pull request #180 from sanmai/patch-1
Update README.md
2018-06-01 10:31:34 +02:00
Alexey Kopytko
09aaf39e25
Update README.md
Removed redundant dollar signs since they're not used anywhere else in the README.
2018-06-01 15:13:50 +09:00
Yves Rutschle
7acf9627ee fix memory leak when using transparent proxying 2018-05-29 12:38:57 +02:00
Yves Rutschle
b8e63a4d9d fixed systemd config to correct path 2018-05-26 17:02:15 +00:00
yrutschle
f451cc8bed
Merge pull request #172 from WeirdCarrotMonster/master
Added support for socks5 protocol
2018-04-16 22:38:06 +02:00
Eugene Protozanov
a43dd11fc9 Added support for socks5 protocol 2018-04-16 15:27:31 +04:00
yrutschle
3a61c8b0b1
Merge pull request #167 from HighwindsHipsApp/master
Fixed parent/child usage after initial fork in sslh-fork.c
2018-02-11 18:42:21 +01:00
Josh Tway
0c928fedbb Fixed issue in sslh-fork.c where the parent was being used instead of the child after forking. This was breaking multiple unit tests (on CentOS 7 at least) 2018-02-08 17:22:22 -05:00
Yves Rutschle
1a6ba5edc0 fix IPv6 parse error introduced in 7bf3e12c30d0585743792982ed8bcfc44aecae34 2018-01-27 22:59:52 +01:00
117 changed files with 30226 additions and 2512 deletions

63
.github/workflows/container-build.yaml vendored Normal file
View File

@ -0,0 +1,63 @@
name: Create and publish Container image
on:
push:
branches:
- master
tags:
- 'v*'
pull_request:
branches:
- master
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
context: .
file: Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

8
.gitignore vendored
View File

@ -3,8 +3,14 @@
*.o
cscope.*
echosrv
libsslh.a
sslh-fork
sslh-select
sslh-ev
systemd-sslh-generator
sslh.8.gz
tags
version.h
/config.status
/config.log
/config.h
/Makefile

186
ChangeLog
View File

@ -1,3 +1,179 @@
v2.2.1:
Added a boolean setting "is_unix" for listen and
protocol entries. This will use the 'host' setting
as a path name to a socket file, and connections
(listening or connecting) will be performed on Unix
socket instead of Internet sockets.
Support HAProxy's proxyprotocol on the backend
server side.
Lots of documentation about a new, simpler way to
perform transparent proxying.
New "verbose" option that overrides all other
verbose settings.
v2.1.3:
Fix Landlock access to /etc/hosts.deny and
/etc/hosts.allow.
v2.1.2:
Fix inetd mode.
v2.1.1:
Various minor fixes.
v2.1.0:
Support for the Landlock LSM. After initial setup,
sslh gives up all local file access rights.
Reintroduced --ssl as an alias to --tls.
Introduce autoconf to adapt to landlock presence.
Close connexion without error message if remote
client forcefully closes connexion, for Windows.
v2.0.1:
Fix resolve_on_forward setting, which would crash
sslh reliably.
v2.0.0:
v2.0:
New sslh-ev: this is functionally equivalent to
sslh-select (mono-process, only forks for specified
protocols), but based on libev, which should make it
scalable to large numbers of connections.
New log system: instead of --verbose with arbitrary
levels, there are now several message classes. Each
message class can be set to go to stderr, syslog, or
both. Classes are documented in example.cfg.
UDP connections are now managed in a hash to avoid
linear searches. The downside is that the number of
UDP connections is a hard limit, configurable with
the 'udp_max_connections', which defaults to 1024.
Timeouts are managed with lists.
inetd merges stderr output to what is sent to the
client, which is a security issue as it might give
information to an attacker. When inetd is activated,
stderr is forcibly closed.
New protocol-level option `resolve_on_forward`,
requests that target names are resolved at each
connection instead of at startup. Useful for dynamic
DNS situations. (Paul Schroeder/milkpirate)
New probe for MSRDP (akappner).
v1.22: 17AUG2021
sslh-select now supports UDP protocols.
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 explicitly 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
listening socket, if all client protocols support it.
(Craig Andrews)
Added 'minlength' option to skip a probe if less
than that many bytes have been received (mostly for
regex)
Update Let's Encrypt entry in example.cfg for tls-alpn-01
challenges; tls-sni-* challenges are now deprecated.
Log to syslog even if in foreground (for people who
use fail2ban)
Use syslog_facility: "none" to disable syslog
output.
Changed exit code for illegal command line parameter
from 1 to 6 (for testing purposes)
v1.20: 20NOV2018
Added support for socks5 protocol (Eugene Protozanov)
New probing method:
Before, probes were tried in order, repeating on the
same probe as long it returned PROBE_AGAIN before
moving to the next one. This means a probe which
requires a lot of data (i.e. return PROBE_AGAIN for
a long time) could prevent successful matches from
subsequent probes. The configuration file needed to
take that into account.
Now, all probes are tried each time new data is
found. If any probe matches, use it. If at least one
probe requires more data, wait for more. If all
probes failed, connect to the last one. So the only
thing to know when writing the configuration file is
that 'anyprot' needs to be last.
Test suite heavily refactored; `t` uses `test.cfg`
to decide which probes to test and all setup is
automatic; probes get tested with 'fast' (entire
first message in one packet) and 'slow' (one byte at
a time); when SNI/ALPN are defined, all combinations
are tested.
Old 'tls' probe removed, 'sni_alpn' probe renamed as 'tls'.
You'll need to change 'sni_alpn' to 'tls' in
your configuration file, if ever you used it.
v1.19: 20JAN2018
Added 'syslog_facility' configuration option to
specify where to log.
@ -37,7 +213,7 @@ v1.18: 29MAR2016
v1.17: 09MAR2015
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
Transparant proxy support for FreeBSD.
Transparent proxy support for FreeBSD.
(Ruben van Staveren)
Using -F with no argument will try
@ -66,7 +242,7 @@ v1.16: 11FEB2014
Libcap support: Keep only CAP_NET_ADMIN if started
as root with transparent proxying and dropping
priviledges (enable USELIBCAP in Makefile). This
privileges (enable USELIBCAP in Makefile). This
avoids having to mess with filesystem capabilities.
(Sebastian Schmidt/yath)
@ -75,7 +251,7 @@ v1.16: 11FEB2014
actual errors if connections are dropped before
getting to getpeername).
Set IP_FREEDBIND if available to bind to addresses
Set IP_FREEBIND if available to bind to addresses
that don't yet exist.
v1.15: 27JUL2013
@ -160,7 +336,7 @@ v1.11: 21APR2012
--user isn't specified, just run as current user.
No longer create PID file by default, it should be
explicitely set with --pidfile.
explicitly set with --pidfile.
No longer log to syslog if in foreground. Logs are
instead output to stderr.
@ -251,7 +427,7 @@ v1.8: 15JUL2011
v1.7: 01FEB2010
Added CentOS init.d script (Andre Krajnik).
Fixed default ssl address inconsistancy, now
Fixed default ssl address inconsistency, now
defaults to "localhost:443" and fixed documentation
accordingly (pointed by Markus Schalke).

38
Dockerfile Normal file
View File

@ -0,0 +1,38 @@
ARG ALPINE_VERSION="latest"
ARG TARGET_ARCH="library"
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
WORKDIR /sslh
RUN apk add --no-cache \
'gcc' \
'libconfig-dev' \
'make' \
'musl-dev' \
'pcre2-dev' \
'perl' \
;
COPY . /sslh
RUN ./configure && make sslh-select && strip sslh-select
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
RUN apk add --no-cache \
'libconfig' \
'pcre2' \
'iptables' \
'ip6tables' \
'libcap' \
&& \
adduser -s '/bin/sh' -S -D sslh && \
setcap cap_net_bind_service,cap_net_raw+ep /usr/local/bin/sslh
COPY "./container-entrypoint.sh" "/init"
ENTRYPOINT [ "/init" ]
# required for updating iptables
USER root:root

128
Makefile
View File

@ -1,128 +0,0 @@
# Configuration
VERSION=$(shell ./genver.sh -r)
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
COV_TEST= # Perform test coverage?
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
MANDIR?=$(PREFIX)/share/man/man8
MAN=sslh.8.gz # man page name
# End of configuration -- the rest should take care of
# itself
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC ?= gcc
CFLAGS ?=-Wall -g $(CFLAGS_COV)
LIBS=
OBJS=common.o sslh-main.o probe.o tls.o
CONDITIONAL_TARGETS=
ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap
CPPFLAGS+=-DLIBWRAP
endif
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
endif
ifneq ($(strip $(USELIBCAP)),)
LIBS:=$(LIBS) -lcap
CPPFLAGS+=-DLIBCAP
endif
ifneq ($(strip $(USESYSTEMD)),)
LIBS:=$(LIBS) -lsystemd
CPPFLAGS+=-DSYSTEMD
CONDITIONAL_TARGETS+=systemd-sslh-generator
endif
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
.c.o: *.h version.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
version.h:
./genver.sh >version.h
sslh: sslh-fork sslh-select
$(OBJS): version.h
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-select: version.h $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
systemd-sslh-generator: systemd-sslh-generator.o
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
echosrv: version.h $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
$(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
# Create release: export clean tree and tag current
# configuration
release:
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
# generic install: install binary and man page
install: sslh $(MAN)
mkdir -p $(DESTDIR)/$(BINDIR)
mkdir -p $(DESTDIR)/$(MANDIR)
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
install -p -m 0644 $(MAN) $(DESTDIR)/$(MANDIR)/$(MAN)
# "extended" install for Debian: install startup script
install-debian: install sslh $(MAN)
sed -e "s+^PREFIX=+PREFIX=$(PREFIX)+" scripts/etc.init.d.sslh > /etc/init.d/sslh
chmod 755 /etc/init.d/sslh
update-rc.d sslh defaults
uninstall:
rm -f $(DESTDIR)$(BINDIR)/sslh $(DESTDIR)$(MANDIR)/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh
update-rc.d sslh remove
distclean: clean
rm -f tags cscope.*
clean:
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags --globals -T *.[ch]
cscope:
-find . -name "*.[chS]" >cscope.files
-cscope -b -R
test:
./t

158
Makefile.in Normal file
View File

@ -0,0 +1,158 @@
VERSION=$(shell ./genver.sh -r)
# Configuration -- you probably need to `make clean` if you
# change any of these
# uncomment the following line to disable landlock
# override undefine HAVE_LANDLOCK
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
ENABLE_REGEX=1 # Enable regex probes
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBEV=1 # Use libev?
USESYSTEMD= # Make use of systemd socket activation
COV_TEST= # Perform test coverage?
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
MANDIR?=$(PREFIX)/share/man/man8
MAN=sslh.8.gz # man page name
# End of configuration -- the rest should take care of
# itself
ifneq ($(strip $(ENABLE_SANITIZER)),)
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
endif
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC ?= gcc
AR ?= ar
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
LIBS=-lm -lpcre2-8 @LIBS@
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
OBJS_A=libsslh.a
FORK_OBJS=sslh-fork.o $(OBJS_A)
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
EV_OBJS=processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o $(OBJS_A)
CONDITIONAL_TARGETS=
ifneq ($(strip $(ENABLE_REGEX)),)
CPPFLAGS+=-DENABLE_REGEX
endif
ifneq ($(strip $(USELIBCONFIG)),)
LIBS:=$(LIBS) -lconfig
CPPFLAGS+=-DLIBCONFIG
endif
ifneq ($(strip $(USESYSTEMD)),)
LIBS:=$(LIBS) -lsystemd
CPPFLAGS+=-DSYSTEMD
CONDITIONAL_TARGETS+=systemd-sslh-generator
endif
ifneq ($(strip $(USELIBEV)),)
CONDITIONAL_TARGETS+=sslh-ev
endif
all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
%.o: %.c %.h version.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
$(OBJS_A): $(OBJS)
$(AR) rcs $(OBJS_A) $(OBJS)
version.h: .FORCE
./genver.sh >version.h
.FORCE:
$(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
c2s:
conf2struct sslhconf.cfg
conf2struct echosrv.cfg
sslh-conf.c sslh-conf.h: sslhconf.cfg
$(warning "sslhconf.cfg is more recent than sslh-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
sslh-fork: version.h Makefile $(FORK_OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
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-conf.c echosrv-conf.h: echosrv.cfg
$(warning "echosrv.cfg is more recent than echosrv-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
landlock.o: config.h
$(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
# Create release: export clean tree and tag current
# configuration
release:
git archive $(VERSION) --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
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-fork $(MAN)
mkdir -p $(DESTDIR)/$(BINDIR)
mkdir -p $(DESTDIR)/$(MANDIR)
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
install -p -m 0644 $(MAN) $(DESTDIR)/$(MANDIR)/$(MAN)
# "extended" install for Debian: install startup script
install-debian: install sslh $(MAN)
sed -e "s+^PREFIX=+PREFIX=$(PREFIX)+" scripts/etc.init.d.sslh > /etc/init.d/sslh
chmod 755 /etc/init.d/sslh
update-rc.d sslh defaults
uninstall:
rm -f $(DESTDIR)$(BINDIR)/sslh $(DESTDIR)$(MANDIR)/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh
update-rc.d sslh remove
distclean: clean
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
clean:
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
echo "// this is a placeholder for version.h, to make code-checking editors happy" > version.h
tags:
ctags --globals -T *.[ch]
cscope:
-find . -name "*.[chS]" >cscope.files
-cscope -b -R
test:
./t

558
README.md
View File

@ -5,444 +5,204 @@ sslh -- A ssl/ssh multiplexer
them further based on tests performed on the first data
packet sent by the remote client.
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
implemented, and any other protocol that can be tested using
a regular expression, can be recognised. A typical use case
is to allow serving several services on port 443 (e.g. to
connect to SSH from inside a corporate firewall, which
almost never block port 443) while still serving HTTPS on
that port.
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH,
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
protocol that can be tested using a regular expression, can
be recognised. A typical use case is to allow serving
several services on port 443 (e.g. to connect to SSH from
inside a corporate firewall, which almost never block port
443) while still serving HTTPS on that port.
Hence `sslh` acts as a protocol demultiplexer, or a
switchboard. Its name comes from its original function to
serve SSH and HTTPS on the same port.
switchboard. With the SNI and ALPN probe, it makes a good
front-end to a virtual host farm hosted behind a single IP
address.
Compile and install
===================
`sslh` has the bells and whistles expected from a mature
daemon: privilege and capabilities dropping, inetd support,
systemd support, transparent proxying, support for HAProxy's
proxyprotocol, chroot, logging, IPv4 and IPv6, TCP and UDP,
a fork-based, a select-based model, and yet another based on
libev for larger installations.
Dependencies
------------
Install
=======
`sslh` uses [libconfig](http://www.hyperrealm.com/libconfig/)
and [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, these are contained in packages `libwrap0-dev` and
`libconfig8-dev`.
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and
`libconfig-devel`:
yum install libconfig libconfig-devel
If you can't find `libconfig`, or just don't want a
configuration file, set `USELIBCONFIG=` in the Makefile.
Compilation
-----------
After this, the Makefile should work:
make install
There are a couple of configuration options at the beginning
of the Makefile:
* `USELIBWRAP` compiles support for host access control (see
`hosts_access(3)`), you will need `libwrap` headers and
library to compile (`libwrap0-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
Binaries
--------
The Makefile produces two different executables: `sslh-fork`
and `sslh-select`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes.
If you are going to use `sslh` for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
`sslh-fork` is probably more suited for you.
* `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.
Installation
------------
* In general:
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
* For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
* For CentOS:
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
You might need to create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
update-rc.d sslh defaults
Please refer to the [install guide](doc/INSTALL.md).
Configuration
=============
If you use the scripts provided, sslh will get its
configuration from /etc/sslh.cfg. Please refer to
example.cfg for an overview of all the settings.
Please refer to the [configuration guide](doc/config.md).
A good scheme is to use the external name of the machine in
`listen`, and bind `httpd` to `localhost:443` (instead of all
binding to all interfaces): that way, HTTPS connections
coming from inside your network don't need to go through
`sslh`, and `sslh` is only there as a frontal for connections
coming from the internet.
Note that 'external name' in this context refers to the
actual IP address of the machine as seen from your network,
i.e. that that is not `127.0.0.1` in the output of
`ifconfig(8)`.
Libwrap support
---------------
Sslh can optionally perform `libwrap` checks for the sshd
service: because the connection to `sshd` will be coming
locally from `sslh`, `sshd` cannot determine the IP of the
client.
OpenVPN support
---------------
OpenVPN clients connecting to OpenVPN running with
`-port-share` reportedly take more than one second between
the time the TCP connection is established and the time they
send the first data packet. This results in `sslh` with
default settings timing out and assuming an SSH connection.
To support OpenVPN connections reliably, it is necessary to
increase `sslh`'s timeout to 5 seconds.
Instead of using OpenVPN's port sharing, it is more reliable
to use `sslh`'s `--openvpn` option to get `sslh` to do the
port sharing.
Using proxytunnel with sslh
---------------------------
If you are connecting through a proxy that checks that the
outgoing connection really is SSL and rejects SSH, you can
encapsulate all your traffic in SSL using `proxytunnel` (this
should work with `corkscrew` as well). On the server side you
receive the traffic with `stunnel` to decapsulate SSL, then
pipe through `sslh` to switch HTTP on one side and SSL on the
other.
In that case, you end up with something like this:
ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd
Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd
Configuration goes like this on the server side, using `stunnel3`:
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \
sslh -i --http localhost:80 --ssh localhost:22
* stunnel options:
* `-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
* `-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.
Capabilities support
Transparent proxying
--------------------
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
make use of POSIX capabilities; this will save the required
capabilities needed for transparent proxying for unprivileged
processes.
Transparent proxying allows the target server to see the
original client IP address, i.e. `sslh` becomes invisible.
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
`capabilities(7)`).
The same result can be achieved more easily by using
`proxyprotocol` if the backend server supports it. This is a
simple setting to add to the `sslh` protocol configuration,
usually with an equivalently simple setting to add in
the backend server configuration, so try that first.
You can use the `setcap(8)` utility to give these capabilities
to the executable:
This means services behind `sslh` (Apache, `sshd` and so on)
will see the external IP and ports as if the external world
connected directly to them. This simplifies IP-based access
control (or makes it possible at all), and makes it possible
to use IP-based banning tools such as `fail2ban`.
# setcap cap_net_bind_service,cap_net_admin+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).
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).
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"
There are two methods. One uses additional virtual network
interfaces. The principle and basic setup is described
[here](doc/simple_transparent_proxy.md), with further
scenarios described [there](doc/scenarios-for-simple-transparent-proxy.md).
/etc/ipfw/sslh.rules
Another method uses iptable packet marking features, and is
highly dependent on your network environment and
infrastructure setup. There is no known generic approach,
and if you do not find directions for your exact setup, you
will probably need an extensive knowledge of network
management and iptables setup".
#! /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:
It is described in its own [document](doc/tproxy.md).
In most cases, you will be better off following the first
method.
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):
Docker image
------------
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --ssl 192.168.0.1:4443
How to use
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.
Systemd Socket Activation
-------------------------
If compiled with `USESYSTEMD` then it is possible to activate
the service on demand and avoid running any code as root.
In this mode any listen configuration options are ignored and
the sockets are passed by systemd to the service.
Example socket unit:
[Unit]
Before=sslh.service
[Socket]
ListenStream=1.2.3.4:443
ListenStream=5.6.7.8:444
ListenStream=9.10.11.12:445
FreeBind=true
[Install]
WantedBy=sockets.target
Example service unit:
[Unit]
PartOf=sslh.socket
[Service]
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443
KillMode=process
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
User=sslh
---
With this setup only the socket needs to be enabled. The sslh service
will be started on demand and does not need to run as root to bind the
sockets as systemd has already bound and passed them over. If the sslh
service is started on its own without the sockets being passed by systemd
then it will look to use those defined on the command line or config
file as usual. Any number of ListenStreams can be defined in the socket
file and systemd will pass them all over to sslh to use as usual.
```bash
docker run \
--cap-add CAP_NET_RAW \
--cap-add CAP_NET_BIND_SERVICE \
--rm \
-it \
ghcr.io/yrutschle/sslh:latest \
--foreground \
--listen=0.0.0.0:443 \
--ssh=hostname:22 \
--tls=hostname:443
```
To avoid inconsistency between starting via socket and starting directly
via the service Requires=sslh.socket can be added to the service unit to
mandate the use of the socket configuration.
docker-compose example
Rather than overwriting the entire socket file drop in values can be placed
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
values that will be merged.
```yaml
version: "3"
In addition to the above with manual .socket file configuration there is an
optional systemd generator which can be compiled - systemd-sslh-generator
services:
sslh:
image: ghcr.io/yrutschle/sslh:latest
hostname: sslh
ports:
- 443:443
command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
depends_on:
- nginx
- openvpn
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
nginx:
image: nginx
This will also merge with any sslh.socket.d drop in configuration but will be
overriden by a /etc/systemd/system/sslh.socket file.
openvpn:
image: openvpn
```
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Transparent mode 1: using sslh container for networking
Fail2ban
--------
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
```yaml
version: "3"
If using transparent proxying, just use the standard ssh
rules. If you can't or don't want to use transparent
proxying, you can set `fail2ban` rules to block repeated ssh
connections from an IP address (obviously this depends
on the site, there might be legitimate reasons you would get
many connections to ssh from the same IP address...)
services:
sslh:
build: https://github.com/yrutschle/sslh.git
container_name: sslh
environment:
- TZ=${TZ}
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
sysctls:
- net.ipv4.conf.default.route_localnet=1
- net.ipv4.conf.all.route_localnet=1
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
ports:
- 443:443 #sslh
See example files in scripts/fail2ban.
- 80:80 #nginx
- 8443:8443 #nginx
- 1194:1194 #openvpn
extra_hosts:
- localbox:host-gateway
restart: unless-stopped
nginx:
image: nginx:latest
.....
network_mode: service:sslh #set nginx container to use sslh networking.
# ^^^ This is required. This makes nginx reachable by sslh via localhost
openvpn:
image: openvpn:latest
.....
network_mode: service:sslh #set openvpn container to use sslh networking
```
Transparent mode 2: using host networking
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
container_name: sslh
environment:
- TZ=${TZ}
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
# must be set manually
#sysctls:
# - net.ipv4.conf.default.route_localnet=1
# - net.ipv4.conf.all.route_localnet=1
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
network_mode: host
restart: unless-stopped
nginx:
image: nginx:latest
.....
ports:
- 8443:8443 # bind to docker host on port 8443
openvpn:
image: openvpn:latest
.....
ports:
- 1194:1194 # bind to docker host on port 1194
```
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 prefered channel for announcements.
requests, and will be the preferred channel for announcements.
Of course, check the [FAQ](doc/FAQ.md) first!

6006
argtable3.c Normal file

File diff suppressed because it is too large Load Diff

273
argtable3.h Normal file
View File

@ -0,0 +1,273 @@
/*******************************************************************************
* 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
* <sheitmann@users.sourceforge.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of STEWART HEITMANN nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* 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 STEWART HEITMANN 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 ARGTABLE3
#define ARGTABLE3
#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 };
#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 initialised 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 defining 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;
typedef struct arg_rem {
struct arg_hdr hdr; /* The mandatory argtable header struct */
} arg_rem_t;
typedef struct arg_lit {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
} arg_lit_t;
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;
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;
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;
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;
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;
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 };
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 *********************************/
ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, 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);
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);
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);
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);
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);
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);
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);
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 *******************************************/
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 ********/
ARG_EXTERN void arg_free(void** argtable);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,30 +1,34 @@
# This is a basic configuration file that should provide
# sensible values for "standard" setup.
verbose: false;
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
# Default is to bind all interfaces. httpd can be started
# first to bind on localhost, in which case sslh will bind
# only other interfaces.
listen:
(
{ host: "thelonious"; port: "443"; }
{ host: "0.0.0.0"; port: "443"; },
{ host: "[::]"; port: "443"; }
);
# Change to the protocols you want to forward to. The
# defaults here are sensible for services running on
# localhost
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "ssl"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);

98
collection.c Normal file
View File

@ -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);
}

18
collection.h Normal file
View File

@ -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

802
common.c

File diff suppressed because it is too large Load Diff

112
common.h
View File

@ -5,6 +5,7 @@
* enough for the macros to adapt (http://support.microsoft.com/kb/111855)
*/
#ifdef __CYGWIN__
#undef FD_SETSIZE
#define FD_SETSIZE 4096
#endif
@ -33,21 +34,36 @@
#include <sys/capability.h>
#endif
#ifdef __APPLE__
#include <AvailabilityMacros.h>
#endif
#include "config.h"
#include "version.h"
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#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) \
#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)); \
return res; \
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) { \
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
perror(str); \
exit(1); \
}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#if 1
@ -60,14 +76,19 @@
#define IP_FREEBIND 0
#endif
#ifndef TCP_FASTOPEN
#define TCP_FASTOPEN 0
#endif
#ifndef TCP_FASTOPEN_CONNECT
#define TCP_FASTOPEN_CONNECT 30 /* Attempt FastOpen with connect. */
#endif
enum connection_state {
ST_PROBING=1, /* Waiting for timeout to find where to forward */
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 {
@ -77,54 +98,105 @@ 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 proto *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_storage 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 */
int family; /* AF_INET | AF_UNIX */
};
#define FD_CNXCLOSED 0
#define FD_NODATA -1
#define FD_STALLED -2
/* String description of a connection */
#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
struct connection_desc {
char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
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);
void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
int fd2fd(struct queue *target, struct queue *from);
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
void resolve_name(struct addrinfo **out, char* fullname);
void log_connection(struct connection *cnx);
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx);
void log_connection(struct connection_desc* desc, const struct connection *cnx);
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx);
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, char* msg, ...);
void dump_connection(struct connection *cnx);
int resolve_split_name(struct addrinfo **out, const char* hostname, const char* port);
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 defer_write(struct queue *q, void* data, ssize_t data_size);
int defer_write_before(struct queue *q, void* data, ssize_t data_size);
int flush_deferred(struct queue *q);
extern int probing_timeout, verbose, inetd, foreground,
background, transparent, numeric;
extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct sslhcfg_item cfg;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING;
extern const char* user_name, *pid_file, *chroot_path, *facility;
extern const char* server_type;
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
extern int hosts_ctl();
#endif
/* sslh-fork.c */
void start_shoveler(int);
void main_loop(int *listen_sockets, int num_addr_listen);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
/* landlock.c */
void setup_landlock(void);
#endif

21
config.h.in Normal file
View File

@ -0,0 +1,21 @@
#ifndef CONFIG_H
/* Template for config.h, filled by `configure`. */
/* Libwrap, to support host_ctl, /etc/allow and /etc/deny */
#undef HAVE_LIBWRAP
/* Landlock sandboxing Linux LSM */
#undef HAVE_LANDLOCK
/* Support for Proxy-protocol using libproxyprotocol */
#undef HAVE_PROXYPROTOCOL
/* libcap support, to use Linux capabilities */
#undef HAVE_LIBCAP
/* libbsd, to change process name */
#undef HAVE_LIBBSD
#endif

4655
configure vendored Executable file

File diff suppressed because it is too large Load Diff

20
configure.ac Normal file
View File

@ -0,0 +1,20 @@
dnl Use autoconf to generate the `configure` script from this and Makefile.in
dnl This is awkardly adapted from https://github.com/edrosten/autoconf_tutorial
dnl (all mistakes mine)
AC_INIT
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_FILES([Makefile])
AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ], [])
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], [])
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], [])
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
LIBS="$LIBS"
AC_SUBST([LIBS])
AC_OUTPUT

97
container-entrypoint.sh Executable file
View File

@ -0,0 +1,97 @@
#!/bin/sh
# SPDX-License-Identifier: GPL2-or-later
#
# Copyright (C) 2023 Olliver Schinagl <oliver@schinagl.nl>
#
# A beginning user should be able to docker run image bash (or sh) without
# needing to learn about --entrypoint
# https://github.com/docker-library/official-images#consistency
set -eu
bin='sslh'
# run command if it is not starting with a "-" and is an executable in PATH
if [ "${#}" -le 0 ] || \
[ "${1#-}" != "${1}" ] || \
[ -d "${1}" ] || \
! command -v "${1}" > '/dev/null' 2>&1; then
entrypoint='true'
fi
unconfigure_iptables() {
echo "Received SIG TERM/INT/KILL. Removing iptables / routing changes"
set +e # Don't exit if got error
set -x
iptables -t raw -D PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -D POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
iptables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
iptables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip rule del fwmark 0x1 lookup 100
ip route del local 0.0.0.0/0 dev lo table 100
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
ip6tables -t raw -D PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -D POSTROUTING ! -o lo -s ::1/128 -j DROP
ip6tables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
ip6tables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip -6 rule del fwmark 0x1 lookup 100
ip -6 route del local ::/0 dev lo table 100
fi
set -e
set +x
}
configure_iptables() {
echo "Configuring iptables and routing..."
set +e # Don't exit if got error
set -x
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
fi
set -e
set +x
}
for _args in "${@}" ; do
if [ "${_args:-}" = '--transparent' ] ; then
echo '--transparent flag is set'
configure_iptables
trap unconfigure_iptables TERM INT KILL
break
fi
done
# Drop privileges and run as sslh user
sslh_cmd="${entrypoint:+${bin}} ${@}"
echo "Executing with user 'sslh': ${sslh_cmd}"
exec su - sslh -c "${sslh_cmd}" &
wait "${!}"
exit 0

View File

@ -0,0 +1,283 @@
# Daisy-Chaining-Transparency #
This documentation goes a level deeper, what happens in the operating system with IP-addresses, and why some combinations of programs are failing, when they use the same transparency method.
There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.<br>
Unfortunately you will see a lot of errors popping out: **Address already in use**<br>
[This article from Cloudflare blog](https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections) explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem.
Let us look to the following example: We have sslh (S) accepting connections from a client (C) and forwarding one of those connections to a man-in-the-middle (M), which finally forwards this connection to sshd. If everything works perfectly, we would like to see those connections.
![Dataflow-Of-Daisy-Chain](./detailed-ip-transparency.png)
But unfortunately we are receiving in many constellations errors, when M tries to open its connection to our final target sshd, here called T.
Let us look more close, why that is happening. We need for this two terminal windows on the same server.<br>
### First example, uncooperative applications ###
As the problem has nothing to do with transparency itself, but only of reuse of same IP-addresses and ports, we avoid the overhead of the additional capablities, to keep the example easy and clear.
In the first terminal we are starting python3 and entering the following three lines:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.bind(("192.168.255.254", 12345))
>>>
```
Now we are going to the second terminal window, and trying just the same:
```
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.bind(("192.168.255.254", 12345))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 98] Address already in use
sock.bind(("192.168.255.254", 12346)) ##this however works!!
```
Here we are getting the error, which caused many of us hours of research.
The problem is caused by the fact, that the kernel does not know at the moment of the bind()-call, how we want to use the socket. If we use this as a server socket, and will do a listen()-call as next, this will not work, as for server sockets, the two-value tuple ADDRESS:PORT needs to be unique only to the process, tied to this socket.
That is the reason, that there are port ranges, reserved for servers, where the administrator is responsible not to assign the same port to two applications.
But as server ports are coming from a range, which will not be used for client connections, a server can be sure, that if it is started at any time in the future, no outgoing client has used its port for an outbound connection.
Clients are usually using ports from a so called [ephemeral port range](https://en.wikipedia.org/wiki/Ephemeral_port).
However, for clients each connection is valid, as long as one value in the four-tuples describing the connection is different. In our example above, the two values from the destination are different, so this connection could be established (in theory) without conflicts.
To make that happen, you need to deploy a special socket option to this socket, to explain, that we "know the risks" and we will reuse the ip-port combination.
And, as we see from the second bind() in the second example, the error message: _**Address already in use**_ really means: _**Address:Port already in use**_
### Taking Care, part I ###
Ok, now we are entering our two terminals again, pressing Ctrl-D to finish python, and start a new session like this in both terminals:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
>>> sock.bind(("192.168.255.254", 12345))
>>>
```
And we will see, that the problem is solved, when both applications are taking care.
### Taking Care, part II ###
Ok, now we are going back to our terminals, pressing again Ctrl-D to finish python, and start new sessions like this:
In the first terminal we repeat the input from our first example, without the setsockopt() call:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.bind(("192.168.255.254", 12345))
>>>
```
in the second terminal, we enter our modified cooperative example:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
>>> sock.bind(("192.168.255.254", 12345))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 98] Address already in use
>>>
```
Oops, here is our error again. And this is the reason, why I called this method "cooperative". As the first application has bound to that IP:PORT combination, without telling, that "it knows the risk", the kernel denies us, to use this combinations, as we may break the already active application. The first application is not "cooperative" :-(<br>
Ok, but the kernel gives as some more possibilities: As we now get connections from a uncoperative application, we can no longer use the Client-IP-Port combination. We need to use a conflict free port for the client IP, to succeed. So lets get back to terminal 2 and continue after the error with the following commands:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
>>> sock.bind(("192.168.255.254", 12345))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 98] Address already in use
>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
>>> sock.setsockopt(socket.SOL_IP, socket.IP_BIND_ADDRESS_NO_PORT, 1)
>>> sock.bind(("192.168.255.254", 0))
>>>
```
This last example makes it neccessary, that you use a recent version of the python3 socket library, as older versions, have the option _IP_BIND_ADDRESS_NO_PORT_ not yet defined.
With his behaviour, we are telling the kernel, that the kernel should assign us a conflict free port address at latest possible moment, while calling connect().
From `man ip`:
```
Inform the kernel to not reserve an ephemeral port when using bind(2)
with a port number of 0. The port will later be automatically chosen
at connect(2) time, in a way that allows sharing a source port as
long as the 4-tuple is unique.
```
Ok, now with those two actions an application is really ready for cooperative working in ip-transparent chains.
If you are running in problems, with any kind of application, where you are redirecting transparent traffic to from sslh, check the following:
- Are you using a recent version of sslh, having the described feature enabled
- are you using the most recent version of the other application
If you can confirm both checks, tell the maintainers of the other application, about possible fixes, and send them a link to this article.
## Practical Use Of Daisy-Chaining: Proxytunnel Endpoint ##
One reasons, why we want to combine two programs is related to the core functionality of sslh. You wish to hide your ssh connection behind a https port.
But now you would like, to reach this port via the [proxy-tunnel application](https://github.com/proxytunnel/proxytunnel), though an restrictive http(s) proxy. This is in many cases, one of the few methods, to escape from restricted private networks, like in companies, schools and universities. Unfortunately, many of those proxy-servers will check, that the protocol leaving the proxy is really tls. Therefore you need and endpoint in your system, which will terminate the tls-connection and forward the encapsulated ssh stream to the sshd. Sslh can't do tls termination, as this is not a core job of tls. One of the solutions tried here is stunnel. Stunnel can do transparency like sslh, but unfortunately belongs to the uncooperative ip-transparent programs. At the time of writing this article, you can use stunnel as the first-in-chain, and a very recent sslh as second in chain. But nginx (or openresty) is capable of this, however it prefers (at least in the tested versions), mostly the second way just selecting a new random port and not (always) preserving the original source port, what makes debugging of events much easier.
```
stream {
ssl_preread on;
map $ssl_preread_server_name $name {
default master.yourdomain.top;
t1.yourdomain.top t1.yourdomain.top;
t2.yourdomain.top t2.yourdomain.top;
cryptic.foo.bar location.selfsigned.cert;
}
## $destination port :443 is assumed, beeing as real
## webserver. Either anothe nginx http-server or apache
## or anything other ...
map $ssl_preread_server_name $dest {
default 192.168.255.254:443;
t1.yourdomain.top 192.168.255.254:443;
t2.yourdomain.top 192.168.255.254:444;
cryptic.foo.notexist 192.168.255.254:445;
}
## this is the server, to handle incoming tcp streams
## and dispatching them transparent to $dest
## 192.168.255.254:1443 is the address, where
## the front facing sslh sends traffic to
server {
listen 192.168.255.254:1443;
proxy_connect_timeout 5s;
proxy_timeout 3m;
proxy_bind $remote_addr transparent;
proxy_pass $dest;
ssl_preread on;
}
## this is a basic endpoint for proxy-tunnel connections
server {
listen 192.168.255.254:444 ssl;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
## give facl to the nginx user, see later in article
ssl_certificate /etc/letsencrypt/live/$name/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$name/privkey.pem;
ssl_dhparam /etc/nginx/dhparam-nginx.pem;
ssl_session_cache shared:MySSL:10m;
ssl_session_tickets off;
proxy_connect_timeout 5s;
proxy_timeout 3m;
proxy_bind $remote_addr transparent;
proxy_pass 192.168.255.254:22 ;
}
## this is a fancy destination, using some tricks, to fool
## some proxies.
server {
listen 192.168.255.254:445 ssl;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
## this points to a directory, containg a self signed certificate
## created with CN and SAN to an not officially existing
## domain, e.g. cryptic.foo.notexists
ssl_certificate /etc/nginx/$name/fullchain.pem;
ssl_certificate_key /etc/nginx/$name/privkey.pem;
ssl_dhparam /etc/nginx/dhparam-nginx.pem;
ssl_session_cache shared:MySSL:10m;
ssl_session_tickets off;
## additional trick: requiring client certificate authentication
ssl_client_certificate /etc/nginx/public.cert ;
ssl_verify_client on ;
proxy_connect_timeout 5s;
proxy_timeout 3m;
proxy_bind $remote_addr transparent;
proxy_pass 192.168.255.254:22;
}
}
```
This nginx stream server shows a combination of different possibilities, how to establish an endpoint for proxytunnel or similar programs, to hide your ssh connection. Remember: Hiding ssh tunneled in tls does not neccessarly raise the security of your connection. In theory a combination of two methods may also lower the security. In addition there are more drawbacks: Such a connection needs more cpu on both ends for doing double crypto, and the throughput through he connection drops, as the ratio payload/header increases. the single ssh-packets are smaller, than usual.
So it is not recommended for doing scp or sftp through such connections.
### Nginx Configration Explained ###
However, there are situations, where this is the only way, to reach your server.
So we have now this nginx instance helping us out.
The server stanza listening on _192.168.255.254:1443_ is the main process, accepting incoming connections forwarded from sslh. It prereads tls SNI names, to get destination information. When in the nginx-configuration a variable is used, which is set by an map action, the mapping takes place just in that moment, when the variable needs to be expanded.
So we have either default values, in case there is no SNI, or values for individual SNI.
The connection stream is than forwarded ip-transparent to a given destination, which is itself another nginx instance, defined in the configuration file.
#### The Standard TLS-Termination ####
The server stanza listening on _192.168.255.254:444_ is a very basic TLS terminating endpoint. In most cases, a proxy is happy with a destination like this. Here nginx just terminates the TLS connection and shovels the incoming packets over to sshd in ip-transparent way. The proxytunnel configuration for this target will look like:
```
host t2.yourdomain.top
ProxyCommand /path/to/proxytunnel -e -p PROXY-IP:3128 -d t2.yourdomain.top:443
ServerAliveInterval 30
```
If you have no proxytunnel on your system, you can also use openssl. In this case the configuration looks like:
```
host t2-alias
ProxyCommand openssl s_client -proxy PROXY-IP:3128 -connect t2.yourdomain.top:443
ServerAliveInterval 30
```
`ssh ts-alias` will use this configuration.
One point from the above configuration needs further explanation, as we just refer to our common letsencrypt store, for certificates.
##### Excurse To File ACLs #####
We are using here our common letsencrypt certificate store, as copying certificates after renewal is a pain in the as and prone to errors.
A small script makes sure, that all applications can read the certificates:
```
for i in Debian-exim dovecot mail www-data nginx ; do
setfacl -Rdm u:$i:rX ./archive/
setfacl -Rdm u:$i:rX ./live/
setfacl -Rm u:$i:rX ./archive/
setfacl -Rm u:$i:rX ./live/
for file in $( find ./live ./archive -type l,f ) ; do
echo -e "$i $file set"
setfacl -m u:$i:r $file
done
done
```
This script needs only to be run, when a new application user needs access.
The first two lines are making sure (watch the **d**), that the given options
on those directories will be the default for newly created files below.
The uppercase **X** means, that **x**-access will only be granted to directories or files, having already **x** set for others.
#### The Tricky TLS Termination ####
The server stanza listening on _192.168.255.254:445_ is a more tricky TLS terminating endpoint. Some proxies are trying to inspect the destination, before letting you go. Some of them you can fool, others not.
One trick can be to use an phantasy domain name with a self signed certificate, no dns server will ever resolve. As you are using this name as SNI name in your proxy-tunnel connection, it will work. This can be also a way, to hide your sshd, if someone, who knows you are using sslh tries to find your sshd. As long, as this person does not get access to proxies you are using, this may help in some situations.
Another trick is, requiring a client certificate for authentication. This is in each case the much better approach, as you can combine this also with official connected domain names. Without the client certificate no sshd!
The client certificate does not increase the above mentioned ratio between payload and headers, as this is only used while establishing the TLS connection.
It prevents however a proxy, doing a parallel sneak to your destination, to figure out what is behind. I have seen proxies letting you connect with this method, others are denying access.
To access this endpoint, the proxytunnel configuration inside `~/.ssh/config`
will look like:
```
host cryptic.foo.notexists
ProxyCommand /path/to/proxytunnel -e -c ~/public.cert -k ~/private.pem -C ~/myOwnCA.cert -p PROXY-IP:3128 -o cryptic.foo.notexists -d t1.yourdomain.top:443
ServerAliveInterval 30
```
-C gives the certfile to your certificate used for selfsigning the server certificate. You can also set -z for not verifying the certificate, but that is not recommended! `t1.yourdomain.top` represents a valid domain name, where the listening sslh can be reached. This name is not taken for SNI, because **-o** sets our hidden SNI name. Whenever you enter `ssh cryptic.foo.notexists` you will get connected to your server!
The best reommendation however is: Avoid self signed certs, if you are not really sure, what you are doing.

170
doc/FAQ.md Normal file
View File

@ -0,0 +1,170 @@
Frequently Asked Questions
==========================
When something doesn't work, look up here... and if it still
doesn't work, report how what was suggested here went.
It's also worth reading [how to ask
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
before posting on the mailing list or opening an issue in
GitHub.
Getting more info
=================
There are several `verbose` options that each enable a set
of messages, each related to some event type. See
`example.cfg` for a list of them.
If something doesn't work, you'll want to run `sslh` with
lots of logging, and the logging directly in the terminal
(Otherwise, logs are sent to `syslog`, and usually end up in
`/var/log/auth.log`). There is a general `--verbose` option
that will allow you to enable all messages:
```
sslh -v 3 -f -F myconfig.cfg
```
forward to [PROBE] failed:connect: Connection refused
=====================================================
Usually this means `sslh` is configured to forward a
protocol somewhere, but no service is listening on the
target address. Check your `sslh` configuration, check the
corresponding server really is listening and running.
Finally, check the server is listening where you expect it
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"] }
);
```

171
doc/INSTALL.md Normal file
View File

@ -0,0 +1,171 @@
Pre-built binaries
==================
Docker images of `master` and of the tagged versions are
available directly from [Github](https://github.com/yrutschle/sslh/pkgs/container/sslh).
Windows binaries for Cygwin are graciously produced by
nono303 on his [repository](https://github.com/nono303/sslh).
Compile and install
===================
Dependencies
------------
`sslh` uses:
* [libconfig](http://www.hyperrealm.com/libconfig/).
For Debian this is contained in package `libconfig-dev`.
You can compile with or without it using USELIBCONFIG in the Makefile.
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, this is contained in packages `libwrap0-dev`.
Presence of libwrap is checked by the configure script.
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
You can compile with or without it using USESYSTEMD in the Makefile.
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
Presence of libcap is checked by the configure script.
* [libconfig++-dev](https://packages.debian.org/bookworm/libconfig++-dev), in package `lìbconfig++-dev`
* libbsd, to enable to change the process name (as shown in `ps`,
so each forked process shows what protocol and what connection it is serving),
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
Presence of libbsd is checked by the configure script.
* libpcre2, in package `libpcre2-dev`.
You can compile with or without it using ENABLE_REGEX in the Makefile.
* libev-dev, in package `libev-dev`.
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
* [libproxyprotocol](https://github.com/kosmas-valianos/libproxyprotocol.git)
to support HAProxy's [ProxyProtocol](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt).
As this is not part of the distribution packages, set
C_INCLUDE_PATH, LD_LIBRARY_PATH, and LIBRARY_PATH to the appropriate
values:
```
export C_INCLUDE_PATH=/home/user/src/libproxyprotocol/src
export LD_LIBRARY_PATH=/home/user/src/libproxyprotocol/libs
export LIBRARY_PATH=/home/user/src/libproxyprotocol/libs
```
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and `libconfig-devel`:
yum install libconfig libconfig-devel
If you want to rebuild `sslh-conf.c` (after a `make distclean` for example),
you will also need to add [conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
(v1.5) to your path.
The test scripts are written in Perl, and will require
`IO::Socket::INET6` (`libio-socket-inet6-perl` in Debian).
Compilation
-----------
First you have to run `./configure` in the _**./sslh**_ directory. After this,
the Makefile is created, and you can do your configuration changes in the Makefile.
After each run of ./configure, those changes are gone and the Makefile is recreated.
There are a couple of configuration options at the beginning of the Makefile:
* `# override undefine HAVE_LANDLOCK` if you uncomment this line, sslh will be compiled
without landlock. This works with gcc versions < 12. Otherwise, if your system has
linux/landlock.h in the include path, the configure script creates a _**config.h**_ file,
which defines HAVE_LANDLOCK. It is not enough, to set this to 0, you must delete it,
when you don't wish to have landlock in your binary.
* `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`),
you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration file.
You will need `libconfig` headers to compile (`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
* `USELIBCAP` compiles support for libcap, which allows to inherit capabilities to
daughter-processes, which run as restricted users. You need this, when you wish to
make sure, that the --user= parameter can be used, without setting capabilities etc.
to your binaries, to make this work.
Now you can do either a plain `make` to create the binaries, or you can do an
`make install` to create the binaries and install them.
Generating the configuration parser
-----------------------------------
The configuration file and command line parser is generated by `conf2struct`,
from `sslhconf.cfg`, which generates `sslh-conf.c` and `sslh-conf.h`.
The resulting files are included in the source
so `sslh` can be built without `conf2struct` installed.
Further, to prevent build issues,
`sslh-conf.[ch]` has no dependency to `sslhconf.cfg` in the Makefile.
In the event of adding configuration settings,
they need to be regenerated using `make c2s`.
Binaries
--------
The Makefile produces three different executables:
`sslh-fork`, `sslh-select` and `sslh-ev`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead of many processes.
If you are going to use `sslh` for a "small" setup
(less than a dozen ssh connections and a low-traffic https server)
then `sslh-fork` is probably more suited for you.
* `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
------------
* In general:
```sh
./configure
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
```
* For Debian:
```sh
cp scripts/etc.init.d.sslh /etc/init.d/sslh
```
* For CentOS:
```sh
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
```
You might need to create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
```sh
update-rc.d sslh defaults
```

View File

@ -33,7 +33,7 @@ with launchctl or simply reboot.
<string>0.0.0.0:443</string>
<string>--ssh</string>
<string>localhost:22</string>
<string>--ssl</string>
<string>--tls</string>
<string>localhost:443</string>
</array>
<key>QueueDirectories</key>

72
doc/README.Windows.md Normal file
View File

@ -0,0 +1,72 @@
It is possible to run `sslh` on Windows. The `fork` model
should be avoided as it is very inefficient on Windows, but
`sslh-select` and `sslh-ev` both work with good performance
(prefer the latter, however).
The following script downloads the latest cygwin, the latest version of sslh, and then compiles and copies the binaries with dependancies to an output folder.
It may be needed to correct it from time to time, but it works. I use it in a virtual machine.
Just retrieve WGET.EXE from https://eternallybored.org/misc/wget/ or git binaries.
Copy the 3 files
GO.cmd
wget.exe
compile.sh
to C root folder, then execute **GO.cmd** with administrative rights.
with **GO.cmd**
@ECHO OFF
CD /D "%~dp0"
NET SESSION >NUL 2>&1
IF %ERRORLEVEL% NEQ 0 (
ECHO Permission denied. This script must be run as an Administrator.
ECHO:
GOTO FIN
) ELSE (
ECHO Running as Administrator.
TIMEOUT /T 2 >NUL
wget --no-check-certificate https://www.cygwin.com/setup-x86_64.exe
IF NOT EXIST setup-x86_64.exe GOTO FIN
MKDIR C:\Z
setup-x86_64.exe -l C:\Z -s ftp://ftp.funet.fi/pub/mirrors/sourceware.org/pub/cygwin/ -q -P make -P git -P gcc-g++ -P autoconf -P automake -P libtool -P libpcre-devel -P libpcre2-devel -P bison -P libev-devel
MKDIR C:\cygwin64\home\user
COPY COMPILE.SH C:\cygwin64\home\user
START C:\cygwin64\bin\mintty.exe /bin/bash --login -i ~/compile.sh
START EXPLORER C:\zzSORTIE
)
:FIN
PAUSE
EXIT
and **compile.sh**
# SAVE FILE TO UNIX FORMAT
# COPY IT IN C cygwin64 home user
git clone https://github.com/hyperrealm/libconfig.git
cd libconfig
autoreconf -fi
./configure
make
make install
cd ..
cp /usr/local/lib/libconfig.* /usr/lib
git clone https://github.com/yrutschle/sslh.git
cd sslh
make
cd ..
mkdir /cygdrive/c/zzSORTIE
cp ./sslh/sslh*.exe /cygdrive/c/zzSORTIE
cp /usr/local/bin/cygconfig-11.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygwin1.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygpcreposix-0.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygpcre-1.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygev-4.dll /cygdrive/c/zzSORTIE
cp /cygdrive/c/cygwin64/bin/cygpcre2-8-0.dll /cygdrive/c/zzSORTIE
This method was contributed by lerenardo on [github](https://github.com/yrutschle/sslh/issues/196#issuecomment-1692805639).

202
doc/config.md Normal file
View File

@ -0,0 +1,202 @@
Configuration
=============
If you use the scripts provided, sslh will get its
configuration from /etc/sslh.cfg. Please refer to
example.cfg for an overview of all the settings.
A good scheme is to use the external name of the machine in
`listen`, and bind `httpd` to `localhost:443` (instead of all
binding to all interfaces): that way, HTTPS connections
coming from inside your network don't need to go through
`sslh`, and `sslh` is only there as a frontal for connections
coming from the internet.
Note that 'external name' in this context refers to the
actual IP address of the machine as seen from your network,
i.e. that that is not `127.0.0.1` in the output of
`ifconfig(8)`.
Libwrap support
---------------
Sslh can optionally perform `libwrap` checks for the sshd
service: because the connection to `sshd` will be coming
locally from `sslh`, `sshd` cannot determine the IP of the
client.
OpenVPN support
---------------
OpenVPN clients connecting to OpenVPN running with
`-port-share` reportedly take more than one second between
the time the TCP connection is established and the time they
send the first data packet. This results in `sslh` with
default settings timing out and assuming an SSH connection.
To support OpenVPN connections reliably, it is necessary to
increase `sslh`'s timeout to 5 seconds.
Instead of using OpenVPN's port sharing, it is more reliable
to use `sslh`'s `--openvpn` option to get `sslh` to do the
port sharing.
Using proxytunnel with sslh
---------------------------
If you are connecting through a proxy that checks that the
outgoing connection really is SSL and rejects SSH, you can
encapsulate all your traffic in SSL using `proxytunnel` (this
should work with `corkscrew` as well). On the server side you
receive the traffic with `stunnel` to decapsulate SSL, then
pipe through `sslh` to switch HTTP on one side and SSL on the
other.
In that case, you end up with something like this:
ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd
Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd
Configuration goes like this on the server side, using `stunnel3`:
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \
sslh -i --http localhost:80 --ssh localhost:22
* stunnel options:
* `-f` for foreground/debugging
* `-p` for specifying the key and certificate
* `-d` for specifying which interface and port
we're listening to for incoming connections
* `-l` summons `sslh` in inetd mode.
* sslh options:
* `-i` for inetd mode
* `--http` to forward HTTP connections to port 80,
and SSH connections to port 22.
Capabilities support
--------------------
On Linux (only?), you can compile sslh with `USELIBCAP=1` set
in the Makefile to make use of POSIX capabilities; this will
save the required capabilities needed for transparent proxying
for unprivileged processes.
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_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_raw+pe sslh-select
Then you can run sslh-select as an unprivileged user, e.g.:
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
Transparent proxy support
-------------------------
Transparent proxying is described in its own
[document](tproxy.md).
It might be easier to configure `sslh` to use Proxyprotocol
if the backend server supports it.
Systemd Socket Activation
-------------------------
If compiled with `USESYSTEMD` then it is possible to activate
the service on demand and avoid running any code as root.
In this mode any listen configuration options are ignored and
the sockets are passed by systemd to the service.
Example socket unit:
[Unit]
Before=sslh.service
[Socket]
ListenStream=1.2.3.4:443
ListenStream=5.6.7.8:444
ListenStream=9.10.11.12:445
FreeBind=true
[Install]
WantedBy=sockets.target
Example service unit:
[Unit]
PartOf=sslh.socket
[Service]
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_RAW
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
User=sslh
With this setup only the socket needs to be enabled. The sslh service
will be started on demand and does not need to run as root to bind the
sockets as systemd has already bound and passed them over. If the sslh
service is started on its own without the sockets being passed by systemd
then it will look to use those defined on the command line or config
file as usual. Any number of ListenStreams can be defined in the socket
file and systemd will pass them all over to sslh to use as usual.
To avoid inconsistency between starting via socket and starting directly
via the service Requires=sslh.socket can be added to the service unit to
mandate the use of the socket configuration.
Rather than overwriting the entire socket file drop in values can be placed
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
values that will be merged.
In addition to the above with manual .socket file configuration there is an
optional systemd generator which can be compiled - systemd-sslh-generator
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overridden by a /etc/systemd/system/sslh.socket file.
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Fail2ban
--------
If using transparent proxying, just use the standard ssh
rules. If you can't or don't want to use transparent
proxying, you can set `fail2ban` rules to block repeated ssh
connections from an IP address (obviously this depends
on the site, there might be legitimate reasons you would get
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 overridden with `udp_timeout`. This allows to
process both single-datagram protocols such as DNS, and
connection-based protocols such as QUIC.
An example for supporting QUIC is shown in `example.cfg`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,51 @@
# Three Scenarios for the simple transparent proxy setup #
![Simple Transparent Proxy Examples](./sslh-examples-v3.png)
## Introduction ##
The first example is the configuration, which was described in the previous document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture.
The connections have two different endings, showing the direction of the opening connection (SYN flag) and the answer connection (SYN-ACK flags). This is important, as the traffic in the transparent proxy setup flows somewhat unexpected.
## Example 1 ##
The first example shows the setup, which is described in the [previous document](./simple_transparent_proxy.md). You see the Client connecting to sslh (red connection). When sslh accepts this connection, the SYN-ACK packet is send back to the client, which sends the first data packet(s) together with the ACK for the SYN-ACK. So the bidirectional tcp connection is fully open.
Sslh opens now the blue connection to sshd and needs for that elevation rights, as it uses the clients IP address as its own address for opening this connection. Now things are becoming complicated: Sshd send back the first packet with SYN-ACK flags (green line), addressed to the clients IP (dotted line). As already described, that would go wrong, so our routing trick makes this packet beeing deflected back to sslh, so this tcp connection is also opened. But we have here now an asymetric behaviour, that the read and write pathes of the tcp connection are going different routes.
The sslh process shuffles now all the bytes coming from sshd from the green line to the red line, vice versa for the packets from the client.
## Example 2 ##
In this example sshd is running on another server. No matter, if this is docker, kvm, virtualbox or another physical host, connected with an ethernet cable. Here we need no dummy interface, so we need another way, to configure our routing deflection. The principle is the same: We need to force packets coming back from sshd going to sslh and not directly back to the client.
In this case its your decision, where those rules will be tied in, options are:
* the startscript of sslh
* the docker or kvm configuration
* the configuration of the outgoing interface
Its two lines you need:
```
ip rule add from SSHD_ADDRESS/32 table sslh
ip route add local 0.0.0.0/0 dev lo table sslh
```
On the sshd host, we need no additional rules, as all traffic is coming back to our sslh host, because this is in this setting the default gateway. The only thing, we need to do: Assign a unique IP address only for sshd and all other services, you wish to hide behind sslh and host on this device.
There are two ways, how you can add multiple ip addresses to one device. The new _**ip addr add**_ supports multiple add statements to one and the same interface name. So you can just duplicate the interface stancas in the _**/etc/network/interfaces**_ configuration. The problem with this method is, that some older managment tools, like ifconfig are unable to show the additional addresses. So when you are used to some older tools, you may configure sub-interfaces like eth0:1.
However my recommendation is, migrate to new tools, get used to it, as old tools don't show you the whole configuration!
## Example 3 ##
This is now the extended version of the previous example. The target host has another path back to the client, as there is a default route to another interface. Now we need **TWO** routing deflections, one on the sslh host, like in scenario 2, and one on the sshd target host.
The routing setup on the target host looks like:
* Add an routing table name for the deflection table in _**/etc/iproute2/rt_tables**_
* Find a location, where you will hook the two routing rules in
```
ip rule add from SSHD_ADDRESS/32 table sslh_routeback
ip route add default via SERVER1_ETH1_IP dev eth0 table sslh_routeback
```
This is setting up a default route for all traffic, originating from the ip address sshd (or any other service) is using, back to the host, hosting sslh. On that host, those packets will be deflected again with the same rule from scenario 2.
Be aware, that scenario 3 can look very different and the picture above shows only one of those setups. Each configuration, where packets from the target system can find their way back, without beeing forcibly routed through the sslh hosting system, belongs into this category. This are e.g. virtual machines or containers, having interfaces in the same network, like the sslh hosting system. Even, when they look in some drawings embedded in their host, as soon, as they have network interfaces, allowing a direct connection to the client, it is scenario 3!
## Modifications ##
Now you can think about many modifications, but the tools will be the same, for all other thinkable scenarios. You must always make sure, that packets from foreign hosts, will find their way back to the sslh host. So if the chain consists of three or four servers, all need the deflection rules.
## Important Finding On Routing ##
When I went ahead and wrote in my first drawings the warning, that the kernel in scenario 2 and 3 needs to have forwarding in place, I finally tested, that this is not true. **Both scenarios are working without kernel forwarding beeing activated!**
The background: The deflecting routing table cames into the game, before the kernel has to made the decision, that packets with non local ip addresses in source and destination must be forwarded. After the routing rule deliveres the packet to sslh and sslh rewrites the source ip, the packet is treated as local, and can pass the system.

View File

@ -0,0 +1,135 @@
# Transparent Proxy Configuration Using IP Routing#
This documentation is another explanation of the transparent proxy with the goal, beeing secure and minimalistic. Besides this documentation will explain, how and why this configuration works.
The explanation will only describe the connection to sshd, so the target sshd can be replace with any other target service, sslh supports.
## Introduction in the data flow ##
This chapter can be skipped, if you just like to configure things fast.
This chapter is a little excurse to the dataflow. First point of all is something, which you will unfortunately not see in the nice routing diagrams for iptables or netfilter-tables (nft) like: [Iptables at wikipedia](https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Netfilter-packet-flow.svg/2560px-Netfilter-packet-flow.svg.png), [Netfilter Flow](https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Netfilter_hooks_into_Linux_networking_packet_flows).
Packets from local application talking to other local applications are routed through the loopback-interface. They leave postrouting to lo and reentering from there prerouting, without passing ingress/egress.
This has nothing to do with the "**route_localnet = 1**" trick, which only makes, that the the local ip range 10.0.0.0/8 gets routed!
As you can read in many articles, this is nothing you should do, as you may bring your system at risk, because it allows to leak packets from outside to applications, which feel themselves secure, by using those unroutable addresses.
#### A Simple Simulation ####
You can prove this behaviour with a simple test:
```
# In one terminal start socat as a local echo server
# this is simulating sshd
socat TCP4-LISTEN:2000,bind=SERVER_IP,fork EXEC:cat
# In the next terminal start another instance of socat,
# simulating sslh
socat TCP-LISTEN:3000,bind=SERVER_IP,fork TCP:SERVER_IP:2000
# In another terminal you can watch the traffic on lo
tcpdump -i lo port 2000
# In the last terminal talk to the echo server
telnet SERVER_IP 2000
```
You will see your traffic on lo, but not on eth0, if you retry the tcpdump there.
If you setup sslh as non transparent proxy, it will just work, as what we have seen.
#### Going Transparent ####
In case of transparent proxy however, sslh uses some tricks, to reuse the clients IP on its outgoing interface to sshd. It opens the interface in raw mode, so it either needs to be started as root and drop privileges after binding, or you will need to give some capabilities to the sslh binary (cap_net_bind_service,cap_net_raw+ep), if you will start it as restricted user.
In this setup we continue, with dropping priviledges.
Doing so, you can send packets to the sshd, listening on another interface, but, the answer packets from sshd will get routed back to the client. This however will not work, as the client would refuse those packets, because they don't belong to a tcp session, the client opened. In most cases those packets would even not reach the client, as source ip addresses from private address space, will be blocked by most internet routers and connection providers.
So its mandatory, to use some tricks, to get those packets back to sslh. All configurations, I have seen so far, are using two components for that. They bind sshd to lo, and than they introduce some firewall rules, to mark packets, originating from the sshd port on lo, so that those packets can be routed in a next step -based on that marking- back to sslh.
##### Drawbacks From Using loopback #####
This idea has some serious drawbacks: First, you need to allow routing of the local address space, 127.0.0.0/8, with kernel configurations. Search for the string "net.ipv4.conf.default.route_localnet" and you will find lot of articles, why you should not do this.
By allowing this, you need additional firewall rules, dropping martian packets, which otherwise would get routed to the internet from other applications, running on lo, not aware, that their traffic could be routed. You need further firewall rules, blocking incoming packets to loopback addresses, as otherwise some applications (especially udp) could be the goal of some bad traffic.
##### Using A Dedicated Interface #####
So this configuration makes use of a own interface, just for the services, where sslh should hide the traffic for. We use a interface of the dummy kernel module, which was designed just for this case. It is an interface, beeing there, having no cable connection or whatsever, but applications can bind to it. We assign to this interface just a /32 private address, as this interface is not part of any network.
Doing so, we can avoid all the hassle with marking certain packets, coming from the single applications, sslh has to hide, as we now just route ALL traffic from this specific interface by its ip to sslh.
We need one routing rule and one routing table, this covers as many targets sslh will serve on this interface, without adding additional rules for adding apache, openvpn and others.
We need no firewall rules, preventing martians, as this single routing rule will deadroute all traffic from this interface, if sslh is not catching it up.
We only need firewall protection for this specific ip address, when we have activated ip forwarding on that system. If the system is no router and needs no forwarding, there is no protection needed.
## Finally The Configuration ##
As described, we need as a first step a dedicated interface, just for the services, sslh should hide. Its possible, to generate individual interfaces for different configurations, however, that makes things again more complex and has no advantages seen so far.
### Named Routing Table ###
As we configure the needed routing rules in the interface configuration, we need to define a name for the sslh routing table first.
Using named routing tables helps, understanding the routing configuration, as a name indicates, why this routing table is configured.
To do so go to _**/etc/iproute2/rt_tables**_ and add a line
```
111 sslh
```
With newer versions of iproute2 the /etc/iproute2 directory with the embedded templates got no longer installed. The cause maybe, that the example names, which were not used in any configuration, generated confusion. However, once you need those files, generate them and they will be honoured. You still can use just numbers for your routing table. But doing this, and having more than one routing table, you need a list, which numer belongs to which configuration.
And seeing in the output from `ip route list table all ` the tables names instead just numbers is worth creating the file.
### Dummy Interface ###
Now we configure our dedicated interface.
In the file _**/etc/network/interfaces**_, we place this entry:
```
auto dummy0
iface dummy0 inet static
address 192.168.255.254/32
pre-up modprobe dummy
## Attention! with kernels, not automatically creating a dummy0
## interface after module loading the following line should be:
## pre-up modprobe dummy; if [ ! -e /sys/class/net/dummy0 ]; then ip link add dummy0 type dummy ; fi
post-up ip rule add from 192.168.255.254 table sslh
post-up ip route add local 0.0.0.0/0 dev dummy0 table sslh
pre-down ip route del local 0.0.0.0/0 dev dummy0 table sslh
pre-down ip rule del from 192.168.255.254 table sslh
```
As long, as your system has no other interfaces with private address-space, or is routing such addresses, you can continue with the given example. Otherwise you need to select a conflict free address.
If you are updating a older current configuration, make sure, that you have no longer insecure localnet routing in place:
```
sysctl net.ipv4.conf.default.route_localnet
sysctl net.ipv4.conf.all.route_localnet
```
should both report "0"!
### Explanation Of The Routing Rules ###
The two routing rules in the dummy0 interface configuration are the key for this configuration.
The first line is an routing rule entry, routing everything coming from the dummy0 ip source address to a special routing table _**sslh**_.
The next line generates this table implicitly, by inserting a single rule, routing everything from that ip address to dummy0.
Opposite to other firewall based configurations, we have those rules now tied to the dummy0 device, dedicated to the hidden services.
When this interface comes up, the routing rules are making sure, that no martian packets can leave the system, by some processes using this IP address. When the interface goes down, we delete those rules.
Also the startup script needs lo longer special treatment for the transparent mode.
#### SSLH Default Configuration ####
And finally you need to configute _**/etc/default/sslh**_ with the right settings for all the services, sslh should work for.
```
DAEMON_OPTS="--user sslh --listen SERVER_IP:443 --transparent \
--ssh 192.168.255.254:22 --tls 192.168.255.254:443 \
--pidfile /var/run/sslh/sslh.pid"
```
#### Systemd ####
This setup is now startup agnostic. As we don't need special treatment in the startup script, the sysV based init scripts will just work, like the systemd scripts. Nothing needs to be modified, when going transparent.
#### Remote Setups ####
This concept can also be adapted for several setups, where the sshd (or any other target service) is running in a container, kvm-virtual machine, etc.
Precondition is, that the target system is the next hop and uses the sslh-hosting system as default gateway. In addition you need to bind an additional ip-address, solely used for sshd on the corresponding interface.
Than you can adapt the routing rule, routing traffic coming back from this ip to the sslh-routing-table.
Its also possible, to forward to an next hop system, which has its own default gateway back, bypassing the sslh-host.
In this case, you need to add a special route back to the sslh host, for all traffic with the sshd source ip address. This can be done similar to the two rules described above:
```
# first define a name for the table in /etc/iproute2/rt_tables e.g. sslh-routeback
ip rule add from IPADRESS-OF-SERVIE table sslh-routeback
ip route add default via IPADDRESS-OF_SSLH-HOST dev eth0 table sslh-routeback
```
The details are depending on your network settings. Als long, as the forward chain to the hidden service passes systems under your control, you can add backroutes on each system in that route. Precondition: The used ip address produces no conflict on those systems.
[I added a second document](./scenarios-for-simple-transparent-proxy.md), describing three possible scenarios in detail. Those three scenarios should cover all setups related to transparent proxying.

BIN
doc/sslh-examples-v3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

4017
doc/sslh-examples-v3.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 158 KiB

426
doc/tproxy.md Normal file
View File

@ -0,0 +1,426 @@
# Transparent proxy using packet marking
Before reading further, make sure you try to set up
transparent proxying using [IP routing](doc/simple_transparent_proxy.md).
It is conceptually easier to understand, cleaner, and more
portable.
Using this method is very tricky and
detail-dependant: depending on whether the target server and
sslh are on the same machine, different machines, or
different dockers, and tool versions, all seem to change the
required network configuration somewhat. If it doesn't work,
it's almost certain that the problem is not linked to `sslh`
but to the network setup that surrounds it. If in trouble,
it might be worth trying to set up the network rules
with a simpler server than `sslh`, such as
[`socat`](http://www.dest-unreach.org/socat/)
Users have tried to do at least the following:
* `sslh` and the target servers run on the same host (see [below](#transparent-proxy-to-one-host))
* `sslh` runs on a host, and the target servers run on LXC or dockers running on the same host. No known working setup.
* `sslh` runs on a host, and the target servers run on different hosts on the same local network(see [below](#transparent-proxy-to-two-hosts))
* `sslh` runs on a host, and the target servers run on a different host on a different network (there is a [usecase](https://github.com/yrutschle/sslh/issues/295) for this). No known working setup, and it's unclear it is possible.
## Transparent proxy to one host
### Linux
`sslh` needs extended rights to perform this: you'll need to
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
or run it as root (but don't do that).
The firewalling tables also need to be adjusted as follows.
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
this scheme -- let me know if you manage that:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
Transparent proxying with IPv6 is similarly set up as follows:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
# Not sure if this is needed for ipv6 though
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
Explanation:
To be able to use `localhost` as destination in your sslh config along with transparent proxying
you have to allow routing of loopback addresses as done above.
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
showing how packets will traverse the iptables chains.
Note:
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
These rules will allow you to connect directly to ssh on port
22 (or to any other service behind sslh) as well as through sslh on port 443.
Also remember that iptables configuration and ip routes and
rules won't be necessarily persisted after you reboot. Make
sure to save them properly. For example in CentOS7, you would
do `iptables-save > /etc/sysconfig/iptables`, and add both
`ip` commands to your `/etc/rc.local`.
### FreeBSD
Given you have no firewall defined yet, you can use the following configuration
to have ipfw properly redirect traffic back to sslh
/etc/rc.conf
firewall_enable="YES"
firewall_type="open"
firewall_logif="YES"
firewall_coscripts="/etc/ipfw/sslh.rules"
/etc/ipfw/sslh.rules
#! /bin/sh
# ssl
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
# ssh
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
# xmpp
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
# openvpn (running on other internal system)
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
General notes:
This will only work if `sslh` does not use any loopback
addresses (no `127.0.0.1` or `localhost`), you'll need to use
explicit IP addresses (or names):
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
## Transparent Proxy to Two Hosts
Tutorial by Sean Warner. 19 June 2019 20:35
### 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
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)
* Pi B: 192.168.1.123 - HTTP server (port 8000), SSH server (port 1022 on PiB).
* I send traffic from the internet to my router's external port 443 then use a port forward rule in my router to map that to internal port 4433 where sslh is listening.
![Architecture](tproxy.svg)
### `sslh` build
 
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
I compiled sslh from sources giving the binary pretty much all possible options such as Posix capabilities and systemd support.. here are the first few lines of the makefile:
 
```
# Configuration
VERSION=$(shell ./genver.sh -r)
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=1         # Use libwrap?
USELIBCAP=1         # Use libcap?
USESYSTEMD=1         # Make use of systemd socket activation
COV_TEST=         # Perform test coverage?
PREFIX=/usr/local
BINDIR=$(PREFIX)/sbin
MANDIR=$(PREFIX)/share/man/man8
MAN=sslh.8.gz         # man page name
# End of configuration -- the rest should take care of
# itself
```
 
### systemd setup
Create an sslh systemd service file...
```
# nano /lib/systemd/system/sslh.service
```
Paste in this contents…
```
[Unit]
Description=SSL/SSH multiplexer
After=network.target
Documentation=man:sslh(8)
[Service]
#EnvironmentFile=/etc/default/sslh
#ExecStart=/usr/local/sbin/sslh $DAEMON_OPTS
ExecStart=/usr/local/sbin/sslh -F /etc/sslh/sslh.cfg
KillMode=process
[Install]
WantedBy=multi-user.target
```
Save it and then…
```
# systemctl daemon-reload
```
Start it again to test…
```
# systemctl start sslh
```
 
### Configure `sslh`
First stop `sslh` then open the config file and replace with below, save and start `sslh` again
```
# systemctl stop sslh
# nano /etc/sslh/sslh.cfg
# systemctl start sslh
```
```
verbose: true;
foreground: true;
inetd: false;
numeric: true;
transparent: true;
timeout: 2;
user: "sslh";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# You must have a port forward rule in the router: external port 443 <-> internal port 4433
# Local ip address of PiA is: 192.168.1.124, sslh and stunnel4 are running on this Pi
# Local ip address of PiB is: 192.168.1.123, http server and ssh server on this Pi
listen:
(
{ host: "192.168.1.124"; port: "4433"; }
);
# sslh demultiplexes based on the Protocol and Hostname
protocols:
(
{ name: "tls"; sni_hostnames: [ "www.example.com" ]; host: "192.168.1.124"; port: "443"; log_level: 1; },
# This probe is for tls encrypted ssh. SSLH forwards it to stunnel on port 4480 which decrypts it and sends it to the ssh server on PiA port 1022
{ name: "tls"; sni_hostnames: [ "ssh.example.com" ]; host: "192.168.1.124"; port: "4480"; log_level: 1; },
{ name: "http"; host: "192.168.1.123"; port: "8000"; log_level: 1; },
{ name: "ssh"; host: "192.168.1.123"; port: "1022"; log_level: 1; }
);
```
 
### Configure `stunnel`
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
```
# systemctl stop stunnel4
# nano /etc/stunnel/stunnel.conf
# systemctl start stunnel4
```
```
# Debugging stuff (may be useful for troubleshooting)
foreground = yes
#debug = 5 # this is the default
debug = 7
output = /var/log/stunnel4/stunnel.log
pid = /var/run/stunnel4/stunnel.pid
fips = no
cert = /etc/letsencrypt/live/example.com/fullchain.pem
key = /etc/letsencrypt/live/example.com/privkey.pem
[ssh]
accept = 192.168.1.124:4480
connect = 192.168.1.124:1022
TIMEOUTclose = 0
```
 
### 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..
```
# nano /usr/local/sbin/piA_tproxy_add.sh
```
``` piA_tproxy_add.sh
iptables -t mangle -N SSLH
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH
iptables -t mangle -A SSLH --jump MARK --set-mark 0x1
iptables -t mangle -A SSLH --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
```
```
# nano /usr/local/sbin/piA_tproxy_rm.sh
```
``` piA_tproxy_rm.sh
iptables -t mangle -D PREROUTING -p tcp -m socket --transparent -j SSLH
iptables -t mangle -D OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH
iptables -t mangle -D SSLH --jump MARK --set-mark 0x1
iptables -t mangle -D SSLH --jump ACCEPT
iptables -t mangle -X SSLH
ip rule del fwmark 0x1 lookup 100
ip route del local 0.0.0.0/0 dev lo table 100
```
Make them executable..
```
# chmod +rx piA_tproxy_add.sh
# chmod +rx piA_tproxy_rm.sh
```
Now run the "add" script on Pi A!
```
# piA_tproxy_add.sh
# piA_tproxy_rm.sh
```
## Configure iptables for Pi B
```
# nano /usr/local/sbin/piB_tproxy_add.sh
```
``` piB_tproxy_add.sh
iptables -t mangle -N SSLHSSL
iptables -t mangle -A OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL
iptables -t mangle -A SSLHSSL --jump MARK --set-mark 0x1
iptables -t mangle -A SSLHSSL --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add default via 192.168.1.124 table 100
ip route flush cache
```
```
# nano /usr/local/sbin/piB_tproxy_rm.sh
```
```
iptables -t mangle -D OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL
iptables -t mangle -D SSLHSSL --jump MARK --set-mark 0x1
iptables -t mangle -D SSLHSSL --jump ACCEPT
iptables -t mangle -X SSLHSSL
ip rule del fwmark 0x1 lookup 100
ip route del default via 192.168.1.124 table 100
ip route flush cache
```
Make them executable..
```
# chmod +rx piB_tproxy_add.sh
# chmod +rx piB_tproxy_rm.sh
```
Now run the "add" script on Pi B!
```
# piB_tproxy_add.sh
# piB_tproxy_rm.sh
```
 
### Testing
* Getting to sshd on PiA
I did this test using 4G from my phone (outside the LAN)
To simulate this I use `proxytunnel`. External port 443 is forwarded by my router to 4433. I need to arrive at `sslh` (port 4433) with ssh encrypted as TLS (hence I use the -e switch) and the `sni_hostname` set to ssh.example.com so that `sslh` will demultiplex to `stunnel` (port 4480) which will decrypt and forward to ssh server on PiA… see `sslh.cfg` and `stunnel.conf`.
The first IP:port is just a free HTTPS proxy I found on https://free-proxy-list.net
I execute this command from a terminal window..
```
# proxytunnel -v -e -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443
```
* Getting to sshd on PiB
I did this test using 4G from my phone (outside the LAN)
My smartphone telecom provider blocks ssh over port 443 so I need to use `proxytunnel` to encrypt.
Use the Proxytunnel `-X` switch to encrypt from local proxy to destination only so by the time we get to the destination it is unencrypted and `sslh` will see the ssh protocol and demultiplex to PiB as per `sslh.cfg`.
```
# proxytunnel -v -X -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443
```
Now when you test it all look at the output in daemon.log like this:
```
# grep -i 'ssl' /var/log/daemon.log
```
You should see that the IP address and port from the “connection from” and “forwarded from” fields are the same.
Special thanks and appreciation to Michael Yelsukov without whom I would never have got this working.
Any feedback or corrections very welcome!

635
doc/tproxy.svg Normal file
View File

@ -0,0 +1,635 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="297mm"
height="210mm"
viewBox="0 0 297 210"
version="1.1"
id="svg288"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Transparent Proxy to Two Hosts-new.svg">
<defs
id="defs282">
<linearGradient
id="linearGradient4840"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4838" />
</linearGradient>
<linearGradient
id="linearGradient4802"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4800" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.01"
inkscape:cx="511.12411"
inkscape:cy="388.13656"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-page="true"
inkscape:window-width="1920"
inkscape:window-height="1005"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata285">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-87)">
<rect
style="fill:#000000;fill-opacity:0;stroke:#fa9600;stroke-width:0.68479234;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4977-0"
width="37.333149"
height="17.888765"
x="234.24397"
y="232.81883" />
<ellipse
id="path290"
cx="38.405174"
cy="133.55173"
rx="21.853449"
ry="28.965515"
style="stroke-width:0.26458332;fill-opacity:1;fill:none" />
<ellipse
style="fill:none;stroke-width:0.26458332"
id="path4806"
cx="44.482758"
cy="135.49138"
rx="28.965515"
ry="27.025862" />
<ellipse
style="fill:none;stroke-width:0.26458332"
id="path4808"
cx="124.26724"
cy="151.26724"
rx="29.612068"
ry="24.698277" />
<ellipse
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.76499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4812"
ry="30.796005"
rx="24.16864"
cy="140.54999"
cx="43.31974" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.59869254;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4852"
width="27.302826"
height="13.554869"
x="29.797318"
y="133.28833" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:6.6431241px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.31139645"
x="38.145908"
y="130.0871"
id="text138"
transform="scale(0.9172844,1.0901744)"><tspan
sodipodi:role="line"
x="38.145908"
y="130.0871"
id="tspan136"
style="stroke-width:0.31139645"><tspan
x="38.145908"
y="130.0871"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.31139645"
id="tspan134">Client</tspan></tspan></text>
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4862"
width="20.232847"
height="37.612331"
x="101.42361"
y="121.64869" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.67072004;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4864"
width="121.91585"
height="91.301468"
x="156.41544"
y="116.72592" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4866"
width="122.95342"
height="37.871742"
x="155.89668"
y="223.04117" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#f40000;stroke-width:0.701;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4868"
width="82.747147"
height="23.645355"
x="175.0919"
y="128.09322" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4870-7"
width="26.977121"
height="14.785542"
x="220.4861"
y="185.84906" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4870-7-1"
width="26.977123"
height="14.785542"
x="184.17078"
y="185.84906" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4919"
width="27.495911"
height="11.413391"
x="219.96732"
y="164.44894" />
<rect
style="fill:none;fill-opacity:0;stroke:#800080;stroke-width:0.85740757;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.96517412"
id="rect4921"
width="139.8217"
height="161.06387"
x="148.11099"
y="108.15634" />
<path
style="fill:#000000;stroke:#c98f26;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.88627452;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:0"
d="m 57.326389,140.58456 c 43.837821,-0.25939 44.097221,-0.25939 44.097221,-0.25939"
id="path4925"
inkscape:connector-curvature="0" />
<path
style="fill:#000000;fill-opacity:0;stroke:#c98f26;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.88627451"
d="m 122.01937,140.18462 c 53.1252,-0.25845 53.43955,-0.25845 53.43955,-0.25845"
id="path4925-7"
inkscape:connector-curvature="0" />
<path
style="fill:#000000;fill-opacity:0;stroke:#c98f26;stroke-width:0.58399999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.75200005, 0.58400002;stroke-dashoffset:0;stroke-opacity:0.88627451"
d="m 101.35344,140.71552 c 19.77892,-0.26191 19.89596,-0.26191 19.89596,-0.26191"
id="path4925-4"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 198.1781,185.71937 c -0.25939,-33.9808 -0.25939,-33.9808 -0.25939,-33.9808"
id="path4971"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 233.45588,164.18955 c 0,-12.45098 0,-12.45098 0,-12.45098"
id="path4973"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 233.19648,185.45997 c 0,-9.59763 0,-9.59763 0,-9.59763"
id="path4975"
inkscape:connector-curvature="0" />
<rect
style="fill:none;fill-opacity:0;stroke:#fa9600;stroke-width:0.665;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4977"
width="37.35294"
height="16.860703"
x="161.2299"
y="233.1991" />
<path
style="fill:none;stroke:#fa9600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.60784316;stroke-miterlimit:4;stroke-dasharray:none"
d="m 178.79609,151.73857 c 0.25941,81.45016 0.25941,81.45016 0.25941,81.45016"
id="path4994"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#fa9600;stroke-width:0.66078299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.60784316"
d="m 253.45564,151.97084 c 0.25959,80.36694 0.25959,80.36694 0.25959,80.36694"
id="path4994-0"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="32.424427"
y="103.23161"
id="text5013"><tspan
sodipodi:role="line"
id="tspan5011"
x="32.424427"
y="103.23161"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.29166698px;line-height:0;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.26458332">Internet</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="102.3172"
y="116.44047"
id="text5013-6"><tspan
sodipodi:role="line"
id="tspan5011-2"
x="102.3172"
y="116.44047"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.29166698px;line-height:0;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.26458332">Router</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#800080;fill-opacity:0.96470588;stroke:none;stroke-width:0.26458332"
x="188.76727"
y="105.30679"
id="text5053"><tspan
sodipodi:role="line"
id="tspan5051"
x="188.76727"
y="105.30679"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#800080;fill-opacity:0.96470588;stroke:none;stroke-width:0.26458332;stroke-opacity:0.96517412">Local Area Network</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
x="92.344772"
y="138.5094"
id="text5057"><tspan
sodipodi:role="line"
id="tspan5055"
x="92.344772"
y="138.5094"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
x="92.414749"
y="138.5529"
id="text5057-0"><tspan
sodipodi:role="line"
id="tspan5055-6"
x="92.414749"
y="138.5529"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
x="92.414749"
y="138.5529"
id="text5057-0-8"><tspan
sodipodi:role="line"
id="tspan5055-6-2"
x="92.414749"
y="138.5529"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.3396225px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.28349054"
x="151.81532"
y="147.89809"
id="text5057-0-8-6"
transform="scale(1.0714604,0.93330561)"><tspan
sodipodi:role="line"
id="tspan5055-6-2-7"
x="151.81532"
y="147.89809"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.28349054">4433</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.33962345px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.28349057"
x="114.9891"
y="148.08023"
id="text5057-0-8-6-4"
transform="scale(1.0714604,0.93330561)"><tspan
sodipodi:role="line"
id="tspan5055-6-2-7-3"
x="114.9891"
y="148.08023"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.28349057">4433</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="208.55414"
y="135.19525"
id="text144"><tspan
sodipodi:role="line"
x="208.55414"
y="135.19525"
id="tspan142"
style="stroke-width:0.26458332"><tspan
x="208.55414"
y="135.19525"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444447px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#f40000;fill-opacity:1;stroke-width:0.26458332"
id="tspan140">SSLH</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="158.75"
y="121.48432"
id="text5159"><tspan
sodipodi:role="line"
id="tspan5157"
x="158.75"
y="121.48432"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.26458332;font-size:3.88055556px">192.168.1.124 (Pi A)</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="157.30107"
y="258.37653"
id="text5159-0"><tspan
sodipodi:role="line"
id="tspan5157-1"
x="157.30107"
y="258.37653"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.26458332">192.168.1.123 (Pi B)</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fa9600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.25511"
y="242.15076"
id="text168"><tspan
sodipodi:role="line"
x="167.25511"
y="242.15076"
id="tspan166"
style="stroke-width:0.26458332"><tspan
x="167.25511"
y="242.15076"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan164">Web Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fa9600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="239.92085"
y="241.98419"
id="text174"><tspan
sodipodi:role="line"
x="239.92085"
y="241.98419"
id="tspan172"
style="stroke-width:0.26458332"><tspan
x="239.92085"
y="241.98419"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan170">SSH Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.9187"
y="190.6398"
id="text186"><tspan
sodipodi:role="line"
x="167.9187"
y="190.6398"
id="tspan184"
style="stroke-width:0.26458332"><tspan
x="167.9187"
y="190.6398"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan182">http</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.48399"
y="157.93468"
id="text180"><tspan
sodipodi:role="line"
x="167.48399"
y="157.93468"
id="tspan178"
style="stroke-width:0.26458332"><tspan
x="167.48399"
y="157.93468"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan176">8000</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.74596"
y="229.97469"
id="text192"><tspan
sodipodi:role="line"
x="167.74596"
y="229.97469"
id="tspan190"
style="stroke-width:0.26458332"><tspan
x="167.74596"
y="229.97469"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan188">8000</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="255.3237"
y="190.91431"
id="text246"><tspan
sodipodi:role="line"
x="255.3237"
y="190.91431"
id="tspan244"
style="stroke-width:0.26458332"><tspan
x="255.3237"
y="190.91431"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan242">ssh</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="254.88899"
y="158.20918"
id="text240"><tspan
sodipodi:role="line"
x="254.88899"
y="158.20918"
id="tspan238"
style="stroke-width:0.26458332"><tspan
x="254.88899"
y="158.20918"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan236">1022</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="255.15096"
y="230.24921"
id="text252"><tspan
sodipodi:role="line"
x="255.15096"
y="230.24921"
id="tspan250"
style="stroke-width:0.26458332"><tspan
x="255.15096"
y="230.24921"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan248">1022</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
x="186.26637"
y="193.70544"
id="text156"><tspan
sodipodi:role="line"
x="186.26637"
y="193.70544"
id="tspan154"
style="stroke-width:0.26458332"><tspan
x="186.26637"
y="193.70544"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="tspan152">Web Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
x="225.25404"
y="170.88316"
id="text150"><tspan
sodipodi:role="line"
x="225.25404"
y="170.88316"
id="tspan148"
style="stroke-width:0.26458332"><tspan
x="225.25404"
y="170.88316"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="tspan146">STUNNEL</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
x="222.6344"
y="194.19792"
id="text162"><tspan
sodipodi:role="line"
x="222.6344"
y="194.19792"
id="tspan160"
style="stroke-width:0.26458332"><tspan
x="222.6344"
y="194.19792"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="tspan158">SSH Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="199.15128"
y="157.29231"
id="text198"><tspan
sodipodi:role="line"
x="199.15128"
y="157.29231"
id="tspan196"
style="stroke-width:0.26458332"><tspan
x="199.15128"
y="157.29231"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan194">443</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="199.41324"
y="182.17886"
id="text210"><tspan
sodipodi:role="line"
x="199.41324"
y="182.17886"
id="tspan208"
style="stroke-width:0.26458332"><tspan
x="199.41324"
y="182.17886"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan206">443</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="199.15128"
y="170.65247"
id="text204"><tspan
sodipodi:role="line"
x="199.15128"
y="170.65247"
id="tspan202"
style="stroke-width:0.26458332"><tspan
x="199.15128"
y="170.65247"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan200">tls</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="223.09605"
y="158.60213"
id="text216"><tspan
sodipodi:role="line"
x="223.09605"
y="158.60213"
id="tspan214"
style="stroke-width:0.26458332"><tspan
x="223.09605"
y="158.60213"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan212">4480</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="235.14638"
y="158.60213"
id="text222"><tspan
sodipodi:role="line"
x="235.14638"
y="158.60213"
id="tspan220"
style="stroke-width:0.26458332"><tspan
x="235.14638"
y="158.60213"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan218">tls</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="222.57213"
y="182.44083"
id="text228"><tspan
sodipodi:role="line"
x="222.57213"
y="182.44083"
id="tspan226"
style="stroke-width:0.26458332"><tspan
x="222.57213"
y="182.44083"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan224">1022</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="234.88441"
y="182.44083"
id="text234"><tspan
sodipodi:role="line"
x="234.88441"
y="182.44083"
id="tspan232"
style="stroke-width:0.26458332"><tspan
x="234.88441"
y="182.44083"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan230">ssh</tspan></tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

12
echo_test.cfg Normal file
View File

@ -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";

1330
echosrv-conf.c Normal file

File diff suppressed because it is too large Load Diff

66
echosrv-conf.h Normal file
View File

@ -0,0 +1,66 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:59 2025.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 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

330
echosrv.c
View File

@ -1,6 +1,6 @@
/* echosrv: a simple line echo server with optional prefix adding.
*
* echsrv --listen localhost6:1234 --prefix "ssl: "
* echosrv --listen localhost6:1234 --prefix "ssl: "
*
* This will bind to 1234, and echo every line pre-pending "ssl: ". This is
* used for testing: we create several such servers with different prefixes,
@ -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,80 +41,53 @@
#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;
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);
}
}
void start_echo(int fd)
{
int res;
ssize_t res;
char buffer[1 << 20];
int ret, prefix_len;
ssize_t ret;
size_t prefix_len;
int first = 1;
prefix_len = strlen(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));
if (ret == -1) {
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
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;
@ -117,30 +95,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);
ssize_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, "%zd %s\n", len, data + prefix_len);
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
ssize_t 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[])
{
@ -148,11 +328,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
echosrv.cfg Normal file
View File

@ -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
echoѕrv-conf.h Normal file
View File

@ -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

View File

@ -3,7 +3,6 @@
# not be used as a starting point for a working
# configuration. Instead use basic.cfg.
verbose: true;
foreground: true;
inetd: false;
numeric: false;
@ -13,10 +12,39 @@ user: "nobody";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
# Defaults are indicated here, and should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
# Each option relates to a different set of messages.
verbose-config: 0; # print configuration at startup
verbose-config-error: 3; # print configuration errors
verbose-connections: 3; # trace established incoming address to forward address
verbose-connections-error: 3; # connection errors
verbose-connections-try: 0; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-packets: 0; # hexdump packets on which probing is done
verbose-probe-info: 0; # what's happening during the probe process
verbose-probe-error: 3; # failures and problems during probing
verbose-system-error: 3; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 3; # internal errors, the kind that should never happen
# This one is special and overrides all previous options if
# set, as a quick way to get "as much as possible"
#verbose: 3;
# Specify a path to the logfile.
#logfile: "/var/log/sslh.log"
# Specify the number of concurrent UDP connection that can
# be managed (default 1024)
udp_max_connections: 16;
# Specify which syslog facility to use (names for your
# system are usually defined in /usr/include/*/sys/syslog.h
# or equivalent)
# Default is "auth"
# "none" disables use of syslog
syslog_facility: "auth";
# List of interfaces on which we should listen
@ -24,7 +52,9 @@ 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"; },
{ host: "/tmp/unix_socket"; is_unix: true; port: ""; }
);
# List of protocols
@ -41,53 +71,92 @@ listen:
# connection (default is off)
# fork: Should a new process be forked for this protocol?
# (only useful for sslh-select)
# tfo_ok: Set to true if the server supports TCP FAST OPEN
# resolve_on_forward: Set to true if server address should be resolved on
# (every) newly incoming connection (again)
# transparent: Set to true to proxy this protocol
# transparently (server sees the remote client IP
# address). Same as the global option, but per-protocol
# is_unix: [true|false] connect to a UNIX socket. The host
# field becomes the pathname to the socket, and the port
# field is unused (but necessary).
# proxyprotocol: <1|2>; When connecting to the backend
# server, a proxyprotocol header of the specified
# version will be added, containing the client's
# connection information.
#
# Probe-specific options:
# (sslh will try each probe in order they are declared, and
# connect to the first that matches.)
#
# tls:
# sni_hostnames: list of FQDN for that target
# sni_hostnames: list of FQDN for that target. Each name can
# include wildcard following glob(7) rules.
# alpn_protocols: list of ALPN protocols for that target, see:
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
#
# if both sni_hostnames AND alpn_protocols are specified, both must match
#
# if neither are set, it is just checked whether this is the TLS protocol or not
#
# Obviously set the most specific probes
# first, and if you use TLS with no ALPN/SNI
# set it as the last TLS probe
# regex:
# regex_patterns: list of patterns to match for
# that target.
#
# sslh will try each probe in order they are declared, and
# connect to the first that matches.
#
# You can specify several of 'regex' and 'tls'.
#
# If you want to filter on incoming IP addresses, you can
# use libwrap which will use /etc/hosts.allow and
# /etc/hosts.deny.
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; keepalive: true; fork: true; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22";
keepalive: true; fork: true; tfo_ok: true },
# UNIX socket to a local NGINX. The socket name is in 'host'; 'port' is necessary but not used.
{ name: "http"; is_unix: true; host: "/tmp/nginx.sock"; port: ""; },
# match BOTH ALPN/SNI
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0;},
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true },
# just match ALPN
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0;},
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; tfo_ok: true },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0; tfo_ok: true },
# just match SNI
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;},
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; tfo_ok: true },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0; tfo_ok: true },
# Let's Encrypt (tls-sni-* challenges)
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; sni_hostnames: [ "*.*.acme.invalid" ]; log_level: 0;},
# Let's Encrypt (tls-alpn-* challenges)
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; alpn_protocols: [ "acme-tls/1" ]; log_level: 0;},
# catch anything else TLS
{ name: "tls"; host: "localhost"; port: "443"; },
{ 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" ]; },
# Jabber
{ name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ]; },
{ name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ];
minlength: 60; # Won't even try to match the regex if we don't have that many bytes
},
# Catch-all
# Catch-all (but better use 'anyprot')
{ name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; },
# Where to connect in case of timeout (defaults to ssh)

111
gap.c Normal file
View File

@ -0,0 +1,111 @@
/*
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) {
free(gap);
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 - 1; i++) {
gap->array[i] = gap->array[i+1];
}
return 0;
}

43
gap.h Normal file
View File

@ -0,0 +1,43 @@
#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)
{
/* sslh-ev routinely reads before it writes. It's not clear if it should be
* its job to check the length (and add a gap_getlen()), or if it should be
* gap_get()'s job. This will do for now */
if (index >= gap->len) return NULL;
return gap->array[index];
}
#endif

View File

@ -7,17 +7,17 @@ else
QUIET=0
fi
if ! `(git status | grep -q "On branch") 2> /dev/null`; then
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
# If we don't have git, we can't work out what
# version this is. It must have been downloaded as a
# zip file.
# zip file.
# If downloaded from the release page, the directory
# has the version number.
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
if [ "x$release" = "x" ]; then
# If downloaded from the head, Github creates the
# If downloaded from the head, GitHub creates the
# zip file with all files dated from the last
# change: use the Makefile's modification time as a
# release number
@ -25,10 +25,10 @@ if ! `(git status | grep -q "On branch") 2> /dev/null`; then
fi
fi
if head=`git rev-parse --verify HEAD 2>/dev/null`; then
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
# generate the version info based on the tag
release=`(git describe --tags || git --describe || git describe --all --long) \
2>/dev/null | tr -d '\n'`
2>/dev/null | tr -s '/' '-' | tr -d '\n'`
# Are there uncommitted changes?
git update-index --refresh --unmerged > /dev/null

224
hash.c Normal file
View File

@ -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

28
hash.h Normal file
View File

@ -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

7
hashtest/Makefile Normal file
View File

@ -0,0 +1,7 @@
CFLAGS=-DHASH_TESTING -O2 -Wall
OBJ=../hash.o ../gap.o htest.o
htest: $(OBJ)
$(CC) -o htest $(OBJ)

8
hashtest/delete.tst Normal file
View File

@ -0,0 +1,8 @@
# Basic delete
a 10 aa
a 10 ab
a 10 ac
a 20 ba
a 21 bb
d 21 bb

34
hashtest/delete.tst.ref Normal file
View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

11
hashtest/delete_empty.tst Normal file
View File

@ -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

View File

@ -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>

39
hashtest/delete_full.tst Normal file
View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

8
hashtest/delete_wrap.tst Normal file
View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

BIN
hashtest/htest Executable file

Binary file not shown.

109
hashtest/htest.c Normal file
View File

@ -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;
}

6
hashtest/insert.tst Normal file
View File

@ -0,0 +1,6 @@
# Basic insertions
a 10 aa
a 10 ab
a 10 ac
a 20 ba
a 21 bb

34
hashtest/insert.tst.ref Normal file
View File

@ -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>

View File

@ -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

View File

@ -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>

40
hashtest/insert_full.tst Normal file
View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

7
hashtest/insert_wrap.tst Normal file
View File

@ -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

View File

@ -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>

41
hashtest/mkrand.pl Executable file
View File

@ -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;
}
}
}

30
hashtest/run Executable file
View File

@ -0,0 +1,30 @@
#! /usr/bin/perl -w
# This runs all the tests.
# Tests scripts are in *.tst files.
# Corresponding output is put in *.out.
# Reference output is put in *.ref.
# Any discrepancy will be reported!
use strict;
my @res;
foreach my $fn (`ls *.tst`) {
chomp $fn;
my $cmd = "./htest $fn $fn.out";
print "$cmd\n";
`$cmd`;
my $res = system("diff -u $fn.ref $fn.out");
push @res, [$fn, ($res == 0 ? "OK" : "*KO*")];
}
format =
@<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>
$_->[0], $_->[1]
.
#format_name STDOUT test_result;
map { write; } @res;

186
landlock.c Normal file
View File

@ -0,0 +1,186 @@
/*
* Setup a sandbox using the Landlock LSM, if available.
# Copyright (C) 2023 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
#
*/
#include "config.h"
#include "log.h"
#ifdef HAVE_LANDLOCK
#define _GNU_SOURCE
#include <linux/landlock.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
/* Ubuntu 22.04 does not have this symbol */
#ifndef LANDLOCK_ACCESS_FS_REFER
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
#endif
#ifndef landlock_create_ruleset
static inline int
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
const size_t size, const __u32 flags)
{
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
}
#endif
#ifndef landlock_add_rule
static inline int landlock_add_rule(const int ruleset_fd,
const enum landlock_rule_type rule_type,
const void *const rule_attr,
const __u32 flags)
{
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
flags);
}
#endif
#ifndef landlock_restrict_self
static inline int landlock_restrict_self(const int ruleset_fd,
const __u32 flags)
{
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
}
#endif
typedef enum {
LL_TREE,
LL_FILE
} ll_obj_type;
static int add_path_ro(int ruleset_fd, ll_obj_type otype, const char* path)
{
int fd = open(path, O_PATH | O_CLOEXEC);
if (fd < 0) {
print_message(msg_config_error, "Landlock: Failed to open %s: %s\n", path, strerror(errno));
return -1;
}
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = (otype == LL_TREE ? LANDLOCK_ACCESS_FS_READ_DIR : 0 ) |
LANDLOCK_ACCESS_FS_READ_FILE,
.parent_fd = fd,
};
int res = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
if (res) {
print_message(msg_config_error, "Landlock: Failed to update the ruleset with \"%s\": %s\n",
path, strerror(errno));
close(path_beneath.parent_fd);
return -1;
}
// close helper handle
close(fd);
return 0;
}
static int add_libs(int ruleset_fd)
{
/* Access to libraries, to be able to fork */
add_path_ro(ruleset_fd, LL_TREE, "/lib");
add_path_ro(ruleset_fd, LL_TREE, "/usr/lib");
add_path_ro(ruleset_fd, LL_FILE, "/etc/ld.so.cache"); /* To avoid searching all libs... */
return 0;
}
static int add_resolv(int ruleset_fd)
{
/* Files to resolve names (required when dynamic resolution is used) */
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts");
add_path_ro(ruleset_fd, LL_FILE, "/etc/resolv.conf");
add_path_ro(ruleset_fd, LL_FILE, "/etc/nsswitch.conf");
return 0;
}
static int add_libwrap(int ruleset_fd)
{
/* Files for libwrap */
#ifdef LIBWRAP
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
#endif
return 0;
}
void setup_landlock(void)
{
__u64 restrict_rules =
LANDLOCK_ACCESS_FS_EXECUTE |
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_REMOVE_DIR |
LANDLOCK_ACCESS_FS_REMOVE_FILE |
LANDLOCK_ACCESS_FS_MAKE_CHAR |
LANDLOCK_ACCESS_FS_MAKE_DIR |
LANDLOCK_ACCESS_FS_MAKE_REG |
LANDLOCK_ACCESS_FS_MAKE_SOCK |
LANDLOCK_ACCESS_FS_MAKE_FIFO |
LANDLOCK_ACCESS_FS_MAKE_BLOCK |
LANDLOCK_ACCESS_FS_MAKE_SYM |
LANDLOCK_ACCESS_FS_REFER;
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = restrict_rules
};
/* ruleset_addr.handled_access_fs contains all rights that will be restricted
* unless explicitly added */
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
print_message(msg_config_error, "Landlock: Failed to create a ruleset");
return;
}
/* Add all the paths we need */
add_libs(ruleset_fd);
add_resolv(ruleset_fd);
add_libwrap(ruleset_fd);
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
print_message(msg_config_error, "Landlock: Failed to restrict privileges");
return;
}
if (landlock_restrict_self(ruleset_fd, 0)) {
print_message(msg_config_error, "Landlock: Failed to enforce ruleset");
return;
}
close(ruleset_fd);
print_message(msg_config, "Landlock: all restricted\n");
}
#else /* HAVE_LANDLOCK */
void setup_landlock(void)
{
print_message(msg_config, "Landlock: not built in\n");
return;
}
#endif /* HAVE_LANDLOCK */

211
log.c Normal file
View File

@ -0,0 +1,211 @@
/*
# log: processing of all outgoing messages
#
# Copyright (C) 2007-2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define SYSLOG_NAMES
#define _GNU_SOURCE
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include "sslh-conf.h"
#include "common.h"
#include "log.h"
msg_info msg_config = {
LOG_INFO,
&cfg.verbose_config
};
msg_info msg_config_error = {
LOG_ERR,
&cfg.verbose_config_error
};
msg_info msg_fd = {
LOG_DEBUG,
&cfg.verbose_fd
};
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
msg_info msg_int_error = {
LOG_CRIT,
&cfg.verbose_system_error
};
/* System errors: when the system around us fails us: memory allocation, fork, ... */
msg_info msg_system_error = {
LOG_ERR,
&cfg.verbose_system_error
};
msg_info msg_packets = {
LOG_INFO,
&cfg.verbose_packets
};
/* additional info when attempting outgoing connections */
msg_info msg_connections_try = {
LOG_DEBUG,
&cfg.verbose_connections_try
};
/* Connection information and failures (e.g. forbidden by policy) */
msg_info msg_connections = {
LOG_INFO,
&cfg.verbose_connections
};
/* Connection failures, e.g. target server not present */
msg_info msg_connections_error = {
LOG_ERR,
&cfg.verbose_connections_error
};
/* comment the probing process */
msg_info msg_probe_info = {
LOG_INFO,
&cfg.verbose_probe_info
};
/* probing errors, e.g. inconsistent data in connections */
msg_info msg_probe_error = {
LOG_ERR,
&cfg.verbose_probe_error
};
/* Bitmasks in verbose-* values */
#define MSG_STDOUT 1
#define MSG_SYSLOG 2
#define MSG_FILE 4
static FILE* logfile_fp = NULL;
/* Prints a message to stderr and/or syslog if appropriate */
void print_message(msg_info info, const char* str, ...)
{
va_list ap;
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
va_start(ap, str);
vfprintf(stderr, str, ap);
va_end(ap);
}
if (*info.verbose & MSG_SYSLOG) {
va_start(ap, str);
vsyslog(info.log_level, str, ap);
va_end(ap);
}
if (*info.verbose & MSG_FILE && logfile_fp != NULL) {
va_start(ap, str);
vfprintf(logfile_fp, str, ap);
fflush(logfile_fp);
va_end(ap);
}
}
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
/* Open syslog connection with appropriate banner;
* banner is made up of basename(bin_name)+"[pid]" */
void setup_syslog(const char* bin_name) {
char *name1, *name2;
int res, fn;
if (!strcmp(cfg.syslog_facility, "none")) {
do_syslog = 0;
return;
}
name1 = strdup(bin_name);
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
break;
if (facilitynames[fn].c_val == -1) {
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
exit(1);
}
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
free(name1);
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
}
void setup_logfile()
{
if (cfg.logfile == NULL)
{
return;
}
logfile_fp = fopen(cfg.logfile, "a");
if (logfile_fp == NULL)
{
fprintf(stderr, "Could not open logfile %s for writing: %s\n", cfg.logfile, strerror(errno));
exit(1);
}
}
void close_logfile()
{
if (logfile_fp != NULL)
{
fclose(logfile_fp);
logfile_fp = NULL;
}
}
/* syslogs who connected to where
* desc: string description of the connection. if NULL, log_connection will
* manage on its own
* cnx: connection descriptor
* */
void log_connection(struct connection_desc* desc, const struct connection *cnx)
{
struct connection_desc d;
if (cnx->proto->log_level < 1)
return;
if (!desc) {
desc = &d;
if (!get_connection_desc(desc, cnx)) {
print_message(msg_connections, "%s: lost incoming connection\n",
cnx->proto->name);
return;
}
}
print_message(msg_connections, "%s:connection from %s to %s forwarded from %s to %s\n",
cnx->proto->name,
desc->peer,
desc->service,
desc->local,
desc->target);
}

36
log.h Normal file
View File

@ -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 */

440
probe.c
View File

@ -1,7 +1,7 @@
/*
# probe.c: Code for probing protocols
#
# Copyright (C) 2007-2015 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,45 +22,50 @@
#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 proto*);
static int is_openvpn_protocol(const char *p, int len, struct proto*);
static int is_tinc_protocol(const char *p, int len, struct proto*);
static int is_xmpp_protocol(const char *p, int len, struct proto*);
static int is_http_protocol(const char *p, int len, struct proto*);
static int is_tls_protocol(const char *p, int len, struct proto*);
static int is_adb_protocol(const char *p, int len, struct proto*);
static int is_true(const char *p, int len, struct proto* 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
*/
static struct proto builtins[] = {
/* description service saddr log_level keepalive fork probe */
{ "ssh", "sshd", NULL, 1, 0, 1, is_ssh_protocol},
{ "openvpn", NULL, NULL, 1, 0, 1, is_openvpn_protocol },
{ "tinc", NULL, NULL, 1, 0, 1, is_tinc_protocol },
{ "xmpp", NULL, NULL, 1, 0, 0, is_xmpp_protocol },
{ "http", NULL, NULL, 1, 0, 0, is_http_protocol },
{ "ssl", NULL, NULL, 1, 0, 0, is_tls_protocol },
{ "tls", NULL, NULL, 1, 0, 0, is_tls_protocol },
{ "adb", NULL, NULL, 1, 0, 0, is_adb_protocol },
{ "anyprot", NULL, NULL, 1, 0, 0, is_true }
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 },
{ "adb", is_adb_protocol },
{ "socks5", is_socks5_protocol },
{ "syslog", is_syslog_protocol },
{ "teamspeak", is_teamspeak_protocol },
{ "msrdp", is_msrdp_protocol },
{ "anyprot", is_true }
};
static struct proto *protocols;
static char* on_timeout = "ssh";
struct proto* get_builtins(void) {
/* TODO I think this has to go */
struct protocol_probe_desc* get_builtins(void) {
return builtins;
}
@ -68,70 +73,59 @@ int get_num_builtins(void) {
return ARRAY_SIZE(builtins);
}
/* Sets the protocol name to connect to in case of timeout */
void set_ontimeout(const char* name)
{
int res = asprintf(&on_timeout, "%s", name);
CHECK_RES_DIE(res, "asprintf");
}
/* Returns the protocol to connect to in case of timeout;
* if not found, return the first protocol specified
*/
struct proto* timeout_protocol(void)
struct sslhcfg_protocols_item* timeout_protocol(void)
{
struct proto* p = get_first_protocol();
for (; p && strcmp(p->description, on_timeout); p = p->next);
if (p) return p;
return get_first_protocol();
int i;
for (i = 0; i < cfg.protocols_len; i++) {
if (!strcmp(cfg.protocols[i].name, cfg.on_timeout)) return &cfg.protocols[i];
}
return &cfg.protocols[0];
}
/* returns the first protocol (caller can then follow the *next pointers) */
struct proto* get_first_protocol(void)
{
return protocols;
}
void set_protocol_list(struct proto* prots)
{
protocols = prots;
}
/* 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 proto *proto)
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (len < 4)
return PROBE_AGAIN;
@ -149,22 +143,73 @@ static int is_ssh_protocol(const char *p, int len, struct proto *proto)
* 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 proto *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) + sizeof(uint32_t))
return PROBE_NEXT;
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))) <= 5u)
return PROBE_MATCH;
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160) + sizeof(uint32_t))
return PROBE_NEXT;
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u)
return PROBE_MATCH;
return PROBE_NEXT;
}
}
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (proto->is_udp == 0)
return PROBE_NEXT;
// Handshake Init: 148 bytes
if (len != 148)
return PROBE_NEXT;
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
if (ntohl(*(uint32_t*)p) != 0x01000000)
return PROBE_NEXT;
return PROBE_MATCH;
}
/* 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 proto *proto)
static int is_tinc_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (len < 2)
return PROBE_AGAIN;
@ -176,15 +221,18 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto)
* (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 proto *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;
/* sometimes the word 'jabber' shows up late in the initial string,
sometimes after a newline. this makes sure we snarf the entire preamble
and detect it. (fixed for adium/pidgin) */
if (len < 50)
return PROBE_AGAIN;
return memmem(p, len, "jabber", 6) ? 1 : 0;
return PROBE_NEXT;
}
static int probe_http_method(const char *p, int len, const char *opt)
@ -192,11 +240,11 @@ static int probe_http_method(const char *p, int len, const char *opt)
if (len < strlen(opt))
return PROBE_AGAIN;
return !strncmp(p, opt, len);
return !strncmp(p, opt, strlen(opt));
}
/* Is the buffer the beginning of an HTTP connection? */
static int is_http_protocol(const char *p, int len, struct proto *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 */
@ -221,45 +269,15 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
return PROBE_NEXT;
}
static int is_sni_alpn_protocol(const char *p, int len, struct proto *proto)
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
int valid_tls;
valid_tls = parse_tls_header(proto->data, p, len);
if(valid_tls < 0)
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
/* There *was* a valid match */
return PROBE_MATCH;
}
static int is_tls_protocol(const char *p, int len, struct proto *proto)
{
if (len < 6)
return PROBE_AGAIN;
/* TLS packet starts with a record "Hello" (0x16), followed by the number of
* the highest version of SSL/TLS supported.
*
* A SSLv2 record header contains a two or three byte length code. If the
* most significant bit is set in the first byte of the record length code
* then the record has no padding and the total header length will be 2
* bytes, otherwise the record has padding and the total header length will
* be 3 bytes. Next, a 1 char sized client-hello (0x01) is expected,
* followed by a 2 char sized version that indicates the highest version of
* TLS/SSL supported by the sender. [SSL2] Hickman, Kipp, "The SSL Protocol"
*
* We're checking the highest version of TLS/SSL supported against
* (0x03 0x00-0x03) (RFC6101 A.1). This means we reject the usage of SSLv2
* and lower, which is actually a good thing (RFC6176).
*/
if (p[0] == 0x16) // TLS client-hello
return p[1] == 0x03 && ( p[2] >= 0 && p[2] <= 0x03);
if ((p[0] & 0x80) != 0) // SSLv2 client-hello, no padding
return p[2] == 0x01 && p[3] == 0x03 && ( p[4] >= 0 && p[4] <= 0x03);
else // SSLv2 client-hello, padded
return p[3] == 0x01 && p[4] == 0x03 && ( p[5] >= 0 && p[5] <= 0x03);
switch (parse_tls_header(proto->data, p, len)) {
case TLS_MATCH: return PROBE_MATCH;
case TLS_NOMATCH: return PROBE_NEXT;
case TLS_ELENGTH: return PROBE_AGAIN;
default: return PROBE_NEXT;
}
}
static int probe_adb_cnxn_message(const char *p)
@ -272,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 proto *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.
@ -305,102 +323,178 @@ static int is_adb_protocol(const char *p, int len, struct proto *proto)
if (len < min_data_packet_size + sizeof(empty_message))
return PROBE_AGAIN;
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
if (memcmp(&p[0], empty_message, sizeof(empty_message)) != 0)
return PROBE_NEXT;
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
}
static int regex_probe(const char *p, int len, struct proto *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;
if (len < 2)
return PROBE_AGAIN;
/* First byte should be socks protocol version */
if (p[0] != 5)
return PROBE_NEXT;
/* Second byte should be number of supported
* authentication methods, assuming maximum of 10,
* as defined in https://www.iana.org/assignments/socks-methods/socks-methods.xhtml
*/
char m_count = p[1];
if (m_count < 1 || m_count > 10)
return PROBE_NEXT;
if (len < 2 + m_count)
return PROBE_AGAIN;
/* Each authentication method number should be in range 0..9
* (https://www.iana.org/assignments/socks-methods/socks-methods.xhtml)
*/
for (i = 0; i < m_count; i++) {
if (p[2 + i] > 9)
return PROBE_NEXT;
}
return PROBE_MATCH;
}
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 proto *p;
int n;
struct sslhcfg_protocols_item* p;
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);
/* Dump hex values of the packet */
if (verbose>1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buffer, n);
}
*proto_out = NULL;
for (i = 0; i < proto_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
p = proto_in[i];
if (n > 0) {
int res = PROBE_NEXT;
if (! p->probe) continue;
defer_write(&cnx->q[1], buffer, n);
print_message(msg_probe_info, "probing for %s\n", p->name);
for (p = cnx->proto; p && res == PROBE_NEXT; p = p->next) {
if (! p->probe) continue;
if (verbose) fprintf(stderr, "probing for %s\n", p->description);
/* Don't probe last protocol if it is anyprot (and store last protocol) */
if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
break;
cnx->proto = p;
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
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;
}
if (res != PROBE_NEXT)
return 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) {
*proto_out = p;
return PROBE_MATCH;
}
if (res == PROBE_AGAIN)
again++;
}
if (again)
return PROBE_AGAIN;
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
/* Everything failed: match the last one */
/* If none worked, return the first one affected (that's completely
* arbitrary) */
cnx->proto = protocols;
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;
}
/* Returns the structure for specified protocol or NULL if not found */
static struct proto* get_protocol(const char* description)
{
int i;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].description, description)) {
return &builtins[i];
}
}
return NULL;
}
/* Returns the probe for specified protocol:
* parameter is the description in builtins[], or "regex"
* */
T_PROBE* get_probe(const char* description) {
struct proto* p = get_protocol(description);
int i;
if (p)
return p->probe;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].name, description)) {
return builtins[i].probe;
}
}
/* Special case of "regex" probe (we don't want to set it in builtins
* because builtins is also used to build the command-line options and
@ -408,10 +502,6 @@ T_PROBE* get_probe(const char* description) {
if (!strcmp(description, "regex"))
return regex_probe;
/* Special case of "sni/alpn" probe for same reason as above*/
if (!strcmp(description, "sni_alpn"))
return is_sni_alpn_protocol;
/* Special case of "timeout" is allowed as a probe name in the
* configuration file even though it's not really a probe */
if (!strcmp(description, "timeout"))

43
probe.h
View File

@ -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 */
@ -12,30 +13,19 @@ typedef enum {
PROBE_AGAIN, /* Not enough data for this probe, try again with more data */
} probe_result;
struct proto;
typedef int T_PROBE(const char*, int, struct proto*);
struct sslhcfg_protocols_item;
typedef int T_PROBE(const char*, ssize_t, struct sslhcfg_protocols_item*);
/* For each protocol we need: */
struct proto {
const char* description; /* a string that says what it is (for logging and command-line parsing) */
const char* service; /* service name to do libwrap checks */
struct addrinfo *saddr; /* list of addresses to try and switch that protocol */
int log_level; /* 0: No logging of connection
* 1: Log incoming connection
*/
int keepalive; /* 0: No keepalive ; 1: Set Keepalive for this connection */
int fork; /* 0: Connection can run within shared process ; 1: Separate process required for this connection */
/* function to probe that protocol; parameters are buffer and length
* containing the data to probe, and a pointer to the protocol structure */
struct protocol_probe_desc {
const char* name;
T_PROBE* probe;
/* opaque pointer ; used to pass list of regex to regex probe, or TLSProtocol struct to sni/alpn probe */
void* data;
struct proto *next; /* pointer to next protocol in list, NULL if last */
};
#include "sslh-conf.h"
/* Returns a pointer to the array of builtin protocols */
struct proto * get_builtins(void);
struct protocol_probe_desc* get_builtins(void);
/* Returns the number of builtin protocols */
int get_num_builtins(void);
@ -44,10 +34,10 @@ int get_num_builtins(void);
T_PROBE* get_probe(const char* description);
/* Returns the head of the configured protocols */
struct proto* get_first_protocol(void);
struct sslhcfg_protocols_item* get_first_protocol(void);
/* Set the list of configured protocols */
void set_protocol_list(struct proto*);
void set_protocol_list(struct sslhcfg_protocols_item*);
/* probe_client_protocol
*
@ -58,6 +48,13 @@ void set_protocol_list(struct proto*);
*/
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);
@ -65,8 +62,8 @@ void set_ontimeout(const char* name);
*
* Returns the protocol to connect to in case of timeout
*/
struct proto* timeout_protocol(void);
struct sslhcfg_protocols_item* timeout_protocol(void);
void hexdump(const char*, unsigned int);
void hexdump(msg_info, const char*, unsigned int);
#endif

115
processes.c Normal file
View File

@ -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
processes.h Normal file
View File

@ -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

120
proxyprotocol.c Normal file
View File

@ -0,0 +1,120 @@
/*
# proxyprotocol: Support for HAProxy's proxyprotocol
#
# Copyright (C) 2025 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#include "config.h"
#ifdef HAVE_PROXYPROTOCOL
#include <proxy_protocol.h>
#include "common.h"
#include "log.h"
/* Converts socket family to libproxyprotocol family */
static int family_to_pp(int af_family)
{
switch (af_family) {
case AF_INET:
return ADDR_FAMILY_INET;
case AF_INET6:
return ADDR_FAMILY_INET6;
case AF_UNIX:
return ADDR_FAMILY_UNIX;
default:
print_message(msg_int_error, "Unknown internal socket family %d\n", af_family);
return -1;
}
}
typedef char libpp_addr[108]; /* This is hardcoded in libproxyprotocol/proxy_protocol.h */
/* Fills *addr, *host and *serv with the connection information corresponding
* to fd. *host is the IP address as string and *serv is the service (port)
* */
static int get_info(int fd, struct addrinfo* addr, libpp_addr* host, uint16_t* serv)
{
char serv_str[NI_MAXSERV];
int res;
res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen);
CHECK_RES_RETURN(res, "getpeername", -1);
res = getnameinfo(addr->ai_addr, addr->ai_addrlen,
(char*)host, sizeof(*host),
serv_str, sizeof(serv_str),
NI_NUMERICHOST | NI_NUMERICSERV );
CHECK_RES_RETURN(res, "getnameinfo", -1);
*serv = atoi(serv_str);
return 0;
}
int pp_write_header(int pp_version, struct connection* cnx)
{
pp_info_t pp_info_in_v1 = {
.transport_protocol = TRANSPORT_PROTOCOL_STREAM,
};
uint16_t pp1_hdr_len;
int32_t error;
struct sockaddr_storage ss;
struct addrinfo addr;
int res;
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
res = get_info(cnx->q[0].fd,
&addr,
&pp_info_in_v1.src_addr,
&pp_info_in_v1.src_port);
if (res == -1) return -1;
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
res = get_info(cnx->q[1].fd,
&addr,
&pp_info_in_v1.dst_addr,
&pp_info_in_v1.dst_port
);
if (res == -1) return -1;
uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
if (!pp1_hdr) {
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error));
return -1;
}
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
pp_info_clear(&pp_info_in_v1);
free(pp1_hdr);
return 0;
}
#endif /* HAVE_PROXYPROTOCOL */

15
proxyprotocol.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef PROXYPROTOCOL_H
#define PROXYPROTOCOL_H
#if HAVE_PROXYPROTOCOL
int pp_write_header(int pp_version, struct connection* cnx);
#else /* HAVE_PROXYPROTOCOL */
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
#endif /* HAVE_PROXYPROTOCOL */
#endif /* PROXYPROTOCOL_H */

View File

@ -0,0 +1,186 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: sslh
# Required-Start: $remote_fs $syslog $network
# Required-Stop: $remote_fs $syslog $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: ssl/ssh multiplexer
# Description: sslh lets one accept both HTTPS and SSH connections on the
# same port. It makes it possible to connect to an SSH server
# on port 443 (e.g. from inside a corporate firewall) while
# still serving HTTPS on that port.
### END INIT INFO
# Original Author: Guillaume Delacour <gui@iroqwa.org>
# modified and optimized for current sslh-fork
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="ssl/ssh multiplexer"
NAME=sslh
DAEMON=/usr/sbin/$NAME
DAEMON_OPTS=""
PIDFILE=/var/run/sslh/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
RUN=yes
# If you want to use a configuration file, put -F/path/to/sslh.cfg
# into /etc/default/sslh DAEMON_OPTS
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
# Exit if the package is not installed
if [ -x "$DAEMON" ]
then
echo "Can not start \"$DAEMON\", path not available"
log_failure_msg "Can not start \"$DAEMON\", path not available"
fi
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
# Use this if you want the user to explicitly set 'RUN' in
# /etc/default/
if [ "$RUN" != "yes" ]
then
echo "$NAME disabled, please adjust the configuration to your needs "
log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it."
return 2
fi
# sslh write the pid as sslh user
if [ ! -d /var/run/sslh/ ]
then
mkdir -p /var/run/sslh
chown sslh:sslh /var/run/sslh
fi
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_OPTS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/45/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# As long, as the started sslh is sslh-fork, don't kill the still existing
# connections. You may need the following construct for sslh-ev and sslh-select,
# as sslh has currently no function reloading its configuration.
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
#start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
#[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
# don't activate this, as this kills only the leading process
# of sslh-fork, and the spawned worker stays connected listening.
# After that, the Owner of the PID from PIDFILE is gone, the
# listening connection is still blocked
# sslh can't reload its configuration as of Aug 2024
#do_reload() {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP),
# then implement that here.
#
# start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
# return 0
#}
case "$1" in
start)
# check if sslh is launched via inetd
if [ -f /etc/inetd.conf ] && [ $(egrep -q "^https.*/usr/sbin/sslh" /etc/inetd.conf|wc -l) -ne 0 ]
then
echo "sslh is started from inetd."
exit 1
fi
log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) log_end_msg 0 ;;
2) log_end_msg 1 ;;
esac
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) log_end_msg 0 ;;
2) log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2
exit 3
;;
esac

View File

@ -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
@ -18,7 +18,7 @@
#CONFIG=/etc/sslh.cfg
#
# Extra option to pass on comand line
# Extra option to pass on command line
# Those can supersede configuration file settings
#
#OPTIONS=

View File

@ -8,7 +8,7 @@
# but many connection attempts from the same
# origin is reason enough to block.
#
# Verion: 2014-03-28
# Version: 2014-03-28
[INCLUDES]
@ -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 =

View File

@ -0,0 +1,27 @@
[Unit]
Description=SSL/SSH multiplexer (select mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target

View File

@ -1,14 +1,14 @@
[Unit]
Description=SSL/SSH multiplexer
Description=SSL/SSH multiplexer (fork mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/bin/sslh --foreground $DAEMON_OPTS
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict

2580
sslh-conf.c Normal file

File diff suppressed because it is too large Load Diff

134
sslh-conf.h Normal file
View File

@ -0,0 +1,134 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:58 2025.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 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 is_udp;
int is_unix;
int keepalive;
};
struct sslhcfg_protocols_item {
char* name;
char* host;
char* port;
int service_is_present;
char* service;
int is_unix;
int is_udp;
int udp_timeout;
int fork;
int tfo_ok;
int transparent;
int resolve_on_forward;
int log_level;
int keepalive;
size_t sni_hostnames_len;
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;
int proxyprotocol_is_present;
int proxyprotocol;
T_PROBE* probe;
struct addrinfo* saddr;
void* data;
dl_list timeouts;
};
struct sslhcfg_item {
int verbose;
int verbose_config;
int verbose_config_error;
int verbose_connections;
int verbose_connections_try;
int verbose_connections_error;
int verbose_fd;
int verbose_packets;
int verbose_probe_info;
int verbose_probe_error;
int verbose_system_error;
int verbose_int_error;
int version;
int foreground;
int inetd;
int numeric;
int transparent;
int timeout;
int udp_max_connections;
int user_is_present;
char* user;
int pidfile_is_present;
char* pidfile;
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;
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

153
sslh-ev.c Normal file
View File

@ -0,0 +1,153 @@
/*
sslh-ev: mono-processus server based on libev
# Copyright (C) 2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#include <stdlib.h>
#include <ev.h>
#include "gap.h"
#include "log.h"
#include "udp-listener.h"
#include "tcp-listener.h"
const char* server_type = "sslh-ev";
static struct ev_loop* loop;
/* Libev watchers */
struct watchers {
/* one set of ev_io for read, one for write, indexed by file descriptor */
gap_array *ev_ior, *ev_iow;
struct listen_endpoint* listen_sockets;
gap_array* fd2ls; /* Array indexed by file descriptor, pointing to listen_sockets */
};
static void cnx_read_cb(EV_P_ ev_io *w, int revents);
static void cnx_write_cb(EV_P_ ev_io *w, int wevents);
static void cnx_accept_cb(EV_P_ ev_io *w, int revents);
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
int num_addr_listen)
{
*w = malloc(sizeof(**w));
(*w)->ev_ior = gap_init(num_addr_listen);
(*w)->ev_iow = gap_init(num_addr_listen);
(*w)->listen_sockets = listen_sockets;
(*w)->fd2ls = gap_init(0);
/* Create watchers for listen sockets */
for (int i = 0; i < num_addr_listen; i++) {
ev_io* io = malloc(sizeof(*io));
ev_io_init(io, &cnx_accept_cb, listen_sockets[i].socketfd, EV_READ);
ev_io_start(EV_A_ io);
gap_set((*w)->ev_ior, i, io);
gap_set((*w)->fd2ls, listen_sockets[i].socketfd, &listen_sockets[i]);
set_nonblock(listen_sockets[i].socketfd);
}
}
void watchers_add_read(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_ior, fd);
if (!io) {
io = malloc(sizeof(*io));
ev_io_init(io, &cnx_read_cb, fd, EV_READ);
ev_io_set(io, fd, EV_READ);
gap_set(w->ev_ior, fd, io);
}
ev_io_start(loop, io);
}
void watchers_del_read(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_ior, fd);
if (io) ev_io_stop(EV_A_ io);
}
void watchers_add_write(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_iow, fd);
if (!io) {
io = malloc(sizeof(*io));
ev_io_init(io, &cnx_write_cb, fd, EV_WRITE);
ev_io_set(io, fd, EV_WRITE);
gap_set(w->ev_iow, fd, io);
}
ev_io_start(loop, io);
}
void watchers_del_write(watchers* w, int fd)
{
ev_io* io = gap_get(w->ev_iow, fd);
if (io) ev_io_stop(EV_A_ io);
}
/* /watchers */
#include "processes.h"
/* Libev callbacks */
static void cnx_read_cb(EV_P_ ev_io *w, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
cnx_read_process(info, w->fd);
}
static void cnx_write_cb(EV_P_ ev_io *w, int wevents)
{
struct loop_info* info = ev_userdata(EV_A);
cnx_write_process(info, w->fd);
}
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
struct loop_info ev_info = {0};
loop = EV_DEFAULT;
ev_info.collection = collection_init(0);
ev_info.probing_list = gap_init(0);
udp_init(&ev_info);
tcp_init();
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info);
ev_run(EV_A_ 0);
}
void start_shoveler(int listen_socket) {
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
exit(1);
}

View File

@ -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,11 +22,16 @@
#include "common.h"
#include "probe.h"
#include "sslh-conf.h"
#include "tcp-probe.h"
#include "log.h"
#if HAVE_LIBBSD
#include <bsd/unistd.h>
#endif
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
*/
@ -53,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 (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;
}
}
@ -70,8 +74,8 @@ void start_shoveler(int in_socket)
fd_set fds;
struct timeval tv;
int res = PROBE_AGAIN;
int out_socket;
struct connection cnx;
struct connection_desc desc;
init_cnx(&cnx);
cnx.q[0].fd = in_socket;
@ -79,7 +83,7 @@ void start_shoveler(int in_socket)
FD_ZERO(&fds);
FD_SET(in_socket, &fds);
memset(&tv, 0, sizeof(tv));
tv.tv_sec = probing_timeout;
tv.tv_sec = cfg.timeout;
while (res == PROBE_AGAIN) {
/* POSIX does not guarantee that tv will be updated, but the client can
@ -94,8 +98,7 @@ void start_shoveler(int in_socket)
} else {
/* Timed out: it's necessarily SSH */
cnx.proto = timeout_protocol();
if (verbose)
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->description);
print_message(msg_fd, "timed out, connect to %s\n", cnx.proto->name);
break;
}
}
@ -106,27 +109,28 @@ void start_shoveler(int in_socket)
}
/* Connect the target socket */
out_socket = connect_addr(&cnx, in_socket);
CHECK_RES_DIE(out_socket, "connect");
connect_addr(&cnx, in_socket, BLOCKING);
CHECK_RES_DIE(cnx.q[1].fd, "connect");
cnx.q[1].fd = out_socket;
set_capabilities(0);
log_connection(&cnx);
get_connection_desc(&desc, &cnx);
log_connection(&desc, &cnx);
set_proctitle_shovel(&desc, &cnx);
flush_deferred(&cnx.q[1]);
shovel(&cnx);
close(in_socket);
close(out_socket);
close(cnx.q[1].fd);
if (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)
@ -138,46 +142,117 @@ void stop_listeners(int sig)
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
void set_listen_procname(struct listen_endpoint *listen_socket)
{
int in_socket, i, res;
#if HAVE_LIBBSD
int res;
struct addrinfo addr;
struct sockaddr_storage ss;
char listen_addr[NI_MAXHOST+1+NI_MAXSERV+1];
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
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);
}
#endif
}
/* At least MacOS does not know these two options, so define them to something
* equivalent for our use case */
#ifndef ENONET
#define ENONET EWOULDBLOCK
#endif
/* /MacOS kludge */
/* TCP listener: connections, fork a child for each new connection
* IN:
* endpoint: array of listening endpoint objects
* 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 i, in_socket;
while (1) {
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__FILE__, __LINE__, "accept", errno, strerror(errno));
switch(in_socket) {
case ENETDOWN: /* accept(2) cites all these errnos as "you should retry" */
case EPROTO:
case ENOPROTOOPT:
case EHOSTDOWN:
case ENONET:
case EHOSTUNREACH:
case EOPNOTSUPP:
case ENETUNREACH:
case ECONNABORTED:
continue;
default: /* Otherwise, it's something wrong in our parameters, we fail */
return;
}
}
print_message(msg_fd, "accepted fd %d\n", in_socket);
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]) {
case 0: break;
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 */
case 0:
set_listen_procname(&listen_sockets[i]);
if (listen_sockets[i].type == SOCK_DGRAM)
print_message(msg_config_error, "UDP not supported in sslh-fork\n");
else
tcp_listener(listen_sockets, num_addr_listen, i);
default:
/* Listening process just accepts a connection, forks, and goes
* back to listening */
while (1)
{
in_socket = accept(listen_sockets[i], 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
exit(0);
break;
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);
}
/* We're in the parent, we don't need to do anything */
default:
break;
}
}
@ -189,10 +264,11 @@ void main_loop(int listen_sockets[], int num_addr_listen)
res = sigaction(SIGTERM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
wait(NULL);
}
/* The actual main is in common.c: it's the same for both version of
/* The actual main() is in sslh_main.c: it's the same for all versions of
* the server
*/

View File

@ -2,7 +2,7 @@
# main: processing of config file, command line options and start the main
# loop.
#
# Copyright (C) 2007-2016 Yves Rutschle
# Copyright (C) 2007-2018 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@ -26,82 +26,28 @@
#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
#include "common.h"
#include "probe.h"
#include "log.h"
#include "tcp-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 <add> [-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" \
"";
#if HAVE_LIBBSD
#include <bsd/unistd.h>
#endif
#if HAVE_LIBCAP
#include <sys/capability.h>
#endif
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
static struct option const_options[] = {
{ "inetd", no_argument, &inetd, 1 },
{ "foreground", no_argument, &foreground, 1 },
{ "background", no_argument, &background, 1 },
{ "transparent", no_argument, &transparent, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "verbose", no_argument, &verbose, 1 },
{ "user", required_argument, 0, 'u' },
{ "config", optional_argument, 0, 'F' },
{ "pidfile", required_argument, 0, 'P' },
{ "chroot", required_argument, 0, 'C' },
{ "timeout", required_argument, 0, 't' },
{ "on-timeout", required_argument, 0, OPT_ONTIMEOUT },
{ "listen", required_argument, 0, 'p' },
{}
};
static struct option* all_options;
static struct proto* builtins;
static const char *optstr = "vt:T:p:VP:C:F::";
static void print_usage(void)
{
struct proto *p;
int i;
int res;
char *prots = "";
p = get_builtins();
for (i = 0; i < get_num_builtins(); i++) {
res = asprintf(&prots, "%s\t[--%s <addr>]\n", prots, p[i].description);
CHECK_RES_DIE(res, "asprintf");
}
fprintf(stderr, USAGE_STRING, prots);
}
static void printcaps(void) {
#ifdef LIBCAP
#if HAVE_LIBCAP
cap_t caps;
char* desc;
ssize_t len;
@ -110,7 +56,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);
@ -119,525 +65,266 @@ static void printcaps(void) {
static void printsettings(void)
{
char buf[NI_MAXHOST];
struct addrinfo *a;
struct proto *p;
char buf[NI_MAXHOST + 256]; /* 256 > " family %d %d" for reasonable ints */
int i;
struct sslhcfg_protocols_item *p;
for (p = get_first_protocol(); p; p = p->next) {
fprintf(stderr,
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s]\n",
p->description,
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" : "");
}
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", probing_timeout,
timeout_protocol()->description);
}
/* Extract configuration on addresses and ports on which to listen.
* out: newly allocated list of addrinfo to listen to
*/
#ifdef LIBCONFIG
static int config_listen(config_t *config, struct addrinfo **listen)
{
config_setting_t *setting, *addr;
int len, i, keepalive;
const char *hostname, *port;
setting = config_lookup(config, "listen");
if (setting) {
len = config_setting_length(setting);
for (i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
}
keepalive = 0;
config_setting_lookup_bool(addr, "keepalive", &keepalive);
resolve_split_name(listen, hostname, port);
/* 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 (keepalive)
(*listen)->ai_flags = SO_KEEPALIVE;
for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i];
if (p->is_unix) {
sprintf(buf, "unix socket: %s", p->host);
} else {
strcpy(buf, "resolve on forward");
if (!p->resolve_on_forward) {
sprintaddr(buf, sizeof(buf), p->saddr);
size_t len = strlen(buf);
sprintf(buf+len, " family %d %d",
p->saddr->ai_family,
p->saddr->ai_addr->sa_family);
}
}
print_message(msg_config,
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
p->name,
buf,
p->service,
p->log_level,
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "",
p->transparent ? "transparent" : ""
);
}
return 0;
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);
}
#endif
#ifdef LIBCONFIG
static void setup_regex_probe(struct proto *p, config_setting_t* probes)
{
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
#ifdef ENABLE_REGEX
int num_probes, errsize, i, res;
char *err;
const char * expr;
regex_t** probe_list;
{
size_t num_patterns, i;
int error;
pcre2_code** pattern_list;
PCRE2_SIZE error_offset;
PCRE2_UCHAR8 err_str[120];
num_probes = config_setting_length(probes);
if (!num_probes) {
fprintf(stderr, "%s: no probes specified\n", p->description);
exit(1);
}
num_patterns = p->regex_patterns_len;
p->probe = get_probe("regex");
probe_list = calloc(num_probes + 1, sizeof(*probe_list));
p->data = (void*)probe_list;
pattern_list = calloc(num_patterns + 1, sizeof(*pattern_list));
CHECK_ALLOC(pattern_list, "calloc");
p->data = (void*)pattern_list;
for (i = 0; i < num_probes; i++) {
probe_list[i] = malloc(sizeof(*(probe_list[i])));
expr = config_setting_get_string_elem(probes, i);
res = regcomp(probe_list[i], expr, REG_EXTENDED);
if (res) {
err = malloc(errsize = regerror(res, probe_list[i], NULL, 0));
regerror(res, probe_list[i], err, errsize);
fprintf(stderr, "%s:%s\n", expr, err);
free(err);
for (i = 0; i < num_patterns; i++) {
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
fprintf(stderr, "line %d: regex probe specified but not compiled in\n", config_setting_source_line(probes));
exit(5);
#endif
}
#endif
#ifdef LIBCONFIG
static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char* name, int alpn)
{
int num_probes, i, max_server_name_len, server_name_len;
const char * config_item;
char** sni_hostname_list;
num_probes = config_setting_length(config_items);
if (!num_probes) {
fprintf(stderr, "%s: no %s specified\n", p->description, name);
return;
}
max_server_name_len = 0;
for (i = 0; i < num_probes; i++) {
server_name_len = strlen(config_setting_get_string_elem(config_items, i));
if(server_name_len > max_server_name_len)
max_server_name_len = server_name_len;
}
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
for (i = 0; i < num_probes; i++) {
config_item = config_setting_get_string_elem(config_items, i);
sni_hostname_list[i] = malloc(max_server_name_len);
strcpy (sni_hostname_list[i], config_item);
if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]);
}
p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list);
}
static void setup_sni_alpn(struct proto *p, config_setting_t* prot)
{
config_setting_t *sni_hostnames, *alpn_protocols;
p->data = (void*)new_tls_data();
sni_hostnames = config_setting_get_member(prot, "sni_hostnames");
alpn_protocols = config_setting_get_member(prot, "alpn_protocols");
if(sni_hostnames && config_setting_is_array(sni_hostnames)) {
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0);
}
if(alpn_protocols && config_setting_is_array(alpn_protocols)) {
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1);
}
return;
}
#endif
/* Extract configuration for protocols to connect to.
* out: newly-allocated list of protocols
/* Perform some fixups on configuration after reading it.
* if verbose is present, override all other verbose options
*/
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
void config_finish(struct sslhcfg_item* cfg)
{
config_setting_t *setting, *prot, *patterns;
const char *hostname, *port, *name;
int i, num_prots;
struct proto *p, *prev = NULL;
if (cfg->verbose) {
cfg->verbose_config = cfg->verbose;
cfg->verbose_config_error = cfg->verbose;
cfg->verbose_connections = cfg->verbose;
cfg->verbose_connections_try = cfg->verbose;
cfg->verbose_connections_error = cfg->verbose;
cfg->verbose_fd = cfg->verbose;
cfg->verbose_packets = cfg->verbose;
cfg->verbose_probe_info = cfg->verbose;
cfg->verbose_probe_error = cfg->verbose;
cfg->verbose_system_error = cfg->verbose;
cfg->verbose_int_error = cfg->verbose;
}
}
setting = config_lookup(config, "protocols");
if (setting) {
num_prots = config_setting_length(setting);
for (i = 0; i < num_prots; i++) {
p = calloc(1, sizeof(*p));
if (i == 0) *prots = p;
if (prev) prev->next = p;
prev = p;
/* Checks that the UNIX socket specified exists and is accessible
* Dies otherwise
*/
static void check_access_unix_socket(struct sslhcfg_protocols_item* p)
{
/* TODO */
return;
}
prot = config_setting_get_elem(setting, i);
if ((config_setting_lookup_string(prot, "name", &name) &&
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
)) {
p->description = name;
config_setting_lookup_string(prot, "service", &(p->service));
config_setting_lookup_bool(prot, "keepalive", &p->keepalive);
config_setting_lookup_bool(prot, "fork", &p->fork);
if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) {
p->log_level = 1;
}
/* For each protocol in the configuration, resolve address and set up protocol
* options if required
*/
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), hostname, port)) {
fprintf(stderr, "line %d: cannot resolve %s:%s\n", config_setting_source_line(prot), hostname, port);
exit(1);
}
if (p->is_unix) {
check_access_unix_socket(p);
} else if (
!p->resolve_on_forward &&
resolve_split_name(&(p->saddr), p->host, p->port)
) {
print_message(msg_config_error, "cannot resolve %s:%s\n",
p->host, p->port);
exit(4);
}
p->probe = get_probe(name);
if (!p->probe || !strcmp(name, "sni_alpn")) {
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
exit(1);
}
p->probe = get_probe(p->name);
if (!p->probe) {
print_message(msg_config_error, "%s: probe unknown\n", p->name);
exit(1);
}
/* Probe-specific options: regex patterns */
if (!strcmp(name, "regex")) {
patterns = config_setting_get_member(prot, "regex_patterns");
if (patterns && config_setting_is_array(patterns)) {
setup_regex_probe(p, patterns);
}
}
if (!strcmp(cfg.protocols[i].name, "regex")) {
setup_regex_probe(&cfg.protocols[i]);
}
/* Probe-specific options: SNI/ALPN */
if (!strcmp(name, "tls")) {
setup_sni_alpn(p, prot);
}
if (!strcmp(cfg.protocols[i].name, "tls")) {
cfg.protocols[i].data = (void*)new_tls_data();
if (cfg.protocols[i].sni_hostnames_len)
tls_data_set_list(cfg.protocols[i].data, 0,
(const char**) cfg.protocols[i].sni_hostnames,
cfg.protocols[i].sni_hostnames_len);
if (cfg.protocols[i].alpn_protocols_len)
tls_data_set_list(cfg.protocols[i].data, 1,
(const char**) cfg.protocols[i].alpn_protocols,
cfg.protocols[i].alpn_protocols_len);
}
} else {
fprintf(stderr, "line %d: Illegal protocol description (missing name, host or port)\n", config_setting_source_line(prot));
p->timeouts.head = NULL;
p->timeouts.tail = NULL;
}
}
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 (!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") != 0) {
if (cfg->protocols[i].sni_hostnames_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option sni_hostnames is only applicable for tls\n",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
if (cfg->protocols[i].alpn_protocols_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option alpn_protocols is only applicable for tls\n",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
}
if (cfg->protocols[i].is_udp) {
if (cfg->protocols[i].tfo_ok) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option tfo_ok is not applicable for udp connections\n",
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
exit(1);
}
} else {
if (!strcmp(cfg->protocols[i].name, "wireguard")) {
print_message(msg_config_error, "Wireguard works only with UDP\n");
exit(1);
}
}
}
return 0;
}
#endif
/* Parses a config file
* in: *filename
* out: *listen, a newly-allocated linked list of listen addrinfo
* *prots, a newly-allocated linked list of protocols
* 1 on error, 0 on success
/* Connect stdin, stdout, stderr to /dev/null. It is better to keep them around
* so they do not get re-used by socket descriptors, and accidently used by
* some library code.
*/
#ifdef LIBCONFIG
static int config_parse(char *filename, struct addrinfo **listen, struct proto **prots)
void close_std(void)
{
config_t config;
int timeout;
const char* str;
int newfd;
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* If it's a parse error then there will be a line number for the failure
* an I/O error (such as non-existent file) will have the error line as 0
*/
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
exit(1);
}
fprintf(stderr, "%s:%s\n",
filename,
config_error_text(&config));
return 1;
}
if(config_lookup_bool(&config, "verbose", &verbose) == CONFIG_FALSE) {
config_lookup_int(&config, "verbose", &verbose);
}
config_lookup_bool(&config, "inetd", &inetd);
config_lookup_bool(&config, "foreground", &foreground);
config_lookup_bool(&config, "numeric", &numeric);
config_lookup_bool(&config, "transparent", &transparent);
if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) {
probing_timeout = timeout;
}
if (config_lookup_string(&config, "on-timeout", &str)) {
set_ontimeout(str);
}
config_lookup_string(&config, "user", &user_name);
config_lookup_string(&config, "pidfile", &pid_file);
config_lookup_string(&config, "chroot", &chroot_path);
config_lookup_string(&config, "syslog_facility", &facility);
config_listen(&config, listen);
config_protocols(&config, prots);
return 0;
}
#endif
/* Adds protocols to the list of options, so command-line parsing uses the
* protocol definition array
* options: array of options to add to; must be big enough
* n_opts: number of options in *options before calling (i.e. where to append)
* prot: array of protocols
* n_prots: number of protocols in *prot
* */
static void append_protocols(struct option *options, int n_opts, struct proto *prot , int n_prots)
{
int o, p;
for (o = n_opts, p = 0; p < n_prots; o++, p++) {
options[o].name = prot[p].description;
options[o].has_arg = required_argument;
options[o].flag = 0;
options[o].val = p + PROT_SHIFT;
if ((newfd = open("/dev/null", O_RDWR))) {
dup2 (newfd, STDIN_FILENO);
dup2 (newfd, STDOUT_FILENO);
dup2 (newfd, STDERR_FILENO);
/* close the helper handle, as this is now unnecessary */
close(newfd);
} else {
print_message(msg_system_error, "Error closing standard filehandles for background daemon\n");
}
}
static void make_alloptions(void)
{
builtins = get_builtins();
/* Create all_options, composed of const_options followed by one option per
* known protocol */
all_options = calloc(ARRAY_SIZE(const_options) + get_num_builtins(), sizeof(struct option));
memcpy(all_options, const_options, sizeof(const_options));
append_protocols(all_options, ARRAY_SIZE(const_options) - 1, builtins, get_num_builtins());
}
/* Performs a first scan of command line options to see if a configuration file
* is specified. If there is one, parse it now before all other options (so
* configuration file settings can be overridden from the command line).
*
* prots: newly-allocated list of configured protocols, if any.
*/
static void cmdline_config(int argc, char* argv[], struct proto** prots)
{
#ifdef LIBCONFIG
int c, res;
char *config_filename;
#endif
make_alloptions();
#ifdef LIBCONFIG
optind = 1;
opterr = 0; /* we're missing protocol options at this stage so don't output errors */
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 'v') {
verbose++;
}
if (c == 'F') {
config_filename = optarg;
if (config_filename) {
res = config_parse(config_filename, &addr_listen, prots);
} else {
/* No configuration file specified -- try default file locations */
res = config_parse("/etc/sslh/sslh.cfg", &addr_listen, prots);
if (!res && verbose) fprintf(stderr, "Using /etc/sslh/sslh.cfg\n");
if (res) {
res = config_parse("/etc/sslh.cfg", &addr_listen, prots);
if (!res && verbose) fprintf(stderr, "Using /etc/sslh.cfg\n");
}
}
if (res)
exit(4);
break;
}
}
#endif
}
/* Parse command-line options. prots points to a list of configured protocols,
* potentially non-allocated */
static void parse_cmdline(int argc, char* argv[], struct proto* prots)
{
int c;
struct addrinfo **a;
struct proto *p;
optind = 1;
opterr = 1;
next_arg:
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 0) continue;
if (c >= PROT_SHIFT) {
if (prots)
for (p = prots; p && p->next; p = p->next) {
/* override if protocol was already defined by config file
* (note it only overrides address and use builtin probe) */
if (!strcmp(p->description, builtins[c-PROT_SHIFT].description)) {
resolve_name(&(p->saddr), optarg);
p->probe = builtins[c-PROT_SHIFT].probe;
goto next_arg;
}
}
/* At this stage, it's a new protocol: add it to the end of the
* list */
if (!prots) {
/* No protocols yet -- create the list */
p = prots = calloc(1, sizeof(*p));
} else {
p->next = calloc(1, sizeof(*p));
p = p->next;
}
memcpy(p, &builtins[c-PROT_SHIFT], sizeof(*p));
resolve_name(&(p->saddr), optarg);
continue;
}
switch (c) {
case 'F':
/* Legal option, but do nothing, it was already processed in
* cmdline_config() */
#ifndef LIBCONFIG
fprintf(stderr, "Built without libconfig support: configuration file not available.\n");
exit(1);
#endif
break;
case 't':
probing_timeout = atoi(optarg);
break;
case OPT_ONTIMEOUT:
set_ontimeout(optarg);
break;
case 'p':
/* 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 'V':
printf("%s %s\n", server_type, VERSION);
exit(0);
case 'u':
user_name = optarg;
break;
case 'P':
pid_file = optarg;
break;
case 'C':
chroot_path = optarg;
break;
case 'v':
verbose++;
break;
default:
print_usage();
exit(2);
}
}
if (!prots) {
fprintf(stderr, "At least one target protocol must be specified.\n");
exit(2);
}
set_protocol_list(prots);
/* If compiling with systemd socket support no need to require listen address */
#ifndef SYSTEMD
if (!addr_listen && !inetd) {
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
#endif
/* Did command-line override foreground setting? */
if (background)
foreground = 0;
}
int main(int argc, char *argv[])
int main(int argc, char *argv[], char* envp[])
{
extern char *optarg;
extern int optind;
int res, num_addr_listen;
struct proto* protocols = NULL;
struct listen_endpoint *listen_sockets;
int *listen_sockets;
#if HAVE_LIBBSD
setproctitle_init(argc, argv, envp);
#endif
/* Init defaults */
pid_file = NULL;
user_name = NULL;
chroot_path = NULL;
memset(&cfg, 0, sizeof(cfg));
res = sslhcfg_cl_parse(argc, argv, &cfg);
if (res) exit(6);
config_finish(&cfg);
cmdline_config(argc, argv, &protocols);
parse_cmdline(argc, argv, protocols);
if (cfg.version) {
printf("%s %s\n", server_type, VERSION);
exit(0);
}
if (inetd)
config_protocols();
config_sanity_check(&cfg);
if (cfg.inetd)
{
verbose = 0;
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0);
exit(0);
}
if (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
if (!foreground) {
if (!cfg.foreground) {
if (fork() > 0) exit(0); /* Detach */
close_std();
/* New session -- become group leader */
if (getuid() == 0) {
@ -648,19 +335,28 @@ int main(int argc, char *argv[])
setup_signals();
if (pid_file)
write_pid_file(pid_file);
if (cfg.pidfile)
write_pid_file(cfg.pidfile);
/* Open syslog connection before we drop privs/chroot */
setup_syslog(argv[0]);
if (user_name || chroot_path)
drop_privileges(user_name, chroot_path);
/* Open log file for writing */
setup_logfile();
if (verbose)
printcaps();
if (cfg.user || cfg.chroot)
drop_privileges(cfg.user, cfg.chroot);
setup_landlock();
printcaps();
print_message(msg_config, "%s %s started\n", server_type, VERSION);
main_loop(listen_sockets, num_addr_listen);
close_logfile();
free(listen_sockets);
return 0;
}

Some files were not shown because too many files have changed in this diff Show More