mirror of
https://github.com/yrutschle/sslh.git
synced 2025-04-13 15:47:15 +03:00
Merge pull request #56 from hogarthj/master
Initial addition of systemd socket based activiation
This commit is contained in:
commit
8f39c106e1
12
Makefile
12
Makefile
@ -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]
|
||||
|
86
README.md
86
README.md
@ -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
|
||||
|
76
common.c
76
common.c
@ -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);
|
||||
|
10
sslh-main.c
10
sslh-main.c
@ -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
153
systemd-sslh-generator.c
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user