Merge pull request #56 from hogarthj/master

Initial addition of systemd socket based activiation
This commit is contained in:
yrutschle 2016-02-05 16:46:47 +01:00
commit 8f39c106e1
5 changed files with 315 additions and 22 deletions

View File

@ -6,6 +6,7 @@ USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBPCRE= # Use libpcre? (needed for regex on musl)
USELIBWRAP?= # Use libwrap?
USELIBCAP= # Use libcap?
USESYSTEMD= # Make use of systemd socket activation
COV_TEST= # Perform test coverage?
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
@ -50,6 +51,12 @@ ifneq ($(strip $(USELIBCAP)),)
CPPFLAGS+=-DLIBCAP
endif
ifneq ($(strip $(USESYSTEMD)),)
LIBS:=$(LIBS) -lsystemd
CPPFLAGS+=-DSYSTEMD
endif
all: sslh $(MAN) echosrv
.c.o: *.h
@ -68,6 +75,9 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
systemd-sslh-generator: systemd-sslh-generator.o
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
echosrv: $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
@ -100,7 +110,7 @@ distclean: clean
rm -f tags cscope.*
clean:
rm -f sslh-fork sslh-select echosrv version.h $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags --globals -T *.[ch]

View File

@ -59,6 +59,8 @@ of the Makefile:
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
Binaries
--------
@ -303,6 +305,90 @@ explicit IP addresses (or names):
This will not work:
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
Systemd Socket Activation
-------------------------
If compiled with `USESYSTEMD` then it is possible to activate
the service on demand and avoid running any code as root.
In this mode any listen configuration options are ignored and
the sockets are passed by systemd to the service.
Example socket unit:
[Unit]
Before=sslh.service
[Socket]
ListenStream=1.2.3.4:443
ListenStream=5.6.7.8:444
ListenStream=9.10.11.12:445
FreeBind=true
[Install]
WantedBy=sockets.target
Example service unit:
[Unit]
PartOf=sslh.socket
[Service]
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443
KillMode=process
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
User=sslh
With this setup only the socket needs to be enabled. The sslh service
will be started on demand and does not need to run as root to bind the
sockets as systemd has already bound and passed them over. If the sslh
service is started on its own without the sockets being passed by systemd
then it will look to use those defined on the command line or config
file as usual. Any number of ListenStreams can be defined in the socket
file and systemd will pass them all over to sslh to use as usual.
To avoid inconsistency between starting via socket and starting directly
via the service Requires=sslh.socket can be added to the service unit to
mandate the use of the socket configuration.
Rather than overwriting the entire socket file drop in values can be placed
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
values that will be merged.
In addition to the above with manual .socket file configuration there is an
optional systemd generator which can be compiled - systemd-sslh-generator
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overriden by a /etc/systemd/system/sslh.socket file.
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using

View File

@ -11,13 +11,19 @@
#include "common.h"
#include "probe.h"
/* Added to make the code compilable under CYGWIN
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
/*
/* Make use of systemd socket activation
* */
#ifdef SYSTEMD
#include <systemd/sd-daemon.h>
#endif
/*
* 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
@ -44,14 +50,35 @@ void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
char buf[NI_MAXHOST];
if (res == -1) {
fprintf(stderr, "%s:%s: %s\n",
sprintaddr(buf, sizeof(buf), addr),
syscall,
fprintf(stderr, "%s:%s: %s\n",
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
exit(1);
}
}
int get_fd_sockets(int *sockfd[])
{
int sd = 0;
#ifdef SYSTEMD
sd = sd_listen_fds(0);
if (sd < 0) {
fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd));
exit(1);
}
if (sd > 0) {
*sockfd = malloc(sd * sizeof(*sockfd[0]));
for (int i = 0; i < sd; i++) {
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
}
}
#endif
return sd;
}
/* Starts listening sockets on specified addresses.
* IN: addr[], num_addr
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
@ -64,6 +91,13 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
struct addrinfo *addr;
int i, res, one;
int num_addr = 0;
int sd_socks = 0;
sd_socks = get_fd_sockets(sockfd);
if (sd_socks > 0) {
return sd_socks;
}
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;
@ -147,7 +181,7 @@ int bind_peer(int fd, int fd_from)
}
/* Connect to first address that works and returns a file descriptor, or -1 if
* none work.
* none work.
* If transparent proxying is on, use fd_from peer address on external address
* of new file descriptor. */
int connect_addr(struct connection *cnx, int fd_from)
@ -168,8 +202,8 @@ int connect_addr(struct connection *cnx, int fd_from)
/* When transparent, make sure both connections use the same address family */
if (transparent && a->ai_family != from.ai_addr->sa_family)
continue;
if (verbose)
fprintf(stderr, "connecting to %s family %d len %d\n",
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);
@ -185,7 +219,7 @@ int connect_addr(struct connection *cnx, int fd_from)
}
res = connect(fd, a->ai_addr, a->ai_addrlen);
if (res == -1) {
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
cnx->proto->description, strerror(errno));
close(fd);
} else {
@ -203,10 +237,10 @@ int connect_addr(struct connection *cnx, int fd_from)
}
/* Store some data to write to the queue later */
int defer_write(struct queue *q, void* data, int data_size)
int defer_write(struct queue *q, void* data, int data_size)
{
char *p;
if (verbose)
if (verbose)
fprintf(stderr, "**** writing deferred on fd %d\n", q->fd);
p = realloc(q->begin_deferred_data, q->deferred_data_size + data_size);
@ -270,7 +304,7 @@ void dump_connection(struct connection *cnx)
}
/*
/*
* moves data from one fd to other
*
* returns number of bytes copied if success
@ -338,16 +372,16 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
int res;
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
host, sizeof(host),
serv, sizeof(serv),
numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
if (res) {
log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
/* Name resolution failed: do it numerically instead */
res = getnameinfo(a->ai_addr, a->ai_addrlen,
host, sizeof(host),
serv, sizeof(serv),
host, sizeof(host),
serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV);
/* should not fail but... */
if (res) {
@ -362,7 +396,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
return buf;
}
/* Turns a hostname and port (or service) into a list of struct addrinfo
/* Turns a hostname and port (or service) into a list of struct addrinfo
* returns 0 on success, -1 otherwise and logs error
**/
int resolve_split_name(struct addrinfo **out, const char* host, const char* serv)
@ -450,7 +484,7 @@ void log_connection(struct connection *cnx)
addr.ai_addrlen = sizeof(ss);
res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
if (res == -1) return; /* Can happen if connection drops before we get here.
if (res == -1) return; /* Can happen if connection drops before we get here.
In that case, don't log anything (there is no connection) */
sprintaddr(peer, sizeof(peer), &addr);
@ -553,7 +587,7 @@ void setup_signals(void)
}
/* Open syslog connection with appropriate banner;
/* Open syslog connection with appropriate banner;
* banner is made up of basename(bin_name)+"[pid]" */
void setup_syslog(const char* bin_name) {
char *name1, *name2;
@ -563,7 +597,7 @@ void setup_syslog(const char* bin_name) {
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
CHECK_RES_DIE(res, "asprintf");
openlog(name2, LOG_CONS, LOG_AUTH);
free(name1);
free(name1);
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
log_message(LOG_INFO, "%s %s started\n", server_type, VERSION);
@ -635,7 +669,7 @@ void drop_privileges(const char* user_name)
/* remove extraneous groups in case we belong to several extra groups that
* may have unwanted rights. If non-root when calling setgroups(), it
* fails, which is fine because... we have no unwanted rights
* fails, which is fine because... we have no unwanted rights
* (see POS36-C for security context)
* */
setgroups(0, NULL);

View File

@ -564,10 +564,13 @@ next_arg:
set_protocol_list(prots);
/* If compiling with systemd socket support no need to require listen address */
#ifndef SYSTEMD
if (!addr_listen && !inetd) {
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
#endif
/* Did command-line override foreground setting? */
if (background)
@ -604,6 +607,13 @@ int main(int argc, char *argv[])
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
#ifdef SYSTEMD
if (num_addr_listen < 1) {
fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n");
exit(1);
}
#endif
if (!foreground) {
if (fork() > 0) exit(0); /* Detach */

153
systemd-sslh-generator.c Normal file
View File

@ -0,0 +1,153 @@
#include <libconfig.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static char* resolve_listen(const char *hostname, const char *port) {
/* Need room in the strcat for \0 and :
* the format in the socket unit file is hostname:port */
char *conn = (char*)malloc(strlen(hostname)+strlen(port)+2);
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
return conn;
}
static int get_listen_from_conf(const char *filename, char **listen) {
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
return -1;
}
} else {
setting = config_lookup(&config, "listen");
if (setting) {
len = config_setting_length(setting);
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
listen[i] = malloc(strlen(resolve_listen(hostname, port)));
strcpy(listen[i], resolve_listen(hostname, port));
}
}
}
}
return len;
}
static int write_socket_unit(FILE *socket, char **listen, int num_addr, const char *source) {
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"SourcePath=%s\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
}
}
fclose(config);
num_addr = get_listen_from_conf(sslh_conf, listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir != "") {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
}
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}