git ssb

0+

cel / sslh



Commit ae008179f033c8409c69b13787a539351bace626

v1.10:

	Fixed calls referring to sockaddr length so they work
	with FreeBSD.

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

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

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

	Support for XMPP (Arnaud Gendre).

	Updated README.MacOSX (Aaron Madlon-Kay).
Yves Rutschle committed on 7/10/2013, 9:14:15 PM
Parent: a9c9941988bfa759887061f9e11fd09b67bd0f38

Files changed

ChangeLogchanged
Makefilechanged
READMEchanged
README.MacOSXchanged
common.cchanged
common.hchanged
sslh-fork.cchanged
sslh-select.cchanged
sslh.podchanged
tchanged
echosrv.cadded
sslh-main.cadded
t_loadadded
ChangeLogView
@@ -1,5 +1,27 @@
1+v1.10:
2+ Fixed calls referring to sockaddr length so they work
3+ with FreeBSD.
4+
5+ Try target addresses in turn until one works if
6+ there are several (e.g. "localhost:22" resolves to
7+ an IPv6 address and an IPv4 address and sshd does
8+ not listen on IPv6).
9+
10+ Fixed sslh-fork so killing the head process kills
11+ the listener processes.
12+
13+ Heavily cleaned up test suite. Added stress test
14+ t_load script. Added coverage (requires lcov).
15+
16+ Support for XMPP (Arnaud Gendre).
17+
18+ Updated README.MacOSX (Aaron Madlon-Kay).
19+
120 v1.9: 02AUG2011
21+ WARNING: This version does not work with FreeBSD and
22+ derivatives!
23+
224 WARNING: Options changed, you'll need to update your
325 start-up scripts! Log format changed, you'll need to
426 update log processing scripts!
527
MakefileView
@@ -1,28 +1,33 @@
11 # Configuration
22
3-VERSION="v1.9"
3+VERSION="v1.10"
44 USELIBWRAP= # Use libwrap?
5+COV_TEST= # Perform test coverage?
56 PREFIX=/usr/local
67
78 MAN=sslh.8.gz # man page name
89
910 # End of configuration -- the rest should take care of
1011 # itself
1112
13+ifneq ($(strip $(COV_TEST)),)
14+ CFLAGS_COV=-fprofile-arcs -ftest-coverage
15+endif
16+
1217 CC = gcc
13-CFLAGS=-Wall -g
18+CFLAGS=-Wall -g $(CFLAGS_COV)
1419
1520 #LIBS=-lnet
1621 LIBS=
17-OBJS=common.o
22+OBJS=common.o sslh-main.o
1823
1924 ifneq ($(strip $(USELIBWRAP)),)
2025 LIBS:=$(LIBS) -lwrap
2126 CFLAGS:=$(CFLAGS) -DLIBWRAP
2227 endif
2328
24-all: sslh $(MAN)
29+all: sslh $(MAN) echosrv
2530
2631 .c.o: *.h
2732 $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -c $<
2833
@@ -30,14 +35,16 @@
3035 sslh: $(OBJS) sslh-fork sslh-select
3136
3237 sslh-fork: $(OBJS) sslh-fork.o Makefile common.h
3338 $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
34- strip sslh-fork
39+ #strip sslh-fork
3540
3641 sslh-select: $(OBJS) sslh-select.o Makefile common.h
3742 $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS)
38- strip sslh-select
43+ #strip sslh-select
3944
45+echosrv: $(OBJS) echosrv.o
46+ $(CC) $(CFLAGS) -o echosrv echosrv.o common.o $(LIBS)
4047
4148 $(MAN): sslh.pod Makefile
4249 pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
4350
@@ -57,9 +64,9 @@
5764 rm -f $(PREFIX)/sbin/sslh $(PREFIX)/share/man/man8/$(MAN) /etc/init.d/sslh /etc/default/sslh
5865 update-rc.d sslh remove
5966
6067 clean:
61- rm -f sslh-fork sslh-select $(MAN) *.o
68+ rm -f sslh-fork sslh-select echosrv $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
6269
6370 tags:
6471 ctags -T *.[ch]
6572
READMEView
@@ -1,9 +1,9 @@
11 ===== sslh -- A ssl/ssh multiplexer. =====
22
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
3+sslh accepts HTTPS, SSH, OpenVPN, tinc and XMPP connections
4+on the same port. This makes it possible to connect to any
5+of these servers on port 443 (e.g. from inside a corporate
66 firewall, which almost never block port 443) while still
77 serving HTTPS on that port.
88
99 ==== Compile and install ====
README.MacOSXView
@@ -12,43 +12,43 @@
1212 with launchctl or simply reboot.
1313
1414 ----BEGIN FILE----
1515 <?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">
16+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1817 <plist version="1.0">
1918 <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/>
19+ <key>Disabled</key>
20+ <false/>
21+ <key>KeepAlive</key>
22+ <true/>
23+ <key>Label</key>
24+ <string>net.rutschle.sslh</string>
25+ <key>ProgramArguments</key>
26+ <array>
27+ <string>/opt/local/sbin/sslh</string>
28+ <string>-f</string>
29+ <string>-v</string>
30+ <string>-u</string>
31+ <string>nobody</string>
32+ <string>-p</string>
33+ <string>0.0.0.0:443</string>
34+ <string>--ssh</string>
35+ <string>localhost:22</string>
36+ <string>--ssl</string>
37+ <string>localhost:443</string>
38+ </array>
39+ <key>QueueDirectories</key>
40+ <array/>
41+ <key>RunAtLoad</key>
42+ <true/>
43+ <key>StandardErrorPath</key>
44+ <string>/Library/Logs/sslh.log</string>
45+ <key>StandardOutPath</key>
46+ <string>/Library/Logs/sslh.log</string>
47+ <key>WatchPaths</key>
48+ <array/>
5049 </dict>
5150 </plist>
51+
5252 ----END FILE----
5353
5454
common.cView
@@ -33,104 +33,140 @@
3333
3434 int is_ssh_protocol(const char *p, int len);
3535 int is_openvpn_protocol(const char *p, int len);
3636 int is_tinc_protocol(const char *p, int len);
37+int is_xmpp_protocol(const char *p, int len);
3738 int is_true(const char *p, int len) { return 1; }
3839
40+/* Table of all the protocols we know how to connect to.
41+ *
42+ * The first protocol in the table is where we connect in case of timeout
43+ * (client didn't speak: typically this is SSH.)
44+ *
45+ * The last protocol in the table is where we connect if client spoke but we
46+ * couldn't probe what it's saying.
47+ */
3948 struct proto protocols[] = {
4049 /* affected description service saddr probe */
4150 { 0, "ssh", "sshd", {0}, is_ssh_protocol },
4251 { 0, "openvpn", NULL, {0}, is_openvpn_protocol },
4352 { 0, "tinc", NULL, {0}, is_tinc_protocol },
53+ { 0, "xmpp", NULL, {0}, is_xmpp_protocol },
4454 /* probe for SSL always successes: it's the default, and must be tried last
4555 **/
4656 { 0, "ssl", NULL, {0}, is_true }
4757 };
58+int num_known_protocols = ARRAY_SIZE(protocols);
4859
49-
50-const char* USAGE_STRING =
51-"sslh " VERSION "\n" \
52-"usage:\n" \
53-"\tsslh [-v] [-i] [-V] [-f]\n"
54-"\t[-t <timeout>] [-P <pidfile>] -u <username> -p <add> [-p <addr> ...] \n" \
55-"\t[--ssh <addr>] [--ssl <addr>] [--openvpn <addr>] [--tinc <addr>]\n\n" \
56-"-v: verbose\n" \
57-"-V: version\n" \
58-"-f: foreground\n" \
59-"-p: address and port to listen on. default: 0.0.0.0:443.\n Can be used several times to bind to several addresses.\n" \
60-"--ssh: SSH address: where to connect an SSH connection.\n" \
61-"--ssl: SSL address: where to connect an SSL connection.\n" \
62-"--openvpn: OpenVPN address: where to connect an OpenVPN connection.\n" \
63-"--tinc: tinc address: where to connect a tinc connection.\n" \
64-"-P: PID file. Default: /var/run/sslh.pid.\n" \
65-"-i: Run as a inetd service.\n" \
66-"";
67-
68-
6960 /*
70- * Settings that depend on the command line.
71- * They're set in main(), but also used in other places, and it'd be
72- * heavy-handed to pass it all as parameters
61+ * Settings that depend on the command line. They're set in main(), but also
62+ * used in other places in common.c, and it'd be heavy-handed to pass it all as
63+ * parameters
7364 */
7465 int verbose = 0;
7566 int probing_timeout = 2;
7667 int inetd = 0;
7768 int foreground = 0;
7869 int numeric = 0;
7970 char *user_name, *pid_file;
8071
81-struct sockaddr_storage *addr_listen = NULL; /* what addresses do we listen to? */
82-int num_addr_listen = 0; /* How many addresses do we listen to? */
72+struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
8373
8474 #ifdef LIBWRAP
8575 #include <tcpd.h>
8676 int allow_severity =0, deny_severity = 0;
8777 #endif
8878
8979
9080 /* check result and die, printing the offending address and error */
91-void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall)
81+void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
9282 {
93- char buf[64];
83+ char buf[NI_MAXHOST];
9484
9585 if (res == -1) {
9686 fprintf(stderr, "%s:%s: %s\n",
97- sprintaddr(buf, sizeof(buf), sock),
87+ sprintaddr(buf, sizeof(buf), addr),
9888 syscall,
9989 strerror(errno));
10090 exit(1);
10191 }
10292 }
10393
10494 /* Starts listening sockets on specified addresses.
10595 * IN: addr[], num_addr
106- * OUT: sockfd[]
107- * Bound file descriptors are returned in alread-allocated *sockfd pointer
108- Returns file descriptor
96+ * OUT: *sockfd[] pointer to newly-allocated array of file descriptors
97+ * Returns number of addresses bound
98+ * Bound file descriptors are returned in newly-allocated *sockfd pointer
10999 */
110-void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr)
100+int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
111101 {
112102 struct sockaddr_storage *saddr;
103+ struct addrinfo *addr;
113104 int i, res, reuse;
105+ int num_addr = 0;
114106
115- for (i = 0; i < num_addr; i++) {
116- saddr = &addr[i];
107+ for (addr = addr_list; addr; addr = addr->ai_next)
108+ num_addr++;
117109
118- sockfd[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
119- check_res_dumpdie(sockfd[i], saddr, "socket");
110+ if (verbose)
111+ fprintf(stderr, "listening to %d addresses\n", num_addr);
120112
113+ *sockfd = malloc(num_addr * sizeof(*sockfd[0]));
114+
115+ for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
116+ if (!addr) {
117+ fprintf(stderr, "FATAL: Inconsistent listen number. This should not happen.\n");
118+ exit(1);
119+ }
120+ saddr = (struct sockaddr_storage*)addr->ai_addr;
121+
122+ (*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
123+ check_res_dumpdie((*sockfd)[i], addr, "socket");
124+
121125 reuse = 1;
122- res = setsockopt(sockfd[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
123- check_res_dumpdie(res, saddr, "setsockopt");
126+ res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
127+ check_res_dumpdie(res, addr, "setsockopt");
124128
125- res = bind (sockfd[i], (struct sockaddr*)saddr, sizeof(*saddr));
126- check_res_dumpdie(res, saddr, "bind");
129+ res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
130+ check_res_dumpdie(res, addr, "bind");
127131
128- res = listen (sockfd[i], 50);
129- check_res_dumpdie(res, saddr, "listen");
132+ res = listen ((*sockfd)[i], 50);
133+ check_res_dumpdie(res, addr, "listen");
134+
130135 }
136+
137+ return num_addr;
131138 }
132139
140+/* Connect to first address that works and returns a file descriptor, or -1 if
141+ * none work. cnx_name points to the name of the service (for logging) */
142+int connect_addr(struct addrinfo *addr, char* cnx_name)
143+{
144+ struct addrinfo *a;
145+ char buf[NI_MAXHOST];
146+ int fd, res;
147+
148+ for (a = addr; a; a = a->ai_next) {
149+ if (verbose)
150+ fprintf(stderr, "connecting to %s family %d len %d\n",
151+ sprintaddr(buf, sizeof(buf), a),
152+ a->ai_addr->sa_family, a->ai_addrlen);
153+ fd = socket(a->ai_family, SOCK_STREAM, 0);
154+ if (fd == -1) {
155+ log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx_name, strerror(errno));
156+ } else {
157+ res = connect(fd, a->ai_addr, a->ai_addrlen);
158+ if (res == -1) {
159+ log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
160+ cnx_name, strerror(errno));
161+ } else {
162+ return fd;
163+ }
164+ }
165+ }
166+ return -1;
167+}
168+
133169 /* Store some data to write to the queue later */
134170 int defer_write(struct queue *q, void* data, int data_size)
135171 {
136172 if (verbose)
@@ -289,11 +325,21 @@
289325 * (protocol is undocumented, but starts with "0 " in 1.0.15)
290326 * */
291327 int is_tinc_protocol( const char *p, int len)
292328 {
293- return !strncmp(p, "0 ", len);
329+ return !strncmp(p, "0 ", 2);
294330 }
295331
332+/* Is the buffer the beginning of a jabber (XMPP) connections?
333+ * (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
334+ * clients, just checking first frame containing "jabber" in xml entity)
335+ * */
336+int is_xmpp_protocol( const char *p, int len)
337+{
338+ return strstr(p, "jabber") ? 1 : 0;
339+}
340+
341+
296342 /*
297343 * Read the beginning of data coming from the client connection and check if
298344 * it's a known protocol. Then leave the data on the defered
299345 * write buffer of the connection and returns the protocol index in the
@@ -334,25 +380,35 @@
334380 exit(1);
335381 }
336382
337383 /* returns a string that prints the IP and port of the sockaddr */
338-char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s)
384+char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
339385 {
340386 char host[NI_MAXHOST], serv[NI_MAXSERV];
387+ int res;
341388
342- getnameinfo((struct sockaddr*)s, sizeof(*s), host, sizeof(host), serv, sizeof(serv), numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
389+ res = getnameinfo(a->ai_addr, a->ai_addrlen,
390+ host, sizeof(host),
391+ serv, sizeof(serv),
392+ numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
393+
394+ if (res) {
395+ fprintf(stderr, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
396+ exit(1);
397+ }
398+
343399 snprintf(buf, size, "%s:%s", host, serv);
344400
345401 return buf;
346402 }
347403
348-/* turns a "hostname:port" string into a struct sockaddr;
349-sock: socket address to which to copy the addr
404+/* turns a "hostname:port" string into a list of struct addrinfo;
405+out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done
350406 fullname: input string -- it gets clobbered
351407 */
352-void resolve_name(struct sockaddr_storage *sock, char* fullname)
408+void resolve_name(struct addrinfo **out, char* fullname)
353409 {
354- struct addrinfo *addr, hint;
410+ struct addrinfo hint;
355411 char *serv, *host;
356412 int res;
357413
358414 char *sep = strrchr(fullname, ':');
@@ -370,19 +426,15 @@
370426 memset(&hint, 0, sizeof(hint));
371427 hint.ai_family = PF_UNSPEC;
372428 hint.ai_socktype = SOCK_STREAM;
373429
374- res = getaddrinfo(host, serv, &hint, &addr);
430+ res = getaddrinfo(host, serv, &hint, out);
375431 if (res) {
376432 fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
377433 if (res == EAI_SERVICE)
378434 fprintf(stderr, "(Check you have specified all ports)\n");
379- exit(1);
435+ exit(4);
380436 }
381-
382- memcpy(sock, addr->ai_addr, sizeof(*sock));
383-
384- freeaddrinfo(addr);
385437 }
386438
387439 /* Log to syslog, and to stderr if foreground */
388440 void log_message(int type, char* msg, ...)
@@ -401,43 +453,42 @@
401453
402454 /* syslogs who connected to where */
403455 void log_connection(struct connection *cnx)
404456 {
405- struct sockaddr_storage peeraddr; /* Who's connecting to sshd */
406- struct sockaddr_storage listenaddr; /* Where is it connecting to */
407- struct sockaddr_storage forwardfromaddr; /* Where is it forwarded from */
408- struct sockaddr_storage targetaddr; /* Where is it forwarded to */
409- socklen_t size = sizeof(peeraddr);
457+ struct addrinfo addr;
458+ struct sockaddr_storage ss;
410459 #define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
411- char buf[MAX_NAMELENGTH], buf2[MAX_NAMELENGTH], buf3[MAX_NAMELENGTH], buf4[MAX_NAMELENGTH];
460+ char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
461+ local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
412462 int res;
413463
414- memset(&peeraddr, 0, sizeof(peeraddr));
415- memset(&listenaddr, 0, sizeof(listenaddr));
416- memset(&forwardfromaddr, 0, sizeof(forwardfromaddr));
417- memset(&targetaddr, 0, sizeof(targetaddr));
464+ addr.ai_addr = (struct sockaddr*)&ss;
465+ addr.ai_addrlen = sizeof(ss);
418466
419- res = getpeername(cnx->q[0].fd, (struct sockaddr*)&peeraddr, &size);
467+ res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
420468 if (res == -1) return; /* that should never happen, right? */
469+ sprintaddr(peer, sizeof(peer), &addr);
421470
422- size = sizeof(listenaddr);
423- res = getsockname(cnx->q[0].fd, (struct sockaddr*)&listenaddr, &size);
471+ addr.ai_addrlen = sizeof(ss);
472+ res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
424473 if (res == -1) return;
474+ sprintaddr(service, sizeof(service), &addr);
425475
426- size = sizeof(targetaddr);
427- res = getpeername(cnx->q[1].fd, (struct sockaddr*)&targetaddr, &size);
476+ addr.ai_addrlen = sizeof(ss);
477+ res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
428478 if (res == -1) return;
479+ sprintaddr(target, sizeof(target), &addr);
429480
430- size = sizeof(forwardfromaddr);
431- res = getsockname(cnx->q[1].fd, (struct sockaddr*)&forwardfromaddr, &size);
481+ addr.ai_addrlen = sizeof(ss);
482+ res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
432483 if (res == -1) return;
484+ sprintaddr(local, sizeof(local), &addr);
433485
434- log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n",
435- sprintaddr(buf, sizeof(buf), &peeraddr),
436- sprintaddr(buf2, sizeof(buf2), &listenaddr),
437- sprintaddr(buf3, sizeof(buf3), &forwardfromaddr),
438- sprintaddr(buf4, sizeof(buf4), &targetaddr));
439-
486+ log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n",
487+ peer,
488+ service,
489+ local,
490+ target);
440491 }
441492
442493
443494 /* libwrap (tcpd): check the connection is legal. This is necessary because
@@ -497,8 +548,16 @@
497548 action.sa_handler = NULL;
498549 action.sa_flags = SA_NOCLDWAIT;
499550 res = sigaction(SIGCHLD, &action, NULL);
500551 CHECK_RES_DIE(res, "sigaction");
552+
553+
554+ /* Set SIGTERM to exit. For some reason if it's not set explicitely,
555+ * coverage information is lost when killing the process */
556+ memset(&action, 0, sizeof(action));
557+ action.sa_handler = exit;
558+ res = sigaction(SIGTERM, &action, NULL);
559+ CHECK_RES_DIE(res, "sigaction");
501560 }
502561
503562 /* Open syslog connection with appropriate banner;
504563 * banner is made up of basename(bin_name)+"[pid]" */
@@ -520,9 +579,9 @@
520579 int res;
521580 struct passwd *pw = getpwnam(user_name);
522581 if (!pw) {
523582 fprintf(stderr, "%s: not found\n", user_name);
524- exit(1);
583+ exit(2);
525584 }
526585 if (verbose)
527586 fprintf(stderr, "turning into %s\n", user_name);
528587
@@ -539,31 +598,34 @@
539598
540599 f = fopen(pidfile, "w");
541600 if (!f) {
542601 perror(pidfile);
543- exit(1);
602+ exit(3);
544603 }
545604
546605 fprintf(f, "%d\n", getpid());
547606 fclose(f);
548607 }
549608
550609 void printsettings(void)
551610 {
552- char buf[64];
611+ char buf[NI_MAXHOST];
612+ struct addrinfo *a;
553613 int i;
554614
555615 for (i = 0; i < ARRAY_SIZE(protocols); i++) {
556616 if (protocols[i].affected)
557617 fprintf(stderr,
558- "%s addr: %s. libwrap service: %s\n",
618+ "%s addr: %s. libwrap service: %s family %d %d\n",
559619 protocols[i].description,
560620 sprintaddr(buf, sizeof(buf), &protocols[i].saddr),
561- protocols[i].service);
621+ protocols[i].service,
622+ protocols[i].saddr.ai_family,
623+ protocols[i].saddr.ai_addr->sa_family);
562624 }
563625 fprintf(stderr, "listening on:\n");
564- for (i = 0; i < num_addr_listen; i++) {
565- fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), &addr_listen[i]));
626+ for (a = addr_listen; a; a = a->ai_next) {
627+ fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a));
566628 }
567629 fprintf(stderr, "timeout to ssh: %d\n", probing_timeout);
568630 }
569631
@@ -573,9 +635,8 @@
573635 * n_opts: number of options in *options before calling (i.e. where to append)
574636 * prot: array of protocols
575637 * n_prots: number of protocols in *prot
576638 * */
577-#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
578639 void append_protocols(struct option *options, int n_opts, struct proto *prot, int n_prots)
579640 {
580641 int o, p;
581642
@@ -586,127 +647,4 @@
586647 options[o].val = p + PROT_SHIFT;
587648 }
588649 }
589650
590-void parse_cmdline(int argc, char* argv[])
591-{
592- int c, affected = 0;
593- struct option const_options[] = {
594- { "inetd", no_argument, &inetd, 1 },
595- { "foreground", no_argument, &foreground, 1 },
596- { "verbose", no_argument, &verbose, 1 },
597- { "numeric", no_argument, &numeric, 1 },
598- { "user", required_argument, 0, 'u' },
599- { "pidfile", required_argument, 0, 'P' },
600- { "timeout", required_argument, 0, 't' },
601- { "listen", required_argument, 0, 'p' },
602- };
603- struct option all_options[ARRAY_SIZE(const_options) + ARRAY_SIZE(protocols) + 1];
604-
605- memset(all_options, 0, sizeof(all_options));
606- memcpy(all_options, const_options, sizeof(const_options));
607- append_protocols(all_options, ARRAY_SIZE(const_options), protocols, ARRAY_SIZE(protocols));
608-
609- while ((c = getopt_long_only(argc, argv, "t:l:s:o:T:p:VP:", all_options, NULL)) != -1) {
610- if (c == 0) continue;
611-
612- if (c >= PROT_SHIFT) {
613- affected++;
614- protocols[c - PROT_SHIFT].affected = 1;
615- resolve_name(&protocols[c - PROT_SHIFT].saddr, optarg);
616- continue;
617- }
618-
619- switch (c) {
620-
621- case 't':
622- probing_timeout = atoi(optarg);
623- break;
624-
625- case 'p':
626- num_addr_listen++;
627- addr_listen = realloc(addr_listen, num_addr_listen * sizeof(addr_listen[0]));
628- resolve_name(&addr_listen[num_addr_listen - 1], optarg);
629- break;
630-
631- case 'V':
632- printf("%s %s\n", server_type, VERSION);
633- exit(0);
634-
635- case 'u':
636- user_name = optarg;
637- break;
638-
639- case 'P':
640- pid_file = optarg;
641- break;
642-
643- default:
644- fprintf(stderr, USAGE_STRING);
645- exit(2);
646- }
647- }
648-
649- if (!affected) {
650- fprintf(stderr, "At least one target protocol must be specified.\n");
651- exit(2);
652- }
653-
654- if (!num_addr_listen) {
655- fprintf(stderr, "No listening address specified; use at least one -p option\n");
656- exit(1);
657- }
658-
659-}
660-
661-int main(int argc, char *argv[])
662-{
663-
664- extern char *optarg;
665- extern int optind;
666- int res;
667-
668- int *listen_sockets;
669-
670- /* Init defaults */
671- pid_file = "/var/run/sslh.pid";
672- user_name = "nobody";
673- foreground = 0;
674-
675- parse_cmdline(argc, argv);
676-
677- if (inetd)
678- {
679- verbose = 0;
680- start_shoveler(0);
681- exit(0);
682- }
683-
684- if (verbose)
685- printsettings();
686-
687- listen_sockets = malloc(num_addr_listen * sizeof(*listen_sockets));
688- start_listen_sockets(listen_sockets, addr_listen, num_addr_listen);
689- free(addr_listen);
690-
691- if (!foreground)
692- if (fork() > 0) exit(0); /* Detach */
693-
694- setup_signals();
695-
696- write_pid_file(pid_file);
697-
698- drop_privileges(user_name);
699-
700- /* New session -- become group leader */
701- if (getuid() == 0) {
702- res = setsid();
703- CHECK_RES_DIE(res, "setsid: already process leader");
704- }
705-
706- /* Open syslog connection */
707- setup_syslog(argv[0]);
708-
709- main_loop(listen_sockets, num_addr_listen);
710-
711- return 0;
712-}
common.hView
@@ -15,8 +15,9 @@
1515 #include <pwd.h>
1616 #include <syslog.h>
1717 #include <libgen.h>
1818 #include <time.h>
19+#include <getopt.h>
1920
2021 #ifndef VERSION
2122 #define VERSION "v?"
2223 #endif
@@ -45,30 +46,26 @@
4546 ST_PROBING=1, /* Waiting for timeout to find where to forward */
4647 ST_SHOVELING /* Connexion is established */
4748 };
4849
50+typedef int T_PROTO_ID; /* Index into protocols[] array */
4951
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_TINC,
56- PROT_SSL,
57-} T_PROTO_ID;
58-
5952 /* For each protocol we need: */
6053 struct proto {
6154 int affected; /* are we actually using it? */
6255 char* description; /* a string that says what it is (for logging and command-line parsing) */
6356 char* service; /* service name to do libwrap checks */
64- struct sockaddr_storage saddr; /* where to switch that protocol */
57+ struct addrinfo saddr; /* where to switch that protocol */
6558 int (*probe)(const char*, int); /* function to probe that protocol */
6659 };
6760
6861 /* A table in common.c contains all the known protocols */
6962 extern struct proto protocols[];
63+extern int num_known_protocols;
7064
65+/* this is used to pass protocols through the command-line parameter parsing */
66+#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
67+
7168 /* A 'queue' is composed of a file descriptor (which can be read from or
7269 * written to), and a queue for defered write data */
7370 struct queue {
7471 int fd;
@@ -93,12 +90,12 @@
9390
9491
9592 /* common.c */
9693 void init_cnx(struct connection *cnx);
97-void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr);
94+int connect_addr(struct addrinfo *addr, char* cnx_name);
9895 int fd2fd(struct queue *target, struct queue *from);
99-char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s);
100-void resolve_name(struct sockaddr_storage *sock, char* fullname) ;
96+char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
97+void resolve_name(struct addrinfo **out, char* fullname);
10198 T_PROTO_ID probe_client_protocol(struct connection *cnx);
10299 void log_connection(struct connection *cnx);
103100 int check_access_rights(int in_socket, char* service);
104101 void setup_signals(void);
@@ -109,20 +106,21 @@
109106 void parse_cmdline(int argc, char* argv[]);
110107 void log_message(int type, char* msg, ...);
111108 void dump_connection(struct connection *cnx);
112109
110+void append_protocols(struct option *options, int n_opts, struct proto *prot, int n_prots);
111+int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
113112
114113 int defer_write(struct queue *q, void* data, int data_size);
115114 int flush_defered(struct queue *q);
116115
117-extern int probing_timeout, verbose, inetd;
118-extern struct sockaddr_storage *addr_listen, addr_ssl, addr_ssh, addr_openvpn;
116+extern int probing_timeout, verbose, inetd, foreground, numeric;
117+extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
118+extern struct addrinfo *addr_listen;
119119 extern const char* USAGE_STRING;
120120 extern char* user_name, *pid_file;
121121 extern const char* server_type;
122122
123123 /* sslh-fork.c */
124124 void start_shoveler(int);
125125
126126 void main_loop(int *listen_sockets, int num_addr_listen);
127-
128-
sslh-fork.cView
@@ -1,8 +1,8 @@
11 /*
22 Reimplementation of sslh in C
33
4-# Copyright (C) 2007-2008 Yves Rutschle
4+# Copyright (C) 2007-2011 Yves Rutschle
55 #
66 # This program is free software; you can redistribute it
77 # and/or modify it under the terms of the GNU General Public
88 # License as published by the Free Software Foundation; either
@@ -67,12 +67,11 @@
6767 void start_shoveler(int in_socket)
6868 {
6969 fd_set fds;
7070 struct timeval tv;
71- struct sockaddr_storage *saddr;
71+ struct addrinfo *saddr;
7272 int res;
7373 int out_socket;
74- char *target;
7574 struct connection cnx;
7675 T_PROTO_ID prot;
7776
7877 init_cnx(&cnx);
@@ -91,24 +90,20 @@
9190 /* Received data: figure out what protocol it is */
9291 prot = probe_client_protocol(&cnx);
9392 } else {
9493 /* Timed out: it's necessarily SSH */
95- prot = PROT_SSH;
94+ prot = 0;
9695 }
9796
9897 saddr = &protocols[prot].saddr;
99- target = protocols[prot].description;
10098 if (protocols[prot].service &&
10199 check_access_rights(in_socket, protocols[prot].service)) {
102100 exit(0);
103101 }
104102
105103 /* Connect the target socket */
106- out_socket = socket(saddr->ss_family, SOCK_STREAM, 0);
107- res = connect(out_socket, (struct sockaddr*)saddr, sizeof(addr_ssl));
108- CHECK_RES_DIE(res, "connect");
109- if (verbose)
110- fprintf(stderr, "connected to something\n");
104+ out_socket = connect_addr(saddr, protocols[prot].description);
105+ CHECK_RES_DIE(out_socket, "connect");
111106
112107 cnx.q[1].fd = out_socket;
113108
114109 log_connection(&cnx);
@@ -125,14 +120,33 @@
125120
126121 exit(0);
127122 }
128123
129-void main_loop(int *listen_sockets, int num_addr_listen)
124+static int *listener_pid;
125+static int listener_pid_number = 0;
126+
127+void stop_listeners(int sig)
130128 {
131- int in_socket, i;
129+ int i;
132130
131+ for (i = 0; i < listener_pid_number; i++) {
132+ kill(listener_pid[i], sig);
133+ }
134+}
135+
136+void main_loop(int listen_sockets[], int num_addr_listen)
137+{
138+ int in_socket, i, res;
139+ struct sigaction action;
140+
141+ listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
142+
143+ /* Start one process for each listening address */
133144 for (i = 0; i < num_addr_listen; i++) {
134- if (!fork()) {
145+ if (!(listener_pid[i] = fork())) {
146+
147+ /* Listening process just accepts a connection, forks, and goes
148+ * back to listening */
135149 while (1)
136150 {
137151 in_socket = accept(listen_sockets[i], 0, 0);
138152 if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
@@ -146,8 +160,19 @@
146160 close(in_socket);
147161 }
148162 }
149163 }
164+
165+ /* Set SIGTERM to "stop_listeners" which further kills all listener
166+ * processes. Note this won't kill processes that listeners forked, which
167+ * means active connections remain active. */
168+ memset(&action, 0, sizeof(action));
169+ action.sa_handler = stop_listeners;
170+ res = sigaction(SIGTERM, &action, NULL);
171+ CHECK_RES_DIE(res, "sigaction");
172+
173+ listener_pid_number = num_addr_listen;
174+ wait(NULL);
150175 }
151176
152177 /* The actual main is in common.c: it's the same for both version of
153178 * the server
sslh-select.cView
@@ -29,9 +29,10 @@
2929 /* cnx_num_alloc is the number of connection to allocate at once (at start-up,
3030 * and then every time we get too many simultaneous connections: e.g. start
3131 * with 100 slots, then if we get more than 100 connections allocate another
3232 * 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.
33+ * allocate as many structures at once as will fit in one page (which is 102
34+ * in sslh 1.9 on Linux on x86)
3435 */
3536 static long cnx_num_alloc;
3637
3738 /* Make the file descriptor non-block */
@@ -54,16 +55,18 @@
5455 {
5556 int i;
5657
5758 for (i = 0; i < 2; i++) {
58- if (verbose)
59- fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
59+ if (cnx->q[i].fd != -1) {
60+ if (verbose)
61+ fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
6062
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);
63+ close(cnx->q[i].fd);
64+ FD_CLR(cnx->q[i].fd, fds);
65+ FD_CLR(cnx->q[i].fd, fds2);
66+ if (cnx->q[i].defered_data)
67+ free(cnx->q[i].defered_data);
68+ }
6669 }
6770 init_cnx(cnx);
6871 return 0;
6972 }
@@ -109,32 +112,30 @@
109112
110113 return in_socket;
111114 }
112115
116+
113117 /* Connect queue 1 of connection to SSL; returns new file descriptor */
114-int connect_queue(struct connection *cnx, struct sockaddr_storage *addr,
118+int connect_queue(struct connection *cnx, struct addrinfo *addr,
115119 char* cnx_name,
116120 fd_set *fds_r, fd_set *fds_w)
117121 {
118122 struct queue *q = &cnx->q[1];
119- int res;
120123
121- q->fd = socket(addr->ss_family, SOCK_STREAM, 0);
122- res = connect(q->fd, (struct sockaddr*)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 {
124+ q->fd = connect_addr(addr, cnx_name);
125+ if (q->fd != -1) {
126+ log_connection(cnx);
129127 set_nonblock(q->fd);
130128 flush_defered(q);
131129 if (q->defered_data) {
132130 FD_SET(q->fd, fds_w);
133131 } else {
134132 FD_SET(q->fd, fds_r);
135133 }
136134 return q->fd;
135+ } else {
136+ tidy_connection(cnx, fds_r, fds_w);
137+ return -1;
137138 }
138139 }
139140
140141 /* shovels data from active fd to the other
@@ -187,9 +188,9 @@
187188 *
188189 * That way, each pair of file descriptor (read from one, write to the other)
189190 * is monitored either for read or for write, but never for both.
190191 */
191-void main_loop(int *listen_sockets, int num_addr_listen)
192+void main_loop(int listen_sockets[], int num_addr_listen)
192193 {
193194 fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
194195 fd_set readfds, writefds; /* working read and write fd sets */
195196 struct timeval tv;
@@ -291,9 +292,9 @@
291292
292293 /* If timed out it's SSH, otherwise the client sent
293294 * data so probe the protocol */
294295 if ((cnx[i].probe_timeout < time(NULL))) {
295- prot = PROT_SSH;
296+ prot = 0;
296297 } else {
297298 prot = probe_client_protocol(&cnx[i]);
298299 }
299300
sslh.podView
@@ -5,18 +5,19 @@
55 sslh - ssl/ssh multiplexer
66
77 =head1 SYNOPSIS
88
9-sslh [ B<-t> I<num> ] [B<-p> I<listening address> [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] [-n]
9+sslh [ B<-t> I<num> ] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--ssl> I<target address for SSL>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<-u> I<username>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
1010
1111 =head1 DESCRIPTION
1212
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.
13+B<sslh> accepts HTTPS, SSH, OpenVPN, tinc and XMPP connections
14+on the same port. This makes it possible to connect to any
15+of these servers on port 443 (e.g. from inside a corporate
16+firewall, which almost never block port 443) while still
17+serving HTTPS on that port.
1818
19+
1920 The idea is to have B<sslh> listen to the external 443 port,
2021 accept the incoming connections, work out what type of
2122 connection it is, and then fordward to the appropriate
2223 server.
@@ -26,9 +27,11 @@
2627 The protocol detection is made based on the first bytes sent
2728 by the client: SSH connections start by identifying each
2829 other's versions using clear text "SSH-2.0" strings (or
2930 equivalent version strings). This is defined in RFC4253,
30-4.2. Meanwhile, OpenVPN clients start with 0x00 0x0D 0x38.
31+4.2. Meanwhile, OpenVPN clients start with 0x00 0x0D 0x38,
32+tinc clients start with "0 ", and XMPP client start with a
33+packet containing "jabber".
3134
3235 Additionally, two kind of SSH clients exist: the client
3336 waits for the server to send its version string ("Shy"
3437 client, which is the case of OpenSSH and Putty), or the
@@ -89,8 +92,13 @@
8992
9093 Interface and port on which to forward OpenVPN connections,
9194 typically I<localhost:1194>.
9295
96+=item B<--xmpp> I<target address>
97+
98+Interface and port on which to forward XMPP connections,
99+typically I<localhost:5222>.
100+
93101 =item B<--tinc> I<target address>
94102
95103 Interface and port on which to forward tinc connections,
96104 typically I<localhost:655>.
tView
@@ -1,229 +1,302 @@
11 #! /usr/bin/perl -w
22
33 # Test script for sslh
44
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-
105 use strict;
11-use IO::Socket::INET;
6+use IO::Socket::INET6;
127 use Test::More qw/no_plan/;
138
149 # We use ports 9000, 9001 and 9002 -- hope that won't clash
1510 # with anything...
16-my $ssh_port = 9000;
17-my $ssl_port = 9001;
11+my $ssh_address = "ip6-localhost:9000";
12+my $ssl_address = "ip6-localhost:9001";
1813 my $sslh_port = 9002;
19-my $pidfile = "/tmp/sslh.pid";
14+my $no_listen = 9003; # Port on which no-one listens
15+my $pidfile = "/tmp/sslh_test.pid";
2016
21-# How many connections will be created during the last test
22-my $NUM_SSL_CNX = 20;
23-my $NUM_SSH_CNX = 20;
24-
2517 # Which tests do we run
2618 my $SSL_CNX = 1;
2719 my $SSH_SHY_CNX = 1;
2820 my $SSH_BOLD_CNX = 1;
2921 my $SSL_MIX_SSH = 1;
3022 my $SSH_MIX_SSL = 1;
3123 my $BIG_MSG = 1;
32-my $MANY_CNX = 1;
24+my $STALL_CNX = 1;
3325
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;
26+# Robustness tests. These are mostly to achieve full test
27+# coverage, but do not necessarily result in an actual test
28+# (e.g. some tests need to be run with valgrind to check all
29+# memory management code).
30+my $RB_CNX_NOSERVER = 1;
31+my $RB_PARAM_NOHOST = 1;
32+my $RB_WRONG_USERNAME = 1;
33+my $RB_OPEN_PID_FILE = 1;
34+my $RB_BIND_ADDRESS = 1;
35+my $RB_RESOLVE_ADDRESS = 1;
3936
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;
37+`lcov --directory . --zerocounters`;
4238
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;
39+
40+my ($ssh_pid, $ssl_pid);
41+
42+if (!($ssh_pid = fork)) {
43+ exec "./echosrv --listen $ssh_address --prefix 'ssh: '";
5144 }
52-warn "spawned $sslh_pid\n";
53-sleep 1;
5445
46+if (!($ssl_pid = fork)) {
47+ exec "./echosrv --listen $ssl_address --prefix 'ssl: '";
48+}
5549
56-my $test_data = "hello world\n";
50+my @binaries = ('sslh-select', 'sslh-fork');
51+for my $binary (@binaries) {
52+ warn "Testing $binary\n";
5753
54+# Start sslh with the right plumbing
55+ my $sslh_pid;
56+ if (!($sslh_pid = fork)) {
57+ my $user = (getpwuid $<)[0]; # Run under current username
58+ exec "./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
59+ #exec "valgrind --leak-check=full ./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
60+ exit 0;
61+ }
62+ warn "spawned $sslh_pid\n";
63+ sleep 1; # valgrind can be heavy -- wait 5 seconds
64+
65+
66+ my $test_data = "hello world\n";
67+
5868 # 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");
69+ if ($SSL_CNX) {
70+ print "***Test: SSL connection\n";
71+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
72+ warn "$!\n" unless $cnx_l;
73+ if (defined $cnx_l) {
74+ print $cnx_l $test_data;
75+ my $data = <$cnx_l>;
76+ is($data, "ssl: $test_data", "SSL connection");
77+ }
6878 }
69-}
7079
7180 # 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");
81+ if ($SSH_SHY_CNX) {
82+ print "***Test: Shy SSH connection\n";
83+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
84+ warn "$!\n" unless $cnx_h;
85+ if (defined $cnx_h) {
86+ sleep 3;
87+ print $cnx_h $test_data;
88+ my $data = <$cnx_h>;
89+ is($data, "ssh: $test_data", "Shy SSH connection");
90+ }
8291 }
83-}
8492
8593 # 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");
94+ if ($SSH_BOLD_CNX) {
95+ print "***Test: Bold SSH connection\n";
96+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
97+ warn "$!\n" unless $cnx_h;
98+ if (defined $cnx_h) {
99+ my $td = "SSH-2.0 testsuite\t$test_data";
100+ print $cnx_h $td;
101+ my $data = <$cnx_h>;
102+ is($data, "ssh: $td", "Bold SSH connection");
103+ }
97104 }
98-}
99105
100106 # 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");
107+ if ($SSL_MIX_SSH) {
108+ print "***Test: One SSL half-started then one SSH\n";
109+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
110+ warn "$!\n" unless $cnx_l;
111+ if (defined $cnx_l) {
112+ print $cnx_l $test_data;
113+ my $cnx_h= new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
114+ warn "$!\n" unless $cnx_h;
115+ if (defined $cnx_h) {
116+ sleep 3;
117+ print $cnx_h $test_data;
118+ my $data_h = <$cnx_h>;
119+ is($data_h, "ssh: $test_data", "SSH during SSL being established");
120+ }
121+ my $data = <$cnx_l>;
122+ is($data, "ssl: $test_data", "SSL connection interrupted by SSH");
123+ }
124+ }
125+
126+# Test: One SSH half-started then one SSL
127+ if ($SSH_MIX_SSL) {
128+ print "***Test: One SSH half-started then one SSL\n";
129+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
108130 warn "$!\n" unless $cnx_h;
109131 if (defined $cnx_h) {
110132 sleep 3;
111- my $ssh_data = $ssh_listen->accept;
133+ my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
134+ warn "$!\n" unless $cnx_l;
135+ if (defined $cnx_l) {
136+ print $cnx_l $test_data;
137+ my $data = <$cnx_l>;
138+ is($data, "ssl: $test_data", "SSL during SSH being established");
139+ }
112140 print $cnx_h $test_data;
113- my $data_h = <$ssh_data>;
114- is($data_h, $test_data, "SSH during SSL being established");
141+ my $data = <$cnx_h>;
142+ is($data, "ssh: $test_data", "SSH connection interrupted by SSL");
115143 }
116- my $ssl_data = $ssl_listen->accept;
117- my $data = <$ssl_data>;
118- is($data, $test_data, "SSL connection interrupted by SSH");
119144 }
120-}
121145
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;
146+
147+# Test: Big messages (careful: don't go over echosrv's buffer limit (1M))
148+ if ($BIG_MSG) {
149+ print "***Test: big message\n";
129150 my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
130151 warn "$!\n" unless $cnx_l;
152+ my $test_data2 = "helloworld";
153+ my $rept = 10000;
131154 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");
155+ print $cnx_l ($test_data2 x $rept);
156+ print $cnx_l "\n";
157+ my $data = <$cnx_l>;
158+ is($data, "ssl: ". ($test_data2 x $rept) . "\n", "Big message");
136159 }
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");
141160 }
142-}
143161
162+# Test: Stalled connection
163+# Create two connections, stall one, check the other one
164+# works, unstall first and check it works fine
165+ if ($STALL_CNX) {
166+ print "***Test: Stalled connection\n";
167+ my $cnx_1 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
168+ warn "$!\n" unless defined $cnx_1;
169+ my $cnx_2 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
170+ warn "$!\n" unless defined $cnx_2;
171+ my $test_data2 = "helloworld";
172+ my $rept = 10000;
173+ if (defined $cnx_1 and defined $cnx_2) {
174+ print $cnx_1 ($test_data2 x $rept);
175+ print $cnx_1 "\n";
176+ print $cnx_2 ($test_data2 x $rept);
177+ print $cnx_2 "\n";
178+ my $data = <$cnx_2>;
179+ is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (1)");
180+ print $cnx_2 ($test_data2 x $rept);
181+ print $cnx_2 "\n";
182+ $data = <$cnx_2>;
183+ is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
184+ $data = <$cnx_1>;
185+ is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
144186
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");
187+ }
158188 }
189+
190+ my $pid = `cat $pidfile`;
191+ warn "killing $pid\n";
192+ kill TERM => $pid or warn "kill process: $!\n";
193+ sleep 1;
159194 }
160195
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- }
196+# Robustness: Connecting to non-existant server
197+if ($RB_CNX_NOSERVER) {
198+ print "***Test: Connecting to non-existant server\n";
199+ my $sslh_pid;
200+ if (!($sslh_pid = fork)) {
201+ my $user = (getpwuid $<)[0]; # Run under current username
202+ exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --ssl localhost:$no_listen -P $pidfile";
174203 }
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- }
204+ warn "spawned $sslh_pid\n";
205+
206+ sleep 1;
207+
208+ my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
209+ warn "$!\n" unless $cnx_h;
210+ if (defined $cnx_h) {
211+ sleep 1;
212+ my $test_data = "hello";
213+ print $cnx_h $test_data;
184214 }
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- }
215+ # Ideally we should check a log is emitted.
191216
192-# Make up a random order so we don't always hit the
193-# connexions in the same order
217+ kill TERM => `cat $pidfile` or warn "kill: $!\n";
218+ sleep 1;
219+}
194220
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- }
221+
222+# Robustness: No hostname in address
223+if ($RB_PARAM_NOHOST) {
224+ print "***Test: No hostname in address\n";
225+ my $sslh_pid;
226+ if (!($sslh_pid = fork)) {
227+ my $user = (getpwuid $<)[0]; # Run under current username
228+ exec "./sslh-select -v -f -u $user --listen $sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
208229 }
230+ warn "spawned $sslh_pid\n";
231+ waitpid $sslh_pid, 0;
232+ my $code = $? >> 8;
233+ warn "exited with $code\n";
234+ is($code, 1, "Exit status on illegal option");
235+}
209236
210- my @cnx = (@cnx_l, @cnx_l);
211- my @rcv = (@ssl_data, @ssl_data);
237+# Robustness: User does not exist
238+if ($RB_WRONG_USERNAME) {
239+ print "***Test: Changing to non-existant username\n";
240+ my $sslh_pid;
241+ if (!($sslh_pid = fork)) {
242+ my $user = (getpwuid $<)[0]; # Run under current username
243+ exec "./sslh-select -v -f -u ${user}_doesnt_exist --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
244+ }
245+ warn "spawned $sslh_pid\n";
246+ waitpid $sslh_pid, 0;
247+ my $code = $? >> 8;
248+ warn "exited with $code\n";
249+ is($code, 2, "Exit status on non-existant username");
250+}
212251
213- fisher_yates_shuffle(\@rcv, \@cnx);
252+# Robustness: Can't open PID file
253+if ($RB_OPEN_PID_FILE) {
254+ print "***Test: Can't open PID file\n";
255+ my $sslh_pid;
256+ if (!($sslh_pid = fork)) {
257+ my $user = (getpwuid $<)[0]; # Run under current username
258+ exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P /dont_exist/$pidfile";
259+ # You don't have a /dont_exist/ directory, do you?!
260+ }
261+ warn "spawned $sslh_pid\n";
262+ waitpid $sslh_pid, 0;
263+ my $code = $? >> 8;
264+ warn "exited with $code\n";
265+ is($code, 3, "Exit status if can't open PID file");
266+}
214267
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- }
268+# Robustness: Can't bind address
269+if ($RB_BIND_ADDRESS) {
270+ print "***Test: Can't bind address\n";
271+ my $sslh_pid;
272+ if (!($sslh_pid = fork)) {
273+ my $user = (getpwuid $<)[0]; # Run under current username
274+ exec "./sslh-select -v -f -u $user --listen 74.125.39.106:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
224275 }
276+ warn "spawned $sslh_pid\n";
277+ waitpid $sslh_pid, 0;
278+ my $code = $? >> 8;
279+ warn "exited with $code\n";
280+ is($code, 1, "Exit status if can't bind address");
225281 }
226282
283+# Robustness: Can't resolve address
284+if ($RB_RESOLVE_ADDRESS) {
285+ print "***Test: Can't resolve address\n";
286+ my $sslh_pid;
287+ if (!($sslh_pid = fork)) {
288+ my $user = (getpwuid $<)[0]; # Run under current username
289+ exec "./sslh-select -v -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
290+ }
291+ warn "spawned $sslh_pid\n";
292+ waitpid $sslh_pid, 0;
293+ my $code = $? >> 8;
294+ warn "exited with $code\n";
295+ is($code, 4, "Exit status if can't resolve address");
296+}
227297
298+`lcov --directory . --capture --output-file sslh_cov.info`;
299+`genhtml sslh_cov.info`;
228300
229-kill 15, `cat $pidfile` or warn "kill: $!\n";
301+`killall echosrv`;
302+
echosrv.cView
@@ -1,0 +1,166 @@
1+/* echosrv: a simple line echo server with optional prefix adding.
2+ *
3+ * echsrv --listen localhost6:1234 --prefix "ssl: "
4+ *
5+ * This will bind to 1234, and echo every line pre-pending "ssl: ". This is
6+ * used for testing: we create several such servers with different prefixes,
7+ * then we connect test clients that can then check they get the proper data
8+ * back (thus testing that shoveling works both ways) with the correct prefix
9+ * (thus testing it connected to the expected service).
10+ * **/
11+
12+#define _GNU_SOURCE
13+#include <sys/types.h>
14+#include <fcntl.h>
15+#include <string.h>
16+#include <unistd.h>
17+#include <stdlib.h>
18+#include <stdarg.h>
19+#include <stdio.h>
20+#include <signal.h>
21+#include <sys/socket.h>
22+#include <sys/wait.h>
23+#include <netinet/in.h>
24+#include <arpa/inet.h>
25+#include <netdb.h>
26+#include <pwd.h>
27+#include <syslog.h>
28+#include <libgen.h>
29+#include <getopt.h>
30+
31+#include "common.h"
32+
33+/* Added to make the code compilable under CYGWIN
34+ * */
35+#ifndef SA_NOCLDWAIT
36+#define SA_NOCLDWAIT 0
37+#endif
38+
39+const char* USAGE_STRING =
40+"echosrv\n" \
41+"usage:\n" \
42+"\techosrv [-v] --listen <address:port> [--prefix <prefix>]\n"
43+"-v: verbose\n" \
44+"--listen: address to listen on. Can be specified multiple times.\n" \
45+"--prefix: add specified prefix before every line echoed.\n"
46+"";
47+
48+const char* server_type = "echsrv"; /* keep setup_syslog happy */
49+
50+/*
51+ * Settings that depend on the command line.
52+ */
53+char* prefix = "";
54+int port;
55+
56+void parse_cmdline(int argc, char* argv[])
57+{
58+ int c;
59+ struct option options[] = {
60+ { "verbose", no_argument, &verbose, 1 },
61+ { "numeric", no_argument, &numeric, 1 },
62+ { "listen", required_argument, 0, 'l' },
63+ { "prefix", required_argument, 0, 'p' },
64+ };
65+ struct addrinfo **a;
66+
67+ while ((c = getopt_long_only(argc, argv, "l:p:", options, NULL)) != -1) {
68+ if (c == 0) continue;
69+
70+ switch (c) {
71+
72+ case 'l':
73+ /* find the end of the listen list */
74+ for (a = &addr_listen; *a; a = &((*a)->ai_next));
75+ /* append the specified addresses */
76+ resolve_name(a, optarg);
77+ break;
78+
79+ case 'p':
80+ prefix = optarg;
81+ break;
82+
83+ default:
84+ fprintf(stderr, "%s", USAGE_STRING);
85+ exit(2);
86+ }
87+ }
88+
89+ if (!addr_listen) {
90+ fprintf(stderr, "No listening port specified\n");
91+ exit(1);
92+ }
93+}
94+
95+void start_echo(int fd)
96+{
97+ fd_set fds;
98+ int res;
99+ char buffer[1 << 20];
100+ char* ret;
101+ FILE *socket;
102+
103+ FD_ZERO(&fds);
104+
105+ socket = fdopen(fd, "r+");
106+ if (!socket) {
107+ CHECK_RES_DIE(-1, "fdopen");
108+ }
109+
110+ while (1) {
111+ ret = fgets(buffer, sizeof(buffer), socket);
112+ if (!ret) {
113+ fprintf(stderr, "%s", strerror(ferror(socket)));
114+ return;
115+ }
116+ res = fprintf(socket, "%s%s", prefix, buffer);
117+ if (res < 0) {
118+ fprintf(stderr, "%s", strerror(ferror(socket)));
119+ return;
120+ }
121+
122+ fflush(socket);
123+ }
124+}
125+
126+void main_loop(int listen_sockets[], int num_addr_listen)
127+{
128+ int in_socket, i;
129+
130+ for (i = 0; i < num_addr_listen; i++) {
131+ if (!fork()) {
132+ while (1)
133+ {
134+ in_socket = accept(listen_sockets[i], 0, 0);
135+ if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
136+
137+ if (!fork())
138+ {
139+ close(listen_sockets[i]);
140+ start_echo(in_socket);
141+ exit(0);
142+ }
143+ close(in_socket);
144+ }
145+ }
146+ }
147+ wait(NULL);
148+}
149+
150+int main(int argc, char *argv[])
151+{
152+
153+ extern char *optarg;
154+ extern int optind;
155+ int num_addr_listen;
156+
157+ int *listen_sockets;
158+
159+ parse_cmdline(argc, argv);
160+
161+ num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
162+
163+ main_loop(listen_sockets, num_addr_listen);
164+
165+ return 0;
166+}
sslh-main.cView
@@ -1,0 +1,196 @@
1+/*
2+# main: processing of command line options and start the main loop.
3+#
4+# Copyright (C) 2007-2011 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 <stdarg.h>
30+#include <stdio.h>
31+#include <signal.h>
32+#include <sys/socket.h>
33+#include <sys/wait.h>
34+#include <netinet/in.h>
35+#include <arpa/inet.h>
36+#include <netdb.h>
37+#include <pwd.h>
38+#include <syslog.h>
39+#include <libgen.h>
40+#include <getopt.h>
41+
42+#include "common.h"
43+
44+const char* USAGE_STRING =
45+"sslh " VERSION "\n" \
46+"usage:\n" \
47+"\tsslh [-v] [-i] [-V] [-f] [-n]\n"
48+"\t[-t <timeout>] [-P <pidfile>] -u <username> -p <add> [-p <addr> ...] \n" \
49+"%s\n\n" \
50+"-v: verbose\n" \
51+"-V: version\n" \
52+"-f: foreground\n" \
53+"-n: numeric output\n" \
54+"-t: timeout before connecting to SSH.\n" \
55+"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
56+"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
57+"-P: PID file. Default: /var/run/sslh.pid.\n" \
58+"-i: Run as a inetd service.\n" \
59+"";
60+
61+void print_usage(void)
62+{
63+ int i;
64+ char *prots = "";
65+
66+ for (i = 0; i < num_known_protocols; i++)
67+ asprintf(&prots, "%s\t[--%s <addr>]\n", prots, protocols[i].description);
68+
69+ fprintf(stderr, USAGE_STRING, prots);
70+}
71+
72+void parse_cmdline(int argc, char* argv[])
73+{
74+ int c, affected = 0;
75+ struct option const_options[] = {
76+ { "inetd", no_argument, &inetd, 1 },
77+ { "foreground", no_argument, &foreground, 1 },
78+ { "verbose", no_argument, &verbose, 1 },
79+ { "numeric", no_argument, &numeric, 1 },
80+ { "user", required_argument, 0, 'u' },
81+ { "pidfile", required_argument, 0, 'P' },
82+ { "timeout", required_argument, 0, 't' },
83+ { "listen", required_argument, 0, 'p' },
84+ };
85+ struct option all_options[ARRAY_SIZE(const_options) + num_known_protocols + 1];
86+ struct addrinfo *addr, **a;
87+
88+ memset(all_options, 0, sizeof(all_options));
89+ memcpy(all_options, const_options, sizeof(const_options));
90+ append_protocols(all_options, ARRAY_SIZE(const_options), protocols, num_known_protocols);
91+
92+ while ((c = getopt_long_only(argc, argv, "t:T:p:VP:", all_options, NULL)) != -1) {
93+ if (c == 0) continue;
94+
95+ if (c >= PROT_SHIFT) {
96+ affected++;
97+ protocols[c - PROT_SHIFT].affected = 1;
98+ resolve_name(&addr, optarg);
99+ protocols[c - PROT_SHIFT].saddr= *addr;
100+ continue;
101+ }
102+
103+ switch (c) {
104+
105+ case 't':
106+ probing_timeout = atoi(optarg);
107+ break;
108+
109+ case 'p':
110+ /* find the end of the listen list */
111+ for (a = &addr_listen; *a; a = &((*a)->ai_next));
112+ /* append the specified addresses */
113+ resolve_name(a, optarg);
114+
115+ break;
116+
117+ case 'V':
118+ printf("%s %s\n", server_type, VERSION);
119+ exit(0);
120+
121+ case 'u':
122+ user_name = optarg;
123+ break;
124+
125+ case 'P':
126+ pid_file = optarg;
127+ break;
128+
129+ default:
130+ print_usage();
131+ exit(2);
132+ }
133+ }
134+
135+ if (!affected) {
136+ fprintf(stderr, "At least one target protocol must be specified.\n");
137+ exit(2);
138+ }
139+
140+ if (!addr_listen) {
141+ fprintf(stderr, "No listening address specified; use at least one -p option\n");
142+ exit(1);
143+ }
144+
145+}
146+
147+int main(int argc, char *argv[])
148+{
149+
150+ extern char *optarg;
151+ extern int optind;
152+ int res, num_addr_listen;
153+
154+ int *listen_sockets;
155+
156+ /* Init defaults */
157+ pid_file = "/var/run/sslh.pid";
158+ user_name = "nobody";
159+ foreground = 0;
160+
161+ parse_cmdline(argc, argv);
162+
163+ if (inetd)
164+ {
165+ verbose = 0;
166+ start_shoveler(0);
167+ exit(0);
168+ }
169+
170+ if (verbose)
171+ printsettings();
172+
173+ num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
174+
175+ if (!foreground)
176+ if (fork() > 0) exit(0); /* Detach */
177+
178+ setup_signals();
179+
180+ drop_privileges(user_name);
181+
182+ /* New session -- become group leader */
183+ if (getuid() == 0) {
184+ res = setsid();
185+ CHECK_RES_DIE(res, "setsid: already process leader");
186+ }
187+
188+ write_pid_file(pid_file);
189+
190+ /* Open syslog connection */
191+ setup_syslog(argv[0]);
192+
193+ main_loop(listen_sockets, num_addr_listen);
194+
195+ return 0;
196+}
t_loadView
@@ -1,0 +1,128 @@
1+#! /usr/bin/perl -w
2+
3+# Test script for sslh -- mass communication
4+
5+# This creates many clients that perform concurrent
6+# connections, disconnect at any time, and try to generally
7+# behave as badly as possible.
8+
9+# It can be used to test sslh behaves properly with many
10+# clients, however its main use is to get an idea of how
11+# much load it can take on your system before things start
12+# to go wrong.
13+
14+use strict;
15+use IO::Socket::INET6;
16+use Data::Dumper;
17+
18+## BEGIN TEST CONFIG
19+
20+# Do we test sslh-select or sslh-fork?
21+my $sslh_binary = "./sslh-select";
22+
23+# How many clients to we start for each protocol?
24+my $NUM_CNX = 30;
25+
26+# Delay between starting new processes when starting up. If
27+# you start 200 processes in under a second, things go wrong
28+# and it's not sslh's fault (typically the echosrv won't be
29+# forking fast enough).
30+my $start_time_delay = 0.5;
31+
32+# If you test 4 protocols, you'll start $NUM_CNX * 4 clients
33+# (e.g. 40), starting one every $start_time_delay seconds.
34+
35+# Max times we repeat the test string: allows to test for
36+# large messages.
37+my $block_rpt = 4096;
38+
39+# Probability to stop a client after a message (e.g. with
40+# .01 a client will send an average of 100 messages before
41+# disconnecting).
42+my $stop_client_probability = .001;
43+
44+# What protocols we test, and on what ports
45+# Just comment out protocols you don't want to use.
46+my %protocols = (
47+ "ssh" => { address => "localhost:9001", client => client("ssh") },
48+ "ssl" => { address => "localhost:9002", client => client("ssl") },
49+ "openvpn" => {address => "localhost:9003", client => client("openvpn") },
50+ "tinc" => {address => "localhost:9004", client => client("tinc") },
51+);
52+
53+##END CONFIG
54+
55+
56+# We use ports 9000, 9001 and 9002 -- hope that won't clash
57+# with anything...
58+my $sslh_address = "localhost:9000";
59+my $pidfile = "/tmp/sslh_test.pid";
60+
61+sub client {
62+ my ($service) = @_;
63+
64+ return sub {
65+ while (1) {
66+ my $cnx = new IO::Socket::INET(PeerHost => $sslh_address);
67+ my $test_data = "$service testing " x int(rand($block_rpt)+1) . "\n";
68+
69+ sleep 5 if $service eq "ssh";
70+ if ($service eq "openvpn") {
71+ syswrite $cnx, "\x00\x0F\x38\n";
72+ my $msg;
73+ sysread $cnx, $msg, 14; # length "openvpn: \x0\xF\x38\n" => 14
74+ }
75+ if ($service eq "tinc") {
76+ syswrite $cnx, "0 \n";
77+ my $msg;
78+ sysread $cnx, $msg, 10; # length "tinc: 0 \n" => 10
79+ }
80+ while (1) {
81+ print $cnx $test_data;
82+ my $r = <$cnx>;
83+ ($? = 1, die "$service got [$r]\n") if ($r ne "$service: $test_data");
84+ last if rand(1) < $stop_client_probability;
85+ }
86+ }
87+ exit 0;
88+ }
89+}
90+
91+foreach my $p (keys %protocols) {
92+ if (!fork) {
93+ exec "./echosrv --listen $protocols{$p}->{address} --prefix '$p: '";
94+ }
95+}
96+
97+# Start sslh with the right plumbing
98+my $sslh_pid;
99+if (!($sslh_pid = fork)) {
100+ my $user = (getpwuid $<)[0]; # Run under current username
101+ my $prots = join " ", map "--$_ $protocols{$_}->{address}", keys %protocols;
102+ my $cmd = "$sslh_binary -f -t 3 -u $user --listen $sslh_address $prots -P $pidfile";
103+ print "$cmd\n";
104+ exec $cmd;
105+ exit 0;
106+}
107+warn "spawned $sslh_pid\n";
108+sleep 2; # valgrind can be heavy -- wait 5 seconds
109+
110+
111+for (1 .. $NUM_CNX) {
112+ foreach my $p (keys %protocols) {
113+ if (!fork) {
114+ warn "starting $p\n";
115+ &{$protocols{$p}->{client}};
116+ exit;
117+ }
118+ # Give a little time so we don't overrun the
119+ # listen(2) backlog.
120+ select undef, undef, undef, $start_time_delay;
121+ }
122+}
123+
124+wait;
125+
126+
127+`killall echosrv`;
128+

Built with git-ssb-web