1
0
mirror of https://github.com/moparisthebest/sslh synced 2024-11-23 01:22:19 -05:00
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).
This commit is contained in:
Yves Rutschle 2013-07-10 23:14:15 +02:00
parent a9c9941988
commit ae008179f0
13 changed files with 1052 additions and 490 deletions

View File

@ -1,4 +1,26 @@
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).
v1.9: 02AUG2011 v1.9: 02AUG2011
WARNING: This version does not work with FreeBSD and
derivatives!
WARNING: Options changed, you'll need to update your WARNING: Options changed, you'll need to update your
start-up scripts! Log format changed, you'll need to start-up scripts! Log format changed, you'll need to
update log processing scripts! update log processing scripts!

View File

@ -1,7 +1,8 @@
# Configuration # Configuration
VERSION="v1.9" VERSION="v1.10"
USELIBWRAP= # Use libwrap? USELIBWRAP= # Use libwrap?
COV_TEST= # Perform test coverage?
PREFIX=/usr/local PREFIX=/usr/local
MAN=sslh.8.gz # man page name MAN=sslh.8.gz # man page name
@ -9,19 +10,23 @@ MAN=sslh.8.gz # man page name
# End of configuration -- the rest should take care of # End of configuration -- the rest should take care of
# itself # itself
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
endif
CC = gcc CC = gcc
CFLAGS=-Wall -g CFLAGS=-Wall -g $(CFLAGS_COV)
#LIBS=-lnet #LIBS=-lnet
LIBS= LIBS=
OBJS=common.o OBJS=common.o sslh-main.o
ifneq ($(strip $(USELIBWRAP)),) ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap LIBS:=$(LIBS) -lwrap
CFLAGS:=$(CFLAGS) -DLIBWRAP CFLAGS:=$(CFLAGS) -DLIBWRAP
endif endif
all: sslh $(MAN) all: sslh $(MAN) echosrv
.c.o: *.h .c.o: *.h
$(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -c $< $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -c $<
@ -31,12 +36,14 @@ sslh: $(OBJS) sslh-fork sslh-select
sslh-fork: $(OBJS) sslh-fork.o Makefile common.h sslh-fork: $(OBJS) sslh-fork.o Makefile common.h
$(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS) $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
strip sslh-fork #strip sslh-fork
sslh-select: $(OBJS) sslh-select.o Makefile common.h sslh-select: $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS) $(CC) $(CFLAGS) -D'VERSION=$(VERSION)' -o sslh-select sslh-select.o $(OBJS) $(LIBS)
strip sslh-select #strip sslh-select
echosrv: $(OBJS) echosrv.o
$(CC) $(CFLAGS) -o echosrv echosrv.o common.o $(LIBS)
$(MAN): sslh.pod Makefile $(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN) pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
@ -58,7 +65,7 @@ uninstall:
update-rc.d sslh remove update-rc.d sslh remove
clean: clean:
rm -f sslh-fork sslh-select $(MAN) *.o rm -f sslh-fork sslh-select echosrv $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags: tags:
ctags -T *.[ch] ctags -T *.[ch]

6
README
View File

@ -1,8 +1,8 @@
===== sslh -- A ssl/ssh multiplexer. ===== ===== sslh -- A ssl/ssh multiplexer. =====
sslh accepts HTTPS, SSH and OpenVPN connections on the same sslh accepts HTTPS, SSH, OpenVPN, tinc and XMPP connections
port. This makes it possible to connect to an SSH server or on the same port. This makes it possible to connect to any
an OpenVPN on port 443 (e.g. from inside a corporate of these servers on port 443 (e.g. from inside a corporate
firewall, which almost never block port 443) while still firewall, which almost never block port 443) while still
serving HTTPS on that port. serving HTTPS on that port.

View File

@ -13,8 +13,7 @@ with launchctl or simply reboot.
----BEGIN FILE---- ----BEGIN FILE----
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>Disabled</key> <key>Disabled</key>
@ -32,9 +31,9 @@ with launchctl or simply reboot.
<string>nobody</string> <string>nobody</string>
<string>-p</string> <string>-p</string>
<string>0.0.0.0:443</string> <string>0.0.0.0:443</string>
<string>-s</string> <string>--ssh</string>
<string>localhost:22</string> <string>localhost:22</string>
<string>-l</string> <string>--ssl</string>
<string>localhost:443</string> <string>localhost:443</string>
</array> </array>
<key>QueueDirectories</key> <key>QueueDirectories</key>
@ -49,6 +48,7 @@ with launchctl or simply reboot.
<array/> <array/>
</dict> </dict>
</plist> </plist>
----END FILE---- ----END FILE----

354
common.c
View File

@ -34,42 +34,33 @@
int is_ssh_protocol(const char *p, int len); int is_ssh_protocol(const char *p, int len);
int is_openvpn_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_tinc_protocol(const char *p, int len);
int is_xmpp_protocol(const char *p, int len);
int is_true(const char *p, int len) { return 1; } int is_true(const char *p, int len) { return 1; }
/* Table of all the protocols we know how to connect to.
*
* The first protocol in the table is where we connect in case of timeout
* (client didn't speak: typically this is SSH.)
*
* The last protocol in the table is where we connect if client spoke but we
* couldn't probe what it's saying.
*/
struct proto protocols[] = { struct proto protocols[] = {
/* affected description service saddr probe */ /* affected description service saddr probe */
{ 0, "ssh", "sshd", {0}, is_ssh_protocol }, { 0, "ssh", "sshd", {0}, is_ssh_protocol },
{ 0, "openvpn", NULL, {0}, is_openvpn_protocol }, { 0, "openvpn", NULL, {0}, is_openvpn_protocol },
{ 0, "tinc", NULL, {0}, is_tinc_protocol }, { 0, "tinc", NULL, {0}, is_tinc_protocol },
{ 0, "xmpp", NULL, {0}, is_xmpp_protocol },
/* probe for SSL always successes: it's the default, and must be tried last /* 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 }
}; };
int num_known_protocols = ARRAY_SIZE(protocols);
const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f]\n"
"\t[-t <timeout>] [-P <pidfile>] -u <username> -p <add> [-p <addr> ...] \n" \
"\t[--ssh <addr>] [--ssl <addr>] [--openvpn <addr>] [--tinc <addr>]\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 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" \
"";
/* /*
* Settings that depend on the command line. * Settings that depend on the command line. They're set in main(), but also
* They're set in main(), but also used in other places, and it'd be * used in other places in common.c, and it'd be heavy-handed to pass it all as
* heavy-handed to pass it all as parameters * parameters
*/ */
int verbose = 0; int verbose = 0;
int probing_timeout = 2; int probing_timeout = 2;
@ -78,8 +69,7 @@ int foreground = 0;
int numeric = 0; int numeric = 0;
char *user_name, *pid_file; char *user_name, *pid_file;
struct sockaddr_storage *addr_listen = NULL; /* what addresses do we listen to? */ struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
int num_addr_listen = 0; /* How many addresses do we listen to? */
#ifdef LIBWRAP #ifdef LIBWRAP
#include <tcpd.h> #include <tcpd.h>
@ -88,13 +78,13 @@ int allow_severity =0, deny_severity = 0;
/* check result and die, printing the offending address and error */ /* check result and die, printing the offending address and error */
void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall) void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
{ {
char buf[64]; char buf[NI_MAXHOST];
if (res == -1) { if (res == -1) {
fprintf(stderr, "%s:%s: %s\n", fprintf(stderr, "%s:%s: %s\n",
sprintaddr(buf, sizeof(buf), sock), sprintaddr(buf, sizeof(buf), addr),
syscall, syscall,
strerror(errno)); strerror(errno));
exit(1); exit(1);
@ -103,31 +93,77 @@ void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall)
/* Starts listening sockets on specified addresses. /* Starts listening sockets on specified addresses.
* IN: addr[], num_addr * IN: addr[], num_addr
* OUT: sockfd[] * OUT: *sockfd[] pointer to newly-allocated array of file descriptors
* Bound file descriptors are returned in alread-allocated *sockfd pointer * Returns number of addresses bound
Returns file descriptor * Bound file descriptors are returned in newly-allocated *sockfd pointer
*/ */
void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr) int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
{ {
struct sockaddr_storage *saddr; struct sockaddr_storage *saddr;
struct addrinfo *addr;
int i, res, reuse; int i, res, reuse;
int num_addr = 0;
for (i = 0; i < num_addr; i++) { for (addr = addr_list; addr; addr = addr->ai_next)
saddr = &addr[i]; num_addr++;
sockfd[i] = socket(saddr->ss_family, SOCK_STREAM, 0); if (verbose)
check_res_dumpdie(sockfd[i], saddr, "socket"); fprintf(stderr, "listening to %d addresses\n", num_addr);
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
if (!addr) {
fprintf(stderr, "FATAL: Inconsistent listen number. This should not happen.\n");
exit(1);
}
saddr = (struct sockaddr_storage*)addr->ai_addr;
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dumpdie((*sockfd)[i], addr, "socket");
reuse = 1; reuse = 1;
res = setsockopt(sockfd[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
check_res_dumpdie(res, saddr, "setsockopt"); check_res_dumpdie(res, addr, "setsockopt");
res = bind (sockfd[i], (struct sockaddr*)saddr, sizeof(*saddr)); res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
check_res_dumpdie(res, saddr, "bind"); check_res_dumpdie(res, addr, "bind");
res = listen ((*sockfd)[i], 50);
check_res_dumpdie(res, addr, "listen");
res = listen (sockfd[i], 50);
check_res_dumpdie(res, saddr, "listen");
} }
return num_addr;
}
/* Connect to first address that works and returns a file descriptor, or -1 if
* none work. cnx_name points to the name of the service (for logging) */
int connect_addr(struct addrinfo *addr, char* cnx_name)
{
struct addrinfo *a;
char buf[NI_MAXHOST];
int fd, res;
for (a = addr; a; a = a->ai_next) {
if (verbose)
fprintf(stderr, "connecting to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a),
a->ai_addr->sa_family, a->ai_addrlen);
fd = socket(a->ai_family, SOCK_STREAM, 0);
if (fd == -1) {
log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx_name, strerror(errno));
} else {
res = connect(fd, a->ai_addr, a->ai_addrlen);
if (res == -1) {
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
cnx_name, strerror(errno));
} else {
return fd;
}
}
}
return -1;
} }
/* Store some data to write to the queue later */ /* Store some data to write to the queue later */
@ -290,9 +326,19 @@ int is_openvpn_protocol (const char*p,int len)
* */ * */
int is_tinc_protocol( const char *p, int len) int is_tinc_protocol( const char *p, int len)
{ {
return !strncmp(p, "0 ", len); return !strncmp(p, "0 ", 2);
} }
/* Is the buffer the beginning of a jabber (XMPP) connections?
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
* clients, just checking first frame containing "jabber" in xml entity)
* */
int is_xmpp_protocol( const char *p, int len)
{
return strstr(p, "jabber") ? 1 : 0;
}
/* /*
* Read the beginning of data coming from the client connection and check if * Read the beginning of data coming from the client connection and check if
* it's a known protocol. Then leave the data on the defered * it's a known protocol. Then leave the data on the defered
@ -335,23 +381,33 @@ T_PROTO_ID probe_client_protocol(struct connection *cnx)
} }
/* returns a string that prints the IP and port of the sockaddr */ /* returns a string that prints the IP and port of the sockaddr */
char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s) char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
{ {
char host[NI_MAXHOST], serv[NI_MAXSERV]; char host[NI_MAXHOST], serv[NI_MAXSERV];
int res;
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
if (res) {
fprintf(stderr, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
exit(1);
}
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); snprintf(buf, size, "%s:%s", host, serv);
return buf; return buf;
} }
/* turns a "hostname:port" string into a struct sockaddr; /* turns a "hostname:port" string into a list of struct addrinfo;
sock: socket address to which to copy the addr out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done
fullname: input string -- it gets clobbered fullname: input string -- it gets clobbered
*/ */
void resolve_name(struct sockaddr_storage *sock, char* fullname) void resolve_name(struct addrinfo **out, char* fullname)
{ {
struct addrinfo *addr, hint; struct addrinfo hint;
char *serv, *host; char *serv, *host;
int res; int res;
@ -371,17 +427,13 @@ void resolve_name(struct sockaddr_storage *sock, char* fullname)
hint.ai_family = PF_UNSPEC; hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM; hint.ai_socktype = SOCK_STREAM;
res = getaddrinfo(host, serv, &hint, &addr); res = getaddrinfo(host, serv, &hint, out);
if (res) { if (res) {
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname); fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
if (res == EAI_SERVICE) if (res == EAI_SERVICE)
fprintf(stderr, "(Check you have specified all ports)\n"); fprintf(stderr, "(Check you have specified all ports)\n");
exit(1); exit(4);
} }
memcpy(sock, addr->ai_addr, sizeof(*sock));
freeaddrinfo(addr);
} }
/* Log to syslog, and to stderr if foreground */ /* Log to syslog, and to stderr if foreground */
@ -402,41 +454,40 @@ void log_message(int type, char* msg, ...)
/* syslogs who connected to where */ /* syslogs who connected to where */
void log_connection(struct connection *cnx) void log_connection(struct connection *cnx)
{ {
struct sockaddr_storage peeraddr; /* Who's connecting to sshd */ struct addrinfo addr;
struct sockaddr_storage listenaddr; /* Where is it connecting to */ struct sockaddr_storage ss;
struct sockaddr_storage forwardfromaddr; /* Where is it forwarded from */
struct sockaddr_storage targetaddr; /* Where is it forwarded to */
socklen_t size = sizeof(peeraddr);
#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1) #define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
char buf[MAX_NAMELENGTH], buf2[MAX_NAMELENGTH], buf3[MAX_NAMELENGTH], buf4[MAX_NAMELENGTH]; char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
int res; int res;
memset(&peeraddr, 0, sizeof(peeraddr)); addr.ai_addr = (struct sockaddr*)&ss;
memset(&listenaddr, 0, sizeof(listenaddr)); addr.ai_addrlen = sizeof(ss);
memset(&forwardfromaddr, 0, sizeof(forwardfromaddr));
memset(&targetaddr, 0, sizeof(targetaddr));
res = getpeername(cnx->q[0].fd, (struct sockaddr*)&peeraddr, &size); res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; /* that should never happen, right? */ if (res == -1) return; /* that should never happen, right? */
sprintaddr(peer, sizeof(peer), &addr);
size = sizeof(listenaddr); addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[0].fd, (struct sockaddr*)&listenaddr, &size); res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; if (res == -1) return;
sprintaddr(service, sizeof(service), &addr);
size = sizeof(targetaddr); addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[1].fd, (struct sockaddr*)&targetaddr, &size); res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; if (res == -1) return;
sprintaddr(target, sizeof(target), &addr);
size = sizeof(forwardfromaddr); addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[1].fd, (struct sockaddr*)&forwardfromaddr, &size); res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; if (res == -1) return;
sprintaddr(local, sizeof(local), &addr);
log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n", log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n",
sprintaddr(buf, sizeof(buf), &peeraddr), peer,
sprintaddr(buf2, sizeof(buf2), &listenaddr), service,
sprintaddr(buf3, sizeof(buf3), &forwardfromaddr), local,
sprintaddr(buf4, sizeof(buf4), &targetaddr)); target);
} }
@ -498,6 +549,14 @@ void setup_signals(void)
action.sa_flags = SA_NOCLDWAIT; action.sa_flags = SA_NOCLDWAIT;
res = sigaction(SIGCHLD, &action, NULL); res = sigaction(SIGCHLD, &action, NULL);
CHECK_RES_DIE(res, "sigaction"); CHECK_RES_DIE(res, "sigaction");
/* Set SIGTERM to exit. For some reason if it's not set explicitely,
* coverage information is lost when killing the process */
memset(&action, 0, sizeof(action));
action.sa_handler = exit;
res = sigaction(SIGTERM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
} }
/* Open syslog connection with appropriate banner; /* Open syslog connection with appropriate banner;
@ -521,7 +580,7 @@ void drop_privileges(char* user_name)
struct passwd *pw = getpwnam(user_name); struct passwd *pw = getpwnam(user_name);
if (!pw) { if (!pw) {
fprintf(stderr, "%s: not found\n", user_name); fprintf(stderr, "%s: not found\n", user_name);
exit(1); exit(2);
} }
if (verbose) if (verbose)
fprintf(stderr, "turning into %s\n", user_name); fprintf(stderr, "turning into %s\n", user_name);
@ -540,7 +599,7 @@ void write_pid_file(char* pidfile)
f = fopen(pidfile, "w"); f = fopen(pidfile, "w");
if (!f) { if (!f) {
perror(pidfile); perror(pidfile);
exit(1); exit(3);
} }
fprintf(f, "%d\n", getpid()); fprintf(f, "%d\n", getpid());
@ -549,20 +608,23 @@ void write_pid_file(char* pidfile)
void printsettings(void) void printsettings(void)
{ {
char buf[64]; char buf[NI_MAXHOST];
struct addrinfo *a;
int i; int i;
for (i = 0; i < ARRAY_SIZE(protocols); i++) { for (i = 0; i < ARRAY_SIZE(protocols); i++) {
if (protocols[i].affected) if (protocols[i].affected)
fprintf(stderr, fprintf(stderr,
"%s addr: %s. libwrap service: %s\n", "%s addr: %s. libwrap service: %s family %d %d\n",
protocols[i].description, protocols[i].description,
sprintaddr(buf, sizeof(buf), &protocols[i].saddr), sprintaddr(buf, sizeof(buf), &protocols[i].saddr),
protocols[i].service); protocols[i].service,
protocols[i].saddr.ai_family,
protocols[i].saddr.ai_addr->sa_family);
} }
fprintf(stderr, "listening on:\n"); fprintf(stderr, "listening on:\n");
for (i = 0; i < num_addr_listen; i++) { for (a = addr_listen; a; a = a->ai_next) {
fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), &addr_listen[i])); fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a));
} }
fprintf(stderr, "timeout to ssh: %d\n", probing_timeout); fprintf(stderr, "timeout to ssh: %d\n", probing_timeout);
} }
@ -574,7 +636,6 @@ void printsettings(void)
* prot: array of protocols * prot: array of protocols
* n_prots: number of protocols in *prot * 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) void append_protocols(struct option *options, int n_opts, struct proto *prot, int n_prots)
{ {
int o, p; int o, p;
@ -587,126 +648,3 @@ void append_protocols(struct option *options, int n_opts, struct proto *prot, in
} }
} }
void parse_cmdline(int argc, char* argv[])
{
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;
}
switch (c) {
case 't':
probing_timeout = atoi(optarg);
break;
case 'p':
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':
printf("%s %s\n", server_type, VERSION);
exit(0);
case 'u':
user_name = optarg;
break;
case 'P':
pid_file = optarg;
break;
default:
fprintf(stderr, USAGE_STRING);
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[])
{
extern char *optarg;
extern int optind;
int res;
int *listen_sockets;
/* Init defaults */
pid_file = "/var/run/sslh.pid";
user_name = "nobody";
foreground = 0;
parse_cmdline(argc, argv);
if (inetd)
{
verbose = 0;
start_shoveler(0);
exit(0);
}
if (verbose)
printsettings();
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 */
setup_signals();
write_pid_file(pid_file);
drop_privileges(user_name);
/* New session -- become group leader */
if (getuid() == 0) {
res = setsid();
CHECK_RES_DIE(res, "setsid: already process leader");
}
/* Open syslog connection */
setup_syslog(argv[0]);
main_loop(listen_sockets, num_addr_listen);
return 0;
}

View File

@ -16,6 +16,7 @@
#include <syslog.h> #include <syslog.h>
#include <libgen.h> #include <libgen.h>
#include <time.h> #include <time.h>
#include <getopt.h>
#ifndef VERSION #ifndef VERSION
#define VERSION "v?" #define VERSION "v?"
@ -46,27 +47,23 @@ enum connection_state {
ST_SHOVELING /* Connexion is established */ ST_SHOVELING /* Connexion is established */
}; };
typedef int T_PROTO_ID; /* Index into protocols[] array */
/* Different types of protocols we support.
* These must match the order of the protocols[] array in common.c */
typedef enum protocol_type {
PROT_SSH,
PROT_OPENVPN,
PROT_TINC,
PROT_SSL,
} T_PROTO_ID;
/* For each protocol we need: */ /* For each protocol we need: */
struct proto { struct proto {
int affected; /* are we actually using it? */ int affected; /* are we actually using it? */
char* description; /* a string that says what it is (for logging and command-line parsing) */ char* description; /* a string that says what it is (for logging and command-line parsing) */
char* service; /* service name to do libwrap checks */ char* service; /* service name to do libwrap checks */
struct sockaddr_storage saddr; /* where to switch that protocol */ struct addrinfo saddr; /* where to switch that protocol */
int (*probe)(const char*, int); /* function to probe that protocol */ int (*probe)(const char*, int); /* function to probe that protocol */
}; };
/* A table in common.c contains all the known protocols */ /* A table in common.c contains all the known protocols */
extern struct proto protocols[]; extern struct proto protocols[];
extern int num_known_protocols;
/* this is used to pass protocols through the command-line parameter parsing */
#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
/* A 'queue' is composed of a file descriptor (which can be read from or /* A 'queue' is composed of a file descriptor (which can be read from or
* written to), and a queue for defered write data */ * written to), and a queue for defered write data */
@ -94,10 +91,10 @@ struct connection {
/* common.c */ /* common.c */
void init_cnx(struct connection *cnx); void init_cnx(struct connection *cnx);
void start_listen_sockets(int sockfd[], struct sockaddr_storage addr[], int num_addr); int connect_addr(struct addrinfo *addr, char* cnx_name);
int fd2fd(struct queue *target, struct queue *from); int fd2fd(struct queue *target, struct queue *from);
char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s); char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
void resolve_name(struct sockaddr_storage *sock, char* fullname) ; void resolve_name(struct addrinfo **out, char* fullname);
T_PROTO_ID probe_client_protocol(struct connection *cnx); T_PROTO_ID probe_client_protocol(struct connection *cnx);
void log_connection(struct connection *cnx); void log_connection(struct connection *cnx);
int check_access_rights(int in_socket, char* service); int check_access_rights(int in_socket, char* service);
@ -110,12 +107,15 @@ void parse_cmdline(int argc, char* argv[]);
void log_message(int type, char* msg, ...); void log_message(int type, char* msg, ...);
void dump_connection(struct connection *cnx); void dump_connection(struct connection *cnx);
void append_protocols(struct option *options, int n_opts, struct proto *prot, int n_prots);
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
int defer_write(struct queue *q, void* data, int data_size); int defer_write(struct queue *q, void* data, int data_size);
int flush_defered(struct queue *q); int flush_defered(struct queue *q);
extern int probing_timeout, verbose, inetd; extern int probing_timeout, verbose, inetd, foreground, numeric;
extern struct sockaddr_storage *addr_listen, addr_ssl, addr_ssh, addr_openvpn; extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING; extern const char* USAGE_STRING;
extern char* user_name, *pid_file; extern char* user_name, *pid_file;
extern const char* server_type; extern const char* server_type;
@ -124,5 +124,3 @@ extern const char* server_type;
void start_shoveler(int); void start_shoveler(int);
void main_loop(int *listen_sockets, int num_addr_listen); void main_loop(int *listen_sockets, int num_addr_listen);

166
echosrv.c Executable file
View File

@ -0,0 +1,166 @@
/* echosrv: a simple line echo server with optional prefix adding.
*
* echsrv --listen localhost6:1234 --prefix "ssl: "
*
* This will bind to 1234, and echo every line pre-pending "ssl: ". This is
* used for testing: we create several such servers with different prefixes,
* then we connect test clients that can then check they get the proper data
* back (thus testing that shoveling works both ways) with the correct prefix
* (thus testing it connected to the expected service).
* **/
#define _GNU_SOURCE
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include "common.h"
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
const char* USAGE_STRING =
"echosrv\n" \
"usage:\n" \
"\techosrv [-v] --listen <address:port> [--prefix <prefix>]\n"
"-v: verbose\n" \
"--listen: address to listen on. Can be specified multiple times.\n" \
"--prefix: add specified prefix before every line echoed.\n"
"";
const char* server_type = "echsrv"; /* keep setup_syslog happy */
/*
* Settings that depend on the command line.
*/
char* prefix = "";
int port;
void parse_cmdline(int argc, char* argv[])
{
int c;
struct option options[] = {
{ "verbose", no_argument, &verbose, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "listen", required_argument, 0, 'l' },
{ "prefix", required_argument, 0, 'p' },
};
struct addrinfo **a;
while ((c = getopt_long_only(argc, argv, "l:p:", options, NULL)) != -1) {
if (c == 0) continue;
switch (c) {
case 'l':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
/* append the specified addresses */
resolve_name(a, optarg);
break;
case 'p':
prefix = optarg;
break;
default:
fprintf(stderr, "%s", USAGE_STRING);
exit(2);
}
}
if (!addr_listen) {
fprintf(stderr, "No listening port specified\n");
exit(1);
}
}
void start_echo(int fd)
{
fd_set fds;
int res;
char buffer[1 << 20];
char* ret;
FILE *socket;
FD_ZERO(&fds);
socket = fdopen(fd, "r+");
if (!socket) {
CHECK_RES_DIE(-1, "fdopen");
}
while (1) {
ret = fgets(buffer, sizeof(buffer), socket);
if (!ret) {
fprintf(stderr, "%s", strerror(ferror(socket)));
return;
}
res = fprintf(socket, "%s%s", prefix, buffer);
if (res < 0) {
fprintf(stderr, "%s", strerror(ferror(socket)));
return;
}
fflush(socket);
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
{
int in_socket, i;
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_sockets[i]);
start_echo(in_socket);
exit(0);
}
close(in_socket);
}
}
}
wait(NULL);
}
int main(int argc, char *argv[])
{
extern char *optarg;
extern int optind;
int num_addr_listen;
int *listen_sockets;
parse_cmdline(argc, argv);
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
main_loop(listen_sockets, num_addr_listen);
return 0;
}

View File

@ -1,7 +1,7 @@
/* /*
Reimplementation of sslh in C Reimplementation of sslh in C
# Copyright (C) 2007-2008 Yves Rutschle # Copyright (C) 2007-2011 Yves Rutschle
# #
# This program is free software; you can redistribute it # This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public # and/or modify it under the terms of the GNU General Public
@ -68,10 +68,9 @@ void start_shoveler(int in_socket)
{ {
fd_set fds; fd_set fds;
struct timeval tv; struct timeval tv;
struct sockaddr_storage *saddr; struct addrinfo *saddr;
int res; int res;
int out_socket; int out_socket;
char *target;
struct connection cnx; struct connection cnx;
T_PROTO_ID prot; T_PROTO_ID prot;
@ -92,22 +91,18 @@ void start_shoveler(int in_socket)
prot = probe_client_protocol(&cnx); prot = probe_client_protocol(&cnx);
} else { } else {
/* Timed out: it's necessarily SSH */ /* Timed out: it's necessarily SSH */
prot = PROT_SSH; prot = 0;
} }
saddr = &protocols[prot].saddr; saddr = &protocols[prot].saddr;
target = protocols[prot].description;
if (protocols[prot].service && if (protocols[prot].service &&
check_access_rights(in_socket, protocols[prot].service)) { check_access_rights(in_socket, protocols[prot].service)) {
exit(0); exit(0);
} }
/* Connect the target socket */ /* Connect the target socket */
out_socket = socket(saddr->ss_family, SOCK_STREAM, 0); out_socket = connect_addr(saddr, protocols[prot].description);
res = connect(out_socket, (struct sockaddr*)saddr, sizeof(addr_ssl)); CHECK_RES_DIE(out_socket, "connect");
CHECK_RES_DIE(res, "connect");
if (verbose)
fprintf(stderr, "connected to something\n");
cnx.q[1].fd = out_socket; cnx.q[1].fd = out_socket;
@ -126,12 +121,31 @@ void start_shoveler(int in_socket)
exit(0); exit(0);
} }
void main_loop(int *listen_sockets, int num_addr_listen) static int *listener_pid;
{ static int listener_pid_number = 0;
int in_socket, i;
void stop_listeners(int sig)
{
int i;
for (i = 0; i < listener_pid_number; i++) {
kill(listener_pid[i], sig);
}
}
void main_loop(int listen_sockets[], int num_addr_listen)
{
int in_socket, i, res;
struct sigaction action;
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
/* Start one process for each listening address */
for (i = 0; i < num_addr_listen; i++) { for (i = 0; i < num_addr_listen; i++) {
if (!fork()) { if (!(listener_pid[i] = fork())) {
/* Listening process just accepts a connection, forks, and goes
* back to listening */
while (1) while (1)
{ {
in_socket = accept(listen_sockets[i], 0, 0); in_socket = accept(listen_sockets[i], 0, 0);
@ -147,6 +161,17 @@ void main_loop(int *listen_sockets, int num_addr_listen)
} }
} }
} }
/* Set SIGTERM to "stop_listeners" which further kills all listener
* processes. Note this won't kill processes that listeners forked, which
* means active connections remain active. */
memset(&action, 0, sizeof(action));
action.sa_handler = stop_listeners;
res = sigaction(SIGTERM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
listener_pid_number = num_addr_listen;
wait(NULL);
} }
/* The actual main is in common.c: it's the same for both version of /* The actual main is in common.c: it's the same for both version of

196
sslh-main.c Normal file
View File

@ -0,0 +1,196 @@
/*
# main: processing of command line options and start the main loop.
#
# Copyright (C) 2007-2011 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include "common.h"
const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f] [-n]\n"
"\t[-t <timeout>] [-P <pidfile>] -u <username> -p <add> [-p <addr> ...] \n" \
"%s\n\n" \
"-v: verbose\n" \
"-V: version\n" \
"-f: foreground\n" \
"-n: numeric output\n" \
"-t: timeout before connecting to SSH.\n" \
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
"-P: PID file. Default: /var/run/sslh.pid.\n" \
"-i: Run as a inetd service.\n" \
"";
void print_usage(void)
{
int i;
char *prots = "";
for (i = 0; i < num_known_protocols; i++)
asprintf(&prots, "%s\t[--%s <addr>]\n", prots, protocols[i].description);
fprintf(stderr, USAGE_STRING, prots);
}
void parse_cmdline(int argc, char* argv[])
{
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) + num_known_protocols + 1];
struct addrinfo *addr, **a;
memset(all_options, 0, sizeof(all_options));
memcpy(all_options, const_options, sizeof(const_options));
append_protocols(all_options, ARRAY_SIZE(const_options), protocols, num_known_protocols);
while ((c = getopt_long_only(argc, argv, "t:T:p:VP:", all_options, NULL)) != -1) {
if (c == 0) continue;
if (c >= PROT_SHIFT) {
affected++;
protocols[c - PROT_SHIFT].affected = 1;
resolve_name(&addr, optarg);
protocols[c - PROT_SHIFT].saddr= *addr;
continue;
}
switch (c) {
case 't':
probing_timeout = atoi(optarg);
break;
case 'p':
/* find the end of the listen list */
for (a = &addr_listen; *a; a = &((*a)->ai_next));
/* append the specified addresses */
resolve_name(a, optarg);
break;
case 'V':
printf("%s %s\n", server_type, VERSION);
exit(0);
case 'u':
user_name = optarg;
break;
case 'P':
pid_file = optarg;
break;
default:
print_usage();
exit(2);
}
}
if (!affected) {
fprintf(stderr, "At least one target protocol must be specified.\n");
exit(2);
}
if (!addr_listen) {
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
}
int main(int argc, char *argv[])
{
extern char *optarg;
extern int optind;
int res, num_addr_listen;
int *listen_sockets;
/* Init defaults */
pid_file = "/var/run/sslh.pid";
user_name = "nobody";
foreground = 0;
parse_cmdline(argc, argv);
if (inetd)
{
verbose = 0;
start_shoveler(0);
exit(0);
}
if (verbose)
printsettings();
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
if (!foreground)
if (fork() > 0) exit(0); /* Detach */
setup_signals();
drop_privileges(user_name);
/* New session -- become group leader */
if (getuid() == 0) {
res = setsid();
CHECK_RES_DIE(res, "setsid: already process leader");
}
write_pid_file(pid_file);
/* Open syslog connection */
setup_syslog(argv[0]);
main_loop(listen_sockets, num_addr_listen);
return 0;
}

View File

@ -30,7 +30,8 @@ const char* server_type = "sslh-select";
* and then every time we get too many simultaneous connections: e.g. start * and then every time we get too many simultaneous connections: e.g. start
* with 100 slots, then if we get more than 100 connections allocate another * with 100 slots, then if we get more than 100 connections allocate another
* 100 slots, and so on). We never free up connection structures. We try to * 100 slots, and so on). We never free up connection structures. We try to
* allocate as many structures at once as will fit in one page. * allocate as many structures at once as will fit in one page (which is 102
* in sslh 1.9 on Linux on x86)
*/ */
static long cnx_num_alloc; static long cnx_num_alloc;
@ -55,6 +56,7 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
int i; int i;
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
if (cnx->q[i].fd != -1) {
if (verbose) if (verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd); fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
@ -64,6 +66,7 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
if (cnx->q[i].defered_data) if (cnx->q[i].defered_data)
free(cnx->q[i].defered_data); free(cnx->q[i].defered_data);
} }
}
init_cnx(cnx); init_cnx(cnx);
return 0; return 0;
} }
@ -110,22 +113,17 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_
return in_socket; return in_socket;
} }
/* Connect queue 1 of connection to SSL; returns new file descriptor */ /* Connect queue 1 of connection to SSL; returns new file descriptor */
int connect_queue(struct connection *cnx, struct sockaddr_storage *addr, int connect_queue(struct connection *cnx, struct addrinfo *addr,
char* cnx_name, char* cnx_name,
fd_set *fds_r, fd_set *fds_w) fd_set *fds_r, fd_set *fds_w)
{ {
struct queue *q = &cnx->q[1]; struct queue *q = &cnx->q[1];
int res;
q->fd = socket(addr->ss_family, SOCK_STREAM, 0); q->fd = connect_addr(addr, cnx_name);
res = connect(q->fd, (struct sockaddr*)addr, sizeof(*addr)); if (q->fd != -1) {
log_connection(cnx); log_connection(cnx);
if (res == -1) {
tidy_connection(cnx, fds_r, fds_w);
log_message(LOG_ERR, "forward to %s failed\n", cnx_name);
return -1;
} else {
set_nonblock(q->fd); set_nonblock(q->fd);
flush_defered(q); flush_defered(q);
if (q->defered_data) { if (q->defered_data) {
@ -134,6 +132,9 @@ int connect_queue(struct connection *cnx, struct sockaddr_storage *addr,
FD_SET(q->fd, fds_r); FD_SET(q->fd, fds_r);
} }
return q->fd; return q->fd;
} else {
tidy_connection(cnx, fds_r, fds_w);
return -1;
} }
} }
@ -188,7 +189,7 @@ int is_fd_active(int fd, fd_set* set)
* That way, each pair of file descriptor (read from one, write to the other) * 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. * is monitored either for read or for write, but never for both.
*/ */
void main_loop(int *listen_sockets, int num_addr_listen) 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 fds_r, fds_w; /* reference fd sets (used to init the next 2) */
fd_set readfds, writefds; /* working read and write fd sets */ fd_set readfds, writefds; /* working read and write fd sets */
@ -292,7 +293,7 @@ void main_loop(int *listen_sockets, int num_addr_listen)
/* If timed out it's SSH, otherwise the client sent /* If timed out it's SSH, otherwise the client sent
* data so probe the protocol */ * data so probe the protocol */
if ((cnx[i].probe_timeout < time(NULL))) { if ((cnx[i].probe_timeout < time(NULL))) {
prot = PROT_SSH; prot = 0;
} else { } else {
prot = probe_client_protocol(&cnx[i]); prot = probe_client_protocol(&cnx[i]);
} }

View File

@ -6,15 +6,16 @@
=head1 SYNOPSIS =head1 SYNOPSIS
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] 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]
=head1 DESCRIPTION =head1 DESCRIPTION
B<sslh> accepts HTTPS, SSH and OpenVPN connections on the B<sslh> accepts HTTPS, SSH, OpenVPN, tinc and XMPP connections
same port. This makes it possible to connect to an SSH on the same port. This makes it possible to connect to any
server or an OpenVPN on port 443 (e.g. from inside a of these servers on port 443 (e.g. from inside a corporate
corporate firewall, which almost never block port 443) while firewall, which almost never block port 443) while still
still serving HTTPS on that port. serving HTTPS on that port.
The idea is to have B<sslh> listen to the external 443 port, The idea is to have B<sslh> listen to the external 443 port,
accept the incoming connections, work out what type of accept the incoming connections, work out what type of
@ -27,7 +28,9 @@ The protocol detection is made based on the first bytes sent
by the client: SSH connections start by identifying each by the client: SSH connections start by identifying each
other's versions using clear text "SSH-2.0" strings (or other's versions using clear text "SSH-2.0" strings (or
equivalent version strings). This is defined in RFC4253, equivalent version strings). This is defined in RFC4253,
4.2. Meanwhile, OpenVPN clients start with 0x00 0x0D 0x38. 4.2. Meanwhile, OpenVPN clients start with 0x00 0x0D 0x38,
tinc clients start with "0 ", and XMPP client start with a
packet containing "jabber".
Additionally, two kind of SSH clients exist: the client Additionally, two kind of SSH clients exist: the client
waits for the server to send its version string ("Shy" waits for the server to send its version string ("Shy"
@ -90,6 +93,11 @@ typically I<localhost:22>.
Interface and port on which to forward OpenVPN connections, Interface and port on which to forward OpenVPN connections,
typically I<localhost:1194>. typically I<localhost:1194>.
=item B<--xmpp> I<target address>
Interface and port on which to forward XMPP connections,
typically I<localhost:5222>.
=item B<--tinc> I<target address> =item B<--tinc> I<target address>
Interface and port on which to forward tinc connections, Interface and port on which to forward tinc connections,

331
t
View File

@ -2,25 +2,17 @@
# Test script for sslh # Test script for sslh
# The principle is to create two listening sockets which
# will act as the ssh and ssl servers, and then perform a
# number of connections in various combinations to check
# that the server behaves properly.
use strict; use strict;
use IO::Socket::INET; use IO::Socket::INET6;
use Test::More qw/no_plan/; use Test::More qw/no_plan/;
# We use ports 9000, 9001 and 9002 -- hope that won't clash # We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything... # with anything...
my $ssh_port = 9000; my $ssh_address = "ip6-localhost:9000";
my $ssl_port = 9001; my $ssl_address = "ip6-localhost:9001";
my $sslh_port = 9002; my $sslh_port = 9002;
my $pidfile = "/tmp/sslh.pid"; my $no_listen = 9003; # Port on which no-one listens
my $pidfile = "/tmp/sslh_test.pid";
# How many connections will be created during the last test
my $NUM_SSL_CNX = 20;
my $NUM_SSH_CNX = 20;
# Which tests do we run # Which tests do we run
my $SSL_CNX = 1; my $SSL_CNX = 1;
@ -29,76 +21,90 @@ my $SSH_BOLD_CNX = 1;
my $SSL_MIX_SSH = 1; my $SSL_MIX_SSH = 1;
my $SSH_MIX_SSL = 1; my $SSH_MIX_SSL = 1;
my $BIG_MSG = 1; my $BIG_MSG = 1;
my $MANY_CNX = 1; my $STALL_CNX = 1;
# the Listen parameter needs to be bigger than the max number of connexions # Robustness tests. These are mostly to achieve full test
# we'll make during the last test (we open a bunch of SSH connexions, and # coverage, but do not necessarily result in an actual test
# accept them all at once afterwards) # (e.g. some tests need to be run with valgrind to check all
my $ssh_listen = new IO::Socket::INET(LocalHost=> "localhost:$ssh_port", Blocking => 1, Reuse => 1, Listen => $NUM_SSH_CNX + 1); # memory management code).
die "error1: $!\n" unless $ssh_listen; my $RB_CNX_NOSERVER = 1;
my $RB_PARAM_NOHOST = 1;
my $RB_WRONG_USERNAME = 1;
my $RB_OPEN_PID_FILE = 1;
my $RB_BIND_ADDRESS = 1;
my $RB_RESOLVE_ADDRESS = 1;
my $ssl_listen = new IO::Socket::INET(LocalHost=> "localhost:$ssl_port", Blocking => 1, Reuse => 1, Listen => $NUM_SSL_CNX + 1); `lcov --directory . --zerocounters`;
die "error2: $!\n" unless $ssl_listen;
my ($ssh_pid, $ssl_pid);
if (!($ssh_pid = fork)) {
exec "./echosrv --listen $ssh_address --prefix 'ssh: '";
}
if (!($ssl_pid = fork)) {
exec "./echosrv --listen $ssl_address --prefix 'ssl: '";
}
my @binaries = ('sslh-select', 'sslh-fork');
for my $binary (@binaries) {
warn "Testing $binary\n";
# Start sslh with the right plumbing # Start sslh with the right plumbing
my $sslh_pid; my $sslh_pid;
if (!($sslh_pid = fork)) { if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-fork -v -u $user -p localhost:$sslh_port -s localhost:$ssh_port -l localhost:$ssl_port -P $pidfile"; exec "./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
#exec "./sslh-select -v -f -u $user -p localhost:$sslh_port -s localhost:$ssh_port -l localhost:$ssl_port -P $pidfile"; #exec "valgrind --leak-check=full ./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
#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";
exit 0; exit 0;
} }
warn "spawned $sslh_pid\n"; warn "spawned $sslh_pid\n";
sleep 1; sleep 1; # valgrind can be heavy -- wait 5 seconds
my $test_data = "hello world\n"; my $test_data = "hello world\n";
# Test: SSL connection # Test: SSL connection
if ($SSL_CNX) { if ($SSL_CNX) {
print "***Test: SSL connection\n"; print "***Test: SSL connection\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l; warn "$!\n" unless $cnx_l;
if (defined $cnx_l) { if (defined $cnx_l) {
print $cnx_l $test_data; print $cnx_l $test_data;
my $ssl_data = $ssl_listen->accept; my $data = <$cnx_l>;
my $data = <$ssl_data>; is($data, "ssl: $test_data", "SSL connection");
is($data, $test_data, "SSL connection"); }
} }
}
# Test: Shy SSH connection # Test: Shy SSH connection
if ($SSH_SHY_CNX) { if ($SSH_SHY_CNX) {
print "***Test: Shy SSH connection\n"; print "***Test: Shy SSH connection\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h; warn "$!\n" unless $cnx_h;
if (defined $cnx_h) { if (defined $cnx_h) {
sleep 3; sleep 3;
my $ssh_data = $ssh_listen->accept;
print $cnx_h $test_data; print $cnx_h $test_data;
my $data = <$ssh_data>; my $data = <$cnx_h>;
is($data, $test_data, "Shy SSH connection"); is($data, "ssh: $test_data", "Shy SSH connection");
}
} }
}
# Test: Bold SSH connection # Test: Bold SSH connection
if ($SSH_BOLD_CNX) { if ($SSH_BOLD_CNX) {
print "***Test: Bold SSH connection\n"; print "***Test: Bold SSH connection\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h; warn "$!\n" unless $cnx_h;
if (defined $cnx_h) { if (defined $cnx_h) {
my $td = "SSH-2.0 testsuite\n$test_data"; my $td = "SSH-2.0 testsuite\t$test_data";
print $cnx_h $td; print $cnx_h $td;
my $ssh_data = $ssh_listen->accept; my $data = <$cnx_h>;
my $data = <$ssh_data>; is($data, "ssh: $td", "Bold SSH connection");
$data .= <$ssh_data>; }
is($data, $td, "Bold SSH connection");
} }
}
# Test: One SSL half-started then one SSH # Test: One SSL half-started then one SSH
if ($SSL_MIX_SSH) { if ($SSL_MIX_SSH) {
print "***Test: One SSL half-started then one SSH\n"; print "***Test: One SSL half-started then one SSH\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l; warn "$!\n" unless $cnx_l;
@ -108,19 +114,17 @@ if ($SSL_MIX_SSH) {
warn "$!\n" unless $cnx_h; warn "$!\n" unless $cnx_h;
if (defined $cnx_h) { if (defined $cnx_h) {
sleep 3; sleep 3;
my $ssh_data = $ssh_listen->accept;
print $cnx_h $test_data; print $cnx_h $test_data;
my $data_h = <$ssh_data>; my $data_h = <$cnx_h>;
is($data_h, $test_data, "SSH during SSL being established"); is($data_h, "ssh: $test_data", "SSH during SSL being established");
}
my $data = <$cnx_l>;
is($data, "ssl: $test_data", "SSL connection interrupted by SSH");
} }
my $ssl_data = $ssl_listen->accept;
my $data = <$ssl_data>;
is($data, $test_data, "SSL connection interrupted by SSH");
} }
}
# Test: One SSH half-started then one SSL # Test: One SSH half-started then one SSL
if ($SSH_MIX_SSL) { if ($SSH_MIX_SSL) {
print "***Test: One SSH half-started then one SSL\n"; print "***Test: One SSH half-started then one SSL\n";
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_h; warn "$!\n" unless $cnx_h;
@ -130,20 +134,18 @@ if ($SSH_MIX_SSL) {
warn "$!\n" unless $cnx_l; warn "$!\n" unless $cnx_l;
if (defined $cnx_l) { if (defined $cnx_l) {
print $cnx_l $test_data; print $cnx_l $test_data;
my $ssl_data = $ssl_listen->accept; my $data = <$cnx_l>;
my $data = <$ssl_data>; is($data, "ssl: $test_data", "SSL during SSH being established");
is($data, $test_data, "SSL during SSH being established");
} }
my $ssh_data = $ssh_listen->accept;
print $cnx_h $test_data; print $cnx_h $test_data;
my $data = <$ssh_data>; my $data = <$cnx_h>;
is($data, $test_data, "SSH connection interrupted by SSL"); is($data, "ssh: $test_data", "SSH connection interrupted by SSL");
}
} }
}
# Test: Big messages # Test: Big messages (careful: don't go over echosrv's buffer limit (1M))
if ($BIG_MSG) { if ($BIG_MSG) {
print "***Test: big message\n"; print "***Test: big message\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l; warn "$!\n" unless $cnx_l;
@ -152,78 +154,149 @@ if ($BIG_MSG) {
if (defined $cnx_l) { if (defined $cnx_l) {
print $cnx_l ($test_data2 x $rept); print $cnx_l ($test_data2 x $rept);
print $cnx_l "\n"; print $cnx_l "\n";
my $ssl_data = $ssl_listen->accept; my $data = <$cnx_l>;
my $data = <$ssl_data>; is($data, "ssl: ". ($test_data2 x $rept) . "\n", "Big message");
is($data, $test_data2 x $rept . "\n", "Big message");
} }
}
# Test: Stalled connection
# Create two connections, stall one, check the other one
# works, unstall first and check it works fine
if ($STALL_CNX) {
print "***Test: Stalled connection\n";
my $cnx_1 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_1;
my $cnx_2 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_2;
my $test_data2 = "helloworld";
my $rept = 10000;
if (defined $cnx_1 and defined $cnx_2) {
print $cnx_1 ($test_data2 x $rept);
print $cnx_1 "\n";
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
my $data = <$cnx_2>;
is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (1)");
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
$data = <$cnx_2>;
is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
$data = <$cnx_1>;
is($data, "ssl: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
}
}
my $pid = `cat $pidfile`;
warn "killing $pid\n";
kill TERM => $pid or warn "kill process: $!\n";
sleep 1;
} }
# Test: several connections active at once # Robustness: Connecting to non-existant server
# We start 50 SSH connexions, then open 50 SSL connexion, then accept the 50 if ($RB_CNX_NOSERVER) {
# SSH connexions, then we randomize the order of connexions and write 1000 print "***Test: Connecting to non-existant server\n";
# messages on each connexion and check we get it on the other end. my $sslh_pid;
if ($MANY_CNX) { if (!($sslh_pid = fork)) {
print "***Test: several connexions active at once\n"; my $user = (getpwuid $<)[0]; # Run under current username
my (@cnx_h, @ssh_data); exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --ssl localhost:$no_listen -P $pidfile";
for (1..$NUM_SSH_CNX) { }
warn "spawned $sslh_pid\n";
sleep 1;
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "----> $!\n" unless defined $cnx_h; warn "$!\n" unless $cnx_h;
if (defined $cnx_h) { if (defined $cnx_h) {
push @cnx_h, $cnx_h; sleep 1;
} my $test_data = "hello";
} print $cnx_h $test_data;
my (@cnx_l, @ssl_data);
for (1..$NUM_SSL_CNX) {
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "----> $!\n" unless defined $cnx_l;
if (defined $cnx_l) {
push @cnx_l, $cnx_l;
print $cnx_l " ";
push @ssl_data, ($ssl_listen->accept)[0];
}
}
# give time to the connections to turn to SSH
sleep 4;
# and accept all SSH connections...
for (1..$NUM_SSH_CNX) {
push @ssh_data, $ssh_listen->accept;
} }
# Ideally we should check a log is emitted.
# Make up a random order so we don't always hit the kill TERM => `cat $pidfile` or warn "kill: $!\n";
# connexions in the same order sleep 1;
# fisher_yates_shuffle( \@array ) : generate a random permutation
# of @array in place (from
# http://docstore.mik.ua/orelly/perl/cookbook/ch04_18.htm,
# modified to shuffle two arrays in the same way)
sub fisher_yates_shuffle {
my ($array1, $array2) = @_;
my $i;
for ($i = @$array1; --$i; ) {
my $j = int rand ($i+1);
next if $i == $j;
@$array1[$i,$j] = @$array1[$j,$i];
@$array2[$i,$j] = @$array2[$j,$i];
}
}
my @cnx = (@cnx_l, @cnx_l);
my @rcv = (@ssl_data, @ssl_data);
fisher_yates_shuffle(\@rcv, \@cnx);
# Send a bunch of messages
for my $cnt (1..1000) {
foreach (@cnx) {
print $_ "$cnt$test_data";
}
foreach (@rcv) {
my $data = <$_>;
like($data, qr/ ?$cnt$test_data/, "Multiple messages [$cnt]");
}
}
} }
# Robustness: No hostname in address
if ($RB_PARAM_NOHOST) {
print "***Test: No hostname in address\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen $sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 1, "Exit status on illegal option");
}
# Robustness: User does not exist
if ($RB_WRONG_USERNAME) {
print "***Test: Changing to non-existant username\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u ${user}_doesnt_exist --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 2, "Exit status on non-existant username");
}
# Robustness: Can't open PID file
if ($RB_OPEN_PID_FILE) {
print "***Test: Can't open PID file\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P /dont_exist/$pidfile";
# You don't have a /dont_exist/ directory, do you?!
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 3, "Exit status if can't open PID file");
}
# Robustness: Can't bind address
if ($RB_BIND_ADDRESS) {
print "***Test: Can't bind address\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen 74.125.39.106:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 1, "Exit status if can't bind address");
}
# Robustness: Can't resolve address
if ($RB_RESOLVE_ADDRESS) {
print "***Test: Can't resolve address\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
is($code, 4, "Exit status if can't resolve address");
}
`lcov --directory . --capture --output-file sslh_cov.info`;
`genhtml sslh_cov.info`;
`killall echosrv`;
kill 15, `cat $pidfile` or warn "kill: $!\n";

128
t_load Executable file
View File

@ -0,0 +1,128 @@
#! /usr/bin/perl -w
# Test script for sslh -- mass communication
# This creates many clients that perform concurrent
# connections, disconnect at any time, and try to generally
# behave as badly as possible.
# It can be used to test sslh behaves properly with many
# clients, however its main use is to get an idea of how
# much load it can take on your system before things start
# to go wrong.
use strict;
use IO::Socket::INET6;
use Data::Dumper;
## BEGIN TEST CONFIG
# Do we test sslh-select or sslh-fork?
my $sslh_binary = "./sslh-select";
# How many clients to we start for each protocol?
my $NUM_CNX = 30;
# Delay between starting new processes when starting up. If
# you start 200 processes in under a second, things go wrong
# and it's not sslh's fault (typically the echosrv won't be
# forking fast enough).
my $start_time_delay = 0.5;
# If you test 4 protocols, you'll start $NUM_CNX * 4 clients
# (e.g. 40), starting one every $start_time_delay seconds.
# Max times we repeat the test string: allows to test for
# large messages.
my $block_rpt = 4096;
# Probability to stop a client after a message (e.g. with
# .01 a client will send an average of 100 messages before
# disconnecting).
my $stop_client_probability = .001;
# What protocols we test, and on what ports
# Just comment out protocols you don't want to use.
my %protocols = (
"ssh" => { address => "localhost:9001", client => client("ssh") },
"ssl" => { address => "localhost:9002", client => client("ssl") },
"openvpn" => {address => "localhost:9003", client => client("openvpn") },
"tinc" => {address => "localhost:9004", client => client("tinc") },
);
##END CONFIG
# We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything...
my $sslh_address = "localhost:9000";
my $pidfile = "/tmp/sslh_test.pid";
sub client {
my ($service) = @_;
return sub {
while (1) {
my $cnx = new IO::Socket::INET(PeerHost => $sslh_address);
my $test_data = "$service testing " x int(rand($block_rpt)+1) . "\n";
sleep 5 if $service eq "ssh";
if ($service eq "openvpn") {
syswrite $cnx, "\x00\x0F\x38\n";
my $msg;
sysread $cnx, $msg, 14; # length "openvpn: \x0\xF\x38\n" => 14
}
if ($service eq "tinc") {
syswrite $cnx, "0 \n";
my $msg;
sysread $cnx, $msg, 10; # length "tinc: 0 \n" => 10
}
while (1) {
print $cnx $test_data;
my $r = <$cnx>;
($? = 1, die "$service got [$r]\n") if ($r ne "$service: $test_data");
last if rand(1) < $stop_client_probability;
}
}
exit 0;
}
}
foreach my $p (keys %protocols) {
if (!fork) {
exec "./echosrv --listen $protocols{$p}->{address} --prefix '$p: '";
}
}
# Start sslh with the right plumbing
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $prots = join " ", map "--$_ $protocols{$_}->{address}", keys %protocols;
my $cmd = "$sslh_binary -f -t 3 -u $user --listen $sslh_address $prots -P $pidfile";
print "$cmd\n";
exec $cmd;
exit 0;
}
warn "spawned $sslh_pid\n";
sleep 2; # valgrind can be heavy -- wait 5 seconds
for (1 .. $NUM_CNX) {
foreach my $p (keys %protocols) {
if (!fork) {
warn "starting $p\n";
&{$protocols{$p}->{client}};
exit;
}
# Give a little time so we don't overrun the
# listen(2) backlog.
select undef, undef, undef, $start_time_delay;
}
}
wait;
`killall echosrv`;