Compare commits

..
64 Commits
Author SHA1 Message Date
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 Kuzník 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 Weißschuh).

	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
	(Sébastien Luttringer)
2013-07-10 23:16:50 +02:00
Yves Rutschle 9bcb2cdd7a v1.12: 08MAY2012
Added support for configuration file.

	New protocol probes can be defined using regular
	expressions that match the first packet sent by the
	client.

	sslh now connects timed out connections to the first
	configured protocol instead of 'ssh' (just make sure
	ssh is the first defined protocol).

	sslh now tries protocols in the order in which they
	are defined (just make sure sslh is the last defined
	protocol).
2013-07-10 23:15:38 +02:00
Yves Rutschle 26b4bcd089 v1.11: 21APR2012
WARNING: defaults have been removed for --user and
	--pidfile options, update your start-up scripts!

	No longer stop sslh when reverse DNS requests fail
	for logging.

	Added HTTP probe.

	No longer create new session if running in
	foreground.

	No longer default to changing user to 'nobody'. If
	--user isn't specified, just run as current user.

	No longer create PID file by default, it should be
	explicitely set with --pidfile.

	No longer log to syslog if in foreground. Logs are
	instead output to stderr.

	The four changes above make it straightforward to
	integrate sslh with systemd, and should help with
	launchd.
2013-07-10 23:14:48 +02:00
Yves Rutschle ae008179f0 v1.10:
Fixed calls referring to sockaddr length so they work
	with FreeBSD.

	Try target addresses in turn until one works if
	there are several (e.g. "localhost:22" resolves to
	an IPv6 address and an IPv4 address and sshd does
	not listen on IPv6).

	Fixed sslh-fork so killing the head process kills
	the listener processes.

	Heavily cleaned up test suite. Added stress test
	t_load script. Added coverage (requires lcov).

	Support for XMPP (Arnaud Gendre).

	Updated README.MacOSX (Aaron Madlon-Kay).
2013-07-10 23:14:15 +02:00
Yves Rutschle a9c9941988 v1.9: 02AUG2011
WARNING: Options changed, you'll need to update your
	start-up scripts! Log format changed, you'll need to
	update log processing scripts!

	Now supports IPv6 throughout (both on listening and
	forwarding)

	Logs now contain IPv6 addresses, local forwarding
	address, and resolves names (unless --numeric is
	specified).

	Introduced long options.

	Options -l, -s and -o replaced by their long
	counterparts.

	Defaults for SSL and SSH options suppressed (it's
	legitimate to want to use sslh to mux OpenVPN and
	tinc while not caring about SSH nor SSL).

	Bind to multiple addresses with multiple -p options.

	Support for tinc VPN (experimental).

	Numeric logging option.
2013-07-10 23:13:32 +02:00
Yves Rutschle 80f76c6fc5 v1.8:
Changed log format to make it possible to link
	connections to subsequent logs from other services.

	Updated CentOS init.d script (Andre Krajnik).

	Fixed zombie issue with OpenBSD (The SA_NOCLDWAIT flag is not
	propagated to the child process, so we set up signals after
	the fork.) (François FRITZ)

	Added -o "OpenVPN" and OpenVPN probing and support.

	Added single-threaded, select(2)-based version.

	Added support for "Bold" SSH clients (clients that speak first)
	Thanks to Guillaume Ricaud for spotting a regression
	bug.

	Added -f "foreground" option.

	Added test suite. (only tests connexions. No test for libwrap,
	setsid, setuid and so on) and corresponding 'make
	test' target.

	Added README.MacOSX (thanks Aaron Madlon-Kay)

	Documented use with proxytunnel and corkscrew in
	README.
2013-07-10 23:12:42 +02:00
Yves Rutschle 44f02ddf39 v1.7: 01FEB2010
Added CentOS init.d script (Andre Krajnik).

	Fixed default ssl address inconsistancy, now
	defaults to "localhost:443" and fixed documentation
	accordingly (pointed by Markus Schalke).

	Children no longer bind to the listen socket, so
	parent server can be stopped without killing an
	active child (pointed by Matthias Buecher).

	Inetd support (Dima Barsky).
2013-07-10 23:11:40 +02:00
Yves Rutschle 0658982705 v1.6: 25APR2009
Added -V, version option.
        Install target directory configurable in Makefile
        Changed syslog prefix in auth.log to "sslh[%pid]"
        Man page
        new 'make install' and 'make install-debian' targets
        PID file now specified using -P command line option
        Actually fixed zombie generation (the v1.5 patch got
        lost, doh!)
2013-07-10 23:10:43 +02:00
27 changed files with 3968 additions and 570 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
*.swp
*.swo
*.o
cscope.*
echosrv
sslh-fork
sslh-select
sslh.8.gz
tags
version.h
+256
View File
@@ -0,0 +1,256 @@
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.
New protocol probes can be defined using regular
expressions that match the first packet sent by the
client.
sslh now connects timed out connections to the first
configured protocol instead of 'ssh' (just make sure
ssh is the first defined protocol).
sslh now tries protocols in the order in which they
are defined (just make sure sslh is the last defined
protocol).
v1.11: 21APR2012
WARNING: defaults have been removed for --user and
--pidfile options, update your start-up scripts!
No longer stop sslh when reverse DNS requests fail
for logging.
Added HTTP probe.
No longer create new session if running in
foreground.
No longer default to changing user to 'nobody'. If
--user isn't specified, just run as current user.
No longer create PID file by default, it should be
explicitely set with --pidfile.
No longer log to syslog if in foreground. Logs are
instead output to stderr.
The four changes above make it straightforward to
integrate sslh with systemd, and should help with
launchd.
v1.10: 27NOV2011
Fixed calls referring to sockaddr length so they work
with FreeBSD.
Try target addresses in turn until one works if
there are several (e.g. "localhost:22" resolves to
an IPv6 address and an IPv4 address and sshd does
not listen on IPv6).
Fixed sslh-fork so killing the head process kills
the listener processes.
Heavily cleaned up test suite. Added stress test
t_load script. Added coverage (requires lcov).
Support for XMPP (Arnaud Gendre).
Updated README.MacOSX (Aaron Madlon-Kay).
v1.9: 02AUG2011
WARNING: This version does not work with FreeBSD and
derivatives!
WARNING: Options changed, you'll need to update your
start-up scripts! Log format changed, you'll need to
update log processing scripts!
Now supports IPv6 throughout (both on listening and
forwarding)
Logs now contain IPv6 addresses, local forwarding
address, and resolves names (unless --numeric is
specified).
Introduced long options.
Options -l, -s and -o replaced by their long
counterparts.
Defaults for SSL and SSH options suppressed (it's
legitimate to want to use sslh to mux OpenVPN and
tinc while not caring about SSH nor SSL).
Bind to multiple addresses with multiple -p options.
Support for tinc VPN (experimental).
Numeric logging option.
v1.8: 15JUL2011
Changed log format to make it possible to link
connections to subsequent logs from other services.
Updated CentOS init.d script (Andre Krajnik).
Fixed zombie issue with OpenBSD (The SA_NOCLDWAIT flag is not
propagated to the child process, so we set up signals after
the fork.) (François FRITZ)
Added -o "OpenVPN" and OpenVPN probing and support.
Added single-threaded, select(2)-based version.
Added support for "Bold" SSH clients (clients that speak first)
Thanks to Guillaume Ricaud for spotting a regression
bug.
Added -f "foreground" option.
Added test suite. (only tests connexions. No test for libwrap,
setsid, setuid and so on) and corresponding 'make
test' target.
Added README.MacOSX (thanks Aaron Madlon-Kay)
Documented use with proxytunnel and corkscrew in
README.
v1.7: 01FEB2010
Added CentOS init.d script (Andre Krajnik).
Fixed default ssl address inconsistancy, now
defaults to "localhost:443" and fixed documentation
accordingly (pointed by Markus Schalke).
Children no longer bind to the listen socket, so
parent server can be stopped without killing an
active child (pointed by Matthias Buecher).
Inetd support (Dima Barsky).
v1.6: 25APR2009
Added -V, version option.
Install target directory configurable in Makefile
Changed syslog prefix in auth.log to "sslh[%pid]"
Man page
new 'make install' and 'make install-debian' targets
PID file now specified using -P command line option
Actually fixed zombie generation (the v1.5 patch got
lost, doh!)
v1.5: 10DEC2008
Fixed zombie generation.
Added support scripts (), Makefile.
Changed all 'connexions' to 'connections' to please
pesky users. Damn users.
v1.4: 13JUL2008
Added libwrap support for ssh service (Christian Weinberger)
Only SSH is libwraped, not SSL.
v1.3: 14MAY2008
Added parsing for local interface to listen on
Changed default SSL connection to port 442 (443 doesn't make
sense as a default as we're already listening on 443)
Syslog incoming connections
v1.2: 12MAY2008
Fixed compilation warning for AMD64 (Thx Daniel Lange)
v1.1: 21MAY2007
Making sslhc more like a real daemon:
* If $PIDFILE is defined, write first PID to it upon startup
* Fork at startup (detach from terminal)
(thanks to http://www.enderunix.org/docs/eng/daemon.php -- good checklist)
* Less memory usage (?)
v1.0:
Basic functionality: privilege dropping, target hostnames and ports
configurable.
+84 -7
View File
@@ -1,23 +1,100 @@
# Configuration
USELIBWRAP=1 # Use libwrap?
VERSION=$(shell ./genver.sh -r)
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBWRAP= # Use libwrap?
USELIBCAP= # Use libcap?
COV_TEST= # Perform test coverage?
PREFIX=/usr/local
MAN=sslh.8.gz # man page name
# End of configuration -- the rest should take care of
# itself
CC = gcc
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC ?= gcc
CFLAGS ?=-Wall -g $(CFLAGS_COV)
#LIBS=-lnet
LIBS=
OBJS=common.o sslh-main.o probe.o
ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap
CFLAGS=-DLIBWRAP
CPPFLAGS+=-DLIBWRAP
endif
all:
$(CC) $(CFLAGS) -o sslh sslh.c $(LIBS)
strip sslh
ifneq ($(strip $(USELIBCONFIG)),)
LIBS:=$(LIBS) -lconfig
CPPFLAGS+=-DLIBCONFIG
endif
ifneq ($(strip $(USELIBCAP)),)
LIBS:=$(LIBS) -lcap
CPPFLAGS+=-DLIBCAP
endif
all: sslh $(MAN) echosrv
.c.o: *.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
version.h:
./genver.sh >version.h
sslh: sslh-fork sslh-select
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
#strip sslh-fork
sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
echosrv: $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.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 -pD sslh-fork $(DESTDIR)$(PREFIX)/sbin/sslh
install -pD -m 0644 $(MAN) $(DESTDIR)$(PREFIX)/share/man/man8/$(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 $(DESTDIR)$(PREFIX)/sbin/sslh $(DESTDIR)$(PREFIX)/share/man/man8/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh
update-rc.d sslh remove
distclean: clean
rm -f tags cscope.*
clean:
rm -f sslh-fork sslh-select echosrv version.h $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags --globals -T *.[ch]
cscope:
-find . -name "*.[chS]" >cscope.files
-cscope -b -R
test:
./t
-83
View File
@@ -1,83 +0,0 @@
sslh -- A ssl/ssh multiplexer.
sslh lets one accept both HTTPS and SSH connections on the
same port. It makes it possible to connect to an SSH server
on port 443 (e.g. from inside a corporate firewall) while
still serving HTTPS on that port.
Compilation instructions:
Solaris:
cc -o sslh sslh.c -lresolv -lsocket -lnsl
LynxOS:
gcc -o tcproxy tcproxy.c -lnetinet
Linux:
cc -o sslh sslh.c -lnet
or:
cc -o sslh sslh.c
To compile with libwrap support:
cc -o sslh -DLIBWRAP sslh.c -lwrap
To install:
make
cp sslh /usr/local/sbin
cp scripts/etc.init.d.sslh /etc/init.d/sslh
cp scripts/etc.default.sslh /etc/default/sslh
You can edit settings in /etc/default/sslh:
PIDFILE=/var/run/sslh.pid
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: 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.
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.
Comments? questions? sslh@rutschle.net
HISTORY
v1.5: 10DEC2008
Fixed zombie generation.
Added support scripts (), Makefile.
Changed all 'connexions' to 'connections' to please
pesky users. Damn users.
v1.4: 13JUL2008
Added libwrap support for ssh service (Christian Weinberger)
Only SSH is libwraped, not SSL.
v1.3: 14MAY2008
Added parsing for local interface to listen on
Changed default SSL connection to port 442 (443 doesn't make
sense as a default as we're already listening on 443)
Syslog incoming connections
v1.2: 12MAY2008
Fixed compilation warning for AMD64 (Thx Daniel Lange)
v1.1: 21MAY2007
Making sslhc more like a real daemon:
* If $PIDFILE is defined, write first PID to it upon startup
* Fork at startup (detach from terminal)
(thanks to http://www.enderunix.org/docs/eng/daemon.php -- good checklist)
* Less memory usage (?)
v1.0:
* Basic functionality: privilege dropping, target hostnames and ports
configurable.
+54
View File
@@ -0,0 +1,54 @@
sslh is available for Mac OS X via MacPorts. If you have
MacPorts installed on your system you can install sslh by
executing the following in the Terminal:
port install sslh
Also, the following is a helpful launchd configuration that
covers the most common use case of sslh. Save the following
into a text file, e.g.
/Library/LaunchDaemons/net.rutschle.sslh.plist, then load it
with launchctl or simply reboot.
----BEGIN FILE----
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Disabled</key>
<false/>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>net.rutschle.sslh</string>
<key>ProgramArguments</key>
<array>
<string>/opt/local/sbin/sslh</string>
<string>-f</string>
<string>-v</string>
<string>-u</string>
<string>nobody</string>
<string>-p</string>
<string>0.0.0.0:443</string>
<string>--ssh</string>
<string>localhost:22</string>
<string>--ssl</string>
<string>localhost:443</string>
</array>
<key>QueueDirectories</key>
<array/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/Library/Logs/sslh.log</string>
<key>StandardOutPath</key>
<string>/Library/Logs/sslh.log</string>
<key>WatchPaths</key>
<array/>
</dict>
</plist>
----END FILE----
+261
View File
@@ -0,0 +1,261 @@
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 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
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
=============
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 --> 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 (only?) you can use the `--transparent` option to
request transparent proying. 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).
`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
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
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
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
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"; probe: "builtin"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
{ name: "ssl"; host: "localhost"; port: "443"; probe: "builtin"; },
{ name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
);
+603
View File
@@ -0,0 +1,603 @@
/* Code and variables that is common to both fork and select-based
* servers.
*
* No code here should assume whether sockets are blocking or not.
**/
#define _GNU_SOURCE
#include <stdarg.h>
#include "common.h"
#include "probe.h"
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
/*
* Settings that depend on the command line. They're set in main(), but also
* used in other places in common.c, and it'd be heavy-handed to pass it all as
* parameters
*/
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;
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
#ifdef LIBWRAP
#include <tcpd.h>
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)
{
char buf[NI_MAXHOST];
if (res == -1) {
fprintf(stderr, "%s:%s: %s\n",
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
exit(1);
}
}
/* Starts listening sockets on specified addresses.
* IN: addr[], num_addr
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
* Returns number of addresses bound
* Bound file descriptors are returned in newly-allocated *sockfd pointer
*/
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
{
struct sockaddr_storage *saddr;
struct addrinfo *addr;
int i, res, one;
int num_addr = 0;
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;
if (verbose)
fprintf(stderr, "listening to %d addresses\n", num_addr);
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
if (!addr) {
fprintf(stderr, "FATAL: Inconsistent listen number. This should not happen.\n");
exit(1);
}
saddr = (struct sockaddr_storage*)addr->ai_addr;
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dumpdie((*sockfd)[i], addr, "socket");
one = 1;
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
check_res_dumpdie(res, addr, "setsockopt(SO_REUSEADDR)");
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");
res = listen ((*sockfd)[i], 50);
check_res_dumpdie(res, addr, "listen");
}
return num_addr;
}
/* 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 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");
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
CHECK_RES_DIE(res, "setsockopt");
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;
char buf[NI_MAXHOST];
int fd, res;
for (a = cnx->proto->saddr; a; a = a->ai_next) {
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);
fd = socket(a->ai_family, SOCK_STREAM, 0);
if (fd == -1) {
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
cnx->proto->description, strerror(errno));
} 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->proto->description, strerror(errno));
} else {
return fd;
}
}
}
return -1;
}
/* 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 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;
}
/* tries to flush some of the data for specified queue
* Upon success, the number of bytes written is returned.
* Upon failure, -1 returned (e.g. connexion closed)
* */
int flush_deferred(struct queue *q)
{
int n;
if (verbose)
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
n = write(q->fd, q->deferred_data, q->deferred_data_size);
if (n == -1)
return n;
if (n == q->deferred_data_size) {
/* All has been written -- release the memory */
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->deferred_data += n;
q->deferred_data_size -= n;
}
return n;
}
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 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
*
* 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.
*/
int fd2fd(struct queue *target_q, struct queue *from_q)
{
char buffer[BUFSIZ];
int target, from, size_r, size_w;
target = target_q->fd;
from = from_q->fd;
size_r = read(from, buffer, sizeof(buffer));
if (size_r == -1) {
switch (errno) {
case EAGAIN:
if (verbose)
fprintf(stderr, "reading 0 from %d\n", from);
return FD_NODATA;
case ECONNRESET:
case EPIPE:
return FD_CNXCLOSED;
}
}
CHECK_RES_RETURN(size_r, "read");
if (size_r == 0)
return FD_CNXCLOSED;
size_w = write(target, buffer, size_r);
/* process -1 when we know how to deal with it */
if (size_w == -1) {
switch (errno) {
case EAGAIN:
/* write blocked: Defer data */
defer_write(target_q, buffer, size_r);
return FD_STALLED;
case ECONNRESET:
case EPIPE:
/* remove end closed -- drop the connection */
return FD_CNXCLOSED;
}
} else if (size_w < size_r) {
/* incomplete write -- defer the rest of the data */
defer_write(target_q, buffer + size_w, size_r - size_w);
return FD_STALLED;
}
CHECK_RES_RETURN(size_w, "write");
return size_w;
}
/* returns a string that prints the IP and port of the sockaddr */
char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
{
char host[NI_MAXHOST], serv[NI_MAXSERV];
int res;
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
if (res) {
log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
/* Name resolution failed: do it numerically instead */
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV);
/* should not fail but... */
if (res) {
log_message(LOG_ERR, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
strcpy(host, "?");
strcpy(serv, "?");
}
}
snprintf(buf, size, "%s:%s", host, serv);
return buf;
}
/* Turns a hostname and port (or service) into a list of struct addrinfo
* returns 0 on success, -1 otherwise and logs error
**/
int resolve_split_name(struct addrinfo **out, const char* host, const char* serv)
{
struct addrinfo hint;
int res;
memset(&hint, 0, sizeof(hint));
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
res = getaddrinfo(host, serv, &hint, out);
if (res)
log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv);
return res;
}
/* turns a "hostname:port" string into a list of struct addrinfo;
out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done
fullname: input string -- it gets clobbered
*/
void resolve_name(struct addrinfo **out, char* fullname)
{
char *serv, *host;
int res;
char *sep = strrchr(fullname, ':');
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;
res = resolve_split_name(out, host, serv);
if (res) {
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
if (res == EAI_SERVICE)
fprintf(stderr, "(Check you have specified all ports)\n");
exit(4);
}
}
/* Log to syslog or stderr if foreground */
void log_message(int type, char* msg, ...)
{
va_list ap;
va_start(ap, msg);
if (foreground)
vfprintf(stderr, msg, ap);
else
vsyslog(type, msg, ap);
va_end(ap);
}
/* syslogs who connected to where */
void log_connection(struct connection *cnx)
{
struct addrinfo addr;
struct sockaddr_storage ss;
#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
int res;
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; /* Can happen if connection drops before we get here.
In that case, don't log anything (there is no connection) */
sprintaddr(peer, sizeof(peer), &addr);
addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(service, sizeof(service), &addr);
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(target, sizeof(target), &addr);
addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(local, sizeof(local), &addr);
log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n",
peer,
service,
local,
target);
}
/* libwrap (tcpd): check the connection is legal. This is necessary because
* the actual server will only see a connection coming from localhost and can't
* apply the rules itself.
*
* Returns -1 if access is denied, 0 otherwise
*/
int check_access_rights(int in_socket, const char* service)
{
#ifdef LIBWRAP
struct sockaddr peeraddr;
socklen_t size = sizeof(peeraddr);
char addr_str[NI_MAXHOST], host[NI_MAXHOST];
int res;
res = getpeername(in_socket, &peeraddr, &size);
CHECK_RES_RETURN(res, "getpeername");
/* extract peer address */
res = getnameinfo(&peeraddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
if (res) {
if (verbose)
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
strcpy(addr_str, STRING_UNKNOWN);
}
/* extract peer name */
strcpy(host, STRING_UNKNOWN);
if (!numeric) {
res = getnameinfo(&peeraddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
if (res) {
if (verbose)
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
}
}
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
if (verbose)
fprintf(stderr, "access denied\n");
log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str);
close(in_socket);
return -1;
}
#endif
return 0;
}
void setup_signals(void)
{
int res;
struct sigaction action;
/* Request no SIGCHLD is sent upon termination of
* the children */
memset(&action, 0, sizeof(action));
action.sa_handler = NULL;
action.sa_flags = SA_NOCLDWAIT;
res = sigaction(SIGCHLD, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
/* 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;
res = sigaction(SIGTERM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
/* Ignore SIGPIPE . */
action.sa_handler = SIG_IGN;
res = sigaction(SIGPIPE, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
}
/* Open syslog connection with appropriate banner;
* banner is made up of basename(bin_name)+"[pid]" */
void setup_syslog(const char* bin_name) {
char *name1, *name2;
int res;
name1 = strdup(bin_name);
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) */
log_message(LOG_INFO, "%s %s started\n", server_type, VERSION);
}
/* 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;
struct passwd *pw = getpwnam(user_name);
if (!pw) {
fprintf(stderr, "%s: not found\n", user_name);
exit(2);
}
if (verbose)
fprintf(stderr, "turning into %s\n", user_name);
set_keepcaps(1);
res = setgid(pw->pw_gid);
CHECK_RES_DIE(res, "setgid");
res = setuid(pw->pw_uid);
CHECK_RES_DIE(res, "setuid");
set_capabilities();
set_keepcaps(0);
}
/* Writes my PID */
void write_pid_file(const char* pidfile)
{
FILE *f;
f = fopen(pidfile, "w");
if (!f) {
perror(pidfile);
exit(3);
}
fprintf(f, "%d\n", getpid());
fclose(f);
}
+129
View File
@@ -0,0 +1,129 @@
#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>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <syslog.h>
#include <libgen.h>
#include <time.h>
#include <getopt.h>
#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); \
exit(1); \
}
#define CHECK_RES_RETURN(res, str) \
if (res == -1) { \
log_message(LOG_CRIT, "%s:%d:%s\n", str, errno, strerror(errno)); \
return res; \
}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#if 1
#define TRACE fprintf(stderr, "%s:%d\n", __FILE__, __LINE__);
#else
#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 */
};
/* this is used to pass protocols through the command-line parameter parsing */
#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
/* A 'queue' is composed of a file descriptor (which can be read from or
* written to), and a queue for deferred write data */
struct queue {
int fd;
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);
* */
struct queue q[2];
};
#define FD_CNXCLOSED 0
#define FD_NODATA -1
#define FD_STALLED -2
/* common.c */
void init_cnx(struct connection *cnx);
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);
void log_connection(struct connection *cnx);
int check_access_rights(int in_socket, const char* service);
void setup_signals(void);
void setup_syslog(const char* bin_name);
void drop_privileges(const char* user_name);
void write_pid_file(const char* pidfile);
void log_message(int type, char* msg, ...);
void dump_connection(struct connection *cnx);
int resolve_split_name(struct addrinfo **out, const char* hostname, const char* port);
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
int defer_write(struct queue *q, void* data, int data_size);
int flush_deferred(struct queue *q);
extern int probing_timeout, verbose, inetd, foreground,
background, transparent, numeric;
extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING;
extern const char* user_name, *pid_file;
extern const char* server_type;
/* sslh-fork.c */
void start_shoveler(int);
void main_loop(int *listen_sockets, int num_addr_listen);
#endif
+160
View File
@@ -0,0 +1,160 @@
/* echosrv: a simple line echo server with optional prefix adding.
*
* echsrv --listen localhost6:1234 --prefix "ssl: "
*
* This will bind to 1234, and echo every line pre-pending "ssl: ". This is
* used for testing: we create several such servers with different prefixes,
* then we connect test clients that can then check they get the proper data
* back (thus testing that shoveling works both ways) with the correct prefix
* (thus testing it connected to the expected service).
* **/
#define _GNU_SOURCE
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include "common.h"
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
const char* USAGE_STRING =
"echosrv\n" \
"usage:\n" \
"\techosrv [-v] --listen <address:port> [--prefix <prefix>]\n"
"-v: verbose\n" \
"--listen: address to listen on. Can be specified multiple times.\n" \
"--prefix: add specified prefix before every line echoed.\n"
"";
const char* server_type = "echsrv"; /* keep setup_syslog happy */
/*
* Settings that depend on the command line.
*/
char* prefix = "";
int port;
void parse_cmdline(int argc, char* argv[])
{
int c;
struct option options[] = {
{ "verbose", no_argument, &verbose, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "listen", required_argument, 0, 'l' },
{ "prefix", required_argument, 0, 'p' },
};
struct addrinfo **a;
while ((c = getopt_long_only(argc, argv, "l:p:", options, NULL)) != -1) {
if (c == 0) continue;
switch (c) {
case 'l':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
/* append the specified addresses */
resolve_name(a, optarg);
break;
case 'p':
prefix = optarg;
break;
default:
fprintf(stderr, "%s", USAGE_STRING);
exit(2);
}
}
if (!addr_listen) {
fprintf(stderr, "No listening port specified\n");
exit(1);
}
}
void start_echo(int fd)
{
int res;
char buffer[1 << 20];
int ret, prefix_len;
prefix_len = strlen(prefix);
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, prefix);
while (1) {
ret = read(fd, buffer + prefix_len, sizeof(buffer));
if (ret == -1) {
fprintf(stderr, "%s", strerror(errno));
return;
}
res = write(fd, buffer, ret + prefix_len);
if (res < 0) {
fprintf(stderr, "%s", strerror(errno));
return;
}
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
{
int in_socket, i;
for (i = 0; i < num_addr_listen; i++) {
if (!fork()) {
while (1)
{
in_socket = accept(listen_sockets[i], 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
if (!fork())
{
close(listen_sockets[i]);
start_echo(in_socket);
exit(0);
}
close(in_socket);
}
}
}
wait(NULL);
}
int main(int argc, char *argv[])
{
extern char *optarg;
extern int optind;
int num_addr_listen;
int *listen_sockets;
parse_cmdline(argc, argv);
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
main_loop(listen_sockets, num_addr_listen);
return 0;
}
+53
View File
@@ -0,0 +1,53 @@
# 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;
transparent: false;
timeout: 2;
user: "nobody";
pidfile: "/var/run/sslh.pid";
# List of interfaces on which we should listen
listen:
(
{ host: "thelonious"; port: "443"; },
{ host: "thelonious"; port: "8080"; }
);
# List of protocols
#
# Each protocol entry consists of:
# name: name of the protocol
# 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
# (can be left out, e.g. to use with on-timeout)
#
# sslh will try each probe in order they are declared, and
# connect to the first that matches.
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: "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 a
# protocol with no probe, as is the case with this example.
# This enables you to set a tcpd service name for this
# protocol too.
on-timeout: "timeout";
Executable
+40
View File
@@ -0,0 +1,40 @@
#! /bin/bash
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. Github creates the zip file with all
# files dated from the last change: use the
# Makefile's modification time as a release number
release=zip-`stat -c "%y" Makefile | sed 's/ .*//'`
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
+311
View File
@@ -0,0 +1,311 @@
/*
# probe.c: Code for probing protocols
#
# Copyright (C) 2007-2012 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <regex.h>
#include <ctype.h>
#include "probe.h"
static int is_ssh_protocol(const char *p, int len, struct proto*);
static int is_openvpn_protocol(const char *p, int len, struct proto*);
static int is_tinc_protocol(const char *p, int len, struct proto*);
static int is_xmpp_protocol(const char *p, int len, struct proto*);
static int is_http_protocol(const char *p, int len, struct proto*);
static int is_tls_protocol(const char *p, int len, struct proto*);
static int is_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_tls_protocol },
{ "tls", NULL, NULL, is_tls_protocol },
{ "anyprot", NULL, NULL, is_true }
};
static struct proto *protocols;
static char* on_timeout = "ssh";
struct proto* get_builtins(void) {
return builtins;
}
int get_num_builtins(void) {
return ARRAY_SIZE(builtins);
}
/* Sets the protocol name to connect to in case of timeout */
void set_ontimeout(const char* name)
{
int res = asprintf(&on_timeout, "%s", name);
CHECK_RES_DIE(res, "asprintf");
}
/* Returns the protocol to connect to in case of timeout;
* if not found, return the first protocol specified
*/
struct proto* timeout_protocol(void)
{
struct 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) */
struct proto* get_first_protocol(void)
{
return protocols;
}
void set_protocol_list(struct proto* prots)
{
protocols = prots;
}
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
#define HEXDUMP_COLS 16
void hexdump(const char *mem, unsigned int len)
{
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 (len < 4)
return PROBE_AGAIN;
return !strncmp(p, "SSH-", 4);
}
/* Is the buffer the beginning of an OpenVPN connection?
*
* 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)
{
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)
* */
static int is_tinc_protocol( const char *p, int len, struct proto *proto)
{
if (len < 2)
return PROBE_AGAIN;
return !strncmp(p, "0 ", 2);
}
/* Is the buffer the beginning of a jabber (XMPP) connections?
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
* clients, just checking first frame containing "jabber" in xml entity)
* */
static int is_xmpp_protocol( const char *p, int len, struct proto *proto)
{
if (len < 6)
return PROBE_AGAIN;
return memmem(p, len, "jabber", 6) ? 1 : 0;
}
static int probe_http_method(const char *p, int len, const char *opt)
{
if (len < strlen(opt))
return PROBE_AGAIN;
return !strncmp(p, opt, len);
}
/* 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 (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("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");
#undef PROBE_HTTP_METHOD
return PROBE_NEXT;
}
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 regex_probe(const char *p, int len, struct proto *proto)
{
regex_t **probe = proto->data;
regmatch_t pos = { 0, len };
for (; *probe && regexec(*probe, p, 0, &pos, REG_STARTEND); probe++)
/* try them all */;
return (probe != NULL);
}
/*
* Read the beginning of data coming from the client connection and check if
* it's a known protocol.
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
* which case cnx->proto is set to the appropriate protocol.
*/
int probe_client_protocol(struct connection *cnx)
{
char buffer[BUFSIZ];
struct proto *p;
int n;
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
/* It's possible that read() returns an error, e.g. if the client
* disconnected between the previous call to select() and now. If that
* happens, we just connect to the default protocol so the caller of this
* function does not have to deal with a specific failure condition (the
* connection will just fail later normally). */
if (n > 0) {
int res = PROBE_NEXT;
defer_write(&cnx->q[1], buffer, n);
for (p = cnx->proto; p && res == PROBE_NEXT; p = p->next) {
if (! p->probe) continue;
if (verbose) fprintf(stderr, "probing for %s\n", p->description);
cnx->proto = p;
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
}
if (res != PROBE_NEXT)
return res;
}
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
/* If none worked, return the first one affected (that's completely
* arbitrary) */
cnx->proto = protocols;
return PROBE_MATCH;
}
/* Returns the structure for specified protocol or NULL if not found */
static struct proto* get_protocol(const char* description)
{
int i;
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
if (!strcmp(builtins[i].description, description)) {
return &builtins[i];
}
}
return NULL;
}
/* Returns the probe for specified protocol:
* parameter is the description in builtins[], or "regex"
* */
T_PROBE* get_probe(const char* description) {
struct proto* p = get_protocol(description);
if (p)
return p->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;
return NULL;
}
+65
View File
@@ -0,0 +1,65 @@
/* API for probe.c */
#ifndef __PROBE_H_
#define __PROBE_H_
#include "common.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*);
/* For each protocol we need: */
struct proto {
const char* description; /* a string that says what it is (for logging and command-line parsing) */
const char* service; /* service name to do libwrap checks */
struct addrinfo *saddr; /* list of addresses to try and switch that protocol */
/* 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 */
struct proto *next; /* pointer to next protocol in list, NULL if last */
};
/* Returns a pointer to the array of builtin protocols */
struct proto * get_builtins(void);
/* Returns the number of builtin protocols */
int get_num_builtins(void);
/* Returns the probe for specified protocol */
T_PROBE* get_probe(const char* description);
/* Returns the head of the configured protocols */
struct proto* get_first_protocol(void);
/* Set the list of configured protocols */
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 deferred
* write buffer of the connection and returns a pointer to the protocol
* structure
*/
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
*
* Returns the protocol to connect to in case of timeout
*/
struct proto* timeout_protocol(void);
void hexdump(const char*, unsigned int);
#endif
+2 -1
View File
@@ -1,4 +1,5 @@
PIDFILE=/var/run/sslh.pid
LISTEN=ifname:443
SSH=localhost:22
SSL=localhost:443
USER=nobody
PID=/var/run/sslh.pid
+7 -10
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
@@ -13,23 +15,19 @@ facility=user.info
# /etc/init.d/sslh: start and stop the sslh proxy daemon
# Defaults -- can be overridden in /etc/default/sslh
PIDFILE=/var/run/sslh.pid
LISTEN=thelonious:443
SSH=localhost:22
SSL=localhost:443
if test -f /etc/default/sslh; then
. /etc/default/sslh
export PIDFILE=${PIDFILE}
fi
DAEMON=/usr/local/sbin/sslh
# The prefix is normally filled by make install. If
# installing by hand, fill it in yourself!
PREFIX=
DAEMON=$PREFIX/sbin/sslh
start()
{
echo "Start services: sslh"
$DAEMON -u nobody -p ${LISTEN} -s ${SSH} -l ${SSL}
$DAEMON --user ${USER} --pidfile ${PID} --listen ${LISTEN} --ssh ${SSH} --ssl ${SSL}
logger -t ${tag} -p ${facility} -i 'Started sslh'
}
@@ -37,7 +35,6 @@ stop()
{
echo "Stop services: sslh"
killall $DAEMON
rm ${PIDFILE}
logger -t ${tag} -p ${facility} -i 'Stopped sslh'
}
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
#
# sslh Startup script for the SSL/SSH multiplexer
#
# 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
#
# Authors:
# Andre Krajnik akrajnik@gmail.com - 2010-03-20
# Julien Thomas julthomas@free.fr - 2013-08-25
# Source function library.
. /etc/init.d/functions
if [ -f /etc/sysconfig/sslh ]; then
. /etc/sysconfig/sslh
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 "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
;;
stop)
stop
;;
status)
status -p "$PIDFILE" "$SSLH"
RETVAL=$?
;;
restart)
stop
start
;;
*)
echo "Usage: $PROGNAME {start|stop|status|restart}"
RETVAL=2
;;
esac
exit $RETVAL
+36
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
+11
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
+184
View File
@@ -0,0 +1,184 @@
/*
sslh-fork: forking server
# Copyright (C) 2007-2012 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#include "common.h"
#include "probe.h"
const char* server_type = "sslh-fork";
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
/* shovels data from one fd to the other and vice-versa
returns after one socket closed
*/
int shovel(struct connection *cnx)
{
fd_set fds;
int res, i;
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
FD_ZERO(&fds);
while (1) {
FD_SET(cnx->q[0].fd, &fds);
FD_SET(cnx->q[1].fd, &fds);
res = select(
max_fd,
&fds,
NULL,
NULL,
NULL
);
CHECK_RES_DIE(res, "select");
for (i = 0; i < 2; i++) {
if (FD_ISSET(cnx->q[i].fd, &fds)) {
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
if (!res) {
if (verbose)
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
return res;
}
}
}
}
}
/* Child process that finds out what to connect to and proxies
*/
void start_shoveler(int in_socket)
{
fd_set fds;
struct timeval tv;
int res = PROBE_AGAIN;
int out_socket;
struct connection cnx;
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;
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 */
res = probe_client_protocol(&cnx);
} else {
/* Timed out: it's necessarily SSH */
cnx.proto = timeout_protocol();
break;
}
}
if (cnx.proto->service &&
check_access_rights(in_socket, cnx.proto->service)) {
exit(0);
}
/* Connect the target socket */
out_socket = connect_addr(&cnx, in_socket);
CHECK_RES_DIE(out_socket, "connect");
cnx.q[1].fd = out_socket;
log_connection(&cnx);
flush_deferred(&cnx.q[1]);
shovel(&cnx);
close(in_socket);
close(out_socket);
if (verbose)
fprintf(stderr, "connection closed down\n");
exit(0);
}
static int *listener_pid;
static int listener_pid_number = 0;
void stop_listeners(int sig)
{
int i;
for (i = 0; i < listener_pid_number; i++) {
kill(listener_pid[i], sig);
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
{
int in_socket, i, res;
struct sigaction action;
listener_pid_number = num_addr_listen;
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
/* Start one process for each listening address */
for (i = 0; i < num_addr_listen; i++) {
if (!(listener_pid[i] = fork())) {
/* Listening process just accepts a connection, forks, and goes
* back to listening */
while (1)
{
in_socket = accept(listen_sockets[i], 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
if (!fork())
{
for (i = 0; i < num_addr_listen; ++i)
close(listen_sockets[i]);
start_shoveler(in_socket);
exit(0);
}
close(in_socket);
}
}
}
/* Set SIGTERM to "stop_listeners" which further kills all listener
* processes. Note this won't kill processes that listeners forked, which
* means active connections remain active. */
memset(&action, 0, sizeof(action));
action.sa_handler = stop_listeners;
res = sigaction(SIGTERM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
wait(NULL);
}
/* The actual main is in common.c: it's the same for both version of
* the server
*/
+538
View File
@@ -0,0 +1,538 @@
/*
# main: processing of config file, command line options and start the main
# loop.
#
# Copyright (C) 2007-2012 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define _GNU_SOURCE
#ifdef LIBCONFIG
#include <libconfig.h>
#endif
#include <regex.h>
#include "common.h"
#include "probe.h"
const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f] [-n] [-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" \
"-F: use configuration file\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' },
{ "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 void print_usage(void)
{
struct proto *p;
int i;
int res;
char *prots = "";
p = get_builtins();
for (i = 0; i < get_num_builtins(); i++) {
res = asprintf(&prots, "%s\t[--%s <addr>]\n", prots, p[i].description);
CHECK_RES_DIE(res, "asprintf");
}
fprintf(stderr, USAGE_STRING, prots);
}
static void printcaps(void) {
#ifdef LIBCAP
cap_t caps;
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];
struct addrinfo *a;
struct proto *p;
for (p = get_first_protocol(); p; p = p->next) {
fprintf(stderr,
"%s addr: %s. libwrap service: %s family %d %d\n",
p->description,
sprintaddr(buf, sizeof(buf), p->saddr),
p->service,
p->saddr->ai_family,
p->saddr->ai_addr->sa_family);
}
fprintf(stderr, "listening on:\n");
for (a = addr_listen; a; a = a->ai_next) {
fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a));
}
fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout,
timeout_protocol()->description);
}
/* Extract configuration on addresses and ports on which to listen.
* out: newly allocated list of addrinfo to listen to
*/
#ifdef LIBCONFIG
static int config_listen(config_t *config, struct addrinfo **listen)
{
config_setting_t *setting, *addr;
int len, i;
const char *hostname, *port;
setting = config_lookup(config, "listen");
if (setting) {
len = config_setting_length(setting);
for (i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
}
resolve_split_name(listen, hostname, port);
/* getaddrinfo returned a list of addresses corresponding to the
* specification; move the pointer to the end of that list before
* processing the next specification */
for (; *listen; listen = &((*listen)->ai_next));
}
}
return 0;
}
#endif
#ifdef LIBCONFIG
static void setup_regex_probe(struct proto *p, config_setting_t* probes)
{
int num_probes, errsize, i, res;
char *err;
const char * expr;
regex_t** probe_list;
num_probes = config_setting_length(probes);
if (!num_probes) {
fprintf(stderr, "%s: no probes specified\n", p->description);
exit(1);
}
p->probe = get_probe("regex");
probe_list = calloc(num_probes + 1, sizeof(*probe_list));
p->data = (void*)probe_list;
for (i = 0; i < num_probes; i++) {
probe_list[i] = malloc(sizeof(*(probe_list[i])));
expr = config_setting_get_string_elem(probes, i);
res = regcomp(probe_list[i], expr, 0);
if (res) {
err = malloc(errsize = regerror(res, probe_list[i], NULL, 0));
regerror(res, probe_list[i], err, errsize);
fprintf(stderr, "%s:%s\n", expr, err);
free(err);
exit(1);
}
}
}
#endif
/* Extract configuration for protocols to connect to.
* out: newly-allocated list of protocols
*/
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
{
config_setting_t *setting, *prot, *probes;
const char *hostname, *port, *name;
int i, num_prots;
struct proto *p, *prev = NULL;
setting = config_lookup(config, "protocols");
if (setting) {
num_prots = config_setting_length(setting);
for (i = 0; i < num_prots; i++) {
p = calloc(1, sizeof(*p));
if (i == 0) *prots = p;
if (prev) prev->next = p;
prev = p;
prot = config_setting_get_elem(setting, i);
if ((config_setting_lookup_string(prot, "name", &name) &&
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
)) {
p->description = name;
config_setting_lookup_string(prot, "service", &(p->service));
resolve_split_name(&(p->saddr), hostname, port);
probes = config_setting_get_member(prot, "probe");
if (probes) {
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);
}
}
}
}
}
}
return 0;
}
#endif
/* Parses a config file
* in: *filename
* out: *listen, a newly-allocated linked list of listen addrinfo
* *prots, a newly-allocated linked list of protocols
*/
#ifdef LIBCONFIG
static int config_parse(char *filename, struct addrinfo **listen, struct proto **prots)
{
config_t config;
int timeout;
const char* str;
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
exit(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", (int *)&timeout) == CONFIG_TRUE) {
probing_timeout = timeout;
}
if (config_lookup_string(&config, "on-timeout", &str)) {
set_ontimeout(str);
}
config_lookup_string(&config, "user", &user_name);
config_lookup_string(&config, "pidfile", &pid_file);
config_listen(&config, listen);
config_protocols(&config, prots);
return 0;
}
#endif
/* Adds protocols to the list of options, so command-line parsing uses the
* protocol definition array
* options: array of options to add to; must be big enough
* n_opts: number of options in *options before calling (i.e. where to append)
* prot: array of protocols
* n_prots: number of protocols in *prot
* */
static void append_protocols(struct option *options, int n_opts, struct proto *prot , int n_prots)
{
int o, p;
for (o = n_opts, p = 0; p < n_prots; o++, p++) {
options[o].name = prot[p].description;
options[o].has_arg = required_argument;
options[o].flag = 0;
options[o].val = p + PROT_SHIFT;
}
}
static void make_alloptions(void)
{
builtins = get_builtins();
/* Create all_options, composed of const_options followed by one option per
* known protocol */
all_options = calloc(ARRAY_SIZE(const_options) + get_num_builtins(), sizeof(struct option));
memcpy(all_options, const_options, sizeof(const_options));
append_protocols(all_options, ARRAY_SIZE(const_options) - 1, builtins, get_num_builtins());
}
/* Performs a first scan of command line options to see if a configuration file
* is specified. If there is one, parse it now before all other options (so
* configuration file settings can be overridden from the command line).
*
* prots: newly-allocated list of configured protocols, if any.
*/
static void cmdline_config(int argc, char* argv[], struct proto** prots)
{
#ifdef LIBCONFIG
int c, res;
char *config_filename;
#endif
make_alloptions();
#ifdef LIBCONFIG
optind = 1;
opterr = 0; /* we're missing protocol options at this stage so don't output errors */
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 'F') {
config_filename = optarg;
/* find the end of the listen list */
res = config_parse(config_filename, &addr_listen, prots);
if (res)
exit(4);
break;
}
}
#endif
}
/* Parse command-line options. prots points to a list of configured protocols,
* potentially non-allocated */
static void parse_cmdline(int argc, char* argv[], struct proto* prots)
{
int c;
struct addrinfo **a;
struct proto *p;
optind = 1;
opterr = 1;
next_arg:
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
if (c == 0) continue;
if (c >= PROT_SHIFT) {
if (prots)
for (p = prots; p && p->next; p = p->next) {
/* override if protocol was already defined by config file
* (note it only overrides address and use builtin probe) */
if (!strcmp(p->description, builtins[c-PROT_SHIFT].description)) {
resolve_name(&(p->saddr), optarg);
p->probe = builtins[c-PROT_SHIFT].probe;
goto next_arg;
}
}
/* At this stage, it's a new protocol: add it to the end of the
* list */
if (!prots) {
/* No protocols yet -- create the list */
p = prots = calloc(1, sizeof(*p));
} else {
p->next = calloc(1, sizeof(*p));
p = p->next;
}
memcpy(p, &builtins[c-PROT_SHIFT], sizeof(*p));
resolve_name(&(p->saddr), optarg);
continue;
}
switch (c) {
case 'F':
/* Legal option, but do nothing, it was already processed in
* cmdline_config() */
#ifndef LIBCONFIG
fprintf(stderr, "Built without libconfig support: configuration file not available.\n");
exit(1);
#endif
break;
case 't':
probing_timeout = atoi(optarg);
break;
case OPT_ONTIMEOUT:
set_ontimeout(optarg);
break;
case 'p':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
/* append the specified addresses */
resolve_name(a, optarg);
break;
case 'V':
printf("%s %s\n", server_type, VERSION);
exit(0);
case 'u':
user_name = optarg;
break;
case 'P':
pid_file = optarg;
break;
case 'v':
verbose++;
break;
default:
print_usage();
exit(2);
}
}
if (!prots) {
fprintf(stderr, "At least one target protocol must be specified.\n");
exit(2);
}
set_protocol_list(prots);
if (!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[])
{
extern char *optarg;
extern int optind;
int res, num_addr_listen;
struct proto* protocols = NULL;
int *listen_sockets;
/* Init defaults */
pid_file = NULL;
user_name = NULL;
cmdline_config(argc, argv, &protocols);
parse_cmdline(argc, argv, protocols);
if (inetd)
{
verbose = 0;
start_shoveler(0);
exit(0);
}
if (verbose)
printsettings();
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
if (!foreground) {
if (fork() > 0) exit(0); /* Detach */
/* New session -- become group leader */
if (getuid() == 0) {
res = setsid();
CHECK_RES_DIE(res, "setsid: already process leader");
}
}
setup_signals();
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;
}
+355
View File
@@ -0,0 +1,355 @@
/*
sslh-select: mono-processus server
# Copyright (C) 2007-2010 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define __LINUX__
#include "common.h"
#include "probe.h"
const char* server_type = "sslh-select";
/* cnx_num_alloc is the number of connection to allocate at once (at start-up,
* and then every time we get too many simultaneous connections: e.g. start
* with 100 slots, then if we get more than 100 connections allocate another
* 100 slots, and so on). We never free up connection structures. We try to
* allocate as many structures at once as will fit in one page (which is 102
* in sslh 1.9 on Linux on x86)
*/
static long cnx_num_alloc;
/* Make the file descriptor non-block */
int set_nonblock(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
CHECK_RES_RETURN(flags, "fcntl");
flags |= O_NONBLOCK;
flags = fcntl(fd, F_SETFL, flags);
CHECK_RES_RETURN(flags, "fcntl");
return flags;
}
int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
{
int i;
for (i = 0; i < 2; i++) {
if (cnx->q[i].fd != -1) {
if (verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
close(cnx->q[i].fd);
FD_CLR(cnx->q[i].fd, fds);
FD_CLR(cnx->q[i].fd, fds2);
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 */
int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size)
{
int in_socket, free, i, res;
struct connection *new;
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;
/* Find an empty slot */
for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) {
/* nothing */
}
if (free >= *cnx_size) {
if (verbose)
fprintf(stderr, "buying more slots from the slot machine.\n");
new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0]));
if (!new) {
log_message(LOG_ERR, "unable to realloc -- dropping connection\n");
return -1;
}
*cnx = new;
*cnx_size += cnx_num_alloc;
for (i = free; i < *cnx_size; i++) {
init_cnx(&(*cnx)[i]);
}
}
(*cnx)[free].q[0].fd = in_socket;
(*cnx)[free].state = ST_PROBING;
(*cnx)[free].probe_timeout = time(NULL) + probing_timeout;
if (verbose)
fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free);
return in_socket;
}
/* Connect queue 1 of connection to SSL; returns new file descriptor */
int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w)
{
struct queue *q = &cnx->q[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_deferred(q);
if (q->deferred_data) {
FD_SET(q->fd, fds_w);
} else {
FD_SET(q->fd, fds_r);
}
return q->fd;
} else {
tidy_connection(cnx, fds_r, fds_w);
return -1;
}
}
/* shovels data from active fd to the other
returns after one socket closed or operation would block
*/
void shovel(struct connection *cnx, int active_fd,
fd_set *fds_r, fd_set *fds_w)
{
struct queue *read_q, *write_q;
read_q = &cnx->q[active_fd];
write_q = &cnx->q[1-active_fd];
if (verbose)
fprintf(stderr, "activity on fd%d\n", read_q->fd);
switch(fd2fd(write_q, read_q)) {
case -1:
case FD_CNXCLOSED:
tidy_connection(cnx, fds_r, fds_w);
break;
case FD_STALLED:
FD_SET(write_q->fd, fds_w);
FD_CLR(read_q->fd, fds_r);
break;
default: /* Nothing */
break;
}
}
/* returns true if specified fd is initialised and present in fd_set */
int is_fd_active(int fd, fd_set* set)
{
if (fd == -1) return 0;
return FD_ISSET(fd, set);
}
/* Main loop: the idea is as follow:
* - 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 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 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.
*
* That way, each pair of file descriptor (read from one, write to the other)
* is monitored either for read or for write, but never for both.
*/
void main_loop(int listen_sockets[], int num_addr_listen)
{
fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
fd_set readfds, writefds; /* working read and write fd sets */
struct timeval tv;
int max_fd, in_socket, i, j, res;
struct connection *cnx;
int num_cnx; /* Number of connections in *cnx */
int num_probing = 0; /* Number of connections currently probing
* We use this to know if we need to time out of
* select() */
FD_ZERO(&fds_r);
FD_ZERO(&fds_w);
for (i = 0; i < num_addr_listen; i++) {
FD_SET(listen_sockets[i], &fds_r);
set_nonblock(listen_sockets[i]);
}
max_fd = listen_sockets[num_addr_listen-1] + 1;
cnx_num_alloc = getpagesize() / sizeof(struct connection);
num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
cnx = malloc(num_cnx * sizeof(struct connection));
for (i = 0; i < num_cnx; i++)
init_cnx(&cnx[i]);
while (1)
{
memset(&tv, 0, sizeof(tv));
tv.tv_sec = probing_timeout;
memcpy(&readfds, &fds_r, sizeof(readfds));
memcpy(&writefds, &fds_w, sizeof(writefds));
if (verbose)
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing);
res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL);
if (res < 0)
perror("select");
/* Check main socket for new connections */
for (i = 0; i < num_addr_listen; i++) {
if (FD_ISSET(listen_sockets[i], &readfds)) {
in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx);
if (in_socket != -1)
num_probing++;
if (in_socket > 0) {
FD_SET(in_socket, &fds_r);
if (in_socket >= max_fd)
max_fd = in_socket + 1;;
}
FD_CLR(listen_sockets[i], &readfds);
}
}
/* Check all sockets for write activity */
for (i = 0; i < num_cnx; i++) {
if (cnx[i].q[0].fd != -1) {
for (j = 0; j < 2; j++) {
if (is_fd_active(cnx[i].q[j].fd, &writefds)) {
res = flush_deferred(&cnx[i].q[j]);
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
if (cnx[i].state == ST_PROBING) num_probing--;
tidy_connection(&cnx[i], &fds_r, &fds_w);
if (verbose)
fprintf(stderr, "closed slot %d\n", i);
} else {
/* If no deferred data is left, stop monitoring the fd
* for write, and restart monitoring the other one for reads*/
if (!cnx[i].q[j].deferred_data_size) {
FD_CLR(cnx[i].q[j].fd, &fds_w);
FD_SET(cnx[i].q[1-j].fd, &fds_r);
}
}
}
}
}
}
/* Check all sockets for read activity */
for (i = 0; i < num_cnx; i++) {
for (j = 0; j < 2; j++) {
if (is_fd_active(cnx[i].q[j].fd, &readfds) ||
((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) {
if (verbose)
fprintf(stderr, "processing fd%d slot %d\n", j, i);
switch (cnx[i].state) {
case ST_PROBING:
if (j == 1) {
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
dump_connection(&cnx[i]);
exit(1);
}
/* If timed out it's SSH, otherwise the client sent
* data so probe the protocol */
if ((cnx[i].probe_timeout < time(NULL))) {
cnx[i].proto = timeout_protocol();
} else {
res = probe_client_protocol(&cnx[i]);
if (res == PROBE_AGAIN)
continue;
}
num_probing--;
cnx[i].state = ST_SHOVELING;
/* libwrap check if required for this protocol */
if (cnx[i].proto->service &&
check_access_rights(in_socket, cnx[i].proto->service)) {
tidy_connection(&cnx[i], &fds_r, &fds_w);
res = -1;
} else {
res = connect_queue(&cnx[i], &fds_r, &fds_w);
}
if (res >= max_fd)
max_fd = res + 1;;
break;
case ST_SHOVELING:
shovel(&cnx[i], j, &fds_r, &fds_w);
break;
default: /* illegal */
log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state);
exit(1);
}
}
}
}
}
}
void start_shoveler(int listen_socket) {
fprintf(stderr, "inetd mode is not supported in select mode\n");
exit(1);
}
/* The actual main is in common.c: it's the same for both version of
* the server
*/
-469
View File
@@ -1,469 +0,0 @@
/*
Reimplementation of sslh in C
# Copyright (C) 2007-2008 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define VERSION "1.5"
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <syslog.h>
#ifdef LIBWRAP
#include <tcpd.h>
int allow_severity =0, deny_severity = 0;
#endif
#define CHECK_RES_DIE(res, str) \
if (res == -1) { \
perror(str); \
exit(1); \
}
#define USAGE_STRING \
"sslh v" VERSION "\n" \
"usage:\n" \
"\texport PIDFILE=/var/run/sslhc.pid\n" \
"\tsslh [-t <timeout>] -u <username> -p [listenaddr:]<listenport> \n" \
"\t\t-s [sshhost:]port -l [sslhost:]port [-v]\n\n" \
"-v: verbose\n" \
"-p: address and port to listen on. default: 0.0.0.0:443\n" \
"-s: SSH address: where to connect an SSH connection. default: localhost:22\n" \
"-l: SSL address: where to connect an SSL connection.\n" \
""
int verbose = 0; /* That's really quite global */
/* Starts a listening socket on specified address.
Returns file descriptor
*/
int start_listen_socket(struct sockaddr *addr)
{
struct sockaddr_in *saddr = (struct sockaddr_in*)addr;
int sockfd, res, reuse;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
CHECK_RES_DIE(sockfd, "socket");
reuse = 1;
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
CHECK_RES_DIE(res, "setsockopt");
res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr));
CHECK_RES_DIE(res, "bind");
res = listen (sockfd, 5);
CHECK_RES_DIE(res, "listen");
return sockfd;
}
/*
* moves data from one fd to other
* returns 0 if incoming socket closed, size moved otherwise
*/
int fd2fd(int target, int from)
{
char buffer[BUFSIZ];
int size;
size = read(from, buffer, sizeof(buffer));
CHECK_RES_DIE(size, "read");
if (size == 0)
return 0;
size = write(target, buffer, size);
CHECK_RES_DIE(size, "write");
return size;
}
/* shovels data from one fd to the other and vice-versa
returns after one socket closed
*/
int shovel(int fd1, int fd2)
{
fd_set fds;
int res;
FD_ZERO(&fds);
while (1) {
FD_SET(fd1, &fds);
FD_SET(fd2, &fds);
res = select(
(fd1 > fd2 ? fd1 : fd2 ) + 1,
&fds,
NULL,
NULL,
NULL
);
CHECK_RES_DIE(res, "select");
if (FD_ISSET(fd1, &fds)) {
res = fd2fd(fd2, fd1);
if (!res) {
if (verbose) fprintf(stderr, "client socket closed\n");
return res;
}
}
if (FD_ISSET(fd2, &fds)) {
res = fd2fd(fd1, fd2);
if (!res) {
if (verbose) fprintf(stderr, "server socket closed\n");
return res;
}
}
}
}
/* returns a string that prints the IP and port of the sockaddr */
char* sprintaddr(char* buf, size_t size, struct sockaddr* s)
{
char addr_str[1024];
inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str));
snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port));
return buf;
}
/* turns a "hostname:port" string into a struct sockaddr;
sock: socket address to which to copy the addr
fullname: input string -- it gets clobbered
*/
void resolve_name(struct sockaddr *sock, char* fullname) {
struct addrinfo *addr, hint;
char *serv, *host;
int res;
char *sep = strchr(fullname, ':');
if (!sep) /* No separator: parameter is just a port */
{
serv = fullname;
fprintf(stderr, "names must be fully specified as hostname:port\n");
exit(1);
}
else {
host = fullname;
serv = sep+1;
*sep = 0;
}
memset(&hint, 0, sizeof(hint));
hint.ai_family = PF_INET;
hint.ai_socktype = SOCK_STREAM;
res = getaddrinfo(host, serv, &hint, &addr);
if (res) {
fprintf(stderr, "%s\n", gai_strerror(res));
if (res == EAI_SERVICE)
fprintf(stderr, "(Check you have specified all ports)\n");
exit(1);
}
memcpy(sock, addr->ai_addr, sizeof(*sock));
}
/* syslogs who connected to where */
void log_connection(int socket, char* target)
{
struct sockaddr peeraddr;
socklen_t size = sizeof(peeraddr);
char buf[64];
int res;
res = getpeername(socket, &peeraddr, &size);
CHECK_RES_DIE(res, "getpeername");
syslog(LOG_INFO, "connection from %s forwarded to %s\n",
sprintaddr(buf, sizeof(buf), &peeraddr), target);
}
/*
* Settings that depend on the command line. That's less global than verbose * :-)
* They're set in main(), but also used in start_shoveler(), and it'd be heavy-handed
* to pass it all as parameters
*/
int timeout = 2;
struct sockaddr addr_listen;
struct sockaddr addr_ssl, addr_ssh;
/* libwrap (tcpd): check the ssh connection is legal. This is necessary because
* the actual sshd will only see a connection coming from localhost and can't
* apply the rules itself.
*/
void check_access_rights(int in_socket)
{
#ifdef LIBWRAP
struct sockaddr peeraddr;
socklen_t size = sizeof(peeraddr);
char addr_str[1024];
struct hostent *host;
struct in_addr addr;
int res;
res = getpeername(in_socket, &peeraddr, &size);
CHECK_RES_DIE(res, "getpeername");
inet_ntop(AF_INET, &((struct sockaddr_in*)&peeraddr)->sin_addr, addr_str, sizeof(addr_str));
addr.s_addr = inet_addr(addr_str);
host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
if (!hosts_ctl("sshd", (host ? host->h_name : STRING_UNKNOWN), addr_str, STRING_UNKNOWN)) {
if (verbose)
fprintf(stderr, "access denied\n");
log_connection(in_socket, "access denied");
close(in_socket);
exit(0);
}
#endif
}
/* Child process that finds out what to connect to and proxies
*/
void start_shoveler(int in_socket)
{
fd_set fds;
struct timeval tv;
struct sockaddr *saddr;
int res;
int out_socket;
char *target;
FD_ZERO(&fds);
FD_SET(in_socket, &fds);
memset(&tv, 0, sizeof(tv));
tv.tv_sec = timeout;
res = select(in_socket + 1, &fds, NULL, NULL, &tv);
if (res == -1)
perror("select");
/* Pick the target address depending on whether we timed out or not */
if (FD_ISSET(in_socket, &fds)) {
/* The client wrote something to the socket: it's an SSL connection */
saddr = &addr_ssl;
target = "SSL";
} else {
/* The client hasn't written anything and we timed out: connect to SSH */
saddr = &addr_ssh;
target = "SSH";
/* do hosts_access check if built with libwrap support */
check_access_rights(in_socket);
}
log_connection(in_socket, target);
/* Connect the target socket */
out_socket = socket(AF_INET, SOCK_STREAM, 0);
res = connect(out_socket, saddr, sizeof(addr_ssl));
CHECK_RES_DIE(res, "connect");
if (verbose)
fprintf(stderr, "connected to something\n");
shovel(in_socket, out_socket);
close(in_socket);
close(out_socket);
if (verbose)
fprintf(stderr, "connection closed down\n");
exit(0);
}
/* SIGCHLD handling:
* we need to reap our children
*/
void child_handler(int signo)
{
signal(SIGCHLD, &child_handler);
wait(NULL);
}
void setup_signals(void)
{
void* res;
res = signal(SIGCHLD, &child_handler);
if (res == SIG_ERR) {
perror("signal");
exit(1);
}
}
/* We don't want to run as root -- drop priviledges if required */
void drop_privileges(char* user_name)
{
int res;
struct passwd *pw = getpwnam(user_name);
if (!pw) {
fprintf(stderr, "%s: not found\n", user_name);
exit(1);
}
if (verbose)
fprintf(stderr, "turning into %s\n", user_name);
res = setgid(pw->pw_gid);
CHECK_RES_DIE(res, "setgid");
setuid(pw->pw_uid);
CHECK_RES_DIE(res, "setuid");
}
/* Writes my PID if $PIDFILE is defined */
void write_pid_file(void)
{
char *pidfile = getenv("PIDFILE");
FILE *f;
if (!pidfile)
return;
f = fopen(pidfile, "w");
if (!f) {
perror(pidfile);
exit(1);
}
fprintf(f, "%d\n", getpid());
fclose(f);
}
void printsettings(void)
{
char buf[64];
fprintf(
stderr,
"SSL addr: %s (after timeout %ds)\n",
sprintaddr(buf, sizeof(buf), &addr_ssl),
timeout
);
fprintf(stderr, "SSH addr: %s\n", sprintaddr(buf, sizeof(buf), &addr_ssh));
fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen));
}
int main(int argc, char *argv[])
{
extern char *optarg;
extern int optind;
int c, res;
int in_socket, listen_socket;
/* Init defaults */
char *user_name = "nobody";
char listen_str[] = "0.0.0.0:443";
char ssl_str[] = "localhost:442";
char ssh_str[] = "localhost:22";
resolve_name(&addr_listen, listen_str);
resolve_name(&addr_ssl, ssl_str);
resolve_name(&addr_ssh, ssh_str);
while ((c = getopt(argc, argv, "t:l:s:p:vu:")) != EOF) {
switch (c) {
case 't':
timeout = atoi(optarg);
break;
case 'p':
resolve_name(&addr_listen, optarg);
break;
case 'l':
resolve_name(&addr_ssl, optarg);
break;
case 's':
resolve_name(&addr_ssh, optarg);
break;
case 'v':
verbose += 1;
break;
case 'u':
user_name = optarg;
break;
default:
fprintf(stderr, USAGE_STRING);
exit(2);
}
}
if (verbose)
printsettings();
setup_signals();
listen_socket = start_listen_socket(&addr_listen);
if (fork() > 0) exit(0); /* Detach */
write_pid_file();
drop_privileges(user_name);
/* New session -- become group leader */
res = setsid();
CHECK_RES_DIE(res, "setsid: already process leader");
/* Open syslog connection */
openlog(argv[0], LOG_CONS, LOG_AUTH);
/* Main server loop: accept connections, find what they are, fork shovelers */
while (1)
{
in_socket = accept(listen_socket, 0, 0);
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
if (!fork())
{
start_shoveler(in_socket);
exit(0);
}
close(in_socket);
}
return 0;
}
+220
View File
@@ -0,0 +1,220 @@
# I'm just not gonna write troff :-)
=head1 NAME
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<--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 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 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 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
A configuration file can be supplied to B<sslh>. Command
line arguments override file settings. B<sslh> uses
B<libconfig> to parse the configuration file, so the general
file format is indicated in
L<http://www.hyperrealm.com/libconfig/libconfig_manual.html>.
Please refer to the example configuration file provided with
B<sslh> for the specific format (Options have the same names
as on the command line, except for the list of listen ports
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
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.
=head1 OPTIONS
=over 4
=item B<-t> I<num>, B<--timeout> I<num>
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<-p> I<listening address>, B<--listen> I<listening address>
Interface and port on which to listen, e.g. I<foobar:443>,
where I<foobar> is the name of an interface (typically the
IP address on which the Internet connection ends up).
This can be specified several times to bind B<sslh> to
several addresses.
=item B<--ssl> I<target address>
=item B<--tls> I<target address>
Interface and port on which to forward SSL connection,
typically I<localhost:443>.
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,
typically I<localhost:22>.
=item B<--openvpn> I<target address>
Interface and port on which to forward OpenVPN connections,
typically I<localhost:1194>.
=item B<--xmpp> I<target address>
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,
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.
=item B<-n>, B<--numeric>
Do not attempt to resolve hostnames: logs will contain IP
addresses. This is mostly useful if the system's DNS is slow
and running the I<sslh-select> variant, as DNS requests will
hang all connections.
=item B<-V>
Prints B<sslh> version.
=item B<-u> I<username>, B<--user> I<username>
Requires to run under the specified username.
=item B<-P> I<pidfile>, B<--pidfile> I<pidfile>
Specifies a file in which to write the PID of the main
server.
=item B<-i>, B<--inetd>
Runs as an I<inetd> server. Options B<-P> (PID file), B<-p>
(listen address), B<-u> (user) are ignored.
=item B<-f>, B<--foreground>
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
=over 4
=item F</etc/init.d/sslh>
Start-up script. The standard actions B<start>, B<stop> and
B<restart> are supported.
=item F</etc/default/sslh>
Server configuration. These are environment variables
loaded by the start-up script and passed to B<sslh> as
command-line arguments. Refer to the OPTIONS section for a
detailed explanation of the variables used by B<sslh>.
=back
=head1 SEE ALSO
Last version available from
L<http://www.rutschle.net/tech/sslh>, and can be tracked
from L<http://freecode.com/projects/sslh>.
=head1 AUTHOR
Written by Yves Rutschle
Executable
+332
View File
@@ -0,0 +1,332 @@
#! /usr/bin/perl -w
# Test script for sslh
use strict;
use IO::Socket::INET6;
use Test::More qw/no_plan/;
# We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything...
my $ssh_address = "ip6-localhost:9000";
my $ssl_address = "ip6-localhost:9001";
my $sslh_port = 9002;
my $no_listen = 9003; # Port on which no-one listens
my $pidfile = "/tmp/sslh_test.pid";
# Which tests do we run
my $SSL_CNX = 1;
my $SSH_SHY_CNX = 1;
my $SSH_BOLD_CNX = 1;
my $SSH_PROBE_AGAIN = 1;
my $SSL_MIX_SSH = 1;
my $SSH_MIX_SSL = 1;
my $BIG_MSG = 0; # This test is unreliable
my $STALL_CNX = 0; # This test needs fixing
# Robustness tests. These are mostly to achieve full test
# coverage, but do not necessarily result in an actual test
# (e.g. some tests need to be run with valgrind to check all
# memory management code).
my $RB_CNX_NOSERVER = 1;
my $RB_PARAM_NOHOST = 1;
my $RB_WRONG_USERNAME = 1;
my $RB_OPEN_PID_FILE = 1;
my $RB_BIND_ADDRESS = 1;
my $RB_RESOLVE_ADDRESS = 1;
`lcov --directory . --zerocounters`;
my ($ssh_pid, $ssl_pid);
if (!($ssh_pid = fork)) {
exec "./echosrv --listen $ssh_address --prefix 'ssh: '";
}
if (!($ssl_pid = fork)) {
exec "./echosrv --listen $ssl_address --prefix 'ssl: '";
}
my @binaries = ('sslh-select', 'sslh-fork');
for my $binary (@binaries) {
warn "Testing $binary\n";
# Start sslh with the right plumbing
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $cmd = "./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
warn "$cmd\n";
#exec $cmd;
exec "valgrind --leak-check=full ./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
exit 0;
}
warn "spawned $sslh_pid\n";
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) {
print "***Test: SSL connection\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection");
}
}
# Test: Shy SSH connection
if ($SSH_SHY_CNX) {
print "***Test: Shy SSH connection\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
sleep 3;
print $cnx_h $test_data;
my $data = <$cnx_h>;
is($data, "ssh: $test_data", "Shy SSH connection");
}
}
# Test: Bold SSH connection
if ($SSH_BOLD_CNX) {
print "***Test: Bold SSH connection\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
my $td = "SSH-2.0 testsuite\t$test_data";
print $cnx_h $td;
my $data = <$cnx_h>;
is($data, "ssh: $td", "Bold SSH connection");
}
}
# Test: PROBE_AGAIN, incomplete first frame
if ($SSH_PROBE_AGAIN) {
print "***Test: incomplete SSH first frame\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
my $td = "SSH-2.0 testsuite\t$test_data";
print $cnx_h substr $td, 0, 2;
sleep 1;
print $cnx_h substr $td, 2;
my $data = <$cnx_h>;
is($data, "ssh: $td", "Incomplete first SSH frame");
}
}
# Test: One SSL half-started then one SSH
if ($SSL_MIX_SSH) {
print "***Test: One SSL half-started then one SSH\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $ssl_test_data;
my $cnx_h= new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
sleep 3;
print $cnx_h $test_data;
my $data_h = <$cnx_h>;
is($data_h, "ssh: $test_data", "SSH during SSL being established");
}
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection interrupted by SSH");
}
}
# Test: One SSH half-started then one SSL
if ($SSH_MIX_SSL) {
print "***Test: One SSH half-started then one SSL\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
sleep 3;
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL during SSH being established");
}
print $cnx_h $test_data;
my $data = <$cnx_h>;
is($data, "ssh: $test_data", "SSH connection interrupted by SSL");
}
}
# Test: Big messages (careful: don't go over echosrv's buffer limit (1M))
if ($BIG_MSG) {
print "***Test: big message\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
my $rept = 1000;
my $test_data2 = $ssl_test_data . ("helloworld"x$rept);
if (defined $cnx_l) {
my $n = syswrite $cnx_l, $test_data2;
my ($data);
$n = sysread $cnx_l, $data, 1 << 20;
is($data, "ssl: ". $test_data2, "Big message");
}
}
# Test: Stalled connection
# Create two connections, stall one, check the other one
# works, unstall first and check it works fine
# This test needs fixing.
# Now that echosrv no longer works on "lines" (finishing
# with '\n'), it may cut blocks randomly with prefixes.
# The whole thing needs to be re-thought as it'll only
# work by chance.
if ($STALL_CNX) {
print "***Test: Stalled connection\n";
my $cnx_1 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_1;
my $cnx_2 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_2;
my $test_data2 = "helloworld";
sleep 4;
my $rept = 1000;
if (defined $cnx_1 and defined $cnx_2) {
print $cnx_1 ($test_data2 x $rept);
print $cnx_1 "\n";
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
my $data = <$cnx_2>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (1)");
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
$data = <$cnx_2>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
$data = <$cnx_1>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
}
}
my $pid = `cat $pidfile`;
warn "killing $pid\n";
kill TERM => $pid or warn "kill process: $!\n";
sleep 1;
}
# Robustness: Connecting to non-existant server
if ($RB_CNX_NOSERVER) {
print "***Test: Connecting to non-existant server\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --ssl localhost:$no_listen -P $pidfile";
}
warn "spawned $sslh_pid\n";
sleep 1;
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h;
if (defined $cnx_h) {
sleep 1;
my $test_data = "hello";
print $cnx_h $test_data;
}
# Ideally we should check a log is emitted.
kill TERM => `cat $pidfile` or warn "kill: $!\n";
sleep 1;
}
# Robustness: No hostname in address
if ($RB_PARAM_NOHOST) {
print "***Test: No hostname in address\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen $sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 1, "Exit status on illegal option");
}
# Robustness: User does not exist
if ($RB_WRONG_USERNAME) {
print "***Test: Changing to non-existant username\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u ${user}_doesnt_exist --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 2, "Exit status on non-existant username");
}
# Robustness: Can't open PID file
if ($RB_OPEN_PID_FILE) {
print "***Test: Can't open PID file\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P /dont_exist/$pidfile";
# You don't have a /dont_exist/ directory, do you?!
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 3, "Exit status if can't open PID file");
}
# 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";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 4, "Exit status if can't resolve address");
}
`lcov --directory . --capture --output-file sslh_cov.info`;
`genhtml sslh_cov.info`;
`killall echosrv`;
Executable
+128
View File
@@ -0,0 +1,128 @@
#! /usr/bin/perl -w
# Test script for sslh -- mass communication
# This creates many clients that perform concurrent
# connections, disconnect at any time, and try to generally
# behave as badly as possible.
# It can be used to test sslh behaves properly with many
# clients, however its main use is to get an idea of how
# much load it can take on your system before things start
# to go wrong.
use strict;
use IO::Socket::INET6;
use Data::Dumper;
## BEGIN TEST CONFIG
# Do we test sslh-select or sslh-fork?
my $sslh_binary = "./sslh-select";
# How many clients to we start for each protocol?
my $NUM_CNX = 30;
# Delay between starting new processes when starting up. If
# you start 200 processes in under a second, things go wrong
# and it's not sslh's fault (typically the echosrv won't be
# forking fast enough).
my $start_time_delay = 1;
# If you test 4 protocols, you'll start $NUM_CNX * 4 clients
# (e.g. 40), starting one every $start_time_delay seconds.
# Max times we repeat the test string: allows to test for
# large messages.
my $block_rpt = 4096;
# Probability to stop a client after a message (e.g. with
# .01 a client will send an average of 100 messages before
# disconnecting).
my $stop_client_probability = .001;
# What protocols we test, and on what ports
# Just comment out protocols you don't want to use.
my %protocols = (
"ssh" => { address => "localhost:9001", client => client("ssh") },
"ssl" => { address => "localhost:9002", client => client("ssl") },
"openvpn" => {address => "localhost:9003", client => client("openvpn") },
"tinc" => {address => "localhost:9004", client => client("tinc") },
);
##END CONFIG
# We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything...
my $sslh_address = "localhost:9000";
my $pidfile = "/tmp/sslh_test.pid";
sub client {
my ($service) = @_;
return sub {
while (1) {
my $cnx = new IO::Socket::INET(PeerHost => $sslh_address);
my $test_data = "$service testing " x int(rand($block_rpt)+1) . "\n";
sleep 5 if $service eq "ssh";
if ($service eq "openvpn") {
syswrite $cnx, "\x00\x0F\x38\n";
my $msg;
sysread $cnx, $msg, 14; # length "openvpn: \x0\xF\x38\n" => 14
}
if ($service eq "tinc") {
syswrite $cnx, "0 \n";
my $msg;
sysread $cnx, $msg, 10; # length "tinc: 0 \n" => 10
}
while (1) {
print $cnx $test_data;
my $r = <$cnx>;
($? = 1, die "$service got [$r]\n") if ($r ne "$service: $test_data");
last if rand(1) < $stop_client_probability;
}
}
exit 0;
}
}
foreach my $p (keys %protocols) {
if (!fork) {
exec "./echosrv --listen $protocols{$p}->{address} --prefix '$p: '";
}
}
# Start sslh with the right plumbing
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $prots = join " ", map "--$_ $protocols{$_}->{address}", keys %protocols;
my $cmd = "$sslh_binary -f -t 3 -u $user --listen $sslh_address $prots -P $pidfile";
print "$cmd\n";
exec $cmd;
exit 0;
}
warn "spawned $sslh_pid\n";
sleep 2; # valgrind can be heavy -- wait 5 seconds
for (1 .. $NUM_CNX) {
foreach my $p (keys %protocols) {
if (!fork) {
warn "starting $p\n";
&{$protocols{$p}->{client}};
exit;
}
# Give a little time so we don't overrun the
# listen(2) backlog.
select undef, undef, undef, $start_time_delay;
}
}
wait;
`killall echosrv`;