Browse Source

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).
master v1.10
Yves Rutschle 10 years ago
parent
commit
ae008179f0
  1. 22
      ChangeLog
  2. 21
      Makefile
  3. 6
      README
  4. 64
      README.MacOSX
  5. 356
      common.c
  6. 32
      common.h
  7. 166
      echosrv.c
  8. 51
      sslh-fork.c
  9. 196
      sslh-main.c
  10. 43
      sslh-select.c
  11. 22
      sslh.pod
  12. 401
      t
  13. 128
      t_load

22
ChangeLog

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

21
Makefile

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

6
README

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

64
README.MacOSX

@ -13,42 +13,42 @@ with launchctl or simply reboot. @@ -13,42 +13,42 @@ with launchctl or simply reboot.
----BEGIN FILE----
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Disabled</key>
<false/>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>net.rutschle.sslh</string>
<key>ProgramArguments</key>
<array>
<string>/opt/local/sbin/sslh</string>
<string>-f</string>
<string>-v</string>
<string>-u</string>
<string>nobody</string>
<string>-p</string>
<string>0.0.0.0:443</string>
<string>-s</string>
<string>localhost:22</string>
<string>-l</string>
<string>localhost:443</string>
</array>
<key>QueueDirectories</key>
<array/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/Library/Logs/sslh.log</string>
<key>StandardOutPath</key>
<string>/Library/Logs/sslh.log</string>
<key>WatchPaths</key>
<array/>
<key>Disabled</key>
<false/>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>net.rutschle.sslh</string>
<key>ProgramArguments</key>
<array>
<string>/opt/local/sbin/sslh</string>
<string>-f</string>
<string>-v</string>
<string>-u</string>
<string>nobody</string>
<string>-p</string>
<string>0.0.0.0:443</string>
<string>--ssh</string>
<string>localhost:22</string>
<string>--ssl</string>
<string>localhost:443</string>
</array>
<key>QueueDirectories</key>
<array/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/Library/Logs/sslh.log</string>
<key>StandardOutPath</key>
<string>/Library/Logs/sslh.log</string>
<key>WatchPaths</key>
<array/>
</dict>
</plist>
----END FILE----

356
common.c

@ -34,42 +34,33 @@ @@ -34,42 +34,33 @@
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_xmpp_protocol(const char *p, int len);
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[] = {
/* affected description service saddr probe */
{ 0, "ssh", "sshd", {0}, is_ssh_protocol },
{ 0, "openvpn", NULL, {0}, is_openvpn_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
**/
{ 0, "ssl", NULL, {0}, is_true }
};
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" \
"";
int num_known_protocols = ARRAY_SIZE(protocols);
/*
* Settings that depend on the command line.
* They're set in main(), but also used in other places, and it'd be
* heavy-handed to pass it all as parameters
* Settings that depend on the command line. They're set in main(), but also
* used in other places in common.c, and it'd be heavy-handed to pass it all as
* parameters
*/
int verbose = 0;
int probing_timeout = 2;
@ -78,8 +69,7 @@ int foreground = 0; @@ -78,8 +69,7 @@ int foreground = 0;
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? */
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
#ifdef LIBWRAP
#include <tcpd.h>
@ -88,13 +78,13 @@ int allow_severity =0, deny_severity = 0; @@ -88,13 +78,13 @@ int allow_severity =0, deny_severity = 0;
/* 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) {
fprintf(stderr, "%s:%s: %s\n",
sprintaddr(buf, sizeof(buf), sock),
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
exit(1);
@ -103,31 +93,77 @@ void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall) @@ -103,31 +93,77 @@ void check_res_dumpdie(int res, struct sockaddr_storage *sock, char* syscall)
/* 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
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
* Returns number of addresses bound
* 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 addrinfo *addr;
int i, res, reuse;
int num_addr = 0;
for (i = 0; i < num_addr; i++) {
saddr = &addr[i];
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;
if (verbose)
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], saddr, "socket");
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dumpdie((*sockfd)[i], addr, "socket");
reuse = 1;
res = setsockopt(sockfd[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
check_res_dumpdie(res, saddr, "setsockopt");
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
check_res_dumpdie(res, addr, "setsockopt");
res = bind (sockfd[i], (struct sockaddr*)saddr, sizeof(*saddr));
check_res_dumpdie(res, saddr, "bind");
res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
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 */
@ -290,9 +326,19 @@ int is_openvpn_protocol (const char*p,int len) @@ -290,9 +326,19 @@ int is_openvpn_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
* 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) @@ -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 */
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];
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);
return buf;
}
/* turns a "hostname:port" string into a struct sockaddr;
sock: socket address to which to copy the addr
/* turns a "hostname:port" string into a list of struct addrinfo;
out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done
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;
int res;
@ -371,17 +427,13 @@ void resolve_name(struct sockaddr_storage *sock, char* fullname) @@ -371,17 +427,13 @@ void resolve_name(struct sockaddr_storage *sock, char* fullname)
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
res = getaddrinfo(host, serv, &hint, &addr);
res = getaddrinfo(host, serv, &hint, out);
if (res) {
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
if (res == EAI_SERVICE)
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 */
@ -402,41 +454,40 @@ void log_message(int type, char* msg, ...) @@ -402,41 +454,40 @@ void log_message(int type, char* msg, ...)
/* syslogs who connected to where */
void log_connection(struct connection *cnx)
{
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);
struct addrinfo addr;
struct sockaddr_storage ss;
#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;
memset(&peeraddr, 0, sizeof(peeraddr));
memset(&listenaddr, 0, sizeof(listenaddr));
memset(&forwardfromaddr, 0, sizeof(forwardfromaddr));
memset(&targetaddr, 0, sizeof(targetaddr));
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
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? */
sprintaddr(peer, sizeof(peer), &addr);
size = sizeof(listenaddr);
res = getsockname(cnx->q[0].fd, (struct sockaddr*)&listenaddr, &size);
addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(service, sizeof(service), &addr);
size = sizeof(targetaddr);
res = getpeername(cnx->q[1].fd, (struct sockaddr*)&targetaddr, &size);
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(target, sizeof(target), &addr);
size = sizeof(forwardfromaddr);
res = getsockname(cnx->q[1].fd, (struct sockaddr*)&forwardfromaddr, &size);
addr.ai_addrlen = sizeof(ss);
res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return;
sprintaddr(local, sizeof(local), &addr);
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));
log_message(LOG_INFO, "connection from %s to %s forwarded from %s to %s\n",
peer,
service,
local,
target);
}
@ -498,6 +549,14 @@ void setup_signals(void) @@ -498,6 +549,14 @@ void setup_signals(void)
action.sa_flags = SA_NOCLDWAIT;
res = sigaction(SIGCHLD, &action, NULL);
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;
@ -521,7 +580,7 @@ void drop_privileges(char* user_name) @@ -521,7 +580,7 @@ void drop_privileges(char* user_name)
struct passwd *pw = getpwnam(user_name);
if (!pw) {
fprintf(stderr, "%s: not found\n", user_name);
exit(1);
exit(2);
}
if (verbose)
fprintf(stderr, "turning into %s\n", user_name);
@ -540,7 +599,7 @@ void write_pid_file(char* pidfile) @@ -540,7 +599,7 @@ void write_pid_file(char* pidfile)
f = fopen(pidfile, "w");
if (!f) {
perror(pidfile);
exit(1);
exit(3);
}
fprintf(f, "%d\n", getpid());
@ -549,20 +608,23 @@ void write_pid_file(char* pidfile) @@ -549,20 +608,23 @@ void write_pid_file(char* pidfile)
void printsettings(void)
{
char buf[64];
char buf[NI_MAXHOST];
struct addrinfo *a;
int i;
for (i = 0; i < ARRAY_SIZE(protocols); i++) {
if (protocols[i].affected)
fprintf(stderr,
"%s addr: %s. libwrap service: %s\n",
"%s addr: %s. libwrap service: %s family %d %d\n",
protocols[i].description,
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");
for (i = 0; i < num_addr_listen; i++) {
fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), &addr_listen[i]));
for (a = addr_listen; a; a = a->ai_next) {
fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a));
}
fprintf(stderr, "timeout to ssh: %d\n", probing_timeout);
}
@ -574,7 +636,6 @@ void printsettings(void) @@ -574,7 +636,6 @@ void printsettings(void)
* 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;
@ -587,126 +648,3 @@ void append_protocols(struct option *options, int n_opts, struct proto *prot, in @@ -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;
}

32
common.h

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
#include <syslog.h>
#include <libgen.h>
#include <time.h>
#include <getopt.h>
#ifndef VERSION
#define VERSION "v?"
@ -46,27 +47,23 @@ enum connection_state { @@ -46,27 +47,23 @@ enum connection_state {
ST_SHOVELING /* Connexion is established */
};
/* 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;
typedef int T_PROTO_ID; /* Index into protocols[] array */
/* 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 and command-line parsing) */
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 */
};
/* A table in common.c contains all the known 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
* written to), and a queue for defered write data */
@ -94,10 +91,10 @@ struct connection { @@ -94,10 +91,10 @@ struct connection {
/* common.c */
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);
char* sprintaddr(char* buf, size_t size, struct sockaddr_storage* s);
void resolve_name(struct sockaddr_storage *sock, char* fullname) ;
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
void resolve_name(struct addrinfo **out, char* fullname);
T_PROTO_ID probe_client_protocol(struct connection *cnx);
void log_connection(struct connection *cnx);
int check_access_rights(int in_socket, char* service);
@ -110,12 +107,15 @@ void parse_cmdline(int argc, char* argv[]); @@ -110,12 +107,15 @@ void parse_cmdline(int argc, char* argv[]);
void log_message(int type, char* msg, ...);
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 flush_defered(struct queue *q);
extern int probing_timeout, verbose, inetd;
extern struct sockaddr_storage *addr_listen, addr_ssl, addr_ssh, addr_openvpn;
extern int probing_timeout, verbose, inetd, foreground, numeric;
extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING;
extern char* user_name, *pid_file;
extern const char* server_type;
@ -124,5 +124,3 @@ extern const char* server_type; @@ -124,5 +124,3 @@ extern const char* server_type;
void start_shoveler(int);
void main_loop(int *listen_sockets, int num_addr_listen);

166
echosrv.c

@ -0,0 +1,166 @@ @@ -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;
}

51
sslh-fork.c

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
/*
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
# and/or modify it under the terms of the GNU General Public
@ -68,10 +68,9 @@ void start_shoveler(int in_socket) @@ -68,10 +68,9 @@ void start_shoveler(int in_socket)
{
fd_set fds;
struct timeval tv;
struct sockaddr_storage *saddr;
struct addrinfo *saddr;
int res;
int out_socket;
char *target;
struct connection cnx;
T_PROTO_ID prot;
@ -92,22 +91,18 @@ void start_shoveler(int in_socket) @@ -92,22 +91,18 @@ void start_shoveler(int in_socket)
prot = probe_client_protocol(&cnx);
} else {
/* Timed out: it's necessarily SSH */
prot = PROT_SSH;
prot = 0;
}
saddr = &protocols[prot].saddr;
target = protocols[prot].description;
if (protocols[prot].service &&
check_access_rights(in_socket, protocols[prot].service)) {
exit(0);
}
/* Connect the target socket */
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");
out_socket = connect_addr(saddr, protocols[prot].description);
CHECK_RES_DIE(out_socket, "connect");
cnx.q[1].fd = out_socket;
@ -126,12 +121,31 @@ void start_shoveler(int in_socket) @@ -126,12 +121,31 @@ void start_shoveler(int in_socket)
exit(0);
}
void main_loop(int *listen_sockets, int num_addr_listen)
static int *listener_pid;
static int listener_pid_number = 0;
void stop_listeners(int sig)
{
int in_socket, i;
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++) {
if (!fork()) {
if (!(listener_pid[i] = fork())) {
/* Listening process just accepts a connection, forks, and goes
* back to listening */
while (1)
{
in_socket = accept(listen_sockets[i], 0, 0);
@ -147,6 +161,17 @@ void main_loop(int *listen_sockets, int num_addr_listen) @@ -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

196
sslh-main.c

@ -0,0 +1,196 @@ @@ -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;
}

43
sslh-select.c

@ -30,7 +30,8 @@ const char* server_type = "sslh-select"; @@ -30,7 +30,8 @@ const char* server_type = "sslh-select";
* 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
* 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;
@ -55,14 +56,16 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2) @@ -55,14 +56,16 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2)
int i;
for (i = 0; i < 2; i++) {
if (verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
close(cnx->q[i].fd);
FD_CLR(cnx->q[i].fd, fds);
FD_CLR(cnx->q[i].fd, fds2);
if (cnx->q[i].defered_data)
free(cnx->q[i].defered_data);
if (cnx->q[i].fd != -1) {
if (verbose)
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
close(cnx->q[i].fd);
FD_CLR(cnx->q[i].fd, fds);
FD_CLR(cnx->q[i].fd, fds2);
if (cnx->q[i].defered_data)
free(cnx->q[i].defered_data);
}
}
init_cnx(cnx);
return 0;
@ -110,22 +113,17 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_ @@ -110,22 +113,17 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_
return in_socket;
}
/* 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,
fd_set *fds_r, fd_set *fds_w)
{
struct queue *q = &cnx->q[1];
int res;
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);
log_message(LOG_ERR, "forward to %s failed\n", cnx_name);
return -1;
} else {
q->fd = connect_addr(addr, cnx_name);
if (q->fd != -1) {
log_connection(cnx);
set_nonblock(q->fd);
flush_defered(q);
if (q->defered_data) {
@ -134,6 +132,9 @@ int connect_queue(struct connection *cnx, struct sockaddr_storage *addr, @@ -134,6 +132,9 @@ int connect_queue(struct connection *cnx, struct sockaddr_storage *addr,
FD_SET(q->fd, fds_r);
}
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) @@ -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)
* is monitored either for read or for write, but never for both.