Compare commits

..
133 Commits
Author SHA1 Message Date
Yves Rutschle 4ae2e62d25 v1.20 2018-11-20 22:58:41 +01:00
yrutschle 8ec9799ca0 fix ssl tests 2018-11-04 22:59:01 +01:00
yrutschle 80ad31aec0 refactoring: replace magic constants with symbols 2018-11-04 22:25:16 +01:00
yrutschle d6c714166a only try to parse TLS extensions if settings are actually set 2018-09-30 21:35:45 +02:00
yrutschle aa77922ffd turn ssl setting from command line to tls (ssl no longer exists) 2018-09-30 21:34:22 +02:00
yrutschle 2ee0088c5f turn ssl setting from configuration file to tls (ssl no longer exists) 2018-09-30 20:20:06 +02:00
yrutschle f480eb6c7d refactoring: simpler TLS extension parsing algorithm 2018-09-28 13:58:33 +02:00
yrutschle 6431bb7e35 refactoring: changed magic numbers for use_alpn to named bitfields 2018-09-23 22:29:25 +02:00
yrutschle 6d6ea50066 Merge branch 'master' of https://github.com/yrutschle/sslh 2018-09-23 21:57:16 +02:00
yrutschle 71265a8477 Merge pull request #201 from astiob/probe-strlen
Fix incorrect strncmp length in HTTP method probing
2018-09-23 21:56:51 +02:00
yrutschle 0003680137 remove old tls and ssl targets, only use alpn/sni probe also for TLS with no extensions 2018-09-23 12:07:06 +02:00
Oleg Oshmyan e8f0d3ea53 Fix HTTP method probing 2018-09-22 15:50:40 +03:00
yrutschle a5d00568b5 Merge pull request #199 from rom1dep/sni_alpn_errmatching
tls: proposed fix for incorrect SNI/ALPN matching
2018-08-30 23:16:28 +02:00
yrutschle ffe9971624 test suite for SNI/ALPN with multiple targets and all combinations covered 2018-08-30 19:50:53 +02:00
Yves Rutschle 1693436cc3 automatic test for SNI/ALPN (single target) 2018-08-29 19:18:23 +02:00
Romain DEP. e42f670112 tls: proposed fix for incorrect SNI/ALPN matching 2018-08-21 22:36:01 +02:00
Yves Rutschle 60df92c2b2 prevent repeated reads on broken sockets 2018-08-14 23:05:49 +02:00
Yves Rutschle 8ad32816a6 last_p might be used uninitialised if last probe has no probe set (which I don't think might happen, but anyhow...) 2018-08-14 22:52:52 +02:00
Yves Rutschle 677e385fec new probing algorithm 2018-08-13 22:29:09 +02:00
Yves Rutschle b6db83a701 also test probes when no fragmentation occurs 2018-08-12 21:45:42 +02:00
Yves Rutschle 3a17bd6832 removed obsolete tests 2018-08-12 21:35:42 +02:00
Yves Rutschle 94911c1c2a cleaner framework to test all probes methodically (not all tests work as the probe code needs to be reworked) 2018-08-11 23:13:24 +02:00
Yves Rutschle d7889588da Merge branch 'master' of https://www.github.com/yrutschle/sslh 2018-08-04 22:34:20 +02:00
Yves Rutschle ad2b595280 Test suite: some cleanup and use config file instead of command-line parameters 2018-08-04 22:33:02 +02:00
Yves Rutschle 9df1ab8404 fix man page on timeout default protocol 2018-07-31 19:54:46 +00:00
yrutschle 10fb0bce6f Merge pull request #192 from candrews/patch-5
Include common.h in systemd-sslh-generator.c
2018-07-11 13:18:50 +02:00
Craig Andrews eb53c45351 Include common.h in systemd-sslh-generator.c
Fixes https://github.com/yrutschle/sslh/issues/188
2018-07-06 12:30:28 -04:00
Yves Rutschle a1cc399ae5 fix syslog facility name check 2018-07-03 12:22:03 +02:00
yrutschle 108a9780d8 Merge pull request #187 from msantos/segfault
Fix potential segfaults (unverified malloc() returns) and other robustness issues
2018-06-18 16:45:51 +02:00
Michael Santos 9228171eb0 config: exit if list element is invalid 2018-06-18 10:35:28 -04:00
Michael Santos 8ce2b2ea05 Check memory allocations succeed 2018-06-18 10:35:28 -04:00
Michael Santos 4c132e3c8d config: segfault parsing invalid sni/alpn
Check return value of config_setting_get_string_elem() for error
before passing the result to strlen():

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

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

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

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

    common.c:122:14: warning: Call to 'malloc' has an allocation size of 0 bytes
       *sockfd = malloc(num_addr * sizeof(*sockfd[0]));
		 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2018-06-18 10:35:28 -04:00
yrutschle 336e8bb9d1 Merge pull request #186 from Revertron/patch-1
Fixed a typo
2018-06-14 16:36:13 +02:00
Roman 0ada00474b Fixed a typo
Just a typo in usage text.
2018-06-14 14:31:50 +02:00
Yves Rutschle 95e8f5731c document magic constant in socks5 probe 2018-06-13 12:40:02 +00:00
Yves Rutschle dfd9e14866 fix socks5 probe 2018-06-13 09:52:49 +02:00
yrutschle 552723cc5f Merge pull request #180 from sanmai/patch-1
Update README.md
2018-06-01 10:31:34 +02:00
Alexey Kopytko 09aaf39e25 Update README.md
Removed redundant dollar signs since they're not used anywhere else in the README.
2018-06-01 15:13:50 +09:00
Yves Rutschle 7acf9627ee fix memory leak when using transparent proxying 2018-05-29 12:38:57 +02:00
Yves Rutschle b8e63a4d9d fixed systemd config to correct path 2018-05-26 17:02:15 +00:00
yrutschle f451cc8bed Merge pull request #172 from WeirdCarrotMonster/master
Added support for socks5 protocol
2018-04-16 22:38:06 +02:00
Eugene Protozanov a43dd11fc9 Added support for socks5 protocol 2018-04-16 15:27:31 +04:00
yrutschle 3a61c8b0b1 Merge pull request #167 from HighwindsHipsApp/master
Fixed parent/child usage after initial fork in sslh-fork.c
2018-02-11 18:42:21 +01:00
Josh Tway 0c928fedbb Fixed issue in sslh-fork.c where the parent was being used instead of the child after forking. This was breaking multiple unit tests (on CentOS 7 at least) 2018-02-08 17:22:22 -05:00
Yves Rutschle 1a6ba5edc0 fix IPv6 parse error introduced in 7bf3e12c30 2018-01-27 22:59:52 +01:00
Yves Rutschlé 83be63d282 v1.19 2018-01-20 18:40:15 +00:00
Yves Rutschlé 3ab2acdafa fix fork failure handling 2018-01-11 14:45:32 +00:00
Yves Rutschlé c8c6688d16 add file and line number upon error messages 2018-01-10 22:10:19 +00:00
Yves Rutschlé 9a85efded5 log error message in case fork fails 2018-01-08 18:54:01 +00:00
Yves Rutschlé 7bf3e12c30 Don't clobber data in libconfig space, copy it before
changing it. So far it worked, but really that's not
respecting the contract.
2018-01-07 16:43:50 +00:00
Yves Rutschlé bc72c4ac42 clarify regex examples are just examples 2018-01-07 15:42:52 +00:00
Yves Rutschlé fc9cb27746 fix ALPN configuration logic 2018-01-06 22:43:16 +00:00
Yves Rutschlé 2c3518beb4 configuration examples for fork option 2018-01-06 22:42:39 +00:00
yrutschle 141e4b4f83 Merge pull request #105 from vapier/master
add chroot support
2018-01-03 20:34:05 +01:00
Mike Frysinger 0fb4c6b2ad add chroot support
This allows people to chroot sslh into a path to further harden it.

We have to rework the user logic a bit because we need to look up
the user details *before* we chroot (as we need to read /etc/passwd
files), but do the actual priv dropping *after* we chroot (so we
have permission to make the actual chroot call).

Similarly, we need to open the syslog before we drop privs because
/dev/log won't be available inside the chroot.
2018-01-03 10:19:59 -05:00
yrutschle d9541392f8 Merge pull request #139 from tmolitor-stud-tu/master
Better documentation of transparent proxy support with more features
2018-01-02 23:24:46 +01:00
yrutschle 24cd3d0974 Merge pull request #161 from pali/master
Ensure that IPv6 socket would listen only for IPv6 connections
2018-01-02 23:19:00 +01:00
yrutschle d26eab728c Merge pull request #143 from astiob/select-fork
sslh-select: support forking for particular protocols
2018-01-02 22:26:58 +01:00
yrutschle 53e00c27ff Merge pull request #159 from rdebath/patch-5
Translate systemd C99 and C++ code to old C
2018-01-02 22:23:19 +01:00
yrutschle 063169c167 Merge pull request #162 from vapier/adb
adb: update protocol sniffer
2018-01-02 22:21:19 +01:00
Prameet Shah 79ca4df0d7 adb: update protocol sniffer
Modified is_adb_protocol in patch.c to check if initial host->device
packet sends an empty message for reasons unknown.  This was introduced
in ADB master in https://android-review.googlesource.com/c/342653.
2018-01-02 06:25:27 -05:00
Pali Rohár 3db5e127fd Ensure that IPv6 socket would listen only for IPv6 connections
When transparent mode is enabled and sslh listening on :: IPv6 address then
source origin address is propagated to target application independently if
connection is IPv4 or IPv6.

On Linux by default IPv6 socket can accept also IPv4 connections. More
applications, including OpenSSH server do not accept IPv4 connections on
IPv6 socket and therefore such transparent configuration does not work.
On BSD systems it is turned off by default due to security reasons.

This patch disables IPv4 connections on IPv6 listening sockets. If somebody
needs to have sslh listening on both IPv4 and IPv6 addresses, then still it
is possible by specifying multiple --listen arguments.

I think it is more misleading if option --listen :::443 cause listening on
both IPv4 and IPv6 addresses even IPv4 address was not specified. This can
also cause security related problems for people who do not know about this
fact as documentation does not mentioned this behavior.
2017-12-28 21:31:15 +01:00
Robert de Bath 981091e39d Translate systemd C99 and C++ code to old C 2017-12-17 16:37:00 +00:00
Yves Rutschle 0fc6bc8d12 Revert "Adjust linking so that wrapper libraries are static."
This reverts commit 021eb836e4.

That commit introduces regression, see
https://github.com/yrutschle/sslh/issues/157#issuecomment-352260802
for discussion.
2017-12-17 16:59:57 +01:00
Yves Rutschle b36fc73b7a log timeouts 2017-12-17 14:57:45 +01:00
Yves Rutschle a7f0c456ab die if target cannot be resolved (otherwise, we segfault when printing the settings or later) 2017-12-17 14:55:51 +01:00
yrutschle 7808a3a766 Merge pull request #142 from astiob/select-bugs
Fix several bugs in sslh-select
2017-12-16 19:51:23 +01:00
yrutschle f5b1b881a4 Merge pull request #145 from ariera/master
Doc update about Transparent proxy support
2017-12-16 19:11:34 +01:00
yrutschle f8a16c7a29 Merge pull request #146 from guusdk/SSLv2-clienthello
Allow SSLv2 CLIENT-HELLO (without SSL 2.0)
2017-12-16 19:09:25 +01:00
yrutschle b33c65ed53 Merge pull request #96 from candrews/patch-1
Harden the systemd service
2017-12-16 19:04:52 +01:00
yrutschle 2f8e635b67 Merge pull request #97 from candrews/patch-2
make sure the files using version.h depend on it being generated first
2017-12-16 19:01:28 +01:00
yrutschle b8851d6714 Merge pull request #98 from candrews/patch-3
Build systemd-sslh-generator if USESYSTEMD is set
2017-12-16 18:59:36 +01:00
Craig Andrews e33124718e Harden the systemd service 2017-12-12 16:40:53 -05:00
yrutschle 85b94c3259 Merge pull request #153 from rdebath/master
Move hexdump to verbose level 2
2017-11-28 21:05:50 +01:00
Robert de Bath 4e790e074f Move hexdump to verbose level 2
From the command line you use two "-v" options or in the configuration
file you replace the boolean "verbose:true" with an integer "verbose:2".
2017-11-27 21:05:07 +00:00
Yves Rutschle 6ca1ee7bfd remove leftover debug messages 2017-11-26 20:10:05 +01:00
yrutschle e66e443d5e Merge pull request #151 from rdebath/patch-3
Hexdump for verbose mode.
2017-11-24 13:55:53 +01:00
yrutschle faa928b75a Merge pull request #150 from rdebath/patch-2
Get libpcre working (and by default)
2017-11-24 13:53:52 +01:00
yrutschle 2ad99fd36f Merge pull request #149 from rdebath/patch-1
Fixup compile using -std=c90
2017-11-24 13:52:22 +01:00
Robert de Bath 64485d7a58 Send hexdump to stderr like other verbose logs. 2017-11-23 20:52:54 +00:00
orbitarm 5b756ebd0a verbose: dump hex value of packet
uses the hexdump() function to display the hex value of each probed
packet, making it easy to create regex rules for unsupported connections
2017-11-23 20:52:54 +00:00
Robert de Bath 021eb836e4 Adjust linking so that wrapper libraries are static. 2017-11-23 20:51:57 +00:00
Robert de Bath cb90cc97ae Default to using libpcre and actually use it
as libpcre has to better binary support.

Note, just linking libpcre only has no effect, the posix functions are
provided by libpcreposix.

Use "make USELIBPCRE=" to turn libpcre off and link POSIX library.
2017-11-23 20:51:57 +00:00
Robert de Bath 338daafe87 Use REG_EXTENDED for regex matching
The "7 regex" manual page called 'Basic' regular expressions "Obsolete".

It also matches the pcre expressions slightly better.
2017-11-23 20:51:57 +00:00
Robert de Bath 9fcbe8c7ea Fixup compile using -std=c90 2017-11-23 20:50:49 +00:00
yrutschle 2a7bafdd7f Merge pull request #147 from jmccrohan/master
tls: ensure hostname is always null-terminated
2017-11-13 23:03:12 +01:00
Jonathan McCrohan 3f5d9a4168 tls: ensure hostname is always null-terminated
6cc3382 introduced a potential buffer overflow. Ensure that hostname is
always null-terminated. (Issue #135)

Signed-off-by: Jonathan McCrohan <jmccrohan@gmail.com>
2017-11-13 00:15:41 +00:00
Guus der Kinderen 1f98b97756 Allow SSLv2 CLIENT-HELLO (without SSL 2.0)
The existing TLS probe is documented to ignore SSL 2.0, citing RFC 6176 as a reason.
RFC 6176 does prohibit the usage of SSL 2.0, but does allow for ClientHello messages
in the version 2 CLIENT-HELLO format (as long as those are used to negotiate the use
of a higher protocol).

This commit extends the TLS probe, by making it accept SSL v2 ClientHello messages
that negotiate a version of SSL/TLS 1.0 or higher (which is the same version range
as the original code).
2017-11-10 19:47:07 +01:00
Alejandro Riera 09d11e3bc8 Doc update about Transparent proxy support
Advise users to save the configuration of `iptables` and `ip` rules and routes or they risk loosing it after a reboot and/or crash.
2017-11-08 11:54:26 +01:00
Oleg Oshmyan 2a70470f13 sslh-select: reduce CPU and memory usage in forked processes 2017-10-28 23:27:10 +03:00
Oleg Oshmyan 2544f20bdf sslh-select: support forking for particular protocols
To keep the code simple, use the same event loop in the child process
as in the parent process but close all irrelevant file descriptors.
2017-10-28 23:27:06 +03:00
Oleg Oshmyan 60b11e4964 Fix defer_write when deferred_data != begin_deferred_data
I think this currently never happens, but let's
not wait until it starts happening and blows up.
2017-10-28 23:13:29 +03:00
Oleg Oshmyan b7fafb5039 sslh-select: invoke FD_CLR on fd before closing fd
POSIX requires the fd argument to any FD_ macro to be valid.
2017-10-28 23:13:28 +03:00
Oleg Oshmyan b56f302b85 sslh-select: simplify some code 2017-10-28 23:13:28 +03:00
Oleg Oshmyan 684c9afcc6 sslh-select: actually close socket on error in accept_new_connection
Previously, it was leaked (and the client was left waiting for a timeout).
2017-10-28 23:13:28 +03:00
Oleg Oshmyan a3df50f31f sslh-select: fix connections with deferred data after connect_queue
Previously, if some data was still deferred after the connect_queue
call, the server side of the connection would never start being
monitored for reads, while the client side kept being monitored
and new data from the client could be sent to the server before
the previously deferred data.
2017-10-28 23:13:28 +03:00
Thilo Molitor d243d36add Fix ipv6 config copy-paste errors 2017-10-10 07:36:09 +02:00
Thilo Molitor 74767cb781 Some cleanup 2017-10-10 07:22:44 +02:00
Thilo Molitor 2a76b520d5 Add better documentation of transparent proxy support.
This allows for some more generalized configs.
You don't need to specify ports anymore and still can
connect directly to the running services if you want.

It also allows you to use "localhost" as destination in your sslh config,
something that wasn't possible with the old scheme.
2017-10-10 07:07:28 +02:00
Yves Rutschlé 0929d39a34 move Let's encrypt config before TLS catchall 2017-09-24 19:55:38 +00:00
Yves Rutschle f4d2a8d2ad fix logging to specified facility 2017-07-22 17:20:45 +02:00
Yves Rutschle aa06261d70 added syslog_facility option 2017-07-21 22:46:24 +02:00
Yves Rutschle dd900ebf3e fail gracefully if target protocol decription is incomplete 2017-07-09 20:51:53 +02:00
Yves Rutschlé 21f524f711 Add support for wildcard ALPN/SNI values 2017-06-12 21:05:12 +00:00
Jonathan McCrohan 1e65088b7e example.cfg: Add Let's Encrypt support to config
Provides a sample config for Let's Encrypt using the tls-sni-*
challenges. Requires wildcard support added in 6cc3382.

Signed-off-by: Jonathan McCrohan <jmccrohan@gmail.com>
2017-06-06 01:37:07 +01:00
Jonathan McCrohan 6cc33820d1 tls: permit wildcard ALPN/SNI values
Use fnmatch(3) to provide support for glob style wildcard values in the
ALPN and SNI parameters of the TLS probe.

Signed-off-by: Jonathan McCrohan <jmccrohan@gmail.com>
2017-06-06 01:11:29 +01:00
Yves Rutschle 00d5872aa1 ignore brackets in hostname in config files 2017-04-21 22:33:02 +02:00
Yves Rutschle 7d561af423 allocate listen[] before writing to it... 2017-04-18 21:04:30 +02:00
Yves Rutschle cce42c6882 re-indent 2017-04-18 20:53:19 +02:00
Yves Rutschle b0f4e24ce0 IP_FREEBIND: real fix, ignore catastrophic previous checkin 2017-04-06 17:13:11 +02:00
Yves Rutschle 078827ad3f Some systems define IP_FREEBIND but don't implement it which result in setsockopt() failing. No need to die in that case, just ignore the error. 2017-04-06 16:26:27 +02:00
yrutschle 4413284420 Merge pull request #120 from yann-morin-1998/yem/parallel-make
Makefile: fix parallel build
2017-02-23 22:54:55 +01:00
Yann E. MORIN 70a2ea926e Makefile: fix parallel build
version.h is included by some .o files, but it is generated. As such, it
must be a dependency of those .o files.

Rather than filter exactly which .o file needs it, just add a generic
dependency for all .o files on version.h.

Signed-off-by: "Yann E. MORIN" <yann.morin.1998@free.fr>
2017-02-23 22:49:31 +01:00
Yves Rutschle e4a4e04bf8 Clarify no space after -F (Issue 108, take 2) 2017-01-08 13:00:19 +01:00
Yves Rutschle 067f5d7646 Revert "clarify no space after -F (issue 108)"
This reverts commit f02ce3821c.

That commit accidently imported code that broke transparent
proxying.
2017-01-08 12:54:34 +01:00
yrutschle 718fe0e2e9 Merge pull request #106 from nckx/man-trivialities
Fix a few typos in the man page
2016-12-19 21:55:03 +01:00
yrutschle 864b7badd2 Merge pull request #115 from jimt/patch-1
Fix README typos.
2016-12-19 21:39:09 +01:00
Jim Tittsler 234c088324 Fix README typos. 2016-12-19 10:24:31 +09:00
Yves Rutschlé f02ce3821c clarify no space after -F (issue 108) 2016-11-06 19:44:47 +00:00
Tobias Geerinckx-Rice e492c2808c Fix a few typos in the man page 2016-10-19 02:41:12 +02:00
Craig Andrews a4d2a53a47 Build systemd-sslh-generator if USESYSTEMD is set 2016-06-24 20:30:15 -04:00
Craig Andrews efba558967 make sure the files using version.h depend on it being generated first 2016-06-24 20:11:13 -04:00
yrutschle b4cb910438 Merge pull request #92 from mscherer/complete_man
Add more options in the synopsis
2016-06-05 18:37:22 +02:00
Michael Scherer 975560aa4a Add more options in the synopsis 2016-06-04 11:57:08 +02:00
Yves Rutschlé 63a83cf041 fix SNI/ALPN option parsing (issue 90) 2016-05-20 07:52:24 +00:00
Yves Rutschlé 245fd26459 fix compiler warnings 2016-05-03 08:37:23 +00:00
yrutschle 9cc47a157d Merge pull request #69 from ViKingIX/fix_local_conn
Fix the connection problem in transparent mode.
2016-04-28 11:55:56 +02:00
yrutschle 09b5c84d34 Merge pull request #86 from hogarthj/fix_readme
Merge error in README.md mixing sections
2016-03-31 16:28:32 +02:00
James Hogarth e6cb3596d1 Merge error in README.md mixing sections 2016-03-30 11:48:45 +01:00
ViKing b108809a78 Fix the connection problem in transparent mode.
When the source and destination are the same, the bind_peer() will
fail, thus end the connection. Therefore a check of all the interface
IPs are checked to skip bind() if they are the same.
2015-10-07 00:10:03 +08:00
19 changed files with 1158 additions and 564 deletions

No files matched your search

+47
View File
@@ -1,3 +1,50 @@
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.
TLS now supports SNI and ALPN (Travis Burtrum),
including support for Let's Encrypt challenges
(Jonathan McCrohan)
ADB probe. (Mike Frysinger)
Added per-protocol 'fork' option. (Oleg Oshmyan)
Added chroot option. (Mike Frysinger)
A truckload of bug fixes and documentation
improvements (Various contributors)
v1.18: 29MAR2016
Added USELIBPCRE to make use of regex engine
optional.
+10 -5
View File
@@ -3,7 +3,7 @@
VERSION=$(shell ./genver.sh -r)
ENABLE_REGEX=1 # Enable regex probes
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBPCRE= # Use libpcre? (needed for regex on musl)
USELIBPCRE=1 # Use libpcre? (needed for regex on musl)
USELIBWRAP?= # Use libwrap?
USELIBCAP= # Use libcap?
USESYSTEMD= # Make use of systemd socket activation
@@ -27,6 +27,8 @@ CFLAGS ?=-Wall -g $(CFLAGS_COV)
LIBS=
OBJS=common.o sslh-main.o probe.o tls.o
CONDITIONAL_TARGETS=
ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap
CPPFLAGS+=-DLIBWRAP
@@ -38,7 +40,7 @@ endif
ifneq ($(strip $(USELIBPCRE)),)
CPPFLAGS+=-DLIBPCRE
LIBS:=$(LIBS) -lpcre
LIBS:=$(LIBS) -lpcreposix
endif
ifneq ($(strip $(USELIBCONFIG)),)
@@ -54,12 +56,13 @@ endif
ifneq ($(strip $(USESYSTEMD)),)
LIBS:=$(LIBS) -lsystemd
CPPFLAGS+=-DSYSTEMD
CONDITIONAL_TARGETS+=systemd-sslh-generator
endif
all: sslh $(MAN) echosrv
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
.c.o: *.h
.c.o: *.h version.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
version.h:
@@ -67,6 +70,8 @@ version.h:
sslh: sslh-fork sslh-select
$(OBJS): version.h
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
#strip sslh-fork
@@ -78,7 +83,7 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
systemd-sslh-generator: systemd-sslh-generator.o
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
echosrv: $(OBJS) echosrv.o
echosrv: version.h $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
$(MAN): sslh.pod Makefile
+66 -49
View File
@@ -135,7 +135,7 @@ i.e. that that is not `127.0.0.1` in the output of
Libwrap support
---------------
Sslh can optionnaly perform `libwrap` checks for the sshd
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.
@@ -145,10 +145,10 @@ OpenVPN support
OpenVPN clients connecting to OpenVPN running with
`-port-share` reportedly take more than one second between
the time the TCP connexion is established and the time they
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 connexion.
To support OpenVPN connexions reliably, it is necessary to
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
@@ -205,11 +205,11 @@ and `CAP_NET_ADMIN` for transparent proxying (see
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
sudo 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
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
@@ -231,35 +231,70 @@ Linux:
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 follow.
The example connects to HTTPS on 4443 -- adapt to your needs ;
I don't think it is possible to have `httpd` listen to 443 in
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:
# iptables -t mangle -N SSLH
# iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 22 --jump SSLH
# iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 4443 --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
# 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:
# ip6tables -t mangle -N SSLH
# ip6tables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 22 --jump SSLH
# ip6tables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 4443 --jump SSLH
# ip6tables -t mangle -A SSLH --jump MARK --set-mark 0x1
# ip6tables -t mangle -A SSLH --jump ACCEPT
# ip -6 rule add fwmark 0x1 lookup 100
# ip -6 route add local ::/0 dev lo table 100
# 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
Note that these rules will prevent from connecting directly
to ssh on the port 22, as packets coming out of sshd will be
tagged. If you need to retain direct access to ssh on port
22 as well as through sslh, you can make sshd listen to
22 AND another port (e.g. 2222), and change the above rules
accordingly.
# 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:
@@ -390,32 +425,14 @@ To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Transparent 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.
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 a same IP address (obviously this depends
on the site, there might be legimite reasons you would get
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.
+2 -1
View File
@@ -9,6 +9,7 @@ transparent: false;
timeout: 2;
user: "nobody";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# Change hostname with your external address name.
@@ -19,7 +20,7 @@ listen:
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
+161 -55
View File
@@ -4,10 +4,16 @@
* No code here should assume whether sockets are blocking or not.
**/
#define SYSLOG_NAMES
#define _GNU_SOURCE
#include <stddef.h>
#include <stdarg.h>
#include <grp.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include "common.h"
#include "probe.h"
@@ -35,7 +41,7 @@ int foreground = 0;
int background = 0;
int transparent = 0;
int numeric = 0;
const char *user_name, *pid_file;
const char *user_name, *pid_file, *chroot_path, *facility = "auth";
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
@@ -44,8 +50,13 @@ struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
int allow_severity =0, deny_severity = 0;
#endif
typedef enum {
CR_DIE,
CR_WARN
} CR_ACTION;
/* check result and die, printing the offending address and error */
void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall)
{
char buf[NI_MAXHOST];
@@ -54,7 +65,9 @@ void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
exit(1);
if (act == CR_DIE)
exit(1);
}
}
@@ -69,8 +82,10 @@ int get_fd_sockets(int *sockfd[])
exit(1);
}
if (sd > 0) {
int i;
*sockfd = malloc(sd * sizeof(*sockfd[0]));
for (int i = 0; i < sd; i++) {
CHECK_ALLOC(*sockfd, "malloc");
for (i = 0; i < sd; i++) {
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
}
}
@@ -102,10 +117,16 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;
if (num_addr == 0) {
fprintf(stderr, "FATAL: No available addresses.\n");
exit(1);
}
if (verbose)
fprintf(stderr, "listening to %d addresses\n", num_addr);
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
CHECK_ALLOC(*sockfd, "malloc");
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
if (!addr) {
@@ -115,34 +136,80 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
saddr = (struct sockaddr_storage*)addr->ai_addr;
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dumpdie((*sockfd)[i], addr, "socket");
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_dumpdie(res, addr, "setsockopt(SO_REUSEADDR)");
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_dumpdie(res, addr, "setsockopt(SO_KEEPALIVE)");
printf("set up keepalive\n");
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_dumpdie(res, addr, "setsockopt(IP_FREEBIND)");
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_dumpdie(res, addr, "bind");
check_res_dump(CR_DIE, res, addr, "bind");
res = listen ((*sockfd)[i], 50);
check_res_dumpdie(res, addr, "listen");
check_res_dump(CR_DIE, res, addr, "listen");
}
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
@@ -160,6 +227,11 @@ int bind_peer(int fd, int fd_from)
* got here */
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername");
/* if the destination is the same machine, there's no need to do bind */
if (is_same_machine(&from))
return 0;
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
CHECK_RES_DIE(res, "setsockopt");
@@ -227,7 +299,6 @@ int connect_addr(struct connection *cnx, int fd_from)
one = 1;
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)");
printf("set up keepalive\n");
}
return fd;
}
@@ -240,17 +311,16 @@ int connect_addr(struct connection *cnx, int fd_from)
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)
fprintf(stderr, "**** writing deferred on fd %d\n", q->fd);
p = realloc(q->begin_deferred_data, q->deferred_data_size + data_size);
if (!p) {
perror("realloc");
exit(1);
}
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
CHECK_ALLOC(p, "realloc");
q->deferred_data = q->begin_deferred_data = p;
p += q->deferred_data_size;
q->begin_deferred_data = p;
q->deferred_data = p + data_offset;
p += data_offset + q->deferred_data_size;
q->deferred_data_size += data_size;
memcpy(p, data, data_size);
@@ -398,19 +468,43 @@ 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* host, const char* serv)
*/
int resolve_split_name(struct addrinfo **out, const char* ct_host, const 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 */
}
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;
}
@@ -420,7 +514,7 @@ fullname: input string -- it gets clobbered
*/
void resolve_name(struct addrinfo **out, char* fullname)
{
char *serv, *host, *end;
char *serv, *host;
int res;
/* Find port */
@@ -434,17 +528,6 @@ void resolve_name(struct addrinfo **out, char* fullname)
host = fullname;
/* 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);
}
host++; /* skip first bracket */
*end = 0; /* remove last bracket */
}
res = resolve_split_name(out, host, serv);
if (res) {
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
@@ -591,12 +674,21 @@ void setup_signals(void)
* banner is made up of basename(bin_name)+"[pid]" */
void setup_syslog(const char* bin_name) {
char *name1, *name2;
int res;
int res, fn;
name1 = strdup(bin_name);
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
openlog(name2, LOG_CONS, LOG_AUTH);
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
if (strcmp(facilitynames[fn].c_name, facility) == 0)
break;
if (facilitynames[fn].c_val == -1) {
fprintf(stderr, "Unknown facility %s\n", facility);
exit(1);
}
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
free(name1);
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
@@ -654,33 +746,47 @@ void set_capabilities(void) {
}
/* We don't want to run as root -- drop privileges if required */
void drop_privileges(const char* user_name)
void drop_privileges(const char* user_name, const char* chroot_path)
{
int res;
struct passwd *pw = getpwnam(user_name);
if (!pw) {
fprintf(stderr, "%s: not found\n", user_name);
exit(2);
struct passwd *pw = NULL;
if (user_name) {
pw = getpwnam(user_name);
if (!pw) {
fprintf(stderr, "%s: not found\n", user_name);
exit(2);
}
if (verbose)
fprintf(stderr, "turning into %s\n", user_name);
}
if (verbose)
fprintf(stderr, "turning into %s\n", user_name);
set_keepcaps(1);
if (chroot_path) {
if (verbose)
fprintf(stderr, "chrooting into %s\n", chroot_path);
/* remove extraneous groups in case we belong to several extra groups that
* may have unwanted rights. If non-root when calling setgroups(), it
* fails, which is fine because... we have no unwanted rights
* (see POS36-C for security context)
* */
setgroups(0, NULL);
res = chroot(chroot_path);
CHECK_RES_DIE(res, "chroot");
}
res = setgid(pw->pw_gid);
CHECK_RES_DIE(res, "setgid");
res = setuid(pw->pw_uid);
CHECK_RES_DIE(res, "setuid");
if (user_name) {
set_keepcaps(1);
set_capabilities();
set_keepcaps(0);
/* remove extraneous groups in case we belong to several extra groups
* that may have unwanted rights. If non-root when calling setgroups(),
* it fails, which is fine because... we have no unwanted rights
* (see POS36-C for security context)
* */
setgroups(0, NULL);
res = setgid(pw->pw_gid);
CHECK_RES_DIE(res, "setgid");
res = setuid(pw->pw_uid);
CHECK_RES_DIE(res, "setuid");
set_capabilities();
set_keepcaps(0);
}
}
/* Writes my PID */
+11 -3
View File
@@ -37,16 +37,24 @@
#define CHECK_RES_DIE(res, str) \
if (res == -1) { \
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
perror(str); \
exit(1); \
}
#define CHECK_RES_RETURN(res, str) \
if (res == -1) { \
log_message(LOG_CRIT, "%s:%d:%s\n", str, errno, strerror(errno)); \
log_message(LOG_CRIT, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
return res; \
}
#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
@@ -102,7 +110,7 @@ void log_connection(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);
void drop_privileges(const char* user_name, const char* chroot_path);
void write_pid_file(const char* pidfile);
void log_message(int type, char* msg, ...);
void dump_connection(struct connection *cnx);
@@ -118,7 +126,7 @@ extern int probing_timeout, verbose, inetd, foreground,
extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING;
extern const char* user_name, *pid_file;
extern const char* user_name, *pid_file, *chroot_path, *facility;
extern const char* server_type;
/* sslh-fork.c */
+22 -5
View File
@@ -11,7 +11,13 @@ transparent: false;
timeout: 2;
user: "nobody";
pidfile: "/var/run/sslh.pid";
chroot: "/var/empty";
# Specify which syslog facility to use (names for your
# system are usually defined in /usr/include/*/sys/syslog.h
# or equivalent)
# Default is "auth"
syslog_facility: "auth";
# List of interfaces on which we should listen
# Options:
@@ -33,27 +39,34 @@ listen:
# 1 to log each incoming connection
# keepalive: Should TCP keepalive be on or off for that
# connection (default is off)
# fork: Should a new process be forked for this protocol?
# (only useful for sslh-select)
#
# 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; },
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; keepalive: true; fork: true; },
{ name: "http"; host: "localhost"; port: "80"; },
# match BOTH ALPN/SNI
@@ -67,14 +80,18 @@ protocols:
{ 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;},
# Let's Encrypt (tls-sni-* challenges)
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; sni_hostnames: [ "*.*.acme.invalid" ]; log_level: 0;},
# catch anything else TLS
{ name: "tls"; host: "localhost"; port: "443"; },
# 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" ]; },
# Catch-all
{ name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; },
+135 -72
View File
@@ -40,21 +40,22 @@ 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_socks5_protocol(const char *p, int len, struct proto*);
static int is_true(const char *p, int len, struct proto* proto) { return 1; }
/* Table of protocols that have a built-in probe
*/
static struct proto builtins[] = {
/* description service saddr log_level keepalive probe */
{ "ssh", "sshd", NULL, 1, 0, is_ssh_protocol},
{ "openvpn", NULL, NULL, 1, 0, is_openvpn_protocol },
{ "tinc", NULL, NULL, 1, 0, is_tinc_protocol },
{ "xmpp", NULL, NULL, 1, 0, is_xmpp_protocol },
{ "http", NULL, NULL, 1, 0, is_http_protocol },
{ "ssl", NULL, NULL, 1, 0, is_tls_protocol },
{ "tls", NULL, NULL, 1, 0, is_tls_protocol },
{ "adb", NULL, NULL, 1, 0, is_adb_protocol },
{ "anyprot", NULL, NULL, 1, 0, is_true }
/* 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 },
{ "tls", NULL, NULL, 1, 0, 0, is_tls_protocol },
{ "adb", NULL, NULL, 1, 0, 0, is_adb_protocol },
{ "socks5", NULL, NULL, 1, 0, 0, is_socks5_protocol },
{ "anyprot", NULL, NULL, 1, 0, 0, is_true }
};
static struct proto *protocols;
@@ -107,25 +108,25 @@ void hexdump(const char *mem, unsigned int len)
{
/* print offset */
if(i % HEXDUMP_COLS == 0)
printf("0x%06x: ", i);
fprintf(stderr, "0x%06x: ", i);
/* print hex data */
if(i < len)
printf("%02x ", 0xFF & mem[i]);
fprintf(stderr, "%02x ", 0xFF & mem[i]);
else /* end of block, just aligning for ASCII dump */
printf(" ");
fprintf(stderr, " ");
/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
if(j >= len) /* end of block, not really printing */
putchar(' ');
fputc(' ', stderr);
else if(isprint(mem[j])) /* printable char */
putchar(0xFF & mem[j]);
fputc(0xFF & mem[j], stderr);
else /* other char */
putchar('.');
fputc('.', stderr);
}
putchar('\n');
fputc('\n', stderr);
}
}
}
@@ -178,13 +179,16 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto)
* */
static int is_xmpp_protocol( const char *p, int len, struct proto *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,7 +196,7 @@ 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? */
@@ -221,47 +225,99 @@ 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)
{
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;
}
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
static int is_tls_protocol(const char *p, int len, struct proto *proto)
{
if (len < 3)
return PROBE_AGAIN;
/* TLS packet starts with a record "Hello" (0x16), followed by version
* (0x03 0x00-0x03) (RFC6101 A.1)
* This means we reject SSLv2 and lower, which is actually a good thing (RFC6176)
*/
return p[0] == 0x16 && p[1] == 0x03 && ( p[2] >= 0 && p[2] <= 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 is_adb_protocol(const char *p, int len, struct proto *proto)
static int probe_adb_cnxn_message(const char *p)
{
if (len < 30)
return PROBE_AGAIN;
/* The initial ADB host->device packet has a command type of CNXN, and a
* data payload starting with "host:". Note that current versions of the
* client hardcode "host::" (with empty serialno and banner fields) but
* other clients may populate those fields.
*
* We aren't checking amessage.data_length, under the assumption that
* a packet >= 30 bytes long will have "something" in the payload field.
*/
return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5);
}
static int is_adb_protocol(const char *p, int len, struct proto *proto)
{
/* amessage.data_length is not being checked, under the assumption that
* a packet >= 30 bytes will have "something" in the payload field.
*
* 24 bytes for the message header and 5 bytes for the "host:" tag.
*
* ADB protocol:
* https://android.googlesource.com/platform/system/adb/+/master/protocol.txt
*/
static const unsigned int min_data_packet_size = 30;
if (len < min_data_packet_size)
return PROBE_AGAIN;
if (probe_adb_cnxn_message(&p[0]) == PROBE_MATCH)
return PROBE_MATCH;
/* In ADB v26.0.0 rc1-4321094, the initial host->device packet sends an
* empty message before sending the CNXN command type. This was an
* unintended side effect introduced in
* https://android-review.googlesource.com/c/342653, and will be reverted for
* a future release.
*/
static const unsigned char empty_message[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff
};
if (len < min_data_packet_size + sizeof(empty_message))
return PROBE_AGAIN;
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
return PROBE_NEXT;
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
}
static int is_socks5_protocol(const char *p_in, int len, struct proto *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 regex_probe(const char *p, int len, struct proto *proto)
{
#ifdef ENABLE_REGEX
@@ -288,8 +344,8 @@ static int regex_probe(const char *p, int len, struct proto *proto)
int probe_client_protocol(struct connection *cnx)
{
char buffer[BUFSIZ];
struct proto *p;
int n;
struct proto *p, *last_p = cnx->proto;
int n, res, again = 0;
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
/* It's possible that read() returns an error, e.g. if the client
@@ -297,30 +353,41 @@ int probe_client_protocol(struct connection *cnx)
* happens, we just connect to the default protocol so the caller of this
* function does not have to deal with a specific failure condition (the
* connection will just fail later normally). */
if (n > 0) {
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 (verbose > 1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buffer, n);
}
if (res != PROBE_NEXT)
return res;
defer_write(&cnx->q[1], buffer, n);
}
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
for (p = cnx->proto; p; p = p->next) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
if (! p->probe) continue;
/* If none worked, return the first one affected (that's completely
* arbitrary) */
cnx->proto = protocols;
/* Don't probe last protocol if it is anyprot (and store last protocol) */
if (! p->next) {
last_p = p;
if (!strcmp(p->description, "anyprot"))
break;
}
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
if (verbose) fprintf(stderr, "probing for %s: %s\n", p->description, probe_str[res]);
if (res == PROBE_MATCH) {
cnx->proto = p;
return PROBE_MATCH;
}
if (res == PROBE_AGAIN)
again++;
}
if ((again && (n > 0)) || ((n == -1) && (errno == EAGAIN)))
return PROBE_AGAIN;
/* Everything failed: match the last one */
cnx->proto = last_p;
return PROBE_MATCH;
}
@@ -352,10 +419,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"))
+1
View File
@@ -24,6 +24,7 @@ struct proto {
* 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 */
+17 -1
View File
@@ -4,8 +4,24 @@ 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
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target
+23 -4
View File
@@ -94,6 +94,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);
break;
}
}
@@ -143,11 +145,17 @@ void main_loop(int listen_sockets[], int num_addr_listen)
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++) {
if (!(listener_pid[i] = fork())) {
listener_pid[i] = fork();
switch(listener_pid[i]) {
// Log if fork() fails for some reason
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
// 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)
@@ -155,15 +163,26 @@ void main_loop(int listen_sockets[], int num_addr_listen)
in_socket = accept(listen_sockets[i], 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
if (!fork())
{
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);
}
break;
// We're in the parent, we don't need to do anything
default:
break;
}
}
+108 -34
View File
@@ -39,8 +39,8 @@
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> -p <add> [-p <addr> ...] \n" \
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <addr> [-p <addr> ...] \n" \
"%s\n\n" /* Dynamically built list of builtin protocols */ \
"\t[--on-timeout <addr>]\n" \
"-v: verbose\n" \
@@ -48,8 +48,9 @@ const char* USAGE_STRING =
"-f: foreground\n" \
"-n: numeric output\n" \
"-u: specify under which user to run\n" \
"-C: specify under which chroot path to run\n" \
"--transparent: behave as a transparent proxy\n" \
"-F: use configuration file\n" \
"-F: use configuration file (warning: no space between -F and file name!)\n" \
"--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \
"-t: seconds to wait before connecting to --on-timeout address.\n" \
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
@@ -71,6 +72,7 @@ static struct option const_options[] = {
{ "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' },
@@ -78,7 +80,7 @@ static struct option const_options[] = {
};
static struct option* all_options;
static struct proto* builtins;
static const char *optstr = "vt:T:p:VP:F::";
static const char *optstr = "vt:T:p:VP:C:F::";
@@ -123,14 +125,15 @@ static void printsettings(void)
for (p = get_first_protocol(); p; p = p->next) {
fprintf(stderr,
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s]\n",
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s]\n",
p->description,
sprintaddr(buf, sizeof(buf), p->saddr),
p->service,
p->log_level,
p->saddr->ai_family,
p->saddr->ai_addr->sa_family,
p->keepalive ? "keepalive" : "");
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "");
}
fprintf(stderr, "listening on:\n");
for (a = addr_listen; a; a = a->ai_next) {
@@ -144,6 +147,32 @@ static void printsettings(void)
}
/* To removed in v1.21 */
const char* ssl_err_msg = "Usage of 'ssl' setting is deprecated and will be removed in v1.21. Please use 'tls' instead\n";
void ssl_to_tls(char* setting)
{
if (!strcmp(setting, "ssl")) {
strcpy(setting, "tls"); /* legacy configuration */
log_message(LOG_INFO, ssl_err_msg);
}
}
/* Turn 'ssl' command line option to 'tls'. To removed in v1.21 */
void cmd_ssl_to_tls(int argc, char* argv[])
{
int i;
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "--ssl")) {
strcpy(argv[i], "--tls");
/* foreground option not parsed yet, syslog not open, just print on
* stderr and hope for the best */
fprintf(stderr, ssl_err_msg);
}
}
}
/* Extract configuration on addresses and ports on which to listen.
* out: newly allocated list of addrinfo to listen to
*/
@@ -207,14 +236,21 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
p->probe = get_probe("regex");
probe_list = calloc(num_probes + 1, sizeof(*probe_list));
CHECK_ALLOC(probe_list, "calloc");
p->data = (void*)probe_list;
for (i = 0; i < num_probes; i++) {
probe_list[i] = malloc(sizeof(*(probe_list[i])));
CHECK_ALLOC(probe_list[i], "malloc");
expr = config_setting_get_string_elem(probes, i);
res = regcomp(probe_list[i], expr, 0);
if (expr == NULL) {
fprintf(stderr, "%s: invalid probe specified\n", p->description);
exit(1);
}
res = regcomp(probe_list[i], expr, REG_EXTENDED);
if (res) {
err = malloc(errsize = regerror(res, probe_list[i], NULL, 0));
CHECK_ALLOC(err, "malloc");
regerror(res, probe_list[i], err, errsize);
fprintf(stderr, "%s:%s\n", expr, err);
free(err);
@@ -232,13 +268,9 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
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;
const char * config_item, *server_name;
char** sni_hostname_list;
if(!config_items || !config_setting_is_array(config_items)) {
fprintf(stderr, "%s: no %s specified\n", p->description, name);
return;
}
num_probes = config_setting_length(config_items);
if (!num_probes) {
fprintf(stderr, "%s: no %s specified\n", p->description, name);
@@ -247,16 +279,27 @@ static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items,
max_server_name_len = 0;
for (i = 0; i < num_probes; i++) {
server_name_len = strlen(config_setting_get_string_elem(config_items, i));
server_name = config_setting_get_string_elem(config_items, i);
if (server_name == NULL) {
fprintf(stderr, "%s: invalid %s specified\n", p->description, name);
exit(1);
}
server_name_len = strlen(server_name);
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);
CHECK_ALLOC(sni_hostname_list, "calloc");
for (i = 0; i < num_probes; i++) {
config_item = config_setting_get_string_elem(config_items, i);
if (config_item == NULL) {
fprintf(stderr, "%s: invalid %s specified\n", p->description, name);
exit(1);
}
sni_hostname_list[i] = malloc(max_server_name_len);
CHECK_ALLOC(sni_hostname_list[i], "malloc");
strcpy (sni_hostname_list[i], config_item);
if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]);
}
@@ -264,12 +307,20 @@ static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items,
p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list);
}
static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, config_setting_t* alpn_protocols)
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();
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0);
setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1);
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)) {
setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0);
}
if(alpn_protocols && config_setting_is_array(alpn_protocols)) {
setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1);
}
}
#endif
@@ -279,8 +330,9 @@ static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, con
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
{
config_setting_t *setting, *prot, *patterns, *sni_hostnames, *alpn_protocols;
const char *hostname, *port, *name;
config_setting_t *setting, *prot, *patterns;
const char *hostname, *port, *cfg_name;
char* name;
int i, num_prots;
struct proto *p, *prev = NULL;
@@ -289,27 +341,36 @@ static int config_protocols(config_t *config, struct proto **prots)
num_prots = config_setting_length(setting);
for (i = 0; i < num_prots; i++) {
p = calloc(1, sizeof(*p));
CHECK_ALLOC(p, "calloc");
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) &&
if ((config_setting_lookup_string(prot, "name", &cfg_name) &&
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
)) {
/* To removed in v1.21 */
name = strdup(cfg_name);
ssl_to_tls(name);
/* /remove */
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;
}
resolve_split_name(&(p->saddr), hostname, port);
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")) {
if (!p->probe) {
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
exit(1);
}
@@ -324,14 +385,12 @@ static int config_protocols(config_t *config, struct proto **prots)
/* Probe-specific options: SNI/ALPN */
if (!strcmp(name, "tls")) {
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)) || (alpn_protocols && config_setting_is_array(alpn_protocols))) {
setup_sni_alpn(p, sni_hostnames, alpn_protocols);
}
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);
}
}
}
@@ -371,7 +430,10 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto *
return 1;
}
config_lookup_bool(&config, "verbose", &verbose);
if(config_lookup_bool(&config, "verbose", &verbose) == CONFIG_FALSE) {
config_lookup_int(&config, "verbose", &verbose);
}
config_lookup_bool(&config, "inetd", &inetd);
config_lookup_bool(&config, "foreground", &foreground);
config_lookup_bool(&config, "numeric", &numeric);
@@ -387,6 +449,9 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto *
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);
@@ -421,6 +486,7 @@ static void make_alloptions(void)
/* 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));
CHECK_ALLOC(all_options, "calloc");
memcpy(all_options, const_options, sizeof(const_options));
append_protocols(all_options, ARRAY_SIZE(const_options) - 1, builtins, get_num_builtins());
}
@@ -438,6 +504,8 @@ static void cmdline_config(int argc, char* argv[], struct proto** prots)
char *config_filename;
#endif
cmd_ssl_to_tls(argc, argv); /* To remove in v1.21 */
make_alloptions();
#ifdef LIBCONFIG
@@ -499,8 +567,10 @@ next_arg:
if (!prots) {
/* No protocols yet -- create the list */
p = prots = calloc(1, sizeof(*p));
CHECK_ALLOC(p, "calloc");
} else {
p->next = calloc(1, sizeof(*p));
CHECK_ALLOC(p->next, "calloc");
p = p->next;
}
memcpy(p, &builtins[c-PROT_SHIFT], sizeof(*p));
@@ -547,6 +617,10 @@ next_arg:
pid_file = optarg;
break;
case 'C':
chroot_path = optarg;
break;
case 'v':
verbose++;
break;
@@ -591,6 +665,7 @@ int main(int argc, char *argv[])
/* Init defaults */
pid_file = NULL;
user_name = NULL;
chroot_path = NULL;
cmdline_config(argc, argv, &protocols);
parse_cmdline(argc, argv, protocols);
@@ -629,13 +704,12 @@ int main(int argc, char *argv[])
if (pid_file)
write_pid_file(pid_file);
if (user_name)
drop_privileges(user_name);
/* Open syslog connection */
/* Open syslog connection before we drop privs/chroot */
setup_syslog(argv[0]);
if (user_name || chroot_path)
drop_privileges(user_name, chroot_path);
if (verbose)
printcaps();
+138 -18
View File
@@ -27,6 +27,8 @@
const char* server_type = "sslh-select";
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
/* 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
@@ -61,9 +63,9 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
if (verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
close(cnx->q[i].fd);
FD_CLR(cnx->q[i].fd, fds);
FD_CLR(cnx->q[i].fd, fds2);
close(cnx->q[i].fd);
if (cnx->q[i].deferred_data)
free(cnx->q[i].deferred_data);
}
@@ -93,11 +95,16 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_
in_socket = accept(listen_socket, 0, 0);
CHECK_RES_RETURN(in_socket, "accept");
if (!fd_is_in_range(in_socket))
if (!fd_is_in_range(in_socket)) {
close(in_socket);
return -1;
}
res = set_nonblock(in_socket);
if (res == -1) return -1;
if (res == -1) {
close(in_socket);
return -1;
}
/* Find an empty slot */
for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) {
@@ -109,6 +116,7 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_
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;
@@ -140,9 +148,9 @@ int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
flush_deferred(q);
if (q->deferred_data) {
FD_SET(q->fd, fds_w);
} else {
FD_SET(q->fd, fds_r);
FD_CLR(cnx->q[0].fd, fds_r);
}
FD_SET(q->fd, fds_r);
return q->fd;
} else {
tidy_connection(cnx, fds_r, fds_w);
@@ -180,6 +188,93 @@ 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)
{
fd_set fds_r, fds_w;
int res, i;
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
FD_ZERO(&fds_r);
FD_ZERO(&fds_w);
while (1) {
for (i = 0; i < 2; i++) {
if (cnx->q[i].deferred_data_size) {
FD_SET(cnx->q[i].fd, &fds_w);
FD_CLR(cnx->q[1-i].fd, &fds_r);
} else {
FD_CLR(cnx->q[i].fd, &fds_w);
FD_SET(cnx->q[1-i].fd, &fds_r);
}
}
res = select(
max_fd,
&fds_r,
&fds_w,
NULL,
NULL
);
CHECK_RES_DIE(res, "select");
for (i = 0; i < 2; i++) {
if (FD_ISSET(cnx->q[i].fd, &fds_w)) {
res = flush_deferred(&cnx->q[i]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (verbose)
fprintf(stderr, "%s socket closed\n", i ? "server" : "client");
return;
}
}
if (FD_ISSET(cnx->q[i].fd, &fds_r)) {
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
if (!res) {
if (verbose)
fprintf(stderr, "socket closed\n");
return;
}
}
}
}
}
/* Child process that makes internal connection and proxies
*/
void connect_proxy(struct connection *cnx)
{
int in_socket;
int out_socket;
/* Minimize the file descriptor value to help select() */
in_socket = dup(cnx->q[0].fd);
if (in_socket == -1) {
in_socket = cnx->q[0].fd;
} else {
close(cnx->q[0].fd);
cnx->q[0].fd = in_socket;
}
/* Connect the target socket */
out_socket = connect_addr(cnx, in_socket);
CHECK_RES_DIE(out_socket, "connect");
cnx->q[1].fd = out_socket;
log_connection(cnx);
shovel_single(cnx);
close(in_socket);
close(out_socket);
if (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)
{
@@ -206,7 +301,8 @@ void main_loop(int listen_sockets[], int num_addr_listen)
fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
fd_set readfds, writefds; /* working read and write fd sets */
struct timeval tv;
int max_fd, in_socket, i, j, res;
int max_fd, i, j, res;
int in_socket = 0;
struct connection *cnx;
int num_cnx; /* Number of connections in *cnx */
int num_probing = 0; /* Number of connections currently probing
@@ -226,6 +322,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
cnx = malloc(num_cnx * sizeof(struct connection));
CHECK_ALLOC(cnx, "malloc");
for (i = 0; i < num_cnx; i++)
init_cnx(&cnx[i]);
@@ -248,15 +345,12 @@ void main_loop(int listen_sockets[], int num_addr_listen)
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 != -1)
num_probing++;
if (in_socket > 0) {
num_probing++;
FD_SET(in_socket, &fds_r);
if (in_socket >= max_fd)
max_fd = in_socket + 1;;
}
FD_CLR(listen_sockets[i], &readfds);
}
}
@@ -305,6 +399,10 @@ void main_loop(int listen_sockets[], int num_addr_listen)
* 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)
@@ -314,14 +412,36 @@ void main_loop(int listen_sockets[], int num_addr_listen)
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 {
res = connect_queue(&cnx[i], &fds_r, &fds_w);
}
/* 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;;
+19 -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<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [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<--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]
=head1 DESCRIPTION
@@ -58,7 +58,7 @@ expressions, B<sslh> connects to that protocol.
=head2 Probing protocols
When receiving an incoming connection, B<sslh> will read the
first bytes sent be the connecting client. It will then
first bytes sent by the connecting client. It will then
probe for the protocol in the order specified on the command
line (or the configuration file). Therefore B<--anyprot>
should alway be used last, as it always succeeds and further
@@ -78,12 +78,15 @@ connections and LOG_ERR for failures.
=over 4
=item B<-F> I<filename>, B<--config> I<filename>
=item B<-F>I<filename>, B<--config> I<filename>
Uses I<filename> has configuration file. If other
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
@@ -92,7 +95,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>
@@ -123,7 +130,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 to RFC6176 which prohibits the usage of SSLv2. If
compliant with RFC6176 which prohibits the usage of SSLv2. If
you wish to accept SSLv2, use B<--default> instead.
=item B<--ssh> I<target address>
@@ -181,6 +188,10 @@ Prints B<sslh> version.
Requires to run under the specified username.
=item B<-C> I<chroot>, B<--chroot> I<chroot>
Requires to run under the specified chroot.
=item B<-P> I<pidfile>, B<--pidfile> I<pidfile>
Specifies a file in which to write the PID of the main
@@ -226,10 +237,10 @@ detailed explanation of the variables used by B<sslh>.
=head1 SEE ALSO
Last version available from
The latest version is available from
L<http://www.rutschle.net/tech/sslh>, and can be tracked
from L<http://freecode.com/projects/sslh>.
=head1 AUTHOR
Written by Yves Rutschle
Written by Yves Rutschle.
+107 -107
View File
@@ -2,13 +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);
/* Need room in the strcat for \0 and :
* the format in the socket unit file is hostname:port */
char *conn = malloc(strlen(hostname)+strlen(port)+2);
CHECK_ALLOC(conn, "malloc");
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
@@ -18,136 +19,135 @@ static char* resolve_listen(const char *hostname, const char *port) {
}
static int get_listen_from_conf(const char *filename, char **listen) {
static int get_listen_from_conf(const char *filename, char **listen[]) {
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
return -1;
}
} else {
setting = config_lookup(&config, "listen");
if (setting) {
len = config_setting_length(setting);
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
return -1;
} else {
listen[i] = malloc(strlen(resolve_listen(hostname, port)));
strcpy(listen[i], resolve_listen(hostname, port));
}
}
} else {
setting = config_lookup(&config, "listen");
if (setting) {
int i;
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
CHECK_ALLOC(*listen, "malloc");
for (i = 0; i < len; i++) {
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;
} else {
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
CHECK_ALLOC((*listen)[i], "malloc");
strcpy((*listen)[i], resolve_listen(hostname, port));
}
}
}
}
}
return len;
return len;
}
static int write_socket_unit(FILE *socket, char **listen, int num_addr, const char *source) {
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
int i;
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"SourcePath=%s\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"SourcePath=%s\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
for (i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
return 0;
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
}
}
}
fclose(config);
fclose(config);
num_addr = get_listen_from_conf(sslh_conf, &listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir && *runtime_unit_dir) {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
CHECK_ALLOC(runtime_conf, "malloc");
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
}
num_addr = get_listen_from_conf(sslh_conf, listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir != "") {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
}
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}
+177 -118
View File
@@ -2,27 +2,30 @@
# 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 $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
@@ -32,22 +35,158 @@ 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;
`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: '";
# 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: protocol $prefix data [$data]\n";
close $cnx;
$opts{expected} =~ s/^ssl/tls/; # to remove in 1.21
is($prefix, $opts{expected}, "probe $opts{expected} connected correctly");
is($data, $opts{data}, "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 (%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 },
'ssl' => {
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello ssl alone"
},
'tls' => {
# Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`)
data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn",
# 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" },
'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};
$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
);
}
}
} 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');
for my $binary (@binaries) {
warn "Testing $binary\n";
@@ -56,10 +195,10 @@ for my $binary (@binaries) {
my $sslh_pid;
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 -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
my $cmd = "./$binary -v -f -u $user -Ftest.cfg";
verbose_exec $cmd;
#exec "valgrind --leak-check=full ./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
exit 0;
}
warn "spawned $sslh_pid\n";
@@ -67,21 +206,7 @@ for my $binary (@binaries) {
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) {
@@ -96,35 +221,6 @@ for my $binary (@binaries) {
}
}
# 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";
@@ -142,7 +238,7 @@ for my $binary (@binaries) {
}
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection interrupted by SSH");
is($data, "tls: $ssl_test_data", "SSL connection interrupted by SSH");
}
}
@@ -159,7 +255,7 @@ 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");
is($data, "tls: $ssl_test_data", "SSL during SSH being established");
}
print $cnx_h $test_data;
my $data = <$cnx_h>;
@@ -168,53 +264,12 @@ for my $binary (@binaries) {
}
# 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);
}
# 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;
}
my $pid = `cat $pidfile`;
@@ -228,7 +283,6 @@ 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";
}
warn "spawned $sslh_pid\n";
@@ -249,12 +303,19 @@ 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";
}
warn "spawned $sslh_pid\n";
@@ -269,7 +330,6 @@ 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";
}
warn "spawned $sslh_pid\n";
@@ -284,7 +344,6 @@ 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";
# You don't have a /dont_exist/ directory, do you?!
}
+42
View File
@@ -0,0 +1,42 @@
# Configuration file for testing (use both by sslh under
# test and the test script `t`)
verbose: 2;
foreground: true;
inetd: false;
numeric: false;
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; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; fork: 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: "tls"; host: "localhost"; port: "9021"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni1" ]; },
{ name: "ssl"; 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: "ssl"; host: "localhost"; port: "9025"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on-timeout: "ssh";
+62 -84
View File
@@ -30,6 +30,7 @@
*/
#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include <fnmatch.h> /* fnmatch() */
#include "tls.h"
#define TLS_HEADER_LEN 5
@@ -40,9 +41,13 @@
#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;
TLS_MATCHMODE match_mode;
char** sni_hostname_list;
char** alpn_protocol_list;
};
@@ -56,12 +61,9 @@ static int has_match(char**, const char*, size_t);
* 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) {
@@ -73,12 +75,12 @@ 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;
return TLS_EPROTOCOL;
}
tls_version_major = data[1];
@@ -87,7 +89,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
if (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 */
@@ -97,18 +99,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");
return -5;
return TLS_EPROTOCOL;
}
/* Skip past fixed length records:
@@ -122,46 +124,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;
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) {
@@ -170,51 +180,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 */
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 */
@@ -222,9 +198,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
@@ -237,14 +215,14 @@ 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)) {
return len;
} else {
return -2;
return TLS_ENOEXT;
}
default:
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
@@ -254,9 +232,9 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return TLS_ENOEXT;
}
static int
@@ -268,7 +246,7 @@ 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)) {
return len;
@@ -279,30 +257,36 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
}
/* 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;
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);
if(!strncmp(*item, name, name_len)) {
if(!fnmatch(*item, name_nullterminated, 0)) {
free(name_nullterminated);
return 1;
}
}
free(name_nullterminated);
return 0;
}
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;
}
@@ -311,16 +295,10 @@ struct TLSProtocol *
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, char** list) {
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->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->match_mode.tls_match_sni = 1;
}
return tls_data;
+10
View File
@@ -35,4 +35,14 @@ int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_
struct TLSProtocol *new_tls_data();
struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, char**);
#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