git ssb

0+

cel / sslh



Commit 80f76c6fc58f4ae5c3d25f42017a11d4918e1502

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.
Yves Rutschle committed on 7/10/2013, 9:12:42 PM
Parent: 44f02ddf39a9d4c338dae207cfd1fb6bc6bfd1e0

Files changed

ChangeLogchanged
Makefilechanged
READMEchanged
scripts/etc.rc.d.init.d.sslh.centoschanged
sslh.podchanged
README.MacOSXadded
sslh.cdeleted
common.cadded
common.hadded
sslh-fork.cadded
sslh-select.cadded
tadded
ChangeLogView
@@ -1,4 +1,34 @@
1+v1.8:
2+ Changed log format to make it possible to link
3+ connections to subsequent logs from other services.
4+
5+ Updated CentOS init.d script (Andre Krajnik).
6+
7+ Fixed zombie issue with OpenBSD (The SA_NOCLDWAIT flag is not
8+ propagated to the child process, so we set up signals after
9+ the fork.) (Fran�ois FRITZ)
10+
11+ Added -o "OpenVPN" and OpenVPN probing and support.
12+
13+ Added single-threaded, select(2)-based version.
14+
15+ Added support for "Bold" SSH clients (clients that speak first)
16+ Thanks to Guillaume Ricaud for spotting a regression
17+ bug.
18+
19+ Added -f "foreground" option.
20+
21+ Added test suite. (only tests connexions. No test for libwrap,
22+ setsid, setuid and so on) and corresponding 'make
23+ test' target.
24+
25+ Added README.MacOSX (thanks Aaron Madlon-Kay)
26+
27+ Documented use with proxytunnel and corkscrew in
28+ README.
29+
30+
131 v1.7: 01FEB2010
232 Added CentOS init.d script (Andre Krajnik).
333
434 Fixed default ssl address inconsistancy, now
MakefileView
@@ -1,38 +1,50 @@
11 # Configuration
22
3-VERSION="v1.7a"
4-USELIBWRAP=1 # Use libwrap?
3+VERSION="v1.8"
4+USELIBWRAP= # Use libwrap?
55 PREFIX=/usr/local
66
77 MAN=sslh.8.gz # man page name
88
99 # End of configuration -- the rest should take care of
1010 # itself
1111
1212 CC = gcc
13-CFLAGS=-Wall
13+CFLAGS=-Wall -g
1414
1515 #LIBS=-lnet
1616 LIBS=
17+OBJS=common.o
1718
1819 ifneq ($(strip $(USELIBWRAP)),)
1920 LIBS:=$(LIBS) -lwrap
2021 CFLAGS:=$(CFLAGS) -DLIBWRAP
2122 endif
2223
2324 all: sslh $(MAN)
2425
25-sslh: sslh.c Makefile
26- $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh sslh.c $(LIBS)
27- strip sslh
26+.c.o: *.h
27+ $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -c $<
2828
29+
30+sslh: $(OBJS) sslh-fork sslh-select
31+
32+sslh-fork: $(OBJS) sslh-fork.o Makefile
33+ $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
34+ strip sslh-fork
35+
36+sslh-select: $(OBJS) sslh-select.o Makefile
37+ $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS)
38+ strip sslh-select
39+
40+
2941 $(MAN): sslh.pod Makefile
3042 pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
3143
3244 # generic install: install binary and man page
3345 install: sslh $(MAN)
34- install -D sslh $(PREFIX)/sbin/sslh
46+ install -D sslh-fork $(PREFIX)/sbin/sslh
3547 install -D -m 0644 $(MAN) $(PREFIX)/share/man/man8/$(MAN)
3648
3749 # "extended" install for Debian: install startup script
3850 install-debian: install sslh $(MAN)
@@ -45,5 +57,12 @@
4557 rm -f $(PREFIX)/sbin/sslh $(PREFIX)/share/man/man8/$(MAN) /etc/init.d/sslh /etc/default/sslh
4658 update-rc.d sslh remove
4759
4860 clean:
49- rm -f sslh $(MAN)
61+ rm -f sslh-fork sslh-select $(MAN) *.o
62+
63+tags:
64+ ctags -T *.[ch]
65+
66+test:
67+ ./t
68+
READMEView
@@ -1,43 +1,43 @@
11 ===== sslh -- A ssl/ssh multiplexer. =====
22
3-sslh lets one accept both HTTPS and SSH connections on the
4-same port. It makes it possible to connect to an SSH server
5-on port 443 (e.g. from inside a corporate firewall) while
6-still serving HTTPS on that port.
3+sslh accepts HTTPS, SSH and OpenVPN connections on the same
4+port. This makes it possible to connect to an SSH server or
5+an OpenVPN on port 443 (e.g. from inside a corporate
6+firewall, which almost never block port 443) while still
7+serving HTTPS on that port.
78
89 ==== Compile and install ====
910
1011 If you're lucky, the Makefile will work for you:
1112
1213 make install
1314
14-(see below for configuration hints)
15+The Makefile produces two different executables: sslh-fork
16+and sslh-select.
1517
18+sslh-fork forks a new process for each incoming connection.
19+It is well-tested and very reliable, but incurs the overhead
20+of many processes. sslh-select uses only one thread, which
21+monitors all connections at once. It is more recent and less
22+tested, but only incurs a 16 byte overhead per connection.
23+Also, if it stops, you'll lose all connections, which means
24+you can't upgrade it remotely.
1625
17-Otherwise:
26+If you are going to use sslh for a "small" setup (less than
27+a dozen ssh connections and a low-traffic https server) then
28+sslh-fork is probably more suited for you. If you are going
29+to use sslh on a "medium" setup (a few thousand ssh
30+connections, and another few thousand sslh connections),
31+sslh-select will be better. If you have a very large site
32+(tens of thousands of connections), you'll need a vapourware
33+version that would use libevent or something like that.
1834
19-Compilation instructions (the binary produced won't contain
20-the version number, which is stored only in the Makefile)
2135
22-Solaris:
23- cc -o sslh sslh.c -lresolv -lsocket -lnsl
24-
25-LynxOS:
26- gcc -o tcproxy tcproxy.c -lnetinet
27-
28-Linux:
29- cc -o sslh sslh.c -lnet
30-or:
31- cc -o sslh sslh.c
32-
33-To compile with libwrap support:
34- cc -o sslh -DLIBWRAP sslh.c -lwrap
35-
3636 To install:
3737
3838 make
39-cp sslh /usr/local/sbin
39+cp sslh-fork /usr/local/sbin/sslh
4040 cp scripts/etc.default.sslh /etc/default/sslh
4141
4242 For Debian:
4343 cp scripts/etc.init.d.sslh /etc/init.d/sslh
@@ -78,15 +78,50 @@
7878 client.
7979
8080 ==== OpenVPN support ====
8181
82-OpenVPN clients reportedly take more than one second between
82+OpenVPN clients connecting to OpenVPN running with
83+-port-share reportedly take more than one second between
8384 the time the TCP connexion is established and the time they
8485 send the first data packet. This results in sslh with
8586 default settings timing out and assuming an SSH connexion.
8687 To support OpenVPN connexions reliably, it is necessary to
8788 increase sslh's timeout to 5 seconds.
8889
90+Instead of using OpenVPN's port sharing, it is more reliable
91+to use sslh's -o option to get sslh to do the port sharing.
92+
93+==== Using proxytunnel with sslh ====
94+
95+If you are connecting through a proxy that checks that the
96+outgoing connection really is SSL and rejects SSH, you can
97+encapsulate all your traffic in SSL using proxytunnel (this
98+should work with corkscrew as well). On the server side you
99+receive the traffic with stunnel to decapsulate SSL, then
100+pipe through sslh to switch HTTP on one side and SSL on the
101+other.
102+
103+In that case, you end up with something like this:
104+
105+ssh -> proxytunnel -e --------ssh/ssl------> stunnel ---ssh---> sslh --> sshd
106+
107+navigateur --------http/ssl------> stunnel ---http---> sslh --> http:80
108+
109+Configuration goes like this:
110+
111+On the server side, using stunnel3:
112+stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- sslh -i -l localhost:80 -s localhost:22
113+
114+stunnel options: -f for foreground/debugging, -p specifies
115+the key + certificate, -d specifies which interface and port
116+we're listening to for incoming connexions, -l summons sslh
117+in inetd mode.
118+
119+sslh options: -i for inetd mode, -l to forward SSL
120+connexions (in fact normal HTTP at that stage) to port 80,
121+and SSH connexions to port 22. This works because sslh
122+considers that anything that is not SSH is SSL.
123+
89124 ==== IP_TPROXY support ====
90125
91126 There is a netfilter patch that adds an option to the Linux
92127 TCP/IP stack to allow a program to set the source address
@@ -111,5 +146,13 @@
111146 when/if the feature finds its way into the main kernel and
112147 it becomes usuable by non-root processes.
113148
114149
115-Comments? questions? sslh@rutschle.net
150+==== Comments? Questions? ====
151+
152+You can subscribe to the sslh mailing list here:
153+http://rutschle.net/cgi-bin/mailman/listinfo/sslh
154+
155+This mailing list should be used for discussion, feature
156+requests, and will be the prefered channel for
157+announcements.
158+
scripts/etc.rc.d.init.d.sslh.centosView
@@ -4,39 +4,36 @@
44 # sslh This shell script takes care of starting and stopping
55 # sslh - a daemon switching incoming connection between SSH and SSL/HTTPS servers
66 #
77 # Author: Andre Krajnik akrajnik@gmail.com
8+# 2010-03-20
89 #
10+#
911 # chkconfig: 2345 13 87
12+#
1013 # description: sslh - a daemon switching incoming connection between SSH and SSL/HTTPS servers
1114
1215 # Source function library.
1316 . /etc/init.d/functions
1417
1518 # ./sslh -p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22
1619
17-SSLH='/usr/local/sbin/sslh'
18-PIDFILE='/var/run/sslh'
20+SSLH="/usr/local/sbin/sslh"
21+PIDFILE="/var/run/sslh"
1922
20-OPTIONS='-p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22 -P $PIDFILE'
23+OPTIONS="-p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22"
2124
2225 if [ -f /etc/sysconfig/sslh ]; then
2326 . /etc/sysconfig/sslh
2427 fi
2528
26-
2729 start() {
2830 echo -n "Starting SSL-SSH-Switch: "
2931 if [ -f $PIDFILE ]; then
3032 PID=`cat $PIDFILE`
3133 echo sslh already running: $PID
3234 exit 2;
33- elif [ -f $PIDFILE ]; then
34- PID=`cat $PIDFILE`
35- echo sslh already running: $PID
36- exit 2;
3735 else
38- cd $SLAPD_DIR
3936 daemon $SSLH $OPTIONS
4037 RETVAL=$?
4138 echo
4239 [ $RETVAL -eq 0 ] && touch $PIDFILE
@@ -74,4 +71,6 @@
7471 ;;
7572 esac
7673 exit $?
7774
75+
76+
sslh.podView
@@ -5,34 +5,44 @@
55 sslh - ssl/ssh multiplexer
66
77 =head1 SYNOPSIS
88
9-sslh [ B<-t> I<num> ] [B<-p> I<listening address>] [B<-l> I<target address for SSL>] [B<-s> I<target address for SSH>] [B<-u> I<username>] [B<-P> I<pidfile>] [-v] [-i] [-V]
9+sslh [ B<-t> I<num> ] [B<-p> I<listening address>] [B<-l> I<target address for SSL>] [B<-s> I<target address for SSH>] [B<-o> I<target address for OpenVPN>] [B<-u> I<username>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f]
1010
1111 =head1 DESCRIPTION
1212
13-B<sslh> lets one accept both HTTPS and SSH connections on
14-the same port. It makes it possible to connect to an SSH
15-server on port 443 (e.g. from inside a corporate firewall,
16-which almost never block port 443) while still serving HTTPS
17-on that port.
13+B<sslh> accepts HTTPS, SSH and OpenVPN connections on the
14+same port. This makes it possible to connect to an SSH
15+server or an OpenVPN on port 443 (e.g. from inside a
16+corporate firewall, which almost never block port 443) while
17+still serving HTTPS on that port.
1818
1919 The idea is to have B<sslh> listen to the external 443 port,
2020 accept the incoming connections, work out what type of
2121 connection it is, and then fordward to the appropriate
2222 server.
2323
2424 =head2 Protocol detection
2525
26-The protocol detection is made based on a small difference
27-between SSL and SSH: an SSL client connecting to a server
28-speaks first, whereas an SSH client expects the SSH server
29-to speak first (announcing itself with a banner). B<sslh>
30-waits for some time for the incoming connection to send data.
31-If it does before the timeout occurs, it is supposed to be
32-an SSL connection. Otherwise, it is supposed to be an SSH
33-connection.
26+The protocol detection is made based on the first bytes sent
27+by the client: SSH connections start by identifying each
28+other's versions using clear text "SSH-2.0" strings (or
29+equivalent version strings). This is defined in RFC4253,
30+4.2. Meanwhile, OpenVPN clients start with 0x00 0x0D 0x38.
3431
32+Additionally, two kind of SSH clients exist: the client
33+waits for the server to send its version string ("Shy"
34+client, which is the case of OpenSSH and Putty), or the
35+client sends its version first ("Bold" client, which is the
36+case of Bitvise Tunnelier and ConnectBot).
37+
38+B<sslh> waits for some time for the incoming connection to
39+send data. If it stays quiet after the timeout period, it is
40+assumed to be a shy SSH client, and is connected to the SSH
41+server. Otherwise, B<sslh> reads the first packet the client
42+provides, and connects it to the SSH server if it starts
43+with "SSH-", or connects it to the SSL server otherwise.
44+
3545 =head2 Libwrap support
3646
3747 One drawback of B<sslh> is that the B<ssh> and B<httpd>
3848 servers do not see the original IP address of the client
@@ -77,8 +87,15 @@
7787
7888 Interface and port on which to forward SSH connection,
7989 defaults to I<localhost:22>.
8090
91+=item B<-o> I<target address for OpenVPN>
92+
93+Interface and port on which to forward OpenVPN connections.
94+This parameter is optional, and has no default. If not
95+specified, incoming OpenVPN connections will not be detected
96+as such and treated the same as SSL.
97+
8198 =item B<-v>
8299
83100 Increase verboseness.
84101
@@ -99,10 +116,16 @@
99116
100117 =item B<-i>
101118
102119 Runs as an I<inetd> server. Options B<-P> (PID file), B<-p>
103-(listen address), B<-U> (user) are ignored.
120+(listen address), B<-u> (user) are ignored.
104121
122+=item B<-f>
123+
124+Runs in foreground. The server will not fork and will remain connected
125+to the terminal. Messages normally sent to B<syslog> will also be sent
126+to I<stderr>.
127+
105128 =back
106129
107130 =head1 FILES
108131
README.MacOSXView
@@ -1,0 +1,54 @@
1+
2+sslh is available for Mac OS X via MacPorts. If you have
3+MacPorts installed on your system you can install sslh by
4+executing the following in the Terminal:
5+
6+port install sslh
7+
8+Also, the following is a helpful launchd configuration that
9+covers the most common use case of sslh. Save the following
10+into a text file, e.g.
11+/Library/LaunchDaemons/net.rutschle.sslh.plist, then load it
12+with launchctl or simply reboot.
13+
14+----BEGIN FILE----
15+<?xml version="1.0" encoding="UTF-8"?>
16+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
17+"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
18+<plist version="1.0">
19+<dict>
20+ <key>Disabled</key>
21+ <false/>
22+ <key>KeepAlive</key>
23+ <true/>
24+ <key>Label</key>
25+ <string>net.rutschle.sslh</string>
26+ <key>ProgramArguments</key>
27+ <array>
28+ <string>/opt/local/sbin/sslh</string>
29+ <string>-f</string>
30+ <string>-v</string>
31+ <string>-u</string>
32+ <string>nobody</string>
33+ <string>-p</string>
34+ <string>0.0.0.0:443</string>
35+ <string>-s</string>
36+ <string>localhost:22</string>
37+ <string>-l</string>
38+ <string>localhost:443</string>
39+ </array>
40+ <key>QueueDirectories</key>
41+ <array/>
42+ <key>RunAtLoad</key>
43+ <true/>
44+ <key>StandardErrorPath</key>
45+ <string>/Library/Logs/sslh.log</string>
46+ <key>StandardOutPath</key>
47+ <string>/Library/Logs/sslh.log</string>
48+ <key>WatchPaths</key>
49+ <array/>
50+</dict>
51+</plist>
52+----END FILE----
53+
54+
sslh.cView
@@ -1,498 +1,0 @@
1-/*
2- Reimplementation of sslh in C
3-
4-# Copyright (C) 2007-2008 Yves Rutschle
5-#
6-# This program is free software; you can redistribute it
7-# and/or modify it under the terms of the GNU General Public
8-# License as published by the Free Software Foundation; either
9-# version 2 of the License, or (at your option) any later
10-# version.
11-#
12-# This program is distributed in the hope that it will be
13-# useful, but WITHOUT ANY WARRANTY; without even the implied
14-# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15-# PURPOSE. See the GNU General Public License for more
16-# details.
17-#
18-# The full text for the General Public License is here:
19-# http://www.gnu.org/licenses/gpl.html
20-
21-*/
22-
23-#define _GNU_SOURCE
24-#include <sys/types.h>
25-#include <fcntl.h>
26-#include <string.h>
27-#include <unistd.h>
28-#include <stdlib.h>
29-#include <stdio.h>
30-#include <signal.h>
31-#include <sys/socket.h>
32-#include <sys/wait.h>
33-#include <netinet/in.h>
34-#include <arpa/inet.h>
35-#include <netdb.h>
36-#include <pwd.h>
37-#include <syslog.h>
38-#include <libgen.h>
39-
40-#ifdef LIBWRAP
41-#include <tcpd.h>
42-int allow_severity =0, deny_severity = 0;
43-#endif
44-
45-#ifndef VERSION
46-#define VERSION "v?"
47-#endif
48-
49-#define CHECK_RES_DIE(res, str) \
50-if (res == -1) { \
51- perror(str); \
52- exit(1); \
53-}
54-
55-#define USAGE_STRING \
56-"sslh " VERSION "\n" \
57-"usage:\n" \
58-"\tsslh [-t <timeout>] -u <username> -p [listenaddr:]<listenport> \n" \
59-"\t\t-s [sshhost:]port -l [sslhost:]port [-P pidfile] [-v] [-i] [-V]\n\n" \
60-"-v: verbose\n" \
61-"-V: version\n" \
62-"-p: address and port to listen on. default: 0.0.0.0:443\n" \
63-"-s: SSH address: where to connect an SSH connection. default: localhost:22\n" \
64-"-l: SSL address: where to connect an SSL connection.\n" \
65-"-P: PID file. Default: /var/run/sslh.pid.\n" \
66-"-i: Run as a inetd service.\n" \
67-""
68-
69-int verbose = 0; /* That's really quite global */
70-
71-/* Starts a listening socket on specified address.
72- Returns file descriptor
73- */
74-int start_listen_socket(struct sockaddr *addr)
75-{
76- struct sockaddr_in *saddr = (struct sockaddr_in*)addr;
77- int sockfd, res, reuse;
78-
79- sockfd = socket(AF_INET, SOCK_STREAM, 0);
80- CHECK_RES_DIE(sockfd, "socket");
81-
82- reuse = 1;
83- res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
84- CHECK_RES_DIE(res, "setsockopt");
85-
86- res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr));
87- CHECK_RES_DIE(res, "bind");
88-
89- res = listen (sockfd, 5);
90- CHECK_RES_DIE(res, "listen");
91-
92- return sockfd;
93-}
94-
95-
96-/*
97- * moves data from one fd to other
98- * returns 0 if incoming socket closed, size moved otherwise
99- */
100-int fd2fd(int target, int from)
101-{
102- char buffer[BUFSIZ];
103- int size;
104-
105- size = read(from, buffer, sizeof(buffer));
106- CHECK_RES_DIE(size, "read");
107-
108- if (size == 0)
109- return 0;
110-
111- size = write(target, buffer, size);
112- CHECK_RES_DIE(size, "write");
113-
114- return size;
115-}
116-
117-/* shovels data from one fd to the other and vice-versa
118- returns after one socket closed
119- */
120-int shovel(int fd1, int fd2)
121-{
122- fd_set fds;
123- int res;
124-
125- FD_ZERO(&fds);
126- while (1) {
127- FD_SET(fd1, &fds);
128- FD_SET(fd2, &fds);
129-
130- res = select(
131- (fd1 > fd2 ? fd1 : fd2 ) + 1,
132- &fds,
133- NULL,
134- NULL,
135- NULL
136- );
137- CHECK_RES_DIE(res, "select");
138-
139- if (FD_ISSET(fd1, &fds)) {
140- res = fd2fd(fd2, fd1);
141- if (!res) {
142- if (verbose) fprintf(stderr, "client socket closed\n");
143- return res;
144- }
145- }
146-
147- if (FD_ISSET(fd2, &fds)) {
148- res = fd2fd(fd1, fd2);
149- if (!res) {
150- if (verbose) fprintf(stderr, "server socket closed\n");
151- return res;
152- }
153- }
154- }
155-}
156-
157-/* returns a string that prints the IP and port of the sockaddr */
158-char* sprintaddr(char* buf, size_t size, struct sockaddr* s)
159-{
160- char addr_str[1024];
161-
162- inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str));
163- snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port));
164- return buf;
165-}
166-
167-/* turns a "hostname:port" string into a struct sockaddr;
168-sock: socket address to which to copy the addr
169-fullname: input string -- it gets clobbered
170-*/
171-void resolve_name(struct sockaddr *sock, char* fullname) {
172- struct addrinfo *addr, hint;
173- char *serv, *host;
174- int res;
175-
176- char *sep = strchr(fullname, ':');
177-
178- if (!sep) /* No separator: parameter is just a port */
179- {
180- serv = fullname;
181- fprintf(stderr, "names must be fully specified as hostname:port\n");
182- exit(1);
183- }
184- else {
185- host = fullname;
186- serv = sep+1;
187- *sep = 0;
188- }
189-
190- memset(&hint, 0, sizeof(hint));
191- hint.ai_family = PF_INET;
192- hint.ai_socktype = SOCK_STREAM;
193-
194- res = getaddrinfo(host, serv, &hint, &addr);
195- if (res) {
196- fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
197- if (res == EAI_SERVICE)
198- fprintf(stderr, "(Check you have specified all ports)\n");
199- exit(1);
200- }
201-
202- memcpy(sock, addr->ai_addr, sizeof(*sock));
203-}
204-
205-/* syslogs who connected to where */
206-void log_connection(int socket, char* target)
207-{
208- struct sockaddr peeraddr;
209- socklen_t size = sizeof(peeraddr);
210- char buf[64];
211- int res;
212-
213- res = getpeername(socket, &peeraddr, &size);
214- CHECK_RES_DIE(res, "getpeername");
215-
216- syslog(LOG_INFO, "connection from %s forwarded to %s\n",
217- sprintaddr(buf, sizeof(buf), &peeraddr), target);
218-
219-}
220-
221-/*
222- * Settings that depend on the command line. That's less global than verbose * :-)
223- * They're set in main(), but also used in start_shoveler(), and it'd be heavy-handed
224- * to pass it all as parameters
225- */
226-int timeout = 2;
227-struct sockaddr addr_listen;
228-struct sockaddr addr_ssl, addr_ssh;
229-
230-/* libwrap (tcpd): check the ssh connection is legal. This is necessary because
231- * the actual sshd will only see a connection coming from localhost and can't
232- * apply the rules itself.
233- */
234-void check_access_rights(int in_socket)
235-{
236-#ifdef LIBWRAP
237- struct sockaddr peeraddr;
238- socklen_t size = sizeof(peeraddr);
239- char addr_str[1024];
240- struct hostent *host;
241- struct in_addr addr;
242- int res;
243-
244- res = getpeername(in_socket, &peeraddr, &size);
245- CHECK_RES_DIE(res, "getpeername");
246- inet_ntop(AF_INET, &((struct sockaddr_in*)&peeraddr)->sin_addr, addr_str, sizeof(addr_str));
247-
248- addr.s_addr = inet_addr(addr_str);
249- host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
250-
251- if (!hosts_ctl("sshd", (host ? host->h_name : STRING_UNKNOWN), addr_str, STRING_UNKNOWN)) {
252- if (verbose)
253- fprintf(stderr, "access denied\n");
254- log_connection(in_socket, "access denied");
255- close(in_socket);
256- exit(0);
257- }
258-#endif
259-}
260-
261-/* Child process that finds out what to connect to and proxies
262- */
263-void start_shoveler(int in_socket)
264-{
265- fd_set fds;
266- struct timeval tv;
267- struct sockaddr *saddr;
268- int res;
269- int out_socket;
270- char *target;
271-
272- FD_ZERO(&fds);
273- FD_SET(in_socket, &fds);
274- memset(&tv, 0, sizeof(tv));
275- tv.tv_sec = timeout;
276- res = select(in_socket + 1, &fds, NULL, NULL, &tv);
277- if (res == -1)
278- perror("select");
279-
280- /* Pick the target address depending on whether we timed out or not */
281- if (FD_ISSET(in_socket, &fds)) {
282- /* The client wrote something to the socket: it's an SSL connection */
283- saddr = &addr_ssl;
284- target = "SSL";
285- } else {
286- /* The client hasn't written anything and we timed out: connect to SSH */
287- saddr = &addr_ssh;
288- target = "SSH";
289-
290- /* do hosts_access check if built with libwrap support */
291- check_access_rights(in_socket);
292- }
293-
294- log_connection(in_socket, target);
295-
296- /* Connect the target socket */
297- out_socket = socket(AF_INET, SOCK_STREAM, 0);
298- res = connect(out_socket, saddr, sizeof(addr_ssl));
299- CHECK_RES_DIE(res, "connect");
300- if (verbose)
301- fprintf(stderr, "connected to something\n");
302-
303- shovel(in_socket, out_socket);
304-
305- close(in_socket);
306- close(out_socket);
307-
308- if (verbose)
309- fprintf(stderr, "connection closed down\n");
310-
311- exit(0);
312-}
313-
314-void setup_signals(void)
315-{
316- int res;
317- struct sigaction action;
318-
319- /* Request no SIGCHLD is sent upon termination of
320- * the children */
321- memset(&action, 0, sizeof(action));
322- action.sa_handler = NULL;
323- action.sa_flags = SA_NOCLDWAIT;
324- res = sigaction(SIGCHLD, &action, NULL);
325- CHECK_RES_DIE(res, "sigaction");
326-}
327-
328-/* Open syslog connection with appropriate banner;
329- * banner is made up of basename(bin_name)+"[pid]" */
330-void setup_syslog(char* bin_name) {
331- char *name1, *name2;
332-
333- name1 = strdup(bin_name);
334- asprintf(&name2, "%s[%d]", basename(name1), getpid());
335- openlog(name2, LOG_CONS, LOG_AUTH);
336- free(name1);
337- /* Don't free name2, as openlog(3) uses it (at least in glibc) */
338-}
339-
340-/* We don't want to run as root -- drop priviledges if required */
341-void drop_privileges(char* user_name)
342-{
343- int res;
344- struct passwd *pw = getpwnam(user_name);
345- if (!pw) {
346- fprintf(stderr, "%s: not found\n", user_name);
347- exit(1);
348- }
349- if (verbose)
350- fprintf(stderr, "turning into %s\n", user_name);
351-
352- res = setgid(pw->pw_gid);
353- CHECK_RES_DIE(res, "setgid");
354- setuid(pw->pw_uid);
355- CHECK_RES_DIE(res, "setuid");
356-}
357-
358-/* Writes my PID */
359-void write_pid_file(char* pidfile)
360-{
361- FILE *f;
362-
363- f = fopen(pidfile, "w");
364- if (!f) {
365- perror(pidfile);
366- exit(1);
367- }
368-
369- fprintf(f, "%d\n", getpid());
370- fclose(f);
371-}
372-
373-void printsettings(void)
374-{
375- char buf[64];
376-
377- fprintf(
378- stderr,
379- "SSL addr: %s (after timeout %ds)\n",
380- sprintaddr(buf, sizeof(buf), &addr_ssl),
381- timeout
382- );
383- fprintf(stderr, "SSH addr: %s\n", sprintaddr(buf, sizeof(buf), &addr_ssh));
384- fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen));
385-}
386-
387-int main(int argc, char *argv[])
388-{
389-
390- extern char *optarg;
391- extern int optind;
392- int c, res;
393-
394- int in_socket, listen_socket;
395-
396- /* Init defaults */
397- char *user_name = "nobody";
398- char listen_str[] = "0.0.0.0:443";
399- char ssl_str[] = "localhost:443";
400- char ssh_str[] = "localhost:22";
401- char *pid_file = "/var/run/sslh.pid";
402- char inetd = 0;
403-
404- resolve_name(&addr_listen, listen_str);
405- resolve_name(&addr_ssl, ssl_str);
406- resolve_name(&addr_ssh, ssh_str);
407-
408- while ((c = getopt(argc, argv, "t:l:s:p:P:ivVu:")) != EOF) {
409- switch (c) {
410-
411- case 't':
412- timeout = atoi(optarg);
413- break;
414-
415- case 'p':
416- resolve_name(&addr_listen, optarg);
417- break;
418-
419- case 'l':
420- resolve_name(&addr_ssl, optarg);
421- break;
422-
423- case 's':
424- resolve_name(&addr_ssh, optarg);
425- break;
426-
427- case 'i':
428- inetd = 1;
429- break;
430-
431- case 'v':
432- verbose += 1;
433- break;
434-
435- case 'V':
436- printf("sslh %s\n", VERSION);
437- exit(0);
438-
439- case 'u':
440- user_name = optarg;
441- break;
442-
443- case 'P':
444- pid_file = optarg;
445- break;
446-
447- default:
448- fprintf(stderr, USAGE_STRING);
449- exit(2);
450- }
451- }
452-
453- if(inetd)
454- {
455- verbose = 0;
456- start_shoveler(0);
457- exit(0);
458- }
459-
460- if (verbose)
461- printsettings();
462-
463- setup_signals();
464-
465- listen_socket = start_listen_socket(&addr_listen);
466-
467- if (fork() > 0) exit(0); /* Detach */
468-
469- write_pid_file(pid_file);
470-
471- drop_privileges(user_name);
472-
473- /* New session -- become group leader */
474- res = setsid();
475- CHECK_RES_DIE(res, "setsid: already process leader");
476-
477- /* Open syslog connection */
478- setup_syslog(argv[0]);
479-
480- /* Main server loop: accept connections, find what they are, fork shovelers */
481- while (1)
482- {
483- in_socket = accept(listen_socket, 0, 0);
484- if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
485-
486- if (!fork())
487- {
488- close(listen_socket);
489- start_shoveler(in_socket);
490- exit(0);
491- }
492- close(in_socket);
493- }
494-
495- return 0;
496-}
497-
498-
common.cView
@@ -1,0 +1,614 @@
1+/* Code and variables that is common to both fork and select-based
2+ * servers.
3+ *
4+ * No code here should assume whether sockets are blocking or not.
5+ **/
6+
7+#define _GNU_SOURCE
8+#include <sys/types.h>
9+#include <fcntl.h>
10+#include <string.h>
11+#include <unistd.h>
12+#include <stdlib.h>
13+#include <stdarg.h>
14+#include <stdio.h>
15+#include <signal.h>
16+#include <sys/socket.h>
17+#include <sys/wait.h>
18+#include <netinet/in.h>
19+#include <arpa/inet.h>
20+#include <netdb.h>
21+#include <pwd.h>
22+#include <syslog.h>
23+#include <libgen.h>
24+
25+#include "common.h"
26+
27+/* Added to make the code compilable under CYGWIN
28+ * */
29+#ifndef SA_NOCLDWAIT
30+#define SA_NOCLDWAIT 0
31+#endif
32+
33+int is_ssh_protocol(const char *p, int len);
34+int is_openvpn_protocol(const char *p, int len);
35+int is_true(const char *p, int len) { return 1; }
36+
37+struct proto protocols[] = {
38+ /* affected description service saddr probe */
39+ { 0, "SSH", "sshd", {0}, is_ssh_protocol },
40+ { 0, "OpenVPN", NULL, {0}, is_openvpn_protocol },
41+ /* probe for SSL always successes: it's the default, and must be tried last
42+ **/
43+ { 0, "SSL", NULL, {0}, is_true }
44+};
45+
46+
47+const char* USAGE_STRING =
48+"sslh " VERSION "\n" \
49+"usage:\n" \
50+"\tsslh [-v] [-i] [-V] [-f]"
51+"[-t <timeout>] -u <username> -p [listenaddr:]<listenport> \n" \
52+"\t\t-s [sshhost:]port -l [sslhost:]port [-P pidfile]\n\n" \
53+"-v: verbose\n" \
54+"-V: version\n" \
55+"-f: foreground\n" \
56+"-p: address and port to listen on. default: 0.0.0.0:443\n" \
57+"-s: SSH address: where to connect an SSH connection. default: localhost:22\n" \
58+"-l: SSL address: where to connect an SSL connection.\n" \
59+"-o: OpenVPN address: where to connect an OpenVPN connection.\n" \
60+"-P: PID file. Default: /var/run/sslh.pid.\n" \
61+"-i: Run as a inetd service.\n" \
62+"";
63+
64+
65+/*
66+ * Settings that depend on the command line.
67+ * They're set in main(), but also used in other places, and it'd be
68+ * heavy-handed to pass it all as parameters
69+ */
70+int verbose = 0;
71+int probing_timeout = 2;
72+int inetd = 0;
73+int foreground = 0;
74+struct sockaddr addr_listen;
75+char *user_name, *pid_file;
76+
77+#ifdef LIBWRAP
78+#include <tcpd.h>
79+int allow_severity =0, deny_severity = 0;
80+#endif
81+
82+
83+
84+/* Starts a listening socket on specified address.
85+ Returns file descriptor
86+ */
87+int start_listen_socket(struct sockaddr *addr)
88+{
89+ struct sockaddr_in *saddr = (struct sockaddr_in*)addr;
90+ int sockfd, res, reuse;
91+
92+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
93+ CHECK_RES_DIE(sockfd, "socket");
94+
95+ reuse = 1;
96+ res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
97+ CHECK_RES_DIE(res, "setsockopt");
98+
99+ res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr));
100+ CHECK_RES_DIE(res, "bind");
101+
102+ res = listen (sockfd, 50);
103+ CHECK_RES_DIE(res, "listen");
104+
105+ return sockfd;
106+}
107+
108+/* Store some data to write to the queue later */
109+int defer_write(struct queue *q, void* data, int data_size)
110+{
111+ if (verbose)
112+ fprintf(stderr, "**** writing defered on fd %d\n", q->fd);
113+ q->defered_data = malloc(data_size);
114+ q->begin_defered_data = q->defered_data;
115+ q->defered_data_size = data_size;
116+ memcpy(q->defered_data, data, data_size);
117+
118+ return 0;
119+}
120+
121+/* tries to flush some of the data for specified queue
122+ * Upon success, the number of bytes written is returned.
123+ * Upon failure, -1 returned (e.g. connexion closed)
124+ * */
125+int flush_defered(struct queue *q)
126+{
127+ int n;
128+
129+ if (verbose)
130+ fprintf(stderr, "flushing defered data to fd %d\n", q->fd);
131+
132+ n = write(q->fd, q->defered_data, q->defered_data_size);
133+ if (n == -1)
134+ return n;
135+
136+ if (n == q->defered_data_size) {
137+ /* All has been written -- release the memory */
138+ free(q->begin_defered_data);
139+ q->begin_defered_data = NULL;
140+ q->defered_data = NULL;
141+ q->defered_data_size = 0;
142+ } else {
143+ /* There is data left */
144+ q->defered_data += n;
145+ q->defered_data_size -= n;
146+ }
147+
148+
149+ return n;
150+}
151+
152+
153+void init_cnx(struct connection *cnx)
154+{
155+ memset(cnx, 0, sizeof(*cnx));
156+ cnx->q[0].fd = -1;
157+ cnx->q[1].fd = -1;
158+}
159+
160+void dump_connection(struct connection *cnx)
161+{
162+ printf("state: %d\n", cnx->state);
163+ printf("fd %d, %d defered\n", cnx->q[0].fd, cnx->q[0].defered_data_size);
164+ printf("fd %d, %d defered\n", cnx->q[1].fd, cnx->q[1].defered_data_size);
165+}
166+
167+
168+/*
169+ * moves data from one fd to other
170+ *
171+ * retuns number of bytes copied if success
172+ * returns 0 (FD_CNXCLOSED) if incoming socket closed
173+ * returns FD_NODATA if no data was available
174+ * returns FD_STALLED if data was read, could not be written, and has been
175+ * stored in temporary buffer.
176+ *
177+ * slot for debug only and may go away at some point
178+ */
179+int fd2fd(struct queue *target_q, struct queue *from_q)
180+{
181+ char buffer[BUFSIZ];
182+ int target, from, size_r, size_w;
183+
184+ target = target_q->fd;
185+ from = from_q->fd;
186+
187+ size_r = read(from, buffer, sizeof(buffer));
188+ if (size_r == -1) {
189+ switch (errno) {
190+ case EAGAIN:
191+ if (verbose)
192+ fprintf(stderr, "reading 0 from %d\n", from);
193+ return FD_NODATA;
194+
195+ case ECONNRESET:
196+ case EPIPE:
197+ return FD_CNXCLOSED;
198+ }
199+ }
200+
201+ CHECK_RES_RETURN(size_r, "read");
202+
203+ if (size_r == 0)
204+ return FD_CNXCLOSED;
205+
206+ size_w = write(target, buffer, size_r);
207+ /* process -1 when we know how to deal with it */
208+ if ((size_w == -1)) {
209+ switch (errno) {
210+ case EAGAIN:
211+ /* write blocked: Defer data */
212+ defer_write(target_q, buffer, size_r);
213+ return FD_STALLED;
214+
215+ case ECONNRESET:
216+ case EPIPE:
217+ /* remove end closed -- drop the connection */
218+ return FD_CNXCLOSED;
219+ }
220+ } else if (size_w < size_r) {
221+ /* incomplete write -- defer the rest of the data */
222+ defer_write(target_q, buffer + size_w, size_r - size_w);
223+ return FD_STALLED;
224+ }
225+
226+ CHECK_RES_RETURN(size_w, "write");
227+
228+ return size_w;
229+}
230+
231+/* If the client wrote something first, read it and check if it's a SSH banner.
232+ * Data is left in appropriate defered write buffer.
233+ */
234+int is_ssh_protocol(const char *p, int len)
235+{
236+ if (!strncmp(p, "SSH-", 4)) {
237+ return 1;
238+ }
239+ return 0;
240+}
241+
242+/* Is the buffer the beginning of an OpenVPN connection?
243+ * (code lifted from OpenVPN port-share option)
244+ */
245+int is_openvpn_protocol (const char*p,int len)
246+{
247+#define P_OPCODE_SHIFT 3
248+#define P_CONTROL_HARD_RESET_CLIENT_V2 7
249+ if (len >= 3)
250+ {
251+ return p[0] == 0
252+ && p[1] >= 14
253+ && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
254+ }
255+ else if (len >= 2)
256+ {
257+ return p[0] == 0 && p[1] >= 14;
258+ }
259+ else
260+ return 0;
261+}
262+
263+
264+/*
265+ * Read the beginning of data coming from the client connection and check if
266+ * it's a known protocol. Then leave the data on the defered
267+ * write buffer of the connection and returns the protocol index in the
268+ * protocols[] array *
269+ */
270+T_PROTO_ID probe_client_protocol(struct connection *cnx)
271+{
272+ char buffer[BUFSIZ];
273+ int n, i;
274+
275+ n = read(cnx->q[0].fd, buffer, sizeof(buffer));
276+ /* It's possible that read() returns an error, e.g. if the client
277+ * disconnected between the previous call to select() and now. If that
278+ * happens, we just connect to the default protocol so the caller of this
279+ * function does not have to deal with a specific failure condition (the
280+ * connection will just fail later normally). */
281+ if (n > 0) {
282+ defer_write(&cnx->q[1], buffer, n);
283+
284+ for (i = 0; i < ARRAY_SIZE(protocols); i++) {
285+ if (protocols[i].affected) {
286+ if (protocols[i].probe(buffer, n)) {
287+ return i;
288+ }
289+ }
290+ }
291+ }
292+
293+ /* If none worked, return the last one */
294+ return ARRAY_SIZE(protocols) - 1;
295+}
296+
297+/* returns a string that prints the IP and port of the sockaddr */
298+char* sprintaddr(char* buf, size_t size, struct sockaddr* s)
299+{
300+ char addr_str[1024];
301+
302+ inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str));
303+ snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port));
304+ return buf;
305+}
306+
307+/* turns a "hostname:port" string into a struct sockaddr;
308+sock: socket address to which to copy the addr
309+fullname: input string -- it gets clobbered
310+*/
311+void resolve_name(struct sockaddr *sock, char* fullname)
312+{
313+ struct addrinfo *addr, hint;
314+ char *serv, *host;
315+ int res;
316+
317+ char *sep = strchr(fullname, ':');
318+
319+ if (!sep) /* No separator: parameter is just a port */
320+ {
321+ serv = fullname;
322+ fprintf(stderr, "names must be fully specified as hostname:port\n");
323+ exit(1);
324+ }
325+ else {
326+ host = fullname;
327+ serv = sep+1;
328+ *sep = 0;
329+ }
330+
331+ memset(&hint, 0, sizeof(hint));
332+ hint.ai_family = PF_INET;
333+ hint.ai_socktype = SOCK_STREAM;
334+
335+ res = getaddrinfo(host, serv, &hint, &addr);
336+ if (res) {
337+ fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
338+ if (res == EAI_SERVICE)
339+ fprintf(stderr, "(Check you have specified all ports)\n");
340+ exit(1);
341+ }
342+
343+ memcpy(sock, addr->ai_addr, sizeof(*sock));
344+
345+ freeaddrinfo(addr);
346+}
347+
348+/* Log to syslog, and to stderr if foreground */
349+void log_message(int type, char* msg, ...)
350+{
351+ va_list ap;
352+
353+ va_start(ap, msg);
354+ vsyslog(type, msg, ap);
355+ va_end(ap);
356+
357+ va_start(ap, msg);
358+ if (foreground)
359+ vfprintf(stderr, msg, ap);
360+ va_end(ap);
361+}
362+
363+/* syslogs who connected to where */
364+void log_connection(struct connection *cnx)
365+{
366+ struct sockaddr peeraddr, localaddr;
367+ socklen_t size = sizeof(peeraddr);
368+ char buf[64], buf2[64];
369+ int res;
370+
371+ res = getpeername(cnx->q[0].fd, &peeraddr, &size);
372+ if (res == -1) return; /* that should never happen, right? */
373+
374+ res = getpeername(cnx->q[1].fd, &localaddr, &size);
375+ if (res == -1) return; /* that should never happen, right? */
376+
377+ log_message(LOG_INFO, "connection from %s forwarded to %s\n",
378+ sprintaddr(buf, sizeof(buf), &peeraddr), sprintaddr(buf2, sizeof(buf2), &localaddr));
379+
380+}
381+
382+
383+/* libwrap (tcpd): check the connection is legal. This is necessary because
384+ * the actual server will only see a connection coming from localhost and can't
385+ * apply the rules itself.
386+ *
387+ * Returns -1 if access is denied, 0 otherwise
388+ */
389+int check_access_rights(int in_socket, const char* service)
390+{
391+#ifdef LIBWRAP
392+ struct sockaddr peeraddr;
393+ socklen_t size = sizeof(peeraddr);
394+ char addr_str[1024];
395+ struct hostent *host;
396+ struct in_addr addr;
397+ int res;
398+
399+ res = getpeername(in_socket, &peeraddr, &size);
400+ CHECK_RES_DIE(res, "getpeername");
401+ inet_ntop(AF_INET, &((struct sockaddr_in*)&peeraddr)->sin_addr, addr_str, sizeof(addr_str));
402+
403+ addr.s_addr = inet_addr(addr_str);
404+ host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
405+
406+ if (!hosts_ctl(service, (host ? host->h_name : STRING_UNKNOWN), addr_str, STRING_UNKNOWN)) {
407+ if (verbose)
408+ fprintf(stderr, "access denied\n");
409+ log_connection(in_socket, "access denied");
410+ close(in_socket);
411+ return -1;
412+ }
413+#endif
414+ return 0;
415+}
416+
417+
418+void setup_signals(void)
419+{
420+ int res;
421+ struct sigaction action;
422+
423+ /* Request no SIGCHLD is sent upon termination of
424+ * the children */
425+ memset(&action, 0, sizeof(action));
426+ action.sa_handler = NULL;
427+ action.sa_flags = SA_NOCLDWAIT;
428+ res = sigaction(SIGCHLD, &action, NULL);
429+ CHECK_RES_DIE(res, "sigaction");
430+}
431+
432+/* Open syslog connection with appropriate banner;
433+ * banner is made up of basename(bin_name)+"[pid]" */
434+void setup_syslog(char* bin_name) {
435+ char *name1, *name2;
436+
437+ name1 = strdup(bin_name);
438+ asprintf(&name2, "%s[%d]", basename(name1), getpid());
439+ openlog(name2, LOG_CONS, LOG_AUTH);
440+ free(name1);
441+ /* Don't free name2, as openlog(3) uses it (at least in glibc) */
442+
443+ log_message(LOG_INFO, "%s %s started\n", server_type, VERSION);
444+}
445+
446+/* We don't want to run as root -- drop priviledges if required */
447+void drop_privileges(char* user_name)
448+{
449+ int res;
450+ struct passwd *pw = getpwnam(user_name);
451+ if (!pw) {
452+ fprintf(stderr, "%s: not found\n", user_name);
453+ exit(1);
454+ }
455+ if (verbose)
456+ fprintf(stderr, "turning into %s\n", user_name);
457+
458+ res = setgid(pw->pw_gid);
459+ CHECK_RES_DIE(res, "setgid");
460+ setuid(pw->pw_uid);
461+ CHECK_RES_DIE(res, "setuid");
462+}
463+
464+/* Writes my PID */
465+void write_pid_file(char* pidfile)
466+{
467+ FILE *f;
468+
469+ f = fopen(pidfile, "w");
470+ if (!f) {
471+ perror(pidfile);
472+ exit(1);
473+ }
474+
475+ fprintf(f, "%d\n", getpid());
476+ fclose(f);
477+}
478+
479+void printsettings(void)
480+{
481+ char buf[64];
482+ int i;
483+
484+ for (i = 0; i < ARRAY_SIZE(protocols); i++) {
485+ if (protocols[i].affected)
486+ fprintf(stderr,
487+ "%s addr: %s. libwrap service: %s\n",
488+ protocols[i].description,
489+ sprintaddr(buf, sizeof(buf), &protocols[i].saddr),
490+ protocols[i].service);
491+ }
492+ fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen));
493+}
494+
495+void parse_cmdline(int argc, char* argv[])
496+{
497+ int c;
498+
499+ while ((c = getopt(argc, argv, "t:l:s:o:p:P:ivfVu:")) != EOF) {
500+ switch (c) {
501+
502+ case 't':
503+ probing_timeout = atoi(optarg);
504+ break;
505+
506+ case 'p':
507+ resolve_name(&addr_listen, optarg);
508+ break;
509+
510+ case 'l':
511+ protocols[PROT_SSL].affected = 1;
512+ resolve_name(&protocols[PROT_SSL].saddr, optarg);
513+ break;
514+
515+ case 's':
516+ protocols[PROT_SSH].affected = 1;
517+ resolve_name(&protocols[PROT_SSH].saddr, optarg);
518+ break;
519+
520+ case 'o':
521+ protocols[PROT_OPENVPN].affected = 1;
522+ resolve_name(&protocols[PROT_OPENVPN].saddr, optarg);
523+ break;
524+
525+ case 'i':
526+ inetd = 1;
527+ break;
528+
529+ case 'f':
530+ foreground = 1;
531+ break;
532+
533+ case 'v':
534+ verbose += 1;
535+ break;
536+
537+ case 'V':
538+ printf("%s %s\n", server_type, VERSION);
539+ exit(0);
540+
541+ case 'u':
542+ user_name = optarg;
543+ break;
544+
545+ case 'P':
546+ pid_file = optarg;
547+ break;
548+
549+ default:
550+ fprintf(stderr, USAGE_STRING);
551+ exit(2);
552+ }
553+ }
554+}
555+
556+int main(int argc, char *argv[])
557+{
558+
559+ extern char *optarg;
560+ extern int optind;
561+ int res;
562+
563+ int listen_socket;
564+
565+ /* Init defaults */
566+ char listen_str[] = "0.0.0.0:443";
567+ char ssl_str[] = "localhost:443";
568+ char ssh_str[] = "localhost:22";
569+ pid_file = "/var/run/sslh.pid";
570+ user_name = "nobody";
571+ foreground = 0;
572+
573+ resolve_name(&addr_listen, listen_str);
574+ protocols[PROT_SSL].affected = 1;
575+ resolve_name(&protocols[PROT_SSL].saddr, ssl_str);
576+ protocols[PROT_SSH].affected = 1;
577+ resolve_name(&protocols[PROT_SSH].saddr, ssh_str);
578+
579+ parse_cmdline(argc, argv);
580+
581+ if (inetd)
582+ {
583+ verbose = 0;
584+ start_shoveler(0);
585+ exit(0);
586+ }
587+
588+ if (verbose)
589+ printsettings();
590+
591+ listen_socket = start_listen_socket(&addr_listen);
592+
593+ if (!foreground)
594+ if (fork() > 0) exit(0); /* Detach */
595+
596+ setup_signals();
597+
598+ write_pid_file(pid_file);
599+
600+ drop_privileges(user_name);
601+
602+ /* New session -- become group leader */
603+ if (getuid() == 0) {
604+ res = setsid();
605+ CHECK_RES_DIE(res, "setsid: already process leader");
606+ }
607+
608+ /* Open syslog connection */
609+ setup_syslog(argv[0]);
610+
611+ main_loop(listen_socket);
612+
613+ return 0;
614+}
common.hView
@@ -1,0 +1,127 @@
1+#define _GNU_SOURCE
2+#include <sys/types.h>
3+#include <fcntl.h>
4+#include <errno.h>
5+#include <string.h>
6+#include <unistd.h>
7+#include <stdlib.h>
8+#include <stdio.h>
9+#include <signal.h>
10+#include <sys/socket.h>
11+#include <sys/wait.h>
12+#include <netinet/in.h>
13+#include <arpa/inet.h>
14+#include <netdb.h>
15+#include <pwd.h>
16+#include <syslog.h>
17+#include <libgen.h>
18+#include <time.h>
19+
20+#ifndef VERSION
21+#define VERSION "v?"
22+#endif
23+
24+#define CHECK_RES_DIE(res, str) \
25+ if (res == -1) { \
26+ perror(str); \
27+ exit(1); \
28+ }
29+
30+#define CHECK_RES_RETURN(res, str) \
31+ if (res == -1) { \
32+ log_message(LOG_CRIT, "%s: %d\n", str, errno); \
33+ return res; \
34+ }
35+
36+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
37+
38+#if 1
39+#define TRACE fprintf(stderr, "%s:%d\n", __FILE__, __LINE__);
40+#else
41+#define TRACE
42+#endif
43+
44+enum connection_state {
45+ ST_PROBING=1, /* Waiting for timeout to find where to forward */
46+ ST_SHOVELING /* Connexion is established */
47+};
48+
49+
50+/* Different types of protocols we support.
51+ * These must match the order of the protocols[] array in common.c */
52+typedef enum protocol_type {
53+ PROT_SSH,
54+ PROT_OPENVPN,
55+ PROT_SSL,
56+} T_PROTO_ID;
57+
58+/* For each protocol we need: */
59+struct proto {
60+ int affected; /* are we actually using it? */
61+ char* description; /* a string that says what it is (for logging) */
62+ char* service; /* service name to do libwrap checks */
63+ struct sockaddr saddr; /* where to switch that protocol */
64+ int (*probe)(const char*, int); /* function to probe that protocol */
65+};
66+
67+/* A table in common.c contains all the known protocols */
68+extern struct proto protocols[];
69+
70+/* A 'queue' is composed of a file descriptor (which can be read from or
71+ * written to), and a queue for defered write data */
72+struct queue {
73+ int fd;
74+ void *begin_defered_data;
75+ void *defered_data;
76+ int defered_data_size;
77+};
78+
79+struct connection {
80+ enum connection_state state;
81+ time_t probe_timeout;
82+
83+ /* q[0]: queue for external connection (client);
84+ * q[1]: queue for internal connection (httpd or sshd);
85+ * */
86+ struct queue q[2];
87+};
88+
89+#define FD_CNXCLOSED 0
90+#define FD_NODATA -1
91+#define FD_STALLED -2
92+
93+
94+/* common.c */
95+void init_cnx(struct connection *cnx);
96+int start_listen_socket(struct sockaddr *addr);
97+int fd2fd(struct queue *target, struct queue *from);
98+T_PROTO_ID probe_client_protocol(struct connection *cnx);
99+char* sprintaddr(char* buf, size_t size, struct sockaddr* s);
100+void resolve_name(struct sockaddr *sock, char* fullname) ;
101+void log_connection(struct connection *cnx);
102+int check_access_rights(int in_socket, const char* service);
103+void setup_signals(void);
104+void setup_syslog(char* bin_name);
105+void drop_privileges(char* user_name);
106+void write_pid_file(char* pidfile);
107+void printsettings(void);
108+void parse_cmdline(int argc, char* argv[]);
109+void log_message(int type, char* msg, ...);
110+void dump_connection(struct connection *cnx);
111+
112+
113+int defer_write(struct queue *q, void* data, int data_size);
114+int flush_defered(struct queue *q);
115+
116+extern int probing_timeout, verbose, inetd;
117+extern struct sockaddr addr_listen, addr_ssl, addr_ssh, addr_openvpn;
118+extern const char* USAGE_STRING;
119+extern char* user_name, *pid_file;
120+extern const char* server_type;
121+
122+/* sslh-fork.c */
123+void start_shoveler(int);
124+
125+void main_loop(int);
126+
127+
sslh-fork.cView
@@ -1,0 +1,151 @@
1+/*
2+ Reimplementation of sslh in C
3+
4+# Copyright (C) 2007-2008 Yves Rutschle
5+#
6+# This program is free software; you can redistribute it
7+# and/or modify it under the terms of the GNU General Public
8+# License as published by the Free Software Foundation; either
9+# version 2 of the License, or (at your option) any later
10+# version.
11+#
12+# This program is distributed in the hope that it will be
13+# useful, but WITHOUT ANY WARRANTY; without even the implied
14+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15+# PURPOSE. See the GNU General Public License for more
16+# details.
17+#
18+# The full text for the General Public License is here:
19+# http://www.gnu.org/licenses/gpl.html
20+
21+*/
22+
23+#include "common.h"
24+
25+const char* server_type = "sslh-fork";
26+
27+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
28+
29+/* shovels data from one fd to the other and vice-versa
30+ returns after one socket closed
31+ */
32+int shovel(struct connection *cnx)
33+{
34+ fd_set fds;
35+ int res, i;
36+ int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
37+
38+ FD_ZERO(&fds);
39+ while (1) {
40+ FD_SET(cnx->q[0].fd, &fds);
41+ FD_SET(cnx->q[1].fd, &fds);
42+
43+ res = select(
44+ max_fd,
45+ &fds,
46+ NULL,
47+ NULL,
48+ NULL
49+ );
50+ CHECK_RES_DIE(res, "select");
51+
52+ for (i = 0; i < 2; i++) {
53+ if (FD_ISSET(cnx->q[i].fd, &fds)) {
54+ res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
55+ if (!res) {
56+ if (verbose)
57+ fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
58+ return res;
59+ }
60+ }
61+ }
62+ }
63+}
64+
65+/* Child process that finds out what to connect to and proxies
66+ */
67+void start_shoveler(int in_socket)
68+{
69+ fd_set fds;
70+ struct timeval tv;
71+ struct sockaddr *saddr;
72+ int res;
73+ int out_socket;
74+ char *target;
75+ struct connection cnx;
76+ T_PROTO_ID prot;
77+
78+ init_cnx(&cnx);
79+
80+ FD_ZERO(&fds);
81+ FD_SET(in_socket, &fds);
82+ memset(&tv, 0, sizeof(tv));
83+ tv.tv_sec = probing_timeout;
84+ res = select(in_socket + 1, &fds, NULL, NULL, &tv);
85+ if (res == -1)
86+ perror("select");
87+
88+ cnx.q[0].fd = in_socket;
89+
90+ if (FD_ISSET(in_socket, &fds)) {
91+ /* Received data: figure out what protocol it is */
92+ prot = probe_client_protocol(&cnx);
93+ } else {
94+ /* Timed out: it's necessarily SSH */
95+ prot = PROT_SSH;
96+ }
97+
98+ saddr = &protocols[prot].saddr;
99+ target = protocols[prot].description;
100+ if (protocols[prot].service &&
101+ check_access_rights(in_socket, protocols[prot].service)) {
102+ exit(0);
103+ }
104+
105+ /* Connect the target socket */
106+ out_socket = socket(AF_INET, SOCK_STREAM, 0);
107+ res = connect(out_socket, saddr, sizeof(addr_ssl));
108+ CHECK_RES_DIE(res, "connect");
109+ if (verbose)
110+ fprintf(stderr, "connected to something\n");
111+
112+ cnx.q[1].fd = out_socket;
113+
114+ log_connection(&cnx);
115+
116+ flush_defered(&cnx.q[1]);
117+
118+ shovel(&cnx);
119+
120+ close(in_socket);
121+ close(out_socket);
122+
123+ if (verbose)
124+ fprintf(stderr, "connection closed down\n");
125+
126+ exit(0);
127+}
128+
129+void main_loop(int listen_socket)
130+{
131+ int in_socket;
132+
133+ while (1)
134+ {
135+ in_socket = accept(listen_socket, 0, 0);
136+ if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
137+
138+ if (!fork())
139+ {
140+ close(listen_socket);
141+ start_shoveler(in_socket);
142+ exit(0);
143+ }
144+ close(in_socket);
145+ }
146+}
147+
148+/* The actual main is in common.c: it's the same for both version of
149+ * the server
150+ */
151+
sslh-select.cView
@@ -1,0 +1,337 @@
1+/*
2+ sslh: a SSL/SSH multiplexer
3+
4+# Copyright (C) 2007-2010 Yves Rutschle
5+#
6+# This program is free software; you can redistribute it
7+# and/or modify it under the terms of the GNU General Public
8+# License as published by the Free Software Foundation; either
9+# version 2 of the License, or (at your option) any later
10+# version.
11+#
12+# This program is distributed in the hope that it will be
13+# useful, but WITHOUT ANY WARRANTY; without even the implied
14+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15+# PURPOSE. See the GNU General Public License for more
16+# details.
17+#
18+# The full text for the General Public License is here:
19+# http://www.gnu.org/licenses/gpl.html
20+
21+*/
22+
23+#define __LINUX__
24+
25+#include "common.h"
26+
27+const char* server_type = "sslh-select";
28+
29+/* cnx_num_alloc is the number of connection to allocate at once (at start-up,
30+ * and then every time we get too many simultaneous connections: e.g. start
31+ * with 100 slots, then if we get more than 100 connections allocate another
32+ * 100 slots, and so on). We never free up connection structures. We try to
33+ * allocate as many structures at once as will fit in one page.
34+ */
35+static long cnx_num_alloc;
36+
37+/* Make the file descriptor non-block */
38+int set_nonblock(int fd)
39+{
40+ int flags;
41+
42+ flags = fcntl(fd, F_GETFL);
43+ CHECK_RES_RETURN(flags, "fcntl");
44+
45+ flags |= O_NONBLOCK;
46+
47+ flags = fcntl(fd, F_SETFL, flags);
48+ CHECK_RES_RETURN(flags, "fcntl");
49+
50+ return flags;
51+}
52+
53+int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
54+{
55+ int i;
56+
57+ for (i = 0; i < 2; i++) {
58+ if (verbose)
59+ fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
60+
61+ close(cnx->q[i].fd);
62+ FD_CLR(cnx->q[i].fd, fds);
63+ FD_CLR(cnx->q[i].fd, fds2);
64+ if (cnx->q[i].defered_data)
65+ free(cnx->q[i].defered_data);
66+ }
67+ init_cnx(cnx);
68+ return 0;
69+}
70+
71+/* Accepts a connection from the main socket and assigns it to an empty slot.
72+ * If no slots are available, allocate another few. If that fails, drop the
73+ * connexion */
74+int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size)
75+{
76+ int in_socket, free, i, res;
77+ struct connection *new;
78+
79+ in_socket = accept(listen_socket, 0, 0);
80+ CHECK_RES_RETURN(in_socket, "accept");
81+
82+ res = set_nonblock(in_socket);
83+ if (res == -1) return -1;
84+
85+ /* Find an empty slot */
86+ for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) {
87+ /* nothing */
88+ }
89+ if (free >= *cnx_size) {
90+ if (verbose)
91+ fprintf(stderr, "buying more slots from the slot machine.\n");
92+ new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0]));
93+ if (!new) {
94+ log_message(LOG_ERR, "unable to realloc -- dropping connection\n");
95+ return -1;
96+ }
97+ *cnx = new;
98+ *cnx_size += cnx_num_alloc;
99+ for (i = free; i < *cnx_size; i++) {
100+ init_cnx(&(*cnx)[i]);
101+ }
102+ }
103+ (*cnx)[free].q[0].fd = in_socket;
104+ (*cnx)[free].state = ST_PROBING;
105+ (*cnx)[free].probe_timeout = time(NULL) + probing_timeout;
106+
107+ if (verbose)
108+ fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free);
109+
110+ return in_socket;
111+}
112+
113+/* Connect queue 1 of connection to SSL; returns new file descriptor */
114+int connect_queue(struct connection *cnx, struct sockaddr *addr,
115+ char* cnx_name,
116+ fd_set *fds_r, fd_set *fds_w)
117+{
118+ struct queue *q = &cnx->q[1];
119+ int res;
120+
121+ q->fd = socket(AF_INET, SOCK_STREAM, 0);
122+ res = connect(q->fd, addr, sizeof(*addr));
123+ log_connection(cnx);
124+ if (res == -1) {
125+ tidy_connection(cnx, fds_r, fds_w);
126+ log_message(LOG_ERR, "forward to %s failed\n", cnx_name);
127+ return -1;
128+ } else {
129+ set_nonblock(q->fd);
130+ flush_defered(q);
131+ if (q->defered_data) {
132+ FD_SET(q->fd, fds_w);
133+ } else {
134+ FD_SET(q->fd, fds_r);
135+ }
136+ return q->fd;
137+ }
138+}
139+
140+/* shovels data from active fd to the other
141+ returns after one socket closed or operation would block
142+ */
143+void shovel(struct connection *cnx, int active_fd,
144+ fd_set *fds_r, fd_set *fds_w)
145+{
146+ struct queue *read_q, *write_q;
147+
148+ read_q = &cnx->q[active_fd];
149+ write_q = &cnx->q[1-active_fd];
150+
151+ if (verbose)
152+ fprintf(stderr, "activity on fd%d\n", read_q->fd);
153+
154+ switch(fd2fd(write_q, read_q)) {
155+ case -1:
156+ case FD_CNXCLOSED:
157+ tidy_connection(cnx, fds_r, fds_w);
158+ break;
159+
160+ case FD_STALLED:
161+ FD_SET(write_q->fd, fds_w);
162+ FD_CLR(read_q->fd, fds_r);
163+ break;
164+
165+ default: /* Nothing */
166+ break;
167+ }
168+}
169+
170+/* returns true if specified fd is initialised and present in fd_set */
171+int is_fd_active(int fd, fd_set* set)
172+{
173+ if (fd == -1) return 0;
174+ return FD_ISSET(fd, set);
175+}
176+
177+/* Main loop: the idea is as follow:
178+ * - fds_r and fds_w contain the file descritors to monitor in read and write
179+ * - When a file descriptor goes off, process it: read from it, write the data
180+ * to its corresponding pair.
181+ * - When a file descriptor blocks when writing, remove the read fd from fds_r,
182+ * move the data to a defered buffer, and add the write fd to fds_w. Defered
183+ * buffer is allocated dynamically.
184+ * - When we can write to a file descriptor that has defered data, we try to
185+ * write as much as we can. Once all data is written, remove the fd from fds_w
186+ * and add its corresponding pair to fds_r, free the buffer.
187+ *
188+ * That way, each pair of file descriptor (read from one, write to the other)
189+ * is monitored either for read or for write, but never for both.
190+ */
191+void main_loop(int listen_socket)
192+{
193+ fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
194+ fd_set readfds, writefds; /* working read and write fd sets */
195+ struct timeval tv;
196+ int max_fd, in_socket, i, j, res;
197+ struct connection *cnx;
198+ T_PROTO_ID prot;
199+ int num_cnx; /* Number of connections in *cnx */
200+ int num_probing = 0; /* Number of connections currently probing
201+ * We use this to know if we need to time out of
202+ * select() */
203+
204+ FD_ZERO(&fds_r);
205+ FD_ZERO(&fds_w);
206+ FD_SET(listen_socket, &fds_r);
207+ max_fd = listen_socket + 1;
208+
209+ set_nonblock(listen_socket);
210+
211+ cnx_num_alloc = getpagesize() / sizeof(struct connection);
212+
213+ num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
214+ cnx = malloc(num_cnx * sizeof(struct connection));
215+ for (i = 0; i < num_cnx; i++)
216+ init_cnx(&cnx[i]);
217+
218+ while (1)
219+ {
220+ memset(&tv, 0, sizeof(tv));
221+ tv.tv_sec = probing_timeout;
222+
223+ memcpy(&readfds, &fds_r, sizeof(readfds));
224+ memcpy(&writefds, &fds_w, sizeof(writefds));
225+
226+ if (verbose)
227+ fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing);
228+ res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL);
229+ if (res < 0)
230+ perror("select");
231+
232+
233+ /* Check main socket for new connections */
234+ if (FD_ISSET(listen_socket, &readfds)) {
235+ in_socket = accept_new_connection(listen_socket, &cnx, &num_cnx);
236+ num_probing++;
237+
238+ if (in_socket > 0) {
239+ FD_SET(in_socket, &fds_r);
240+ if (in_socket >= max_fd)
241+ max_fd = in_socket + 1;;
242+ }
243+ FD_CLR(listen_socket, &readfds);
244+ }
245+
246+ /* Check all sockets for write activity */
247+ for (i = 0; i < num_cnx; i++) {
248+ if (cnx[i].q[0].fd != -1) {
249+ for (j = 0; j < 2; j++) {
250+ if (is_fd_active(cnx[i].q[j].fd, &writefds)) {
251+ res = flush_defered(&cnx[i].q[j]);
252+ if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
253+ if (cnx[i].state == ST_PROBING) num_probing--;
254+ tidy_connection(&cnx[i], &fds_r, &fds_w);
255+ if (verbose)
256+ fprintf(stderr, "closed slot %d\n", i);
257+ }
258+ /* If no defered data is left, stop monitoring the fd
259+ * for write, and restart monitoring the other one for reads*/
260+ if (!cnx[i].q[j].defered_data_size) {
261+ FD_CLR(cnx[i].q[j].fd, &fds_w);
262+ FD_SET(cnx[i].q[1-j].fd, &fds_r);
263+ }
264+ }
265+ }
266+ }
267+ }
268+
269+ /* Check all sockets for read activity */
270+ for (i = 0; i < num_cnx; i++) {
271+ for (j = 0; j < 2; j++) {
272+ if (is_fd_active(cnx[i].q[j].fd, &readfds) ||
273+ ((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) {
274+ if (verbose)
275+ fprintf(stderr, "processing fd%d slot %d\n", j, i);
276+
277+ switch (cnx[i].state) {
278+
279+ case ST_PROBING:
280+ if (j == 1) {
281+ fprintf(stderr, "Activity on fd2 while probing, impossible\n");
282+ dump_connection(&cnx[i]);
283+ exit(1);
284+ }
285+ num_probing--;
286+ cnx[i].state = ST_SHOVELING;
287+
288+ /* If timed out it's SSH, otherwise the client sent
289+ * data so probe the protocol */
290+ if ((cnx[i].probe_timeout < time(NULL))) {
291+ prot = PROT_SSH;
292+ } else {
293+ prot = probe_client_protocol(&cnx[i]);
294+ }
295+
296+ /* libwrap check if required for this protocol */
297+ if (protocols[prot].service &&
298+ check_access_rights(in_socket, protocols[prot].service)) {
299+ tidy_connection(&cnx[i], &fds_r, &fds_w);
300+ res = -1;
301+ } else {
302+ res = connect_queue(&cnx[i],
303+ &protocols[prot].saddr,
304+ protocols[prot].description,
305+ &fds_r, &fds_w);
306+ }
307+
308+ if (res >= max_fd)
309+ max_fd = res + 1;;
310+ break;
311+
312+ case ST_SHOVELING:
313+ shovel(&cnx[i], j, &fds_r, &fds_w);
314+ break;
315+
316+ default: /* illegal */
317+ log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state);
318+ exit(1);
319+ }
320+ }
321+ }
322+ }
323+ }
324+}
325+
326+
327+void start_shoveler(int listen_socket) {
328+ fprintf(stderr, "inetd mode is not supported in select mode\n");
329+ exit(1);
330+}
331+
332+
333+/* The actual main is in common.c: it's the same for both version of
334+ * the server
335+ */
336+
337+
tView
@@ -1,0 +1,229 @@
1+#! /usr/bin/perl -w
2+
3+# Test script for sslh
4+
5+# The principle is to create two listening sockets which
6+# will act as the ssh and ssl servers, and then perform a
7+# number of connections in various combinations to check
8+# that the server behaves properly.
9+
10+use strict;
11+use IO::Socket::INET;
12+use Test::More qw/no_plan/;
13+
14+# We use ports 9000, 9001 and 9002 -- hope that won't clash
15+# with anything...
16+my $ssh_port = 9000;
17+my $ssl_port = 9001;
18+my $sslh_port = 9002;
19+my $pidfile = "/tmp/sslh.pid";
20+
21+# How many connections will be created during the last test
22+my $NUM_SSL_CNX = 20;
23+my $NUM_SSH_CNX = 20;
24+
25+# Which tests do we run
26+my $SSL_CNX = 1;
27+my $SSH_SHY_CNX = 1;
28+my $SSH_BOLD_CNX = 1;
29+my $SSL_MIX_SSH = 1;
30+my $SSH_MIX_SSL = 1;
31+my $BIG_MSG = 1;
32+my $MANY_CNX = 1;
33+
34+# the Listen parameter needs to be bigger than the max number of connexions
35+# we'll make during the last test (we open a bunch of SSH connexions, and
36+# accept them all at once afterwards)
37+my $ssh_listen = new IO::Socket::INET(LocalHost=> "localhost:$ssh_port", Blocking => 1, Reuse => 1, Listen => $NUM_SSH_CNX + 1);
38+die "error1: $!\n" unless $ssh_listen;
39+
40+my $ssl_listen = new IO::Socket::INET(LocalHost=> "localhost:$ssl_port", Blocking => 1, Reuse => 1, Listen => $NUM_SSL_CNX + 1);
41+die "error2: $!\n" unless $ssl_listen;
42+
43+# Start sslh with the right plumbing
44+my $sslh_pid;
45+if (!($sslh_pid = fork)) {
46+ my $user = (getpwuid $<)[0]; # Run under current username
47+ exec "./sslh-fork -v -u $user -p localhost:$sslh_port -s localhost:$ssh_port -l localhost:$ssl_port -P $pidfile";
48+ #exec "./sslh-select -v -f -u $user -p localhost:$sslh_port -s localhost:$ssh_port -l localhost:$ssl_port -P $pidfile";
49+ #exec "valgrind --leak-check=full ./sslh-select -v -f -u $user -p localhost:$sslh_port -s localhost:$ssh_port -l localhost:$ssl_port -P $pidfile";
50+ exit 0;
51+}
52+warn "spawned $sslh_pid\n";
53+sleep 1;
54+
55+
56+my $test_data = "hello world\n";
57+
58+# Test: SSL connection
59+if ($SSL_CNX) {
60+ print "***Test: SSL connection\n";
61+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
62+ warn "$!\n" unless $cnx_l;
63+ if (defined $cnx_l) {
64+ print $cnx_l $test_data;
65+ my $ssl_data = $ssl_listen->accept;
66+ my $data = <$ssl_data>;
67+ is($data, $test_data, "SSL connection");
68+ }
69+}
70+
71+# Test: Shy SSH connection
72+if ($SSH_SHY_CNX) {
73+ print "***Test: Shy SSH connection\n";
74+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
75+ warn "$!\n" unless $cnx_h;
76+ if (defined $cnx_h) {
77+ sleep 3;
78+ my $ssh_data = $ssh_listen->accept;
79+ print $cnx_h $test_data;
80+ my $data = <$ssh_data>;
81+ is($data, $test_data, "Shy SSH connection");
82+ }
83+}
84+
85+# Test: Bold SSH connection
86+if ($SSH_BOLD_CNX) {
87+ print "***Test: Bold SSH connection\n";
88+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
89+ warn "$!\n" unless $cnx_h;
90+ if (defined $cnx_h) {
91+ my $td = "SSH-2.0 testsuite\n$test_data";
92+ print $cnx_h $td;
93+ my $ssh_data = $ssh_listen->accept;
94+ my $data = <$ssh_data>;
95+ $data .= <$ssh_data>;
96+ is($data, $td, "Bold SSH connection");
97+ }
98+}
99+
100+# Test: One SSL half-started then one SSH
101+if ($SSL_MIX_SSH) {
102+ print "***Test: One SSL half-started then one SSH\n";
103+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
104+ warn "$!\n" unless $cnx_l;
105+ if (defined $cnx_l) {
106+ print $cnx_l $test_data;
107+ my $cnx_h= new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
108+ warn "$!\n" unless $cnx_h;
109+ if (defined $cnx_h) {
110+ sleep 3;
111+ my $ssh_data = $ssh_listen->accept;
112+ print $cnx_h $test_data;
113+ my $data_h = <$ssh_data>;
114+ is($data_h, $test_data, "SSH during SSL being established");
115+ }
116+ my $ssl_data = $ssl_listen->accept;
117+ my $data = <$ssl_data>;
118+ is($data, $test_data, "SSL connection interrupted by SSH");
119+ }
120+}
121+
122+# Test: One SSH half-started then one SSL
123+if ($SSH_MIX_SSL) {
124+ print "***Test: One SSH half-started then one SSL\n";
125+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
126+ warn "$!\n" unless $cnx_h;
127+ if (defined $cnx_h) {
128+ sleep 3;
129+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
130+ warn "$!\n" unless $cnx_l;
131+ if (defined $cnx_l) {
132+ print $cnx_l $test_data;
133+ my $ssl_data = $ssl_listen->accept;
134+ my $data = <$ssl_data>;
135+ is($data, $test_data, "SSL during SSH being established");
136+ }
137+ my $ssh_data = $ssh_listen->accept;
138+ print $cnx_h $test_data;
139+ my $data = <$ssh_data>;
140+ is($data, $test_data, "SSH connection interrupted by SSL");
141+ }
142+}
143+
144+
145+# Test: Big messages
146+if ($BIG_MSG) {
147+ print "***Test: big message\n";
148+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
149+ warn "$!\n" unless $cnx_l;
150+ my $test_data2 = "helloworld";
151+ my $rept = 10000;
152+ if (defined $cnx_l) {
153+ print $cnx_l ($test_data2 x $rept);
154+ print $cnx_l "\n";
155+ my $ssl_data = $ssl_listen->accept;
156+ my $data = <$ssl_data>;
157+ is($data, $test_data2 x $rept . "\n", "Big message");
158+ }
159+}
160+
161+# Test: several connections active at once
162+# We start 50 SSH connexions, then open 50 SSL connexion, then accept the 50
163+# SSH connexions, then we randomize the order of connexions and write 1000
164+# messages on each connexion and check we get it on the other end.
165+if ($MANY_CNX) {
166+ print "***Test: several connexions active at once\n";
167+ my (@cnx_h, @ssh_data);
168+ for (1..$NUM_SSH_CNX) {
169+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
170+ warn "----> $!\n" unless defined $cnx_h;
171+ if (defined $cnx_h) {
172+ push @cnx_h, $cnx_h;
173+ }
174+ }
175+ my (@cnx_l, @ssl_data);
176+ for (1..$NUM_SSL_CNX) {
177+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
178+ warn "----> $!\n" unless defined $cnx_l;
179+ if (defined $cnx_l) {
180+ push @cnx_l, $cnx_l;
181+ print $cnx_l " ";
182+ push @ssl_data, ($ssl_listen->accept)[0];
183+ }
184+ }
185+ # give time to the connections to turn to SSH
186+ sleep 4;
187+ # and accept all SSH connections...
188+ for (1..$NUM_SSH_CNX) {
189+ push @ssh_data, $ssh_listen->accept;
190+ }
191+
192+# Make up a random order so we don't always hit the
193+# connexions in the same order
194+
195+# fisher_yates_shuffle( \@array ) : generate a random permutation
196+# of @array in place (from
197+# http://docstore.mik.ua/orelly/perl/cookbook/ch04_18.htm,
198+# modified to shuffle two arrays in the same way)
199+ sub fisher_yates_shuffle {
200+ my ($array1, $array2) = @_;
201+ my $i;
202+ for ($i = @$array1; --$i; ) {
203+ my $j = int rand ($i+1);
204+ next if $i == $j;
205+ @$array1[$i,$j] = @$array1[$j,$i];
206+ @$array2[$i,$j] = @$array2[$j,$i];
207+ }
208+ }
209+
210+ my @cnx = (@cnx_l, @cnx_l);
211+ my @rcv = (@ssl_data, @ssl_data);
212+
213+ fisher_yates_shuffle(\@rcv, \@cnx);
214+
215+# Send a bunch of messages
216+ for my $cnt (1..1000) {
217+ foreach (@cnx) {
218+ print $_ "$cnt$test_data";
219+ }
220+ foreach (@rcv) {
221+ my $data = <$_>;
222+ like($data, qr/ ?$cnt$test_data/, "Multiple messages [$cnt]");
223+ }
224+ }
225+}
226+
227+
228+
229+kill 15, `cat $pidfile` or warn "kill: $!\n";

Built with git-ssb-web