diff --git a/ChangeLog b/ChangeLog index 150384e..dfab89e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,31 @@ -v1.8: +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. + +v1.8: 15JUL2011 Changed log format to make it possible to link connections to subsequent logs from other services. diff --git a/Makefile b/Makefile index f3dc943..3ab7f64 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Configuration -VERSION="v1.8" +VERSION="v1.9" USELIBWRAP= # Use libwrap? PREFIX=/usr/local @@ -29,11 +29,11 @@ all: sslh $(MAN) sslh: $(OBJS) sslh-fork sslh-select -sslh-fork: $(OBJS) sslh-fork.o Makefile +sslh-fork: $(OBJS) sslh-fork.o Makefile common.h $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS) strip sslh-fork -sslh-select: $(OBJS) sslh-select.o Makefile +sslh-select: $(OBJS) sslh-select.o Makefile common.h $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS) strip sslh-select diff --git a/README b/README index 180a50b..9e385a5 100644 --- a/README +++ b/README @@ -104,22 +104,22 @@ In that case, you end up with something like this: ssh -> proxytunnel -e --------ssh/ssl------> stunnel ---ssh---> sslh --> sshd -navigateur --------http/ssl------> stunnel ---http---> sslh --> http:80 +Web browser --------http/ssl------> stunnel ---http---> sslh --> http:80 Configuration goes like this: On the server side, using stunnel3: -stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- sslh -i -l localhost:80 -s localhost:22 +stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- sslh -i --ssl localhost:80 --ssh localhost:22 stunnel options: -f for foreground/debugging, -p specifies the key + certificate, -d specifies which interface and port we're listening to for incoming connexions, -l summons sslh in inetd mode. -sslh options: -i for inetd mode, -l to forward SSL +sslh options: -i for inetd mode, --ssl to forward SSL connexions (in fact normal HTTP at that stage) to port 80, and SSH connexions to port 22. This works because sslh -considers that anything that is not SSH is SSL. +considers that any protocol it doesn't recognise is SSL. ==== IP_TPROXY support ==== diff --git a/common.c b/common.c index 5ef1d75..c57e06f 100755 --- a/common.c +++ b/common.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "common.h" @@ -32,31 +33,34 @@ int is_ssh_protocol(const char *p, int len); int is_openvpn_protocol(const char *p, int len); +int is_tinc_protocol(const char *p, int len); int is_true(const char *p, int len) { return 1; } struct proto protocols[] = { /* affected description service saddr probe */ - { 0, "SSH", "sshd", {0}, is_ssh_protocol }, - { 0, "OpenVPN", NULL, {0}, is_openvpn_protocol }, + { 0, "ssh", "sshd", {0}, is_ssh_protocol }, + { 0, "openvpn", NULL, {0}, is_openvpn_protocol }, + { 0, "tinc", NULL, {0}, is_tinc_protocol }, /* probe for SSL always successes: it's the default, and must be tried last **/ - { 0, "SSL", NULL, {0}, is_true } + { 0, "ssl", NULL, {0}, is_true } }; const char* USAGE_STRING = "sslh " VERSION "\n" \ "usage:\n" \ -"\tsslh [-v] [-i] [-V] [-f]" -"[-t ] -u -p [listenaddr:] \n" \ -"\t\t-s [sshhost:]port -l [sslhost:]port [-P pidfile]\n\n" \ +"\tsslh [-v] [-i] [-V] [-f]\n" +"\t[-t ] [-P ] -u -p [-p ...] \n" \ +"\t[--ssh ] [--ssl ] [--openvpn ] [--tinc ]\n\n" \ "-v: verbose\n" \ "-V: version\n" \ "-f: foreground\n" \ -"-p: address and port to listen on. default: 0.0.0.0:443\n" \ -"-s: SSH address: where to connect an SSH connection. default: localhost:22\n" \ -"-l: SSL address: where to connect an SSL connection.\n" \ -"-o: OpenVPN address: where to connect an OpenVPN connection.\n" \ +"-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" \ +"--ssh: SSH address: where to connect an SSH connection.\n" \ +"--ssl: SSL address: where to connect an SSL connection.\n" \ +"--openvpn: OpenVPN address: where to connect an OpenVPN connection.\n" \ +"--tinc: tinc address: where to connect a tinc connection.\n" \ "-P: PID file. Default: /var/run/sslh.pid.\n" \ "-i: Run as a inetd service.\n" \ ""; @@ -71,38 +75,59 @@ int verbose = 0; int probing_timeout = 2; int inetd = 0; int foreground = 0; -struct sockaddr addr_listen; +int numeric = 0; char *user_name, *pid_file; +struct sockaddr_storage *addr_listen = NULL; /* what addresses do we listen to? */ +int num_addr_listen = 0; /* How many addresses do we listen to? */ + #ifdef LIBWRAP #include int allow_severity =0, deny_severity = 0; #endif +/* check result and die, printing the offending address and error */ +void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall) +{ + char buf[64]; -/* Starts a listening socket on specified address. + if (res == -1) { + fprintf(stderr, "%s:%s: %s\n", + sprintaddr(buf, sizeof(buf), sock), + syscall, + strerror(errno)); + exit(1); + } +} + +/* Starts listening sockets on specified addresses. + * IN: addr[], num_addr + * OUT: sockfd[] + * Bound file descriptors are returned in alread-allocated *sockfd pointer Returns file descriptor */ -int start_listen_socket(struct sockaddr *addr) +void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr) { - struct sockaddr_in *saddr = (struct sockaddr_in*)addr; - int sockfd, res, reuse; + struct sockaddr_storage *saddr; + int i, res, reuse; - sockfd = socket(AF_INET, SOCK_STREAM, 0); - CHECK_RES_DIE(sockfd, "socket"); + for (i = 0; i < num_addr; i++) { + saddr = &addr[i]; - reuse = 1; - res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); - CHECK_RES_DIE(res, "setsockopt"); + sockfd[i] = socket(saddr->ss_family, SOCK_STREAM, 0); + check_res_dumpdie(sockfd[i], saddr, "socket"); - res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr)); - CHECK_RES_DIE(res, "bind"); + reuse = 1; + res = setsockopt(sockfd[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + check_res_dumpdie(res, saddr, "setsockopt"); - res = listen (sockfd, 50); - CHECK_RES_DIE(res, "listen"); + res = bind (sockfd[i], (struct sockaddr*)saddr, sizeof(*saddr)); + check_res_dumpdie(res, saddr, "bind"); - return sockfd; + res = listen (sockfd[i], 50); + check_res_dumpdie(res, saddr, "listen"); + } } /* Store some data to write to the queue later */ @@ -260,6 +285,13 @@ int is_openvpn_protocol (const char*p,int len) return 0; } +/* Is the buffer the beginning of a tinc connections? + * (protocol is undocumented, but starts with "0 " in 1.0.15) + * */ +int is_tinc_protocol( const char *p, int len) +{ + return !strncmp(p, "0 ", len); +} /* * Read the beginning of data coming from the client connection and check if @@ -290,17 +322,26 @@ T_PROTO_ID probe_client_protocol(struct connection *cnx) } } - /* If none worked, return the last one */ - return ARRAY_SIZE(protocols) - 1; + /* If none worked, return the first one affected (that's completely + * arbitrary) */ + for (i = 0; i < ARRAY_SIZE(protocols); i++) + if (protocols[i].affected) + return i; + + /* At this stage... nothing is affected. This shouldn't happen as we check + * at least one target exists when we parse the commnand line */ + fprintf(stderr, "FATAL: No protocol affected. This should not happen.\n"); + exit(1); } /* returns a string that prints the IP and port of the sockaddr */ -char* sprintaddr(char* buf, size_t size, struct sockaddr* s) +char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s) { - char addr_str[1024]; + char host[NI_MAXHOST], serv[NI_MAXSERV]; + + getnameinfo((struct sockaddr*)s, sizeof(*s), host, sizeof(host), serv, sizeof(serv), numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 ); + snprintf(buf, size, "%s:%s", host, serv); - inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str)); - snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port)); return buf; } @@ -308,28 +349,26 @@ char* sprintaddr(char* buf, size_t size, struct sockaddr* s) sock: socket address to which to copy the addr fullname: input string -- it gets clobbered */ -void resolve_name(struct sockaddr *sock, char* fullname) +void resolve_name(struct sockaddr_storage *sock, char* fullname) { struct addrinfo *addr, hint; char *serv, *host; int res; - char *sep = strchr(fullname, ':'); + char *sep = strrchr(fullname, ':'); if (!sep) /* No separator: parameter is just a port */ { - serv = fullname; fprintf(stderr, "names must be fully specified as hostname:port\n"); exit(1); } - else { - host = fullname; - serv = sep+1; - *sep = 0; - } + + host = fullname; + serv = sep+1; + *sep = 0; memset(&hint, 0, sizeof(hint)); - hint.ai_family = PF_INET; + hint.ai_family = PF_UNSPEC; hint.ai_socktype = SOCK_STREAM; res = getaddrinfo(host, serv, &hint, &addr); @@ -363,19 +402,40 @@ void log_message(int type, char* msg, ...) /* syslogs who connected to where */ void log_connection(struct connection *cnx) { - struct sockaddr peeraddr, localaddr; + struct sockaddr_storage peeraddr; /* Who's connecting to sshd */ + struct sockaddr_storage listenaddr; /* Where is it connecting to */ + struct sockaddr_storage forwardfromaddr; /* Where is it forwarded from */ + struct sockaddr_storage targetaddr; /* Where is it forwarded to */ socklen_t size = sizeof(peeraddr); - char buf[64], buf2[64]; +#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1) + char buf[MAX_NAMELENGTH], buf2[MAX_NAMELENGTH], buf3[MAX_NAMELENGTH], buf4[MAX_NAMELENGTH]; int res; - res = getpeername(cnx->q[0].fd, &peeraddr, &size); + memset(&peeraddr, 0, sizeof(peeraddr)); + memset(&listenaddr, 0, sizeof(listenaddr)); + memset(&forwardfromaddr, 0, sizeof(forwardfromaddr)); + memset(&targetaddr, 0, sizeof(targetaddr)); + + res = getpeername(cnx->q[0].fd, (struct sockaddr*)&peeraddr, &size); if (res == -1) return; /* that should never happen, right? */ - res = getpeername(cnx->q[1].fd, &localaddr, &size); - if (res == -1) return; /* that should never happen, right? */ + size = sizeof(listenaddr); + res = getsockname(cnx->q[0].fd, (struct sockaddr*)&listenaddr, &size); + if (res == -1) return; - log_message(LOG_INFO, "connection from %s forwarded to %s\n", - sprintaddr(buf, sizeof(buf), &peeraddr), sprintaddr(buf2, sizeof(buf2), &localaddr)); + size = sizeof(targetaddr); + res = getpeername(cnx->q[1].fd, (struct sockaddr*)&targetaddr, &size); + if (res == -1) return; + + size = sizeof(forwardfromaddr); + res = getsockname(cnx->q[1].fd, (struct sockaddr*)&forwardfromaddr, &size); + if (res == -1) return; + + log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n", + sprintaddr(buf, sizeof(buf), &peeraddr), + sprintaddr(buf2, sizeof(buf2), &listenaddr), + sprintaddr(buf3, sizeof(buf3), &forwardfromaddr), + sprintaddr(buf4, sizeof(buf4), &targetaddr)); } @@ -386,27 +446,38 @@ void log_connection(struct connection *cnx) * * Returns -1 if access is denied, 0 otherwise */ -int check_access_rights(int in_socket, const char* service) +int check_access_rights(int in_socket, char* service) { #ifdef LIBWRAP struct sockaddr peeraddr; socklen_t size = sizeof(peeraddr); - char addr_str[1024]; - struct hostent *host; - struct in_addr addr; + char addr_str[NI_MAXHOST], host[NI_MAXHOST]; int res; res = getpeername(in_socket, &peeraddr, &size); CHECK_RES_DIE(res, "getpeername"); - inet_ntop(AF_INET, &((struct sockaddr_in*)&peeraddr)->sin_addr, addr_str, sizeof(addr_str)); - addr.s_addr = inet_addr(addr_str); - host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET); + /* extract peer address */ + res = getnameinfo(&peeraddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST); + if (res) { + if (verbose) + fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res)); + strcpy(addr_str, STRING_UNKNOWN); + } + /* extract peer name */ + strcpy(host, STRING_UNKNOWN); + if (!numeric) { + res = getnameinfo(&peeraddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD); + if (res) { + if (verbose) + fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res)); + } + } - if (!hosts_ctl(service, (host ? host->h_name : STRING_UNKNOWN), addr_str, STRING_UNKNOWN)) { + if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) { if (verbose) fprintf(stderr, "access denied\n"); - log_connection(in_socket, "access denied"); + log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str); close(in_socket); return -1; } @@ -489,14 +560,62 @@ void printsettings(void) sprintaddr(buf, sizeof(buf), &protocols[i].saddr), protocols[i].service); } - fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen)); + fprintf(stderr, "listening on:\n"); + for (i = 0; i < num_addr_listen; i++) { + fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), &addr_listen[i])); + } + fprintf(stderr, "timeout to ssh: %d\n", probing_timeout); +} + +/* Adds protocols to the list of options, so command-line parsing uses the + * protocol definition array + * options: array of options to add to; must be big enough + * n_opts: number of options in *options before calling (i.e. where to append) + * prot: array of protocols + * n_prots: number of protocols in *prot + * */ +#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */ +void append_protocols(struct option *options, int n_opts, struct proto *prot, int n_prots) +{ + int o, p; + + for (o = n_opts, p = 0; p < n_prots; o++, p++) { + options[o].name = prot[p].description; + options[o].has_arg = required_argument; + options[o].flag = 0; + options[o].val = p + PROT_SHIFT; + } } void parse_cmdline(int argc, char* argv[]) { - int c; + int c, affected = 0; + struct option const_options[] = { + { "inetd", no_argument, &inetd, 1 }, + { "foreground", no_argument, &foreground, 1 }, + { "verbose", no_argument, &verbose, 1 }, + { "numeric", no_argument, &numeric, 1 }, + { "user", required_argument, 0, 'u' }, + { "pidfile", required_argument, 0, 'P' }, + { "timeout", required_argument, 0, 't' }, + { "listen", required_argument, 0, 'p' }, + }; + struct option all_options[ARRAY_SIZE(const_options) + ARRAY_SIZE(protocols) + 1]; + + memset(all_options, 0, sizeof(all_options)); + memcpy(all_options, const_options, sizeof(const_options)); + append_protocols(all_options, ARRAY_SIZE(const_options), protocols, ARRAY_SIZE(protocols)); + + while ((c = getopt_long_only(argc, argv, "t:l:s:o:T:p:VP:", all_options, NULL)) != -1) { + if (c == 0) continue; + + if (c >= PROT_SHIFT) { + affected++; + protocols[c - PROT_SHIFT].affected = 1; + resolve_name(&protocols[c - PROT_SHIFT].saddr, optarg); + continue; + } - while ((c = getopt(argc, argv, "t:l:s:o:p:P:ivfVu:")) != EOF) { switch (c) { case 't': @@ -504,34 +623,9 @@ void parse_cmdline(int argc, char* argv[]) break; case 'p': - resolve_name(&addr_listen, optarg); - break; - - case 'l': - protocols[PROT_SSL].affected = 1; - resolve_name(&protocols[PROT_SSL].saddr, optarg); - break; - - case 's': - protocols[PROT_SSH].affected = 1; - resolve_name(&protocols[PROT_SSH].saddr, optarg); - break; - - case 'o': - protocols[PROT_OPENVPN].affected = 1; - resolve_name(&protocols[PROT_OPENVPN].saddr, optarg); - break; - - case 'i': - inetd = 1; - break; - - case 'f': - foreground = 1; - break; - - case 'v': - verbose += 1; + num_addr_listen++; + addr_listen = realloc(addr_listen, num_addr_listen * sizeof(addr_listen[0])); + resolve_name(&addr_listen[num_addr_listen - 1], optarg); break; case 'V': @@ -551,6 +645,17 @@ void parse_cmdline(int argc, char* argv[]) exit(2); } } + + if (!affected) { + fprintf(stderr, "At least one target protocol must be specified.\n"); + exit(2); + } + + if (!num_addr_listen) { + fprintf(stderr, "No listening address specified; use at least one -p option\n"); + exit(1); + } + } int main(int argc, char *argv[]) @@ -560,22 +665,13 @@ int main(int argc, char *argv[]) extern int optind; int res; - int listen_socket; + int *listen_sockets; /* Init defaults */ - char listen_str[] = "0.0.0.0:443"; - char ssl_str[] = "localhost:443"; - char ssh_str[] = "localhost:22"; pid_file = "/var/run/sslh.pid"; user_name = "nobody"; foreground = 0; - resolve_name(&addr_listen, listen_str); - protocols[PROT_SSL].affected = 1; - resolve_name(&protocols[PROT_SSL].saddr, ssl_str); - protocols[PROT_SSH].affected = 1; - resolve_name(&protocols[PROT_SSH].saddr, ssh_str); - parse_cmdline(argc, argv); if (inetd) @@ -588,7 +684,9 @@ int main(int argc, char *argv[]) if (verbose) printsettings(); - listen_socket = start_listen_socket(&addr_listen); + listen_sockets = malloc(num_addr_listen * sizeof(*listen_sockets)); + start_listen_sockets(listen_sockets, addr_listen, num_addr_listen); + free(addr_listen); if (!foreground) if (fork() > 0) exit(0); /* Detach */ @@ -608,7 +706,7 @@ int main(int argc, char *argv[]) /* Open syslog connection */ setup_syslog(argv[0]); - main_loop(listen_socket); + main_loop(listen_sockets, num_addr_listen); return 0; } diff --git a/common.h b/common.h index 7748b77..70b9812 100755 --- a/common.h +++ b/common.h @@ -52,15 +52,16 @@ enum connection_state { typedef enum protocol_type { PROT_SSH, PROT_OPENVPN, + PROT_TINC, PROT_SSL, } T_PROTO_ID; /* For each protocol we need: */ struct proto { int affected; /* are we actually using it? */ - char* description; /* a string that says what it is (for logging) */ + char* description; /* a string that says what it is (for logging and command-line parsing) */ char* service; /* service name to do libwrap checks */ - struct sockaddr saddr; /* where to switch that protocol */ + struct sockaddr_storage saddr; /* where to switch that protocol */ int (*probe)(const char*, int); /* function to probe that protocol */ }; @@ -93,13 +94,13 @@ struct connection { /* common.c */ void init_cnx(struct connection *cnx); -int start_listen_socket(struct sockaddr *addr); +void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr); int fd2fd(struct queue *target, struct queue *from); +char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s); +void resolve_name(struct sockaddr_storage *sock, char* fullname) ; T_PROTO_ID probe_client_protocol(struct connection *cnx); -char* sprintaddr(char* buf, size_t size, struct sockaddr* s); -void resolve_name(struct sockaddr *sock, char* fullname) ; void log_connection(struct connection *cnx); -int check_access_rights(int in_socket, const char* service); +int check_access_rights(int in_socket, char* service); void setup_signals(void); void setup_syslog(char* bin_name); void drop_privileges(char* user_name); @@ -114,7 +115,7 @@ int defer_write(struct queue *q, void* data, int data_size); int flush_defered(struct queue *q); extern int probing_timeout, verbose, inetd; -extern struct sockaddr addr_listen, addr_ssl, addr_ssh, addr_openvpn; +extern struct sockaddr_storage *addr_listen, addr_ssl, addr_ssh, addr_openvpn; extern const char* USAGE_STRING; extern char* user_name, *pid_file; extern const char* server_type; @@ -122,6 +123,6 @@ extern const char* server_type; /* sslh-fork.c */ void start_shoveler(int); -void main_loop(int); +void main_loop(int *listen_sockets, int num_addr_listen); diff --git a/scripts/etc.init.d.sslh b/scripts/etc.init.d.sslh index 8a5171f..bcc9485 100755 --- a/scripts/etc.init.d.sslh +++ b/scripts/etc.init.d.sslh @@ -30,7 +30,7 @@ DAEMON=$PREFIX/sbin/sslh start() { echo "Start services: sslh" - $DAEMON -u nobody -p ${LISTEN} -s ${SSH} -l ${SSL} + $DAEMON --user nobody --listen ${LISTEN} --ssh ${SSH} --ssl ${SSL} logger -t ${tag} -p ${facility} -i 'Started sslh' } diff --git a/scripts/etc.rc.d.init.d.sslh.centos b/scripts/etc.rc.d.init.d.sslh.centos index 2007927..2d19a1d 100755 --- a/scripts/etc.rc.d.init.d.sslh.centos +++ b/scripts/etc.rc.d.init.d.sslh.centos @@ -20,7 +20,7 @@ SSLH="/usr/local/sbin/sslh" PIDFILE="/var/run/sslh" -OPTIONS="-p 0.0.0.0:8443 -l 127.0.0.1:443 -s 127.0.0.1:22" +OPTIONS="-p 0.0.0.0:8443 --ssl 127.0.0.1:443 --ssh 127.0.0.1:22" if [ -f /etc/sysconfig/sslh ]; then . /etc/sysconfig/sslh diff --git a/sslh-fork.c b/sslh-fork.c index 893c345..ee9f3dd 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -68,7 +68,7 @@ void start_shoveler(int in_socket) { fd_set fds; struct timeval tv; - struct sockaddr *saddr; + struct sockaddr_storage *saddr; int res; int out_socket; char *target; @@ -103,8 +103,8 @@ void start_shoveler(int in_socket) } /* Connect the target socket */ - out_socket = socket(AF_INET, SOCK_STREAM, 0); - res = connect(out_socket, saddr, sizeof(addr_ssl)); + out_socket = socket(saddr->ss_family, SOCK_STREAM, 0); + res = connect(out_socket, (struct sockaddr*)saddr, sizeof(addr_ssl)); CHECK_RES_DIE(res, "connect"); if (verbose) fprintf(stderr, "connected to something\n"); @@ -126,23 +126,27 @@ void start_shoveler(int in_socket) exit(0); } -void main_loop(int listen_socket) +void main_loop(int *listen_sockets, int num_addr_listen) { - int in_socket; + int in_socket, i; - while (1) - { - in_socket = accept(listen_socket, 0, 0); - if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); + for (i = 0; i < num_addr_listen; i++) { + if (!fork()) { + while (1) + { + in_socket = accept(listen_sockets[i], 0, 0); + if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); - if (!fork()) - { - close(listen_socket); - start_shoveler(in_socket); - exit(0); - } - close(in_socket); - } + if (!fork()) + { + close(listen_sockets[i]); + start_shoveler(in_socket); + exit(0); + } + close(in_socket); + } + } + } } /* The actual main is in common.c: it's the same for both version of diff --git a/sslh-select.c b/sslh-select.c index 7c1592e..63f97bd 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -111,15 +111,15 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_ } /* Connect queue 1 of connection to SSL; returns new file descriptor */ -int connect_queue(struct connection *cnx, struct sockaddr *addr, +int connect_queue(struct connection *cnx, struct sockaddr_storage *addr, char* cnx_name, fd_set *fds_r, fd_set *fds_w) { struct queue *q = &cnx->q[1]; int res; - q->fd = socket(AF_INET, SOCK_STREAM, 0); - res = connect(q->fd, addr, sizeof(*addr)); + q->fd = socket(addr->ss_family, SOCK_STREAM, 0); + res = connect(q->fd, (struct sockaddr*)addr, sizeof(*addr)); log_connection(cnx); if (res == -1) { tidy_connection(cnx, fds_r, fds_w); @@ -188,7 +188,7 @@ int is_fd_active(int fd, fd_set* set) * That way, each pair of file descriptor (read from one, write to the other) * is monitored either for read or for write, but never for both. */ -void main_loop(int listen_socket) +void main_loop(int *listen_sockets, int num_addr_listen) { fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */ fd_set readfds, writefds; /* working read and write fd sets */ @@ -203,10 +203,12 @@ void main_loop(int listen_socket) FD_ZERO(&fds_r); FD_ZERO(&fds_w); - FD_SET(listen_socket, &fds_r); - max_fd = listen_socket + 1; - set_nonblock(listen_socket); + for (i = 0; i < num_addr_listen; i++) { + FD_SET(listen_sockets[i], &fds_r); + set_nonblock(listen_sockets[i]); + } + max_fd = listen_sockets[num_addr_listen-1] + 1; cnx_num_alloc = getpagesize() / sizeof(struct connection); @@ -231,16 +233,18 @@ void main_loop(int listen_socket) /* Check main socket for new connections */ - if (FD_ISSET(listen_socket, &readfds)) { - in_socket = accept_new_connection(listen_socket, &cnx, &num_cnx); - num_probing++; + for (i = 0; i < num_addr_listen; i++) { + if (FD_ISSET(listen_sockets[i], &readfds)) { + in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx); + num_probing++; - if (in_socket > 0) { - FD_SET(in_socket, &fds_r); - if (in_socket >= max_fd) - max_fd = in_socket + 1;; + if (in_socket > 0) { + FD_SET(in_socket, &fds_r); + if (in_socket >= max_fd) + max_fd = in_socket + 1;; + } + FD_CLR(listen_sockets[i], &readfds); } - FD_CLR(listen_socket, &readfds); } /* Check all sockets for write activity */ diff --git a/sslh.pod b/sslh.pod index 8f488ca..e9ab29e 100644 --- a/sslh.pod +++ b/sslh.pod @@ -6,7 +6,7 @@ =head1 SYNOPSIS -sslh [ B<-t> I ] [B<-p> I] [B<-l> I] [B<-s> I] [B<-o> I] [B<-u> I] [B<-P> I] [-v] [-i] [-V] [-f] +sslh [ B<-t> I ] [B<-p> I [B<-p> I ...] [B<-l> I] [B<-s> I] [B<-o> I] [B<-u> I] [B<-P> I] [-v] [-i] [-V] [-f] [-n] =head1 DESCRIPTION @@ -57,69 +57,79 @@ defined in F and F. =over 4 -=item B<-t> I +=item B<-t> I, B<--timeout> I Timeout before a connection is considered to be SSH. Default is 2s. -=item B<-p> I +=item B<-p> I, B<--listen> I Interface and port on which to listen, e.g. I, where I is the name of an interface (typically the IP address on which the Internet connection ends up). -Defaults to I<0.0.0.0:443> (listen to port 443 on all -available interfaces). +This can be specified several times to bind B to +several addresses. -=item B<-l> I +=item B<--ssl> I Interface and port on which to forward SSL connection, typically I. -Defaults to I (this assumes you would -configure your B process to listen to port 443). - Note that you can set B to listen on I and B to listen on I: this allows clients inside your network to just connect directly to B. -=item B<-s> I +=item B<--ssh> I -Interface and port on which to forward SSH connection, -defaults to I. +Interface and port on which to forward SSH connections, +typically I. -=item B<-o> I +=item B<--openvpn> I -Interface and port on which to forward OpenVPN connections. -This parameter is optional, and has no default. If not -specified, incoming OpenVPN connections will not be detected -as such and treated the same as SSL. +Interface and port on which to forward OpenVPN connections, +typically I. -=item B<-v> +=item B<--tinc> I + +Interface and port on which to forward tinc connections, +typically I. + +This is experimental. If you use this feature, please report +the results (even if it works!) + +=item B<-v>, B<--verbose> Increase verboseness. +=item B<-n>, B<--numeric> + +Do not attempt to resolve hostnames: logs will contain IP +addresses. This is mostly useful if the system's DNS is slow +and running the I variant, as DNS requests will +hang all connections. + =item B<-V> Prints B version. -=item B<-u> I +=item B<-u> I, B<--user> I Requires to run under the specified username. Defaults to I (which is not perfect -- ideally B should run under its own UID). -=item B<-P> I +=item B<-P> I, B<--pid-file> I Specifies the file in which to write the PID of the main server. Defaults to I. -=item B<-i> +=item B<-i>, B<--inetd> Runs as an I server. Options B<-P> (PID file), B<-p> (listen address), B<-u> (user) are ignored. -=item B<-f> +=item B<-f>, B<--foreground> Runs in foreground. The server will not fork and will remain connected to the terminal. Messages normally sent to B will also be sent