Commit a9c9941988bfa759887061f9e11fd09b67bd0f38
v1.9: 02AUG2011
WARNING: Options changed, you'll need to update your start-up scripts! Log format changed, you'll need to update log processing scripts! Now supports IPv6 throughout (both on listening and forwarding) Logs now contain IPv6 addresses, local forwarding address, and resolves names (unless --numeric is specified). Introduced long options. Options -l, -s and -o replaced by their long counterparts. Defaults for SSL and SSH options suppressed (it's legitimate to want to use sslh to mux OpenVPN and tinc while not caring about SSH nor SSL). Bind to multiple addresses with multiple -p options. Support for tinc VPN (experimental). Numeric logging option.Yves Rutschle committed on 7/10/2013, 9:13:32 PM
Parent: 80f76c6fc58f4ae5c3d25f42017a11d4918e1502
Files changed
ChangeLog | changed |
Makefile | changed |
README | changed |
common.c | changed |
common.h | changed |
scripts/etc.init.d.sslh | changed |
scripts/etc.rc.d.init.d.sslh.centos | changed |
sslh-fork.c | changed |
sslh-select.c | changed |
sslh.pod | changed |
ChangeLog | ||
---|---|---|
@@ -1,5 +1,32 @@ | ||
1 | -v1.8: | |
1 | +v1.9: 02AUG2011 | |
2 | + WARNING: Options changed, you'll need to update your | |
3 | + start-up scripts! Log format changed, you'll need to | |
4 | + update log processing scripts! | |
5 | + | |
6 | + Now supports IPv6 throughout (both on listening and | |
7 | + forwarding) | |
8 | + | |
9 | + Logs now contain IPv6 addresses, local forwarding | |
10 | + address, and resolves names (unless --numeric is | |
11 | + specified). | |
12 | + | |
13 | + Introduced long options. | |
14 | + | |
15 | + Options -l, -s and -o replaced by their long | |
16 | + counterparts. | |
17 | + | |
18 | + Defaults for SSL and SSH options suppressed (it's | |
19 | + legitimate to want to use sslh to mux OpenVPN and | |
20 | + tinc while not caring about SSH nor SSL). | |
21 | + | |
22 | + Bind to multiple addresses with multiple -p options. | |
23 | + | |
24 | + Support for tinc VPN (experimental). | |
25 | + | |
26 | + Numeric logging option. | |
27 | + | |
28 | +v1.8: 15JUL2011 | |
2 | 29 | Changed log format to make it possible to link |
3 | 30 | connections to subsequent logs from other services. |
4 | 31 | |
5 | 32 | Updated CentOS init.d script (Andre Krajnik). |
Makefile | ||
---|---|---|
@@ -1,7 +1,7 @@ | ||
1 | 1 | # Configuration |
2 | 2 | |
3 | -VERSION="v1.8" | |
3 | +VERSION="v1.9" | |
4 | 4 | USELIBWRAP= # Use libwrap? |
5 | 5 | PREFIX=/usr/local |
6 | 6 | |
7 | 7 | MAN=sslh.8.gz # man page name |
@@ -28,13 +28,13 @@ | ||
28 | 28 | |
29 | 29 | |
30 | 30 | sslh: $(OBJS) sslh-fork sslh-select |
31 | 31 | |
32 | -sslh-fork: $(OBJS) sslh-fork.o Makefile | |
32 | +sslh-fork: $(OBJS) sslh-fork.o Makefile common.h | |
33 | 33 | $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS) |
34 | 34 | strip sslh-fork |
35 | 35 | |
36 | -sslh-select: $(OBJS) sslh-select.o Makefile | |
36 | +sslh-select: $(OBJS) sslh-select.o Makefile common.h | |
37 | 37 | $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS) |
38 | 38 | strip sslh-select |
39 | 39 | |
40 | 40 |
README | ||
---|---|---|
@@ -103,24 +103,24 @@ | ||
103 | 103 | In that case, you end up with something like this: |
104 | 104 | |
105 | 105 | ssh -> proxytunnel -e --------ssh/ssl------> stunnel ---ssh---> sslh --> sshd |
106 | 106 | |
107 | -navigateur --------http/ssl------> stunnel ---http---> sslh --> http:80 | |
107 | +Web browser --------http/ssl------> stunnel ---http---> sslh --> http:80 | |
108 | 108 | |
109 | 109 | Configuration goes like this: |
110 | 110 | |
111 | 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 | |
112 | +stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- sslh -i --ssl localhost:80 --ssh localhost:22 | |
113 | 113 | |
114 | 114 | stunnel options: -f for foreground/debugging, -p specifies |
115 | 115 | the key + certificate, -d specifies which interface and port |
116 | 116 | we're listening to for incoming connexions, -l summons sslh |
117 | 117 | in inetd mode. |
118 | 118 | |
119 | -sslh options: -i for inetd mode, -l to forward SSL | |
119 | +sslh options: -i for inetd mode, --ssl to forward SSL | |
120 | 120 | connexions (in fact normal HTTP at that stage) to port 80, |
121 | 121 | and SSH connexions to port 22. This works because sslh |
122 | -considers that anything that is not SSH is SSL. | |
122 | +considers that any protocol it doesn't recognise is SSL. | |
123 | 123 | |
124 | 124 | ==== IP_TPROXY support ==== |
125 | 125 | |
126 | 126 | There is a netfilter patch that adds an option to the Linux |
common.c | ||
---|---|---|
@@ -20,8 +20,9 @@ | ||
20 | 20 | |
21 | 21 | |
22 | 22 | |
23 | 23 | |
24 | + | |
24 | 25 | |
25 | 26 | |
26 | 27 | |
27 | 28 | /* Added to make the code compilable under CYGWIN |
@@ -31,33 +32,36 @@ | ||
31 | 32 | |
32 | 33 | |
33 | 34 | int is_ssh_protocol(const char *p, int len); |
34 | 35 | int is_openvpn_protocol(const char *p, int len); |
36 | +int is_tinc_protocol(const char *p, int len); | |
35 | 37 | int is_true(const char *p, int len) { return 1; } |
36 | 38 | |
37 | 39 | struct proto protocols[] = { |
38 | 40 | /* affected description service saddr probe */ |
39 | - { 0, "SSH", "sshd", {0}, is_ssh_protocol }, | |
40 | - { 0, "OpenVPN", NULL, {0}, is_openvpn_protocol }, | |
41 | + { 0, "ssh", "sshd", {0}, is_ssh_protocol }, | |
42 | + { 0, "openvpn", NULL, {0}, is_openvpn_protocol }, | |
43 | + { 0, "tinc", NULL, {0}, is_tinc_protocol }, | |
41 | 44 | /* probe for SSL always successes: it's the default, and must be tried last |
42 | 45 | **/ |
43 | - { 0, "SSL", NULL, {0}, is_true } | |
46 | + { 0, "ssl", NULL, {0}, is_true } | |
44 | 47 | }; |
45 | 48 | |
46 | 49 | |
47 | 50 | const char* USAGE_STRING = |
48 | 51 | "sslh " VERSION "\n" \ |
49 | 52 | "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 | +"\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" \ | |
53 | 56 | "-v: verbose\n" \ |
54 | 57 | "-V: version\n" \ |
55 | 58 | "-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" \ | |
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" \ | |
60 | 64 | "-P: PID file. Default: /var/run/sslh.pid.\n" \ |
61 | 65 | "-i: Run as a inetd service.\n" \ |
62 | 66 | ""; |
63 | 67 | |
@@ -70,40 +74,61 @@ | ||
70 | 74 | int verbose = 0; |
71 | 75 | int probing_timeout = 2; |
72 | 76 | int inetd = 0; |
73 | 77 | int foreground = 0; |
74 | -struct sockaddr addr_listen; | |
78 | +int numeric = 0; | |
75 | 79 | char *user_name, *pid_file; |
76 | 80 | |
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? */ | |
83 | + | |
77 | 84 | |
78 | 85 | |
79 | 86 | int allow_severity =0, deny_severity = 0; |
80 | 87 | |
81 | 88 | |
82 | 89 | |
90 | +/* check result and die, printing the offending address and error */ | |
91 | +void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall) | |
92 | +{ | |
93 | + char buf[64]; | |
83 | 94 | |
84 | -/* Starts a listening socket on specified address. | |
95 | + if (res == -1) { | |
96 | + fprintf(stderr, "%s:%s: %s\n", | |
97 | + sprintaddr(buf, sizeof(buf), sock), | |
98 | + syscall, | |
99 | + strerror(errno)); | |
100 | + exit(1); | |
101 | + } | |
102 | +} | |
103 | + | |
104 | +/* Starts listening sockets on specified addresses. | |
105 | + * IN: addr[], num_addr | |
106 | + * OUT: sockfd[] | |
107 | + * Bound file descriptors are returned in alread-allocated *sockfd pointer | |
85 | 108 | Returns file descriptor |
86 | 109 | */ |
87 | -int start_listen_socket(struct sockaddr *addr) | |
110 | +void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr) | |
88 | 111 | { |
89 | - struct sockaddr_in *saddr = (struct sockaddr_in*)addr; | |
90 | - int sockfd, res, reuse; | |
112 | + struct sockaddr_storage *saddr; | |
113 | + int i, res, reuse; | |
91 | 114 | |
92 | - sockfd = socket(AF_INET, SOCK_STREAM, 0); | |
93 | - CHECK_RES_DIE(sockfd, "socket"); | |
115 | + for (i = 0; i < num_addr; i++) { | |
116 | + saddr = &addr[i]; | |
94 | 117 | |
95 | - reuse = 1; | |
96 | - res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); | |
97 | - CHECK_RES_DIE(res, "setsockopt"); | |
118 | + sockfd[i] = socket(saddr->ss_family, SOCK_STREAM, 0); | |
119 | + check_res_dumpdie(sockfd[i], saddr, "socket"); | |
98 | 120 | |
99 | - res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr)); | |
100 | - CHECK_RES_DIE(res, "bind"); | |
121 | + reuse = 1; | |
122 | + res = setsockopt(sockfd[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); | |
123 | + check_res_dumpdie(res, saddr, "setsockopt"); | |
101 | 124 | |
102 | - res = listen (sockfd, 50); | |
103 | - CHECK_RES_DIE(res, "listen"); | |
125 | + res = bind (sockfd[i], (struct sockaddr*)saddr, sizeof(*saddr)); | |
126 | + check_res_dumpdie(res, saddr, "bind"); | |
104 | 127 | |
105 | - return sockfd; | |
128 | + res = listen (sockfd[i], 50); | |
129 | + check_res_dumpdie(res, saddr, "listen"); | |
130 | + } | |
106 | 131 | } |
107 | 132 | |
108 | 133 | /* Store some data to write to the queue later */ |
109 | 134 | int defer_write(struct queue *q, void* data, int data_size) |
@@ -259,8 +284,15 @@ | ||
259 | 284 | else |
260 | 285 | return 0; |
261 | 286 | } |
262 | 287 | |
288 | +/* Is the buffer the beginning of a tinc connections? | |
289 | + * (protocol is undocumented, but starts with "0 " in 1.0.15) | |
290 | + * */ | |
291 | +int is_tinc_protocol( const char *p, int len) | |
292 | +{ | |
293 | + return !strncmp(p, "0 ", len); | |
294 | +} | |
263 | 295 | |
264 | 296 | /* |
265 | 297 | * Read the beginning of data coming from the client connection and check if |
266 | 298 | * it's a known protocol. Then leave the data on the defered |
@@ -289,48 +321,55 @@ | ||
289 | 321 | } |
290 | 322 | } |
291 | 323 | } |
292 | 324 | |
293 | - /* If none worked, return the last one */ | |
294 | - return ARRAY_SIZE(protocols) - 1; | |
325 | + /* If none worked, return the first one affected (that's completely | |
326 | + * arbitrary) */ | |
327 | + for (i = 0; i < ARRAY_SIZE(protocols); i++) | |
328 | + if (protocols[i].affected) | |
329 | + return i; | |
330 | + | |
331 | + /* At this stage... nothing is affected. This shouldn't happen as we check | |
332 | + * at least one target exists when we parse the commnand line */ | |
333 | + fprintf(stderr, "FATAL: No protocol affected. This should not happen.\n"); | |
334 | + exit(1); | |
295 | 335 | } |
296 | 336 | |
297 | 337 | /* returns a string that prints the IP and port of the sockaddr */ |
298 | -char* sprintaddr(char* buf, size_t size, struct sockaddr* s) | |
338 | +char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s) | |
299 | 339 | { |
300 | - char addr_str[1024]; | |
340 | + char host[NI_MAXHOST], serv[NI_MAXSERV]; | |
301 | 341 | |
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)); | |
342 | + getnameinfo((struct sockaddr*)s, sizeof(*s), host, sizeof(host), serv, sizeof(serv), numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 ); | |
343 | + snprintf(buf, size, "%s:%s", host, serv); | |
344 | + | |
304 | 345 | return buf; |
305 | 346 | } |
306 | 347 | |
307 | 348 | /* turns a "hostname:port" string into a struct sockaddr; |
308 | 349 | sock: socket address to which to copy the addr |
309 | 350 | fullname: input string -- it gets clobbered |
310 | 351 | */ |
311 | -void resolve_name(struct sockaddr *sock, char* fullname) | |
352 | +void resolve_name(struct sockaddr_storage *sock, char* fullname) | |
312 | 353 | { |
313 | 354 | struct addrinfo *addr, hint; |
314 | 355 | char *serv, *host; |
315 | 356 | int res; |
316 | 357 | |
317 | - char *sep = strchr(fullname, ':'); | |
358 | + char *sep = strrchr(fullname, ':'); | |
318 | 359 | |
319 | 360 | if (!sep) /* No separator: parameter is just a port */ |
320 | 361 | { |
321 | - serv = fullname; | |
322 | 362 | fprintf(stderr, "names must be fully specified as hostname:port\n"); |
323 | 363 | exit(1); |
324 | 364 | } |
325 | - else { | |
326 | - host = fullname; | |
327 | - serv = sep+1; | |
328 | - *sep = 0; | |
329 | - } | |
330 | 365 | |
366 | + host = fullname; | |
367 | + serv = sep+1; | |
368 | + *sep = 0; | |
369 | + | |
331 | 370 | memset(&hint, 0, sizeof(hint)); |
332 | - hint.ai_family = PF_INET; | |
371 | + hint.ai_family = PF_UNSPEC; | |
333 | 372 | hint.ai_socktype = SOCK_STREAM; |
334 | 373 | |
335 | 374 | res = getaddrinfo(host, serv, &hint, &addr); |
336 | 375 | if (res) { |
@@ -362,22 +401,43 @@ | ||
362 | 401 | |
363 | 402 | /* syslogs who connected to where */ |
364 | 403 | void log_connection(struct connection *cnx) |
365 | 404 | { |
366 | - struct sockaddr peeraddr, localaddr; | |
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 */ | |
367 | 409 | socklen_t size = sizeof(peeraddr); |
368 | - char buf[64], buf2[64]; | |
410 | + | |
411 | + char buf[MAX_NAMELENGTH], buf2[MAX_NAMELENGTH], buf3[MAX_NAMELENGTH], buf4[MAX_NAMELENGTH]; | |
369 | 412 | int res; |
370 | 413 | |
371 | - res = getpeername(cnx->q[0].fd, &peeraddr, &size); | |
372 | - if (res == -1) return; /* that should never happen, right? */ | |
414 | + memset(&peeraddr, 0, sizeof(peeraddr)); | |
415 | + memset(&listenaddr, 0, sizeof(listenaddr)); | |
416 | + memset(&forwardfromaddr, 0, sizeof(forwardfromaddr)); | |
417 | + memset(&targetaddr, 0, sizeof(targetaddr)); | |
373 | 418 | |
374 | - res = getpeername(cnx->q[1].fd, &localaddr, &size); | |
419 | + res = getpeername(cnx->q[0].fd, (struct sockaddr*)&peeraddr, &size); | |
375 | 420 | if (res == -1) return; /* that should never happen, right? */ |
376 | 421 | |
377 | - log_message(LOG_INFO, "connection from %s forwarded to %s\n", | |
378 | - sprintaddr(buf, sizeof(buf), &peeraddr), sprintaddr(buf2, sizeof(buf2), &localaddr)); | |
422 | + size = sizeof(listenaddr); | |
423 | + res = getsockname(cnx->q[0].fd, (struct sockaddr*)&listenaddr, &size); | |
424 | + if (res == -1) return; | |
379 | 425 | |
426 | + size = sizeof(targetaddr); | |
427 | + res = getpeername(cnx->q[1].fd, (struct sockaddr*)&targetaddr, &size); | |
428 | + if (res == -1) return; | |
429 | + | |
430 | + size = sizeof(forwardfromaddr); | |
431 | + res = getsockname(cnx->q[1].fd, (struct sockaddr*)&forwardfromaddr, &size); | |
432 | + if (res == -1) return; | |
433 | + | |
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 | + | |
380 | 440 | } |
381 | 441 | |
382 | 442 | |
383 | 443 | /* libwrap (tcpd): check the connection is legal. This is necessary because |
@@ -385,29 +445,40 @@ | ||
385 | 445 | * apply the rules itself. |
386 | 446 | * |
387 | 447 | * Returns -1 if access is denied, 0 otherwise |
388 | 448 | */ |
389 | -int check_access_rights(int in_socket, const char* service) | |
449 | +int check_access_rights(int in_socket, char* service) | |
390 | 450 | { |
391 | 451 | |
392 | 452 | struct sockaddr peeraddr; |
393 | 453 | socklen_t size = sizeof(peeraddr); |
394 | - char addr_str[1024]; | |
395 | - struct hostent *host; | |
396 | - struct in_addr addr; | |
454 | + char addr_str[NI_MAXHOST], host[NI_MAXHOST]; | |
397 | 455 | int res; |
398 | 456 | |
399 | 457 | res = getpeername(in_socket, &peeraddr, &size); |
400 | 458 | CHECK_RES_DIE(res, "getpeername"); |
401 | - inet_ntop(AF_INET, &((struct sockaddr_in*)&peeraddr)->sin_addr, addr_str, sizeof(addr_str)); | |
402 | 459 | |
403 | - addr.s_addr = inet_addr(addr_str); | |
404 | - host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET); | |
460 | + /* extract peer address */ | |
461 | + res = getnameinfo(&peeraddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST); | |
462 | + if (res) { | |
463 | + if (verbose) | |
464 | + fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res)); | |
465 | + strcpy(addr_str, STRING_UNKNOWN); | |
466 | + } | |
467 | + /* extract peer name */ | |
468 | + strcpy(host, STRING_UNKNOWN); | |
469 | + if (!numeric) { | |
470 | + res = getnameinfo(&peeraddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD); | |
471 | + if (res) { | |
472 | + if (verbose) | |
473 | + fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res)); | |
474 | + } | |
475 | + } | |
405 | 476 | |
406 | - if (!hosts_ctl(service, (host ? host->h_name : STRING_UNKNOWN), addr_str, STRING_UNKNOWN)) { | |
477 | + if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) { | |
407 | 478 | if (verbose) |
408 | 479 | fprintf(stderr, "access denied\n"); |
409 | - log_connection(in_socket, "access denied"); | |
480 | + log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str); | |
410 | 481 | close(in_socket); |
411 | 482 | return -1; |
412 | 483 | } |
413 | 484 | |
@@ -488,53 +559,76 @@ | ||
488 | 559 | protocols[i].description, |
489 | 560 | sprintaddr(buf, sizeof(buf), &protocols[i].saddr), |
490 | 561 | protocols[i].service); |
491 | 562 | } |
492 | - fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen)); | |
563 | + 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])); | |
566 | + } | |
567 | + fprintf(stderr, "timeout to ssh: %d\n", probing_timeout); | |
493 | 568 | } |
494 | 569 | |
570 | +/* Adds protocols to the list of options, so command-line parsing uses the | |
571 | + * protocol definition array | |
572 | + * options: array of options to add to; must be big enough | |
573 | + * n_opts: number of options in *options before calling (i.e. where to append) | |
574 | + * prot: array of protocols | |
575 | + * n_prots: number of protocols in *prot | |
576 | + * */ | |
577 | + | |
578 | +void append_protocols(struct option *options, int n_opts, struct proto *prot, int n_prots) | |
579 | +{ | |
580 | + int o, p; | |
581 | + | |
582 | + for (o = n_opts, p = 0; p < n_prots; o++, p++) { | |
583 | + options[o].name = prot[p].description; | |
584 | + options[o].has_arg = required_argument; | |
585 | + options[o].flag = 0; | |
586 | + options[o].val = p + PROT_SHIFT; | |
587 | + } | |
588 | +} | |
589 | + | |
495 | 590 | void parse_cmdline(int argc, char* argv[]) |
496 | 591 | { |
497 | - int c; | |
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]; | |
498 | 604 | |
499 | - while ((c = getopt(argc, argv, "t:l:s:o:p:P:ivfVu:")) != EOF) { | |
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 | + | |
500 | 619 | switch (c) { |
501 | 620 | |
502 | 621 | case 't': |
503 | 622 | probing_timeout = atoi(optarg); |
504 | 623 | break; |
505 | 624 | |
506 | 625 | case 'p': |
507 | - resolve_name(&addr_listen, optarg); | |
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); | |
508 | 629 | break; |
509 | 630 | |
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 | 631 | case 'V': |
538 | 632 | printf("%s %s\n", server_type, VERSION); |
539 | 633 | exit(0); |
540 | 634 | |
@@ -550,8 +644,19 @@ | ||
550 | 644 | fprintf(stderr, USAGE_STRING); |
551 | 645 | exit(2); |
552 | 646 | } |
553 | 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 | + | |
554 | 659 | } |
555 | 660 | |
556 | 661 | int main(int argc, char *argv[]) |
557 | 662 | { |
@@ -559,24 +664,15 @@ | ||
559 | 664 | extern char *optarg; |
560 | 665 | extern int optind; |
561 | 666 | int res; |
562 | 667 | |
563 | - int listen_socket; | |
668 | + int *listen_sockets; | |
564 | 669 | |
565 | 670 | /* Init defaults */ |
566 | - char listen_str[] = "0.0.0.0:443"; | |
567 | - char ssl_str[] = "localhost:443"; | |
568 | - char ssh_str[] = "localhost:22"; | |
569 | 671 | pid_file = "/var/run/sslh.pid"; |
570 | 672 | user_name = "nobody"; |
571 | 673 | foreground = 0; |
572 | 674 | |
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 | 675 | parse_cmdline(argc, argv); |
580 | 676 | |
581 | 677 | if (inetd) |
582 | 678 | { |
@@ -587,9 +683,11 @@ | ||
587 | 683 | |
588 | 684 | if (verbose) |
589 | 685 | printsettings(); |
590 | 686 | |
591 | - listen_socket = start_listen_socket(&addr_listen); | |
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); | |
592 | 690 | |
593 | 691 | if (!foreground) |
594 | 692 | if (fork() > 0) exit(0); /* Detach */ |
595 | 693 | |
@@ -607,8 +705,8 @@ | ||
607 | 705 | |
608 | 706 | /* Open syslog connection */ |
609 | 707 | setup_syslog(argv[0]); |
610 | 708 | |
611 | - main_loop(listen_socket); | |
709 | + main_loop(listen_sockets, num_addr_listen); | |
612 | 710 | |
613 | 711 | return 0; |
614 | 712 | } |
common.h | ||
---|---|---|
@@ -51,17 +51,18 @@ | ||
51 | 51 | * These must match the order of the protocols[] array in common.c */ |
52 | 52 | typedef enum protocol_type { |
53 | 53 | PROT_SSH, |
54 | 54 | PROT_OPENVPN, |
55 | + PROT_TINC, | |
55 | 56 | PROT_SSL, |
56 | 57 | } T_PROTO_ID; |
57 | 58 | |
58 | 59 | /* For each protocol we need: */ |
59 | 60 | struct proto { |
60 | 61 | int affected; /* are we actually using it? */ |
61 | - char* description; /* a string that says what it is (for logging) */ | |
62 | + char* description; /* a string that says what it is (for logging and command-line parsing) */ | |
62 | 63 | char* service; /* service name to do libwrap checks */ |
63 | - struct sockaddr saddr; /* where to switch that protocol */ | |
64 | + struct sockaddr_storage saddr; /* where to switch that protocol */ | |
64 | 65 | int (*probe)(const char*, int); /* function to probe that protocol */ |
65 | 66 | }; |
66 | 67 | |
67 | 68 | /* A table in common.c contains all the known protocols */ |
@@ -92,15 +93,15 @@ | ||
92 | 93 | |
93 | 94 | |
94 | 95 | /* common.c */ |
95 | 96 | void init_cnx(struct connection *cnx); |
96 | -int start_listen_socket(struct sockaddr *addr); | |
97 | +void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr); | |
97 | 98 | 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) ; | |
98 | 101 | 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 | 102 | void log_connection(struct connection *cnx); |
102 | -int check_access_rights(int in_socket, const char* service); | |
103 | +int check_access_rights(int in_socket, char* service); | |
103 | 104 | void setup_signals(void); |
104 | 105 | void setup_syslog(char* bin_name); |
105 | 106 | void drop_privileges(char* user_name); |
106 | 107 | void write_pid_file(char* pidfile); |
@@ -113,15 +114,15 @@ | ||
113 | 114 | int defer_write(struct queue *q, void* data, int data_size); |
114 | 115 | int flush_defered(struct queue *q); |
115 | 116 | |
116 | 117 | extern int probing_timeout, verbose, inetd; |
117 | -extern struct sockaddr addr_listen, addr_ssl, addr_ssh, addr_openvpn; | |
118 | +extern struct sockaddr_storage *addr_listen, addr_ssl, addr_ssh, addr_openvpn; | |
118 | 119 | extern const char* USAGE_STRING; |
119 | 120 | extern char* user_name, *pid_file; |
120 | 121 | extern const char* server_type; |
121 | 122 | |
122 | 123 | /* sslh-fork.c */ |
123 | 124 | void start_shoveler(int); |
124 | 125 | |
125 | -void main_loop(int); | |
126 | +void main_loop(int *listen_sockets, int num_addr_listen); | |
126 | 127 | |
127 | 128 |
scripts/etc.init.d.sslh | ||
---|---|---|
@@ -29,9 +29,9 @@ | ||
29 | 29 | |
30 | 30 | start() |
31 | 31 | { |
32 | 32 | echo "Start services: sslh" |
33 | - $DAEMON -u nobody -p ${LISTEN} -s ${SSH} -l ${SSL} | |
33 | + $DAEMON --user nobody --listen ${LISTEN} --ssh ${SSH} --ssl ${SSL} | |
34 | 34 | logger -t ${tag} -p ${facility} -i 'Started sslh' |
35 | 35 | } |
36 | 36 | |
37 | 37 | stop() |
scripts/etc.rc.d.init.d.sslh.centos | ||
---|---|---|
@@ -19,9 +19,9 @@ | ||
19 | 19 | |
20 | 20 | SSLH="/usr/local/sbin/sslh" |
21 | 21 | PIDFILE="/var/run/sslh" |
22 | 22 | |
23 | -OPTIONS="-p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22" | |
23 | +OPTIONS="-p 0.0.0.0:8443 --ssl 127.0.0.1:443 --ssh 127.0.0.1:22" | |
24 | 24 | |
25 | 25 | if [ -f /etc/sysconfig/sslh ]; then |
26 | 26 | . /etc/sysconfig/sslh |
27 | 27 | fi |
sslh-fork.c | ||
---|---|---|
@@ -67,9 +67,9 @@ | ||
67 | 67 | void start_shoveler(int in_socket) |
68 | 68 | { |
69 | 69 | fd_set fds; |
70 | 70 | struct timeval tv; |
71 | - struct sockaddr *saddr; | |
71 | + struct sockaddr_storage *saddr; | |
72 | 72 | int res; |
73 | 73 | int out_socket; |
74 | 74 | char *target; |
75 | 75 | struct connection cnx; |
@@ -102,10 +102,10 @@ | ||
102 | 102 | exit(0); |
103 | 103 | } |
104 | 104 | |
105 | 105 | /* Connect the target socket */ |
106 | - out_socket = socket(AF_INET, SOCK_STREAM, 0); | |
107 | - res = connect(out_socket, saddr, sizeof(addr_ssl)); | |
106 | + out_socket = socket(saddr->ss_family, SOCK_STREAM, 0); | |
107 | + res = connect(out_socket, (struct sockaddr*)saddr, sizeof(addr_ssl)); | |
108 | 108 | CHECK_RES_DIE(res, "connect"); |
109 | 109 | if (verbose) |
110 | 110 | fprintf(stderr, "connected to something\n"); |
111 | 111 | |
@@ -125,25 +125,29 @@ | ||
125 | 125 | |
126 | 126 | exit(0); |
127 | 127 | } |
128 | 128 | |
129 | -void main_loop(int listen_socket) | |
129 | +void main_loop(int *listen_sockets, int num_addr_listen) | |
130 | 130 | { |
131 | - int in_socket; | |
131 | + int in_socket, i; | |
132 | 132 | |
133 | - while (1) | |
134 | - { | |
135 | - in_socket = accept(listen_socket, 0, 0); | |
136 | - if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); | |
133 | + for (i = 0; i < num_addr_listen; i++) { | |
134 | + if (!fork()) { | |
135 | + while (1) | |
136 | + { | |
137 | + in_socket = accept(listen_sockets[i], 0, 0); | |
138 | + if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); | |
137 | 139 | |
138 | - if (!fork()) | |
139 | - { | |
140 | - close(listen_socket); | |
141 | - start_shoveler(in_socket); | |
142 | - exit(0); | |
143 | - } | |
144 | - close(in_socket); | |
145 | - } | |
140 | + if (!fork()) | |
141 | + { | |
142 | + close(listen_sockets[i]); | |
143 | + start_shoveler(in_socket); | |
144 | + exit(0); | |
145 | + } | |
146 | + close(in_socket); | |
147 | + } | |
148 | + } | |
149 | + } | |
146 | 150 | } |
147 | 151 | |
148 | 152 | /* The actual main is in common.c: it's the same for both version of |
149 | 153 | * the server |
sslh-select.c | ||
---|---|---|
@@ -110,17 +110,17 @@ | ||
110 | 110 | return in_socket; |
111 | 111 | } |
112 | 112 | |
113 | 113 | /* Connect queue 1 of connection to SSL; returns new file descriptor */ |
114 | -int connect_queue(struct connection *cnx, struct sockaddr *addr, | |
114 | +int connect_queue(struct connection *cnx, struct sockaddr_storage *addr, | |
115 | 115 | char* cnx_name, |
116 | 116 | fd_set *fds_r, fd_set *fds_w) |
117 | 117 | { |
118 | 118 | struct queue *q = &cnx->q[1]; |
119 | 119 | int res; |
120 | 120 | |
121 | - q->fd = socket(AF_INET, SOCK_STREAM, 0); | |
122 | - res = connect(q->fd, addr, sizeof(*addr)); | |
121 | + q->fd = socket(addr->ss_family, SOCK_STREAM, 0); | |
122 | + res = connect(q->fd, (struct sockaddr*)addr, sizeof(*addr)); | |
123 | 123 | log_connection(cnx); |
124 | 124 | if (res == -1) { |
125 | 125 | tidy_connection(cnx, fds_r, fds_w); |
126 | 126 | log_message(LOG_ERR, "forward to %s failed\n", cnx_name); |
@@ -187,9 +187,9 @@ | ||
187 | 187 | * |
188 | 188 | * That way, each pair of file descriptor (read from one, write to the other) |
189 | 189 | * is monitored either for read or for write, but never for both. |
190 | 190 | */ |
191 | -void main_loop(int listen_socket) | |
191 | +void main_loop(int *listen_sockets, int num_addr_listen) | |
192 | 192 | { |
193 | 193 | fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */ |
194 | 194 | fd_set readfds, writefds; /* working read and write fd sets */ |
195 | 195 | struct timeval tv; |
@@ -202,12 +202,14 @@ | ||
202 | 202 | * select() */ |
203 | 203 | |
204 | 204 | FD_ZERO(&fds_r); |
205 | 205 | FD_ZERO(&fds_w); |
206 | - FD_SET(listen_socket, &fds_r); | |
207 | - max_fd = listen_socket + 1; | |
208 | 206 | |
209 | - set_nonblock(listen_socket); | |
207 | + for (i = 0; i < num_addr_listen; i++) { | |
208 | + FD_SET(listen_sockets[i], &fds_r); | |
209 | + set_nonblock(listen_sockets[i]); | |
210 | + } | |
211 | + max_fd = listen_sockets[num_addr_listen-1] + 1; | |
210 | 212 | |
211 | 213 | cnx_num_alloc = getpagesize() / sizeof(struct connection); |
212 | 214 | |
213 | 215 | num_cnx = cnx_num_alloc; /* Start with a set pool of slots */ |
@@ -230,18 +232,20 @@ | ||
230 | 232 | perror("select"); |
231 | 233 | |
232 | 234 | |
233 | 235 | /* 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++; | |
236 | + for (i = 0; i < num_addr_listen; i++) { | |
237 | + if (FD_ISSET(listen_sockets[i], &readfds)) { | |
238 | + in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx); | |
239 | + num_probing++; | |
237 | 240 | |
238 | - if (in_socket > 0) { | |
239 | - FD_SET(in_socket, &fds_r); | |
240 | - if (in_socket >= max_fd) | |
241 | - max_fd = in_socket + 1;; | |
241 | + if (in_socket > 0) { | |
242 | + FD_SET(in_socket, &fds_r); | |
243 | + if (in_socket >= max_fd) | |
244 | + max_fd = in_socket + 1;; | |
245 | + } | |
246 | + FD_CLR(listen_sockets[i], &readfds); | |
242 | 247 | } |
243 | - FD_CLR(listen_socket, &readfds); | |
244 | 248 | } |
245 | 249 | |
246 | 250 | /* Check all sockets for write activity */ |
247 | 251 | for (i = 0; i < num_cnx; i++) { |
sslh.pod | ||
---|---|---|
@@ -5,9 +5,9 @@ | ||
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<-o> I<target address for OpenVPN>] [B<-u> I<username>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] | |
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] | |
10 | 10 | |
11 | 11 | =head1 DESCRIPTION |
12 | 12 | |
13 | 13 | B<sslh> accepts HTTPS, SSH and OpenVPN connections on the |
@@ -56,71 +56,81 @@ | ||
56 | 56 | =head1 OPTIONS |
57 | 57 | |
58 | 58 | =over 4 |
59 | 59 | |
60 | -=item B<-t> I<num> | |
60 | +=item B<-t> I<num>, B<--timeout> I<num> | |
61 | 61 | |
62 | 62 | Timeout before a connection is considered to be SSH. Default |
63 | 63 | is 2s. |
64 | 64 | |
65 | -=item B<-p> I<listening address> | |
65 | +=item B<-p> I<listening address>, B<--listen> I<listening address> | |
66 | 66 | |
67 | 67 | Interface and port on which to listen, e.g. I<foobar:443>, |
68 | 68 | where I<foobar> is the name of an interface (typically the |
69 | 69 | IP address on which the Internet connection ends up). |
70 | 70 | |
71 | -Defaults to I<0.0.0.0:443> (listen to port 443 on all | |
72 | -available interfaces). | |
71 | +This can be specified several times to bind B<sslh> to | |
72 | +several addresses. | |
73 | 73 | |
74 | -=item B<-l> I<target address for SSL> | |
74 | +=item B<--ssl> I<target address> | |
75 | 75 | |
76 | 76 | Interface and port on which to forward SSL connection, |
77 | 77 | typically I<localhost:443>. |
78 | 78 | |
79 | -Defaults to I<localhost:443> (this assumes you would | |
80 | -configure your B<httpd> process to listen to port 443). | |
81 | - | |
82 | 79 | Note that you can set B<sslh> to listen on I<ext_ip:443> and |
83 | 80 | B<httpd> to listen on I<localhost:443>: this allows clients |
84 | 81 | inside your network to just connect directly to B<httpd>. |
85 | 82 | |
86 | -=item B<-s> I<target address for SSH> | |
83 | +=item B<--ssh> I<target address> | |
87 | 84 | |
88 | -Interface and port on which to forward SSH connection, | |
89 | -defaults to I<localhost:22>. | |
85 | +Interface and port on which to forward SSH connections, | |
86 | +typically I<localhost:22>. | |
90 | 87 | |
91 | -=item B<-o> I<target address for OpenVPN> | |
88 | +=item B<--openvpn> I<target address> | |
92 | 89 | |
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. | |
90 | +Interface and port on which to forward OpenVPN connections, | |
91 | +typically I<localhost:1194>. | |
97 | 92 | |
98 | -=item B<-v> | |
93 | +=item B<--tinc> I<target address> | |
99 | 94 | |
95 | +Interface and port on which to forward tinc connections, | |
96 | +typically I<localhost:655>. | |
97 | + | |
98 | +This is experimental. If you use this feature, please report | |
99 | +the results (even if it works!) | |
100 | + | |
101 | +=item B<-v>, B<--verbose> | |
102 | + | |
100 | 103 | Increase verboseness. |
101 | 104 | |
105 | +=item B<-n>, B<--numeric> | |
106 | + | |
107 | +Do not attempt to resolve hostnames: logs will contain IP | |
108 | +addresses. This is mostly useful if the system's DNS is slow | |
109 | +and running the I<sslh-select> variant, as DNS requests will | |
110 | +hang all connections. | |
111 | + | |
102 | 112 | =item B<-V> |
103 | 113 | |
104 | 114 | Prints B<sslh> version. |
105 | 115 | |
106 | -=item B<-u> I<username> | |
116 | +=item B<-u> I<username>, B<--user> I<username> | |
107 | 117 | |
108 | 118 | Requires to run under the specified username. Defaults to |
109 | 119 | I<nobody> (which is not perfect -- ideally B<sslh> should |
110 | 120 | run under its own UID). |
111 | 121 | |
112 | -=item B<-P> I<pidfile> | |
122 | +=item B<-P> I<pidfile>, B<--pid-file> I<pidfile> | |
113 | 123 | |
114 | 124 | Specifies the file in which to write the PID of the main |
115 | 125 | server. Defaults to I</var/run/sslh.pid>. |
116 | 126 | |
117 | -=item B<-i> | |
127 | +=item B<-i>, B<--inetd> | |
118 | 128 | |
119 | 129 | Runs as an I<inetd> server. Options B<-P> (PID file), B<-p> |
120 | 130 | (listen address), B<-u> (user) are ignored. |
121 | 131 | |
122 | -=item B<-f> | |
132 | +=item B<-f>, B<--foreground> | |
123 | 133 | |
124 | 134 | Runs in foreground. The server will not fork and will remain connected |
125 | 135 | to the terminal. Messages normally sent to B<syslog> will also be sent |
126 | 136 | to I<stderr>. |
Built with git-ssb-web