Compare commits

...
136 Commits
Author SHA1 Message Date
Yves Rutschle e831b732db update history 2024-05-16 21:34:45 +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
https://github.com/yrutschle/sslh/commit/711c11c820f8be4f1399024bcaa1d7072de33217:

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 5635dc5142 ("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
Toni Uhlig 61af0b2e09 fix cppcheck complains
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2022-09-11 22: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
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
49 changed files with 5711 additions and 344 deletions

No files matched your search

+63
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 }}
+7
View File
@@ -3,8 +3,15 @@
*.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
+32 -10
View File
@@ -1,5 +1,27 @@
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 functionaly equivalent to
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.
@@ -41,7 +63,7 @@ v1.22: 17AUG2021
combined with incoming TLS with SNI. UDP clients
and servers need to agree on the IPv4/IPv6 they use:
use the same protocol on all sides! Often, this
means explicitely using 'ip4-localhost'.
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.
@@ -83,7 +105,7 @@ v1.21: 11JUL2020
Added TCP_FASTOPEN support for client sockets (if
tfo_ok is specified in their configuration) and for
listenint socket, if all client protocols support it.
listening socket, if all client protocols support it.
(Craig Andrews)
Added 'minlength' option to skip a probe if less
@@ -109,8 +131,8 @@ v1.20: 20NOV2018
Before, probes were tried in order, repeating on the
same probe as long it returned PROBE_AGAIN before
moving to the next one. This means a probe which
requires a lot of data (i.e. returne PROBE_AGAIN for
a long time) could prevent sucessful matches from
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.
@@ -171,7 +193,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
@@ -200,7 +222,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)
@@ -209,7 +231,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
@@ -294,7 +316,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.
@@ -385,7 +407,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).
+32 -17
View File
@@ -1,23 +1,38 @@
FROM alpine:latest as build
ARG ALPINE_VERSION="latest"
ARG TARGET_ARCH="library"
ADD . /sslh
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
RUN \
apk add \
gcc \
libconfig-dev \
make \
musl-dev \
pcre2-dev \
perl && \
cd /sslh && \
make sslh-select && \
strip sslh-select
WORKDIR /sslh
FROM alpine:latest
RUN apk add --no-cache \
'gcc' \
'libconfig-dev' \
'make' \
'musl-dev' \
'pcre2-dev' \
'perl' \
;
COPY --from=build /sslh/sslh-select /sslh
COPY . /sslh
RUN apk --no-cache add libconfig pcre2
RUN ./configure && make sslh-select && strip sslh-select
ENTRYPOINT [ "/sslh", "--foreground"]
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
+20 -9
View File
@@ -3,9 +3,11 @@ VERSION=$(shell ./genver.sh -r)
# Configuration -- you probably need to `make clean` if you
# change any of these
# landlock is checked by `configure` and set in config.h
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
ENABLE_REGEX=1 # Enable regex probes
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBEV=1 # Use libev?
USELIBWRAP?= # Use libwrap?
USELIBCAP= # Use libcap?
USESYSTEMD= # Make use of systemd socket activation
@@ -30,10 +32,11 @@ endif
CC ?= gcc
AR ?= ar
CFLAGS ?=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
LIBS=-lm -lpcre2-8
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o
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
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)
@@ -71,8 +74,11 @@ ifneq ($(strip $(USELIBBSD)),)
CPPFLAGS+=-DLIBBSD
endif
ifneq ($(strip $(USELIBEV)),)
CONDITIONAL_TARGETS+=sslh-ev
endif
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
%.o: %.c %.h version.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
@@ -83,13 +89,15 @@ $(OBJS_A): $(OBJS)
version.h:
./genver.sh >version.h
sslh: sslh-fork sslh-select sslh-ev
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
sslh-conf.c sslh-conf.h: sslhconf.cfg
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)
@@ -104,11 +112,14 @@ 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
conf2struct 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)
@@ -128,7 +139,7 @@ docker-clean:
yes | docker image prune
# generic install: install binary and man page
install: sslh $(MAN)
install: sslh-fork $(MAN)
mkdir -p $(DESTDIR)/$(BINDIR)
mkdir -p $(DESTDIR)/$(MANDIR)
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
@@ -148,7 +159,7 @@ distclean: clean
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
clean:
rm -f sslh-fork sslh-select sslh-ev echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags --globals -T *.[ch]
+103 -12
View File
@@ -11,7 +11,7 @@ protocol that can be tested using a regular expression, can
be recognised. A typical use case is to allow serving
several services on port 443 (e.g. to connect to SSH from
inside a corporate firewall, which almost never block port
443) while still serving HTTPS on that port.
443) while still serving HTTPS on that port.
Hence `sslh` acts as a protocol demultiplexer, or a
switchboard. With the SNI and ALPN probe, it makes a good
@@ -20,9 +20,10 @@ address.
`sslh` has the bells and whistles expected from a mature
daemon: privilege and capabilities dropping, inetd support,
systemd support, transparent proxying, chroot, logging,
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
model, and more.
systemd support, transparent proxying, chroot, logging,
IPv4 and IPv6, TCP and UDP, a fork-based, a select-based
model, and yet another based on libev for larger
installations.
Install
=======
@@ -35,6 +36,18 @@ Configuration
Please refer to the [configuration guide](doc/config.md).
Transparent proxying allows the target server to see the
original client IP address, i.e. `sslh` becomes invisible.
This makes it easier to use the server's logs, and potential
IP-based banning ability.
Configuration of transparent proxying is highly dependent on
your network environment and infrastructure setup. There is
no known generic approach, and if you do not find directions
for your exact setup, you will probably need an extensive
knowledge of network management and iptables setup".
It is described in its own [document](doc/tproxy.md).
Docker image
@@ -44,16 +57,15 @@ How to use
---
Build docker image
make docker
```bash
docker run \
--cap-add CAP_NET_RAW \
--cap-add CAP_NET_BIND_SERVICE \
--rm \
-it \
sslh:latest \
ghcr.io/yrutschle/sslh:latest \
--foreground \
--listen=0.0.0.0:443 \
--ssh=hostname:22 \
--tls=hostname:443
@@ -61,16 +73,16 @@ docker run \
docker-compose example
```
```yaml
version: "3"
services:
sslh:
image: sslh:latest
image: ghcr.io/yrutschle/sslh:latest
hostname: sslh
ports:
- 443:443
command: --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
depends_on:
- nginx
- openvpn
@@ -82,6 +94,85 @@ services:
image: openvpn
```
Transparent mode 1: using sslh container for networking
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
container_name: sslh
environment:
- TZ=${TZ}
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
sysctls:
- net.ipv4.conf.default.route_localnet=1
- net.ipv4.conf.all.route_localnet=1
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
ports:
- 443:443 #sslh
- 80:80 #nginx
- 8443:8443 #nginx
- 1194:1194 #openvpn
extra_hosts:
- localbox:host-gateway
restart: unless-stopped
nginx:
image: nginx:latest
.....
network_mode: service:sslh #set nginx container to use sslh networking.
# ^^^ This is required. This makes nginx reachable by sslh via localhost
openvpn:
image: openvpn:latest
.....
network_mode: service:sslh #set openvpn container to use sslh networking
```
Transparent mode 2: using host networking
```yaml
version: "3"
services:
sslh:
build: https://github.com/yrutschle/sslh.git
container_name: sslh
environment:
- TZ=${TZ}
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
# must be set manually
#sysctls:
# - net.ipv4.conf.default.route_localnet=1
# - net.ipv4.conf.all.route_localnet=1
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
network_mode: host
restart: unless-stopped
nginx:
image: nginx:latest
.....
ports:
- 8443:8443 # bind to docker host on port 8443
openvpn:
image: openvpn:latest
.....
ports:
- 1194:1194 # bind to docker host on port 1194
```
Comments? Questions?
====================
+13 -13
View File
@@ -2876,9 +2876,9 @@ static void arg_file_resetfn(struct arg_file* parent) {
static const char* arg_basename(const char* filename) {
const char *result = NULL, *result1, *result2;
/* Find the last occurrence of eother file separator character. */
/* Two alternative file separator chars are supported as legal */
/* file separators but not both together in the same filename. */
/* Find the last occurrence of other file separator character. */
/* Two alternative file separator chars are supported as legal */
/* file separators but not both together in the same filename. */
result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL);
result2 = (filename ? strrchr(filename, FILESEPARATOR2) : NULL);
@@ -2927,7 +2927,7 @@ static int arg_file_scanfn(struct arg_file* parent, const char* argval) {
} else if (!argval) {
/* a valid argument with no argument value was given. */
/* This happens when an optional argument value was invoked. */
/* leave parent arguiment value unaltered but still count the argument. */
/* leave parent argument value unaltered but still count the argument. */
parent->count++;
} else {
parent->filename[parent->count] = argval;
@@ -3173,7 +3173,7 @@ static int arg_int_scanfn(struct arg_int* parent, const char* argval) {
} else if (!argval) {
/* a valid argument with no argument value was given. */
/* This happens when an optional argument value was invoked. */
/* leave parent arguiment value unaltered but still count the argument. */
/* leave parent argument value unaltered but still count the argument. */
parent->count++;
} else {
long int val;
@@ -3813,8 +3813,8 @@ static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_O
#endif
#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */
#define OP_OR (MAX_CHAR + 2)
#define OP_EXPR (MAX_CHAR + 3) /* parentesis () */
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */
#define OP_EXPR (MAX_CHAR + 3) /* parenthesis () */
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parenthesis (?:) */
#define OP_DOT (MAX_CHAR + 5)
#define OP_CLASS (MAX_CHAR + 6)
#define OP_CCLASS (MAX_CHAR + 7)
@@ -5313,7 +5313,7 @@ static void arg_parse_untagged(int argc, char** argv, struct arg_hdr** table, st
}
}
/* if a tenative error still remains at this point then register it as a proper error */
/* if a tentative error still remains at this point then register it as a proper error */
if (errorlast) {
arg_register_error(endtable, parentlast, errorlast, optarglast);
optind++;
@@ -5384,7 +5384,7 @@ int arg_parse(int argc, char** argv, void** argtable) {
/*
Fill in the local copy of argv[]. We need a local copy
because getopt rearranges argv[] which adversely affects
susbsequent parsing attempts.
subsequent parsing attempts.
*/
for (i = 0; i < argc; i++)
argvcopy[i] = argv[i];
@@ -5451,7 +5451,7 @@ static void arg_cat_option(char* dest, size_t ndest, const char* shortopts, cons
if (shortopts) {
char option[3];
/* note: option array[] is initialiazed dynamically here to satisfy */
/* note: option array[] is initialized dynamically here to satisfy */
/* a deficiency in the watcom compiler wrt static array initializers. */
option[0] = '-';
option[1] = shortopts[0];
@@ -5509,7 +5509,7 @@ static void arg_cat_optionv(char* dest, size_t ndest, const char* shortopts, con
/* "-a|-b|-c" */
char shortopt[3];
/* note: shortopt array[] is initialiazed dynamically here to satisfy */
/* note: shortopt array[] is initialized dynamically here to satisfy */
/* a deficiency in the watcom compiler wrt static array initializers. */
shortopt[0] = '-';
shortopt[1] = *c;
@@ -5881,7 +5881,7 @@ static void arg_print_formatted_ds(arg_dstr_t ds, const unsigned lmargin, const
* Prints the glossary in strict GNU format.
* Differences to arg_print_glossary() are:
* - wraps lines after 80 chars
* - indents lines without shortops
* - indents lines without shortopts
* - does not accept formatstrings
*
* Contributed by Uli Fouquet
@@ -5956,7 +5956,7 @@ int arg_nullcheck(void** argtable) {
* that entry were still allocated ok. Those subsequent allocations will not be
* deallocated by arg_free().
* Despite the unlikeliness of the problem occurring, and the even unlikelier event
* that it has any deliterious effect, it is fixed regardless by replacing arg_free()
* that it has any deleterious effect, it is fixed regardless by replacing arg_free()
* with the newer arg_freetable() function.
* We still keep arg_free() for backwards compatibility.
*/
+2 -2
View File
@@ -87,7 +87,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
* that particular arg_xxx arguments, performing post-parse checks, and
* reporting errors.
* These functions are private to the individual arg_xxx source code
* and are the pointer to them are initiliased by that arg_xxx struct's
* 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.
@@ -95,7 +95,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
typedef struct arg_hdr {
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
const char* shortopts; /* String defining the short options */
const char* longopts; /* String defiing the long 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 */
+5 -3
View File
@@ -11,9 +11,13 @@ pidfile: "/var/run/sslh.pid";
# Change hostname with your external address name, or the IP
# of the interface that receives connections
# Default is to bind all interfaces. httpd can be started
# first to bind on localhost, in which case sslh will bind
# only other interfaces.
listen:
(
{ host: "thelonious"; port: "443"; }
{ host: "0.0.0.0"; port: "443"; },
{ host: "[::]"; port: "443"; }
);
@@ -24,8 +28,6 @@ protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);
+42 -20
View File
@@ -110,7 +110,7 @@ int make_listen_tfo(int s)
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
}
/* Starts listening on a single address
/* Starts listening on a single address
* Returns a socket filehandle, or dies with message in case of major error */
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
{
@@ -262,7 +262,7 @@ int bind_peer(int fd, int fd_from)
/* if the destination is the same machine, there's no need to do bind */
if (is_same_machine(&from))
return 0;
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT");
@@ -278,7 +278,26 @@ int bind_peer(int fd, int fd_from)
}
#endif /* IP_TRANSPARENT / IP_BINDANY */
res = bind(fd, from.ai_addr, from.ai_addrlen);
CHECK_RES_RETURN(res, "bind", res);
if (res == -1) {
if (errno != EADDRINUSE) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__,
"bind", errno, strerror(errno));
return res;
}
/*
* If there is more than one transparent mode proxy going on, such as
* using sslh as the target of stunnel also in transparent mode, then
* the (ip,port) combination will already be bound for the previous application.
* In that case, the best we can do is bind with a different port.
* This does mean the local server can't use the ident protocol as the port will
* have changed, but most people won't care.
* Also note that stunnel uses the same logic for the same situation.
*/
((struct sockaddr_in *)from.ai_addr)->sin_port = 0;
res = bind(fd, from.ai_addr, from.ai_addrlen);
CHECK_RES_RETURN(res, "bind", res);
}
return 0;
}
@@ -348,6 +367,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
if (transparent) {
res = bind_peer(fd, fd_from);
if (res == -1) close(fd);
CHECK_RES_RETURN(res, "bind_peer", res);
}
res = connect(fd, a->ai_addr, a->ai_addrlen);
@@ -371,7 +391,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
}
/* Store some data to write to the queue later */
int defer_write(struct queue *q, void* data, int data_size)
int defer_write(struct queue *q, void* data, ssize_t data_size)
{
char *p;
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
@@ -383,7 +403,7 @@ int defer_write(struct queue *q, void* data, int data_size)
q->begin_deferred_data = p;
q->deferred_data = p + data_offset;
p += data_offset + q->deferred_data_size;
q->deferred_data_size += data_size;
q->deferred_data_size += (int)data_size;
memcpy(p, data, data_size);
return 0;
@@ -395,13 +415,13 @@ int defer_write(struct queue *q, void* data, int data_size)
* */
int flush_deferred(struct queue *q)
{
int n;
ssize_t n;
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
n = write(q->fd, q->deferred_data, q->deferred_data_size);
if (n == -1)
return n;
return (int)n;
if (n == q->deferred_data_size) {
/* All has been written -- release the memory */
@@ -412,10 +432,10 @@ int flush_deferred(struct queue *q)
} else {
/* There is data left */
q->deferred_data += n;
q->deferred_data_size -= n;
q->deferred_data_size -= (int)n;
}
return n;
return (int)n;
}
@@ -450,7 +470,8 @@ void dump_connection(struct connection *cnx)
int fd2fd(struct queue *target_q, struct queue *from_q)
{
char buffer[BUFSIZ];
int target, from, size_r, size_w;
int target, from;
ssize_t size_r, size_w;
target = target_q->fd;
from = from_q->fd;
@@ -462,6 +483,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
return FD_NODATA;
case ECONNRESET:
case ENOTSOCK:
case EPIPE:
return FD_CNXCLOSED;
}
@@ -494,7 +516,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
return size_w;
return (int)size_w;
}
/* returns a string that prints the IP and port of the sockaddr */
@@ -529,7 +551,8 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
}
/* Turns a hostname and port (or service) into a list of struct addrinfo
* returns 0 on success, -1 otherwise and logs error
* On success, returns 0
* On failure, returns -1 or one of getaddrinfo() codes
*/
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
{
@@ -555,7 +578,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
res = getaddrinfo(host, serv, &hint, out);
if (res)
print_message(msg_system_error, "%s `%s:%s'\n", gai_strerror(res), host, serv);
print_message(msg_system_error, "resolve_split_name: %s `%s:%s'\n", gai_strerror(res), host, serv);
return res;
}
@@ -831,20 +854,19 @@ void write_pid_file(const char* pidfile)
f = fopen(pidfile, "w");
if (!f) {
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
exit(3);
print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno));
return;
}
res = fprintf(f, "%d\n", getpid());
if (res < 0) {
print_message(msg_system_error, "write_pid_file:fprintf:%s", strerror(errno));
exit(3);
print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno));
return;
}
res = fclose(f);
if (res == EOF) {
print_message(msg_system_error, "write_pid_file:fclose:%s", strerror(errno));
exit(3);
print_message(msg_system_error, "write_pid_file: fclose: %s\n", strerror(errno));
return;
}
}
+7 -2
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
@@ -112,7 +113,7 @@ struct connection {
struct queue q[2];
/* SOCK_DGRAM */
struct sockaddr client_addr; /* Contains the remote client address */
struct sockaddr_storage client_addr; /* Contains the remote client address */
socklen_t addrlen;
int local_endpoint; /* Contains the local address */
@@ -171,7 +172,7 @@ int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
int start_listen_sockets(struct listen_endpoint *sockfd[]);
int defer_write(struct queue *q, void* data, int data_size);
int defer_write(struct queue *q, void* data, ssize_t data_size);
int flush_deferred(struct queue *q);
extern struct sslhcfg_item cfg;
@@ -183,4 +184,8 @@ void start_shoveler(int);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
/* landlock.c */
void setup_landlock(void);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef CONFIG_H
/* Template for config.h, filled by `configure`. */
/* Landlock sandboxing Linux LSM */
#undef HAVE_LANDLOCK
#endif
Vendored Executable
+4469
View File
File diff suppressed because it is too large. Load diff
+9
View File
@@ -0,0 +1,9 @@
dnl Use autoconf to generate the `configure` script from this and Makefile.in
AC_INIT
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_FILES([Makefile])
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
AC_OUTPUT
+97
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
+1 -1
View File
@@ -7,7 +7,7 @@ doesn't work, report how what was suggested here went.
It's also worth reading [how to ask
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
before posting on the mailing list or opening an issue in
Github.
GitHub.
Getting more info
=================
+89 -74
View File
@@ -1,3 +1,13 @@
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
===================
@@ -6,125 +16,130 @@ Dependencies
`sslh` uses:
* [libconfig](http://www.hyperrealm.com/libconfig/). For
Debian this is contained in package `libconfig-dev`. You
can compile with or without it using USELIBCONFIG in the
Makefile.
* [libconfig](http://www.hyperrealm.com/libconfig/).
For Debian this is contained in package `libconfig-dev`.
You can compile with or without it using USELIBCONFIG in the Makefile.
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, this is contained in packages
`libwrap0-dev`. You
can compile with or without it using USELIBWRAP in the
Makefile.
For Debian, this is contained in packages `libwrap0-dev`.
You can compile with or without it using USELIBWRAP in the Makefile.
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`. You
can compile with or without it using USESYSTEMD in the
Makefile.
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
You can compile with or without it using USESYSTEMD in the Makefile.
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`. You can compile with or without it using USELIBCAP in the Makefile
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
You can compile with or without it using USELIBCAP in the Makefile
* 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.
* libbsd, to enable to change the process name (as shown in `ps`,
so each forked process shows what protocol and what connection it is serving),
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
* libpcre2, in package `libpcre-dev`. You can compile
with or without it using ENABLE_REGEX in the Makefile.
* libpcre2, in package `libpcre2-dev`.
You can compile with or without it using ENABLE_REGEX in the Makefile.
* libev-dev, in package `libev-dev`. If you build a binary
specifically and do not build `sslh-ev`, you don't need
this.
* libev-dev, in package `libev-dev`.
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and
`libconfig-devel`:
For Fedora, you'll need packages `libconfig` and `libconfig-devel`:
yum install libconfig libconfig-devel
yum install libconfig libconfig-devel
If you want to rebuild `sslh-conf.c` (after a `make
distclean` for example), you will also need to add
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
If you want to rebuild `sslh-conf.c` (after a `make distclean` for example),
you will also need to add [conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
(v1.5) to your path.
The test scripts are written in Perl, and will require
IO::Socket::INET6 (libio-socket-inet6-perl in Debian).
`IO::Socket::INET6` (`libio-socket-inet6-perl` in Debian).
Compilation
-----------
After this, the Makefile should work:
make install
make install
There are a couple of configuration options at the beginning
of the Makefile:
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).
* `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).
* `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).
* `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`).
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`.
* `USELIBBSD` compiles support for updating the process name (as shown
by `ps`).
Binaries
--------
The Makefile produces three different executables: `sslh-fork`,
`sslh-select` and `sslh-ev`:
The Makefile produces three different executables:
`sslh-fork`, `sslh-select` and `sslh-ev`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes.
If you are going to use `sslh` for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
`sslh-fork` is probably more suited for you.
It is well-tested and very reliable, but incurs the overhead of many processes.
If you are going to use `sslh` for a "small" setup
(less than a dozen ssh connections and a low-traffic https server)
then `sslh-fork` is probably more suited for you.
* `sslh-select` uses only one thread, which monitors all
connections at once. It only incurs a 16 byte overhead per
connection. Also, if it stops, you'll lose all connections,
which means you can't upgrade it remotely. If you are going
to use `sslh` on a "medium" setup (a few hundreds of
connections), or if you are on a system where forking is
expensive (e.g. Windows), `sslh-select` will be better.
* `sslh-select` uses only one thread, which monitors all connections at once.
It only incurs a 16 byte overhead per connection.
Also, if it stops, you'll lose all connections,
which means you can't upgrade it remotely.
If you are going to use `sslh` on a "medium" setup (a few hundreds of connections),
or if you are on a system where forking is expensive (e.g. Windows),
`sslh-select` will be better.
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as a backend.
This allows using specific kernel APIs that
allow to manage thousands of connections concurrently.
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as
a backend. This allows using specific kernel APIs that
allow to manage thousands of connections concurrently.
Installation
------------
* In general:
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
```sh
./configure
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
```
* For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
```sh
cp scripts/etc.init.d.sslh /etc/init.d/sslh
```
* For CentOS:
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
```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:
update-rc.d sslh defaults
```sh
update-rc.d sslh defaults
```
+72
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).
+7 -9
View File
@@ -92,19 +92,15 @@ to the executable:
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
Then you can run sslh-select as an unpriviledged user, e.g.:
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 allows the target server to see the
original client IP address, i.e. `sslh` becomes invisible.
This makes it easier to use the server's logs, and potential
IP-based banning ability.
Set up can get complicated, so it has its own [document](tproxy.md).
Transparent proxying is described in its own
[document](tproxy.md).
Systemd Socket Activation
-------------------------
@@ -167,7 +163,7 @@ This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overriden by a /etc/systemd/system/sslh.socket file.
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
@@ -196,6 +192,8 @@ will wait for incoming UDP packets, run the probes in the
usual fashion, and forward packets to the appropriate
target. `sslh` will then remember the association between
remote host to target server for 60 seconds by default,
which can be overriden with `udp_timeout`. This allows to
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`.
+1 -1
View File
@@ -60,7 +60,7 @@ this scheme -- let me know if you manage that:
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:
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
+3 -3
View File
@@ -365,7 +365,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
return 0;
}
/* Copy the value of a string argument to arbitary memory
/* Copy the value of a string argument to arbitrary memory
* location that must be large enough, converting on the way
* (i.e. CFG_INT gets atoi() and so on) */
/* 0: success
@@ -862,7 +862,7 @@ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, con
if (pmatch[pmatch_cnt].rm_so == -1) {
/* This should not happen as regexec() did
* match before, unless there is a
* discrepency between the regex and the
* discrepancy between the regex and the
* number of backreferences */
return 0;
}
@@ -1155,7 +1155,7 @@ static void scalar_to_string(char** strp, config_setting_t* s)
/* Typesets all the settings in a configuration as a
* newly-allocated string. The string management is caller's
* responsability.
* responsibility.
* Returns the number of scalars in the configuration */
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
{
+6 -5
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,
@@ -62,9 +62,10 @@ void check_res_dump(int res, struct addrinfo *addr, char* syscall)
void start_echo(int fd)
{
int res;
ssize_t res;
char buffer[1 << 20];
int ret, prefix_len;
ssize_t ret;
size_t prefix_len;
int first = 1;
prefix_len = strlen(cfg.prefix);
@@ -151,7 +152,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
while (1) {
addrlen = sizeof(src_addr);
size_t len = recvfrom(listen_socket->socketfd,
ssize_t len = recvfrom(listen_socket->socketfd,
data + prefix_len,
sizeof(data) - prefix_len,
0,
@@ -166,7 +167,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
int res = sendto(listen_socket->socketfd,
ssize_t res = sendto(listen_socket->socketfd,
data,
len + prefix_len,
0,
+3 -1
View File
@@ -78,7 +78,9 @@ listen:
# 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
#
+5 -2
View File
@@ -48,7 +48,10 @@ gap_array* gap_init(int len)
gap->len = gap_len_alloc(elem_size);
if (gap->len < len) gap->len = len;
gap->array = malloc(gap->len * elem_size);
if (!gap->array) return NULL;
if (!gap->array) {
free(gap);
return NULL;
}
for (int i = 0; i < gap->len; i++)
gap->array[i] = NULL;
@@ -99,7 +102,7 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len)
else
return -1;
for (i = start; i < len; i++) {
for (i = start; i < len - 1; i++) {
gap->array[i] = gap->array[i+1];
}
+5
View File
@@ -32,6 +32,11 @@ static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, voi
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];
}
+5 -5
View File
@@ -10,14 +10,14 @@ fi
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
# If we don't have git, we can't work out what
# version this is. It must have been downloaded as a
# zip file.
# zip file.
# If downloaded from the release page, the directory
# has the version number.
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
if [ "x$release" = "x" ]; then
# If downloaded from the head, Github creates the
# If downloaded from the head, GitHub creates the
# zip file with all files dated from the last
# change: use the Makefile's modification time as a
# release number
@@ -28,7 +28,7 @@ fi
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
# generate the version info based on the tag
release=`(git describe --tags || git --describe || git describe --all --long) \
2>/dev/null | tr -d '\n'`
2>/dev/null | tr -s '/' '-' | tr -d '\n'`
# Are there uncommitted changes?
git update-index --refresh --unmerged > /dev/null
+1 -1
View File
@@ -5,7 +5,7 @@
# Tests scripts are in *.tst files.
# Corresponding output is put in *.out.
# Reference output is put in *.ref.
# Any discrepency will be reported!
# Any discrepancy will be reported!
use strict;
+156
View File
@@ -0,0 +1,156 @@
/*
* 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;
}
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;
}
/* Access to libraries, to be able to fork */
add_path_ro(ruleset_fd, LL_TREE, "/lib");
add_path_ro(ruleset_fd, LL_TREE, "/usr/lib");
add_path_ro(ruleset_fd, LL_FILE, "/etc/ld.so.cache"); /* To avoid searching all libs... */
/* Files to resolve names (required when dynamic resolution is used) */
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts");
add_path_ro(ruleset_fd, LL_FILE, "/etc/resolv.conf");
add_path_ro(ruleset_fd, LL_FILE, "/etc/nsswitch.conf");
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 */
+9 -4
View File
@@ -107,10 +107,11 @@ void print_message(msg_info info, const char* str, ...)
{
va_list ap;
va_start(ap, str);
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd)
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);
@@ -194,7 +195,11 @@ void log_connection(struct connection_desc* desc, const struct connection *cnx)
if (!desc) {
desc = &d;
get_connection_desc(desc, cnx);
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",
+3 -3
View File
@@ -173,13 +173,13 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
* whereas the packet id is increased with every transmitted datagram.
*/
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))
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))
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)
@@ -323,7 +323,7 @@ static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
if (len < min_data_packet_size + sizeof(empty_message))
return PROBE_AGAIN;
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
if (memcmp(&p[0], empty_message, sizeof(empty_message)) != 0)
return PROBE_NEXT;
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
+1 -1
View File
@@ -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=
+1 -1
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]
+27
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
@@ -1,10 +1,10 @@
[Unit]
Description=SSL/SSH multiplexer
Description=SSL/SSH multiplexer (fork mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
+29 -3
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Sep 11 21:43:25 2022.
* on Tue Jan 2 21:25:47 2024.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2021 Yves Rutschle
@@ -471,6 +471,7 @@ struct arg_file* sslhcfg_conffile;
struct arg_str* sslhcfg_listen;
struct arg_str* sslhcfg_ssh;
struct arg_str* sslhcfg_tls;
struct arg_str* sslhcfg_ssl;
struct arg_str* sslhcfg_openvpn;
struct arg_str* sslhcfg_tinc;
struct arg_str* sslhcfg_wireguard;
@@ -1333,6 +1334,15 @@ static struct compound_cl_target sslhcfg_openvpn_targets [] = {
{ 0 }
};
static struct compound_cl_target sslhcfg_ssl_targets [] = {
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
{ 0 }
};
static struct compound_cl_target sslhcfg_tls_targets [] = {
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
@@ -1395,6 +1405,18 @@ static struct compound_cl_arg compound_cl_args[] = {
.override_const = "tls",
},
{ /* arg: ssl */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssl,
.base_entry = & table_sslhcfg [26],
.targets = sslhcfg_ssl_targets,
.override_desc = & table_sslhcfg_protocols [0],
.override_matchindex = 0,
.override_const = "tls",
},
{ /* arg: openvpn */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_openvpn,
@@ -2199,6 +2221,7 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
sslhcfg_ssh = arg_strn(NULL, "ssh", "<host:port>", 0, 10, "Set up ssh target"),
sslhcfg_tls = arg_strn(NULL, "tls", "<host:port>", 0, 10, "Set up TLS/SSL target"),
sslhcfg_ssl = arg_strn(NULL, "ssl", "<host:port>", 0, 10, "Set up TLS/SSL target"),
sslhcfg_openvpn = arg_strn(NULL, "openvpn", "<host:port>", 0, 10, "Set up OpenVPN target"),
sslhcfg_tinc = arg_strn(NULL, "tinc", "<host:port>", 0, 10, "Set up tinc target"),
sslhcfg_wireguard = arg_strn(NULL, "wireguard", "<host:port>", 0, 10, "Set up WireGuard target"),
@@ -2216,10 +2239,13 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
/* Parse command line */
nerrors = arg_parse(argc, argv, argtable);
if (nerrors) {
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
// print bad args
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
// print usage
arg_print_syntax(stdout, argtable, "\n");
// print options
arg_print_glossary(stdout, argtable, " %-25s\t%s\n");
return -1;
fprintf(stderr, "Invalid args are ignored, please fix them\n");
}
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Sep 11 21:43:25 2022.
* on Tue Jan 2 21:25:47 2024.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2021 Yves Rutschle
-6
View File
@@ -39,8 +39,6 @@ struct watchers {
struct listen_endpoint* listen_sockets;
gap_array* fd2ls; /* Array indexed by file descriptor, pointing to listen_sockets */
int max_fd; /* legacy to be removed, still required for UDP */
};
static void cnx_read_cb(EV_P_ ev_io *w, int revents);
@@ -80,8 +78,6 @@ void watchers_add_read(watchers* w, int fd)
gap_set(w->ev_ior, fd, io);
}
ev_io_start(loop, io);
if (fd > w->max_fd) w->max_fd = fd + 1;
}
void watchers_del_read(watchers* w, int fd)
@@ -101,8 +97,6 @@ void watchers_add_write(watchers* w, int fd)
gap_set(w->ev_iow, fd, io);
}
ev_io_start(loop, io);
if (fd > w->max_fd) w->max_fd = fd + 1;
}
void watchers_del_write(watchers* w, int fd)
+29
View File
@@ -164,6 +164,13 @@ void set_listen_procname(struct listen_endpoint *listen_socket)
}
/* At least MacOS does not know these two options, so define them to something
* equivalent for our use case */
#ifndef ENONET
#define ENONET EWOULDBLOCK
#endif
/* /MacOS kludge */
/* TCP listener: connections, fork a child for each new connection
* IN:
* endpoint: array of listening endpoint objects
@@ -177,6 +184,26 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
while (1) {
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
CHECK_RES_RETURN(in_socket, "accept", /*void*/ );
if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__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()) {
@@ -223,6 +250,8 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
else
tcp_listener(listen_sockets, num_addr_listen, i);
exit(0);
break;
/* We're in the parent, we don't need to do anything */
+17 -7
View File
@@ -37,6 +37,7 @@
#include "common.h"
#include "probe.h"
#include "log.h"
#include "tcp-probe.h"
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
@@ -60,20 +61,26 @@ static void printcaps(void) {
static void printsettings(void)
{
char buf[NI_MAXHOST];
char buf[NI_MAXHOST + 256]; /* 256 > " family %d %d" for reasonable ints */
int i;
struct sslhcfg_protocols_item *p;
for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i];
strcpy(buf, "resolve on forward");
if (!p->resolve_on_forward) {
sprintaddr(buf, sizeof(buf), p->saddr);
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 family %d %d [%s] [%s] [%s]\n",
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
p->name,
sprintaddr(buf, sizeof(buf), p->saddr),
buf,
p->service,
p->log_level,
p->saddr->ai_family,
p->saddr->ai_addr->sa_family,
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "",
p->transparent ? "transparent" : ""
@@ -92,7 +99,8 @@ static void printsettings(void)
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
#ifdef ENABLE_REGEX
{
int num_patterns, i, error;
size_t num_patterns, i;
int error;
pcre2_code** pattern_list;
PCRE2_SIZE error_offset;
PCRE2_UCHAR8 err_str[120];
@@ -180,7 +188,7 @@ void config_sanity_check(struct sslhcfg_item* cfg)
#endif
for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "tls")) {
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
if (cfg->protocols[i].sni_hostnames_len) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Config option sni_hostnames is only applicable for tls\n",
@@ -239,6 +247,7 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd)
{
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0);
exit(0);
}
@@ -277,6 +286,7 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.user || cfg.chroot)
drop_privileges(cfg.user, cfg.chroot);
setup_landlock();
printcaps();
+4 -4
View File
@@ -67,7 +67,7 @@ static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
void watchers_add_read(watchers* w, int fd)
{
FD_SET(fd, &w->fds_r);
if (fd > w->max_fd)
if (fd + 1 > w->max_fd)
w->max_fd = fd + 1;
}
@@ -148,7 +148,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
fd_info.watchers->max_fd, fd_info.num_probing);
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
NULL, fd_info.num_probing ? &tv : NULL);
if (res < 0)
perror("select");
@@ -180,9 +180,9 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
for (i = 0; i < fd_info.num_probing; i++) {
struct connection* cnx = gap_get(fd_info.probing_list, i);
if (!cnx || cnx->state != ST_PROBING) {
print_message(msg_int_error, "Inconsistent probing: cnx=%0xp\n", cnx);
print_message(msg_int_error, "Inconsistent probing: cnx=0x%p\n", cnx);
if (cnx)
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx);
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx->state);
exit(1);
}
if (cnx->probe_timeout < time(NULL)) {
+13
View File
@@ -186,6 +186,19 @@ cl_groups: (
{ path: "tfo_ok"; value: 1 }
);
},
# Redundant with the --tls setting before, for backwards compatibility
{ name: "ssl"; pattern: "(.+):(\w+)"; description: "Set up TLS/SSL target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "tls" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 },
{ path: "tfo_ok"; value: 1 }
);
},
{ name: "openvpn"; pattern: "(.+):(\w+)"; description: "Set up OpenVPN target";
list: "protocols";
override: "name";
+251 -85
View File
@@ -1,39 +1,72 @@
#include <libconfig.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <sys/stat.h>
#include "common.h"
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
static char* resolve_listen(const char *hostname, const char *port) {
/* Need room in the strcat for \0 and :
* the format in the socket unit file is hostname:port */
char *conn = malloc(strlen(hostname)+strlen(port)+2);
CHECK_ALLOC(conn, "malloc");
typedef struct FileList FileList;
struct FileList {
char *name;
struct FileList *prev;
};
static void free_file_list(FileList *fl) {
while (fl != NULL) {
FileList *prev = fl->prev;
free(fl->name);
free(fl);
fl = prev;
}
}
static FILE *err_log;
static bool systemd_invoked = false;
static const char
*socket_ext = ".socket",
*dropin_ext = ".conf",
*service_ext = ".service.d/",
*fork_unit_file = "/sslh@",
*select_unit_file = "/sslh-select@",
*fork_unit = "# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Requires=sslh@%s.socket\n"
"Conflicts=sslh-select@%s.service\n"
"PartOf=sslh@%s.socket\n",
*select_unit = "# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Requires=sslh@%s.socket\n"
"Conflicts=sslh@%s.service\n"
"PartOf=sslh@%s.socket\n";
static char *resolve_listen(const char *hostname, const char *port) {
char *conn = calloc(1, strlen(hostname) + strlen(port) + 2);
CHECK_ALLOC(conn, "malloc")
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
return conn;
}
static int get_listen_from_conf(const char *filename, char **listen[]) {
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
fprintf(err_log,
"systemd-sslh-generator: %s%d%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
@@ -42,114 +75,247 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
} else {
setting = config_lookup(&config, "listen");
if (setting) {
int i;
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
CHECK_ALLOC(*listen, "malloc");
for (i = 0; i < len; i++) {
CHECK_ALLOC(*listen, "malloc")
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
if (!(config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) {
fprintf(err_log,
"systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
CHECK_ALLOC((*listen)[i], "malloc");
strcpy((*listen)[i], resolve_listen(hostname, port));
(*listen)[i] = resolve_listen(hostname, port);
}
}
}
}
return len;
}
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
int i;
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"Before=sslh@%s.service\n"
"SourcePath=%s\n"
"PartOf=sslh@%s.service\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (i = 0; i < num_addr; i++) {
cfg,
source,
cfg);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
fprintf(socket,
"\n[Install]\n"
"WantedBy=sockets.target\n");
}
static int write_unit_dropin(const char *runtime_unit_dir, const bool is_fork, const char *cfg) {
const char
*unit_file = is_fork ? fork_unit_file : select_unit_file,
*unit_string = is_fork ? fork_unit : select_unit;
FILE *dropin_fd = stdout;
if (systemd_invoked) {
//Systemd drop-in configuration for the base select service sslh@%I.service
const size_t runtime_len = strlen(runtime_unit_dir);
size_t len = strlen(unit_file) + strlen(cfg) + strlen(service_ext) + strlen(dropin_ext);
char dropin_dir[runtime_len + len + 1];
strcpy(dropin_dir, runtime_unit_dir);
strcat(dropin_dir, unit_file);
strcat(dropin_dir, cfg);
strcat(dropin_dir, service_ext);
if (mkdir(dropin_dir, S_IRWXU | S_IRWXG | S_IROTH)) {
fprintf(err_log,
"systemd-sslh-generator: Could not create directory '%s' to generate drop-in configuration: %s\n",
dropin_dir,
strerror(errno));
return errno;
}
len = len + strlen(cfg) + strlen(dropin_ext);
char dropin_path[len + 1];
strcpy(dropin_path, dropin_dir);
strcat(dropin_path, cfg);
strcat(dropin_path, dropin_ext);
dropin_fd = fopen(dropin_path, "w");
if (!dropin_fd) {
fprintf(err_log,
"systemd-sslh-generator: Could not open '%s' to generate drop-in configuration: %s\n",
dropin_path,
strerror(errno));
return errno;
}
}
fprintf(dropin_fd, unit_string, cfg, cfg, cfg);
if (systemd_invoked) {
fclose(dropin_fd);
}
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
int status = 0;
const char *config_dir = "/etc/sslh/";
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
DIR *d = opendir(config_dir);
FileList *fa = NULL;
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
if (d) {
struct dirent *dir;
while ((dir = readdir(d)) != NULL) {
if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
continue;
}
FileList *lfa = malloc(sizeof(FileList));
CHECK_ALLOC(lfa, "malloc")
lfa->name = malloc(strlen(dir->d_name) + 1);
CHECK_ALLOC(lfa->name, "malloc")
strcpy(lfa->name, dir->d_name);
lfa->prev = NULL;
if (fa) {
lfa->prev = fa;
}
fa = lfa;
}
closedir(d);
} else {
fprintf(err_log,
"systemd-sslh-generator: Configuration directory '/etc/sslh/' does not exist! No units generated.\n");
//Config directory /etc/sslh/ does not exist
return 0;
}
fclose(config);
num_addr = get_listen_from_conf(sslh_conf, &listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir && *runtime_unit_dir) {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
CHECK_ALLOC(runtime_conf, "malloc");
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
if (!fa) {
fprintf(err_log,
"systemd-sslh-generator: Configuration directory '/etc/sslh/' is empty! No units generated.\n");
//No configuration files in /etc/sslh/
return 0;
}
FileList *fa_ref = fa;
// size_t num_listen_addresses = 0;
// char **listen_addresses = NULL;
//Process all config files
do {
char *end = strstr(fa->name, ".cfg");
if (!end) {
continue;
}
//Current sslh config name
const size_t end_count = end - fa->name;
char config_name[end_count + 1];
memcpy(config_name, fa->name, end_count);
config_name[end_count] = '\0';
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
//Full path to current sslh config
char full_path[strlen(config_dir) + strlen(fa->name) + 1];
strcpy(full_path, config_dir);
strcat(full_path, fa->name);
FILE *config = fopen(full_path, "r");
if (!config) {
fprintf(err_log,
"systemd-sslh-generator: Could not open config file '%s': %s\n",
full_path,
strerror(errno));
return errno;
} else {
fclose(config);
int num_addr = get_listen_from_conf(full_path, &listen);
if (num_addr <= 0) {
fprintf(err_log,
"systemd-sslh-generator: sslh config '%s' contains no valid listen configurations!\n",
fa->name);
status |= -1;
continue;
}
FILE *socket_fd = stdout;
if (systemd_invoked) {
//Systemd socket for the current sslh config
const size_t len = strlen(runtime_unit_dir) + strlen(fork_unit_file) + strlen(config_name) + strlen(socket_ext);
char socket_path[len + 1];
strcpy(socket_path, runtime_unit_dir);
strcat(socket_path, fork_unit_file);
strcat(socket_path, config_name);
strcat(socket_path, socket_ext);
socket_fd = fopen(socket_path, "w");
if (!socket_fd) {
fprintf(err_log,
"systemd-sslh-generator: Could not open '%s' to generate socket configuration: %s\n",
socket_path,
strerror(errno));
status |= errno;
continue;
}
}
//Write socket unit
write_socket_unit(socket_fd, listen, num_addr, config_name, full_path);
//Write forking drop-in config
status |= write_unit_dropin(runtime_unit_dir, false, config_name);
//Write select drop-in config
status |= write_unit_dropin(runtime_unit_dir, true, config_name);
if (systemd_invoked) {
fclose(socket_fd);
}
// if (listen_addresses) {
// //Check for overlapping addresses
// for (size_t i = 0; i < num_listen_addresses; i++) {
// for (size_t j = 0; j < num_addr; j++) {
// if (strcmp(*(listen_addresses + i), *(listen + j)) == 0) {
// fprintf(err_log, "systemd-sslh-generator: Overlapping listen addresses across sslh configurations!");
// return -1;
// }
// }
// }
// }
// char *listen_addresses_copy[num_listen_addresses + num_addr];
// if (listen_addresses) {
// //Copy previous configurations' listen addresses into temp
// memcpy(listen_addresses_copy, listen_addresses, num_listen_addresses);
// //Free global list of listen addresses
// free(listen_addresses);
// }
// //Copy listen addresses from current configuration into temp
// memcpy(listen_addresses_copy + num_listen_addresses, listen, num_addr);
// num_listen_addresses += num_addr;
// listen_addresses = malloc(num_listen_addresses * sizeof(char *));
// CHECK_ALLOC(listen_addresses, "malloc")
// //Append all addresses to global list
// memcpy(listen_addresses, listen_addresses_copy, num_listen_addresses);
//Free all allocated listen strings
for (size_t i = 0; i < num_addr; i++) {
free(*(listen + i));
}
}
} while((fa = fa->prev));
free_file_list(fa_ref);
return status;
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
int main(int argc, char *argv[]) {
if (argc == 1 || argc == 4) {
systemd_invoked = argc == 4;
if (systemd_invoked) {
err_log = fopen("/dev/kmsg", "w");
if (!err_log) {
return -1;
}
} else {
err_log = stderr;
}
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
if (systemd_invoked) {
fclose(err_log);
}
if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
}
return r < 0 ? -1 : 0;
} else {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}
+1 -1
View File
@@ -396,7 +396,7 @@ if ($RB_RESOLVE_ADDRESS) {
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v 3 -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
exec "./sslh-select -v 3 -f -u $user --listen blahblah.nonexistent:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
+4 -4
View File
@@ -14,7 +14,7 @@
use strict;
use IO::Socket::INET6;
use Data::Dumper;
use Conf::Libconfig;
use Conf::Libconfig 1.0.3;
## BEGIN TEST CONFIG
@@ -44,7 +44,7 @@ $conf->read_file("test.cfg");
# Pick one address for TCP and one for UDP
my @listen = @{$conf->fetch_array("listen")};
my @listen = @{$conf->value("listen")};
my ($sslh_tcp_address, $sslh_udp_address);
foreach my $l (@listen) {
@@ -186,7 +186,7 @@ sub udp_client {
}
}
foreach my $p (@{$conf->fetch_array("protocols")}) {
foreach my $p (@{$conf->value("protocols")}) {
if (!fork) {
my $udp = $p->{is_udp} ? "--udp" : "";
my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null";
@@ -203,7 +203,7 @@ sleep 2; # Let echosrv's and sslh start
my ($c_in, $c_out);
pipe $c_in, $c_out;
my @protocols = @{$conf->fetch_array("protocols")};
my @protocols = @{$conf->value("protocols")};
if (!fork) {
# Process that starts all the clients
+4 -6
View File
@@ -67,14 +67,16 @@ static void shovel(struct connection *cnx, int active_fd, struct loop_info* fd_i
}
/* Returns the queue index that contains the specified file descriptor */
/* Returns the queue index that contains the specified file descriptor.
* This is called by the *_process functions, which got cnx from the fd, so it
* is impossible for fd to not be cnx, hence we die if it happens */
static int active_queue(struct connection* cnx, int fd)
{
if (cnx->q[0].fd == fd) return 0;
if (cnx->q[1].fd == fd) return 1;
print_message(msg_int_error, "file descriptor %d not found in connection object\n", fd);
return -1;
exit(1);
}
/* Process a TCP read event on the specified file descriptor */
@@ -156,13 +158,9 @@ static int connect_queue(struct connection* cnx,
log_connection(NULL, cnx);
flush_deferred(q);
if (q->deferred_data) {
/*
FD_SET(q->fd, &fd_info->watchers->fds_w);
FD_CLR(cnx->q[0].fd, &fd_info->watchers->fds_r); */
watchers_add_write(fd_info->watchers, q->fd);
watchers_del_read(fd_info->watchers, cnx->q[0].fd);
}
/* FD_SET(q->fd, &fd_info->watchers->fds_r); */
watchers_add_read(fd_info->watchers, q->fd);
collection_add_fd(fd_info->collection, cnx, q->fd);
return q->fd;
+3 -2
View File
@@ -60,12 +60,13 @@ int probe_client_protocol(struct connection *cnx)
static void tcp_protocol_list_init(void)
{
tcp_protocols = calloc(cfg.protocols_len, sizeof(tcp_protocols));
CHECK_ALLOC(tcp_protocols, "tcp_protocols");
for (int i = 0; i < cfg.protocols_len; i++) {
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
if (!p->is_udp) {
tcp_protocols[tcp_protocols_len] = p;
tcp_protocols_len++;
tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols));
tcp_protocols[tcp_protocols_len-1] = p;
}
}
}
+2 -1
View File
@@ -42,7 +42,7 @@ listen:
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; },
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "http"; host: "localhost"; port: "9002"; },
{ name: "tinc"; host: "localhost"; port: "9003"; },
@@ -53,6 +53,7 @@ protocols:
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
udp_timeout: 30;
regex_patterns: [ "^foo" ];
resolve_on_forward: true;
},
{ name: "regex"; host: "localhost"; port: "9011";
regex_patterns: [ "^foo", "^bar" ];
+4 -4
View File
@@ -224,7 +224,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
switch (data[pos]) { /* name type */
case 0x00: /* host_name */
if(has_match(tls_data->sni_hostname_list, tls_data->sni_list_len, data + pos + 3, len)) {
return len;
return (int)len;
} else {
return TLS_ENOEXT;
}
@@ -253,7 +253,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
return TLS_EPROTOCOL;
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
return len;
return (int)len;
} else if (len > 0) {
print_message(msg_probe_error, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
}
@@ -301,11 +301,11 @@ struct TLSProtocol *
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, const char** list, size_t list_len) {
if (alpn) {
tls_data->alpn_protocol_list = list;
tls_data->alpn_list_len = list_len;
tls_data->alpn_list_len = (int)list_len;
tls_data->match_mode.tls_match_alpn = 1;
} else {
tls_data->sni_hostname_list = list;
tls_data->sni_list_len = list_len;
tls_data->sni_list_len = (int)list_len;
tls_data->match_mode.tls_match_sni = 1;
}
+41 -18
View File
@@ -36,10 +36,10 @@
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
{
struct sockaddr* addr1 = &cnx1->client_addr;
struct sockaddr_storage* addr1 = &cnx1->client_addr;
socklen_t addrlen1 = cnx1->addrlen;
struct sockaddr* addr2 = &cnx2->client_addr;
struct sockaddr_storage* addr2 = &cnx2->client_addr;
socklen_t addrlen2 = cnx2->addrlen;
if (addrlen1 != addrlen2) return -1;
@@ -52,13 +52,13 @@ static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
* lowest bytes of remote port */
static int hash_make_key(hash_item new)
{
struct sockaddr* addr = &new->client_addr;
struct sockaddr_storage* addr = &new->client_addr;
//socklen_t addrlen = new->addrlen;
struct sockaddr_in* addr4;
struct sockaddr_in6* addr6;
int out;
switch (addr->sa_family) {
switch (((struct sockaddr*)addr)->sa_family) {
case AF_INET:
addr4 = (struct sockaddr_in*)addr;
out = addr4->sin_port;
@@ -86,13 +86,14 @@ static void udp_protocol_list_init(void)
if (p->is_udp) {
udp_protocols_len++;
udp_protocols = realloc(udp_protocols, udp_protocols_len * sizeof(*udp_protocols));
CHECK_ALLOC(udp_protocols, "realloc");
udp_protocols[udp_protocols_len-1] = p;
}
}
}
/* Configuration sanity check for UDP:
* - If there is a listening addres, there must be at least one target
* - If there is a listening address, there must be at least one target
*/
static void udp_sanity_check(void)
{
@@ -129,7 +130,7 @@ void udp_init(struct loop_info* fd_info)
* If yes, returns file descriptor of connection
* If not, returns -1
* */
static int known_source(hash* h, struct sockaddr* addr, socklen_t addrlen)
static int known_source(hash* h, struct sockaddr_storage* addr, socklen_t addrlen)
{
struct connection search;
search.client_addr = *addr;
@@ -221,6 +222,28 @@ static void mark_active(struct connection* cnx)
}
/* Creates a new non-blocking socket */
static int nonblocking_socket(struct sslhcfg_protocols_item* proto)
{
int res;
if (proto->resolve_on_forward) {
res = resolve_split_name(&(proto->saddr), proto->host,
proto->port);
if (res) return -1;
}
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
res = set_nonblock(out);
if (res == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, "udp:socket:nonblock", errno, strerror(errno));
close(out);
return -1;
}
return out;
}
/* Process UDP coming from outside (client towards server)
* If it's a new source, probe; otherwise, forward to previous target
* Returns: newly allocate connections, for new connections
@@ -229,15 +252,16 @@ static void mark_active(struct connection* cnx)
struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
{
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
struct sockaddr src_addr;
struct sockaddr_storage src_addr;
struct addrinfo addrinfo;
struct sslhcfg_protocols_item* proto;
cnx_collection* collection = fd_info->collection;
struct connection* cnx;
ssize_t len;
socklen_t addrlen;
int res, target, out = -1;
char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
ssize_t res;
int target, out = -1;
char data[65536]; /* Theoretical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
malloc/free overhead for each packet, when really 64K is not that much */
@@ -245,19 +269,19 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
udp_timeouts(fd_info);
addrlen = sizeof(src_addr);
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
if (len < 0) {
perror("recvfrom");
return NULL;
}
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
addrinfo.ai_addr = &src_addr;
addrinfo.ai_addr = (struct sockaddr*) &src_addr;
addrinfo.ai_addrlen = addrlen;
print_message(msg_probe_info, "received %ld UDP from %d:%s\n",
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
if (target == -1) {
res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
res = probe_buffer(data, (int)len, udp_protocols, udp_protocols_len, &proto);
/* First version: if we can't work out the protocol from the first
* packet, drop it. Conceivably, we could store several packets to
* run probes on packet sets */
@@ -266,9 +290,8 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
return NULL;
}
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
res = set_nonblock(out);
CHECK_RES_RETURN(res, "udp:socket:nonblock", NULL);
out = nonblocking_socket(proto);
if (out == -1) return NULL;
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
if (!cnx) return NULL;
target = out;
@@ -281,7 +304,7 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
res = new_source(fd_info->hash_sources, cnx);
if (res == -1) {
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increaѕe udp_max_connections");
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increase udp_max_connections");
collection_remove_cnx(collection, cnx);
return NULL;
}
@@ -302,13 +325,13 @@ void udp_s2c_forward(struct connection* cnx)
{
int sockfd = cnx->target_sock;
char data[65536];
int res;
ssize_t res;
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
CHECK_RES_DIE(res, "udp_listener/recvfrom");
res = sendto(cnx->local_endpoint, data, res, 0,
&cnx->client_addr, cnx->addrlen);
(struct sockaddr*)&cnx->client_addr, cnx->addrlen);
mark_active(cnx);
}