Compare commits

...
271 Commits
Author SHA1 Message Date
Yves Rutschle 6aa19d080a updated version numbers for release 2021-08-17 21:55:18 +02:00
yrutschle a43385b8db remove hardcoded quick50 and teamspeak. to be replaced with a generalised regex setup to come in next version 2021-08-13 18:03:59 +02:00
yrutschle 1261a5f4c7 updated some references to obsolete ssl option 2021-08-06 22:43:38 +02:00
yrutschle 40da147efd keep track of next UDP timeout to only go through all connections when that happens 2021-08-06 22:29:00 +02:00
yrutschle e4936454c5 refactor: take time only once when computing UPD timeouts 2021-08-05 17:05:08 +02:00
yrutschle 63f9c4a582 added syslog probe (fixes #34) 2021-08-04 15:29:05 +02:00
yrutschle 0e45107797 advertise new UDP protocols 2021-08-01 22:02:42 +02:00
yrutschle 4584e719e1 added hardcoded probe for Teamspeak3 2021-08-01 22:01:59 +02:00
yrutschle 75e426c80f added hardcoded probe for QUICK50 2021-08-01 21:35:24 +02:00
yrutschle e40d43bea5 removed obsolete ssl test pattern 2021-08-01 21:27:04 +02:00
yrutschle 2196146224 Merge branch 'master' of https://github.com/yrutschle/sslh 2021-08-01 21:25:39 +02:00
Yves Rutschle 2f822741b3 Merge pull request #297 from jeremiejig/master
update doc CAP_NET_RAW, remove CAP_SETUID/SETGID
2021-08-01 21:25:05 +02:00
Jeremiejig 760def3444 update doc CAP_NET_RAW, remove CAP_SETUID/SETGID
Update documentation CAP_NET_ADMIN -> CAP_NET_RAW.

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

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

So append -lpcre after -lpcreposix

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

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

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

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

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

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

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

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

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

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

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

    common.c:122:14: warning: Call to 'malloc' has an allocation size of 0 bytes
       *sockfd = malloc(num_addr * sizeof(*sockfd[0]));
		 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2018-06-18 10:35:28 -04:00
yrutschle 336e8bb9d1 Merge pull request #186 from Revertron/patch-1
Fixed a typo
2018-06-14 16:36:13 +02:00
Roman 0ada00474b Fixed a typo
Just a typo in usage text.
2018-06-14 14:31:50 +02:00
Yves Rutschle 95e8f5731c document magic constant in socks5 probe 2018-06-13 12:40:02 +00:00
Yves Rutschle dfd9e14866 fix socks5 probe 2018-06-13 09:52:49 +02:00
yrutschle 552723cc5f Merge pull request #180 from sanmai/patch-1
Update README.md
2018-06-01 10:31:34 +02:00
Alexey Kopytko 09aaf39e25 Update README.md
Removed redundant dollar signs since they're not used anywhere else in the README.
2018-06-01 15:13:50 +09:00
Yves Rutschle 7acf9627ee fix memory leak when using transparent proxying 2018-05-29 12:38:57 +02:00
Yves Rutschle b8e63a4d9d fixed systemd config to correct path 2018-05-26 17:02:15 +00:00
yrutschle f451cc8bed Merge pull request #172 from WeirdCarrotMonster/master
Added support for socks5 protocol
2018-04-16 22:38:06 +02:00
Eugene Protozanov a43dd11fc9 Added support for socks5 protocol 2018-04-16 15:27:31 +04:00
45 changed files with 14455 additions and 1974 deletions

No files matched your search

+105
View File
@@ -1,3 +1,108 @@
v1.22: 17AUG2021
sslh-select now supports UDP protocols.
Probes specified in the `protocols`
configuration entry are tried on incoming packets,
TCP or UDP, and forwarded based on the input
protocol (an incoming TCP connection will be
forwarded as TCP, and same with UDP).
This has been tested with DNS as shown in udp.cfg:
incoming packets that contain my domain name are
assumed to be a DNS request and forwarded
accordingly. Note this could cause problems if
combined with incoming TLS with SNI. UDP clients
and servers need to agree on the IPv4/IPv6 they use:
use the same protocol on all sides! Often, this
means explicitely using 'ip4-localhost'.
UDP sender-receiver pairs (connections, so to speak)
are kept for 60s, which can be changed with
`udp_timeout` in the configuration.
Added probes for UDP protocols QUICK and Teamspeak.
Added probes for syslog protocol.
sslh-select refactored to change linear searches
through connections to linear searches through
fd_set.
Fixed a libconfig call to support libconfig 1.7.3.
Added symbol to support libconfig 1.4.9, still in
use in CentOS7.
Warn about unknown settings in the configuration
file.
Added per-protocol `transparent` option. sslh-fork
drops the capability after creating the server-side
transparent socket. Transparent now uses CAP_NET_RAW
instead of CAP_NET_ADMIN.
Removed compile-time option to use POSIX regex. Now
regex must be PCRE2 (Perl-Compatible). This was in
fact the case since v1.21, as PCRE are used to parse
the config file.
v1.21: 11JUL2020
WARNING:
Moved configuration and command-line management to
use conf2struct. Changes are:
* `--ssl` and using `name: 'ssl'` in config file is no longer supported, use `tls` instead.
* command line option <-F|--config> no longer defaults to /etc/sslh.cfg, so you have to
specify it explicitly.
* command line option <-v|--verbose> takes a mandatory integer parameter
Added TCP_FASTOPEN support for client sockets (if
tfo_ok is specified in their configuration) and for
listenint socket, if all client protocols support it.
(Craig Andrews)
Added 'minlength' option to skip a probe if less
than that many bytes have been received (mostly for
regex)
Update Let's Encrypt entry in example.cfg for tls-alpn-01
challenges; tls-sni-* challenges are now deprecated.
Log to syslog even if in foreground (for people who
use fail2ban)
Use syslog_facility: "none" to disable syslog
output.
Changed exit code for illegal command line parameter
from 1 to 6 (for testing purposes)
v1.20: 20NOV2018
Added support for socks5 protocol (Eugene Protozanov)
New probing method:
Before, probes were tried in order, repeating on the
same probe as long it returned PROBE_AGAIN before
moving to the next one. This means a probe which
requires a lot of data (i.e. returne PROBE_AGAIN for
a long time) could prevent sucessful matches from
subsequent probes. The configuration file needed to
take that into account.
Now, all probes are tried each time new data is
found. If any probe matches, use it. If at least one
probe requires more data, wait for more. If all
probes failed, connect to the last one. So the only
thing to know when writing the configuration file is
that 'anyprot' needs to be last.
Test suite heavily refactored; `t` uses `test.cfg`
to decide which probes to test and all setup is
automatic; probes get tested with 'fast' (entire
first message in one packet) and 'slow' (one byte at
a time); when SNI/ALPN are defined, all combinations
are tested.
Old 'tls' probe removed, 'sni_alpn' probe renamed as 'tls'.
You'll need to change 'sni_alpn' to 'tls' in
your configuration file, if ever you used it.
v1.19: 20JAN2018
Added 'syslog_facility' configuration option to
specify where to log.
+23
View File
@@ -0,0 +1,23 @@
FROM alpine:latest as build
ADD . /sslh
RUN \
apk add \
gcc \
libconfig-dev \
make \
musl-dev \
pcre-dev \
perl && \
cd /sslh && \
make sslh-select && \
strip sslh-select
FROM alpine:latest
COPY --from=build /sslh/sslh-select /sslh
RUN apk --no-cache add libconfig pcre
ENTRYPOINT [ "/sslh", "--foreground"]
+34 -16
View File
@@ -1,12 +1,14 @@
# Configuration
VERSION=$(shell ./genver.sh -r)
# Configuration -- you probably need to `make clean` if you
# change any of these
ENABLE_REGEX=1 # Enable regex probes
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBPCRE=1 # Use libpcre? (needed for regex on musl)
USELIBWRAP?= # Use libwrap?
USELIBCAP= # Use libcap?
USESYSTEMD= # Make use of systemd socket activation
USELIBBSD?= # Use libbsd (needed to update process name in `ps`)
COV_TEST= # Perform test coverage?
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
@@ -22,10 +24,10 @@ ifneq ($(strip $(COV_TEST)),)
endif
CC ?= gcc
CFLAGS ?=-Wall -g $(CFLAGS_COV)
CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV)
LIBS=
OBJS=common.o sslh-main.o probe.o tls.o
LIBS=-lm -lpcre2-8
OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o udp-listener.o collection.o gap.o
CONDITIONAL_TARGETS=
@@ -38,11 +40,6 @@ ifneq ($(strip $(ENABLE_REGEX)),)
CPPFLAGS+=-DENABLE_REGEX
endif
ifneq ($(strip $(USELIBPCRE)),)
CPPFLAGS+=-DLIBPCRE
LIBS:=$(LIBS) -lpcreposix
endif
ifneq ($(strip $(USELIBCONFIG)),)
LIBS:=$(LIBS) -lconfig
CPPFLAGS+=-DLIBCONFIG
@@ -59,6 +56,11 @@ ifneq ($(strip $(USESYSTEMD)),)
CONDITIONAL_TARGETS+=systemd-sslh-generator
endif
ifneq ($(strip $(USELIBBSD)),)
LIBS:=$(LIBS) -lbsd
CPPFLAGS+=-DLIBBSD
endif
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
@@ -70,21 +72,27 @@ version.h:
sslh: sslh-fork sslh-select
$(OBJS): version.h
$(OBJS): version.h common.h collection.h sslh-conf.h gap.h
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
sslh-conf.c sslh-conf.h: sslhconf.cfg
conf2struct sslhconf.cfg
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
#strip sslh-fork
sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
sslh-select: version.h $(OBJS) sslh-select.o Makefile
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
systemd-sslh-generator: systemd-sslh-generator.o
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
echosrv: version.h $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
echosrv-conf.c echosrv-conf.h: echosrv.cfg
conf2struct echosrv.cfg
echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
$(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
@@ -93,6 +101,16 @@ $(MAN): sslh.pod Makefile
# configuration
release:
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
# Build docker image
docker:
docker image build -t "sslh:${VERSION}" .
docker image tag "sslh:${VERSION}" sslh:latest
docker-clean:
yes | docker image rm "sslh:${VERSION}" sslh:latest
yes | docker image prune
# generic install: install binary and man page
install: sslh $(MAN)
@@ -112,7 +130,7 @@ uninstall:
update-rc.d sslh remove
distclean: clean
rm -f tags cscope.*
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
clean:
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
+59 -409
View File
@@ -5,444 +5,94 @@ sslh -- A ssl/ssh multiplexer
them further based on tests performed on the first data
packet sent by the remote client.
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
implemented, and any other protocol that can be tested using
a regular expression, can be recognised. A typical use case
is to allow serving several services on port 443 (e.g. to
connect to SSH from inside a corporate firewall, which
almost never block port 443) while still serving HTTPS on
that port.
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH,
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
protocol that can be tested using a regular expression, can
be recognised. A typical use case is to allow serving
several services on port 443 (e.g. to connect to SSH from
inside a corporate firewall, which almost never block port
443) while still serving HTTPS on that port.
Hence `sslh` acts as a protocol demultiplexer, or a
switchboard. Its name comes from its original function to
serve SSH and HTTPS on the same port.
switchboard. With the SNI and ALPN probe, it makes a good
front-end to a virtual host farm hosted behind a single IP
address.
Compile and install
===================
`sslh` has the bells and whistles expected from a mature
daemon: privilege and capabilities dropping, inetd support,
systemd support, transparent proxying, chroot, logging,
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
model, and more.
Dependencies
------------
Install
=======
`sslh` uses [libconfig](http://www.hyperrealm.com/libconfig/)
and [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, these are contained in packages `libwrap0-dev` and
`libconfig8-dev`.
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and
`libconfig-devel`:
yum install libconfig libconfig-devel
If you can't find `libconfig`, or just don't want a
configuration file, set `USELIBCONFIG=` in the Makefile.
Compilation
-----------
After this, the Makefile should work:
make install
There are a couple of configuration options at the beginning
of the Makefile:
* `USELIBWRAP` compiles support for host access control (see
`hosts_access(3)`), you will need `libwrap` headers and
library to compile (`libwrap0-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
Binaries
--------
The Makefile produces two different executables: `sslh-fork`
and `sslh-select`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes.
If you are going to use `sslh` for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
`sslh-fork` is probably more suited for you.
* `sslh-select` uses only one thread, which monitors all connections
at once. It is more recent and less tested, but only incurs a 16
byte overhead per connection. Also, if it stops, you'll lose all
connections, which means you can't upgrade it remotely.
If you are going to use `sslh` on a "medium" setup (a few thousand ssh
connections, and another few thousand ssl connections),
`sslh-select` will be better.
If you have a very large site (tens of thousands of connections),
you'll need a vapourware version that would use libevent or
something like that.
Installation
------------
* In general:
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
* For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
* For CentOS:
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
You might need to create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
update-rc.d sslh defaults
Please refer to the [install guide](doc/INSTALL.md).
Configuration
=============
If you use the scripts provided, sslh will get its
configuration from /etc/sslh.cfg. Please refer to
example.cfg for an overview of all the settings.
Please refer to the [configuration guide](doc/config.md).
A good scheme is to use the external name of the machine in
`listen`, and bind `httpd` to `localhost:443` (instead of all
binding to all interfaces): that way, HTTPS connections
coming from inside your network don't need to go through
`sslh`, and `sslh` is only there as a frontal for connections
coming from the internet.
Note that 'external name' in this context refers to the
actual IP address of the machine as seen from your network,
i.e. that that is not `127.0.0.1` in the output of
`ifconfig(8)`.
Libwrap support
---------------
Docker image
------------
Sslh can optionally perform `libwrap` checks for the sshd
service: because the connection to `sshd` will be coming
locally from `sslh`, `sshd` cannot determine the IP of the
client.
How to use
OpenVPN support
---------------
---
OpenVPN clients connecting to OpenVPN running with
`-port-share` reportedly take more than one second between
the time the TCP connection is established and the time they
send the first data packet. This results in `sslh` with
default settings timing out and assuming an SSH connection.
To support OpenVPN connections reliably, it is necessary to
increase `sslh`'s timeout to 5 seconds.
Build docker image
Instead of using OpenVPN's port sharing, it is more reliable
to use `sslh`'s `--openvpn` option to get `sslh` to do the
port sharing.
make docker
Using proxytunnel with sslh
---------------------------
If you are connecting through a proxy that checks that the
outgoing connection really is SSL and rejects SSH, you can
encapsulate all your traffic in SSL using `proxytunnel` (this
should work with `corkscrew` as well). On the server side you
receive the traffic with `stunnel` to decapsulate SSL, then
pipe through `sslh` to switch HTTP on one side and SSL on the
other.
```bash
docker container run \
--rm \
-it \
--listen=0.0.0.0:443 \
--ssh=hostname:22 \
--tlshostname:443 \
sslh:latest
```
In that case, you end up with something like this:
docker-compose example
ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd
Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd
```
---
version: "3"
Configuration goes like this on the server side, using `stunnel3`:
services:
sslh:
image: sslh:latest
hostname: sslh
ports:
- 443:443/tcp
command: --listen=0.0.0.0:443 --tlshostname:443 --openvpn=openvpn:1194
depends_on:
- nginx
- openvpn
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \
sslh -i --http localhost:80 --ssh localhost:22
nginx:
image: nginx
hostname: nginx
* stunnel options:
* `-f` for foreground/debugging
* `-p` for specifying the key and certificate
* `-d` for specifying which interface and port
we're listening to for incoming connexions
* `-l` summons `sslh` in inetd mode.
* sslh options:
* `-i` for inetd mode
* `--http` to forward HTTP connexions to port 80,
and SSH connexions to port 22.
Capabilities support
--------------------
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
make use of POSIX capabilities; this will save the required
capabilities needed for transparent proxying for unprivileged
processes.
Alternatively, you may use filesystem capabilities instead
of starting sslh as root and asking it to drop privileges.
You will need `CAP_NET_BIND_SERVICE` for listening on port 443
and `CAP_NET_ADMIN` for transparent proxying (see
`capabilities(7)`).
You can use the `setcap(8)` utility to give these capabilities
to the executable:
# setcap cap_net_bind_service,cap_net_admin+pe sslh-select
Then you can run sslh-select as an unpriviledged user, e.g.:
$ sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443
Caveat: `CAP_NET_ADMIN` does give sslh too many rights, e.g.
configuring the interface. If you're not going to use
transparent proxying, just don't use it (or use the libcap method).
Transparent proxy support
-------------------------
On Linux and FreeBSD you can use the `--transparent` option to
request transparent proxying. This means services behind `sslh`
(Apache, `sshd` and so on) will see the external IP and ports
as if the external world connected directly to them. This
simplifies IP-based access control (or makes it possible at
all).
Linux:
`sslh` needs extended rights to perform this: you'll need to
give it `CAP_NET_ADMIN` capabilities (see appropriate chapter)
or run it as root (but don't do that).
The firewalling tables also need to be adjusted as follows.
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
this scheme -- let me know if you manage that:
$ # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
$ sysctl -w net.ipv4.conf.default.route_localnet=1
$ sysctl -w net.ipv4.conf.all.route_localnet=1
$ # DROP martian packets as they would have been if route_localnet was zero
$ # Note: packets not leaving the server aren't affected by this, thus sslh will still work
$ iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
$ iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
$ # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
$ iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
$ # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
$ iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
$ # Configure routing for those marked packets
$ ip rule add fwmark 0x1 lookup 100
$ ip route add local 0.0.0.0/0 dev lo table 100
Tranparent proxying with IPv6 is similarly set up as follows:
$ # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
$ # Not sure if this is needed for ipv6 though
$ sysctl -w net.ipv4.conf.default.route_localnet=1
$ sysctl -w net.ipv4.conf.all.route_localnet=1
$ # DROP martian packets as they would have been if route_localnet was zero
$ # Note: packets not leaving the server aren't affected by this, thus sslh will still work
$ ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
$ ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
$ # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
$ ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
$ # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
$ ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
$ # Configure routing for those marked packets
$ ip -6 rule add fwmark 0x1 lookup 100
$ ip -6 route add local ::/0 dev lo table 100
Explanation:
To be able to use `localhost` as destination in your sslh config along with transparent proxying
you have to allow routing of loopback addresses as done above.
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
showing how packets will traverse the iptables chains.
Note:
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
These rules will allow you to connect directly to ssh on port
22 (or to any other service behind sslh) as well as through sslh on port 443.
Also remember that iptables configuration and ip routes and
rules won't be necessarily persisted after you reboot. Make
sure to save them properly. For example in CentOS7, you would
do `iptables-save > /etc/sysconfig/iptables`, and add both
`ip` commands to your `/etc/rc.local`.
FreeBSD:
Given you have no firewall defined yet, you can use the following configuration
to have ipfw properly redirect traffic back to sslh
/etc/rc.conf
firewall_enable="YES"
firewall_type="open"
firewall_logif="YES"
firewall_coscripts="/etc/ipfw/sslh.rules"
/etc/ipfw/sslh.rules
#! /bin/sh
# ssl
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
# ssh
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
# xmpp
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
# openvpn (running on other internal system)
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
General notes:
This will only work if `sslh` does not use any loopback
addresses (no `127.0.0.1` or `localhost`), you'll need to use
explicit IP addresses (or names):
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --ssl 192.168.0.1:4443
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
Systemd Socket Activation
-------------------------
If compiled with `USESYSTEMD` then it is possible to activate
the service on demand and avoid running any code as root.
In this mode any listen configuration options are ignored and
the sockets are passed by systemd to the service.
Example socket unit:
[Unit]
Before=sslh.service
[Socket]
ListenStream=1.2.3.4:443
ListenStream=5.6.7.8:444
ListenStream=9.10.11.12:445
FreeBind=true
[Install]
WantedBy=sockets.target
Example service unit:
[Unit]
PartOf=sslh.socket
[Service]
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443
KillMode=process
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
User=sslh
With this setup only the socket needs to be enabled. The sslh service
will be started on demand and does not need to run as root to bind the
sockets as systemd has already bound and passed them over. If the sslh
service is started on its own without the sockets being passed by systemd
then it will look to use those defined on the command line or config
file as usual. Any number of ListenStreams can be defined in the socket
file and systemd will pass them all over to sslh to use as usual.
To avoid inconsistency between starting via socket and starting directly
via the service Requires=sslh.socket can be added to the service unit to
mandate the use of the socket configuration.
Rather than overwriting the entire socket file drop in values can be placed
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
values that will be merged.
In addition to the above with manual .socket file configuration there is an
optional systemd generator which can be compiled - systemd-sslh-generator
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overriden by a /etc/systemd/system/sslh.socket file.
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Fail2ban
--------
If using transparent proxying, just use the standard ssh
rules. If you can't or don't want to use transparent
proxying, you can set `fail2ban` rules to block repeated ssh
connections from an IP address (obviously this depends
on the site, there might be legitimate reasons you would get
many connections to ssh from the same IP address...)
See example files in scripts/fail2ban.
openvpn:
image: openvpn:latest
hostname: openvpn
```
Comments? Questions?
====================
You can subscribe to the `sslh` mailing list here:
<http://rutschle.net/cgi-bin/mailman/listinfo/sslh>
<https://lists.rutschle.net/mailman/listinfo/sslh>
This mailing list should be used for discussion, feature
requests, and will be the prefered channel for announcements.
requests, and will be the preferred channel for announcements.
Of course, check the [FAQ](doc/FAQ.md) first!
+6006
View File
File diff suppressed because it is too large. Load diff
+273
View File
@@ -0,0 +1,273 @@
/*******************************************************************************
* argtable3: Declares the main interfaces of the library
*
* This file is part of the argtable3 library.
*
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
* <sheitmann@users.sourceforge.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of STEWART HEITMANN nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
#ifndef ARGTABLE3
#define ARGTABLE3
#include <stdio.h> /* FILE */
#include <time.h> /* struct tm */
#ifdef __cplusplus
extern "C" {
#endif
#define ARG_REX_ICASE 1
#define ARG_DSTR_SIZE 200
#define ARG_CMD_NAME_LEN 100
#define ARG_CMD_DESCRIPTION_LEN 256
#ifndef ARG_REPLACE_GETOPT
#define ARG_REPLACE_GETOPT 1 /* use the embedded getopt as the system getopt(3) */
#endif /* ARG_REPLACE_GETOPT */
/* bit masks for arg_hdr.flag */
enum { ARG_TERMINATOR = 0x1, ARG_HASVALUE = 0x2, ARG_HASOPTVALUE = 0x4 };
#if defined(_WIN32)
#if defined(argtable3_EXPORTS)
#define ARG_EXTERN __declspec(dllexport)
#elif defined(argtable3_IMPORTS)
#define ARG_EXTERN __declspec(dllimport)
#else
#define ARG_EXTERN
#endif
#else
#define ARG_EXTERN
#endif
typedef struct _internal_arg_dstr* arg_dstr_t;
typedef void* arg_cmd_itr_t;
typedef void(arg_resetfn)(void* parent);
typedef int(arg_scanfn)(void* parent, const char* argval);
typedef int(arg_checkfn)(void* parent);
typedef void(arg_errorfn)(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname);
typedef void(arg_dstr_freefn)(char* buf);
typedef int(arg_cmdfn)(int argc, char* argv[], arg_dstr_t res);
typedef int(arg_comparefn)(const void* k1, const void* k2);
/*
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
* The argtable library requires each arg_xxx struct to have an arg_hdr
* struct as its first data member.
* The argtable library functions then use this data to identify the
* properties of the command line option, such as its option tags,
* datatype string, and glossary strings, and so on.
* Moreover, the arg_hdr struct contains pointers to custom functions that
* are provided by each arg_xxx struct which perform the tasks of parsing
* that particular arg_xxx arguments, performing post-parse checks, and
* reporting errors.
* These functions are private to the individual arg_xxx source code
* and are the pointer to them are initiliased by that arg_xxx struct's
* constructor function. The user could alter them after construction
* if desired, but the original intention is for them to be set by the
* constructor and left unaltered.
*/
typedef struct arg_hdr {
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
const char* shortopts; /* String defining the short options */
const char* longopts; /* String defiing the long options */
const char* datatype; /* Description of the argument data type */
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
int mincount; /* Minimum number of occurences of this option accepted */
int maxcount; /* Maximum number of occurences if this option accepted */
void* parent; /* Pointer to parent arg_xxx struct */
arg_resetfn* resetfn; /* Pointer to parent arg_xxx reset function */
arg_scanfn* scanfn; /* Pointer to parent arg_xxx scan function */
arg_checkfn* checkfn; /* Pointer to parent arg_xxx check function */
arg_errorfn* errorfn; /* Pointer to parent arg_xxx error function */
void* priv; /* Pointer to private header data for use by arg_xxx functions */
} arg_hdr_t;
typedef struct arg_rem {
struct arg_hdr hdr; /* The mandatory argtable header struct */
} arg_rem_t;
typedef struct arg_lit {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
} arg_lit_t;
typedef struct arg_int {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
int* ival; /* Array of parsed argument values */
} arg_int_t;
typedef struct arg_dbl {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
double* dval; /* Array of parsed argument values */
} arg_dbl_t;
typedef struct arg_str {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
const char** sval; /* Array of parsed argument values */
} arg_str_t;
typedef struct arg_rex {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
const char** sval; /* Array of parsed argument values */
} arg_rex_t;
typedef struct arg_file {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args*/
const char** filename; /* Array of parsed filenames (eg: /home/foo.bar) */
const char** basename; /* Array of parsed basenames (eg: foo.bar) */
const char** extension; /* Array of parsed extensions (eg: .bar) */
} arg_file_t;
typedef struct arg_date {
struct arg_hdr hdr; /* The mandatory argtable header struct */
const char* format; /* strptime format string used to parse the date */
int count; /* Number of matching command line args */
struct tm* tmval; /* Array of parsed time values */
} arg_date_t;
enum { ARG_ELIMIT = 1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG };
typedef struct arg_end {
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of errors encountered */
int* error; /* Array of error codes */
void** parent; /* Array of pointers to offending arg_xxx struct */
const char** argval; /* Array of pointers to offending argv[] string */
} arg_end_t;
typedef struct arg_cmd_info {
char name[ARG_CMD_NAME_LEN];
char description[ARG_CMD_DESCRIPTION_LEN];
arg_cmdfn* proc;
} arg_cmd_info_t;
/**** arg_xxx constructor functions *********************************/
ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, const char* glossary);
ARG_EXTERN struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary);
ARG_EXTERN struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary);
ARG_EXTERN struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary);
ARG_EXTERN struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
ARG_EXTERN struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
ARG_EXTERN struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
ARG_EXTERN struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
ARG_EXTERN struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
ARG_EXTERN struct arg_rex* arg_rexn(const char* shortopts,
const char* longopts,
const char* pattern,
const char* datatype,
int mincount,
int maxcount,
int flags,
const char* glossary);
ARG_EXTERN struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
ARG_EXTERN struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
ARG_EXTERN struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary);
ARG_EXTERN struct arg_end* arg_end(int maxerrors);
#define ARG_DSTR_STATIC ((arg_dstr_freefn*)0)
#define ARG_DSTR_VOLATILE ((arg_dstr_freefn*)1)
#define ARG_DSTR_DYNAMIC ((arg_dstr_freefn*)3)
/**** other functions *******************************************/
ARG_EXTERN int arg_nullcheck(void** argtable);
ARG_EXTERN int arg_parse(int argc, char** argv, void** argtable);
ARG_EXTERN void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
ARG_EXTERN void arg_print_syntax(FILE* fp, void** argtable, const char* suffix);
ARG_EXTERN void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix);
ARG_EXTERN void arg_print_glossary(FILE* fp, void** argtable, const char* format);
ARG_EXTERN void arg_print_glossary_gnu(FILE* fp, void** argtable);
ARG_EXTERN void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
ARG_EXTERN void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
ARG_EXTERN void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix);
ARG_EXTERN void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix);
ARG_EXTERN void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format);
ARG_EXTERN void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable);
ARG_EXTERN void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname);
ARG_EXTERN void arg_freetable(void** argtable, size_t n);
ARG_EXTERN arg_dstr_t arg_dstr_create(void);
ARG_EXTERN void arg_dstr_destroy(arg_dstr_t ds);
ARG_EXTERN void arg_dstr_reset(arg_dstr_t ds);
ARG_EXTERN void arg_dstr_free(arg_dstr_t ds);
ARG_EXTERN void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc);
ARG_EXTERN void arg_dstr_cat(arg_dstr_t ds, const char* str);
ARG_EXTERN void arg_dstr_catc(arg_dstr_t ds, char c);
ARG_EXTERN void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...);
ARG_EXTERN char* arg_dstr_cstr(arg_dstr_t ds);
ARG_EXTERN void arg_cmd_init(void);
ARG_EXTERN void arg_cmd_uninit(void);
ARG_EXTERN void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description);
ARG_EXTERN void arg_cmd_unregister(const char* name);
ARG_EXTERN int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res);
ARG_EXTERN unsigned int arg_cmd_count(void);
ARG_EXTERN arg_cmd_info_t* arg_cmd_info(const char* name);
ARG_EXTERN arg_cmd_itr_t arg_cmd_itr_create(void);
ARG_EXTERN void arg_cmd_itr_destroy(arg_cmd_itr_t itr);
ARG_EXTERN int arg_cmd_itr_advance(arg_cmd_itr_t itr);
ARG_EXTERN char* arg_cmd_itr_key(arg_cmd_itr_t itr);
ARG_EXTERN arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr);
ARG_EXTERN int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k);
ARG_EXTERN void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn);
ARG_EXTERN void arg_make_get_help_msg(arg_dstr_t res);
ARG_EXTERN void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable);
ARG_EXTERN void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end);
ARG_EXTERN int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode);
ARG_EXTERN void arg_set_module_name(const char* name);
ARG_EXTERN void arg_set_module_version(int major, int minor, int patch, const char* tag);
/**** deprecated functions, for back-compatibility only ********/
ARG_EXTERN void arg_free(void** argtable);
#ifdef __cplusplus
}
#endif
#endif
+2 -2
View File
@@ -1,7 +1,7 @@
# This is a basic configuration file that should provide
# sensible values for "standard" setup.
verbose: false;
verbose: 0;
foreground: false;
inetd: false;
numeric: false;
@@ -24,7 +24,7 @@ protocols:
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "ssl"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);
+97
View File
@@ -0,0 +1,97 @@
/*
collection.c: management of a collection of connections, for sslh-select
# Copyright (C) 2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#include "common.h"
#include "collection.h"
#include "sslh-conf.h"
#include "gap.h"
/* Info to keep track of all connections */
struct cnx_collection {
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
};
/* Allocates and initialises a new collection of connections. */
cnx_collection* collection_init(void)
{
cnx_collection* collection;
collection = malloc(sizeof(*collection));
CHECK_ALLOC(collection, "collection_init(collection)");
memset(collection, 0, sizeof(*collection));
collection->fd2cnx = gap_init();
return collection;
}
/* Caveat: might not work, as has never been used */
void collection_destroy(cnx_collection* collection)
{
/* Caveat 2: no code to free connections yet */
gap_destroy(collection->fd2cnx);
free(collection);
}
/* Points the file descriptor to the specified connection index */
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
{
gap_set(collection->fd2cnx, fd, cnx);
return 0;
}
/* Allocates a connection and inits it with specified file descriptor */
struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collection, int fd)
{
struct connection* cnx = malloc(sizeof(*cnx));
if (!cnx) return NULL;
init_cnx(cnx);
cnx->type = SOCK_STREAM;
cnx->q[0].fd = fd;
cnx->state = ST_PROBING;
cnx->probe_timeout = time(NULL) + cfg.timeout;
gap_set(collection->fd2cnx, fd, cnx);
return cnx;
}
/* Remove a connection from the collection */
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx)
{
if (cnx->q[0].fd != -1)
gap_set(collection->fd2cnx, cnx->q[0].fd, NULL);
if (cnx->q[1].fd != -1)
gap_set(collection->fd2cnx, cnx->q[1].fd, NULL);
free(cnx);
return 0;
}
/* Returns the connection that contains the file descriptor */
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd)
{
return gap_get(collection->fd2cnx, fd);
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef COLLECTION_H
#define COLLECTION_H
typedef struct cnx_collection cnx_collection;
cnx_collection* collection_init(void);
void collection_destroy(cnx_collection* collection);
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
/* Remove a connection from the collection */
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd);
#endif
+324 -193
View File
@@ -16,6 +16,7 @@
#include "common.h"
#include "probe.h"
#include "sslh-conf.h"
/* Added to make the code compilable under CYGWIN
* */
@@ -29,22 +30,20 @@
#include <systemd/sd-daemon.h>
#endif
#ifdef LIBBSD
#include <bsd/unistd.h>
#endif
/*
* Settings that depend on the command line. They're set in main(), but also
* used in other places in common.c, and it'd be heavy-handed to pass it all as
* parameters
* Settings that depend on the command line or the config file
*/
int verbose = 0;
int probing_timeout = 2;
int inetd = 0;
int foreground = 0;
int background = 0;
int transparent = 0;
int numeric = 0;
const char *user_name, *pid_file, *chroot_path, *facility = "auth";
struct sslhcfg_item cfg;
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
#ifdef LIBWRAP
#include <tcpd.h>
int allow_severity =0, deny_severity = 0;
@@ -71,7 +70,7 @@ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall
}
}
int get_fd_sockets(int *sockfd[])
int get_fd_sockets(struct listen_endpoint *sockfd[])
{
int sd = 0;
@@ -84,8 +83,10 @@ int get_fd_sockets(int *sockfd[])
if (sd > 0) {
int i;
*sockfd = malloc(sd * sizeof(*sockfd[0]));
CHECK_ALLOC(*sockfd, "malloc");
for (i = 0; i < sd; i++) {
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
(*sockfd)[i].type = SOCK_STREAM;
}
}
#endif
@@ -93,74 +94,156 @@ int get_fd_sockets(int *sockfd[])
return sd;
}
/* Starts listening sockets on specified addresses.
* IN: addr[], num_addr
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
* Returns number of addresses bound
* Bound file descriptors are returned in newly-allocated *sockfd pointer
*/
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
/* Set TCP_FASTOPEN on listening socket if all client protocols support it */
int make_listen_tfo(int s)
{
struct sockaddr_storage *saddr;
struct addrinfo *addr;
int i, res, one;
int num_addr = 0;
int sd_socks = 0;
int i, qlen = 5;
sd_socks = get_fd_sockets(sockfd);
/* Don't do it if not supported */
if (!TCP_FASTOPEN)
return 0;
if (sd_socks > 0) {
return sd_socks;
}
/* Don't do it if any protocol does not specify it */
for (i = 0; i < cfg.protocols_len; i++) {
if (! cfg.protocols[i].tfo_ok)
return 0;
}
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;
if (verbose)
fprintf(stderr, "listening to %d addresses\n", num_addr);
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
if (!addr) {
fprintf(stderr, "FATAL: Inconsistent listen number. This should not happen.\n");
exit(1);
}
saddr = (struct sockaddr_storage*)addr->ai_addr;
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dump(CR_DIE, (*sockfd)[i], addr, "socket");
one = 1;
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
if (addr->ai_flags & SO_KEEPALIVE) {
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
}
if (IP_FREEBIND) {
res = setsockopt((*sockfd)[i], IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
}
if (addr->ai_addr->sa_family == AF_INET6) {
res = setsockopt((*sockfd)[i], IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
}
res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
check_res_dump(CR_DIE, res, addr, "bind");
res = listen ((*sockfd)[i], 50);
check_res_dump(CR_DIE, res, addr, "listen");
}
return num_addr;
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
}
/* Starts listening on a single address
* Returns a socket filehandle, or dies with message in case of major error */
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
{
struct sockaddr_storage *saddr;
int sockfd, one, res;
saddr = (struct sockaddr_storage*)addr->ai_addr;
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
check_res_dump(CR_DIE, sockfd, addr, "socket");
one = 1;
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
res = make_listen_tfo(sockfd);
check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)");
if (keepalive) {
res = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
}
if (IP_FREEBIND) {
res = setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
}
if (addr->ai_addr->sa_family == AF_INET6) {
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
}
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
check_res_dump(CR_DIE, res, addr, "bind");
if (!udp) {
res = listen (sockfd, 50);
check_res_dump(CR_DIE, res, addr, "listen");
}
return sockfd;
}
/* Starts listening sockets on specified addresses.
* OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects
* Returns number of addresses bound
*/
int start_listen_sockets(struct listen_endpoint *sockfd[])
{
struct addrinfo *addr, *start_addr;
char buf[NI_MAXHOST];
int i, res;
int num_addr = 0, keepalive = 0, udp = 0;
int sd_socks = 0;
sd_socks = get_fd_sockets(sockfd);
if (sd_socks > 0) {
return sd_socks;
}
*sockfd = NULL;
if (cfg.verbose) fprintf(stderr, "Listening to:\n");
for (i = 0; i < cfg.listen_len; i++) {
keepalive = cfg.listen[i].keepalive;
udp = cfg.listen[i].is_udp;
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
if (res) exit(4);
for (addr = start_addr; addr; addr = addr->ai_next) {
num_addr++;
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd));
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
if (cfg.verbose)
fprintf(stderr, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
cfg.listen[i].keepalive ? "keepalive" : "",
cfg.listen[i].is_udp ? "udp" : "");
}
freeaddrinfo(start_addr);
}
return num_addr;
}
/* returns 1 if given address is on the local machine: iterate through all
* network interfaces and check their addresses */
int is_same_machine(struct addrinfo* from)
{
struct ifaddrs *ifaddrs_p = NULL, *ifa;
int match = 0;
getifaddrs(&ifaddrs_p);
for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next)
{
if (!ifa->ifa_addr)
continue;
if (from->ai_addr->sa_family == ifa->ifa_addr->sa_family)
{
int family = ifa->ifa_addr->sa_family;
if (family == AF_INET)
{
struct sockaddr_in *from_addr = (struct sockaddr_in*)from->ai_addr;
struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr;
if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) {
match = 1;
break;
}
}
else if (family == AF_INET6)
{
struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from->ai_addr;
struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr;
if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) {
match = 1;
break;
}
}
}
}
freeifaddrs(ifaddrs_p);
return match;
}
/* Transparent proxying: bind the peer address of fd to the peer address of
* fd_from */
#define IP_TRANSPARENT 19
@@ -177,83 +260,73 @@ int bind_peer(int fd, int fd_from)
/* getpeername can fail with ENOTCONN if connection was dropped before we
* got here */
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername");
CHECK_RES_RETURN(res, "getpeername", res);
/* if the destination is the same machine, there's no need to do bind */
struct ifaddrs *ifaddrs_p = NULL, *ifa;
getifaddrs(&ifaddrs_p);
for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next)
{
if (!ifa->ifa_addr)
continue;
int match = 0;
if (from.ai_addr->sa_family == ifa->ifa_addr->sa_family)
{
int family = ifa->ifa_addr->sa_family;
if (family == AF_INET)
{
struct sockaddr_in *from_addr = (struct sockaddr_in*)from.ai_addr;
struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr;
if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr)
match = 1;
}
else if (family == AF_INET6)
{
struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from.ai_addr;
struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr;
if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16))
match = 1;
}
}
if (match) /* the destination is the same as the source, should not create a transparent bind */
return 0;
}
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");
CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT");
#else
if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans));
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY");
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res);
#ifdef IPV6_BINDANY
} else { /* IPv6 */
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans));
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY");
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res);
#endif /* IPV6_BINDANY */
}
#endif /* IP_TRANSPARENT / IP_BINDANY */
res = bind(fd, from.ai_addr, from.ai_addrlen);
CHECK_RES_RETURN(res, "bind");
CHECK_RES_RETURN(res, "bind", res);
return 0;
}
/* Make the file descriptor non-block */
int set_nonblock(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
CHECK_RES_RETURN(flags, "fcntl", -1);
flags |= O_NONBLOCK;
flags = fcntl(fd, F_SETFL, flags);
CHECK_RES_RETURN(flags, "fcntl", -1);
return flags;
}
/* Connect to first address that works and returns a file descriptor, or -1 if
* none work.
* If transparent proxying is on, use fd_from peer address on external address
* of new file descriptor. */
int connect_addr(struct connection *cnx, int fd_from)
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
{
struct addrinfo *a, from;
struct sockaddr_storage ss;
char buf[NI_MAXHOST];
int fd, res, one;
int transparent = cnx->proto->transparent || cfg.transparent;
memset(&from, 0, sizeof(from));
from.ai_addr = (struct sockaddr*)&ss;
from.ai_addrlen = sizeof(ss);
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername");
CHECK_RES_RETURN(res, "getpeername", res);
for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent, make sure both connections use the same address family */
if (transparent && a->ai_family != from.ai_addr->sa_family)
continue;
if (verbose)
if (cfg.verbose)
fprintf(stderr, "connecting to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a),
a->ai_addr->sa_family, a->ai_addrlen);
@@ -262,25 +335,35 @@ int connect_addr(struct connection *cnx, int fd_from)
fd = socket(a->ai_family, SOCK_STREAM, 0);
if (fd == -1) {
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
cnx->proto->description, strerror(errno));
cnx->proto->name, strerror(errno));
} else {
one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one));
/* no need to check return value; if it's not supported, that's okay */
if (blocking == NON_BLOCKING) {
set_nonblock(fd);
}
if (transparent) {
res = bind_peer(fd, fd_from);
CHECK_RES_RETURN(res, "bind_peer");
CHECK_RES_RETURN(res, "bind_peer", res);
}
res = connect(fd, a->ai_addr, a->ai_addrlen);
if (res == -1) {
/* EINPROGRESS indicates it might take time. If it eventually
* fails, it'll be caught as a failed read */
if ((res == -1) && (errno != EINPROGRESS)) {
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
cnx->proto->description, strerror(errno));
cnx->proto->name, strerror(errno));
close(fd);
} else {
if (cnx->proto->keepalive) {
one = 1;
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)");
}
return fd;
continue; /* Try the next address */
}
if (cnx->proto->keepalive) {
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res);
}
return fd;
}
}
return -1;
@@ -291,14 +374,11 @@ int defer_write(struct queue *q, void* data, int data_size)
{
char *p;
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
if (verbose)
if (cfg.verbose)
fprintf(stderr, "**** writing deferred on fd %d\n", q->fd);
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
if (!p) {
perror("realloc");
exit(1);
}
CHECK_ALLOC(p, "realloc");
q->begin_deferred_data = p;
q->deferred_data = p + data_offset;
@@ -317,7 +397,7 @@ int flush_deferred(struct queue *q)
{
int n;
if (verbose)
if (cfg.verbose)
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
n = write(q->fd, q->deferred_data, q->deferred_data_size);
@@ -345,14 +425,16 @@ void init_cnx(struct connection *cnx)
memset(cnx, 0, sizeof(*cnx));
cnx->q[0].fd = -1;
cnx->q[1].fd = -1;
cnx->proto = get_first_protocol();
cnx->proto = NULL;
}
void dump_connection(struct connection *cnx)
{
printf("state: %d\n", cnx->state);
printf("fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
printf("fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
printf("0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
hexdump(cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
printf("1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
hexdump(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
}
@@ -377,7 +459,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
if (size_r == -1) {
switch (errno) {
case EAGAIN:
if (verbose)
if (cfg.verbose)
fprintf(stderr, "reading 0 from %d\n", from);
return FD_NODATA;
@@ -387,7 +469,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
}
}
CHECK_RES_RETURN(size_r, "read");
CHECK_RES_RETURN(size_r, "read",FD_CNXCLOSED);
if (size_r == 0)
return FD_CNXCLOSED;
@@ -403,7 +485,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
case ECONNRESET:
case EPIPE:
/* remove end closed -- drop the connection */
/* remote end closed -- drop the connection */
return FD_CNXCLOSED;
}
} else if (size_w < size_r) {
@@ -412,7 +494,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
return FD_STALLED;
}
CHECK_RES_RETURN(size_w, "write");
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
return size_w;
}
@@ -426,7 +508,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
if (res) {
log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
@@ -451,32 +533,23 @@ 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
*/
int resolve_split_name(struct addrinfo **out, const char* ct_host, const char* serv)
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
{
struct addrinfo hint;
char *end;
int res;
char* host, *host_base;
memset(&hint, 0, sizeof(hint));
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
/* Copy parameter so not to clobber data in libconfig */
res = asprintf(&host_base, "%s", ct_host);
if (res == -1) {
log_message(LOG_ERR, "asprintf: cannot allocate memory");
return -1;
}
host = host_base;
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
* around IP address */
if (host[0] == '[') {
end = strrchr(host, ']');
if (!end) {
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
return -1;
}
host++; /* skip first bracket */
*end = 0; /* remove last bracket */
@@ -485,7 +558,6 @@ int resolve_split_name(struct addrinfo **out, const char* ct_host, const char* s
res = getaddrinfo(host, serv, &hint, out);
if (res)
log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv);
free(host_base);
return res;
}
@@ -519,60 +591,97 @@ void resolve_name(struct addrinfo **out, char* fullname)
}
/* Log to syslog or stderr if foreground */
void log_message(int type, char* msg, ...)
void log_message(int type, const char* msg, ...)
{
va_list ap;
va_start(ap, msg);
if (foreground)
if (cfg.foreground)
vfprintf(stderr, msg, ap);
else
vsyslog(type, msg, ap);
va_end(ap);
if (do_syslog) {
va_start(ap, msg);
vsyslog(type, msg, ap);
va_end(ap);
}
}
/* syslogs who connected to where */
void log_connection(struct connection *cnx)
/* Fills a connection description; returns 0 on failure */
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx)
{
int res;
struct addrinfo addr;
struct sockaddr_storage ss;
#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
int res;
if (cnx->proto->log_level < 1)
return;
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; /* Can happen if connection drops before we get here.
if (res == -1) return 0; /* Can happen if connection drops before we get here.
In that case, don't log anything (there is no connection) */
sprintaddr(peer, sizeof(peer), &addr);
sprintaddr(desc->peer, sizeof(desc->peer), &addr);
addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(service, sizeof(service), &addr);
if (res == -1) return 0;
sprintaddr(desc->service, sizeof(desc->service), &addr);
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(target, sizeof(target), &addr);
if (res == -1) return 0;
sprintaddr(desc->target, sizeof(desc->target), &addr);
addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(local, sizeof(local), &addr);
if (res == -1) return 0;
sprintaddr(desc->local, sizeof(desc->local), &addr);
return 1;
}
/* syslogs who connected to where
* desc: string description of the connection. if NULL, log_connection will
* manage on its own
* cnx: connection descriptor
* */
void log_connection(struct connection_desc* desc, const struct connection *cnx)
{
struct connection_desc d;
if (cnx->proto->log_level < 1)
return;
if (!desc) {
desc = &d;
get_connection_desc(desc, cnx);
}
log_message(LOG_INFO, "%s:connection from %s to %s forwarded from %s to %s\n",
cnx->proto->description,
peer,
service,
local,
target);
cnx->proto->name,
desc->peer,
desc->service,
desc->local,
desc->target);
}
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
{
#ifdef LIBBSD
struct connection_desc d;
if (!desc) {
desc = &d;
get_connection_desc(desc, cnx);
}
setproctitle("shovel %s %s->%s => %s->%s",
cnx->proto->name,
desc->peer,
desc->service,
desc->local,
desc->target);
#endif
}
@@ -594,27 +703,27 @@ int check_access_rights(int in_socket, const char* service)
int res;
res = getpeername(in_socket, &peer.saddr, &size);
CHECK_RES_RETURN(res, "getpeername");
CHECK_RES_RETURN(res, "getpeername", res);
/* extract peer address */
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
if (res) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
strcpy(addr_str, STRING_UNKNOWN);
}
/* extract peer name */
strcpy(host, STRING_UNKNOWN);
if (!numeric) {
if (!cfg.numeric) {
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
if (res) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
}
}
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "access denied\n");
log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str);
close(in_socket);
@@ -657,15 +766,20 @@ void setup_syslog(const char* bin_name) {
char *name1, *name2;
int res, fn;
if (!strcmp(cfg.syslog_facility, "none")) {
do_syslog = 0;
return;
}
name1 = strdup(bin_name);
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
if (strcmp(facilitynames[fn].c_name, facility) == 0)
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
break;
if (fn == -1) {
fprintf(stderr, "Unknown facility %s\n", facility);
if (facilitynames[fn].c_val == -1) {
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
exit(1);
}
@@ -688,16 +802,33 @@ void set_keepcaps(int val) {
#endif
}
/* set needed capabilities for effective and permitted, clear rest */
void set_capabilities(void) {
/* Returns true if anything requires transparent proxying. */
static int use_transparent(void)
{
#ifdef LIBCAP
if (cfg.transparent)
return 1;
for (int i = 0; i < cfg.protocols_len; i++)
if (cfg.protocols[i].transparent)
return 1;
#endif
return 0;
}
/* set needed capabilities for effective and permitted, clear rest
* IN: cap_net_admin: set to 1 to set CAP_NET_RAW
* */
void set_capabilities(int cap_net_admin) {
#ifdef LIBCAP
int res;
cap_t caps;
cap_value_t cap_list[10];
int ncap = 0;
if (transparent)
cap_list[ncap++] = CAP_NET_ADMIN;
if (cap_net_admin)
cap_list[ncap++] = CAP_NET_RAW;
caps = cap_init();
@@ -738,12 +869,12 @@ void drop_privileges(const char* user_name, const char* chroot_path)
fprintf(stderr, "%s: not found\n", user_name);
exit(2);
}
if (verbose)
if (cfg.verbose)
fprintf(stderr, "turning into %s\n", user_name);
}
if (chroot_path) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "chrooting into %s\n", chroot_path);
res = chroot(chroot_path);
@@ -765,7 +896,7 @@ void drop_privileges(const char* user_name, const char* chroot_path)
res = setuid(pw->pw_uid);
CHECK_RES_DIE(res, "setuid");
set_capabilities();
set_capabilities(use_transparent());
set_keepcaps(0);
}
}
+65 -13
View File
@@ -35,6 +35,9 @@
#include "version.h"
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define CHECK_RES_DIE(res, str) \
if (res == -1) { \
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
@@ -42,12 +45,19 @@
exit(1); \
}
#define CHECK_RES_RETURN(res, str) \
#define CHECK_RES_RETURN(res, str, ret) \
if (res == -1) { \
log_message(LOG_CRIT, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
return res; \
return ret; \
}
#define CHECK_ALLOC(a, str) \
if (!a) { \
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
perror(str); \
exit(1); \
}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#if 1
@@ -60,6 +70,14 @@
#define IP_FREEBIND 0
#endif
#ifndef TCP_FASTOPEN
#define TCP_FASTOPEN 0
#endif
#ifndef TCP_FASTOPEN_CONNECT
#define TCP_FASTOPEN_CONNECT 30 /* Attempt FastOpen with connect. */
#endif
enum connection_state {
ST_PROBING=1, /* Waiting for timeout to find where to forward */
ST_SHOVELING /* Connexion is established */
@@ -78,53 +96,87 @@ struct queue {
};
struct connection {
int type; /* SOCK_DGRAM | SOCK_STREAM */
struct sslhcfg_protocols_item* proto; /* Where to connect to */
/* SOCK_STREAM */
enum connection_state state;
time_t probe_timeout;
struct proto *proto;
/* q[0]: queue for external connection (client);
* q[1]: queue for internal connection (httpd or sshd);
* */
struct queue q[2];
/* SOCK_DGRAM */
struct sockaddr client_addr; /* Contains the remote client address */
socklen_t addrlen;
int local_endpoint; /* Contains the local address */
time_t last_active;
/* We need one local socket for each target server, so we know where to
* forward server responses */
int target_sock;
};
struct listen_endpoint {
int socketfd; /* file descriptor of listening socket */
int type; /* SOCK_DGRAM | SOCK_STREAM */
};
#define FD_CNXCLOSED 0
#define FD_NODATA -1
#define FD_STALLED -2
/* String description of a connection */
#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
struct connection_desc {
char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
};
typedef enum {
NON_BLOCKING = 0,
BLOCKING = 1
} connect_blocking;
/* common.c */
void init_cnx(struct connection *cnx);
int connect_addr(struct connection *cnx, int fd_from);
int set_nonblock(int fd);
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
int fd2fd(struct queue *target, struct queue *from);
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
void resolve_name(struct addrinfo **out, char* fullname);
void log_connection(struct connection *cnx);
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx);
void log_connection(struct connection_desc* desc, const struct connection *cnx);
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx);
int check_access_rights(int in_socket, const char* service);
void setup_signals(void);
void setup_syslog(const char* bin_name);
void drop_privileges(const char* user_name, const char* chroot_path);
void set_capabilities(int cap_net_admin);
void write_pid_file(const char* pidfile);
void log_message(int type, char* msg, ...);
void log_message(int type, const char* msg, ...);
void dump_connection(struct connection *cnx);
int resolve_split_name(struct addrinfo **out, const char* hostname, const char* port);
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
int start_listen_sockets(struct listen_endpoint *sockfd[]);
int defer_write(struct queue *q, void* data, int data_size);
int flush_deferred(struct queue *q);
extern int probing_timeout, verbose, inetd, foreground,
background, transparent, numeric;
extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct sslhcfg_item cfg;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING;
extern const char* user_name, *pid_file, *chroot_path, *facility;
extern const char* server_type;
/* sslh-fork.c */
void start_shoveler(int);
void main_loop(int *listen_sockets, int num_addr_listen);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
#endif
+169
View File
@@ -0,0 +1,169 @@
Frequently Asked Questions
==========================
When something doesn't work, look up here... and if it still
doesn't work, report how what was suggested here went.
It's also worth reading [how to ask
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
before posting on the mailing list or opening an issue in
Github.
Getting more info
=================
In general, if something doesn't work, you'll want to run
`sslh` with lots of logging, and the logging directly in
the terminal (Otherwise, logs are sent to `syslog`, and
usually end up in `/var/log/auth.log`). You will achieve
this by running `sslh` in foreground with verbose:
```
sslh -v 1 -f -F myconfig.cfg
```
Higher values of `verbose` produce more information. 1 is
usually sufficient. 2 will also print incoming packets used
for probing.
forward to [PROBE] failed:connect: Connection refused
=====================================================
Usually this means `sslh` is configured to forward a
protocol somewhere, but no service is listening on the
target address. Check your `sslh` configuration, check the
corresponding server really is listening and running.
Finally, check the server is listening where you expect it
to:
```
netstat -lpt
```
I get a segmentation fault!
===========================
Well, it's not yours (fault): a segfault is always a bug in
the programme. Usually standard use cases are well tested,
so it may be related to something unusual in your
configuration, or even something wrong, but it should still
never result in a segfault.
Thankfully, when they are deterministic, segfaults are
usually fairly easy to fix if you're willing to run a few
diagnostics to help the developer.
First, make sure you have debug symbols:
```
$ file sslh-select
sslh-select: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a758ac75ff11f1ace577705b4d6627e301940b59, with debug_info, not stripped
```
Note `with debug_info, not stripped` at the end. If you
don't have that, your distribution stripped the binary: you
will need to get the source code and compile it yourself
(that way, you will also get the latest version).
Install `valgrind` and run `sslh` under it:
```
valgrind --leak-check=full ./sslh-fork -v 2 -f -F yourconfig.cfg
```
Report the full output to the mailing list or github.
Valgrind is very powerful and gives precise hints of what is
wrong and why. For example on `sslh` issue
(#273)[https://github.com/yrutschle/sslh/issues/273]:
```
sudo valgrind --leak-check=full ./sslh-fork -v 2 -f -F /etc/sslh.cfg
==20037== Memcheck, a memory error detector
==20037== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20037== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==20037== Command: ./sslh-fork -v 2 -f -F /etc/sslh.cfg
==20037==
sslh-fork v1.21b-1-g2c93a01-dirty started
--20037-- WARNING: unhandled arm-linux syscall: 403
--20037-- You may be able to write your own handler.
--20037-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
--20037-- Nevertheless we consider this a bug. Please report
--20037-- it at http://valgrind.org/support/bug_reports.html.
==20040== Conditional jump or move depends on uninitialised value(s)
==20040== at 0x112A3C: parse_tls_header (tls.c:162)
==20040== by 0x111CEF: is_tls_protocol (probe.c:214)
==20040== by 0x11239F: probe_client_protocol (probe.c:366)
==20040== by 0x10A8F7: start_shoveler (sslh-fork.c:98)
==20040== by 0x10AE9B: main_loop (sslh-fork.c:200)
==20040== by 0x1114FB: main (sslh-main.c:322)
==20040==
```
Here we see that something wrong is happening at `tls.c`
line 162, and it's linked to an uninitialised value.
Using sslh for virtual hosting
==============================
Virtual hosting refers to having several domain names behind
a single IP address. All Web servers handle this, but
sometimes it can be useful to do it with `sslh`.
TLS virtual hosting with SNI
----------------------------
For TLS, this is done very simply using Server Name
Indication, SNI for short, which is a TLS extension whereby
the client indicates the name of the server it wishes to
connect to. This can be a very powerful way to separate
several TLS-based services hosted behind the same port:
simply name each service with its own hostname. For example,
we could define `mail.rutschle.net`, `im.rutschle.net`,
`www.rutschle.net`, all of which point to the same IP
address. `sslh` uses the `sni_hostnames` setting of the
TLS probe to do this, e.g.:
```
protocols: (
{ name: "tls";
host: "localhost";
port: "993";
sni_hostnames: [ "mail.rutschle.net" ];
},
{ name: "tls";
host: "localhost";
port: "xmpp-client";
sni_hostnames: [ "im.rutschle.net" ];
},
{ name: "tls";
host: "localhost";
port: "4443";
sni_hostnames: [ "www.rutschle.net" ];
}
);
```
HTTP virtual hosting with regex
-------------------------------
If you wish to serve several Web domains over HTTP through
`sslh`, you can do this simply by using regular expressions
on the Host specification part of the HTTP query.
The following example forwards connections to `host_A.acme`
to 192.168.0.2, and connections to `host_B.acme` to
192.168.0.3.
```
protocols: (
{ name: "regex";
host: "192.168.0.2";
port: "80";
regex_patterns:
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_A.acme"] },
{ name: "regex";
host: "192.168.0.3";
port: "80";
regex_patterns:
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] }
);
```
+120
View File
@@ -0,0 +1,120 @@
Compile and install
===================
Dependencies
------------
`sslh` uses:
* [libconfig](http://www.hyperrealm.com/libconfig/). For
Debian this is contained in package `libconfig8-dev`. You
can compile with or without it using USELIBCONFIG in the
Makefile.
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, this is contained in packages
`libwrap0-dev`. 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.
* [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.
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and
`libconfig-devel`:
yum install libconfig libconfig-devel
If you want to rebuild `sslh-conf.c` (after a `make
distclean` for example), you will also need to add
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
(v1.5) to your path.
Compilation
-----------
After this, the Makefile should work:
make install
There are a couple of configuration options at the beginning
of the Makefile:
* `USELIBWRAP` compiles support for host access control (see
`hosts_access(3)`), you will need `libwrap` headers and
library to compile (`libwrap0-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
* `USELIBBSD` compiles support for updating the process name (as shown
by `ps`).
Binaries
--------
The Makefile produces two different executables: `sslh-fork`
and `sslh-select`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes.
If you are going to use `sslh` for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
`sslh-fork` is probably more suited for you.
* `sslh-select` uses only one thread, which monitors all connections
at once. It is more recent and less tested, but only incurs a 16
byte overhead per connection. Also, if it stops, you'll lose all
connections, which means you can't upgrade it remotely.
If you are going to use `sslh` on a "medium" setup (a few thousand ssh
connections, and another few thousand ssl connections),
`sslh-select` will be better.
If you have a very large site (tens of thousands of connections),
you'll need a vapourware version that would use libevent or
something like that.
Installation
------------
* In general:
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
* For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
* For CentOS:
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
You might need to create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
update-rc.d sslh defaults
View File
File renamed without changes.
+347
View File
@@ -0,0 +1,347 @@
Configuration
=============
If you use the scripts provided, sslh will get its
configuration from /etc/sslh.cfg. Please refer to
example.cfg for an overview of all the settings.
A good scheme is to use the external name of the machine in
`listen`, and bind `httpd` to `localhost:443` (instead of all
binding to all interfaces): that way, HTTPS connections
coming from inside your network don't need to go through
`sslh`, and `sslh` is only there as a frontal for connections
coming from the internet.
Note that 'external name' in this context refers to the
actual IP address of the machine as seen from your network,
i.e. that that is not `127.0.0.1` in the output of
`ifconfig(8)`.
Libwrap support
---------------
Sslh can optionally perform `libwrap` checks for the sshd
service: because the connection to `sshd` will be coming
locally from `sslh`, `sshd` cannot determine the IP of the
client.
OpenVPN support
---------------
OpenVPN clients connecting to OpenVPN running with
`-port-share` reportedly take more than one second between
the time the TCP connection is established and the time they
send the first data packet. This results in `sslh` with
default settings timing out and assuming an SSH connection.
To support OpenVPN connections reliably, it is necessary to
increase `sslh`'s timeout to 5 seconds.
Instead of using OpenVPN's port sharing, it is more reliable
to use `sslh`'s `--openvpn` option to get `sslh` to do the
port sharing.
Using proxytunnel with sslh
---------------------------
If you are connecting through a proxy that checks that the
outgoing connection really is SSL and rejects SSH, you can
encapsulate all your traffic in SSL using `proxytunnel` (this
should work with `corkscrew` as well). On the server side you
receive the traffic with `stunnel` to decapsulate SSL, then
pipe through `sslh` to switch HTTP on one side and SSL on the
other.
In that case, you end up with something like this:
ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd
Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd
Configuration goes like this on the server side, using `stunnel3`:
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \
sslh -i --http localhost:80 --ssh localhost:22
* stunnel options:
* `-f` for foreground/debugging
* `-p` for specifying the key and certificate
* `-d` for specifying which interface and port
we're listening to for incoming connexions
* `-l` summons `sslh` in inetd mode.
* sslh options:
* `-i` for inetd mode
* `--http` to forward HTTP connexions to port 80,
and SSH connexions to port 22.
Capabilities support
--------------------
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
make use of POSIX capabilities; this will save the required
capabilities needed for transparent proxying for unprivileged
processes.
Alternatively, you may use filesystem capabilities instead
of starting sslh as root and asking it to drop privileges.
You will need `CAP_NET_BIND_SERVICE` for listening on port 443
and `CAP_NET_RAW` for transparent proxying (see
`capabilities(7)`).
You can use the `setcap(8)` utility to give these capabilities
to the executable:
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
Then you can run sslh-select as an unpriviledged user, e.g.:
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
Transparent proxy support
-------------------------
On Linux and FreeBSD you can use the `--transparent` option to
request transparent proxying. This means services behind `sslh`
(Apache, `sshd` and so on) will see the external IP and ports
as if the external world connected directly to them. This
simplifies IP-based access control (or makes it possible at
all).
You can refer to Sean Warn'ѕ [tutorial](tproxy.md) for a
different set-up which enables transparent proxying between
two different machines. The following may only work if
`sslh` and the final servers are on the same machine.
Note that getting this to work is very tricky and
detail-dependant: depending on whether the target server and
sslh are on the same machine, different machines, or
different dockers, and tool versions, all seem to change the
required network configuration somewhat. If it doesn't work,
it's almost certain that the problem is not linked to `sslh`
but to the network setup that surrounds it.
Linux:
`sslh` needs extended rights to perform this: you'll need to
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
or run it as root (but don't do that).
The firewalling tables also need to be adjusted as follows.
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
this scheme -- let me know if you manage that:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
Tranparent proxying with IPv6 is similarly set up as follows:
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
# Not sure if this is needed for ipv6 though
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Configure routing for those marked packets
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
Explanation:
To be able to use `localhost` as destination in your sslh config along with transparent proxying
you have to allow routing of loopback addresses as done above.
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
showing how packets will traverse the iptables chains.
Note:
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
These rules will allow you to connect directly to ssh on port
22 (or to any other service behind sslh) as well as through sslh on port 443.
Also remember that iptables configuration and ip routes and
rules won't be necessarily persisted after you reboot. Make
sure to save them properly. For example in CentOS7, you would
do `iptables-save > /etc/sysconfig/iptables`, and add both
`ip` commands to your `/etc/rc.local`.
FreeBSD:
Given you have no firewall defined yet, you can use the following configuration
to have ipfw properly redirect traffic back to sslh
/etc/rc.conf
firewall_enable="YES"
firewall_type="open"
firewall_logif="YES"
firewall_coscripts="/etc/ipfw/sslh.rules"
/etc/ipfw/sslh.rules
#! /bin/sh
# ssl
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
# ssh
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
# xmpp
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
# openvpn (running on other internal system)
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
General notes:
This will only work if `sslh` does not use any loopback
addresses (no `127.0.0.1` or `localhost`), you'll need to use
explicit IP addresses (or names):
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
Systemd Socket Activation
-------------------------
If compiled with `USESYSTEMD` then it is possible to activate
the service on demand and avoid running any code as root.
In this mode any listen configuration options are ignored and
the sockets are passed by systemd to the service.
Example socket unit:
[Unit]
Before=sslh.service
[Socket]
ListenStream=1.2.3.4:443
ListenStream=5.6.7.8:444
ListenStream=9.10.11.12:445
FreeBind=true
[Install]
WantedBy=sockets.target
Example service unit:
[Unit]
PartOf=sslh.socket
[Service]
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --tls 127.0.0.1:443
KillMode=process
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
User=sslh
With this setup only the socket needs to be enabled. The sslh service
will be started on demand and does not need to run as root to bind the
sockets as systemd has already bound and passed them over. If the sslh
service is started on its own without the sockets being passed by systemd
then it will look to use those defined on the command line or config
file as usual. Any number of ListenStreams can be defined in the socket
file and systemd will pass them all over to sslh to use as usual.
To avoid inconsistency between starting via socket and starting directly
via the service Requires=sslh.socket can be added to the service unit to
mandate the use of the socket configuration.
Rather than overwriting the entire socket file drop in values can be placed
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
values that will be merged.
In addition to the above with manual .socket file configuration there is an
optional systemd generator which can be compiled - systemd-sslh-generator
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overriden by a /etc/systemd/system/sslh.socket file.
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Fail2ban
--------
If using transparent proxying, just use the standard ssh
rules. If you can't or don't want to use transparent
proxying, you can set `fail2ban` rules to block repeated ssh
connections from an IP address (obviously this depends
on the site, there might be legitimate reasons you would get
many connections to ssh from the same IP address...)
See example files in scripts/fail2ban.
UDP
---
`sslh` can perform demultiplexing on UDP packets as well.
This only works with `sslh-select` (it is not possible to
support UDP with a forking model). Specify a listening
address and target protocols with `is_udp: true`. `sslh`
will wait for incoming UDP packets, run the probes in the
usual fashion, and forward packets to the appropriate
target. `sslh` will then remember the association between
remote host to target server for 60 seconds by default,
which can be overriden with `udp_timeout`. This allows to
process both single-datagram protocols such as DNS, and
connection-based protocols such as QUIC.
+272
View File
@@ -0,0 +1,272 @@
Transparent Proxy to Two Hosts
==============================
Tutorial by Sean Warner. 19 June 2019 20:35
Aim
---
* Show that `sslh` can transparently proxy requests from the internet to services on two separate hosts that are both on the same LAN.
* The IP address of the client initiating the request is what the destination should see… and not the IP address of the host that `sslh` is running on, which is what happens when `sslh` is not running in transparent mode.
* The solution here only works for my very specific use-case but hopefully others can adapt it to suits their needs.
Overview of my Network
----------------------
Two Raspberry Pis on my home LAN:
* Pi A: 192.168.1.124 – `sslh` (Port 4433), Apache2 web server for https (port 443), `stunnel` (port 4480) to decrypt ssh traffic and forward to SSH server (also on Pi A at Port 1022)
* Pi B: 192.168.1.123 - HTTP server (port 8000), SSH server (port 1022 on PiB).
* I send traffic from the internet to my router's external port 443 then use a port forward rule in my router to map that to internal port 4433 where sslh is listening.
![Architecture](tproxy.svg)
`sslh` build
------------
 
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
I compiled sslh from sources giving the binary pretty much all possible options such as Posix capabilities and systemd support.. here are the first few lines of the makefile:
 
```
# Configuration
VERSION=$(shell ./genver.sh -r)
ENABLE_REGEX=1         # Enable regex probes
USELIBCONFIG=1         # Use libconfig? (necessary to use configuration files)
USELIBPCRE=1 # Use libpcre? (needed for regex on musl)
USELIBWRAP=1         # Use libwrap?
USELIBCAP=1         # Use libcap?
USESYSTEMD=1         # Make use of systemd socket activation
COV_TEST=         # Perform test coverage?
PREFIX=/usr/local
BINDIR=$(PREFIX)/sbin
MANDIR=$(PREFIX)/share/man/man8
MAN=sslh.8.gz         # man page name
# End of configuration -- the rest should take care of
# itself
```
 
systemd setup
-------------
Create an sslh systemd service file...
```
# nano /lib/systemd/system/sslh.service
```
Paste in this contents…
```
[Unit]
Description=SSL/SSH multiplexer
After=network.target
Documentation=man:sslh(8)
[Service]
#EnvironmentFile=/etc/default/sslh
#ExecStart=/usr/local/sbin/sslh $DAEMON_OPTS
ExecStart=/usr/local/sbin/sslh -F /etc/sslh/sslh.cfg
KillMode=process
[Install]
WantedBy=multi-user.target
```
Save it and then…
```
# systemctl daemon-reload
```
Start it again to test…
```
# systemctl start sslh
```
 
Configure `sslh`
----------------
First stop `sslh` then open the config file and replace with below, save and start `sslh` again
```
# systemctl stop sslh
# nano /etc/sslh/sslh.cfg
# systemctl start sslh
```
```
verbose: true;
foreground: true;
inetd: false;
numeric: true;
transparent: true;
timeout: 2;
user: "sslh";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# You must have a port forward rule in the router: external port 443 <-> internal port 4433
# Local ip address of PiA is: 192.168.1.124, sslh and stunnel4 are running on this Pi
# Local ip address of PiB is: 192.168.1.123, http server and ssh server on this Pi
listen:
(
{ host: "192.168.1.124"; port: "4433"; }
);
# sslh demultiplexes based on the Protocol and Hostname
protocols:
(
{ name: "tls"; sni_hostnames: [ "www.example.com" ]; host: "192.168.1.124"; port: "443"; log_level: 1; },
# This probe is for tls encrypted ssh. SSLH forwards it to stunnel on port 4480 which decrypts it and sends it to the ssh server on PiA port 1022
{ name: "tls"; sni_hostnames: [ "ssh.example.com" ]; host: "192.168.1.124"; port: "4480"; log_level: 1; },
{ name: "http"; host: "192.168.1.123"; port: "8000"; log_level: 1; },
{ name: "ssh"; host: "192.168.1.123"; port: "1022"; log_level: 1; }
);
```
 
Configure `stunnel`
-------------------
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
```
# systemctl stop stunnel4
# nano /etc/stunnel/stunnel.conf
# systemctl start stunnel4
```
```
# Debugging stuff (may be useful for troubleshooting)
foreground = yes
#debug = 5 # this is the default
debug = 7
output = /var/log/stunnel4/stunnel.log
pid = /var/run/stunnel4/stunnel.pid
fips = no
cert = /etc/letsencrypt/live/example.com/fullchain.pem
key = /etc/letsencrypt/live/example.com/privkey.pem
[ssh]
accept = 192.168.1.124:4480
connect = 192.168.1.124:1022
TIMEOUTclose = 0
```
 
Configure iptables for Pi A
--------------------------
The `_add.sh` script creates the rules, the `_rm.sh` script removes the rules.
They will be lost if you reboot but there are ways to make them load again on start-up..
```
# nano /usr/local/sbin/piA_tproxy_add.sh
```
``` piA_tproxy_add.sh
iptables -t mangle -N SSLH
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH
iptables -t mangle -A SSLH --jump MARK --set-mark 0x1
iptables -t mangle -A SSLH --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
```
```
# nano /usr/local/sbin/piA_tproxy_rm.sh
```
``` piA_tproxy_rm.sh
iptables -t mangle -D PREROUTING -p tcp -m socket --transparent -j SSLH
iptables -t mangle -D OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH
iptables -t mangle -D SSLH --jump MARK --set-mark 0x1
iptables -t mangle -D SSLH --jump ACCEPT
iptables -t mangle -X SSLH
ip rule del fwmark 0x1 lookup 100
ip route del local 0.0.0.0/0 dev lo table 100
```
Make them executable..
```
# chmod +rx piA_tproxy_add.sh
# chmod +rx piA_tproxy_rm.sh
```
Now run the "add" script on Pi A!
```
# piA_tproxy_add.sh
# piA_tproxy_rm.sh
```
Configure iptables for Pi B
--------------------------
```
# nano /usr/local/sbin/piB_tproxy_add.sh
```
``` piB_tproxy_add.sh
iptables -t mangle -N SSLHSSL
iptables -t mangle -A OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL
iptables -t mangle -A SSLHSSL --jump MARK --set-mark 0x1
iptables -t mangle -A SSLHSSL --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add default via 192.168.1.124 table 100
ip route flush cache
```
```
# nano /usr/local/sbin/piB_tproxy_rm.sh
```
```
iptables -t mangle -D OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL
iptables -t mangle -D SSLHSSL --jump MARK --set-mark 0x1
iptables -t mangle -D SSLHSSL --jump ACCEPT
iptables -t mangle -X SSLHSSL
ip rule del fwmark 0x1 lookup 100
ip route del default via 192.168.1.124 table 100
ip route flush cache
```
Make them executable..
```
# chmod +rx piB_tproxy_add.sh
# chmod +rx piB_tproxy_rm.sh
```
Now run the "add" script on Pi B!
```
# piB_tproxy_add.sh
# piB_tproxy_rm.sh
```
 
Testing
-------
* Getting to sshd on PiA
I did this test using 4G from my phone (outside the LAN)
To simulate this I use `proxytunnel`. External port 443 is forwarded by my router to 4433. I need to arrive at `sslh` (port 4433) with ssh encrypted as TLS (hence I use the -e switch) and the `sni_hostname` set to ssh.example.com so that `sslh` will demultiplex to `stunnel` (port 4480) which will decrypt and forward to ssh server on PiA… see `sslh.cfg` and `stunnel.conf`.
The first IP:port is just a free HTTPS proxy I found on https://free-proxy-list.net
I execute this command from a terminal window..
```
# proxytunnel -v -e -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443
```
* Getting to sshd on PiB
I did this test using 4G from my phone (outside the LAN)
My smartphone telecom provider blocks ssh over port 443 so I need to use `proxytunnel` to encrypt.
Use the Proxytunnel `-X` switch to encrypt from local proxy to destination only so by the time we get to the destination it is unencrypted and `sslh` will see the ssh protocol and demultiplex to PiB as per `sslh.cfg`.
```
# proxytunnel -v -X -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443
```
Now when you test it all look at the output in daemon.log like this:
```
# grep -i 'ssl' /var/log/daemon.log
```
You should see that the IP address and port from the “connection from” and “forwarded from” fields are the same.
Special thanks and appreciation to Michael Yelsukov without whom I would never have got this working.
Any feedback or corrections very welcome!
+635
View File
@@ -0,0 +1,635 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="297mm"
height="210mm"
viewBox="0 0 297 210"
version="1.1"
id="svg288"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Transparent Proxy to Two Hosts-new.svg">
<defs
id="defs282">
<linearGradient
id="linearGradient4840"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4838" />
</linearGradient>
<linearGradient
id="linearGradient4802"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4800" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.01"
inkscape:cx="511.12411"
inkscape:cy="388.13656"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-page="true"
inkscape:window-width="1920"
inkscape:window-height="1005"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata285">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-87)">
<rect
style="fill:#000000;fill-opacity:0;stroke:#fa9600;stroke-width:0.68479234;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4977-0"
width="37.333149"
height="17.888765"
x="234.24397"
y="232.81883" />
<ellipse
id="path290"
cx="38.405174"
cy="133.55173"
rx="21.853449"
ry="28.965515"
style="stroke-width:0.26458332;fill-opacity:1;fill:none" />
<ellipse
style="fill:none;stroke-width:0.26458332"
id="path4806"
cx="44.482758"
cy="135.49138"
rx="28.965515"
ry="27.025862" />
<ellipse
style="fill:none;stroke-width:0.26458332"
id="path4808"
cx="124.26724"
cy="151.26724"
rx="29.612068"
ry="24.698277" />
<ellipse
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.76499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4812"
ry="30.796005"
rx="24.16864"
cy="140.54999"
cx="43.31974" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.59869254;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4852"
width="27.302826"
height="13.554869"
x="29.797318"
y="133.28833" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:6.6431241px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.31139645"
x="38.145908"
y="130.0871"
id="text138"
transform="scale(0.9172844,1.0901744)"><tspan
sodipodi:role="line"
x="38.145908"
y="130.0871"
id="tspan136"
style="stroke-width:0.31139645"><tspan
x="38.145908"
y="130.0871"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.31139645"
id="tspan134">Client</tspan></tspan></text>
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4862"
width="20.232847"
height="37.612331"
x="101.42361"
y="121.64869" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.67072004;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4864"
width="121.91585"
height="91.301468"
x="156.41544"
y="116.72592" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4866"
width="122.95342"
height="37.871742"
x="155.89668"
y="223.04117" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#f40000;stroke-width:0.701;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4868"
width="82.747147"
height="23.645355"
x="175.0919"
y="128.09322" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4870-7"
width="26.977121"
height="14.785542"
x="220.4861"
y="185.84906" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4870-7-1"
width="26.977123"
height="14.785542"
x="184.17078"
y="185.84906" />
<rect
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4919"
width="27.495911"
height="11.413391"
x="219.96732"
y="164.44894" />
<rect
style="fill:none;fill-opacity:0;stroke:#800080;stroke-width:0.85740757;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.96517412"
id="rect4921"
width="139.8217"
height="161.06387"
x="148.11099"
y="108.15634" />
<path
style="fill:#000000;stroke:#c98f26;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.88627452;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:0"
d="m 57.326389,140.58456 c 43.837821,-0.25939 44.097221,-0.25939 44.097221,-0.25939"
id="path4925"
inkscape:connector-curvature="0" />
<path
style="fill:#000000;fill-opacity:0;stroke:#c98f26;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.88627451"
d="m 122.01937,140.18462 c 53.1252,-0.25845 53.43955,-0.25845 53.43955,-0.25845"
id="path4925-7"
inkscape:connector-curvature="0" />
<path
style="fill:#000000;fill-opacity:0;stroke:#c98f26;stroke-width:0.58399999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.75200005, 0.58400002;stroke-dashoffset:0;stroke-opacity:0.88627451"
d="m 101.35344,140.71552 c 19.77892,-0.26191 19.89596,-0.26191 19.89596,-0.26191"
id="path4925-4"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 198.1781,185.71937 c -0.25939,-33.9808 -0.25939,-33.9808 -0.25939,-33.9808"
id="path4971"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 233.45588,164.18955 c 0,-12.45098 0,-12.45098 0,-12.45098"
id="path4973"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 233.19648,185.45997 c 0,-9.59763 0,-9.59763 0,-9.59763"
id="path4975"
inkscape:connector-curvature="0" />
<rect
style="fill:none;fill-opacity:0;stroke:#fa9600;stroke-width:0.665;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4977"
width="37.35294"
height="16.860703"
x="161.2299"
y="233.1991" />
<path
style="fill:none;stroke:#fa9600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.60784316;stroke-miterlimit:4;stroke-dasharray:none"
d="m 178.79609,151.73857 c 0.25941,81.45016 0.25941,81.45016 0.25941,81.45016"
id="path4994"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#fa9600;stroke-width:0.66078299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.60784316"
d="m 253.45564,151.97084 c 0.25959,80.36694 0.25959,80.36694 0.25959,80.36694"
id="path4994-0"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="32.424427"
y="103.23161"
id="text5013"><tspan
sodipodi:role="line"
id="tspan5011"
x="32.424427"
y="103.23161"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.29166698px;line-height:0;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.26458332">Internet</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="102.3172"
y="116.44047"
id="text5013-6"><tspan
sodipodi:role="line"
id="tspan5011-2"
x="102.3172"
y="116.44047"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.29166698px;line-height:0;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.26458332">Router</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#800080;fill-opacity:0.96470588;stroke:none;stroke-width:0.26458332"
x="188.76727"
y="105.30679"
id="text5053"><tspan
sodipodi:role="line"
id="tspan5051"
x="188.76727"
y="105.30679"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#800080;fill-opacity:0.96470588;stroke:none;stroke-width:0.26458332;stroke-opacity:0.96517412">Local Area Network</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
x="92.344772"
y="138.5094"
id="text5057"><tspan
sodipodi:role="line"
id="tspan5055"
x="92.344772"
y="138.5094"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
x="92.414749"
y="138.5529"
id="text5057-0"><tspan
sodipodi:role="line"
id="tspan5055-6"
x="92.414749"
y="138.5529"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
x="92.414749"
y="138.5529"
id="text5057-0-8"><tspan
sodipodi:role="line"
id="tspan5055-6-2"
x="92.414749"
y="138.5529"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.3396225px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.28349054"
x="151.81532"
y="147.89809"
id="text5057-0-8-6"
transform="scale(1.0714604,0.93330561)"><tspan
sodipodi:role="line"
id="tspan5055-6-2-7"
x="151.81532"
y="147.89809"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.28349054">4433</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.33962345px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.28349057"
x="114.9891"
y="148.08023"
id="text5057-0-8-6-4"
transform="scale(1.0714604,0.93330561)"><tspan
sodipodi:role="line"
id="tspan5055-6-2-7-3"
x="114.9891"
y="148.08023"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.28349057">4433</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="208.55414"
y="135.19525"
id="text144"><tspan
sodipodi:role="line"
x="208.55414"
y="135.19525"
id="tspan142"
style="stroke-width:0.26458332"><tspan
x="208.55414"
y="135.19525"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444447px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#f40000;fill-opacity:1;stroke-width:0.26458332"
id="tspan140">SSLH</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="158.75"
y="121.48432"
id="text5159"><tspan
sodipodi:role="line"
id="tspan5157"
x="158.75"
y="121.48432"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.26458332;font-size:3.88055556px">192.168.1.124 (Pi A)</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="157.30107"
y="258.37653"
id="text5159-0"><tspan
sodipodi:role="line"
id="tspan5157-1"
x="157.30107"
y="258.37653"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.26458332">192.168.1.123 (Pi B)</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fa9600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.25511"
y="242.15076"
id="text168"><tspan
sodipodi:role="line"
x="167.25511"
y="242.15076"
id="tspan166"
style="stroke-width:0.26458332"><tspan
x="167.25511"
y="242.15076"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan164">Web Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fa9600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="239.92085"
y="241.98419"
id="text174"><tspan
sodipodi:role="line"
x="239.92085"
y="241.98419"
id="tspan172"
style="stroke-width:0.26458332"><tspan
x="239.92085"
y="241.98419"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan170">SSH Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.9187"
y="190.6398"
id="text186"><tspan
sodipodi:role="line"
x="167.9187"
y="190.6398"
id="tspan184"
style="stroke-width:0.26458332"><tspan
x="167.9187"
y="190.6398"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan182">http</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.48399"
y="157.93468"
id="text180"><tspan
sodipodi:role="line"
x="167.48399"
y="157.93468"
id="tspan178"
style="stroke-width:0.26458332"><tspan
x="167.48399"
y="157.93468"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan176">8000</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="167.74596"
y="229.97469"
id="text192"><tspan
sodipodi:role="line"
x="167.74596"
y="229.97469"
id="tspan190"
style="stroke-width:0.26458332"><tspan
x="167.74596"
y="229.97469"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan188">8000</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="255.3237"
y="190.91431"
id="text246"><tspan
sodipodi:role="line"
x="255.3237"
y="190.91431"
id="tspan244"
style="stroke-width:0.26458332"><tspan
x="255.3237"
y="190.91431"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan242">ssh</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="254.88899"
y="158.20918"
id="text240"><tspan
sodipodi:role="line"
x="254.88899"
y="158.20918"
id="tspan238"
style="stroke-width:0.26458332"><tspan
x="254.88899"
y="158.20918"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan236">1022</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="255.15096"
y="230.24921"
id="text252"><tspan
sodipodi:role="line"
x="255.15096"
y="230.24921"
id="tspan250"
style="stroke-width:0.26458332"><tspan
x="255.15096"
y="230.24921"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
id="tspan248">1022</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
x="186.26637"
y="193.70544"
id="text156"><tspan
sodipodi:role="line"
x="186.26637"
y="193.70544"
id="tspan154"
style="stroke-width:0.26458332"><tspan
x="186.26637"
y="193.70544"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="tspan152">Web Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
x="225.25404"
y="170.88316"
id="text150"><tspan
sodipodi:role="line"
x="225.25404"
y="170.88316"
id="tspan148"
style="stroke-width:0.26458332"><tspan
x="225.25404"
y="170.88316"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="tspan146">STUNNEL</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
x="222.6344"
y="194.19792"
id="text162"><tspan
sodipodi:role="line"
x="222.6344"
y="194.19792"
id="tspan160"
style="stroke-width:0.26458332"><tspan
x="222.6344"
y="194.19792"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="tspan158">SSH Server</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="199.15128"
y="157.29231"
id="text198"><tspan
sodipodi:role="line"
x="199.15128"
y="157.29231"
id="tspan196"
style="stroke-width:0.26458332"><tspan
x="199.15128"
y="157.29231"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan194">443</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="199.41324"
y="182.17886"
id="text210"><tspan
sodipodi:role="line"
x="199.41324"
y="182.17886"
id="tspan208"
style="stroke-width:0.26458332"><tspan
x="199.41324"
y="182.17886"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan206">443</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="199.15128"
y="170.65247"
id="text204"><tspan
sodipodi:role="line"
x="199.15128"
y="170.65247"
id="tspan202"
style="stroke-width:0.26458332"><tspan
x="199.15128"
y="170.65247"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan200">tls</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="223.09605"
y="158.60213"
id="text216"><tspan
sodipodi:role="line"
x="223.09605"
y="158.60213"
id="tspan214"
style="stroke-width:0.26458332"><tspan
x="223.09605"
y="158.60213"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan212">4480</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="235.14638"
y="158.60213"
id="text222"><tspan
sodipodi:role="line"
x="235.14638"
y="158.60213"
id="tspan220"
style="stroke-width:0.26458332"><tspan
x="235.14638"
y="158.60213"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan218">tls</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="222.57213"
y="182.44083"
id="text228"><tspan
sodipodi:role="line"
x="222.57213"
y="182.44083"
id="tspan226"
style="stroke-width:0.26458332"><tspan
x="222.57213"
y="182.44083"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan224">1022</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="234.88441"
y="182.44083"
id="text234"><tspan
sodipodi:role="line"
x="234.88441"
y="182.44083"
id="tspan232"
style="stroke-width:0.26458332"><tspan
x="234.88441"
y="182.44083"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
id="tspan230">ssh</tspan></tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

+1307
View File
File diff suppressed because it is too large. Load diff
+66
View File
@@ -0,0 +1,66 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Fri Aug 13 18:03:20 2021.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2021 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef C2S_ECHOCFG_H
#define C2S_ECHOCFG_H
#ifdef LIBCONFIG
# include <libconfig.h>
#endif
struct echocfg_listen_item {
char* host;
char* port;
};
struct echocfg_item {
int udp;
char* prefix;
size_t listen_len;
struct echocfg_listen_item* listen;
};
int echocfg_parse_file(
const char* filename,
struct echocfg_item* echocfg,
const char** errmsg);
void echocfg_fprint(
FILE* out,
struct echocfg_item *echocfg,
int depth);
int echocfg_cl_parse(
int argc,
char* argv[],
struct echocfg_item *echocfg);
#endif
+249 -70
View File
@@ -27,8 +27,13 @@
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include <errno.h>
#define cfg sslhcfg
#include "common.h"
#undef cfg
#include "echosrv-conf.h"
/* Added to make the code compilable under CYGWIN
* */
@@ -36,58 +41,21 @@
#define SA_NOCLDWAIT 0
#endif
const char* USAGE_STRING =
"echosrv\n" \
"usage:\n" \
"\techosrv [-v] --listen <address:port> [--prefix <prefix>]\n"
"-v: verbose\n" \
"--listen: address to listen on. Can be specified multiple times.\n" \
"--prefix: add specified prefix before every line echoed.\n"
"";
struct echocfg_item cfg;
const char* server_type = "echsrv"; /* keep setup_syslog happy */
/*
* Settings that depend on the command line.
*/
char* prefix = "";
int port;
void parse_cmdline(int argc, char* argv[])
void check_res_dump(int res, struct addrinfo *addr, char* syscall)
{
int c;
struct option options[] = {
{ "verbose", no_argument, &verbose, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "listen", required_argument, 0, 'l' },
{ "prefix", required_argument, 0, 'p' },
};
struct addrinfo **a;
char buf[NI_MAXHOST];
while ((c = getopt_long_only(argc, argv, "l:p:", options, NULL)) != -1) {
if (c == 0) continue;
if (res == -1) {
if (addr)
fprintf(stderr, "error %s:%s: %s\n",
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
else
fprintf(stderr, "Dying just because\n");
switch (c) {
case 'l':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
/* append the specified addresses */
resolve_name(a, optarg);
break;
case 'p':
prefix = optarg;
break;
default:
fprintf(stderr, "%s", USAGE_STRING);
exit(2);
}
}
if (!addr_listen) {
fprintf(stderr, "No listening port specified\n");
exit(1);
}
}
@@ -97,19 +65,28 @@ void start_echo(int fd)
int res;
char buffer[1 << 20];
int ret, prefix_len;
int first = 1;
prefix_len = strlen(prefix);
prefix_len = strlen(cfg.prefix);
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, prefix);
strcpy(buffer, cfg.prefix);
while (1) {
ret = read(fd, buffer + prefix_len, sizeof(buffer));
if (ret == -1) {
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
if (ret <= 0) {
fprintf(stderr, "%s", strerror(errno));
return;
}
res = write(fd, buffer, ret + prefix_len);
if (first) {
res = write(fd, buffer, ret + prefix_len);
first = 0;
if (write(1, buffer, ret + prefix_len) < 0) {
fprintf(stderr, "%s", strerror(errno));
}
} else {
res = write(fd, buffer + prefix_len, ret);
}
if (res < 0) {
fprintf(stderr, "%s", strerror(errno));
return;
@@ -117,30 +94,228 @@ void start_echo(int fd)
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
/* TCP echo server: accepts connections to an endpoint, forks an echo for each
* connection, forever. Prefix is added at start of response stream */
void tcp_echo(struct listen_endpoint* listen_socket)
{
int in_socket, i;
while (1) {
int in_socket = accept(listen_socket->socketfd, 0, 0);
CHECK_RES_DIE(in_socket, "accept");
if (!fork())
{
close(listen_socket->socketfd);
start_echo(in_socket);
exit(0);
}
close(in_socket);
}
}
void print_udp_xchange(int sockfd, struct sockaddr* addr, socklen_t addrlen)
{
struct addrinfo src_addrinfo, to_addrinfo;
char str_addr[NI_MAXHOST+1+NI_MAXSERV+1];
char str_addr2[NI_MAXHOST+1+NI_MAXSERV+1];
struct sockaddr_storage ss;
src_addrinfo.ai_addr = (struct sockaddr*)&ss;
src_addrinfo.ai_addrlen = sizeof(ss);
getsockname(sockfd, src_addrinfo.ai_addr, &src_addrinfo.ai_addrlen);
to_addrinfo.ai_addr = addr;
to_addrinfo.ai_addrlen = sizeof(*addr);
fprintf(stderr, "UDP local %s remote %s\n",
sprintaddr(str_addr, sizeof(str_addr), &src_addrinfo),
sprintaddr(str_addr2, sizeof(str_addr2), &to_addrinfo)
);
}
/* UDP echo server: receive packets, return them, forever.
* Prefix is added at each packet */
void udp_echo(struct listen_endpoint* listen_socket)
{
char data[65536];
struct sockaddr src_addr;
socklen_t addrlen;
memset(data, 0, sizeof(data));
size_t prefix_len = strlen(cfg.prefix);
memcpy(data, cfg.prefix, prefix_len);
while (1) {
addrlen = sizeof(src_addr);
size_t len = recvfrom(listen_socket->socketfd,
data + prefix_len,
sizeof(data) - prefix_len,
0,
&src_addr,
&addrlen);
if (len < 0) {
perror("recvfrom");
}
*(data + prefix_len + len) = 0;
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
int res = sendto(listen_socket->socketfd,
data,
len + prefix_len,
0,
&src_addr,
addrlen);
if (res < 0) {
perror("sendto");
}
}
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
int i;
for (i = 0; i < num_addr_listen; i++) {
if (!fork()) {
while (1)
{
in_socket = accept(listen_sockets[i], 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
if (!fork())
{
close(listen_sockets[i]);
start_echo(in_socket);
exit(0);
}
close(in_socket);
if (cfg.udp) {
udp_echo(&listen_sockets[i]);
} else {
tcp_echo(&listen_sockets[i]);
}
}
}
wait(NULL);
}
/* Following is a number of utility functions copied from common.c: linking
* against common.o directly means echosrv has to work with sslh config struct,
* which makes it all too awkward */
/* simplified from common.c */
char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
{
char host[NI_MAXHOST], serv[NI_MAXSERV];
int res;
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
0 );
if (res) {
/* Name resolution failed: do it numerically instead */
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV);
/* should not fail but... */
if (res) {
strcpy(host, "?");
strcpy(serv, "?");
}
}
snprintf(buf, size, "%s:%s", host, serv);
return buf;
}
/* simplified from common.c */
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
{
struct sockaddr_storage *saddr;
int sockfd, one, res;
saddr = (struct sockaddr_storage*)addr->ai_addr;
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
check_res_dump(sockfd, addr, "socket");
one = 1;
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
check_res_dump(res, addr, "setsockopt(SO_REUSEADDR)");
if (addr->ai_addr->sa_family == AF_INET6) {
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
check_res_dump(res, addr, "setsockopt(IPV6_V6ONLY)");
}
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
check_res_dump(res, addr, "bind");
if (!udp) {
res = listen (sockfd, 50);
check_res_dump(res, addr, "listen");
}
return sockfd;
}
/* simplified from common.c */
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
{
struct addrinfo hint;
char *end;
int res;
memset(&hint, 0, sizeof(hint));
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
* around IP address */
if (host[0] == '[') {
end = strrchr(host, ']');
if (!end) {
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
return -1;
}
host++; /* skip first bracket */
*end = 0; /* remove last bracket */
}
res = getaddrinfo(host, serv, &hint, out);
if (res)
fprintf(stderr, "%s `%s:%s'\n", gai_strerror(res), host, serv);
return res;
}
int start_listen_sockets(struct listen_endpoint *sockfd[])
{
struct addrinfo *addr, *start_addr;
char buf[NI_MAXHOST];
int i, res;
int num_addr = 0, keepalive = 0, udp = 0;
*sockfd = NULL;
fprintf(stderr, "Listening to:\n");
for (i = 0; i < cfg.listen_len; i++) {
udp = cfg.udp;
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
if (res) exit(4);
for (addr = start_addr; addr; addr = addr->ai_next) {
num_addr++;
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd));
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
fprintf(stderr, "%d:\t%s\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr));
}
freeaddrinfo(start_addr);
}
return num_addr;
}
int main(int argc, char *argv[])
{
@@ -148,11 +323,15 @@ int main(int argc, char *argv[])
extern int optind;
int num_addr_listen;
int *listen_sockets;
struct listen_endpoint *listen_sockets;
parse_cmdline(argc, argv);
memset(&cfg, 0, sizeof(cfg));
if (echocfg_cl_parse(argc, argv, &cfg))
exit(1);
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
echocfg_fprint(stdout, &cfg, 0);
num_addr_listen = start_listen_sockets(&listen_sockets);
main_loop(listen_sockets, num_addr_listen);
+37
View File
@@ -0,0 +1,37 @@
# conf2struct for echosrv
header: "echosrv-conf.h";
parser: "echosrv-conf.c";
printer: true;
conffile_option: ("F", "config");
config: {
name: "echocfg",
type: "list",
items: (
{name: "udp", type: "bool"; default: false; },
{name: "prefix", type: "string"; },
{ name: "listen",
type: "list",
items: (
{ name: "host"; type: "string"; var: true; },
{ name: "port"; type: "string"; var: true; }
)
}
)
}
cl_groups: (
{ name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port";
short: "p"; argdesc: "<host:port>";
list: "listen";
# no override, this just adds to the list (and thus can be specified several times)
targets: (
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" }
);
}
)
+40 -16
View File
@@ -3,7 +3,7 @@
# not be used as a starting point for a working
# configuration. Instead use basic.cfg.
verbose: true;
verbose: 0;
foreground: true;
inetd: false;
numeric: false;
@@ -17,6 +17,7 @@ chroot: "/var/empty";
# system are usually defined in /usr/include/*/sys/syslog.h
# or equivalent)
# Default is "auth"
# "none" disables use of syslog
syslog_facility: "auth";
# List of interfaces on which we should listen
@@ -24,7 +25,8 @@ syslog_facility: "auth";
listen:
(
{ host: "thelonious"; port: "443"; },
{ host: "thelonious"; port: "8080"; keepalive: true; }
{ host: "thelonious"; port: "8080"; keepalive: true; },
{ host: "thelonious"; is_udp: true; port: "443" }
);
# List of protocols
@@ -41,53 +43,75 @@ listen:
# connection (default is off)
# fork: Should a new process be forked for this protocol?
# (only useful for sslh-select)
# tfo_ok: Set to true if the server supports TCP FAST OPEN
# transparent: Set to true to proxy this protocol
# transparently (server sees the remote client IP
# address). Same as the global option, but per-protocol
#
# Probe-specific options:
# (sslh will try each probe in order they are declared, and
# connect to the first that matches.)
#
# tls:
# sni_hostnames: list of FQDN for that target
# alpn_protocols: list of ALPN protocols for that target, see:
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
#
# if both sni_hostnames AND alpn_protocols are specified, both must match
#
# if neither are set, it is just checked whether this is the TLS protocol or not
#
# Obviously set the most specific probes
# first, and if you use TLS with no ALPN/SNI
# set it as the last TLS probe
# regex:
# regex_patterns: list of patterns to match for
# that target.
#
# sslh will try each probe in order they are declared, and
# connect to the first that matches.
#
# You can specify several of 'regex' and 'tls'.
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; keepalive: true; fork: true; },
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22";
keepalive: true; fork: true; tfo_ok: true },
{ name: "http"; host: "localhost"; port: "80"; },
# match BOTH ALPN/SNI
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0;},
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true },
# just match ALPN
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0;},
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; tfo_ok: true },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0; tfo_ok: true },
# just match SNI
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;},
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; tfo_ok: true },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0; tfo_ok: true },
# Let's Encrypt (tls-sni-* challenges)
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; sni_hostnames: [ "*.*.acme.invalid" ]; log_level: 0;},
# Let's Encrypt (tls-alpn-* challenges)
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; alpn_protocols: [ "acme-tls/1" ]; log_level: 0;},
# catch anything else TLS
{ name: "tls"; host: "localhost"; port: "443"; },
{ name: "tls"; host: "localhost"; port: "443"; tfo_ok: true },
# Forward UDP
{ name: "regex"; host: "localhost"; is_udp: true; port: "123";
udp_timeout: 20; # Time after which the "connection" is forgotten
regex_patterns: [ "hello" ]; },
# Forward Teamspeak3 (Voice only)
{ name: "regex"; host: "localhost"; is_udp: true; port: "9987"; regex_patterns: [ "TS3INIT1" ]; },
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
# Remember that the regex needs to be adjusted for every supported QUIC version.
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
# Regex examples -- better use the built-in probes for real-world use!
# OpenVPN
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
# Jabber
{ name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ]; },
{ name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ];
minlength: 60; # Won't even try to match the regex if we don't have that many bytes
},
# Catch-all
# Catch-all (but better use 'anyprot')
{ name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; },
# Where to connect in case of timeout (defaults to ssh)
+128
View File
@@ -0,0 +1,128 @@
/*
gap.c: gap, a simple, dynamically-growing array
of pointers that never shrinks
# Copyright (C) 2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include "sslh-conf.h"
#include "gap.h"
typedef struct gap_array {
int len; /* Number of elements in array */
void** array;
} gap_array;
/* Allocate one page-worth of elements */
static int gap_len_alloc(int elem_size)
{
return getpagesize() / elem_size;
}
/* Creates a new gap, all pointers are initialised at NULL */
gap_array* gap_init(void)
{
gap_array* gap = malloc(sizeof(*gap));
if (!gap) return NULL;
memset(gap, 0, sizeof(*gap));
int elem_size = sizeof(gap->array[0]);
gap->len = gap_len_alloc(elem_size);
gap->array = malloc(gap->len * elem_size);
if (!gap->array) return NULL;
for (int i = 0; i < gap->len; i++)
gap->array[i] = NULL;
return gap;
}
void* gap_get(gap_array* gap, int index)
{
return gap->array[index];
}
static int gap_extend(gap_array* gap)
{
int elem_size = sizeof(gap->array[0]);
int new_length = gap->len + gap_len_alloc(elem_size);
void** new = realloc(gap->array, new_length * elem_size);
if (!new) return -1;
gap->array = new;
for (int i = gap->len; i < new_length; i++) {
gap->array[i] = NULL;
}
gap->len = new_length;
return 0;
}
int gap_set(gap_array* gap, int index, void* ptr)
{
if (index >= gap->len) {
int res = gap_extend(gap);
if (res == -1) return -1;
}
gap->array[index] = ptr;
return 0;
}
void gap_destroy(gap_array* gap)
{
free(gap->array);
free(gap);
}
/* In gap, find element pointing to ptr, then shift the rest of the array that
* is considered len elements long.
* A poor man's list, if you will. Currently only used to remove probing
* connections, so it only copies a few pointers at most.
* Returns -1 if ptr was not found */
int gap_remove_ptr(gap_array* gap, void* ptr, int len)
{
int start, i;
for (i = 0; i < len; i++)
if (gap->array[i] == ptr)
break;
if (i < len)
start = i;
else
return -1;
for (i = start; i < len; i++) {
gap->array[i] = gap->array[i+1];
}
return 0;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef GAP_H
#define GAP_H
typedef struct gap_array gap_array;
gap_array* gap_init();
void* gap_get(gap_array* gap, int index);
int gap_set(gap_array* gap, int index, void* ptr);
void gap_destroy(gap_array* gap);
int gap_remove_ptr(gap_array* gap, void* ptr, int len);
#endif
+2 -2
View File
@@ -7,7 +7,7 @@ else
QUIET=0
fi
if ! `(git status | grep -q "On branch") 2> /dev/null`; then
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
# If we don't have git, we can't work out what
# version this is. It must have been downloaded as a
# zip file.
@@ -25,7 +25,7 @@ if ! `(git status | grep -q "On branch") 2> /dev/null`; then
fi
fi
if head=`git rev-parse --verify HEAD 2>/dev/null`; then
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
# generate the version info based on the tag
release=`(git describe --tags || git --describe || git describe --all --long) \
2>/dev/null | tr -d '\n'`
+175 -153
View File
@@ -1,7 +1,7 @@
/*
# probe.c: Code for probing protocols
#
# Copyright (C) 2007-2015 Yves Rutschle
# Copyright (C) 2007-2019 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -22,45 +22,43 @@
#define _GNU_SOURCE
#include <stdio.h>
#ifdef ENABLE_REGEX
#ifdef LIBPCRE
#include <pcreposix.h>
#else
#include <regex.h>
#endif
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#endif
#include <ctype.h>
#include "probe.h"
static int is_ssh_protocol(const char *p, int len, struct proto*);
static int is_openvpn_protocol(const char *p, int len, struct proto*);
static int is_tinc_protocol(const char *p, int len, struct proto*);
static int is_xmpp_protocol(const char *p, int len, struct proto*);
static int is_http_protocol(const char *p, int len, struct proto*);
static int is_tls_protocol(const char *p, int len, struct proto*);
static int is_adb_protocol(const char *p, int len, struct proto*);
static int is_true(const char *p, int len, struct proto* proto) { return 1; }
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; }
/* Table of protocols that have a built-in probe
*/
static struct proto builtins[] = {
/* description service saddr log_level keepalive fork probe */
{ "ssh", "sshd", NULL, 1, 0, 1, is_ssh_protocol},
{ "openvpn", NULL, NULL, 1, 0, 1, is_openvpn_protocol },
{ "tinc", NULL, NULL, 1, 0, 1, is_tinc_protocol },
{ "xmpp", NULL, NULL, 1, 0, 0, is_xmpp_protocol },
{ "http", NULL, NULL, 1, 0, 0, is_http_protocol },
{ "ssl", NULL, NULL, 1, 0, 0, is_tls_protocol },
{ "tls", NULL, NULL, 1, 0, 0, is_tls_protocol },
{ "adb", NULL, NULL, 1, 0, 0, is_adb_protocol },
{ "anyprot", NULL, NULL, 1, 0, 0, is_true }
static struct protocol_probe_desc builtins[] = {
/* description probe */
{ "ssh", is_ssh_protocol},
{ "openvpn", is_openvpn_protocol },
{ "tinc", is_tinc_protocol },
{ "xmpp", is_xmpp_protocol },
{ "http", is_http_protocol },
{ "tls", is_tls_protocol },
{ "adb", is_adb_protocol },
{ "socks5", is_socks5_protocol },
{ "syslog", is_syslog_protocol },
{ "anyprot", is_true }
};
static struct proto *protocols;
static char* on_timeout = "ssh";
struct proto* get_builtins(void) {
/* TODO I think this has to go */
struct protocol_probe_desc* get_builtins(void) {
return builtins;
}
@@ -68,34 +66,18 @@ int get_num_builtins(void) {
return ARRAY_SIZE(builtins);
}
/* Sets the protocol name to connect to in case of timeout */
void set_ontimeout(const char* name)
{
int res = asprintf(&on_timeout, "%s", name);
CHECK_RES_DIE(res, "asprintf");
}
/* Returns the protocol to connect to in case of timeout;
* if not found, return the first protocol specified
*/
struct proto* timeout_protocol(void)
struct sslhcfg_protocols_item* timeout_protocol(void)
{
struct proto* p = get_first_protocol();
for (; p && strcmp(p->description, on_timeout); p = p->next);
if (p) return p;
return get_first_protocol();
int i;
for (i = 0; i < cfg.protocols_len; i++) {
if (!strcmp(cfg.protocols[i].name, cfg.on_timeout)) return &cfg.protocols[i];
}
return &cfg.protocols[0];
}
/* returns the first protocol (caller can then follow the *next pointers) */
struct proto* get_first_protocol(void)
{
return protocols;
}
void set_protocol_list(struct proto* prots)
{
protocols = prots;
}
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
#define HEXDUMP_COLS 16
@@ -131,7 +113,7 @@ void hexdump(const char *mem, unsigned int len)
}
/* Is the buffer the beginning of an SSH connection? */
static int is_ssh_protocol(const char *p, int len, struct proto *proto)
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (len < 4)
return PROBE_AGAIN;
@@ -149,7 +131,7 @@ static int is_ssh_protocol(const char *p, int len, struct proto *proto)
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
* and OpenVPN ssl.c, ssl.h and options.c
*/
static int is_openvpn_protocol (const char*p,int len, struct proto *proto)
static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto)
{
int packet_len;
@@ -164,7 +146,7 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto)
* Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf
* First connection starts with "0 " in 1.0.15)
* */
static int is_tinc_protocol( const char *p, int len, struct proto *proto)
static int is_tinc_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (len < 2)
return PROBE_AGAIN;
@@ -176,15 +158,18 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto)
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
* clients, just checking first frame containing "jabber" in xml entity)
* */
static int is_xmpp_protocol( const char *p, int len, struct proto *proto)
static int is_xmpp_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
if (memmem(p, len, "jabber", 6))
return PROBE_MATCH;
/* sometimes the word 'jabber' shows up late in the initial string,
sometimes after a newline. this makes sure we snarf the entire preamble
and detect it. (fixed for adium/pidgin) */
if (len < 50)
return PROBE_AGAIN;
return memmem(p, len, "jabber", 6) ? 1 : 0;
return PROBE_NEXT;
}
static int probe_http_method(const char *p, int len, const char *opt)
@@ -192,11 +177,11 @@ static int probe_http_method(const char *p, int len, const char *opt)
if (len < strlen(opt))
return PROBE_AGAIN;
return !strncmp(p, opt, len);
return !strncmp(p, opt, strlen(opt));
}
/* Is the buffer the beginning of an HTTP connection? */
static int is_http_protocol(const char *p, int len, struct proto *proto)
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
int res;
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
@@ -221,45 +206,15 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
return PROBE_NEXT;
}
static int is_sni_alpn_protocol(const char *p, int len, struct proto *proto)
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
int valid_tls;
valid_tls = parse_tls_header(proto->data, p, len);
if(valid_tls < 0)
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
/* There *was* a valid match */
return PROBE_MATCH;
}
static int is_tls_protocol(const char *p, int len, struct proto *proto)
{
if (len < 6)
return PROBE_AGAIN;
/* TLS packet starts with a record "Hello" (0x16), followed by the number of
* the highest version of SSL/TLS supported.
*
* A SSLv2 record header contains a two or three byte length code. If the
* most significant bit is set in the first byte of the record length code
* then the record has no padding and the total header length will be 2
* bytes, otherwise the record has padding and the total header length will
* be 3 bytes. Next, a 1 char sized client-hello (0x01) is expected,
* followed by a 2 char sized version that indicates the highest version of
* TLS/SSL supported by the sender. [SSL2] Hickman, Kipp, "The SSL Protocol"
*
* We're checking the highest version of TLS/SSL supported against
* (0x03 0x00-0x03) (RFC6101 A.1). This means we reject the usage of SSLv2
* and lower, which is actually a good thing (RFC6176).
*/
if (p[0] == 0x16) // TLS client-hello
return p[1] == 0x03 && ( p[2] >= 0 && p[2] <= 0x03);
if ((p[0] & 0x80) != 0) // SSLv2 client-hello, no padding
return p[2] == 0x01 && p[3] == 0x03 && ( p[4] >= 0 && p[4] <= 0x03);
else // SSLv2 client-hello, padded
return p[3] == 0x01 && p[4] == 0x03 && ( p[5] >= 0 && p[5] <= 0x03);
switch (parse_tls_header(proto->data, p, len)) {
case TLS_MATCH: return PROBE_MATCH;
case TLS_NOMATCH: return PROBE_NEXT;
case TLS_ELENGTH: return PROBE_AGAIN;
default: return PROBE_NEXT;
}
}
static int probe_adb_cnxn_message(const char *p)
@@ -272,7 +227,7 @@ static int probe_adb_cnxn_message(const char *p)
return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5);
}
static int is_adb_protocol(const char *p, int len, struct proto *proto)
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
/* amessage.data_length is not being checked, under the assumption that
* a packet >= 30 bytes will have "something" in the payload field.
@@ -311,16 +266,66 @@ static int is_adb_protocol(const char *p, int len, struct proto *proto)
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
}
static int regex_probe(const char *p, int len, struct proto *proto)
static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_protocols_item* proto)
{
unsigned char* p = (unsigned char*)p_in;
int i;
if (len < 2)
return PROBE_AGAIN;
/* First byte should be socks protocol version */
if (p[0] != 5)
return PROBE_NEXT;
/* Second byte should be number of supported
* authentication methods, assuming maximum of 10,
* as defined in https://www.iana.org/assignments/socks-methods/socks-methods.xhtml
*/
char m_count = p[1];
if (m_count < 1 || m_count > 10)
return PROBE_NEXT;
if (len < 2 + m_count)
return PROBE_AGAIN;
/* Each authentication method number should be in range 0..9
* (https://www.iana.org/assignments/socks-methods/socks-methods.xhtml)
*/
for (i = 0; i < m_count; i++) {
if (p[2 + i] > 9)
return PROBE_NEXT;
}
return PROBE_MATCH;
}
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
int res, i, j;
res = sscanf(p, "<%d>", &i);
if (res == 1) return 1;
res = sscanf(p, "%d <%d>", &i, &j);
if (res == 2) return 1;
return 0;
}
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
#ifdef ENABLE_REGEX
regex_t **probe = proto->data;
regmatch_t pos = { 0, len };
pcre2_code**probe = (pcre2_code**)proto->data;
pcre2_match_data* matches;
for (; *probe && regexec(*probe, p, 0, &pos, REG_STARTEND); probe++)
/* try them all */;
matches = pcre2_match_data_create(1, NULL);
return (*probe != NULL);
for (; *probe; probe++) {
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL);
if (res >= 0) return 1;
}
return 0;
#else
/* Should never happen as we check when loading config file */
fprintf(stderr, "FATAL: regex probe called but not built in\n");
@@ -328,17 +333,69 @@ static int regex_probe(const char *p, int len, struct proto *proto)
#endif
}
/*
/* Run all the probes on a buffer
* Returns
* PROBE_AGAIN if not enough data, and set *proto to NULL
* PROBE_MATCH if protocol is identified, in which case *proto is set to
* point to the appropriate protocol
* */
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
{
struct sslhcfg_protocols_item* p;
int i, res, again = 0;
if (cfg.verbose > 1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buf, len);
}
*proto = NULL;
for (i = 0; i < cfg.protocols_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
p = &cfg.protocols[i];
if (! p->probe) continue;
if (cfg.verbose) fprintf(stderr, "probing for %s\n", p->name);
/* Don't probe last protocol if it is anyprot (and store last protocol) */
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot")))
break;
if (p->minlength_is_present && (len < p->minlength )) {
fprintf(stderr, "input too short, %d bytes but need %d\n", len , p->minlength);
again++;
continue;
}
res = p->probe(buf, len, p);
if (cfg.verbose) fprintf(stderr, "probed for %s: %s\n", p->name, probe_str[res]);
if (res == PROBE_MATCH) {
*proto = p;
return PROBE_MATCH;
}
if (res == PROBE_AGAIN)
again++;
}
if (again)
return PROBE_AGAIN;
/* Everything failed: match the last one */
*proto = &cfg.protocols[cfg.protocols_len-1];
return PROBE_MATCH;
}
/*
* Read the beginning of data coming from the client connection and check if
* it's a known protocol.
* it's a known protocol.
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
* which case cnx->proto is set to the appropriate protocol.
*/
int probe_client_protocol(struct connection *cnx)
{
char buffer[BUFSIZ];
struct proto *p;
int n;
ssize_t n;
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
/* It's possible that read() returns an error, e.g. if the client
@@ -347,60 +404,29 @@ int probe_client_protocol(struct connection *cnx)
* function does not have to deal with a specific failure condition (the
* connection will just fail later normally). */
/* Dump hex values of the packet */
if (verbose>1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buffer, n);
}
if (n > 0) {
int res = PROBE_NEXT;
defer_write(&cnx->q[1], buffer, n);
for (p = cnx->proto; p && res == PROBE_NEXT; p = p->next) {
if (! p->probe) continue;
if (verbose) fprintf(stderr, "probing for %s\n", p->description);
cnx->proto = p;
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
}
if (res != PROBE_NEXT)
return res;
return probe_buffer(cnx->q[1].begin_deferred_data,
cnx->q[1].deferred_data_size,
&cnx->proto);
}
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
/* If none worked, return the first one affected (that's completely
* arbitrary) */
cnx->proto = protocols;
/* read() returned an error, so just connect to the last protocol to die */
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
return PROBE_MATCH;
}
/* Returns the structure for specified protocol or NULL if not found */
static struct proto* get_protocol(const char* description)
{
int i;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].description, description)) {
return &builtins[i];
}
}
return NULL;
}
/* Returns the probe for specified protocol:
* parameter is the description in builtins[], or "regex"
* */
T_PROBE* get_probe(const char* description) {
struct proto* p = get_protocol(description);
int i;
if (p)
return p->probe;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].name, description)) {
return builtins[i].probe;
}
}
/* Special case of "regex" probe (we don't want to set it in builtins
* because builtins is also used to build the command-line options and
@@ -408,10 +434,6 @@ T_PROBE* get_probe(const char* description) {
if (!strcmp(description, "regex"))
return regex_probe;
/* Special case of "sni/alpn" probe for same reason as above*/
if (!strcmp(description, "sni_alpn"))
return is_sni_alpn_protocol;
/* Special case of "timeout" is allowed as a probe name in the
* configuration file even though it's not really a probe */
if (!strcmp(description, "timeout"))
+14 -22
View File
@@ -12,30 +12,19 @@ typedef enum {
PROBE_AGAIN, /* Not enough data for this probe, try again with more data */
} probe_result;
struct proto;
typedef int T_PROBE(const char*, int, struct proto*);
struct sslhcfg_protocols_item;
typedef int T_PROBE(const char*, ssize_t, struct sslhcfg_protocols_item*);
/* For each protocol we need: */
struct proto {
const char* description; /* a string that says what it is (for logging and command-line parsing) */
const char* service; /* service name to do libwrap checks */
struct addrinfo *saddr; /* list of addresses to try and switch that protocol */
int log_level; /* 0: No logging of connection
* 1: Log incoming connection
*/
int keepalive; /* 0: No keepalive ; 1: Set Keepalive for this connection */
int fork; /* 0: Connection can run within shared process ; 1: Separate process required for this connection */
/* function to probe that protocol; parameters are buffer and length
* containing the data to probe, and a pointer to the protocol structure */
struct protocol_probe_desc {
const char* name;
T_PROBE* probe;
/* opaque pointer ; used to pass list of regex to regex probe, or TLSProtocol struct to sni/alpn probe */
void* data;
struct proto *next; /* pointer to next protocol in list, NULL if last */
};
#include "sslh-conf.h"
/* Returns a pointer to the array of builtin protocols */
struct proto * get_builtins(void);
struct protocol_probe_desc* get_builtins(void);
/* Returns the number of builtin protocols */
int get_num_builtins(void);
@@ -44,10 +33,10 @@ int get_num_builtins(void);
T_PROBE* get_probe(const char* description);
/* Returns the head of the configured protocols */
struct proto* get_first_protocol(void);
struct sslhcfg_protocols_item* get_first_protocol(void);
/* Set the list of configured protocols */
void set_protocol_list(struct proto*);
void set_protocol_list(struct sslhcfg_protocols_item*);
/* probe_client_protocol
*
@@ -58,6 +47,9 @@ void set_protocol_list(struct proto*);
*/
int probe_client_protocol(struct connection *cnx);
/* Probe, but on a buffer */
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto);
/* set the protocol to connect to in case of timeout */
void set_ontimeout(const char* name);
@@ -65,7 +57,7 @@ void set_ontimeout(const char* name);
*
* Returns the protocol to connect to in case of timeout
*/
struct proto* timeout_protocol(void);
struct sslhcfg_protocols_item* timeout_protocol(void);
void hexdump(const char*, unsigned int);
+1 -1
View File
@@ -9,7 +9,7 @@
# is needed in order to run as sslh user
#
#SSLH_USER=sslh
#setcap cap_net_bind_service,cap_net_admin=+ep $SSLH
#setcap cap_net_bind_service,cap_net_raw=+ep $SSLH
#
# Configuration file for sslh
+2 -2
View File
@@ -4,11 +4,11 @@ After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/bin/sslh --foreground $DAEMON_OPTS
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
+2115
View File
File diff suppressed because it is too large. Load diff
+113
View File
@@ -0,0 +1,113 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Fri Aug 13 18:03:18 2021.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2021 Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef C2S_SSLHCFG_H
#define C2S_SSLHCFG_H
#ifdef LIBCONFIG
# include <libconfig.h>
#endif
#include "probe.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
struct sslhcfg_listen_item {
char* host;
char* port;
int is_udp;
int keepalive;
};
struct sslhcfg_protocols_item {
char* name;
char* host;
char* port;
int service_is_present;
char* service;
int is_udp;
int udp_timeout;
int fork;
int tfo_ok;
int transparent;
int log_level;
int keepalive;
size_t sni_hostnames_len;
char** sni_hostnames;
size_t alpn_protocols_len;
char** alpn_protocols;
size_t regex_patterns_len;
char** regex_patterns;
int minlength_is_present;
int minlength;
T_PROBE* probe;
struct addrinfo* saddr;
void* data;
};
struct sslhcfg_item {
int verbose;
int foreground;
int inetd;
int numeric;
int transparent;
int timeout;
int user_is_present;
char* user;
int pidfile_is_present;
char* pidfile;
int chroot_is_present;
char* chroot;
char* syslog_facility;
char* on_timeout;
char* prefix;
size_t listen_len;
struct sslhcfg_listen_item* listen;
size_t protocols_len;
struct sslhcfg_protocols_item* protocols;
};
int sslhcfg_parse_file(
const char* filename,
struct sslhcfg_item* sslhcfg,
const char** errmsg);
void sslhcfg_fprint(
FILE* out,
struct sslhcfg_item *sslhcfg,
int depth);
int sslhcfg_cl_parse(
int argc,
char* argv[],
struct sslhcfg_item *sslhcfg);
#endif
+87 -40
View File
@@ -1,7 +1,7 @@
/*
sslh-fork: forking server
# Copyright (C) 2007-2012 Yves Rutschle
# Copyright (C) 2007-2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -22,11 +22,15 @@
#include "common.h"
#include "probe.h"
#include "sslh-conf.h"
#include "udp-listener.h"
#ifdef LIBBSD
#include <bsd/unistd.h>
#endif
const char* server_type = "sslh-fork";
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
/* shovels data from one fd to the other and vice-versa
returns after one socket closed
*/
@@ -53,8 +57,8 @@ int shovel(struct connection *cnx)
for (i = 0; i < 2; i++) {
if (FD_ISSET(cnx->q[i].fd, &fds)) {
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
if (!res) {
if (verbose)
if (res == FD_CNXCLOSED) {
if (cfg.verbose)
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
return res;
}
@@ -72,6 +76,7 @@ void start_shoveler(int in_socket)
int res = PROBE_AGAIN;
int out_socket;
struct connection cnx;
struct connection_desc desc;
init_cnx(&cnx);
cnx.q[0].fd = in_socket;
@@ -79,7 +84,7 @@ void start_shoveler(int in_socket)
FD_ZERO(&fds);
FD_SET(in_socket, &fds);
memset(&tv, 0, sizeof(tv));
tv.tv_sec = probing_timeout;
tv.tv_sec = cfg.timeout;
while (res == PROBE_AGAIN) {
/* POSIX does not guarantee that tv will be updated, but the client can
@@ -94,8 +99,8 @@ void start_shoveler(int in_socket)
} else {
/* Timed out: it's necessarily SSH */
cnx.proto = timeout_protocol();
if (verbose)
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->description);
if (cfg.verbose)
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->name);
break;
}
}
@@ -106,12 +111,16 @@ void start_shoveler(int in_socket)
}
/* Connect the target socket */
out_socket = connect_addr(&cnx, in_socket);
out_socket = connect_addr(&cnx, in_socket, BLOCKING);
CHECK_RES_DIE(out_socket, "connect");
set_capabilities(0);
cnx.q[1].fd = out_socket;
log_connection(&cnx);
get_connection_desc(&desc, &cnx);
log_connection(&desc, &cnx);
set_proctitle_shovel(&desc, &cnx);
flush_deferred(&cnx.q[1]);
@@ -120,13 +129,13 @@ void start_shoveler(int in_socket)
close(in_socket);
close(out_socket);
if (verbose)
if (cfg.verbose)
fprintf(stderr, "connection closed down\n");
exit(0);
}
static int *listener_pid;
static pid_t *listener_pid;
static int listener_pid_number = 0;
void stop_listeners(int sig)
@@ -138,48 +147,85 @@ void stop_listeners(int sig)
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
void set_listen_procname(struct listen_endpoint *listen_socket)
{
int in_socket, i, res;
#ifdef LIBBSD
int res;
struct addrinfo addr;
struct sockaddr_storage ss;
char listen_addr[NI_MAXHOST+1+NI_MAXSERV+1];
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
res = getsockname(listen_socket->socketfd, addr.ai_addr, &addr.ai_addrlen);
if (res != -1) {
sprintaddr(listen_addr, sizeof(listen_addr), &addr);
setproctitle("listener %s", listen_addr);
}
#endif
}
/* TCP listener: connections, fork a child for each new connection
* IN:
* endpoint: array of listening endpoint objects
* num_endpoints: size of endpoint array
* active_endpoint: which endpoint is this listener working on
* Does not return
* */
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
{
int i, in_socket;
while (1) {
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
switch(fork()) {
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
case 0: /* In child process */
/* Shoveler processes don't need to hog file descriptors */
for (i = 0; i < num_endpoints; i++)
close(endpoint[i].socketfd);
start_shoveler(in_socket);
exit(0);
default: /* In parent process */
break;
}
close(in_socket);
}
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
int i, res;
struct sigaction action;
listener_pid_number = num_addr_listen;
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
CHECK_ALLOC(listener_pid, "malloc");
/* Start one process for each listening address */
for (i = 0; i < num_addr_listen; i++) {
listener_pid[i] = fork();
switch(listener_pid[i]) {
// Log if fork() fails for some reason
/* Log if fork() fails for some reason */
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
// We're in the child, we have work to do
/* We're in the child, we have work to do */
case 0:
/* Listening process just accepts a connection, forks, and goes
* back to listening */
while (1)
{
in_socket = accept(listen_sockets[i], 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
switch(fork()) {
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
/* In child process */
case 0:
for (i = 0; i < num_addr_listen; ++i)
close(listen_sockets[i]);
start_shoveler(in_socket);
exit(0);
/* In parent process */
default: break;
}
close(in_socket);
}
set_listen_procname(&listen_sockets[i]);
if (listen_sockets[i].type == SOCK_DGRAM)
log_message(LOG_ERR, "UDP not (yet?) supported in sslh-fork\n");
else
tcp_listener(listen_sockets, num_addr_listen, i);
break;
// We're in the parent, we don't need to do anything
/* We're in the parent, we don't need to do anything */
default:
break;
}
@@ -193,6 +239,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
res = sigaction(SIGTERM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
wait(NULL);
}
+90 -495
View File
@@ -2,7 +2,7 @@
# main: processing of config file, command line options and start the main
# loop.
#
# Copyright (C) 2007-2016 Yves Rutschle
# Copyright (C) 2007-2018 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -26,11 +26,12 @@
#include <libconfig.h>
#endif
#ifdef ENABLE_REGEX
#ifdef LIBPCRE
#include <pcreposix.h>
#else
#include <regex.h>
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#endif
#ifdef LIBBSD
#include <bsd/unistd.h>
#endif
#include "common.h"
@@ -40,7 +41,7 @@ const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <add> [-p <addr> ...] \n" \
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <addr> [-p <addr> ...] \n" \
"%s\n\n" /* Dynamically built list of builtin protocols */ \
"\t[--on-timeout <addr>]\n" \
"-v: verbose\n" \
@@ -62,44 +63,6 @@ const char* USAGE_STRING =
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
static struct option const_options[] = {
{ "inetd", no_argument, &inetd, 1 },
{ "foreground", no_argument, &foreground, 1 },
{ "background", no_argument, &background, 1 },
{ "transparent", no_argument, &transparent, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "verbose", no_argument, &verbose, 1 },
{ "user", required_argument, 0, 'u' },
{ "config", optional_argument, 0, 'F' },
{ "pidfile", required_argument, 0, 'P' },
{ "chroot", required_argument, 0, 'C' },
{ "timeout", required_argument, 0, 't' },
{ "on-timeout", required_argument, 0, OPT_ONTIMEOUT },
{ "listen", required_argument, 0, 'p' },
{}
};
static struct option* all_options;
static struct proto* builtins;
static const char *optstr = "vt:T:p:VP:C:F::";
static void print_usage(void)
{
struct proto *p;
int i;
int res;
char *prots = "";
p = get_builtins();
for (i = 0; i < get_num_builtins(); i++) {
res = asprintf(&prots, "%s\t[--%s <addr>]\n", prots, p[i].description);
CHECK_RES_DIE(res, "asprintf");
}
fprintf(stderr, USAGE_STRING, prots);
}
static void printcaps(void) {
#ifdef LIBCAP
cap_t caps;
@@ -120,514 +83,146 @@ static void printcaps(void) {
static void printsettings(void)
{
char buf[NI_MAXHOST];
struct addrinfo *a;
struct proto *p;
int i;
struct sslhcfg_protocols_item *p;
for (p = get_first_protocol(); p; p = p->next) {
for (i = 0; i < cfg.protocols_len; i++ ) {
p = &cfg.protocols[i];
fprintf(stderr,
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s]\n",
p->description,
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n",
p->name,
sprintaddr(buf, sizeof(buf), p->saddr),
p->service,
p->log_level,
p->saddr->ai_family,
p->saddr->ai_addr->sa_family,
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "");
p->fork ? "fork" : "",
p->transparent ? "transparent" : ""
);
}
fprintf(stderr, "listening on:\n");
for (a = addr_listen; a; a = a->ai_next) {
fprintf(stderr,
"\t%s\t[%s]\n",
sprintaddr(buf, sizeof(buf), a),
a->ai_flags & SO_KEEPALIVE ? "keepalive" : "");
}
fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout,
timeout_protocol()->description);
fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout,
timeout_protocol()->name);
}
/* Extract configuration on addresses and ports on which to listen.
* out: newly allocated list of addrinfo to listen to
*/
#ifdef LIBCONFIG
static int config_listen(config_t *config, struct addrinfo **listen)
{
config_setting_t *setting, *addr;
int len, i, keepalive;
const char *hostname, *port;
setting = config_lookup(config, "listen");
if (setting) {
len = config_setting_length(setting);
for (i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
}
keepalive = 0;
config_setting_lookup_bool(addr, "keepalive", &keepalive);
resolve_split_name(listen, hostname, port);
/* getaddrinfo returned a list of addresses corresponding to the
* specification; move the pointer to the end of that list before
* processing the next specification, while setting flags for
* start_listen_sockets() through ai_flags (which is not meant for
* that, but is only used as hint in getaddrinfo, so it's OK) */
for (; *listen; listen = &((*listen)->ai_next)) {
if (keepalive)
(*listen)->ai_flags = SO_KEEPALIVE;
}
}
}
return 0;
}
#endif
#ifdef LIBCONFIG
static void setup_regex_probe(struct proto *p, config_setting_t* probes)
{
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
#ifdef ENABLE_REGEX
int num_probes, errsize, i, res;
char *err;
const char * expr;
regex_t** probe_list;
{
int num_patterns, i, error;
pcre2_code** pattern_list;
PCRE2_SIZE error_offset;
PCRE2_UCHAR8 err_str[120];
num_probes = config_setting_length(probes);
if (!num_probes) {
fprintf(stderr, "%s: no probes specified\n", p->description);
exit(1);
}
num_patterns = p->regex_patterns_len;
p->probe = get_probe("regex");
probe_list = calloc(num_probes + 1, sizeof(*probe_list));
p->data = (void*)probe_list;
pattern_list = calloc(num_patterns + 1, sizeof(*pattern_list));
CHECK_ALLOC(pattern_list, "calloc");
p->data = (void*)pattern_list;
for (i = 0; i < num_probes; i++) {
probe_list[i] = malloc(sizeof(*(probe_list[i])));
expr = config_setting_get_string_elem(probes, i);
res = regcomp(probe_list[i], expr, REG_EXTENDED);
if (res) {
err = malloc(errsize = regerror(res, probe_list[i], NULL, 0));
regerror(res, probe_list[i], err, errsize);
fprintf(stderr, "%s:%s\n", expr, err);
free(err);
for (i = 0; i < num_patterns; i++) {
pattern_list[i] = pcre2_compile((PCRE2_SPTR8)p->regex_patterns[i],
PCRE2_ZERO_TERMINATED, 0,
&error, &error_offset, NULL);
if (!pattern_list[i]) {
pcre2_get_error_message(error, err_str, sizeof(err_str));
fprintf(stderr, "compiling pattern /%s/:%d:%s at offset %ld\n",
p->regex_patterns[i], error, err_str, error_offset);
exit(1);
}
}
}
#else
fprintf(stderr, "line %d: regex probe specified but not compiled in\n", config_setting_source_line(probes));
exit(5);
#endif
}
#endif
#ifdef LIBCONFIG
static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char* name, int alpn)
{
int num_probes, i, max_server_name_len, server_name_len;
const char * config_item;
char** sni_hostname_list;
num_probes = config_setting_length(config_items);
if (!num_probes) {
fprintf(stderr, "%s: no %s specified\n", p->description, name);
return;
}
max_server_name_len = 0;
for (i = 0; i < num_probes; i++) {
server_name_len = strlen(config_setting_get_string_elem(config_items, i));
if(server_name_len > max_server_name_len)
max_server_name_len = server_name_len;
}
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
for (i = 0; i < num_probes; i++) {
config_item = config_setting_get_string_elem(config_items, i);
sni_hostname_list[i] = malloc(max_server_name_len);
strcpy (sni_hostname_list[i], config_item);
if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]);
}
p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list);
}
static void setup_sni_alpn(struct proto *p, config_setting_t* prot)
{
config_setting_t *sni_hostnames, *alpn_protocols;
p->data = (void*)new_tls_data();
sni_hostnames = config_setting_get_member(prot, "sni_hostnames");
alpn_protocols = config_setting_get_member(prot, "alpn_protocols");
if(sni_hostnames && config_setting_is_array(sni_hostnames)) {
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0);
}
if(alpn_protocols && config_setting_is_array(alpn_protocols)) {
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1);
}
return;
}
#endif
/* Extract configuration for protocols to connect to.
* out: newly-allocated list of protocols
/* For each protocol in the configuration, resolve address and set up protocol
* options if required
*/
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
static void config_protocols()
{
config_setting_t *setting, *prot, *patterns;
const char *hostname, *port, *name;
int i, num_prots;
struct proto *p, *prev = NULL;
setting = config_lookup(config, "protocols");
if (setting) {
num_prots = config_setting_length(setting);
for (i = 0; i < num_prots; i++) {
p = calloc(1, sizeof(*p));
if (i == 0) *prots = p;
if (prev) prev->next = p;
prev = p;
prot = config_setting_get_elem(setting, i);
if ((config_setting_lookup_string(prot, "name", &name) &&
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
)) {
p->description = name;
config_setting_lookup_string(prot, "service", &(p->service));
config_setting_lookup_bool(prot, "keepalive", &p->keepalive);
config_setting_lookup_bool(prot, "fork", &p->fork);
if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) {
p->log_level = 1;
}
if (resolve_split_name(&(p->saddr), hostname, port)) {
fprintf(stderr, "line %d: cannot resolve %s:%s\n", config_setting_source_line(prot), hostname, port);
exit(1);
}
p->probe = get_probe(name);
if (!p->probe || !strcmp(name, "sni_alpn")) {
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
exit(1);
}
/* Probe-specific options: regex patterns */
if (!strcmp(name, "regex")) {
patterns = config_setting_get_member(prot, "regex_patterns");
if (patterns && config_setting_is_array(patterns)) {
setup_regex_probe(p, patterns);
}
}
/* Probe-specific options: SNI/ALPN */
if (!strcmp(name, "tls")) {
setup_sni_alpn(p, prot);
}
} else {
fprintf(stderr, "line %d: Illegal protocol description (missing name, host or port)\n", config_setting_source_line(prot));
exit(1);
}
int i;
for (i = 0; i < cfg.protocols_len; i++) {
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
if (resolve_split_name(&(p->saddr), p->host, p->port)) {
fprintf(stderr, "cannot resolve %s:%s\n", p->host, p->port);
exit(4);
}
}
return 0;
}
#endif
/* Parses a config file
* in: *filename
* out: *listen, a newly-allocated linked list of listen addrinfo
* *prots, a newly-allocated linked list of protocols
* 1 on error, 0 on success
*/
#ifdef LIBCONFIG
static int config_parse(char *filename, struct addrinfo **listen, struct proto **prots)
{
config_t config;
int timeout;
const char* str;
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* If it's a parse error then there will be a line number for the failure
* an I/O error (such as non-existent file) will have the error line as 0
*/
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
p->probe = get_probe(p->name);
if (!p->probe) {
fprintf(stderr, "%s: probe unknown\n", p->name);
exit(1);
}
fprintf(stderr, "%s:%s\n",
filename,
config_error_text(&config));
return 1;
}
if(config_lookup_bool(&config, "verbose", &verbose) == CONFIG_FALSE) {
config_lookup_int(&config, "verbose", &verbose);
}
if (!strcmp(cfg.protocols[i].name, "regex")) {
setup_regex_probe(&cfg.protocols[i]);
}
config_lookup_bool(&config, "inetd", &inetd);
config_lookup_bool(&config, "foreground", &foreground);
config_lookup_bool(&config, "numeric", &numeric);
config_lookup_bool(&config, "transparent", &transparent);
if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) {
probing_timeout = timeout;
}
if (config_lookup_string(&config, "on-timeout", &str)) {
set_ontimeout(str);
}
config_lookup_string(&config, "user", &user_name);
config_lookup_string(&config, "pidfile", &pid_file);
config_lookup_string(&config, "chroot", &chroot_path);
config_lookup_string(&config, "syslog_facility", &facility);
config_listen(&config, listen);
config_protocols(&config, prots);
return 0;
}
#endif
/* Adds protocols to the list of options, so command-line parsing uses the
* protocol definition array
* options: array of options to add to; must be big enough
* n_opts: number of options in *options before calling (i.e. where to append)
* prot: array of protocols
* n_prots: number of protocols in *prot
* */
static void append_protocols(struct option *options, int n_opts, struct proto *prot , int n_prots)
{
int o, p;
for (o = n_opts, p = 0; p < n_prots; o++, p++) {
options[o].name = prot[p].description;
options[o].has_arg = required_argument;
options[o].flag = 0;
options[o].val = p + PROT_SHIFT;
if (!strcmp(cfg.protocols[i].name, "tls")) {
cfg.protocols[i].data = (void*)new_tls_data();
if (cfg.protocols[i].sni_hostnames_len)
tls_data_set_list(cfg.protocols[i].data, 0,
(const char**) cfg.protocols[i].sni_hostnames,
cfg.protocols[i].sni_hostnames_len);
if (cfg.protocols[i].alpn_protocols_len)
tls_data_set_list(cfg.protocols[i].data, 1,
(const char**) cfg.protocols[i].alpn_protocols,
cfg.protocols[i].alpn_protocols_len);
}
}
}
static void make_alloptions(void)
{
builtins = get_builtins();
/* Create all_options, composed of const_options followed by one option per
* known protocol */
all_options = calloc(ARRAY_SIZE(const_options) + get_num_builtins(), sizeof(struct option));
memcpy(all_options, const_options, sizeof(const_options));
append_protocols(all_options, ARRAY_SIZE(const_options) - 1, builtins, get_num_builtins());
}
/* Performs a first scan of command line options to see if a configuration file
* is specified. If there is one, parse it now before all other options (so
* configuration file settings can be overridden from the command line).
*
* prots: newly-allocated list of configured protocols, if any.
*/
static void cmdline_config(int argc, char* argv[], struct proto** prots)
{
#ifdef LIBCONFIG
int c, res;
char *config_filename;
#endif
make_alloptions();
#ifdef LIBCONFIG
optind = 1;
opterr = 0; /* we're missing protocol options at this stage so don't output errors */
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 'v') {
verbose++;
}
if (c == 'F') {
config_filename = optarg;
if (config_filename) {
res = config_parse(config_filename, &addr_listen, prots);
} else {
/* No configuration file specified -- try default file locations */
res = config_parse("/etc/sslh/sslh.cfg", &addr_listen, prots);
if (!res && verbose) fprintf(stderr, "Using /etc/sslh/sslh.cfg\n");
if (res) {
res = config_parse("/etc/sslh.cfg", &addr_listen, prots);
if (!res && verbose) fprintf(stderr, "Using /etc/sslh.cfg\n");
}
}
if (res)
exit(4);
break;
}
}
#endif
}
/* Parse command-line options. prots points to a list of configured protocols,
* potentially non-allocated */
static void parse_cmdline(int argc, char* argv[], struct proto* prots)
{
int c;
struct addrinfo **a;
struct proto *p;
optind = 1;
opterr = 1;
next_arg:
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 0) continue;
if (c >= PROT_SHIFT) {
if (prots)
for (p = prots; p && p->next; p = p->next) {
/* override if protocol was already defined by config file
* (note it only overrides address and use builtin probe) */
if (!strcmp(p->description, builtins[c-PROT_SHIFT].description)) {
resolve_name(&(p->saddr), optarg);
p->probe = builtins[c-PROT_SHIFT].probe;
goto next_arg;
}
}
/* At this stage, it's a new protocol: add it to the end of the
* list */
if (!prots) {
/* No protocols yet -- create the list */
p = prots = calloc(1, sizeof(*p));
} else {
p->next = calloc(1, sizeof(*p));
p = p->next;
}
memcpy(p, &builtins[c-PROT_SHIFT], sizeof(*p));
resolve_name(&(p->saddr), optarg);
continue;
}
switch (c) {
case 'F':
/* Legal option, but do nothing, it was already processed in
* cmdline_config() */
#ifndef LIBCONFIG
fprintf(stderr, "Built without libconfig support: configuration file not available.\n");
exit(1);
#endif
break;
case 't':
probing_timeout = atoi(optarg);
break;
case OPT_ONTIMEOUT:
set_ontimeout(optarg);
break;
case 'p':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
/* append the specified addresses */
resolve_name(a, optarg);
break;
case 'V':
printf("%s %s\n", server_type, VERSION);
exit(0);
case 'u':
user_name = optarg;
break;
case 'P':
pid_file = optarg;
break;
case 'C':
chroot_path = optarg;
break;
case 'v':
verbose++;
break;
default:
print_usage();
exit(2);
}
}
if (!prots) {
void config_sanity_check(struct sslhcfg_item* cfg) {
if (!cfg->protocols_len) {
fprintf(stderr, "At least one target protocol must be specified.\n");
exit(2);
}
set_protocol_list(prots);
/* If compiling with systemd socket support no need to require listen address */
#ifndef SYSTEMD
if (!addr_listen && !inetd) {
if (!cfg->listen_len && !cfg->inetd) {
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
#endif
/* Did command-line override foreground setting? */
if (background)
foreground = 0;
}
int main(int argc, char *argv[])
int main(int argc, char *argv[], char* envp[])
{
extern char *optarg;
extern int optind;
int res, num_addr_listen;
struct proto* protocols = NULL;
struct listen_endpoint *listen_sockets;
int *listen_sockets;
#ifdef LIBBSD
setproctitle_init(argc, argv, envp);
#endif
/* Init defaults */
pid_file = NULL;
user_name = NULL;
chroot_path = NULL;
memset(&cfg, 0, sizeof(cfg));
res = sslhcfg_cl_parse(argc, argv, &cfg);
if (res) exit(6);
if (cfg.verbose > 3)
sslhcfg_fprint(stderr, &cfg, 0);
config_protocols();
config_sanity_check(&cfg);
cmdline_config(argc, argv, &protocols);
parse_cmdline(argc, argv, protocols);
if (inetd)
if (cfg.inetd)
{
verbose = 0;
cfg.verbose = 0;
start_shoveler(0);
exit(0);
}
if (verbose)
if (cfg.verbose)
printsettings();
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
num_addr_listen = start_listen_sockets(&listen_sockets);
#ifdef SYSTEMD
if (num_addr_listen < 1) {
@@ -636,7 +231,7 @@ int main(int argc, char *argv[])
}
#endif
if (!foreground) {
if (!cfg.foreground) {
if (fork() > 0) exit(0); /* Detach */
/* New session -- become group leader */
@@ -648,16 +243,16 @@ int main(int argc, char *argv[])
setup_signals();
if (pid_file)
write_pid_file(pid_file);
if (cfg.pidfile)
write_pid_file(cfg.pidfile);
/* Open syslog connection before we drop privs/chroot */
setup_syslog(argv[0]);
if (user_name || chroot_path)
drop_privileges(user_name, chroot_path);
if (cfg.user || cfg.chroot)
drop_privileges(cfg.user, cfg.chroot);
if (verbose)
if (cfg.verbose)
printcaps();
main_loop(listen_sockets, num_addr_listen);
+355 -213
View File
@@ -1,7 +1,7 @@
/*
sslh-select: mono-processus server
# Copyright (C) 2007-2010 Yves Rutschle
# Copyright (C) 2007-2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -20,47 +20,54 @@
*/
/* Why use select(2) rather than poll(2)?
* No real reason except that's how it was written at first. This article:
* https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few
* hundred file descriptors, both become very slow, so there is little
* incentive to move to poll() to support more than FD_SETSIZE (which is 1024
* on many Linux. To support large numbers of descriptors, either use the fork
* version, or we'll have to write a new version based on libev. */
#define __LINUX__
#include <limits.h>
#include "common.h"
#include "probe.h"
#include "udp-listener.h"
#include "collection.h"
#include "gap.h"
static int debug = 0;
const char* server_type = "sslh-select";
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
/* Global state for a select() loop */
struct select_info {
int max_fd; /* Highest fd number to pass to select() */
/* cnx_num_alloc is the number of connection to allocate at once (at start-up,
* and then every time we get too many simultaneous connections: e.g. start
* with 100 slots, then if we get more than 100 connections allocate another
* 100 slots, and so on). We never free up connection structures. We try to
* allocate as many structures at once as will fit in one page (which is 102
* in sslh 1.9 on Linux on x86)
*/
static long cnx_num_alloc;
int num_probing; /* Number of connections currently probing
* We use this to know if we need to time out of
* select() */
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
/* Make the file descriptor non-block */
int set_nonblock(int fd)
{
int flags;
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
cnx_collection* collection; /* Collection of connections linked to this loop */
flags = fcntl(fd, F_GETFL);
CHECK_RES_RETURN(flags, "fcntl");
time_t next_timeout; /* time at which next UDP connection times out */
};
flags |= O_NONBLOCK;
flags = fcntl(fd, F_SETFL, flags);
CHECK_RES_RETURN(flags, "fcntl");
return flags;
}
int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
static int tidy_connection(struct connection *cnx, struct select_info* fd_info)
{
int i;
fd_set* fds = &fd_info->fds_r;
fd_set* fds2 = &fd_info->fds_w;
for (i = 0; i < 2; i++) {
if (cnx->q[i].fd != -1) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
FD_CLR(cnx->q[i].fd, fds);
@@ -70,13 +77,13 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
free(cnx->q[i].deferred_data);
}
}
init_cnx(cnx);
collection_remove_cnx(fd_info->collection, cnx);
return 0;
}
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
* and FD_CLR. Need to drop connections if we go above that limit */
int fd_is_in_range(int fd) {
static int fd_is_in_range(int fd) {
if (fd >= FD_SETSIZE) {
log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n");
return 0;
@@ -87,73 +94,56 @@ int fd_is_in_range(int fd) {
/* Accepts a connection from the main socket and assigns it to an empty slot.
* If no slots are available, allocate another few. If that fails, drop the
* connexion */
int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size)
static struct connection* accept_new_connection(int listen_socket, struct cnx_collection *collection)
{
int in_socket, free, i, res;
struct connection *new;
int in_socket, res;
if (cfg.verbose) fprintf(stderr, "accepting from %d\n", listen_socket);
in_socket = accept(listen_socket, 0, 0);
CHECK_RES_RETURN(in_socket, "accept");
CHECK_RES_RETURN(in_socket, "accept", NULL);
if (!fd_is_in_range(in_socket)) {
close(in_socket);
return -1;
return NULL;
}
res = set_nonblock(in_socket);
if (res == -1) {
close(in_socket);
return -1;
return NULL;
}
/* Find an empty slot */
for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) {
/* nothing */
struct connection* cnx = collection_alloc_cnx_from_fd(collection, in_socket);
if (!cnx) {
close(in_socket);
return NULL;
}
if (free >= *cnx_size) {
if (verbose)
fprintf(stderr, "buying more slots from the slot machine.\n");
new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0]));
if (!new) {
log_message(LOG_ERR, "unable to realloc -- dropping connection\n");
close(in_socket);
return -1;
}
*cnx = new;
*cnx_size += cnx_num_alloc;
for (i = free; i < *cnx_size; i++) {
init_cnx(&(*cnx)[i]);
}
}
(*cnx)[free].q[0].fd = in_socket;
(*cnx)[free].state = ST_PROBING;
(*cnx)[free].probe_timeout = time(NULL) + probing_timeout;
if (verbose)
fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free);
return in_socket;
return cnx;
}
/* Connect queue 1 of connection to SSL; returns new file descriptor */
int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
static int connect_queue(struct connection* cnx,
struct select_info* fd_info)
{
struct queue *q = &cnx->q[1];
q->fd = connect_addr(cnx, cnx->q[0].fd);
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
if ((q->fd != -1) && fd_is_in_range(q->fd)) {
log_connection(cnx);
set_nonblock(q->fd);
log_connection(NULL, cnx);
flush_deferred(q);
if (q->deferred_data) {
FD_SET(q->fd, fds_w);
FD_CLR(cnx->q[0].fd, fds_r);
FD_SET(q->fd, &fd_info->fds_w);
FD_CLR(cnx->q[0].fd, &fd_info->fds_r);
}
FD_SET(q->fd, fds_r);
FD_SET(q->fd, &fd_info->fds_r);
collection_add_fd(fd_info->collection, cnx, q->fd);
return q->fd;
} else {
tidy_connection(cnx, fds_r, fds_w);
tidy_connection(cnx, fd_info);
return -1;
}
}
@@ -161,26 +151,25 @@ int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
/* shovels data from active fd to the other
returns after one socket closed or operation would block
*/
void shovel(struct connection *cnx, int active_fd,
fd_set *fds_r, fd_set *fds_w)
static void shovel(struct connection *cnx, int active_fd, struct select_info* fd_info)
{
struct queue *read_q, *write_q;
read_q = &cnx->q[active_fd];
write_q = &cnx->q[1-active_fd];
if (verbose)
if (cfg.verbose)
fprintf(stderr, "activity on fd%d\n", read_q->fd);
switch(fd2fd(write_q, read_q)) {
case -1:
case FD_CNXCLOSED:
tidy_connection(cnx, fds_r, fds_w);
tidy_connection(cnx, fd_info);
break;
case FD_STALLED:
FD_SET(write_q->fd, fds_w);
FD_CLR(read_q->fd, fds_r);
FD_SET(write_q->fd, &fd_info->fds_w);
FD_CLR(read_q->fd, &fd_info->fds_r);
break;
default: /* Nothing */
@@ -191,7 +180,7 @@ void shovel(struct connection *cnx, int active_fd,
/* shovels data from one fd to the other and vice-versa
returns after one socket closed
*/
void shovel_single(struct connection *cnx)
static void shovel_single(struct connection *cnx)
{
fd_set fds_r, fds_w;
int res, i;
@@ -223,7 +212,7 @@ void shovel_single(struct connection *cnx)
if (FD_ISSET(cnx->q[i].fd, &fds_w)) {
res = flush_deferred(&cnx->q[i]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "%s socket closed\n", i ? "server" : "client");
return;
}
@@ -231,7 +220,7 @@ void shovel_single(struct connection *cnx)
if (FD_ISSET(cnx->q[i].fd, &fds_r)) {
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
if (!res) {
if (verbose)
if (cfg.verbose)
fprintf(stderr, "socket closed\n");
return;
}
@@ -242,7 +231,7 @@ void shovel_single(struct connection *cnx)
/* Child process that makes internal connection and proxies
*/
void connect_proxy(struct connection *cnx)
static void connect_proxy(struct connection *cnx)
{
int in_socket;
int out_socket;
@@ -257,29 +246,260 @@ void connect_proxy(struct connection *cnx)
}
/* Connect the target socket */
out_socket = connect_addr(cnx, in_socket);
out_socket = connect_addr(cnx, in_socket, BLOCKING);
CHECK_RES_DIE(out_socket, "connect");
cnx->q[1].fd = out_socket;
log_connection(cnx);
log_connection(NULL, cnx);
shovel_single(cnx);
close(in_socket);
close(out_socket);
if (verbose)
if (cfg.verbose)
fprintf(stderr, "connection closed down\n");
exit(0);
}
/* returns true if specified fd is initialised and present in fd_set */
int is_fd_active(int fd, fd_set* set)
/* Removes cnx from probing list */
static void remove_probing_cnx(struct select_info* fd_info, struct connection* cnx)
{
if (fd == -1) return 0;
return FD_ISSET(fd, set);
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
fd_info->num_probing--;
}
static void add_probing_cnx(struct select_info* fd_info, struct connection* cnx)
{
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
fd_info->num_probing++;
}
/* Process read activity on a socket in probe state
* IN/OUT cnx: connection data, updated if connected
* IN/OUT info: updated if connected
* */
static void probing_read_process(struct connection* cnx,
struct select_info* fd_info)
{
int res;
/* If timed out it's SSH, otherwise the client sent
* data so probe the protocol */
if ((cnx->probe_timeout < time(NULL))) {
cnx->proto = timeout_protocol();
if (cfg.verbose)
log_message(LOG_INFO,
"timed out, connect to %s\n",
cnx->proto->name);
} else {
res = probe_client_protocol(cnx);
if (res == PROBE_AGAIN)
return;
}
remove_probing_cnx(fd_info, cnx);
cnx->state = ST_SHOVELING;
/* libwrap check if required for this protocol */
if (cnx->proto->service &&
check_access_rights(cnx->q[0].fd, cnx->proto->service)) {
tidy_connection(cnx, fd_info);
res = -1;
} else if (cnx->proto->fork) {
switch (fork()) {
case 0: /* child */
/* TODO: close all file descriptors except 2 */
/* free(cnx); */
connect_proxy(cnx);
exit(0);
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
default: /* parent */
break;
}
tidy_connection(cnx, fd_info);
res = -1;
} else {
res = connect_queue(cnx, fd_info);
}
if (res >= fd_info->max_fd)
fd_info->max_fd = res + 1;;
}
/* Returns the queue index that contains the specified file descriptor */
int active_queue(struct connection* cnx, int fd)
{
if (cnx->q[0].fd == fd) return 0;
if (cnx->q[1].fd == fd) return 1;
log_message(LOG_ERR, "file descriptor %d not found in connection object\n", fd);
return -1;
}
/* Process a connection that is active in read */
static void tcp_read_process(struct select_info* fd_info,
int fd)
{
if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd);
cnx_collection* collection = fd_info->collection;
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
/* Determine active queue (0 or 1): if fd is that of q[1], active_q = 1,
* otherwise it's 0 */
int active_q = active_queue(cnx, fd);
switch (cnx->state) {
case ST_PROBING:
if (active_q == 1) {
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
dump_connection(cnx);
exit(1);
}
probing_read_process(cnx, fd_info);
break;
case ST_SHOVELING:
shovel(cnx, active_q, fd_info);
break;
default: /* illegal */
log_message(LOG_ERR, "Illegal connection state %d\n", cnx->state);
dump_connection(cnx);
exit(1);
}
}
static void cnx_read_process(struct select_info* fd_info, int fd)
{
cnx_collection* collection = fd_info->collection;
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
switch (cnx->type) {
case SOCK_STREAM:
tcp_read_process(fd_info, fd);
break;
case SOCK_DGRAM:
udp_s2c_forward(cnx);
break;
default:
log_message(LOG_ERR, "cnx_read_process: Illegal connection type %d\n", cnx->type);
dump_connection(cnx);
exit(1);
}
}
/* Process a connection that is active in write */
static void cnx_write_process(struct select_info* fd_info, int fd)
{
if (debug) fprintf(stderr, "cnx_write_process fd %d\n", fd);
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd);
int res;
int queue = active_queue(cnx, fd);
res = flush_deferred(&cnx->q[queue]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (cnx->state == ST_PROBING) remove_probing_cnx(fd_info, cnx);
tidy_connection(cnx, fd_info);
} else {
/* If no deferred data is left, stop monitoring the fd
* for write, and restart monitoring the other one for reads*/
if (!cnx->q[queue].deferred_data_size) {
FD_CLR(cnx->q[queue].fd, &fd_info->fds_w);
FD_SET(cnx->q[1-queue].fd, &fd_info->fds_r);
}
}
}
/* Process a connection that accepts a socket
* (For UDP, this means all traffic coming from remote clients)
* */
void cnx_accept_process(struct select_info* fd_info, struct listen_endpoint* listen_socket)
{
int fd = listen_socket->socketfd;
int type = listen_socket->type;
struct connection* cnx;
int new_fd;
if (debug) fprintf(stderr, "cnx_accept_process fd %d\n", fd);
switch (type) {
case SOCK_STREAM:
cnx = accept_new_connection(fd, fd_info->collection);
if (cnx) {
add_probing_cnx(fd_info, cnx);
new_fd = cnx->q[0].fd;
}
break;
case SOCK_DGRAM:
new_fd = udp_c2s_forward(fd, fd_info->collection, fd_info->max_fd);
fprintf(stderr, "new_fd %d\n", new_fd);
if (new_fd == -1)
return;
break;
default:
log_message(LOG_ERR, "Inconsistent cnx type: %d\n", type);
exit(1);
return;
}
FD_SET(new_fd, &fd_info->fds_r);
if (new_fd >= fd_info->max_fd)
fd_info->max_fd = new_fd + 1;
}
/* Check all connections to see if a UDP connections has timed out, then free
* it. At the same time, keep track of the closest, next timeout. Only do the
* search through connections if that timeout actually happened. If the
* connection that would have timed out has had activity, it doesn't matter: we
* go through connections to find the next timeout, which was needed anyway. */
static void udp_timeouts(struct select_info* fd_info)
{
time_t now = time(NULL);
if (now < fd_info->next_timeout) return;
for (int i = 0; i < fd_info->max_fd; i++) {
time_t next_timeout = INT_MAX;
/* if it's either in read or write set, there is a connection
* behind that file descriptor */
if (FD_ISSET(i, &fd_info->fds_r) || FD_ISSET(i, &fd_info->fds_w)) {
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, i);
if (cnx) {
time_t timeout = udp_timeout(cnx);
if (cnx && (timeout <= now)) {
if (cfg.verbose > 3)
fprintf(stderr, "timed out UDP %d\n", cnx->target_sock);
close(cnx->target_sock);
FD_CLR(i, &fd_info->fds_r);
FD_CLR(i, &fd_info->fds_w);
collection_remove_cnx(fd_info->collection, cnx);
} else {
if (timeout < next_timeout) next_timeout = timeout;
}
}
}
if (next_timeout != INT_MAX)
fd_info->next_timeout = next_timeout;
}
}
/* Main loop: the idea is as follow:
@@ -287,7 +507,7 @@ int is_fd_active(int fd, fd_set* set)
* - When a file descriptor goes off, process it: read from it, write the data
* to its corresponding pair.
* - When a file descriptor blocks when writing, remove the read fd from fds_r,
* move the data to a deferred buffer, and add the write fd to fds_w. Defered
* move the data to a deferred buffer, and add the write fd to fds_w. Deferred
* buffer is allocated dynamically.
* - When we can write to a file descriptor that has deferred data, we try to
* write as much as we can. Once all data is written, remove the fd from fds_w
@@ -296,164 +516,86 @@ int is_fd_active(int fd, fd_set* set)
* That way, each pair of file descriptor (read from one, write to the other)
* is monitored either for read or for write, but never for both.
*/
void main_loop(int listen_sockets[], int num_addr_listen)
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
struct select_info fd_info = {0};
fd_set readfds, writefds; /* working read and write fd sets */
struct timeval tv;
int max_fd, in_socket, i, j, res;
struct connection *cnx;
int num_cnx; /* Number of connections in *cnx */
int num_probing = 0; /* Number of connections currently probing
* We use this to know if we need to time out of
* select() */
int i, res;
FD_ZERO(&fds_r);
FD_ZERO(&fds_w);
fd_info.num_probing = 0;
FD_ZERO(&fd_info.fds_r);
FD_ZERO(&fd_info.fds_w);
fd_info.probing_list = gap_init();
for (i = 0; i < num_addr_listen; i++) {
FD_SET(listen_sockets[i], &fds_r);
set_nonblock(listen_sockets[i]);
FD_SET(listen_sockets[i].socketfd, &fd_info.fds_r);
set_nonblock(listen_sockets[i].socketfd);
}
max_fd = listen_sockets[num_addr_listen-1] + 1;
fd_info.max_fd = listen_sockets[num_addr_listen-1].socketfd + 1;
cnx_num_alloc = getpagesize() / sizeof(struct connection);
num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
cnx = malloc(num_cnx * sizeof(struct connection));
for (i = 0; i < num_cnx; i++)
init_cnx(&cnx[i]);
fd_info.collection = collection_init();
while (1)
{
memset(&tv, 0, sizeof(tv));
tv.tv_sec = probing_timeout;
tv.tv_sec = cfg.timeout;
memcpy(&readfds, &fds_r, sizeof(readfds));
memcpy(&writefds, &fds_w, sizeof(writefds));
memcpy(&readfds, &fd_info.fds_r, sizeof(readfds));
memcpy(&writefds, &fd_info.fds_w, sizeof(writefds));
if (verbose)
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing);
res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL);
if (cfg.verbose)
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n",
fd_info.max_fd, fd_info.num_probing);
res = select(fd_info.max_fd, &readfds, &writefds,
NULL, fd_info.num_probing ? &tv : NULL);
if (res < 0)
perror("select");
/* UDP timeouts: clear out connections after some idle time */
udp_timeouts(&fd_info);
/* Check main socket for new connections */
for (i = 0; i < num_addr_listen; i++) {
if (FD_ISSET(listen_sockets[i], &readfds)) {
in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx);
if (in_socket > 0) {
num_probing++;
FD_SET(in_socket, &fds_r);
if (in_socket >= max_fd)
max_fd = in_socket + 1;;
}
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
cnx_accept_process(&fd_info, &listen_sockets[i]);
/* don't also process it as a read socket */
FD_CLR(listen_sockets[i].socketfd, &readfds);
}
}
/* Check all sockets for write activity */
for (i = 0; i < num_cnx; i++) {
if (cnx[i].q[0].fd != -1) {
for (j = 0; j < 2; j++) {
if (is_fd_active(cnx[i].q[j].fd, &writefds)) {
res = flush_deferred(&cnx[i].q[j]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (cnx[i].state == ST_PROBING) num_probing--;
tidy_connection(&cnx[i], &fds_r, &fds_w);
if (verbose)
fprintf(stderr, "closed slot %d\n", i);
} else {
/* If no deferred data is left, stop monitoring the fd
* for write, and restart monitoring the other one for reads*/
if (!cnx[i].q[j].deferred_data_size) {
FD_CLR(cnx[i].q[j].fd, &fds_w);
FD_SET(cnx[i].q[1-j].fd, &fds_r);
}
}
}
}
for (i = 0; i < fd_info.max_fd; i++) {
if (FD_ISSET(i, &writefds)) {
cnx_write_process(&fd_info, i);
}
}
/* Check sockets in probing state for timeouts */
for (i = 0; i < fd_info.num_probing; i++) {
struct connection* cnx = gap_get(fd_info.probing_list, i);
if (!cnx || cnx->state != ST_PROBING) {
log_message(LOG_ERR, "Inconsistent probing: cnx=%0xp\n", cnx);
if (cnx)
log_message(LOG_ERR, "Inconsistent probing: state=%d\n", cnx);
exit(1);
}
if (cnx->probe_timeout < time(NULL)) {
if (cfg.verbose)
fprintf(stderr, "timeout slot %d\n", i);
probing_read_process(cnx, &fd_info);
}
}
/* Check all sockets for read activity */
for (i = 0; i < num_cnx; i++) {
for (j = 0; j < 2; j++) {
if (is_fd_active(cnx[i].q[j].fd, &readfds) ||
((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) {
if (verbose)
fprintf(stderr, "processing fd%d slot %d\n", j, i);
switch (cnx[i].state) {
case ST_PROBING:
if (j == 1) {
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
dump_connection(&cnx[i]);
exit(1);
}
/* If timed out it's SSH, otherwise the client sent
* data so probe the protocol */
if ((cnx[i].probe_timeout < time(NULL))) {
cnx[i].proto = timeout_protocol();
if (verbose)
log_message(LOG_INFO,
"timed out, connect to %s\n",
cnx[i].proto->description);
} else {
res = probe_client_protocol(&cnx[i]);
if (res == PROBE_AGAIN)
continue;
}
num_probing--;
cnx[i].state = ST_SHOVELING;
/* libwrap check if required for this protocol */
if (cnx[i].proto->service &&
check_access_rights(in_socket, cnx[i].proto->service)) {
tidy_connection(&cnx[i], &fds_r, &fds_w);
res = -1;
} else if (cnx[i].proto->fork) {
struct connection *pcnx_i = &cnx[i];
struct connection cnx_i = *pcnx_i;
switch (fork()) {
case 0: /* child */
for (i = 0; i < num_addr_listen; i++)
close(listen_sockets[i]);
for (i = 0; i < num_cnx; i++)
if (&cnx[i] != pcnx_i)
for (j = 0; j < 2; j++)
if (cnx[i].q[j].fd != -1)
close(cnx[i].q[j].fd);
free(cnx);
connect_proxy(&cnx_i);
exit(0);
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
default: /* parent */
break;
}
tidy_connection(&cnx[i], &fds_r, &fds_w);
res = -1;
} else {
res = connect_queue(&cnx[i], &fds_r, &fds_w);
}
if (res >= max_fd)
max_fd = res + 1;;
break;
case ST_SHOVELING:
shovel(&cnx[i], j, &fds_r, &fds_w);
break;
default: /* illegal */
log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state);
exit(1);
}
}
for (i = 0; i < fd_info.max_fd; i++) {
/* Check if it's active AND currently monitored (if a connection
* died, it gets tidied, which closes both sockets, but readfs does
* not know about that */
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.fds_r)) {
cnx_read_process(&fd_info, i);
}
}
}
+7 -8
View File
@@ -6,7 +6,7 @@
=head1 SYNOPSIS
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--ssl> I<target address for SSL>] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
=head1 DESCRIPTION
@@ -84,9 +84,6 @@ Uses I<filename> as configuration file. If other
command-line options are specified, they will override the
configuration file's settings.
When using the shorthand version, make sure there should be
no space between B<-F> and the I<filename>.
=item B<-t> I<num>, B<--timeout> I<num>
Timeout before forwarding the connection to the timeout
@@ -95,7 +92,11 @@ protocol (which should usually be SSH). Default is 2s.
=item B<--on-timeout> I<protocol name>
Name of the protocol to connect to after the timeout period
is over. Default is 'ssh'.
is over. Default is to forward to the first specified
protocol. It usually makes sense to specify 'ssh' as the
timeout protocol, as the SSH specification does not tell
who is supposed to speak first and a large number of SSH
clients wait for the server to send its banner.
=item B<--transparent>
@@ -113,8 +114,6 @@ IP address on which the Internet connection ends up).
This can be specified several times to bind B<sslh> to
several addresses.
=item B<--ssl> I<target address>
=item B<--tls> I<target address>
Interface and port on which to forward SSL connection,
@@ -127,7 +126,7 @@ inside your network to just connect directly to B<httpd>.
Also, B<sslh> probes for SSLv3 (or TLSv1) handshake and will
reject connections from clients requesting SSLv2. This is
compliant with RFC6176 which prohibits the usage of SSLv2. If
you wish to accept SSLv2, use B<--default> instead.
you wish to accept SSLv2, use B<--anyprot> instead.
=item B<--ssh> I<target address>
+245
View File
@@ -0,0 +1,245 @@
# This is a configuration file for the configuration
# generator: it is used by
# conf2struct(http://www.rutschle.net/tech/conf2struct/README.html)
# to generate the config file and command line parser in
# sslh-conf.c
header: "sslh-conf.h";
parser: "sslh-conf.c";
printer: true;
conffile_option: ("F", "config");
# List of includes to define runtime types
# (bug in libconfig? if swallows the brackets if they start
# the string)
includes: (
"probe.h",
" <sys/types.h>",
" <sys/socket.h>",
" <netdb.h>"
);
config: {
name : "sslhcfg",
type: "list",
items: (
{ name: "verbose"; type: "int"; default: 0; short: "v"; },
{ name: "foreground"; type: "bool"; default: false;
short: "f";
description: "Run in foreground instead of as a daemon"; },
{ name: "inetd"; type: "bool"; default: false;
short: "i";
description: "Run in inetd mode: use stdin/stdout instead of network listen"; },
{ name: "numeric"; type: "bool"; default: false;
short: "n";
description: "Print IP addresses and ports as numbers"; },
{ name: "transparent"; type: "bool"; default: false;
description: "Set up as a transparent proxy"; },
{ name: "timeout"; type: "int"; default: 5;
short: "t";
description: "Set up timeout before connecting to default target"; },
{ name: "user"; type: "string"; optional: true;
short: "u";
description: "Username to change to after set-up"; },
{ name: "pidfile"; type: "string"; optional: true;
short: "P"; argdesc: "<file>";
description: "Path to file to store PID of current instance"; },
{ name: "chroot"; type: "string"; optional: true;
short: "C"; argdesc: "<path>";
description: "Root to change to after set-up"; },
{ name: "syslog_facility"; type: "string"; default: "auth";
description: "Facility to syslog to"; },
{ name: "on-timeout"; type: "string"; default: "ssh";
description: "Target to connect to when timing out"; },
{ name: "prefix"; type: "string"; default: ""; description: "Reserved for testing" }, # For echosrv only, not sslh
{ name: "listen",
no_cl_accessors: true; # disable generation of individual cl options for each group element (we create a specific --listen option further below)
type: "list",
items: (
{ name: "host"; type: "string"; var: true; },
{ name: "port"; type: "string"; var: true; },
{ name: "is_udp"; type: "bool"; default: false },
{ name: "keepalive"; type: "bool"; default: false; }
)
},
{ name: "protocols",
no_cl_accessors: true;
type: "list",
items: (
{ name: "name"; type: "string"; },
{ name: "host"; type: "string"; var: true; },
{ name: "port"; type: "string"; var: true; },
{ name: "service"; type: "string"; optional: true; },
{ name: "is_udp"; type: "bool"; default: false },
{ name: "udp_timeout"; type: "int"; default: 60 },
{ name: "fork"; type: "bool"; default: false },
{ name: "tfo_ok"; type: "bool"; default: false;
description: "Set to true if this protocol supports TCP FAST OPEN" },
{ name: "transparent"; type: "bool"; default: false;
description: "Set to proxy this protocol transparently" },
{ name: "log_level"; type: "int"; default: 1 },
{ name: "keepalive"; type: "bool"; default: false },
{ name: "sni_hostnames",
type: "array",
element_type: "string"
},
{ name: "alpn_protocols",
type: "array",
element_type: "string"
},
{ name: "regex_patterns",
type: "array",
element_type: "string"
},
{ name: "minlength"; type: "int"; optional: true },
# Runtime data
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
{ name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" },
{ name: "data"; type: "runtime"; c_type: "void*" }
)
}
)
}
# Command line for list settings: additional options that
# can set up several settings at once. Each option will
# create a new group setting entry if required (with
# defaults set up)
# This only works with string targets
# This may not be the right abstraction at all and way too
# sslh-centric
cl_groups: (
{ name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port";
short: "p"; argdesc: "<host:port>";
list: "listen";
# no override, this just adds to the list (and thus can be specified several times)
targets: (
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" }
);
},
{ name: "ssh"; pattern: "(.+):(\w+)"; description: "Set up ssh target";
list: "protocols"; # List name that we're defining with this command line option
override: "name"; # Field in the group to override. If not found in list, add an item
# (it's mandatory to have that field as one of the targets
# below)
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "ssh" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "fork"; value: 1 },
{ path: "log_level"; value: 1 },
{ path: "tfo_ok"; value: 1 }
);
},
{ name: "tls"; 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";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "openvpn" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 },
{ path: "tfo_ok"; value: 1 }
);
},
{ name: "tinc"; pattern: "(.+):(\w+)"; description: "Set up tinc target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "tinc" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 },
{ path: "tfo_ok"; value: 1 }
);
},
{ name: "xmpp"; pattern: "(.+):(\w+)"; description: "Set up XMPP target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "xmpp" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
},
{ name: "http"; pattern: "(.+):(\w+)"; description: "Set up HTTP (plain) target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "http" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
},
{ name: "adb"; pattern: "(.+):(\w+)"; description: "Set up ADB (Android Debug) target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "adb" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
},
{ name: "socks5"; pattern: "(.+):(\w+)"; description: "Set up socks5 target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "socks5" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
},
{ name: "syslog"; pattern: "(.+):(\w+)"; description: "Set up syslog target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "syslog" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
},
{ name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target";
list: "protocols";
override: "name";
argdesc: "<host:port>";
targets: (
{ path: "name"; value: "anyprot" },
{ path: "host"; value: "$1" },
{ path: "port"; value: "$2" },
{ path: "log_level"; value: 1 }
);
}
)
+6 -1
View File
@@ -2,12 +2,14 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "common.h"
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 = (char*)malloc(strlen(hostname)+strlen(port)+2);
char *conn = malloc(strlen(hostname)+strlen(port)+2);
CHECK_ALLOC(conn, "malloc");
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
@@ -41,6 +43,7 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
int i;
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
CHECK_ALLOC(*listen, "malloc");
for (i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
@@ -51,6 +54,7 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
return -1;
} else {
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
CHECK_ALLOC((*listen)[i], "malloc");
strcpy((*listen)[i], resolve_listen(hostname, port));
}
}
@@ -115,6 +119,7 @@ static int gen_sslh_config(char *runtime_unit_dir) {
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");
+284 -140
View File
@@ -2,86 +2,233 @@
# Test script for sslh
# Uses Conf::Libconfig to read sslh config file: install
# with:
# cpan Conf::Libconfig
use strict;
use IO::Socket::INET6;
use Test::More qw/no_plan/;
use Conf::Libconfig;
# We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything...
my $ssh_address = "ip6-localhost:9000";
my $ssl_address = "ip6-localhost:9001";
my $sslh_port = 9002;
my $no_listen = 9003; # Port on which no-one listens
my $pidfile = "/tmp/sslh_test.pid";
my $conf = new Conf::Libconfig;
$conf->read_file("test.cfg");
my $no_listen = 8083; # Port on which no-one listens
my $pidfile = $conf->lookup_value("pidfile");
my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
my $user = (getpwuid $<)[0]; # Run under current username
# Which tests do we run
my $SSL_CNX = 1;
my $SSH_SHY_CNX = 1;
my $SSH_BOLD_CNX = 1;
my $SSH_PROBE_AGAIN = 1;
my $SSH_SHY_CNX = 0;
my $PROBES_NOFRAG = 1;
my $PROBES_AGAIN = 1;
my $SSL_MIX_SSH = 1;
my $SSH_MIX_SSL = 1;
my $BIG_MSG = 0; # This test is unreliable
my $STALL_CNX = 0; # This test needs fixing
# Robustness tests. These are mostly to achieve full test
# coverage, but do not necessarily result in an actual test
# (e.g. some tests need to be run with valgrind to check all
# memory management code).
my $RB_CNX_NOSERVER = 1;
my $RB_PARAM_NOHOST = 1;
my $RB_WRONG_USERNAME = 1;
my $RB_OPEN_PID_FILE = 1;
my $RB_BIND_ADDRESS = 1;
my $RB_RESOLVE_ADDRESS = 1;
my $RB_CNX_NOSERVER = 0;
my $RB_PARAM_NOHOST = 0;
my $RB_WRONG_USERNAME = 0;
my $RB_OPEN_PID_FILE = 0;
my $RB_RESOLVE_ADDRESS = 0;
my $RB_CL_PARAMS = 0;
`lcov --directory . --zerocounters`;
sub verbose_exec
{
my ($cmd) = @_;
my ($ssh_pid, $ssl_pid);
if (!($ssh_pid = fork)) {
exec "./echosrv --listen $ssh_address --prefix 'ssh: '";
warn "$cmd\n";
if (!fork) {
exec $cmd;
}
}
if (!($ssl_pid = fork)) {
exec "./echosrv --listen $ssl_address --prefix 'ssl: '";
# We want to keep track of tests to print a report at the
# end, so we centralise all calls to Test::More::is here
my $cnt = 1; # test counter
my @results;
sub my_is {
my ($a, $b, $desc) = @_;
my $res = is($a, $b, $desc);
push @results, [$cnt++, $desc, $res];
}
my @binaries = ('sslh-select', 'sslh-fork');
# For SNI/ALPN, build a protocol name as such:
# tls:sni1,sni2,...;alpn1,alpn2,...
# input: a protocol entry from Libconfig
sub make_sni_alpn_name {
my ($prot) = @_;
return "tls:" . (join ",", @{$prot->{sni_hostnames} // []})
. ";" . (join ",", @{$prot->{alpn_protocols} // [] });
}
# Tests one probe: given input data, connect, verify we get
# the expected server, verify shoveling works
# Named options:
# data: what to write
# expected: expected protocol prefix
# no_frag: don't print byte-per-byte
sub test_probe {
my (%opts) = @_;
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx;
return unless $cnx;
my $pattern = $opts{data};
if ($opts{no_frag}) {
syswrite $cnx, $pattern;
} else {
while (length $pattern) {
syswrite $cnx, (substr $pattern, 0, 1, '');
select undef, undef, undef, .01;
}
}
my $data;
my $n = sysread $cnx, $data, 1024;
$data =~ /^(.*?): /;
my $prefix = $1;
$data =~ s/$prefix: //g;
print "Received $n bytes: protocol $prefix data [$data]\n";
close $cnx;
$opts{expected} =~ s/^ssl/tls/; # to remove in 1.21
my_is($prefix, $opts{expected}, "$opts{binary}:$opts{expected}: probe connected correctly");
my_is($data, $opts{data}, "$opts{binary}:$opts{expected}: data shoveled correctly");
}
# Test all probes, with or without fragmentation
# options:
# no_frag: write test patterns all at once (also
# available per-protocol as some probes don't support
# fragmentation)
sub test_probes {
my (%in_opts) = @_;
my @probes = @{$conf->fetch_array("protocols")};
foreach my $p (@probes) {
my %protocols = (
'ssh' => { data => "SSH-2.0 tester" },
'socks5' => { data => "\x05\x04\x01\x02\x03\x04" },
'http' => {
data => "GET index.html HTTP/1.1",
no_frag => 1 },
'tls' => {
# Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`)
data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn",
# Packet with SNI alone
data_sni => "\x16\x03\x01\x00\xb8\x01\x00\x00\xb4\x03\x03\x97\xe4\xe9\xad\x86\xe1\x21\xfd\xc4\x5b\x27\x0e\xad\x4b\x55\xc2\x50\xe4\x1c\x86\x2f\x37\x25\xde\xe8\x9c\x59\xfc\x1b\xa9\x37\x32\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x53\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello sni",
# packet with ALPN alone
data_alpn => "\x16\x03\x01\x00\xb7\x01\x00\x00\xb3\x03\x03\xe2\x90\xa2\x29\x03\x31\xad\x98\x44\x51\x54\x90\x5b\xd9\x51\x0e\x66\xb5\x3f\xe8\x8b\x09\xc9\xe4\x2b\x97\x24\xef\xad\x56\x06\xc9\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x52\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello alpn",
# packet with no SNI, no ALPN
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone"
},
'openvpn' => { data => "\x00\x00" },
'syslog' => { data => "<42> My syslog message" },
'tinc' => { data => "0 hello" },
'xmpp' => {data => "I should get a real jabber connection initialisation here" },
'adb' => { data => "CNXN....................host:..." },
'anyprot' => {data => "hello anyprot this needs to be longer than xmpp and adb which expect about 50 characters, which I all have to write before the timeout!" },
);
my $pattern = $protocols{$p->{name}}->{data};
my %opts = %in_opts;
$opts{no_frag} = 1 if $protocols{$p->{name}}->{no_frag};
if ($p->{sni_hostnames} or $p->{alpn_protocols}) {
my $pname = make_sni_alpn_name($p);
my @sni = @{$p->{sni_hostnames} // [""] };
my @alpn = @{$p->{alpn_protocols} // [""] };
foreach my $sni ( @sni ) {
foreach my $alpn ( @alpn ) {
print "sni: $sni\nalpn: $alpn\n";
$pattern = $protocols{tls}->{
"data". ($sni ? "_sni" : "") .
($alpn ? "_alpn": "")
};
$pattern =~ s/(\$\w+)/$1/eeg;
test_probe(
data => $pattern,
expected => $pname,
%opts
);
}
}
} elsif ($p->{name} eq 'regex') {
foreach my $test (@{$p->{test_patterns}}) {
test_probe(
data => $test->{pattern},
expected => $test->{result},
%opts
);
}
} else {
test_probe(
data => $pattern,
expected => $p->{name},
%opts
);
}
}
}
# Start an echoserver for each service
foreach my $s (@{$conf->fetch_array("protocols")}) {
my $prefix = $s->{name};
$prefix =~ s/^ssl/tls/; # To remove in 1.21
if ($s->{sni_hostnames} or $s->{alpn_protocols}) {
$prefix = make_sni_alpn_name($s);
}
verbose_exec "./echosrv --listen $s->{host}:$s->{port} --prefix '$prefix: '";
}
#my @binaries = ('sslh-select', 'sslh-fork');
my @binaries = ('sslh-select');
for my $binary (@binaries) {
warn "Testing $binary\n";
# Start sslh with the right plumbing
my $sslh_pid;
my ($sslh_pid, $valgrind);
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $cmd = "./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
warn "$cmd\n";
#exec $cmd;
exec "valgrind --leak-check=full ./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
my $cmd = "./$binary -v 4 -f -u $user -F test.cfg";
#$valgrind = 1;
#$cmd = "valgrind --leak-check=full $cmd";
verbose_exec $cmd;
exit 0;
}
warn "spawned $sslh_pid\n";
sleep 5; # valgrind can be heavy -- wait 5 seconds
sleep 1; # Give everyone some time to start
sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds
my $test_data = "hello world\n";
# my $ssl_test_data = (pack 'n', ((length $test_data) + 2)) . $test_data;
my $ssl_test_data = "\x16\x03\x03$test_data\n";
# Test: SSL connection
if ($SSL_CNX) {
print "***Test: SSL connection\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection");
}
}
my $ssl_test_data = "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone";
# Test: Shy SSH connection
if ($SSH_SHY_CNX) {
@@ -89,42 +236,13 @@ for my $binary (@binaries) {
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
sleep 3;
sleep 13;
print $cnx_h $test_data;
my $data = <$cnx_h>;
is($data, "ssh: $test_data", "Shy SSH connection");
my_is($data, "ssh: $test_data", "$binary: Shy SSH connection");
}
}
# Test: Bold SSH connection
if ($SSH_BOLD_CNX) {
print "***Test: Bold SSH connection\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
my $td = "SSH-2.0 testsuite\t$test_data";
print $cnx_h $td;
my $data = <$cnx_h>;
is($data, "ssh: $td", "Bold SSH connection");
}
}
# Test: PROBE_AGAIN, incomplete first frame
if ($SSH_PROBE_AGAIN) {
print "***Test: incomplete SSH first frame\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
my $td = "SSH-2.0 testsuite\t$test_data";
print $cnx_h substr $td, 0, 2;
sleep 1;
print $cnx_h substr $td, 2;
my $data = <$cnx_h>;
is($data, "ssh: $td", "Incomplete first SSH frame");
}
}
# Test: One SSL half-started then one SSH
if ($SSL_MIX_SSH) {
print "***Test: One SSL half-started then one SSH\n";
@@ -138,11 +256,11 @@ for my $binary (@binaries) {
sleep 3;
print $cnx_h $test_data;
my $data_h = <$cnx_h>;
is($data_h, "ssh: $test_data", "SSH during SSL being established");
my_is($data_h, "ssh: $test_data", "$binary: SSH during SSL being established");
}
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection interrupted by SSH");
my_is($data, "tls: $ssl_test_data", "$binary: SSL connection interrupted by SSH");
}
}
@@ -159,62 +277,21 @@ for my $binary (@binaries) {
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL during SSH being established");
my_is($data, "tls: $ssl_test_data", "$binary: SSL during SSH being established");
}
print $cnx_h $test_data;
my $data = <$cnx_h>;
is($data, "ssh: $test_data", "SSH connection interrupted by SSL");
my_is($data, "ssh: $test_data", "$binary: SSH connection interrupted by SSL");
}
}
# Test: Big messages (careful: don't go over echosrv's buffer limit (1M))
if ($BIG_MSG) {
print "***Test: big message\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
my $rept = 1000;
my $test_data2 = $ssl_test_data . ("helloworld"x$rept);
if (defined $cnx_l) {
my $n = syswrite $cnx_l, $test_data2;
my ($data);
$n = sysread $cnx_l, $data, 1 << 20;
is($data, "ssl: ". $test_data2, "Big message");
}
if ($PROBES_NOFRAG) {
test_probes(no_frag => 1, binary => $binary);
}
# Test: Stalled connection
# Create two connections, stall one, check the other one
# works, unstall first and check it works fine
# This test needs fixing.
# Now that echosrv no longer works on "lines" (finishing
# with '\n'), it may cut blocks randomly with prefixes.
# The whole thing needs to be re-thought as it'll only
# work by chance.
if ($STALL_CNX) {
print "***Test: Stalled connection\n";
my $cnx_1 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_1;
my $cnx_2 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_2;
my $test_data2 = "helloworld";
sleep 4;
my $rept = 1000;
if (defined $cnx_1 and defined $cnx_2) {
print $cnx_1 ($test_data2 x $rept);
print $cnx_1 "\n";
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
my $data = <$cnx_2>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (1)");
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
$data = <$cnx_2>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
$data = <$cnx_1>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
}
if ($PROBES_AGAIN) {
test_probes(binary => $binary);
}
my $pid = `cat $pidfile`;
@@ -228,8 +305,7 @@ if ($RB_CNX_NOSERVER) {
print "***Test: Connecting to non-existant server\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --ssl localhost:$no_listen -P $pidfile";
exec "./sslh-select -v 3 -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --tls localhost:$no_listen -P $pidfile";
}
warn "spawned $sslh_pid\n";
@@ -249,19 +325,26 @@ if ($RB_CNX_NOSERVER) {
}
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0];
my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port};
# Use the last TLS echoserv (no SNI/ALPN)
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1];
my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port};
# Robustness: No hostname in address
if ($RB_PARAM_NOHOST) {
print "***Test: No hostname in address\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen $sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
exec "./sslh-select -v 3 -f -u $user --listen $sslh_port --ssh $ssh_address --tls $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 1, "Exit status on illegal option");
my_is($code, 6, "Exit status on illegal option");
}
# Robustness: User does not exist
@@ -269,14 +352,13 @@ if ($RB_WRONG_USERNAME) {
print "***Test: Changing to non-existant username\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u ${user}_doesnt_exist --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
exec "./sslh-select -v 3 -f -u ${user}_doesnt_exist --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 2, "Exit status on non-existant username");
my_is($code, 2, "Exit status on non-existant username");
}
# Robustness: Can't open PID file
@@ -284,15 +366,14 @@ if ($RB_OPEN_PID_FILE) {
print "***Test: Can't open PID file\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P /dont_exist/$pidfile";
exec "./sslh-select -v 3 -f -u $user --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address -P /dont_exist/$pidfile";
# You don't have a /dont_exist/ directory, do you?!
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 3, "Exit status if can't open PID file");
my_is($code, 3, "Exit status if can't open PID file");
}
# Robustness: Can't resolve address
@@ -301,13 +382,62 @@ if ($RB_RESOLVE_ADDRESS) {
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
exec "./sslh-select -v 3 -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 4, "Exit status if can't resolve address");
my_is($code, 4, "Exit status if can't resolve address");
}
# Robustness: verify all command line options work
if ($RB_CL_PARAMS) {
print "***Test: Command line parameters\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
# This doesn't test --inetd
exec "./sslh-select -v 3 -f -u $user -P $pidfile".
" -n --transparent --timeout 10 -C /tmp".
" --syslog-facility auth --on-timeout ssh".
" --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address".
" --openvpn localhost:$no_listen".
" --tinc localhost:$no_listen".
" --xmpp localhost:$no_listen".
" --http localhost:$no_listen".
" --adb localhost:$no_listen".
" --socks5 localhost:$no_listen".
" --anyprot localhost:$no_listen";
exit 0;
}
warn "spawned $sslh_pid\n";
# It will die soon because $user cannot chroot (you
# don't test as root, do you?)
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
my_is($code, 1, "Command line arguments");
print "***Test: Bad command line parameters\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
# This doesn't test --inetd
exec "./sslh-select -v 3 -f -u $user -P $pidfile".
" -n --transparent --timeout 10 -C /tmp".
" --fakeoption".
" --anyprot localhost:$no_listen";
exit 0;
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
my_is($code, 6, "Bad command line parameters");
}
`lcov --directory . --capture --output-file sslh_cov.info`;
@@ -315,3 +445,17 @@ if ($RB_RESOLVE_ADDRESS) {
`killall echosrv`;
format test_results_top =
ID | Description | Status
----+-------------------------------------------------------------------+-------
.
format test_results =
@>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @>>
$_->[0], $_->[1], $_->[2] ? "OK" : "NOK"
.
format_name STDOUT "test_results";
format_top_name STDOUT "test_results_top";
map { write; } @results;
+225 -75
View File
@@ -14,114 +14,264 @@
use strict;
use IO::Socket::INET6;
use Data::Dumper;
use Conf::Libconfig;
## BEGIN TEST CONFIG
# Do we test sslh-select or sslh-fork?
my $sslh_binary = "./sslh-select";
# How many clients to we start for each protocol?
my $NUM_CNX = 30;
# How many total clients to we start? Each client will pick
# a new protocol among what's in test.cfg.
my $NUM_CNX = 50;
# Delay between starting new processes when starting up. If
# you start 200 processes in under a second, things go wrong
# and it's not sslh's fault (typically the echosrv won't be
# forking fast enough).
my $start_time_delay = 1;
# If you test 4 protocols, you'll start $NUM_CNX * 4 clients
# (e.g. 40), starting one every $start_time_delay seconds.
my $start_time_delay = .5;
# Max times we repeat the test string: allows to test for
# large messages.
my $block_rpt = 4096;
my $block_rpt = 5;
# Probability to stop a client after a message (e.g. with
# .01 a client will send an average of 100 messages before
# disconnecting).
my $stop_client_probability = .001;
# What protocols we test, and on what ports
# Just comment out protocols you don't want to use.
my %protocols = (
"ssh" => { address => "localhost:9001", client => client("ssh") },
"ssl" => { address => "localhost:9002", client => client("ssl") },
"openvpn" => {address => "localhost:9003", client => client("openvpn") },
"tinc" => {address => "localhost:9004", client => client("tinc") },
);
##END CONFIG
my $conf = new Conf::Libconfig;
$conf->read_file("test.cfg");
# Pick one address for TCP and one for UDP
my @listen = @{$conf->fetch_array("listen")};
my ($sslh_tcp_address, $sslh_udp_address);
foreach my $l (@listen) {
if ($l->{is_udp}) {
$sslh_udp_address //= "$l->{host}:$l->{port}";
} else {
$sslh_tcp_address //= "$l->{host}:$l->{port}";
}
last if defined $sslh_tcp_address and defined $sslh_udp_address;
}
# code snippets to connect to each protocol
my %connect_params = (
regex => {
is_udp => 1,
sleep => 0,
test_data => "foo bar",
resp_len => 12,
},
ssh => {
sleep => 20, # So it times out 50% of connections
test_data => "SSH-2.0 hello",
resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
},
tinc => {
sleep => 0,
test_data => "0 ",
resp_len => 8, # length "tinc: 0 " => 10
},
openvpn => {
sleep => 0,
test_data => "\x00\x00",
resp_len => 11, # length "openvpn: \x0\x0" => 11
},
);
sub connect_service {
my ($cnx, $service) = @_;
my $params = $connect_params{$service};
sleep rand $params->{sleep};
my $test_data = $params->{test_data};
syswrite $cnx, $test_data;
sleep 1;
sysread $cnx, my $r, $params->{resp_len};
my $expected = "$service: $test_data";
return ($r eq $expected);
}
# We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything...
my $sslh_address = "localhost:9000";
my $pidfile = "/tmp/sslh_test.pid";
sub client {
my ($service) = @_;
my ($protocol, $client_id, $fd_out) = @_;
return sub {
while (1) {
my $cnx = new IO::Socket::INET(PeerHost => $sslh_address);
my $test_data = "$service testing " x int(rand($block_rpt)+1) . "\n";
my $service = $protocol->{name};
sleep 5 if $service eq "ssh";
if ($service eq "openvpn") {
syswrite $cnx, "\x00\x0F\x38\n";
my $msg;
sysread $cnx, $msg, 14; # length "openvpn: \x0\xF\x38\n" => 14
}
if ($service eq "tinc") {
syswrite $cnx, "0 \n";
my $msg;
sysread $cnx, $msg, 10; # length "tinc: 0 \n" => 10
}
while (1) {
print $cnx $test_data;
my $r = <$cnx>;
($? = 1, die "$service got [$r]\n") if ($r ne "$service: $test_data");
last if rand(1) < $stop_client_probability;
}
}
exit 0;
}
}
while (1) {
my $r;
#warn "$client_id: connect $sslh_tcp_address\n";
my $cnx = new IO::Socket::INET(PeerHost => $sslh_tcp_address);
die "$@\n" if (!$cnx);
foreach my $p (keys %protocols) {
if (!fork) {
exec "./echosrv --listen $protocols{$p}->{address} --prefix '$p: '";
}
}
my $cnt = 0;
# Start sslh with the right plumbing
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $prots = join " ", map "--$_ $protocols{$_}->{address}", keys %protocols;
my $cmd = "$sslh_binary -f -t 3 -u $user --listen $sslh_address $prots -P $pidfile";
print "$cmd\n";
exec $cmd;
exit 0;
}
warn "spawned $sslh_pid\n";
sleep 2; # valgrind can be heavy -- wait 5 seconds
#warn "$client_id: connecting $service\n";
for (1 .. $NUM_CNX) {
foreach my $p (keys %protocols) {
if (!fork) {
warn "starting $p\n";
&{$protocols{$p}->{client}};
if (not connect_service($cnx, $service)) {
print $fd_out "$client_id\t0\tC\n";
warn "$client_id: connecting failed\n";
exit;
}
# Give a little time so we don't overrun the
# listen(2) backlog.
select undef, undef, undef, $start_time_delay;
#warn "$client_id: shoveling $service\n";
while (1) {
my $test_data = "$service $cnt" x int(rand($block_rpt)+1) . "\n";
print $cnx $test_data;
$r = <$cnx>;
my $expected= "$test_data";
my $r_l = length $r;
my $e_l = length $expected;
$fd_out->autoflush;
my $error = "";
$error = "E" if $r ne $expected;
print $fd_out ("$client_id\t$r_l\t$error\n");
($? = 1, die "$service got [$r] expected [$expected]\n") if ($r ne $expected);
if (rand(1) < $stop_client_probability) {
print $fd_out ("$client_id\t$r_l\tD\n");
last;
}
$cnt++;
}
}
exit 0;
}
# For now, a simple regex client
sub udp_client {
my ($protocol, $client_id, $fd_out) = @_;
warn "UDP client starts\n";
while (1) {
my $cnx = new IO::Socket::INET(Proto => 'udp', PeerHost => $sslh_udp_address);
# my $cnx; socket $cnx, PF_INET, SOCK_DGRAM, 0 or die "socket: $!\n";
die "$@\n" if (!$cnx);
my $cnt = 0;
while (1) {
my $test_data = "foo udp $cnt"x int(rand($block_rpt)+1). "\n";
my $ipaddr = inet_aton("localhost");
my $portaddr = sockaddr_in(8086, $ipaddr);
my $res = send($cnx, $test_data, 0, $portaddr);
if ($res != length($test_data)) {
die "cannot sendto: $!";
}
my $expected= "$protocol->{name}: $test_data";
my $r;
defined(recv($cnx, $r, length $expected, 0)) or die "recv: $!\n";
my $r_l = length $r;
my $e_l = length $expected;
$fd_out->autoflush;
my $error = "";
$error = "M" if $r ne $expected;
print $fd_out ("$client_id\t$r_l\t$error\n");
($? = 1, die "udp got [$r] expected [$expected]\n") if ($r ne $expected);
if (rand(1) < $stop_client_probability) {
print $fd_out ("$client_id\t$r_l\tD\n");
last;
}
$cnt++;
}
}
}
foreach my $p (@{$conf->fetch_array("protocols")}) {
if (!fork) {
my $udp = $p->{is_udp} ? "--udp" : "";
my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null";
warn "$cmd\n";
exec $cmd;
exit;
}
}
warn "Don't forget to run sslh -F test.cfg!\n";
sleep 2; # Let echosrv's and sslh start
my ($c_in, $c_out);
pipe $c_in, $c_out;
my @protocols = @{$conf->fetch_array("protocols")};
if (!fork) {
# Process that starts all the clients
for my $client_num (1 .. $NUM_CNX) {
if (!fork) {
my @supported_protocols = keys %connect_params;
my $p_name = $supported_protocols[rand @supported_protocols];
my @p = grep { $_->{name} eq $p_name } @protocols;
my $p = shift @p;
if ($p->{is_udp}) {
udp_client($p, "$p->{name}$client_num", $c_out);
} else {
client($p, "$p->{name}$client_num", $c_out);
}
exit;
}
# Give a little time so we don't overrun the
# listen(2) backlog.
select undef, undef, undef, $start_time_delay;
}
exit;
} else {
my %data;
# The condition here selects between pretty output or
# raw output
if (1) {
my $CLEAR_LINE = "\033[2K";
my $CURSOR_HOME = "\033[1;1H";
my $CLEAR_SCREEN = "\033[2J";
# Process that retrieves client output to pretty print
print $CLEAR_SCREEN; # Clear screen
while (<$c_in>) {
chop;
my ($client_id, $r_l, $error, @rest) = split /\t/, $_;
$data{$client_id} = [ 0, ""] if not exists $data{$client_id};
my ($curr_rcv) = ${$data{$client_id}}[0] + $r_l;;
$error //= "";
my ($curr_error) = "${$data{$client_id}}[1]$error";
# Consolidate into... roman numerals!
$curr_error =~ s/D{10}/X/;
$curr_error =~ s/X{10}/C/;
$curr_error =~ s/C{10}/M/;
$data{$client_id} = [ $r_l + $curr_rcv, "$curr_error$error" ];
$client_id =~ /(\d+)/;
my $i = $1;
# print $CURSOR_HOME;
print "\033[$i;1H$CLEAR_LINE$client_id\t$curr_rcv\t$curr_error\n";
#foreach my $i (sort keys %data) {
# ($r_l, $error) = @{$data{$i}};
# print "$CLEAR_LINE$i\t$r_l\t$error\n";
}
} else {
# Just print the client outputs
while (<$c_in>) {
print;
}
}
}
warn "waiting\n";
wait;
warn "finished waiting\n";
`killall echosrv`;
+59
View File
@@ -0,0 +1,59 @@
# Configuration file for testing (use both by sslh under
# test and the test script `t`)
verbose: 4;
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; },
{ host: "localhost"; port: "8081"; keepalive: true; },
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "http"; host: "localhost"; port: "9002"; },
{ name: "tinc"; host: "localhost"; port: "9003"; },
{ name: "openvpn"; host: "localhost"; port: "9004"; },
{ name: "xmpp"; host: "localhost"; port: "9009"; },
{ name: "adb"; host: "localhost"; port: "9010"; },
{ name: "syslog"; host: "localhost"; port: "9013"; },
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
udp_timeout: 30;
regex_patterns: [ "^foo" ];
},
{ name: "regex"; host: "localhost"; port: "9011";
regex_patterns: [ "^foo", "^bar" ];
minlength: 4;
test_patterns: ( # this is used by the test script, not by sslh
{ pattern: "foo"; result: "ssh"; }, # After timeout
{ pattern: "fooo"; result: "regex"; },
{ pattern: "bar"; result: "ssh"; },
{ pattern: "barr"; result: "regex"; },
{ pattern: "barrrr"; result: "regex"; }
);
},
{ name: "tls"; host: "localhost"; port: "9021"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni1" ]; },
{ name: "tls"; host: "localhost"; port: "9022"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni2", "sni3" ]; },
{ name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; },
{ name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; },
{ name: "tls"; host: "localhost"; port: "9025"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
+78 -102
View File
@@ -32,6 +32,7 @@
#include <stdlib.h> /* malloc() */
#include <fnmatch.h> /* fnmatch() */
#include "tls.h"
#include "sslh-conf.h"
#define TLS_HEADER_LEN 5
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
@@ -41,28 +42,31 @@
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
typedef struct {
int tls_match_sni : 1;
int tls_match_alpn : 1;
} TLS_MATCHMODE;
struct TLSProtocol {
int use_alpn;
char** sni_hostname_list;
char** alpn_protocol_list;
TLS_MATCHMODE match_mode;
int sni_list_len;
const char** sni_hostname_list;
int alpn_list_len;
const char** alpn_protocol_list;
};
static int parse_extensions(const struct TLSProtocol *, const char *, size_t);
static int parse_server_name_extension(const struct TLSProtocol *, const char *, size_t);
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
static int has_match(char**, const char*, size_t);
static int has_match(const char**, size_t, const char*, size_t);
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
* hello handshake, returning a status code
*
* Returns:
* >=0 - length of the hostname and updates *hostname
* caller is responsible for freeing *hostname
* -1 - Incomplete request
* -2 - No Host header included in this request
* -3 - Invalid hostname pointer
* < -4 - Invalid TLS client hello
* 0: no match
* 1: match
* < 0: error code (see tls.h)
*/
int
parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
@@ -74,21 +78,21 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Check that our TCP payload is at least large enough for a TLS header */
if (data_len < TLS_HEADER_LEN)
return -1;
return TLS_ELENGTH;
tls_content_type = data[0];
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
return -5;
if (cfg.verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
return TLS_EPROTOCOL;
}
tls_version_major = data[1];
tls_version_minor = data[2];
if (tls_version_major < 3) {
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
if (cfg.verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
tls_version_major, tls_version_minor);
return -2;
return TLS_EVERSION;
}
/* TLS record length */
@@ -98,18 +102,18 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Check we received entire TLS record length */
if (data_len < len)
return -1;
return TLS_ELENGTH;
/*
* Handshake
*/
if (pos + 1 > data_len) {
return -5;
return TLS_EPROTOCOL;
}
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
if (verbose) fprintf(stderr, "Not a client hello\n");
if (cfg.verbose) fprintf(stderr, "Not a client hello\n");
return -5;
return TLS_EPROTOCOL;
}
/* Skip past fixed length records:
@@ -123,46 +127,54 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Session ID */
if (pos + 1 > data_len)
return -5;
return TLS_EPROTOCOL;
len = (unsigned char)data[pos];
pos += 1 + len;
/* Cipher Suites */
if (pos + 2 > data_len)
return -5;
return TLS_EPROTOCOL;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2 + len;
/* Compression Methods */
if (pos + 1 > data_len)
return -5;
return TLS_EPROTOCOL;
len = (unsigned char)data[pos];
pos += 1 + len;
if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
if (verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n");
return -2;
if (cfg.verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n");
return TLS_EVERSION;
}
/* Extensions */
if (pos + 2 > data_len)
return -5;
return TLS_EPROTOCOL;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2;
if (pos + len > data_len)
return -5;
return parse_extensions(tls_data, data + pos, len);
return TLS_EPROTOCOL;
/* By now we know it's TLS. if SNI or ALPN is set, parse extensions to see if
* they match. Otherwise, it's a match already */
if (tls_data &&
(tls_data->match_mode.tls_match_alpn || tls_data->match_mode.tls_match_sni)) {
return parse_extensions(tls_data, data + pos, len);
} else {
return TLS_MATCH;
}
}
static int
parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
size_t pos = 0;
size_t len;
int last_matched = 0;
int sni_match = 0, alpn_match = 0;
if (tls_data == NULL)
return -3;
return TLS_EINVAL;
/* Parse each 4 bytes for the extension header */
while (pos + 4 <= data_len) {
@@ -171,54 +183,17 @@ parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t da
(unsigned char) data[pos + 3];
if (pos + 4 + len > data_len)
return -5;
return TLS_EPROTOCOL;
size_t extension_type = ((unsigned char) data[pos] << 8) +
(unsigned char) data[pos + 1];
/* Check if it's a server name extension */
/* There can be only one extension of each type, so we break
our state and move pos to beginning of the extension here */
if (tls_data->use_alpn == 2) {
/* we want BOTH alpn and sni to match */
if (extension_type == 0x00) { /* Server Name */
if (parse_server_name_extension(tls_data, data + pos + 4, len)) {
/* SNI matched */
if(last_matched) {
/* this is only true if ALPN matched, so return true */
return last_matched;
} else {
/* otherwise store that SNI matched */
last_matched = 1;
}
} else {
/* both can't match */
return -2;
}
} else if (extension_type == 0x10) { /* ALPN */
if (parse_alpn_extension(tls_data, data + pos + 4, len)) {
/* ALPN matched */
if(last_matched) {
/* this is only true if SNI matched, so return true */
return last_matched;
} else {
/* otherwise store that ALPN matched */
last_matched = 1;
}
} else {
/* both can't match */
return -2;
}
}
} else if (extension_type == 0x00 && tls_data->use_alpn == 0) { /* Server Name */
return parse_server_name_extension(tls_data, data + pos + 4, len);
} else if (extension_type == 0x10 && tls_data->use_alpn == 1) { /* ALPN */
if (parse_alpn_extension(tls_data, data + pos + 4, len) > 0) {
return 1;
}
return parse_alpn_extension(tls_data, data + pos + 4, len);
if (extension_type == 0x00 && tls_data->match_mode.tls_match_sni) { /* Server Name */
sni_match = parse_server_name_extension(tls_data, data + pos + 4, len);
if (sni_match < 0) return sni_match;
} else if (extension_type == 0x10 && tls_data->match_mode.tls_match_alpn) { /* ALPN */
alpn_match = parse_alpn_extension(tls_data, data + pos + 4, len);
if (alpn_match < 0) return alpn_match;
}
pos += 4 + len; /* Advance to the next extension header */
@@ -226,9 +201,11 @@ parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return (sni_match && alpn_match)
|| (!tls_data->match_mode.tls_match_sni && alpn_match)
|| (!tls_data->match_mode.tls_match_alpn && sni_match);
}
static int
@@ -241,26 +218,26 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
(unsigned char)data[pos + 2];
if (pos + 3 + len > data_len)
return -5;
return TLS_EPROTOCOL;
switch (data[pos]) { /* name type */
case 0x00: /* host_name */
if(has_match(tls_data->sni_hostname_list, data + pos + 3, len)) {
if(has_match(tls_data->sni_hostname_list, tls_data->sni_list_len, data + pos + 3, len)) {
return len;
} else {
return -2;
return TLS_ENOEXT;
}
default:
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
if (cfg.verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
data[pos]);
}
pos += 3 + len;
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return TLS_ENOEXT;
}
static int
@@ -272,31 +249,34 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
len = (unsigned char)data[pos];
if (pos + 1 + len > data_len)
return -5;
return TLS_EPROTOCOL;
if (len > 0 && has_match(tls_data->alpn_protocol_list, data + pos + 1, len)) {
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
return len;
} else if (len > 0) {
if (verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
if (cfg.verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
}
pos += 1 + len;
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return TLS_ENOEXT;
}
static int
has_match(char** list, const char* name, size_t name_len) {
char **item;
has_match(const char** list, size_t list_len, const char* name, size_t name_len) {
const char **item;
int i;
char *name_nullterminated = malloc(name_len+1);
CHECK_ALLOC(name_nullterminated, "malloc");
memcpy(name_nullterminated, name, name_len);
name_nullterminated[name_len]='\0';
for (item = list; *item; item++) {
if (verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
for (i = 0; i < list_len; i++) {
item = &list[i];
if (cfg.verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
if(!fnmatch(*item, name_nullterminated, 0)) {
free(name_nullterminated);
return 1;
@@ -309,27 +289,23 @@ has_match(char** list, const char* name, size_t name_len) {
struct TLSProtocol *
new_tls_data() {
struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol));
if (tls_data != NULL) {
tls_data->use_alpn = -1;
}
CHECK_ALLOC(tls_data, "malloc");
memset(tls_data, 0, sizeof(*tls_data));
return tls_data;
}
struct TLSProtocol *
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, char** list) {
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;
if(tls_data->use_alpn == 0)
tls_data->use_alpn = 2;
else
tls_data->use_alpn = 1;
tls_data->alpn_list_len = list_len;
tls_data->match_mode.tls_match_alpn = 1;
} else {
tls_data->sni_hostname_list = list;
if(tls_data->use_alpn == 1)
tls_data->use_alpn = 2;
else
tls_data->use_alpn = 0;
tls_data->sni_list_len = list_len;
tls_data->match_mode.tls_match_sni = 1;
}
return tls_data;
+11 -1
View File
@@ -33,6 +33,16 @@ struct TLSProtocol;
int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len);
struct TLSProtocol *new_tls_data();
struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, char**);
struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, size_t);
#define TLS_MATCH 1
#define TLS_NOMATCH 0
#define TLS_EINVAL -1 /* Invalid parameter (NULL data pointer) */
#define TLS_ELENGTH -2 /* Incomplete request */
#define TLS_EVERSION -3 /* TLS version that cannot be parsed */
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
#define TLS_EPROTOCOL -5 /* Protocol error */
#endif
+138
View File
@@ -0,0 +1,138 @@
/*
udp-listener.c: handles demultplexing UDP protocols
# Copyright (C) 2020-2021 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#include "common.h"
#include "probe.h"
#include "sslh-conf.h"
#include "udp-listener.h"
/* Find if the specified source has been seen before. -1 if not found
*
* TODO This is linear search and needs to be changed to something better for
* production if we have more than a dozen sources
* Also, this assumes src_addr from recvfrom() are repeatable for a specific
* source...
* */
static int known_source(cnx_collection* collection, int max_fd, struct sockaddr* addr, socklen_t addrlen)
{
int i;
for (i = 0; i < max_fd; i++) {
struct connection* cnx = collection_get_cnx_from_fd(collection, i);
if (cnx && (cnx->type == SOCK_DGRAM) && cnx->target_sock) {
if (!memcmp(&cnx->client_addr, addr, addrlen)) {
return i;
}
}
}
return -1;
}
/* Process UDP coming from outside (client towards server)
* If it's a new source, probe; otherwise, forward to previous target
* Returns: >= 0 sockfd of newly allocated socket, for new connections
* -1 otherwise
* */
int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd)
{
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
struct sockaddr src_addr;
struct addrinfo addrinfo;
struct sslhcfg_protocols_item* proto;
struct connection* cnx;
ssize_t len;
socklen_t addrlen;
int res, target, out = -1;
char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
malloc/free overhead for each packet, when really 64K is not that much */
addrlen = sizeof(src_addr);
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
if (len < 0) {
perror("recvfrom");
return -1;
}
target = known_source(collection, max_fd, &src_addr, addrlen);
addrinfo.ai_addr = &src_addr;
addrinfo.ai_addrlen = addrlen;
if (cfg.verbose)
fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
if (target == -1) {
res = probe_buffer(data, 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 */
if (cfg.verbose) fprintf(stderr, "UDP probed: %d\n", res);
if (res != PROBE_MATCH) {
return -1;
}
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
if (!cnx) return -1;
target = out;
cnx->target_sock = out;
cnx->proto = proto;
cnx->type = SOCK_DGRAM;
cnx->client_addr = src_addr;
cnx->addrlen = addrlen;
cnx->local_endpoint = sockfd;
}
cnx = collection_get_cnx_from_fd(collection, target);
/* at this point src is the UDP connection */
res = sendto(cnx->target_sock, data, len, 0,
cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen);
cnx->last_active = time(NULL);
fprintf(stderr, "sending %d to %s\n",
res, sprintaddr(data, sizeof(data), cnx->proto->saddr));
return out;
}
void udp_s2c_forward(struct connection* cnx)
{
int sockfd = cnx->target_sock;
char data[65536];
int res;
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
fprintf(stderr, "recvfrom %d\n", res);
CHECK_RES_DIE(res, "udp_listener/recvfrom");
res = sendto(cnx->local_endpoint, data, res, 0,
&cnx->client_addr, cnx->addrlen);
cnx->last_active = time(NULL);
fprintf(stderr, "sendto %d to\n", res);
}
/* returns date at which this socket times out. */
int udp_timeout(struct connection* cnx)
{
if (cnx->type != SOCK_DGRAM) return 0; /* Not a UDP connection */
return cnx->proto->udp_timeout + cnx->last_active;
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef UDPLISTENER_H
#define UDPLISTENER_H
#include "collection.h"
/* UDP listener: upon incoming packet, find where it should go
* This is run in its own process and never returns.
*/
void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint);
/* Process UDP coming from outside (client towards server)
* If it's a new source, probe; otherwise, forward to previous target
* Returns: >= 0 sockfd of newly allocated socket, for new connections
* -1 otherwise
* */
int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd);
/* Process UDP coming from inside (server towards client) */
void udp_s2c_forward(struct connection* cnx);
/* returns how many seconds before socket times out. Negative if timed out
* already.
*/
int udp_timeout(struct connection* cnx);
#endif /* UDPLISTENER_H */
+31
View File
@@ -0,0 +1,31 @@
# Example for UDP protocols
# Listen sockets get a `is_udp : true` option
# Protocol entries work exactly the same as TCP, there is no
# difference. Currently, if mix up TCP and UDP probes, sslh will
# try them in order and forward a UDP packet to a TCP probe
# that matches (but using UDP; meaning, sslh will happily
# forward a UDP packet to udp:localhost:80 if the HTTP
# probe is specified, and matches)
verbose: 3;
foreground: true;
inetd: false;
numeric: false;
transparent: false;
timeout: 10;
pidfile: "/tmp/sslh_test.pid";
# List of interfaces on which we should listen
# Options:
listen: (
{ host: "localhost"; is_udp: true; port: "4443"; }
);
protocols:
(
{ name: "regex"; regex_patterns: [ "rutschle.net" ]; host: "ns1.rutschle.net"; port: "53"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);