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
ChangeLog | changed |
Makefile | changed |
README | changed |
scripts/etc.rc.d.init.d.sslh.centos | changed |
sslh.pod | changed |
README.MacOSX | added |
sslh.c | deleted |
common.c | added |
common.h | added |
sslh-fork.c | added |
sslh-select.c | added |
t | added |
ChangeLog | ||
---|---|---|
@@ -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 | + | |
1 | 31 | v1.7: 01FEB2010 |
2 | 32 | Added CentOS init.d script (Andre Krajnik). |
3 | 33 | |
4 | 34 | Fixed default ssl address inconsistancy, now |
Makefile | ||
---|---|---|
@@ -1,38 +1,50 @@ | ||
1 | 1 | # Configuration |
2 | 2 | |
3 | -VERSION="v1.7a" | |
4 | -USELIBWRAP=1 # Use libwrap? | |
3 | +VERSION="v1.8" | |
4 | +USELIBWRAP= # Use libwrap? | |
5 | 5 | PREFIX=/usr/local |
6 | 6 | |
7 | 7 | MAN=sslh.8.gz # man page name |
8 | 8 | |
9 | 9 | # End of configuration -- the rest should take care of |
10 | 10 | # itself |
11 | 11 | |
12 | 12 | CC = gcc |
13 | -CFLAGS=-Wall | |
13 | +CFLAGS=-Wall -g | |
14 | 14 | |
15 | 15 | #LIBS=-lnet |
16 | 16 | LIBS= |
17 | +OBJS=common.o | |
17 | 18 | |
18 | 19 | ifneq ($(strip $(USELIBWRAP)),) |
19 | 20 | LIBS:=$(LIBS) -lwrap |
20 | 21 | CFLAGS:=$(CFLAGS) -DLIBWRAP |
21 | 22 | endif |
22 | 23 | |
23 | 24 | all: sslh $(MAN) |
24 | 25 | |
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 $< | |
28 | 28 | |
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 | + | |
29 | 41 | $(MAN): sslh.pod Makefile |
30 | 42 | pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN) |
31 | 43 | |
32 | 44 | # generic install: install binary and man page |
33 | 45 | install: sslh $(MAN) |
34 | - install -D sslh $(PREFIX)/sbin/sslh | |
46 | + install -D sslh-fork $(PREFIX)/sbin/sslh | |
35 | 47 | install -D -m 0644 $(MAN) $(PREFIX)/share/man/man8/$(MAN) |
36 | 48 | |
37 | 49 | # "extended" install for Debian: install startup script |
38 | 50 | install-debian: install sslh $(MAN) |
@@ -45,5 +57,12 @@ | ||
45 | 57 | rm -f $(PREFIX)/sbin/sslh $(PREFIX)/share/man/man8/$(MAN) /etc/init.d/sslh /etc/default/sslh |
46 | 58 | update-rc.d sslh remove |
47 | 59 | |
48 | 60 | 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 | + |
README | ||
---|---|---|
@@ -1,43 +1,43 @@ | ||
1 | 1 | ===== sslh -- A ssl/ssh multiplexer. ===== |
2 | 2 | |
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. | |
7 | 8 | |
8 | 9 | ==== Compile and install ==== |
9 | 10 | |
10 | 11 | If you're lucky, the Makefile will work for you: |
11 | 12 | |
12 | 13 | make install |
13 | 14 | |
14 | -(see below for configuration hints) | |
15 | +The Makefile produces two different executables: sslh-fork | |
16 | +and sslh-select. | |
15 | 17 | |
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. | |
16 | 25 | |
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. | |
18 | 34 | |
19 | -Compilation instructions (the binary produced won't contain | |
20 | -the version number, which is stored only in the Makefile) | |
21 | 35 | |
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 | - | |
36 | 36 | To install: |
37 | 37 | |
38 | 38 | make |
39 | -cp sslh /usr/local/sbin | |
39 | +cp sslh-fork /usr/local/sbin/sslh | |
40 | 40 | cp scripts/etc.default.sslh /etc/default/sslh |
41 | 41 | |
42 | 42 | For Debian: |
43 | 43 | cp scripts/etc.init.d.sslh /etc/init.d/sslh |
@@ -78,15 +78,50 @@ | ||
78 | 78 | client. |
79 | 79 | |
80 | 80 | ==== OpenVPN support ==== |
81 | 81 | |
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 | |
83 | 84 | the time the TCP connexion is established and the time they |
84 | 85 | send the first data packet. This results in sslh with |
85 | 86 | default settings timing out and assuming an SSH connexion. |
86 | 87 | To support OpenVPN connexions reliably, it is necessary to |
87 | 88 | increase sslh's timeout to 5 seconds. |
88 | 89 | |
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 | + | |
89 | 124 | ==== IP_TPROXY support ==== |
90 | 125 | |
91 | 126 | There is a netfilter patch that adds an option to the Linux |
92 | 127 | TCP/IP stack to allow a program to set the source address |
@@ -111,5 +146,13 @@ | ||
111 | 146 | when/if the feature finds its way into the main kernel and |
112 | 147 | it becomes usuable by non-root processes. |
113 | 148 | |
114 | 149 | |
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.centos | ||
---|---|---|
@@ -4,39 +4,36 @@ | ||
4 | 4 | # sslh This shell script takes care of starting and stopping |
5 | 5 | # sslh - a daemon switching incoming connection between SSH and SSL/HTTPS servers |
6 | 6 | # |
7 | 7 | # Author: Andre Krajnik akrajnik@gmail.com |
8 | +# 2010-03-20 | |
8 | 9 | # |
10 | +# | |
9 | 11 | # chkconfig: 2345 13 87 |
12 | +# | |
10 | 13 | # description: sslh - a daemon switching incoming connection between SSH and SSL/HTTPS servers |
11 | 14 | |
12 | 15 | # Source function library. |
13 | 16 | . /etc/init.d/functions |
14 | 17 | |
15 | 18 | # ./sslh -p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22 |
16 | 19 | |
17 | -SSLH='/usr/local/sbin/sslh' | |
18 | -PIDFILE='/var/run/sslh' | |
20 | +SSLH="/usr/local/sbin/sslh" | |
21 | +PIDFILE="/var/run/sslh" | |
19 | 22 | |
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" | |
21 | 24 | |
22 | 25 | if [ -f /etc/sysconfig/sslh ]; then |
23 | 26 | . /etc/sysconfig/sslh |
24 | 27 | fi |
25 | 28 | |
26 | - | |
27 | 29 | start() { |
28 | 30 | echo -n "Starting SSL-SSH-Switch: " |
29 | 31 | if [ -f $PIDFILE ]; then |
30 | 32 | PID=`cat $PIDFILE` |
31 | 33 | echo sslh already running: $PID |
32 | 34 | exit 2; |
33 | - elif [ -f $PIDFILE ]; then | |
34 | - PID=`cat $PIDFILE` | |
35 | - echo sslh already running: $PID | |
36 | - exit 2; | |
37 | 35 | else |
38 | - cd $SLAPD_DIR | |
39 | 36 | daemon $SSLH $OPTIONS |
40 | 37 | RETVAL=$? |
41 | 38 | echo |
42 | 39 | [ $RETVAL -eq 0 ] && touch $PIDFILE |
@@ -74,4 +71,6 @@ | ||
74 | 71 | ;; |
75 | 72 | esac |
76 | 73 | exit $? |
77 | 74 | |
75 | + | |
76 | + |
sslh.pod | ||
---|---|---|
@@ -5,34 +5,44 @@ | ||
5 | 5 | sslh - ssl/ssh multiplexer |
6 | 6 | |
7 | 7 | =head1 SYNOPSIS |
8 | 8 | |
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] | |
10 | 10 | |
11 | 11 | =head1 DESCRIPTION |
12 | 12 | |
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. | |
18 | 18 | |
19 | 19 | The idea is to have B<sslh> listen to the external 443 port, |
20 | 20 | accept the incoming connections, work out what type of |
21 | 21 | connection it is, and then fordward to the appropriate |
22 | 22 | server. |
23 | 23 | |
24 | 24 | =head2 Protocol detection |
25 | 25 | |
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. | |
34 | 31 | |
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 | + | |
35 | 45 | =head2 Libwrap support |
36 | 46 | |
37 | 47 | One drawback of B<sslh> is that the B<ssh> and B<httpd> |
38 | 48 | servers do not see the original IP address of the client |
@@ -77,8 +87,15 @@ | ||
77 | 87 | |
78 | 88 | Interface and port on which to forward SSH connection, |
79 | 89 | defaults to I<localhost:22>. |
80 | 90 | |
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 | + | |
81 | 98 | =item B<-v> |
82 | 99 | |
83 | 100 | Increase verboseness. |
84 | 101 | |
@@ -99,10 +116,16 @@ | ||
99 | 116 | |
100 | 117 | =item B<-i> |
101 | 118 | |
102 | 119 | 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. | |
104 | 121 | |
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 | + | |
105 | 128 | =back |
106 | 129 | |
107 | 130 | =head1 FILES |
108 | 131 |
README.MacOSX | ||
---|---|---|
@@ -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.c | ||
---|---|---|
@@ -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 | - | |
8 | -# License as published by the Free Software Foundation; either | |
9 | - | |
10 | - | |
11 | -# | |
12 | -# This program is distributed in the hope that it will be | |
13 | - | |
14 | - | |
15 | -# PURPOSE. See the GNU General Public License for more | |
16 | - | |
17 | -# | |
18 | -# The full text for the General Public License is here: | |
19 | - | |
20 | - | |
21 | -*/ | |
22 | - | |
23 | - | |
24 | - | |
25 | - | |
26 | - | |
27 | - | |
28 | - | |
29 | - | |
30 | - | |
31 | - | |
32 | - | |
33 | - | |
34 | - | |
35 | - | |
36 | - | |
37 | - | |
38 | - | |
39 | - | |
40 | - | |
41 | - | |
42 | -int allow_severity =0, deny_severity = 0; | |
43 | - | |
44 | - | |
45 | - | |
46 | - | |
47 | - | |
48 | - | |
49 | - | |
50 | -if (res == -1) { \ | |
51 | - perror(str); \ | |
52 | - exit(1); \ | |
53 | -} | |
54 | - | |
55 | - | |
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 | - | |
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 | - | |
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.c | ||
---|---|---|
@@ -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 | + | |
8 | + | |
9 | + | |
10 | + | |
11 | + | |
12 | + | |
13 | + | |
14 | + | |
15 | + | |
16 | + | |
17 | + | |
18 | + | |
19 | + | |
20 | + | |
21 | + | |
22 | + | |
23 | + | |
24 | + | |
25 | + | |
26 | + | |
27 | +/* Added to make the code compilable under CYGWIN | |
28 | + * */ | |
29 | + | |
30 | + | |
31 | + | |
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 | + | |
78 | + | |
79 | +int allow_severity =0, deny_severity = 0; | |
80 | + | |
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 | + | |
248 | + | |
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 | + | |
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 | + | |
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.h | ||
---|---|---|
@@ -1,0 +1,127 @@ | ||
1 | + | |
2 | + | |
3 | + | |
4 | + | |
5 | + | |
6 | + | |
7 | + | |
8 | + | |
9 | + | |
10 | + | |
11 | + | |
12 | + | |
13 | + | |
14 | + | |
15 | + | |
16 | + | |
17 | + | |
18 | + | |
19 | + | |
20 | + | |
21 | + | |
22 | + | |
23 | + | |
24 | + | |
25 | + if (res == -1) { \ | |
26 | + perror(str); \ | |
27 | + exit(1); \ | |
28 | + } | |
29 | + | |
30 | + | |
31 | + if (res == -1) { \ | |
32 | + log_message(LOG_CRIT, "%s: %d\n", str, errno); \ | |
33 | + return res; \ | |
34 | + } | |
35 | + | |
36 | + | |
37 | + | |
38 | + | |
39 | + | |
40 | + | |
41 | + | |
42 | + | |
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 | + | |
90 | + | |
91 | + | |
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.c | ||
---|---|---|
@@ -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 | + | |
8 | +# License as published by the Free Software Foundation; either | |
9 | + | |
10 | + | |
11 | +# | |
12 | +# This program is distributed in the hope that it will be | |
13 | + | |
14 | + | |
15 | +# PURPOSE. See the GNU General Public License for more | |
16 | + | |
17 | +# | |
18 | +# The full text for the General Public License is here: | |
19 | + | |
20 | + | |
21 | +*/ | |
22 | + | |
23 | + | |
24 | + | |
25 | +const char* server_type = "sslh-fork"; | |
26 | + | |
27 | + | |
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.c | ||
---|---|---|
@@ -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 | + | |
8 | +# License as published by the Free Software Foundation; either | |
9 | + | |
10 | + | |
11 | +# | |
12 | +# This program is distributed in the hope that it will be | |
13 | + | |
14 | + | |
15 | +# PURPOSE. See the GNU General Public License for more | |
16 | + | |
17 | +# | |
18 | +# The full text for the General Public License is here: | |
19 | + | |
20 | + | |
21 | +*/ | |
22 | + | |
23 | + | |
24 | + | |
25 | + | |
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 | + |
t | ||
---|---|---|
@@ -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