Compare commits

...

126 Commits

Author SHA1 Message Date
Travis Burtrum 8af039d3eb Add ALPN protocol based probe 2016-01-05 00:32:10 -05:00
Yves Rutschle 8758a298ba Changed connection log to include the name of the probe that triggered. 2015-12-15 16:06:14 +01:00
Yves Rutschle 2cb424c646 Added log_level option to configuration file, which switches off log at each connection 2015-12-15 15:51:18 +01:00
yrutschle 24612835c3 Merge pull request #70 from taligentx/master
Fix daemon start syntax
2015-10-20 10:58:58 +02:00
Nikhil Choudhary b09c3aab31 Fix daemon start syntax
Per the changelog for 1.17: argument to -F can no longer be separated from the option by a space, e.g. must be -Ffoo.cfg).  This fixes the `/etc/sslh/sslh.cfg:file I/O error` message if the config file is stored in `/etc/sslh.cfg` as given by Readme.md.
2015-10-19 13:40:51 -05:00
yrutschle 389ab9fbff Merge pull request #67 from eehakkin/master
Fix access rights checking to work with IPv6.
2015-09-28 14:59:16 +02:00
Eero Häkkinen 717c285b31 Fix access rights checking to work with IPv6. 2015-09-27 22:14:00 +03:00
Yves Rutschle 4cbaf447b5 Print error message upon non-existent configuration file 2015-07-28 15:14:21 +02:00
Yves Rutschle ca461ea077 Added support for RFC4366 SNI (Server Name Indication). Changed configuration file format accordingly. 2015-07-17 15:05:06 +02:00
Yves Rutschle 8fdaf6eb08 changed configuration file to accomodate SNI in a cleaner way 2015-07-17 15:04:04 +02:00
Yves Rutschle 5886bd2d43 Print error message upon non-existent configuration file 2015-07-16 17:43:05 +02:00
Yves Rutschle 77ef29358d make code C-compliant 2015-07-15 15:09:39 +02:00
Yves Rutschle 9475d9689b Comment for SNI inclusion 2015-07-15 15:02:37 +02:00
Yves Rutschle fbebdaf66c Add support for Server Name Indication (SNI, RFC4366) 2015-07-15 14:07:16 +02:00
Yves Rutschle fecfb170c8 added reference to Tinc documentation 2015-07-15 13:34:53 +02:00
Travis Burtrum b988540105 Add SNI hostname based probe 2015-07-12 23:10:53 -04:00
Yves Rutschle 3aefaf3004 Added Makefile option to build without libpcre 2015-07-09 15:31:42 +02:00
yrutschle ba945f1a8f Merge pull request #52 from Somasis/master
Makefile: use more variables for install directories
2015-06-22 11:59:52 +02:00
Kylie McClain eb3d3be3ab Makefile: use more variables for install directories 2015-06-22 01:51:36 -04:00
yrutschle 66f85dc608 Merge pull request #51 from cernekee/adb
Add builtin handler for Android Debug Bridge (ADB) protocol
2015-06-10 09:26:55 +02:00
Kevin Cernekee 3469f56012 Add builtin handler for Android Debug Bridge (ADB) protocol
This allows Android devices to run multiple services on one port.  A
common use case involves muxing SSH for SCP / SFTP, and ADB for
sideloading packages or running CTS.

Signed-off-by: Kevin Cernekee <cernekee@chromium.org>
2015-06-09 15:30:27 -07:00
yrutschle 7c35ef8528 Merge pull request #49 from dunn/master
Makefile: fix `install` for Mac OS
2015-05-18 11:12:45 +02:00
Alex Dunn 3bad96865d Makefile: fix `install` for Mac OS 2015-05-14 23:14:40 -07:00
yrutschle 728181109c Merge pull request #47 from hogarthj/master
Build fails in CentOS6 - this fixes it
2015-04-18 10:35:02 +02:00
James Hogarth 2192b28303 Check line number of error so that this works with libconfig-1.3.2 in CentOS6 2015-04-17 23:40:57 +01:00
yrutschle 77a74f0c52 Merge pull request #45 from muxator/patch-1
typo: "transparent proying" -> "transparent proxying"
2015-03-15 12:01:58 +01:00
muxator 48164c4d77 typo: "transparent proying" -> "transparent proxying" 2015-03-15 00:44:39 +01:00
Yves Rutschle 3550cbe77c Finalised v1.17 2015-03-09 21:51:39 +01:00
yrutschle 130348ed48 Merge pull request #44 from antisocialdalek/antisocialdalek-fix-xmpp
add longer check for xmpp preamble
2015-03-09 21:46:18 +01:00
Justin Matlock bdeccfd9ff add longer check for xmpp preamble
original wasn't catching the preamble from Adium or Pidgin XMPP clients, because of a newline after the initial <xml> line. Grew the length of the check string so it'd see the word 'jabber' faster.
2015-03-06 02:58:52 -05:00
yrutschle ce7c5b1ba2 Merge pull request #41 from gapato/patch-1
Fix typo for CentOS install instructions in README
2015-02-04 14:00:12 +01:00
gapato 21552fc176 Fix typo for CentOS install instructions in README 2015-02-04 12:53:32 +01:00
Yves Rutschle 88af6ebaee Updated configuration info and startup scripts to use the configuration file rather than command line parameters 2015-01-01 18:59:05 +01:00
Yves Rutschle 43d2db9123 Fix libconfig issue with integer parameters on x64 2015-01-01 18:35:26 +01:00
Yves Rutschle d91cd59bba Documented configuration trick to have both transparent proxying while still retaining the ability to connect to ssh directly 2015-01-01 18:31:10 +01:00
Yves Rutschle c03168042f Added IPv6 transparent proxying instructions 2014-12-31 14:53:59 +01:00
Yves Rutschle 2705426f63 Attribute previous commit 2014-12-27 19:51:20 +01:00
Yves Rutschle 0458c9840b Use portable way of getting modified time 2014-12-27 19:49:51 +01:00
Yves Rutschle bb4aeb446a Use default configuration filename 2014-12-27 11:57:27 +01:00
Yves Rutschle 74de4f4fd2 Transparent proxy support for FreeBSD (attribution) 2014-12-25 20:15:52 +01:00
Yves Rutschle 56fdc6b4af Transparant proxy support for FreeBSD 2014-12-25 20:08:24 +01:00
yrutschle b6f4c04c36 Merge pull request #25 from guikcd/remove_cant_bind_address_test
Disable Can't bind to address test since IP_FREEBIND allow us to do that
2014-12-25 19:57:47 +01:00
Yves Rutschle b9ddfb4c7a Support RFC5952-style IPv6 addresses 2014-12-22 18:19:02 +01:00
Aaron Madlon-Kay 8c3362e9ce Use portable way of getting modified time 2014-11-22 23:46:50 +09:00
Ruben van Staveren ece6e28e45 #ifdef IP_BINDANY/IPV6_BINDANY cases 2014-07-24 17:29:53 +02:00
Ruben van Staveren 0d8e2438de Correct markdown 2014-07-22 21:43:03 +02:00
Ruben van Staveren 36cf99697b Add instruction for FreeBSD 2014-07-22 20:30:52 +02:00
Ruben van Staveren ddc1efed89 Merge branch 'freebsd_transparent' of https://github.com/rvstaveren/sslh into freebsd_transparent 2014-07-22 20:06:32 +02:00
Ruben van Staveren e2fc091482 When transparent, make sure both connections use the same address family 2014-07-22 20:05:25 +02:00
Ruben van Staveren 42425a8373 Have USELIBWRAP redefineable 2014-07-22 20:05:25 +02:00
Ruben van Staveren e246536be2 FreeBSD way of doing transparent proxy: work in progress 2014-07-22 20:05:25 +02:00
Ruben van Staveren 7d23a55236 When transparent, make sure both connections use the same address family 2014-07-22 19:36:40 +02:00
Ruben van Staveren dedb3672d7 Have USELIBWRAP redefineable 2014-07-22 19:36:29 +02:00
Guillaume Delacour 21a6d3c3ae Disable Can't bind to address test since IP_FREEBIND allow us to do that 2014-07-15 16:22:37 +02:00
Yves Rutschle 9a0a9b9492 Clarified that sslh uses LOG_AUTH facility for logging in manual page 2014-07-15 11:26:16 +02:00
Ruben van Staveren b6de2904f0 FreeBSD way of doing transparent proxy: work in progress 2014-06-20 14:11:25 +02:00
Yves Rutschle d10b539a5a fixed obsolete README reference to -o option 2014-04-19 13:10:12 +02:00
Yves Rutschle 48d4d81e0c minor corrections to usage string 2014-04-19 10:41:17 +02:00
Yves Rutschle 36e05640c0 added -F description to man page 2014-04-19 10:40:53 +02:00
Ondřej Kuzník 7876bddff3 Fix regex probes always matching (#19) 2014-04-09 19:18:52 +01:00
Yves Rutschle 6fb234f85e added fail2ban configuration examples 2014-03-30 18:51:21 +02:00
Yves Rutschle 7d6cac73d4 added transparent option to man page and help 2014-03-30 18:25:03 +02:00
Yves Rutschle 621f0718dd added license file 2014-03-30 18:09:16 +02:00
Yves Rutschle 426797f9c0 call setgroups before setgid 2014-03-30 17:28:00 +02:00
Yves Rutschle 53550ff21e fix errors in previous commit... 2014-02-24 17:52:58 +01:00
Yves Rutschle 9beacc63f9 use directory version when compiling from a tarball without git 2014-02-23 10:41:47 +01:00
Jason Cooper 62cbb55b8e genver.sh: use /bin/sh for portability
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2014-02-17 19:08:02 +01:00
yrutschle 27567c4804 Merge pull request #14 from belobrov-andrey/fd_leak_fix
Fixed possible file descriptor leak.
2014-02-16 13:11:19 +01:00
Belobrov Andrey ff070a6b46 Fixed possible file descriptor leak. 2014-02-14 08:32:38 +04:00
Yves Rutschle 9d2deff6ad Changelog prepared for v1.16 2014-02-11 22:06:01 +01:00
Yves Rutschle 6bcb5c83f2 libcap support: print out process capabilities at startup if verbose 2014-02-09 21:39:27 +01:00
Yves Rutschle 2d3b6c4abd fix Markdown documentation for libcap 2014-02-09 20:50:03 +01:00
Yves Rutschle 4dfa694e8a Merged libcap patch 2014-02-09 20:34:26 +01:00
yrutschle e6318ddde0 Merge pull request #12 from vapier/master
sslh-fork: close all listening sockets in shoveler
2014-02-09 13:59:10 +01:00
Yves Rutschle 67c34a7460 set IP_FREEBIND if available to bind to non-existent interfaces 2014-02-09 13:29:49 +01:00
Mike Frysinger 71ce82815c sslh-fork: close all listening sockets in shoveler
When we're watching multiple sockets, we don't want to just close
the active one we got a connection on before launching the shoveler.
If we want to restart the daemon, we run into problems because the
socket is still in use.  Instead, close all the sockets we were
listening on.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
2014-01-09 10:16:42 -05:00
Yves Rutschle 5998c9ec1a Do not require --listen when --inetd is specified 2014-01-06 22:21:44 +01:00
Yves Rutschle 45996cc1ee minor typesetting fix to manual page 2014-01-06 22:07:33 +01:00
Yves Rutschle 56944e4d38 Generate version tag based on file modification date if git is not present 2013-11-23 16:46:54 +01:00
yrutschle 9c3a838cc5 Merge pull request #8 from nbraud/readme
Markdownify the README
2013-11-05 23:10:24 -08:00
Nicolas Braud-Santoni b24f9820f9 Markdownify the README 2013-11-05 22:34:48 +01:00
Sebastian Schmidt 009faa64b7 Implement libcap support
Use libcap for saving CAP_NET_ADMIN (if --transparent is given) over a
setuid(). We don’t need CAP_NET_BIND_SERVICE as the listening sockets
are established before dropping root.
2013-10-20 21:16:56 +02:00
Yves Rutschle 3f386b6541 initiated TODO list 2013-10-06 12:09:52 +02:00
Yves Rutschle fb0760dd72 Probes made resilient to packets that are too short, or
contain NULLs.
2013-09-28 21:39:00 +02:00
Yves Rutschle f2ca4c13a6 ChangeLog entry for the branch 2013-09-28 21:38:33 +02:00
Yves Rutschle 96f5d6387e new test for PROBE_AGAIN; changed deferred_data to begin_deferred_data where appropriate 2013-09-28 21:33:25 +02:00
Ondrej Kuznk 025545aee3 Fix typos and type warnings 2013-09-28 20:49:46 +02:00
Ondřej Kuzník d14dcdee5c Fix build issues when version.h doesn't exist yet 2013-09-28 20:44:08 +02:00
Ondřej Kuzník 66c7d674a0 is a bashism 2013-09-28 20:42:05 +02:00
Ondřej Kuzník e4fb8b8496 defered -> deferred 2013-09-28 20:42:04 +02:00
Ondřej Kuzník d7bbec0dc7 Simplify function signatures 2013-09-28 20:21:48 +02:00
Ondřej Kuzník bcad6fbade Enable the PROBE_AGAIN return code 2013-09-28 20:21:47 +02:00
Ondřej Kuzník dbafd6510d Allow probes to say they cannot decide yet 2013-09-28 20:21:47 +02:00
Ondřej Kuzník c84a6af847 Introduce the probe return codes. 2013-09-28 20:21:47 +02:00
Ondřej Kuzník c5cd91d92c Let defer_write accumulate data 2013-09-28 20:21:47 +02:00
Ondřej Kuzník 708c3b0177 Make probes work even in the face of arbitrary data 2013-09-28 20:21:47 +02:00
Yves Rutschle ce170814f5 fix genver.sh shell version to bash 2013-09-19 09:25:35 +02:00
Yves Rutschle a168461f46 Merged Makefile LDFLAGS changes 2013-09-17 11:04:37 +02:00
Yves Rutschle 5952ca4aaf Make version.h before any other object 2013-09-17 11:01:05 +02:00
Yves Rutschle a54cc1aa83 Make version.h before any other object 2013-09-17 08:41:10 +02:00
Mike Frysinger 2d23cdc9f4 check asprintf return value
The current asprintf usage triggers many warnings like:

sslh-main.c: In function 'print_usage':
sslh-main.c:86:17: warning: ignoring return value of 'asprintf',
	declared with attribute warn_unused_result [-Wunused-result]

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
2013-09-17 00:26:44 -04:00
Mike Frysinger b8ea0699c4 drop_privileges: fix setuid check
The code attempts to check the return of setuid, but forgets to assign
the result variable.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
2013-09-17 00:23:26 -04:00
Mike Frysinger c54e232673 sslh-main: fix config_lookup_int call
This func takes an int, not a long.  The current code triggers a warning:

sslh-main.c: In function 'config_parse':
sslh-main.c:275:5: warning: passing argument 3 of 'config_lookup_int' from incompatible pointer type [enabled by default]
     if (config_lookup_int(&config, "timeout", &timeout) == CONFIG_TRUE) {
                                               ^
In file included from sslh-main.c:26:0:
/usr/include/libconfig.h:266:12: note: expected 'int *' but argument is of type 'long int *'
 extern LIBCONFIG_API int config_lookup_int(const config_t *config,

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
2013-09-17 00:21:37 -04:00
Mike Frysinger 8252ecf307 Makefile: fix CPPFLAGS handling
This code doesn't respect CPPFLAGS at all.  Fix that and move the
existing -D flags to the right variable.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
2013-09-17 00:18:15 -04:00
Mike Frysinger 4fafb3d376 Makefile: fix LDFLAGS handling
We need these flags to come before all the objects, not after.
Otherwise, flags that impact handling of input objects do not
show up in time.

This also matches standard build system behavior (e.g. autotools).

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
2013-09-17 00:17:23 -04:00
Jason Cooper 7008a1ede4 cscope: add cscope tagging support
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2013-09-16 22:02:29 +02:00
Jason Cooper 820e31bfc0 Makefile: add distclean target, remove tags file
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2013-09-16 22:01:47 +02:00
Jason Cooper f36eb7be39 version.h: dynamically create version number based on git
When building the source from a checked out tag, eg v1.15, VERSION will
equal v1.15.  However, when building from anything other than a tagged
version, you get 'v1.15-4-g50432d5-dirty' meaning I was 4 patches in
front of v1.15, particularly '50432d5' was my current HEAD, and I had
uncommited changes, '-dirty'.

Very useful for folks submitting bug reports on versions they compiled
themselves.

Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2013-09-16 21:56:45 +02:00
Jason Cooper c6adb6a1e1 remove unneeded executable permissions on source files
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2013-09-16 21:56:38 +02:00
Jason Cooper 97ffa562ce git: add .gitignore file
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2013-09-16 21:55:26 +02:00
yrutschle 7afccc6565 Merge pull request #4 from julthomas/jth/rhel-init
MINOR: init: Review RH/CentOS init script
2013-08-30 02:53:18 -07:00
yrutschle 22f4a4bc47 Merge pull request #3 from julthomas/jth/config-transparent
MINOR: config: Option --transparent can be set via configuration file
2013-08-30 02:52:24 -07:00
Yves Rutschle f3c5f098ca fixed getpeername causing sslh to quit 2013-08-29 12:15:50 +02:00
Julien Thomas 5ae9ba184c MINOR: init: Review RH/CentOS init script
This is an update of the init scripts originally written by Andre
Krajnik. It is quite similar to other init scripts brought by common
packages in RH/CentOS. This commit also introduces a pretty straight
forward sysconfig file.
2013-08-26 21:11:17 +02:00
Julien Thomas 43a9bc8fd9 MINOR: config: Option --transparent can be set via configuration file
This patch allows to set option --transparent in an SSLH configuration
file. Without it, transparent mode is only possible by passing the
option on the command line.
2013-08-26 21:07:27 +02:00
yrutschle 569c71f6b1 Merge pull request #1 from cicku/patch-1
Fix for RPM distdir build
2013-08-10 01:22:08 -07:00
Christopher Meng bde20dbaa5 Fix for RPM
- RPM doesn't support root, so for the chroot environment we must define a destdir of RPM %{buildroot}.

- Preserve the timestamp.
2013-08-10 15:06:49 +08:00
Yves Rutschle c60696a6d5 Updated Fedora package requirements 2013-08-09 20:18:22 +02:00
Yves Rutschle c02e2d7aee v1.15 release 2013-07-27 16:25:04 +02:00
Yves Rutschle 59c9be54ad Set FD_SETSIZE to 4096 on Cygwin 2013-07-26 18:42:22 +01:00
Yves Rutschle e3159409c0 check fd < FD_SETSIZE 2013-07-25 21:35:27 +02:00
Yves Rutschle 536f7dee83 Changed SOL_IP to more-portable IPPROTO_IP 2013-07-25 21:33:07 +02:00
Yves Rutschle 2781c75ff9 Added tranparent proyxing 2013-07-21 13:46:45 +02:00
Yves Rutschle d02ffcd154 Fixed bug in sslh-select: if socket dropped while defered_data was present, sslh-select would crash. 2013-07-20 00:45:33 +02:00
Yves Rutschle f842e2e081 v1.14: 21DEC2012
Corrected OpenVPN probe to support pre-shared secret
	mode (OpenVPN port-sharing code is... wrong). Thanks
	to Kai Ellinger for help in investigating and
	testing.

	Added an actual TLS/SSL probe.

	Added configurable --on-timeout protocol
	specification.

	Added a --anyprot protocol probe (equivalent to what
	--ssl was).

	Makefile respects the user's compiler and CFLAG
	choices (falling back to the current values if
	undefined), as well as LDFLAGS.
	(Michael Palimaka)

	Added "After" and "KillMode" to systemd.sslh.service
	(Thomas Weischuh).

	Added LSB tags to etc.init.d.sslh
	(Thomas Varis).
2013-07-10 23:19:33 +02:00
Yves Rutschle 5cd1fa1875 v1.13: 18MAY2012
Write PID file before dropping privileges.

	Added --background, which overrides 'foreground'
	configuration file setting.

	Added example systemd service file from Archlinux in
	scripts/
	https://projects.archlinux.org/svntogit/community.git/tree/trunk/sslh.service?h=packages/sslh
	(Sbastien Luttringer)
2013-07-10 23:16:50 +02:00
29 changed files with 2351 additions and 591 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
*.swp
*.swo
*.o
cscope.*
echosrv
sslh-fork
sslh-select
sslh.8.gz
tags
version.h

339
COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

113
ChangeLog
View File

@ -1,3 +1,116 @@
vNEXT:
Added USELIBPCRE to make use of regex engine
optional.
Added support for RFC4366 SNI and RFC7301 ALPN
(Travis Burtrum)
Changed connection log to include the name of the probe that
triggered.
Changed configuration file format: 'probe' field is
no longer required, 'name' field can now contain
'tls' or 'regex', with corresponding options (see
example.cfg)
Added 'log_level' option to each protocol, which
allows to turn off generation of log at each
connection.
v1.17: 09MAR2015
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
Transparant proxy support for FreeBSD.
(Ruben van Staveren)
Using -F with no argument will try
/etc/sslh/sslh.cfg and then /etc/sslh.cfg as
configuration files. (argument to -F can no longer
be separated from the option by a space, e.g. must
be -Ffoo.cfg)
Call setgroups() before setgid() (fixes potential
privilege escalation).
(Lars Vogdt)
Use portable way of getting modified time for OSX
support.
(Aaron Madlon-Kay)
Example configuration for fail2ban.
(Every Mouw)
v1.16: 11FEB2014
Probes made more resilient, to incoming data
containing NULLs. Also made them behave properly
when receiving too short packets to probe on the
first incoming packet.
(Ondrej Kuzník)
Libcap support: Keep only CAP_NET_ADMIN if started
as root with transparent proxying and dropping
priviledges (enable USELIBCAP in Makefile). This
avoids having to mess with filesystem capabilities.
(Sebastian Schmidt/yath)
Fixed bugs related to getpeername that would cause
sslh to quit erroneously (getpeername can return
actual errors if connections are dropped before
getting to getpeername).
Set IP_FREEDBIND if available to bind to addresses
that don't yet exist.
v1.15: 27JUL2013
Added --transparent option for transparent proxying.
See README for iptables magic and capability
management.
Fixed bug in sslh-select: if number of opened file
descriptor became bigger than FD_SETSIZE, bad things
would happen.
Fixed bug in sslh-select: if socket dropped while
deferred_data was present, sslh-select would crash.
Increased FD_SETSIZE for Cygwin, as the default 64
is too low for even moderate load.
v1.14: 21DEC2012
Corrected OpenVPN probe to support pre-shared secret
mode (OpenVPN port-sharing code is... wrong). Thanks
to Kai Ellinger for help in investigating and
testing.
Added an actual TLS/SSL probe.
Added configurable --on-timeout protocol
specification.
Added a --anyprot protocol probe (equivalent to what
--ssl was).
Makefile respects the user's compiler and CFLAG
choices (falling back to the current values if
undefined), as well as LDFLAGS.
(Michael Palimaka)
Added "After" and "KillMode" to systemd.sslh.service
(Thomas Weißschuh).
Added LSB tags to etc.init.d.sslh
(Thomas Varis).
v1.13: 18MAY2012
Write PID file before dropping privileges.
Added --background, which overrides 'foreground'
configuration file setting.
Added example systemd service file from Archlinux in
scripts/
https://projects.archlinux.org/svntogit/community.git/tree/trunk/sslh.service?h=packages/sslh
(Sébastien Luttringer)
v1.12: 08MAY2012
Added support for configuration file.

View File

@ -1,10 +1,14 @@
# Configuration
VERSION="v1.12"
VERSION=$(shell ./genver.sh -r)
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBWRAP= # Use libwrap?
USELIBPCRE=1 # Use libpcre? (necessary to use regex probe)
USELIBWRAP?= # Use libwrap?
USELIBCAP= # Use libcap?
COV_TEST= # Perform test coverage?
PREFIX=/usr/local
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
MANDIR?=$(PREFIX)/share/man/man8
MAN=sslh.8.gz # man page name
@ -15,66 +19,89 @@ ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC = gcc
CFLAGS=-Wall -g $(CFLAGS_COV)
CC ?= gcc
CFLAGS ?=-Wall -g $(CFLAGS_COV)
LIBS=
OBJS=common.o sslh-main.o probe.o
OBJS=common.o sslh-main.o probe.o tls.o
ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap
CFLAGS:=$(CFLAGS) -DLIBWRAP
CPPFLAGS+=-DLIBWRAP
endif
ifneq ($(strip $(USELIBPCRE)),)
CPPFLAGS+=-DLIBPCRE
endif
ifneq ($(strip $(USELIBCONFIG)),)
LIBS:=$(LIBS) -lconfig
CFLAGS:=$(CFLAGS) -DLIBCONFIG
CPPFLAGS+=-DLIBCONFIG
endif
ifneq ($(strip $(USELIBCAP)),)
LIBS:=$(LIBS) -lcap
CPPFLAGS+=-DLIBCAP
endif
all: sslh $(MAN) echosrv
.c.o: *.h
$(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -c $<
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
version.h:
./genver.sh >version.h
sslh: $(OBJS) sslh-fork sslh-select
sslh: sslh-fork sslh-select
sslh-fork: $(OBJS) sslh-fork.o Makefile common.h
$(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
#strip sslh-fork
sslh-select: $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS)
sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
echosrv: $(OBJS) echosrv.o
$(CC) $(CFLAGS) -o echosrv echosrv.o common.o $(LIBS)
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
$(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
# Create release: export clean tree and tag current
# configuration
release:
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
# generic install: install binary and man page
install: sslh $(MAN)
install -D sslh-fork $(PREFIX)/sbin/sslh
install -D -m 0644 $(MAN) $(PREFIX)/share/man/man8/$(MAN)
mkdir -p $(DESTDIR)/$(BINDIR)
mkdir -p $(DESTDIR)/$(MANDIR)
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
install -p -m 0644 $(MAN) $(DESTDIR)/$(MANDIR)/$(MAN)
# "extended" install for Debian: install startup script
install-debian: install sslh $(MAN)
sed -e "s+^PREFIX=+PREFIX=$(PREFIX)+" scripts/etc.init.d.sslh > /etc/init.d/sslh
chmod 755 /etc/init.d/sslh
cp scripts/etc.default.sslh /etc/default/sslh
update-rc.d sslh defaults
uninstall:
rm -f $(PREFIX)/sbin/sslh $(PREFIX)/share/man/man8/$(MAN) /etc/init.d/sslh /etc/default/sslh
rm -f $(DESTDIR)$(BINDIR)/sslh $(DESTDIR)$(MANDIR)/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh
update-rc.d sslh remove
distclean: clean
rm -f tags cscope.*
clean:
rm -f sslh-fork sslh-select echosrv $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
rm -f sslh-fork sslh-select echosrv version.h $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags -T *.[ch]
ctags --globals -T *.[ch]
cscope:
-find . -name "*.[chS]" >cscope.files
-cscope -b -R
test:
./t

171
README
View File

@ -1,171 +0,0 @@
===== sslh -- A ssl/ssh multiplexer. =====
sslh accepts connections in HTTP, HTTPS, SSH, OpenVPN,
tinc, XMPP, or any other protocol that can be tested using a
regular expression, on the same port. This makes it possible
to connect to any of these servers on port 443 (e.g. from
inside a corporate firewall, which almost never block port
443) while still serving HTTPS on that port.
==== Compile and install ====
If you're lucky, the Makefile will work for you:
make install
There are a couple of configuration options at the beginning
of the Makefile:
USELIBWRAP compiles support for host access control (see
hosts_access(3)), you will need libwrap headers and
library to compile (libwrap0-dev in Debian).
USELIBCONFIG compiles support for the configuration
file. You will need libconfig headers to compile
(libconfig8-dev in Debian).
The Makefile produces two different executables: sslh-fork
and sslh-select.
sslh-fork forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes. sslh-select uses only one thread, which
monitors all connections at once. It is more recent and less
tested, but only incurs a 16 byte overhead per connection.
Also, if it stops, you'll lose all connections, which means
you can't upgrade it remotely.
If you are going to use sslh for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
sslh-fork is probably more suited for you. If you are going
to use sslh on a "medium" setup (a few thousand ssh
connections, and another few thousand sslh connections),
sslh-select will be better. If you have a very large site
(tens of thousands of connections), you'll need a vapourware
version that would use libevent or something like that.
To install:
make
cp sslh-fork /usr/local/sbin/sslh
cp scripts/etc.default.sslh /etc/default/sslh
For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
For CentOS:
cp scripts/etc.rc.d.init.d.sslh /etc/rc.d/init.d/sslh
and probably create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
update-rc.d sslh defaults
==== Configuration ====
You can edit settings in /etc/default/sslh:
LISTEN=ifname:443
SSH=localhost:22
SSL=localhost:443
A good scheme is to use the external name of the machine in
$LISTEN, and bind httpd to localhost:443 (instead of all
binding to all interfaces): that way, https connections
coming from inside your network don't need to go through
sslh, and sslh is only there as a frontal for connections
coming from the internet.
Note that 'external name' in this context refers to the
actual IP address of the machine as seen from your network,
i.e. that that is not 127.0.0.1 in the output of
ifconfig(8).
==== Libwrap support ====
Sslh can optionnaly perform libwrap checks for the sshd
service: because the connection to sshd will be coming
locally from sslh, sshd cannot determine the IP of the
client.
==== OpenVPN support ====
OpenVPN clients connecting to OpenVPN running with
-port-share reportedly take more than one second between
the time the TCP connexion 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
increase sslh's timeout to 5 seconds.
Instead of using OpenVPN's port sharing, it is more reliable
to use sslh's -o option to get sslh to do the port sharing.
==== Using proxytunnel with sslh ====
If you are connecting through a proxy that checks that the
outgoing connection really is SSL and rejects SSH, you can
encapsulate all your traffic in SSL using proxytunnel (this
should work with corkscrew as well). On the server side you
receive the traffic with stunnel to decapsulate SSL, then
pipe through sslh to switch HTTP on one side and SSL on the
other.
In that case, you end up with something like this:
ssh -> proxytunnel -e --------ssh/ssl------> stunnel ---ssh---> sslh --> sshd
Web browser --------http/ssl------> stunnel ---http---> sslh --> http:80
Configuration goes like this:
On the server side, using stunnel3:
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- sslh -i --ssl localhost:80 --ssh localhost:22
stunnel options: -f for foreground/debugging, -p specifies
the key + certificate, -d specifies which interface and port
we're listening to for incoming connexions, -l summons sslh
in inetd mode.
sslh options: -i for inetd mode, --ssl to forward SSL
connexions (in fact normal HTTP at that stage) to port 80,
and SSH connexions to port 22. This works because sslh
considers that any protocol it doesn't recognise is SSL.
==== IP_TPROXY support ====
There is a netfilter patch that adds an option to the Linux
TCP/IP stack to allow a program to set the source address
of an IP packet that it sends. This could let sslh set the
address of packets to that of the actual client, so that
sshd would see and log the IP address of the client, making
sslh transparent.
This is not, and won't be, implemented in sslh for the
following reasons (in increasing order of importance):
* It's not vital: the real connecting IP address can be
found in logs. Little gain.
* It's Linux only: it means increased complexity for no
gain to some users.
* It's a patch: it means it'd only be useful to Linux
users who compile their own kernel.
* Only root can use the feature: that's a definite no-no.
Sslh should not, must not, will never run as root.
This isn't to mean that it won't eventually get implemented,
when/if the feature finds its way into the main kernel and
it becomes usuable by non-root processes.
==== Comments? Questions? ====
You can subscribe to the sslh mailing list here:
http://rutschle.net/cgi-bin/mailman/listinfo/sslh
This mailing list should be used for discussion, feature
requests, and will be the prefered channel for
announcements.

327
README.md Normal file
View File

@ -0,0 +1,327 @@
sslh -- A ssl/ssh multiplexer
=============================
`sslh` accepts connections on specified ports, and forwards
them further based on tests performed on the first data
packet sent by the remote client.
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
implemented, and any other protocol that can be tested using
a regular expression, can be recognised. A typical use case
is to allow serving several services on port 443 (e.g. to
connect to SSH from inside a corporate firewall, which
almost never block port 443) while still serving HTTPS on
that port.
Hence `sslh` acts as a protocol demultiplexer, or a
switchboard. Its name comes from its original function to
serve SSH and HTTPS on the same port.
Compile and install
===================
Dependencies
------------
`sslh` uses [libconfig](http://www.hyperrealm.com/libconfig/)
and [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
For Debian, these are contained in packages `libwrap0-dev` and
`libconfig8-dev`.
For OpenSUSE, these are contained in packages libconfig9 and
libconfig-dev in repository
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
For Fedora, you'll need packages `libconfig` and
`libconfig-devel`:
yum install libconfig libconfig-devel
If you can't find `libconfig`, or just don't want a
configuration file, set `USELIBCONFIG=` in the Makefile.
Compilation
-----------
After this, the Makefile should work:
make install
There are a couple of configuration options at the beginning
of the Makefile:
* `USELIBWRAP` compiles support for host access control (see
`hosts_access(3)`), you will need `libwrap` headers and
library to compile (`libwrap0-dev` in Debian).
* `USELIBCONFIG` compiles support for the configuration
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
Binaries
--------
The Makefile produces two different executables: `sslh-fork`
and `sslh-select`:
* `sslh-fork` forks a new process for each incoming connection.
It is well-tested and very reliable, but incurs the overhead
of many processes.
If you are going to use `sslh` for a "small" setup (less than
a dozen ssh connections and a low-traffic https server) then
`sslh-fork` is probably more suited for you.
* `sslh-select` uses only one thread, which monitors all connections
at once. It is more recent and less tested, but only incurs a 16
byte overhead per connection. Also, if it stops, you'll lose all
connections, which means you can't upgrade it remotely.
If you are going to use `sslh` on a "medium" setup (a few thousand ssh
connections, and another few thousand ssl connections),
`sslh-select` will be better.
If you have a very large site (tens of thousands of connections),
you'll need a vapourware version that would use libevent or
something like that.
Installation
------------
* In general:
make
cp sslh-fork /usr/local/sbin/sslh
cp basic.cfg /etc/sslh.cfg
vi /etc/sslh.cfg
* For Debian:
cp scripts/etc.init.d.sslh /etc/init.d/sslh
* For CentOS:
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
You might need to create links in /etc/rc<x>.d so that the server
start automatically at boot-up, e.g. under Debian:
update-rc.d sslh defaults
Configuration
=============
If you use the scripts provided, sslh will get its
configuration from /etc/sslh.cfg. Please refer to
example.cfg for an overview of all the settings.
A good scheme is to use the external name of the machine in
`listen`, and bind `httpd` to `localhost:443` (instead of all
binding to all interfaces): that way, HTTPS connections
coming from inside your network don't need to go through
`sslh`, and `sslh` is only there as a frontal for connections
coming from the internet.
Note that 'external name' in this context refers to the
actual IP address of the machine as seen from your network,
i.e. that that is not `127.0.0.1` in the output of
`ifconfig(8)`.
Libwrap support
---------------
Sslh can optionnaly perform `libwrap` checks for the sshd
service: because the connection to `sshd` will be coming
locally from `sslh`, `sshd` cannot determine the IP of the
client.
OpenVPN support
---------------
OpenVPN clients connecting to OpenVPN running with
`-port-share` reportedly take more than one second between
the time the TCP connexion 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
increase `sslh`'s timeout to 5 seconds.
Instead of using OpenVPN's port sharing, it is more reliable
to use `sslh`'s `--openvpn` option to get `sslh` to do the
port sharing.
Using proxytunnel with sslh
---------------------------
If you are connecting through a proxy that checks that the
outgoing connection really is SSL and rejects SSH, you can
encapsulate all your traffic in SSL using `proxytunnel` (this
should work with `corkscrew` as well). On the server side you
receive the traffic with `stunnel` to decapsulate SSL, then
pipe through `sslh` to switch HTTP on one side and SSL on the
other.
In that case, you end up with something like this:
ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd
Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd
Configuration goes like this on the server side, using `stunnel3`:
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \
sslh -i --http localhost:80 --ssh localhost:22
* stunnel options:
* `-f` for foreground/debugging
* `-p` for specifying the key and certificate
* `-d` for specifying which interface and port
we're listening to for incoming connexions
* `-l` summons `sslh` in inetd mode.
* sslh options:
* `-i` for inetd mode
* `--http` to forward HTTP connexions to port 80,
and SSH connexions to port 22.
Capabilities support
--------------------
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
make use of POSIX capabilities; this will save the required
capabilities needed for transparent proxying for unprivileged
processes.
Alternatively, you may use filesystem capabilities instead
of starting sslh as root and asking it to drop privileges.
You will need `CAP_NET_BIND_SERVICE` for listening on port 443
and `CAP_NET_ADMIN` for transparent proxying (see
`capabilities(7)`).
You can use the `setcap(8)` utility to give these capabilities
to the executable:
# setcap cap_net_bind_service,cap_net_admin+pe sslh-select
Then you can run sslh-select as an unpriviledged user, e.g.:
$ sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443
Caveat: `CAP_NET_ADMIN` does give sslh too many rights, e.g.
configuring the interface. If you're not going to use
transparent proxying, just don't use it (or use the libcap method).
Transparent proxy support
-------------------------
On Linux and FreeBSD you can use the `--transparent` option to
request transparent proxying. This means services behind `sslh`
(Apache, `sshd` and so on) will see the external IP and ports
as if the external world connected directly to them. This
simplifies IP-based access control (or makes it possible at
all).
Linux:
`sslh` needs extended rights to perform this: you'll need to
give it `CAP_NET_ADMIN` capabilities (see appropriate chapter)
or run it as root (but don't do that).
The firewalling tables also need to be adjusted as 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
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
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
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.
FreeBSD:
Given you have no firewall defined yet, you can use the following configuration
to have ipfw properly redirect traffic back to sslh
/etc/rc.conf
firewall_enable="YES"
firewall_type="open"
firewall_logif="YES"
firewall_coscripts="/etc/ipfw/sslh.rules"
/etc/ipfw/sslh.rules
#! /bin/sh
# ssl
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
# ssh
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
# xmpp
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
# openvpn (running on other internal system)
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
General notes:
This will only work if `sslh` does not use any loopback
addresses (no `127.0.0.1` or `localhost`), you'll need to use
explicit IP addresses (or names):
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --ssl 192.168.0.1:4443
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443
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
many connections to ssh from the same IP address...)
See example files in scripts/fail2ban.
Comments? Questions?
====================
You can subscribe to the `sslh` mailing list here:
<http://rutschle.net/cgi-bin/mailman/listinfo/sslh>
This mailing list should be used for discussion, feature
requests, and will be the prefered channel for announcements.

25
TODO Normal file
View File

@ -0,0 +1,25 @@
Here's a list of features that have been suggested or
sometimes requested. This list is not a roadmap and
shouldn't be construed to mean that any of this will happen.
- configurable behaviour depending on services (e.g.
select() for ssl but fork() for ssh).
- have certain services available only from specified subnets
- some sort of "service knocking" allowing to activate a
service upon some external even, similar to port knocking;
for example, go to a specific URL to enable sslh forwarding
to sshd for a set period of time:
* sslh listens on 443 and only directs to httpd
* user goes somewhere to https://example.org/open_ssh.cgi
* open_ssh.cgi tells sslh
* sslh starts checking if incoming connections are ssh, and
if they are, forward to sshd
* 10 minutes later, sslh stops forwarding to ssh
That would make it almost impossible for an observer
(someone who'd telnet regularly on 443) to ever notice both
services are available on 443.

29
basic.cfg Normal file
View File

@ -0,0 +1,29 @@
# This is a basic configuration file that should provide
# sensible values for "standard" setup.
verbose: false;
foreground: false;
inetd: false;
numeric: false;
transparent: false;
timeout: "2";
user: "nobody";
pidfile: "/var/run/sslh.pid";
# Change hostname with your external address name.
listen:
(
{ host: "thelonious"; port: "443"; }
);
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "ssl"; host: "localhost"; port: "443"; log_level: 0; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);

260
common.c Executable file → Normal file
View File

@ -6,8 +6,10 @@
#define _GNU_SOURCE
#include <stdarg.h>
#include <grp.h>
#include "common.h"
#include "probe.h"
/* Added to make the code compilable under CYGWIN
* */
@ -24,8 +26,10 @@ int verbose = 0;
int probing_timeout = 2;
int inetd = 0;
int foreground = 0;
int background = 0;
int transparent = 0;
int numeric = 0;
const char *user_name, *pid_file, *rule_filename;
const char *user_name, *pid_file;
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
@ -34,7 +38,6 @@ struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
int allow_severity =0, deny_severity = 0;
#endif
/* check result and die, printing the offending address and error */
void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
{
@ -59,7 +62,7 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
{
struct sockaddr_storage *saddr;
struct addrinfo *addr;
int i, res, reuse;
int i, res, one;
int num_addr = 0;
for (addr = addr_list; addr; addr = addr->ai_next)
@ -80,9 +83,14 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dumpdie((*sockfd)[i], addr, "socket");
reuse = 1;
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
check_res_dumpdie(res, addr, "setsockopt");
one = 1;
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
check_res_dumpdie(res, addr, "setsockopt(SO_REUSEADDR)");
if (IP_FREEBIND) {
res = setsockopt((*sockfd)[i], IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
check_res_dumpdie(res, addr, "setsockopt(IP_FREEBIND)");
}
res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
check_res_dumpdie(res, addr, "bind");
@ -95,27 +103,85 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
return num_addr;
}
/* Connect to first address that works and returns a file descriptor, or -1 if
* none work. cnx_name points to the name of the service (for logging) */
int connect_addr(struct addrinfo *addr, const char* cnx_name)
/* Transparent proxying: bind the peer address of fd to the peer address of
* fd_from */
#define IP_TRANSPARENT 19
int bind_peer(int fd, int fd_from)
{
struct addrinfo *a;
struct addrinfo from;
struct sockaddr_storage ss;
int res, trans = 1;
memset(&from, 0, sizeof(from));
from.ai_addr = (struct sockaddr*)&ss;
from.ai_addrlen = sizeof(ss);
/* getpeername can fail with ENOTCONN if connection was dropped before we
* got here */
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername");
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
CHECK_RES_DIE(res, "setsockopt");
#else
if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans));
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY");
#ifdef IPV6_BINDANY
} else { /* IPv6 */
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans));
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY");
#endif /* IPV6_BINDANY */
}
#endif /* IP_TRANSPARENT / IP_BINDANY */
res = bind(fd, from.ai_addr, from.ai_addrlen);
CHECK_RES_RETURN(res, "bind");
return 0;
}
/* Connect to first address that works and returns a file descriptor, or -1 if
* none work.
* If transparent proxying is on, use fd_from peer address on external address
* of new file descriptor. */
int connect_addr(struct connection *cnx, int fd_from)
{
struct addrinfo *a, from;
struct sockaddr_storage ss;
char buf[NI_MAXHOST];
int fd, res;
for (a = addr; a; a = a->ai_next) {
memset(&from, 0, sizeof(from));
from.ai_addr = (struct sockaddr*)&ss;
from.ai_addrlen = sizeof(ss);
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername");
for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent, make sure both connections use the same address family */
if (transparent && a->ai_family != from.ai_addr->sa_family)
continue;
if (verbose)
fprintf(stderr, "connecting to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a),
a->ai_addr->sa_family, a->ai_addrlen);
/* XXX Needs to match ai_family from fd_from when being transparent! */
fd = socket(a->ai_family, SOCK_STREAM, 0);
if (fd == -1) {
log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx_name, strerror(errno));
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
cnx->proto->description, strerror(errno));
} else {
if (transparent) {
res = bind_peer(fd, fd_from);
CHECK_RES_RETURN(res, "bind_peer");
}
res = connect(fd, a->ai_addr, a->ai_addrlen);
if (res == -1) {
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
cnx_name, strerror(errno));
cnx->proto->description, strerror(errno));
close(fd);
} else {
return fd;
}
@ -127,12 +193,20 @@ int connect_addr(struct addrinfo *addr, const char* cnx_name)
/* Store some data to write to the queue later */
int defer_write(struct queue *q, void* data, int data_size)
{
char *p;
if (verbose)
fprintf(stderr, "**** writing defered on fd %d\n", q->fd);
q->defered_data = malloc(data_size);
q->begin_defered_data = q->defered_data;
q->defered_data_size = data_size;
memcpy(q->defered_data, data, data_size);
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);
}
q->deferred_data = q->begin_deferred_data = p;
p += q->deferred_data_size;
q->deferred_data_size += data_size;
memcpy(p, data, data_size);
return 0;
}
@ -141,30 +215,29 @@ int defer_write(struct queue *q, void* data, int data_size)
* Upon success, the number of bytes written is returned.
* Upon failure, -1 returned (e.g. connexion closed)
* */
int flush_defered(struct queue *q)
int flush_deferred(struct queue *q)
{
int n;
if (verbose)
fprintf(stderr, "flushing defered data to fd %d\n", q->fd);
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
n = write(q->fd, q->defered_data, q->defered_data_size);
n = write(q->fd, q->deferred_data, q->deferred_data_size);
if (n == -1)
return n;
if (n == q->defered_data_size) {
if (n == q->deferred_data_size) {
/* All has been written -- release the memory */
free(q->begin_defered_data);
q->begin_defered_data = NULL;
q->defered_data = NULL;
q->defered_data_size = 0;
free(q->begin_deferred_data);
q->begin_deferred_data = NULL;
q->deferred_data = NULL;
q->deferred_data_size = 0;
} else {
/* There is data left */
q->defered_data += n;
q->defered_data_size -= n;
q->deferred_data += n;
q->deferred_data_size -= n;
}
return n;
}
@ -174,26 +247,25 @@ void init_cnx(struct connection *cnx)
memset(cnx, 0, sizeof(*cnx));
cnx->q[0].fd = -1;
cnx->q[1].fd = -1;
cnx->proto = get_first_protocol();
}
void dump_connection(struct connection *cnx)
{
printf("state: %d\n", cnx->state);
printf("fd %d, %d defered\n", cnx->q[0].fd, cnx->q[0].defered_data_size);
printf("fd %d, %d defered\n", cnx->q[1].fd, cnx->q[1].defered_data_size);
printf("fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
printf("fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
}
/*
* moves data from one fd to other
*
* retuns number of bytes copied if success
* returns number of bytes copied if success
* returns 0 (FD_CNXCLOSED) if incoming socket closed
* returns FD_NODATA if no data was available
* returns FD_STALLED if data was read, could not be written, and has been
* stored in temporary buffer.
*
* slot for debug only and may go away at some point
*/
int fd2fd(struct queue *target_q, struct queue *from_q)
{
@ -224,7 +296,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
size_w = write(target, buffer, size_r);
/* process -1 when we know how to deal with it */
if ((size_w == -1)) {
if (size_w == -1) {
switch (errno) {
case EAGAIN:
/* write blocked: Defer data */
@ -302,21 +374,31 @@ fullname: input string -- it gets clobbered
*/
void resolve_name(struct addrinfo **out, char* fullname)
{
char *serv, *host;
char *serv, *host, *end;
int res;
/* Find port */
char *sep = strrchr(fullname, ':');
if (!sep) /* No separator: parameter is just a port */
{
if (!sep) { /* No separator: parameter is just a port */
fprintf(stderr, "%s: names must be fully specified as hostname:port\n", fullname);
exit(1);
}
host = fullname;
serv = sep+1;
*sep = 0;
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);
@ -349,11 +431,15 @@ void log_connection(struct connection *cnx)
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
int res;
if (cnx->proto->log_level < 1)
return;
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; /* that should never happen, right? */
if (res == -1) return; /* Can happen if connection drops before we get here.
In that case, don't log anything (there is no connection) */
sprintaddr(peer, sizeof(peer), &addr);
addr.ai_addrlen = sizeof(ss);
@ -371,7 +457,8 @@ void log_connection(struct connection *cnx)
if (res == -1) return;
sprintaddr(local, sizeof(local), &addr);
log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n",
log_message(LOG_INFO, "%s:connection from %s to %s forwarded from %s to %s\n",
cnx->proto->description,
peer,
service,
local,
@ -388,16 +475,19 @@ void log_connection(struct connection *cnx)
int check_access_rights(int in_socket, const char* service)
{
#ifdef LIBWRAP
struct sockaddr peeraddr;
socklen_t size = sizeof(peeraddr);
union {
struct sockaddr saddr;
struct sockaddr_storage ss;
} peer;
socklen_t size = sizeof(peer);
char addr_str[NI_MAXHOST], host[NI_MAXHOST];
int res;
res = getpeername(in_socket, &peeraddr, &size);
CHECK_RES_DIE(res, "getpeername");
res = getpeername(in_socket, &peer.saddr, &size);
CHECK_RES_RETURN(res, "getpeername");
/* extract peer address */
res = getnameinfo(&peeraddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
if (res) {
if (verbose)
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
@ -406,7 +496,7 @@ int check_access_rights(int in_socket, const char* service)
/* extract peer name */
strcpy(host, STRING_UNKNOWN);
if (!numeric) {
res = getnameinfo(&peeraddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
if (res) {
if (verbose)
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
@ -437,7 +527,7 @@ void setup_signals(void)
res = sigaction(SIGCHLD, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
/* Set SIGTERM to exit. For some reason if it's not set explicitely,
/* Set SIGTERM to exit. For some reason if it's not set explicitly,
* coverage information is lost when killing the process */
memset(&action, 0, sizeof(action));
action.sa_handler = exit;
@ -455,9 +545,11 @@ 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;
name1 = strdup(bin_name);
asprintf(&name2, "%s[%d]", basename(name1), getpid());
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
openlog(name2, LOG_CONS, LOG_AUTH);
free(name1);
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
@ -465,7 +557,57 @@ void setup_syslog(const char* bin_name) {
log_message(LOG_INFO, "%s %s started\n", server_type, VERSION);
}
/* We don't want to run as root -- drop priviledges if required */
/* Ask OS to keep capabilities over a setuid(nonzero) */
void set_keepcaps(int val) {
#ifdef LIBCAP
int res;
res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0);
if (res) {
perror("prctl");
exit(1);
}
#endif
}
/* set needed capabilities for effective and permitted, clear rest */
void set_capabilities(void) {
#ifdef LIBCAP
int res;
cap_t caps;
cap_value_t cap_list[10];
int ncap = 0;
if (transparent)
cap_list[ncap++] = CAP_NET_ADMIN;
caps = cap_init();
#define _cap_set_flag(flag) do { \
res = cap_clear_flag(caps, flag); \
CHECK_RES_DIE(res, "cap_clear_flag(" #flag ")"); \
if (ncap > 0) { \
res = cap_set_flag(caps, flag, ncap, cap_list, CAP_SET); \
CHECK_RES_DIE(res, "cap_set_flag(" #flag ")"); \
} \
} while(0)
_cap_set_flag(CAP_EFFECTIVE);
_cap_set_flag(CAP_PERMITTED);
#undef _cap_set_flag
res = cap_set_proc(caps);
CHECK_RES_DIE(res, "cap_set_proc");
res = cap_free(caps);
if (res) {
perror("cap_free");
exit(1);
}
#endif
}
/* We don't want to run as root -- drop privileges if required */
void drop_privileges(const char* user_name)
{
int res;
@ -477,10 +619,22 @@ void drop_privileges(const char* user_name)
if (verbose)
fprintf(stderr, "turning into %s\n", user_name);
set_keepcaps(1);
/* 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");
setuid(pw->pw_uid);
res = setuid(pw->pw_uid);
CHECK_RES_DIE(res, "setuid");
set_capabilities();
set_keepcaps(0);
}
/* Writes my PID */

38
common.h Executable file → Normal file
View File

@ -1,6 +1,12 @@
#ifndef __COMMON_H_
#define __COMMON_H_
/* FD_SETSIZE is 64 on Cygwin, which is really low. Just redefining it is
* enough for the macros to adapt (http://support.microsoft.com/kb/111855)
*/
#ifdef __CYGWIN__
#define FD_SETSIZE 4096
#endif
#define _GNU_SOURCE
#include <sys/types.h>
@ -22,10 +28,13 @@
#include <time.h>
#include <getopt.h>
#ifndef VERSION
#define VERSION "v?"
#ifdef LIBCAP
#include <sys/prctl.h>
#include <sys/capability.h>
#endif
#include "version.h"
#define CHECK_RES_DIE(res, str) \
if (res == -1) { \
perror(str); \
@ -34,7 +43,7 @@
#define CHECK_RES_RETURN(res, str) \
if (res == -1) { \
log_message(LOG_CRIT, "%s: %d\n", str, errno); \
log_message(LOG_CRIT, "%s:%d:%s\n", str, errno, strerror(errno)); \
return res; \
}
@ -46,6 +55,10 @@
#define TRACE
#endif
#ifndef IP_FREEBIND
#define IP_FREEBIND 0
#endif
enum connection_state {
ST_PROBING=1, /* Waiting for timeout to find where to forward */
ST_SHOVELING /* Connexion is established */
@ -55,17 +68,18 @@ enum connection_state {
#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
/* A 'queue' is composed of a file descriptor (which can be read from or
* written to), and a queue for defered write data */
* written to), and a queue for deferred write data */
struct queue {
int fd;
void *begin_defered_data;
void *defered_data;
int defered_data_size;
void *begin_deferred_data;
void *deferred_data;
int deferred_data_size;
};
struct connection {
enum connection_state state;
time_t probe_timeout;
struct proto *proto;
/* q[0]: queue for external connection (client);
* q[1]: queue for internal connection (httpd or sshd);
@ -80,11 +94,10 @@ struct connection {
/* common.c */
void init_cnx(struct connection *cnx);
int connect_addr(struct addrinfo *addr, const char* cnx_name);
int connect_addr(struct connection *cnx, int fd_from);
int fd2fd(struct queue *target, struct queue *from);
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
void resolve_name(struct addrinfo **out, char* fullname);
struct proto* probe_client_protocol(struct connection *cnx);
void log_connection(struct connection *cnx);
int check_access_rights(int in_socket, const char* service);
void setup_signals(void);
@ -98,13 +111,14 @@ int resolve_split_name(struct addrinfo **out, const char* hostname, const char*
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
int defer_write(struct queue *q, void* data, int data_size);
int flush_defered(struct queue *q);
int flush_deferred(struct queue *q);
extern int probing_timeout, verbose, inetd, foreground, numeric;
extern int probing_timeout, verbose, inetd, foreground,
background, transparent, numeric;
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, *rule_filename;
extern const char* user_name, *pid_file;
extern const char* server_type;
/* sslh-fork.c */

24
echosrv.c Executable file → Normal file
View File

@ -94,32 +94,26 @@ void parse_cmdline(int argc, char* argv[])
void start_echo(int fd)
{
fd_set fds;
int res;
char buffer[1 << 20];
char* ret;
FILE *socket;
int ret, prefix_len;
FD_ZERO(&fds);
prefix_len = strlen(prefix);
socket = fdopen(fd, "r+");
if (!socket) {
CHECK_RES_DIE(-1, "fdopen");
}
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, prefix);
while (1) {
ret = fgets(buffer, sizeof(buffer), socket);
if (!ret) {
fprintf(stderr, "%s", strerror(ferror(socket)));
ret = read(fd, buffer + prefix_len, sizeof(buffer));
if (ret == -1) {
fprintf(stderr, "%s", strerror(errno));
return;
}
res = fprintf(socket, "%s%s", prefix, buffer);
res = write(fd, buffer, ret + prefix_len);
if (res < 0) {
fprintf(stderr, "%s", strerror(ferror(socket)));
fprintf(stderr, "%s", strerror(errno));
return;
}
fflush(socket);
}
}

View File

@ -1,8 +1,14 @@
verbose: false;
# This file is provided as documentation to show what is
# possible. It should not be used as-is, and probably should
# not be used as a starting point for a working
# configuration. Instead use basic.cfg.
verbose: true;
foreground: true;
inetd: false;
numeric: false;
timeout: 2;
transparent: false;
timeout: "2";
user: "nobody";
pidfile: "/var/run/sslh.pid";
@ -10,31 +16,72 @@ pidfile: "/var/run/sslh.pid";
# List of interfaces on which we should listen
listen:
(
{ host: "thelonious"; port: "443"; }
# , { host: "thelonious"; port: "8080"; }
{ host: "thelonious"; port: "443"; },
{ host: "thelonious"; port: "8080"; }
);
# List of protocols
#
# Each protocol entry consists of:
# name: name of the protocol
# name: name of the probe. These are listed on the command
# line (ssh -?), plus 'regex' and 'timeout'.
# service: (optional) libwrap service name (see hosts_access(5))
# host: host name to connect that protocol
# port: port number to connect that protocol
# probe: "builtin" or a list of regular expressions
# host, port: where to connect when this probe succeeds
#
# Probe-specific options:
# 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
# regex:
# regex_patterns: list of patterns to match for
# that target.
#
# In case of timeout sslh will connect to the first
# protocol: this should be SSH.
# SSL should have a "always true" probe, and come last.
# 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"; probe: "builtin"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; },
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
{ name: "ssl"; host: "localhost"; port: "443"; probe: [ "" ]; }
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
{ name: "http"; host: "localhost"; port: "80"; },
# match BOTH ALPN/SNI
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0;},
# just match ALPN
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0;},
# just match SNI
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; },
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;},
# catch anything else TLS
{ name: "tls"; host: "localhost"; port: "443"; },
# 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: [ "" ]; },
# Where to connect in case of timeout (defaults to ssh)
{ name: "timeout"; service: "daytime"; host: "localhost"; port: "daytime"; }
);
# Optionally, specify to which protocol to connect in case
# of timeout (defaults to "ssh").
# You can timeout to any arbitrary address by setting an
# entry in 'protocols' named "timeout".
# This enables you to set a tcpd service name for this
# protocol too.
on-timeout: "timeout";

49
genver.sh Executable file
View File

@ -0,0 +1,49 @@
#! /bin/sh
if [ ${#} -eq 1 ] && [ "x$1" = "x-r" ]; then
# release text only
QUIET=1
else
QUIET=0
fi
if ! `(git status | grep -q "On branch") 2> /dev/null`; then
# If we don't have git, we can't work out what
# version this is. It must have been downloaded as a
# zip file.
# If downloaded from the release page, the directory
# has the version number.
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
if [ "x$release" = "x" ]; then
# If downloaded from the head, Github creates the
# zip file with all files dated from the last
# change: use the Makefile's modification time as a
# release number
release=head-`perl -MPOSIX -e 'print strftime "%Y-%m-%d",localtime((stat "Makefile")[9])'`
fi
fi
if head=`git rev-parse --verify HEAD 2>/dev/null`; then
# generate the version info based on the tag
release=`(git describe --tags || git --describe || git describe --all --long) \
2>/dev/null | tr -d '\n'`
# Are there uncommitted changes?
git update-index --refresh --unmerged > /dev/null
if git diff-index --name-only HEAD | grep -v "^scripts/package" \
| read dummy; then
release="$release-dirty"
fi
fi
if [ $QUIET -ne 1 ]; then
printf "#ifndef _VERSION_H_ \n"
printf "#define _VERSION_H_ \n\n"
printf "#define VERSION \"$release\"\n"
printf "#endif\n"
else
printf "$release\n"
fi

289
probe.c
View File

@ -1,7 +1,7 @@
/*
# probe.c: Code for probing protocols
#
# Copyright (C) 2007-2012 Yves Rutschle
# Copyright (C) 2007-2015 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@ -19,7 +19,12 @@
# http://www.gnu.org/licenses/gpl.html
*/
#define _GNU_SOURCE
#include <stdio.h>
#ifdef LIBPCRE
#include <regex.h>
#endif
#include <ctype.h>
#include "probe.h"
@ -29,21 +34,27 @@ static int is_openvpn_protocol(const char *p, int len, struct proto*);
static int is_tinc_protocol(const char *p, int len, struct proto*);
static int is_xmpp_protocol(const char *p, int len, struct proto*);
static int is_http_protocol(const char *p, int len, struct proto*);
static int is_tls_protocol(const char *p, int len, struct proto*);
static int is_adb_protocol(const char *p, int len, struct proto*);
static int is_true(const char *p, int len, struct proto* proto) { return 1; }
/* Table of protocols that have a built-in probe
*/
static struct proto builtins[] = {
/* description service saddr probe */
{ "ssh", "sshd", NULL, is_ssh_protocol},
{ "openvpn", NULL, NULL, is_openvpn_protocol },
{ "tinc", NULL, NULL, is_tinc_protocol },
{ "xmpp", NULL, NULL, is_xmpp_protocol },
{ "http", NULL, NULL, is_http_protocol },
{ "ssl", NULL, NULL, is_true }
/* description service saddr log_level probe */
{ "ssh", "sshd", NULL, 1, is_ssh_protocol},
{ "openvpn", NULL, NULL, 1, is_openvpn_protocol },
{ "tinc", NULL, NULL, 1, is_tinc_protocol },
{ "xmpp", NULL, NULL, 1, is_xmpp_protocol },
{ "http", NULL, NULL, 1, is_http_protocol },
{ "ssl", NULL, NULL, 1, is_tls_protocol },
{ "tls", NULL, NULL, 1, is_tls_protocol },
{ "adb", NULL, NULL, 1, is_adb_protocol },
{ "anyprot", NULL, NULL, 1, is_true }
};
static struct proto *protocols;
static char* on_timeout = "ssh";
struct proto* get_builtins(void) {
return builtins;
@ -53,12 +64,22 @@ int get_num_builtins(void) {
return ARRAY_SIZE(builtins);
}
/* Returns the protocol to connect to in case of timeout; conventionaly this is
* the first protocol specified (but maybe we'll make it more explicit some
* day)
/* Sets the protocol name to connect to in case of timeout */
void set_ontimeout(const char* name)
{
int res = asprintf(&on_timeout, "%s", name);
CHECK_RES_DIE(res, "asprintf");
}
/* Returns the protocol to connect to in case of timeout;
* if not found, return the first protocol specified
*/
struct proto* timeout_protocol(void) {
return protocols;
struct proto* timeout_protocol(void)
{
struct proto* p = get_first_protocol();
for (; p && strcmp(p->description, on_timeout); p = p->next);
if (p) return p;
return get_first_protocol();
}
/* returns the first protocol (caller can then follow the *next pointers) */
@ -72,41 +93,78 @@ void set_protocol_list(struct proto* prots)
protocols = prots;
}
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
#define HEXDUMP_COLS 16
void hexdump(const char *mem, unsigned int len)
{
unsigned int i, j;
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
{
/* print offset */
if(i % HEXDUMP_COLS == 0)
printf("0x%06x: ", i);
/* print hex data */
if(i < len)
printf("%02x ", 0xFF & mem[i]);
else /* end of block, just aligning for ASCII dump */
printf(" ");
/* 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(' ');
else if(isprint(mem[j])) /* printable char */
putchar(0xFF & mem[j]);
else /* other char */
putchar('.');
}
putchar('\n');
}
}
}
/* Is the buffer the beginning of an SSH connection? */
static int is_ssh_protocol(const char *p, int len, struct proto *proto)
{
if (!strncmp(p, "SSH-", 4)) {
return 1;
}
return 0;
if (len < 4)
return PROBE_AGAIN;
return !strncmp(p, "SSH-", 4);
}
/* Is the buffer the beginning of an OpenVPN connection?
* (code lifted from OpenVPN port-share option)
*
* Code inspired from OpenVPN port-share option; however, OpenVPN code is
* wrong: users using pre-shared secrets have non-initialised key_id fields so
* p[3] & 7 should not be looked at, and also the key_method can be specified
* to 1 which changes the opcode to P_CONTROL_HARD_RESET_CLIENT_V1.
* See:
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
* and OpenVPN ssl.c, ssl.h and options.c
*/
static int is_openvpn_protocol (const char*p,int len, struct proto *proto)
{
#define P_OPCODE_SHIFT 3
#define P_CONTROL_HARD_RESET_CLIENT_V2 7
if (len >= 3)
{
return p[0] == 0
&& p[1] >= 14
&& p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
}
else if (len >= 2)
{
return p[0] == 0 && p[1] >= 14;
}
else
return 0;
int packet_len;
if (len < 2)
return PROBE_AGAIN;
packet_len = ntohs(*(uint16_t*)p);
return packet_len == len - 2;
}
/* Is the buffer the beginning of a tinc connections?
* (protocol is undocumented, but starts with "0 " in 1.0.15)
* Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf
* First connection starts with "0 " in 1.0.15)
* */
static int is_tinc_protocol( const char *p, int len, struct proto *proto)
{
if (len < 2)
return PROBE_AGAIN;
return !strncmp(p, "0 ", 2);
}
@ -116,57 +174,114 @@ 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)
{
return strstr(p, "jabber") ? 1 : 0;
/* 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;
}
static int probe_http_method(const char *p, const char *opt)
static int probe_http_method(const char *p, int len, const char *opt)
{
return !strcmp(p, opt);
if (len < strlen(opt))
return PROBE_AGAIN;
return !strncmp(p, opt, len);
}
/* Is the buffer the beginning of an HTTP connection? */
static int is_http_protocol(const char *p, int len, struct proto *proto)
{
int res;
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
if (strstr(p, "HTTP"))
return 1;
if (memmem(p, len, "HTTP", 4))
return PROBE_MATCH;
#define PROBE_HTTP_METHOD(opt) if ((res = probe_http_method(p, len, opt)) != PROBE_NEXT) return res
/* Otherwise it could be HTTP/1.0 without version: check if it's got an
* HTTP method (RFC2616 5.1.1) */
probe_http_method(p, "OPTIONS");
probe_http_method(p, "GET");
probe_http_method(p, "HEAD");
probe_http_method(p, "POST");
probe_http_method(p, "PUT");
probe_http_method(p, "DELETE");
probe_http_method(p, "TRACE");
probe_http_method(p, "CONNECT");
PROBE_HTTP_METHOD("OPTIONS");
PROBE_HTTP_METHOD("GET");
PROBE_HTTP_METHOD("HEAD");
PROBE_HTTP_METHOD("POST");
PROBE_HTTP_METHOD("PUT");
PROBE_HTTP_METHOD("DELETE");
PROBE_HTTP_METHOD("TRACE");
PROBE_HTTP_METHOD("CONNECT");
return 0;
#undef PROBE_HTTP_METHOD
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;
}
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);
}
static int is_adb_protocol(const char *p, int len, struct proto *proto)
{
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 regex_probe(const char *p, int len, struct proto *proto)
{
regex_t** probe_list = (regex_t**)(proto->data);
int i=0;
#ifdef LIBPCRE
regex_t **probe = proto->data;
regmatch_t pos = { 0, len };
while (probe_list[i]) {
if (!regexec(probe_list[i], p, 0, NULL, 0)) {
return 1;
}
i++;
}
return 0;
for (; *probe && regexec(*probe, p, 0, &pos, REG_STARTEND); probe++)
/* try them all */;
return (*probe != NULL);
#else
/* Should never happen as we check when loading config file */
fprintf(stderr, "FATAL: regex probe called but not built in\n");
exit(5);
#endif
}
/*
* Read the beginning of data coming from the client connection and check if
* it's a known protocol. Then leave the data on the defered
* write buffer of the connection and returns a pointer to the protocol
* structure
* it's a known protocol.
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
* which case cnx->proto is set to the appropriate protocol.
*/
struct proto* probe_client_protocol(struct connection *cnx)
int probe_client_protocol(struct connection *cnx)
{
char buffer[BUFSIZ];
struct proto *p;
@ -179,37 +294,69 @@ struct proto* probe_client_protocol(struct connection *cnx)
* 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 = protocols; p; p = p->next) {
if (p->probe(buffer, n, p)) {
return p;
}
for (p = cnx->proto; p && res == PROBE_NEXT; p = p->next) {
if (! p->probe) continue;
if (verbose) fprintf(stderr, "probing for %s\n", p->description);
cnx->proto = p;
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
}
if (res != PROBE_NEXT)
return res;
}
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
/* If none worked, return the first one affected (that's completely
* arbitrary) */
return protocols;
cnx->proto = protocols;
return PROBE_MATCH;
}
/* Returns the structure for specified protocol or NULL if not found */
static struct proto* get_protocol(const char* description)
{
int i;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].description, description)) {
return &builtins[i];
}
}
return NULL;
}
/* Returns the probe for specified protocol:
* parameter is the description in builtins[], or "regex"
* */
T_PROBE* get_probe(const char* description) {
int i;
struct proto* p = get_protocol(description);
if (p)
return p->probe;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].description, description)) {
return builtins[i].probe;
}
}
/* Special case of "regex" probe (we don't want to set it in builtins
* because builtins is also used to build the command-line options and
* regexp is not legal on the command line)*/
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"))
return is_true;
return NULL;
}

22
probe.h
View File

@ -4,6 +4,13 @@
#define __PROBE_H_
#include "common.h"
#include "tls.h"
typedef enum {
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
PROBE_MATCH, /* Enough data, probe successful -- it's the current protocol */
PROBE_AGAIN, /* Not enough data for this probe, try again with more data */
} probe_result;
struct proto;
typedef int T_PROBE(const char*, int, struct proto*);
@ -13,11 +20,15 @@ struct proto {
const char* description; /* a string that says what it is (for logging and command-line parsing) */
const char* service; /* service name to do libwrap checks */
struct addrinfo *saddr; /* list of addresses to try and switch that protocol */
int log_level; /* 0: No logging of connection
* 1: Log incoming connection
*/
/* function to probe that protocol; parameters are buffer and length
* containing the data to probe, and a pointer to the protocol structure */
T_PROBE* probe;
void* data; /* opaque pointer ; used to pass list of regex to regex probe */
/* opaque pointer ; used to pass list of regex to regex probe, or TLSProtocol struct to sni/alpn probe */
void* data;
struct proto *next; /* pointer to next protocol in list, NULL if last */
};
@ -39,11 +50,14 @@ void set_protocol_list(struct proto*);
/* probe_client_protocol
*
* Read the beginning of data coming from the client connection and check if
* it's a known protocol. Then leave the data on the defered
* it's a known protocol. Then leave the data on the deferred
* write buffer of the connection and returns a pointer to the protocol
* structure
*/
struct proto* probe_client_protocol(struct connection *cnx);
int probe_client_protocol(struct connection *cnx);
/* set the protocol to connect to in case of timeout */
void set_ontimeout(const char* name);
/* timeout_protocol
*
@ -51,4 +65,6 @@ struct proto* probe_client_protocol(struct connection *cnx);
*/
struct proto* timeout_protocol(void);
void hexdump(const char*, unsigned int);
#endif

View File

@ -1,5 +0,0 @@
LISTEN=ifname:443
SSH=localhost:22
SSL=localhost:443
USER=nobody
PID=/var/run/sslh.pid

View File

@ -2,6 +2,8 @@
### BEGIN INIT INFO
# Provides: sslh
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 1
# Short-Description: sslh proxy ssl & ssh connections
@ -25,7 +27,7 @@ DAEMON=$PREFIX/sbin/sslh
start()
{
echo "Start services: sslh"
$DAEMON --user ${USER} --pidfile ${PID} --listen ${LISTEN} --ssh ${SSH} --ssl ${SSL}
$DAEMON -F/etc/sslh.cfg
logger -t ${tag} -p ${facility} -i 'Started sslh'
}

View File

@ -1,56 +1,56 @@
#!/bin/bash
#
# /etc/rc.d/init.d/sslh
# sslh This shell script takes care of starting and stopping
# sslh - a daemon switching incoming connection between SSH and SSL/HTTPS servers
# sslh Startup script for the SSL/SSH multiplexer
#
# Author: Andre Krajnik akrajnik@gmail.com
# 2010-03-20
# chkconfig: - 13 87
# description: Sslh accepts connections on specified ports, and forwards
# them further based on tests performed on the first data
# packet sent by the remote client.
# processname: sslh
# config: /etc/sslh.cfg
# config: /etc/sysconfig/sslh
# pidfile: /var/run/sslh/sslh.pid
#
#
# chkconfig: 2345 13 87
#
# description: sslh - a daemon switching incoming connection between SSH and SSL/HTTPS servers
# Authors:
# Andre Krajnik akrajnik@gmail.com - 2010-03-20
# Julien Thomas julthomas@free.fr - 2013-08-25
# Source function library.
. /etc/init.d/functions
# ./sslh -p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22
SSLH="/usr/local/sbin/sslh"
PIDFILE="/var/run/sslh"
OPTIONS="--user nobody --pidfile $PIDFILE -p 0.0.0.0:8443 --ssl 127.0.0.1:443 --ssh 127.0.0.1:22"
if [ -f /etc/sysconfig/sslh ]; then
. /etc/sysconfig/sslh
fi
start() {
echo -n "Starting SSL-SSH-Switch: "
if [ -f $PIDFILE ]; then
PID=`cat $PIDFILE`
echo sslh already running: $PID
exit 2;
else
daemon $SSLH $OPTIONS
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch $PIDFILE
return $RETVAL
fi
PROGNAME=sslh
SSLH=${SSLH:-/usr/sbin/sslh-select}
SSLH_LANG=${SSLH_LANG:-C}
CONFIG=${CONFIG:-/etc/sslh.cfg}
PIDFILE=${PIDFILE:-/var/run/sslh/sslh.pid}
LOCKFILE=${LOCKFILE:-/var/lock/subsys/sslh}
STOP_TIMEOUT=${STOP_TIMEOUT:-10}
RETVAL=0
start() {
echo -n "Starting $PROGNAME: "
LANG=$SSLH_LANG daemon --pidfile="$PIDFILE" \
${SSLH_USER:+--user="${SSLH_USER}"} \
"$SSLH" ${CONFIG:+-F "$CONFIG"} "$OPTIONS"
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch "$LOCKFILE"
return $RETVAL
}
stop() {
echo -n "Shutting down SSL-SSH-Switch: "
echo
killproc sslh
echo
rm -f $PIDFILE
return 0
echo -n "Stopping $PROGNAME: "
killproc -p "$PIDFILE" -d "$STOP_TIMEOUT" "$SSLH"
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f "$LOCKFILE" "$PIDFILE"
}
# See how we were called.
case "$1" in
start)
start
@ -59,18 +59,17 @@ case "$1" in
stop
;;
status)
status sslh
status -p "$PIDFILE" "$SSLH"
RETVAL=$?
;;
restart)
stop
start
;;
*)
echo "Usage: {start|stop|status|restart}"
exit 1
echo "Usage: $PROGNAME {start|stop|status|restart}"
RETVAL=2
;;
esac
exit $?
exit $RETVAL

View File

@ -0,0 +1,36 @@
#
# The default processing model uses select
# A fork model is also available
#
#SSLH=/usr/sbin/sslh-select
#
# If transparent mode is enabled, the following
# is needed in order to run as sslh user
#
#SSLH_USER=sslh
#setcap cap_net_bind_service,cap_net_admin=+ep $SSLH
#
# Configuration file for sslh
# Set empty to disable configuration file support
#
#CONFIG=/etc/sslh.cfg
#
# Extra option to pass on comand line
# Those can supersede configuration file settings
#
#OPTIONS=
#
# The sslh process is started by default with the C
# locale, it can be changed here
#
#SSLH_LANG=C
#
# If an alternate location is specified in configuration
# file, it needs to be reported here
#
#PIDFILE=/var/run/sslh/sslh.pid

View File

@ -0,0 +1,9 @@
# Add the following to your fail2ban jail.conf
# In Debian you'd append it to /etc/fail2ban/jail.local
[sslh-ssh]
enabled = true
filter = sslh-ssh
action = iptables-multiport[name=sslh,port="443"]
logpath = /var/log/messages
maxretry = 5

View File

@ -0,0 +1,25 @@
# Add the following to you fail2ban configuration file
# In Debian it'd go in /etc/fail2ban/filter.d/sslh-ssh.conf
# Fail2Ban filter for sslh demultiplexed ssh
#
# Doesn't (and cannot) detect auth errors,
# but many connection attempts from the same
# origin is reason enough to block.
#
# Verion: 2014-03-28
[INCLUDES]
# no includes
[Definition]
failregex = ^.+ sslh\[.+\]: connection from <HOST>:.+ to .+ forwarded
from .+ to .+:ssh\s*$
ignoreregex =
# Author: Evert Mouw <post@evert.net>

View File

@ -0,0 +1,11 @@
[Unit]
Description=SSL/SSH multiplexer
After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/bin/sslh --foreground $DAEMON_OPTS
KillMode=process
[Install]
WantedBy=multi-user.target

View File

@ -69,47 +69,49 @@ void start_shoveler(int in_socket)
{
fd_set fds;
struct timeval tv;
struct addrinfo *saddr;
int res;
int res = PROBE_AGAIN;
int out_socket;
struct connection cnx;
struct proto *prot;
init_cnx(&cnx);
cnx.q[0].fd = in_socket;
FD_ZERO(&fds);
FD_SET(in_socket, &fds);
memset(&tv, 0, sizeof(tv));
tv.tv_sec = probing_timeout;
res = select(in_socket + 1, &fds, NULL, NULL, &tv);
if (res == -1)
perror("select");
cnx.q[0].fd = in_socket;
while (res == PROBE_AGAIN) {
/* POSIX does not guarantee that tv will be updated, but the client can
* only postpone the inevitable for so long */
res = select(in_socket + 1, &fds, NULL, NULL, &tv);
if (res == -1)
perror("select");
if (FD_ISSET(in_socket, &fds)) {
/* Received data: figure out what protocol it is */
prot = probe_client_protocol(&cnx);
} else {
/* Timed out: it's necessarily SSH */
prot = timeout_protocol();
if (FD_ISSET(in_socket, &fds)) {
/* Received data: figure out what protocol it is */
res = probe_client_protocol(&cnx);
} else {
/* Timed out: it's necessarily SSH */
cnx.proto = timeout_protocol();
break;
}
}
saddr = prot->saddr;
if (prot->service &&
check_access_rights(in_socket, prot->service)) {
if (cnx.proto->service &&
check_access_rights(in_socket, cnx.proto->service)) {
exit(0);
}
/* Connect the target socket */
out_socket = connect_addr(saddr, prot->description);
out_socket = connect_addr(&cnx, in_socket);
CHECK_RES_DIE(out_socket, "connect");
cnx.q[1].fd = out_socket;
log_connection(&cnx);
flush_defered(&cnx.q[1]);
flush_deferred(&cnx.q[1]);
shovel(&cnx);
@ -155,7 +157,8 @@ void main_loop(int listen_sockets[], int num_addr_listen)
if (!fork())
{
close(listen_sockets[i]);
for (i = 0; i < num_addr_listen; ++i)
close(listen_sockets[i]);
start_shoveler(in_socket);
exit(0);
}

View File

@ -1,7 +1,8 @@
/*
# main: processing of config file, command line options and start the main loop.
# main: processing of config file, command line options and start the main
# loop.
#
# Copyright (C) 2007-2012 Yves Rutschle
# Copyright (C) 2007-2014 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@ -24,7 +25,9 @@
#ifdef LIBCONFIG
#include <libconfig.h>
#endif
#ifdef LIBPCRE
#include <regex.h>
#endif
#include "common.h"
#include "probe.h"
@ -32,51 +35,82 @@
const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f] [-n] [-F <file>]\n"
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F <file>]\n"
"\t[-t <timeout>] [-P <pidfile>] -u <username> -p <add> [-p <addr> ...] \n" \
"%s\n\n" /* Dynamically built list of builtin protocols */ \
"\t[--on-timeout <addr>]\n" \
"-v: verbose\n" \
"-V: version\n" \
"-f: foreground\n" \
"-n: numeric output\n" \
"-u: specify under which user to run\n" \
"--transparent: behave as a transparent proxy\n" \
"-F: use configuration file\n" \
"-t: timeout before connecting to SSH.\n" \
"--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \
"-t: seconds to wait before connecting to --on-timeout address.\n" \
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
"-F: specify a configuration file\n" \
"-P: PID file.\n" \
"-i: Run as a inetd service.\n" \
"";
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
static struct option const_options[] = {
{ "inetd", no_argument, &inetd, 1 },
{ "foreground", no_argument, &foreground, 1 },
{ "background", no_argument, &background, 1 },
{ "transparent", no_argument, &transparent, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "verbose", no_argument, &verbose, 1 },
{ "user", required_argument, 0, 'u' },
{ "config", required_argument, 0, 'F' },
{ "config", optional_argument, 0, 'F' },
{ "pidfile", required_argument, 0, 'P' },
{ "timeout", required_argument, 0, 't' },
{ "on-timeout", required_argument, 0, OPT_ONTIMEOUT },
{ "listen", required_argument, 0, 'p' },
{}
};
static struct option* all_options;
static struct proto* builtins;
static const char *optstr = "vt:T:p:VP:F:";
static const char *optstr = "vt:T:p:VP:F::";
static void print_usage(void)
{
struct proto *p;
int i;
int res;
char *prots = "";
for (p = get_first_protocol(); p; p = p->next)
asprintf(&prots, "%s\t[--%s <addr>]\n", prots, p->description);
p = get_builtins();
for (i = 0; i < get_num_builtins(); i++) {
res = asprintf(&prots, "%s\t[--%s <addr>]\n", prots, p[i].description);
CHECK_RES_DIE(res, "asprintf");
}
fprintf(stderr, USAGE_STRING, prots);
}
static void printcaps(void) {
#ifdef LIBCAP
cap_t caps;
char* desc;
ssize_t len;
caps = cap_get_proc();
desc = cap_to_text(caps, &len);
fprintf(stderr, "capabilities: %s\n", desc);
cap_free(caps);
cap_free(desc);
#endif
}
static void printsettings(void)
{
char buf[NI_MAXHOST];
@ -85,10 +119,11 @@ static void printsettings(void)
for (p = get_first_protocol(); p; p = p->next) {
fprintf(stderr,
"%s addr: %s. libwrap service: %s family %d %d\n",
"%s addr: %s. libwrap service: %s log_level: %d family %d %d\n",
p->description,
sprintaddr(buf, sizeof(buf), p->saddr),
p->service,
p->log_level,
p->saddr->ai_family,
p->saddr->ai_addr->sa_family);
}
@ -96,7 +131,8 @@ static void printsettings(void)
for (a = addr_listen; a; a = a->ai_next) {
fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a));
}
fprintf(stderr, "timeout to ssh: %d\n", probing_timeout);
fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout,
timeout_protocol()->description);
}
@ -141,6 +177,7 @@ static int config_listen(config_t *config, struct addrinfo **listen)
#ifdef LIBCONFIG
static void setup_regex_probe(struct proto *p, config_setting_t* probes)
{
#ifdef LIBPCRE
int num_probes, errsize, i, res;
char *err;
const char * expr;
@ -168,6 +205,55 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
exit(1);
}
}
#else
fprintf(stderr, "line %d: regex probe specified but not compiled in\n", config_setting_source_line(probes));
exit(5);
#endif
}
#endif
#ifdef LIBCONFIG
static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char* name, int alpn)
{
int num_probes, i, max_server_name_len, server_name_len;
const char * config_item;
char** sni_hostname_list;
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);
return;
}
max_server_name_len = 0;
for (i = 0; i < num_probes; i++) {
server_name_len = strlen(config_setting_get_string_elem(config_items, i));
if(server_name_len > max_server_name_len)
max_server_name_len = server_name_len;
}
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
for (i = 0; i < num_probes; i++) {
config_item = config_setting_get_string_elem(config_items, i);
sni_hostname_list[i] = malloc(max_server_name_len);
strcpy (sni_hostname_list[i], config_item);
if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]);
}
p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list);
}
static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, config_setting_t* 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);
}
#endif
@ -177,7 +263,7 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
{
config_setting_t *setting, *prot, *probes;
config_setting_t *setting, *prot, *patterns, *sni_hostnames, *alpn_protocols;
const char *hostname, *port, *name;
int i, num_prots;
struct proto *p, *prev = NULL;
@ -193,36 +279,42 @@ static int config_protocols(config_t *config, struct proto **prots)
prot = config_setting_get_elem(setting, i);
if ((config_setting_lookup_string(prot, "name", &name) &&
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
)) {
p->description = name;
config_setting_lookup_string(prot, "service", &(p->service));
if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) {
p->log_level = 1;
}
resolve_split_name(&(p->saddr), hostname, port);
p->probe = get_probe(name);
if (!p->probe || !strcmp(name, "sni_alpn")) {
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
exit(1);
}
probes = config_setting_get_member(prot, "probe");
if (config_setting_is_array(probes)) {
/* If 'probe' is an array, setup a regex probe using the
* array of strings as pattern */
setup_regex_probe(p, probes);
} else {
/* if 'probe' is 'builtin', set the probe to the
* appropriate builtin protocol */
if (!strcmp(config_setting_get_string(probes), "builtin")) {
p->probe = get_probe(name);
if (!p->probe) {
fprintf(stderr, "%s: no builtin probe for this protocol\n", name);
exit(1);
}
} else {
fprintf(stderr, "%s: illegal probe name\n", name);
exit(1);
/* Probe-specific options: regex patterns */
if (!strcmp(name, "regex")) {
patterns = config_setting_get_member(prot, "regex_patterns");
if (patterns && config_setting_is_array(patterns)) {
setup_regex_probe(p, patterns);
}
}
/* Probe-specific options: SNI/ALPN */
if (!strcmp(name, "tls")) {
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);
}
}
}
}
}
@ -235,31 +327,47 @@ static int config_protocols(config_t *config, struct proto **prots)
* in: *filename
* out: *listen, a newly-allocated linked list of listen addrinfo
* *prots, a newly-allocated linked list of protocols
* 1 on error, 0 on success
*/
#ifdef LIBCONFIG
static int config_parse(char *filename, struct addrinfo **listen, struct proto **prots)
{
config_t config;
long int timeout;
int timeout;
const char* str;
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
fprintf(stderr, "%s:%d:%s\n",
/* If it's a parse error then there will be a line number for the failure
* an I/O error (such as non-existent file) will have the error line as 0
*/
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
exit(1);
exit(1);
}
fprintf(stderr, "%s:%s\n",
filename,
config_error_text(&config));
return 1;
}
config_lookup_bool(&config, "verbose", &verbose);
config_lookup_bool(&config, "inetd", &inetd);
config_lookup_bool(&config, "foreground", &foreground);
config_lookup_bool(&config, "numeric", &numeric);
config_lookup_bool(&config, "transparent", &transparent);
if (config_lookup_int(&config, "timeout", &timeout) == CONFIG_TRUE) {
if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) {
probing_timeout = timeout;
}
if (config_lookup_string(&config, "on-timeout", &str)) {
set_ontimeout(str);
}
config_lookup_string(&config, "user", &user_name);
config_lookup_string(&config, "pidfile", &pid_file);
@ -319,10 +427,22 @@ static void cmdline_config(int argc, char* argv[], struct proto** prots)
optind = 1;
opterr = 0; /* we're missing protocol options at this stage so don't output errors */
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 'v') {
verbose++;
}
if (c == 'F') {
config_filename = optarg;
/* find the end of the listen list */
res = config_parse(config_filename, &addr_listen, prots);
if (config_filename) {
res = config_parse(config_filename, &addr_listen, prots);
} else {
/* No configuration file specified -- try default file locations */
res = config_parse("/etc/sslh/sslh.cfg", &addr_listen, prots);
if (!res && verbose) fprintf(stderr, "Using /etc/sslh/sslh.cfg\n");
if (res) {
res = config_parse("/etc/sslh.cfg", &addr_listen, prots);
if (!res && verbose) fprintf(stderr, "Using /etc/sslh.cfg\n");
}
}
if (res)
exit(4);
break;
@ -386,6 +506,10 @@ next_arg:
probing_timeout = atoi(optarg);
break;
case OPT_ONTIMEOUT:
set_ontimeout(optarg);
break;
case 'p':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
@ -423,11 +547,15 @@ next_arg:
set_protocol_list(prots);
if (!addr_listen) {
if (!addr_listen && !inetd) {
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
/* Did command-line override foreground setting? */
if (background)
foreground = 0;
}
int main(int argc, char *argv[])
@ -471,15 +599,19 @@ int main(int argc, char *argv[])
setup_signals();
if (user_name)
drop_privileges(user_name);
if (pid_file)
write_pid_file(pid_file);
if (user_name)
drop_privileges(user_name);
/* Open syslog connection */
setup_syslog(argv[0]);
if (verbose)
printcaps();
main_loop(listen_sockets, num_addr_listen);
return 0;

View File

@ -64,14 +64,24 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
close(cnx->q[i].fd);
FD_CLR(cnx->q[i].fd, fds);
FD_CLR(cnx->q[i].fd, fds2);
if (cnx->q[i].defered_data)
free(cnx->q[i].defered_data);
if (cnx->q[i].deferred_data)
free(cnx->q[i].deferred_data);
}
}
init_cnx(cnx);
return 0;
}
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
* and FD_CLR. Need to drop connections if we go above that limit */
int fd_is_in_range(int fd) {
if (fd >= FD_SETSIZE) {
log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n");
return 0;
}
return 1;
}
/* Accepts a connection from the main socket and assigns it to an empty slot.
* If no slots are available, allocate another few. If that fails, drop the
* connexion */
@ -83,6 +93,9 @@ 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))
return -1;
res = set_nonblock(in_socket);
if (res == -1) return -1;
@ -116,18 +129,16 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_
/* Connect queue 1 of connection to SSL; returns new file descriptor */
int connect_queue(struct connection *cnx, struct addrinfo *addr,
const char* cnx_name,
fd_set *fds_r, fd_set *fds_w)
int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
{
struct queue *q = &cnx->q[1];
q->fd = connect_addr(addr, cnx_name);
if (q->fd != -1) {
q->fd = connect_addr(cnx, cnx->q[0].fd);
if ((q->fd != -1) && fd_is_in_range(q->fd)) {
log_connection(cnx);
set_nonblock(q->fd);
flush_defered(q);
if (q->defered_data) {
flush_deferred(q);
if (q->deferred_data) {
FD_SET(q->fd, fds_w);
} else {
FD_SET(q->fd, fds_r);
@ -177,13 +188,13 @@ int is_fd_active(int fd, fd_set* set)
}
/* Main loop: the idea is as follow:
* - fds_r and fds_w contain the file descritors to monitor in read and write
* - fds_r and fds_w contain the file descriptors to monitor in read and write
* - When a file descriptor goes off, process it: read from it, write the data
* to its corresponding pair.
* - When a file descriptor blocks when writing, remove the read fd from fds_r,
* move the data to a defered buffer, and add the write fd to fds_w. Defered
* move the data to a deferred buffer, and add the write fd to fds_w. Defered
* buffer is allocated dynamically.
* - When we can write to a file descriptor that has defered data, we try to
* - When we can write to a file descriptor that has deferred data, we try to
* write as much as we can. Once all data is written, remove the fd from fds_w
* and add its corresponding pair to fds_r, free the buffer.
*
@ -197,7 +208,6 @@ void main_loop(int listen_sockets[], int num_addr_listen)
struct timeval tv;
int max_fd, in_socket, i, j, res;
struct connection *cnx;
struct proto *prot;
int num_cnx; /* Number of connections in *cnx */
int num_probing = 0; /* Number of connections currently probing
* We use this to know if we need to time out of
@ -255,18 +265,19 @@ void main_loop(int listen_sockets[], int num_addr_listen)
if (cnx[i].q[0].fd != -1) {
for (j = 0; j < 2; j++) {
if (is_fd_active(cnx[i].q[j].fd, &writefds)) {
res = flush_defered(&cnx[i].q[j]);
res = flush_deferred(&cnx[i].q[j]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (cnx[i].state == ST_PROBING) num_probing--;
tidy_connection(&cnx[i], &fds_r, &fds_w);
if (verbose)
fprintf(stderr, "closed slot %d\n", i);
}
/* If no defered data is left, stop monitoring the fd
* for write, and restart monitoring the other one for reads*/
if (!cnx[i].q[j].defered_data_size) {
FD_CLR(cnx[i].q[j].fd, &fds_w);
FD_SET(cnx[i].q[1-j].fd, &fds_r);
} else {
/* If no deferred data is left, stop monitoring the fd
* for write, and restart monitoring the other one for reads*/
if (!cnx[i].q[j].deferred_data_size) {
FD_CLR(cnx[i].q[j].fd, &fds_w);
FD_SET(cnx[i].q[1-j].fd, &fds_r);
}
}
}
}
@ -289,27 +300,27 @@ void main_loop(int listen_sockets[], int num_addr_listen)
dump_connection(&cnx[i]);
exit(1);
}
num_probing--;
cnx[i].state = ST_SHOVELING;
/* If timed out it's SSH, otherwise the client sent
* data so probe the protocol */
if ((cnx[i].probe_timeout < time(NULL))) {
prot = timeout_protocol();
cnx[i].proto = timeout_protocol();
} else {
prot = probe_client_protocol(&cnx[i]);
res = probe_client_protocol(&cnx[i]);
if (res == PROBE_AGAIN)
continue;
}
num_probing--;
cnx[i].state = ST_SHOVELING;
/* libwrap check if required for this protocol */
if (prot->service &&
check_access_rights(in_socket, prot->service)) {
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],
prot->saddr,
prot->description,
&fds_r, &fds_w);
res = connect_queue(&cnx[i], &fds_r, &fds_w);
}
if (res >= max_fd)

137
sslh.pod
View File

@ -2,59 +2,40 @@
=head1 NAME
sslh - ssl/ssh multiplexer
sslh - protocol demultiplexer
=head1 SYNOPSIS
sslh [B<-F> I<config file>] [ B<-t> I<num> ] [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<-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<--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]
=head1 DESCRIPTION
B<sslh> accepts connections in HTTP, HTTPS, SSH, OpenVPN,
tinc, XMPP, or any other protocol that can be tested using a
regular expression, on the same port. This makes it possible
to connect to any of these servers on port 443 (e.g. from
inside a corporate firewall, which almost never block port
443) while still serving HTTPS on that port.
B<sslh> accepts connections on specified ports, and forwards
them further based on tests performed on the first data
packet sent by the remote client.
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
implemented, and any other protocol that can be tested using
a regular expression, can be recognised. A typical use case
is to allow serving several services on port 443 (e.g. to
connect to ssh from inside a corporate firewall, which
almost never block port 443) while still serving HTTPS on
that port.
The idea is to have B<sslh> listen to the external 443 port,
accept the incoming connections, work out what type of
connection it is, and then fordward to the appropriate
server.
=head2 Protocol detection
The protocol detection is made based on the first bytes sent
by the client: SSH connections start by identifying each
other's versions using clear text "SSH-2.0" strings (or
equivalent version strings). This is defined in RFC4253,
4.2. Meanwhile, OpenVPN clients start with 0x00 0x0D 0x38,
tinc clients start with "0 ", and XMPP client start with a
packet containing "jabber".
Additionally, two kind of SSH clients exist: the client
waits for the server to send its version string ("Shy"
client, which is the case of OpenSSH and Putty), or the
client sends its version first ("Bold" client, which is the
case of Bitvise Tunnelier and ConnectBot).
If the client stays quiet after the timeout period, B<sslh>
will connect to the first protocol defined (in the
configuration file, or on the command line), so SSH should
be defined first in B<sslh> configuration to accomodate for
shy SSH clients.
Hence B<sslh> acts as a protocol demultiplexer, or a
switchboard. Its name comes from its original function to
serve SSH and HTTPS on the same port.
=head2 Libwrap support
One drawback of B<sslh> is that the B<ssh> and B<httpd>
servers do not see the original IP address of the client
anymore, as the connection is forwarded through B<sslh>.
B<sslh> provides enough logging to circumvent that problem.
However it is common to limit access to B<ssh> using
B<libwrap> or B<tcpd>. For this reason, B<sslh> can be
compiled to check SSH accesses against SSH access lists as
defined in F</etc/hosts.allow> and F</etc/hosts.deny>.
One drawback of B<sslh> is that the servers do not see the
original IP address of the client anymore, as the connection
is forwarded through B<sslh>.
For this reason, B<sslh> can be compiled with B<libwrap> to
check accesses defined in F</etc/hosts.allow> and
F</etc/hosts.deny>. Libwrap services can be defined using
the configuration file.
=head2 Configuration file
@ -70,24 +51,55 @@ and the list of protocols).
The configuration file makes it possible to specify
protocols using regular expressions: a list of regular
expressions is given as the I<probe> parameter, and if the
expressions is given as the I<regex_patterns> parameter, and if the
first packet received from the client matches any of these
expressions, B<sslh> connects to that protocol.
Alternatively, the I<probe> parameter can be set to
"builtin", to use the compiled probes which are much faster
than regular expressions.
=head2 Probing protocols
When receiving an incoming connection, B<sslh> will read the
first bytes sent be 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
protocols will never be tried.
If no data is sent by the client, B<sslh> will eventually
time out and connect to the protocol specified with
B<--on-timeout>, or I<ssh> if none is specified.
=head2 Logging
As a security/authorization program, B<sslh> logs to the
LOG_AUTH facility, with priority LOG_INFO for normal
connections and LOG_ERR for failures.
=head1 OPTIONS
=over 4
=item B<-F> I<filename>, B<--config> I<filename>
Uses I<filename> has configuration file. If other
command-line options are specified, they will override the
configuration file's settings.
=item B<-t> I<num>, B<--timeout> I<num>
Timeout before forwarding the connection to the first
configured protocol (which should usually be SSH). Default
is 2s.
Timeout before forwarding the connection to the timeout
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'.
=item B<--transparent>
Makes B<sslh> behave as a transparent proxy, i.e. the
receiving service sees the original client's IP address.
This works on Linux only and involves B<iptables> settings.
Refer to the README for more information.
=item B<-p> I<listening address>, B<--listen> I<listening address>
@ -100,6 +112,8 @@ several addresses.
=item B<--ssl> I<target address>
=item B<--tls> I<target address>
Interface and port on which to forward SSL connection,
typically I<localhost:443>.
@ -107,6 +121,11 @@ Note that you can set B<sslh> to listen on I<ext_ip:443> and
B<httpd> to listen on I<localhost:443>: this allows clients
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
you wish to accept SSLv2, use B<--default> instead.
=item B<--ssh> I<target address>
Interface and port on which to forward SSH connections,
@ -122,6 +141,11 @@ typically I<localhost:1194>.
Interface and port on which to forward XMPP connections,
typically I<localhost:5222>.
=item B<--http> I<target address>
Interface and port on which to forward HTTP connections,
typically I<localhost:80>.
=item B<--tinc> I<target address>
Interface and port on which to forward tinc connections,
@ -130,6 +154,14 @@ typically I<localhost:655>.
This is experimental. If you use this feature, please report
the results (even if it works!)
=item B<--anyprot> I<target address>
Interface and port on which to forward if no other protocol
has been found. Because B<sslh> tries protocols in the order
specified on the command line, this should be specified
last. If no default is specified, B<sslh> will forward
unknown protocols to the first protocol specified.
=item B<-v>, B<--verbose>
Increase verboseness.
@ -165,6 +197,13 @@ Runs in foreground. The server will not fork and will remain connected
to the terminal. Messages normally sent to B<syslog> will also be sent
to I<stderr>.
=item B<--background>
Runs in background. This overrides B<foreground> if set in
the configuration file (or on the command line, but there is
no point setting both on the command line unless you have a
personality disorder).
=back
=head1 FILES

91
t
View File

@ -18,10 +18,11 @@ my $pidfile = "/tmp/sslh_test.pid";
my $SSL_CNX = 1;
my $SSH_SHY_CNX = 1;
my $SSH_BOLD_CNX = 1;
my $SSH_PROBE_AGAIN = 1;
my $SSL_MIX_SSH = 1;
my $SSH_MIX_SSL = 1;
my $BIG_MSG = 1;
my $STALL_CNX = 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
@ -57,15 +58,17 @@ for my $binary (@binaries) {
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 ./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
#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";
sleep 1; # valgrind can be heavy -- wait 5 seconds
sleep 5; # valgrind can be heavy -- wait 5 seconds
my $test_data = "hello world\n";
# my $ssl_test_data = (pack 'n', ((length $test_data) + 2)) . $test_data;
my $ssl_test_data = "\x16\x03\x03$test_data\n";
# Test: SSL connection
if ($SSL_CNX) {
@ -73,9 +76,10 @@ for my $binary (@binaries) {
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $test_data;
my $data = <$cnx_l>;
is($data, "ssl: $test_data", "SSL connection");
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection");
}
}
@ -105,13 +109,29 @@ for my $binary (@binaries) {
}
}
# 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";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $test_data;
print $cnx_l $ssl_test_data;
my $cnx_h= new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
@ -120,8 +140,9 @@ for my $binary (@binaries) {
my $data_h = <$cnx_h>;
is($data_h, "ssh: $test_data", "SSH during SSL being established");
}
my $data = <$cnx_l>;
is($data, "ssl: $test_data", "SSL connection interrupted by SSH");
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection interrupted by SSH");
}
}
@ -135,9 +156,10 @@ for my $binary (@binaries) {
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $test_data;
my $data = <$cnx_l>;
is($data, "ssl: $test_data", "SSL during SSH being established");
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");
}
print $cnx_h $test_data;
my $data = <$cnx_h>;
@ -151,19 +173,24 @@ for my $binary (@binaries) {
print "***Test: big message\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
my $test_data2 = "helloworld";
my $rept = 10000;
my $rept = 1000;
my $test_data2 = $ssl_test_data . ("helloworld"x$rept);
if (defined $cnx_l) {
print $cnx_l ($test_data2 x $rept);
print $cnx_l "\n";
my $data = <$cnx_l>;
is($data, "ssl: ". ($test_data2 x $rept) . "\n", "Big message");
my $n = syswrite $cnx_l, $test_data2;
my ($data);
$n = sysread $cnx_l, $data, 1 << 20;
is($data, "ssl: ". $test_data2, "Big message");
}
}
# 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");
@ -171,20 +198,21 @@ for my $binary (@binaries) {
my $cnx_2 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_2;
my $test_data2 = "helloworld";
my $rept = 10000;
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, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (1)");
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, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
$data = <$cnx_1>;
is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
}
}
@ -267,21 +295,6 @@ if ($RB_OPEN_PID_FILE) {
is($code, 3, "Exit status if can't open PID file");
}
# Robustness: Can't bind address
if ($RB_BIND_ADDRESS) {
print "***Test: Can't bind 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 74.125.39.106:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 1, "Exit status if can't bind address");
}
# Robustness: Can't resolve address
if ($RB_RESOLVE_ADDRESS) {
print "***Test: Can't resolve address\n";

327
tls.c Normal file
View File

@ -0,0 +1,327 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This is a minimal TLS implementation intended only to parse the server name
* extension. This was created based primarily on Wireshark dissection of a
* TLS handshake and RFC4366.
*/
#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include "tls.h"
#define TLS_HEADER_LEN 5
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01
#ifndef MIN
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
struct TLSProtocol {
int use_alpn;
char** sni_hostname_list;
char** alpn_protocol_list;
};
static int parse_extensions(const struct TLSProtocol *, const char *, size_t);
static int parse_server_name_extension(const struct TLSProtocol *, const char *, size_t);
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
static int has_match(char**, const char*, size_t);
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
* hello handshake, returning a status code
*
* Returns:
* >=0 - length of the hostname and updates *hostname
* caller is responsible for freeing *hostname
* -1 - Incomplete request
* -2 - No Host header included in this request
* -3 - Invalid hostname pointer
* < -4 - Invalid TLS client hello
*/
int
parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
char tls_content_type;
char tls_version_major;
char tls_version_minor;
size_t pos = TLS_HEADER_LEN;
size_t len;
/* Check that our TCP payload is at least large enough for a TLS header */
if (data_len < TLS_HEADER_LEN)
return -1;
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;
}
tls_version_major = data[1];
tls_version_minor = data[2];
if (tls_version_major < 3) {
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
tls_version_major, tls_version_minor);
return -2;
}
/* TLS record length */
len = ((unsigned char)data[3] << 8) +
(unsigned char)data[4] + TLS_HEADER_LEN;
data_len = MIN(data_len, len);
/* Check we received entire TLS record length */
if (data_len < len)
return -1;
/*
* Handshake
*/
if (pos + 1 > data_len) {
return -5;
}
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
if (verbose) fprintf(stderr, "Not a client hello\n");
return -5;
}
/* Skip past fixed length records:
1 Handshake Type
3 Length
2 Version (again)
32 Random
to Session ID Length
*/
pos += 38;
/* Session ID */
if (pos + 1 > data_len)
return -5;
len = (unsigned char)data[pos];
pos += 1 + len;
/* Cipher Suites */
if (pos + 2 > data_len)
return -5;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2 + len;
/* Compression Methods */
if (pos + 1 > data_len)
return -5;
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;
}
/* Extensions */
if (pos + 2 > data_len)
return -5;
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);
}
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;
if (tls_data == NULL)
return -3;
/* Parse each 4 bytes for the extension header */
while (pos + 4 <= data_len) {
/* Extension Length */
len = ((unsigned char) data[pos + 2] << 8) +
(unsigned char) data[pos + 3];
if (pos + 4 + len > data_len)
return -5;
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);
}
pos += 4 + len; /* Advance to the next extension header */
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return -2;
}
static int
parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
size_t pos = 2; /* skip server name list length */
size_t len;
while (pos + 3 < data_len) {
len = ((unsigned char)data[pos + 1] << 8) +
(unsigned char)data[pos + 2];
if (pos + 3 + len > data_len)
return -5;
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;
}
default:
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
data[pos]);
}
pos += 3 + len;
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return -2;
}
static int
parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
size_t pos = 2;
size_t len;
while (pos + 1 < data_len) {
len = (unsigned char)data[pos];
if (pos + 1 + len > data_len)
return -5;
if (len > 0 && has_match(tls_data->alpn_protocol_list, data + pos + 1, len)) {
return len;
} else if (len > 0) {
if (verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
}
pos += 1 + len;
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return -2;
}
static int
has_match(char** list, const char* name, size_t name_len) {
char **item;
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)) {
return 1;
}
}
return 0;
}
struct TLSProtocol *
new_tls_data() {
struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol));
if (tls_data != NULL) {
tls_data->use_alpn = -1;
}
return tls_data;
}
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;
} else {
tls_data->sni_hostname_list = list;
if(tls_data->use_alpn == 1)
tls_data->use_alpn = 2;
else
tls_data->use_alpn = 0;
}
return tls_data;
}

38
tls.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TLS_H
#define TLS_H
#include "common.h"
struct TLSProtocol;
int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len);
struct TLSProtocol *new_tls_data();
struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, char**);
#endif