Merge systemd unit multiple configurations

This commit is contained in:
Yves Rűtschlé 2024-04-10 18:06:56 +02:00
parent 1799a81079
commit 4b921be69d
4 changed files with 280 additions and 85 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ libsslh.a
sslh-fork
sslh-select
sslh-ev
systemd-sslh-generator
sslh.8.gz
tags
version.h

View File

@ -0,0 +1,27 @@
[Unit]
Description=SSL/SSH multiplexer (select mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target

View File

@ -1,10 +1,10 @@
[Unit]
Description=SSL/SSH multiplexer
Description=SSL/SSH multiplexer (fork mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true

View File

@ -1,39 +1,72 @@
#include <libconfig.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <sys/stat.h>
#include "common.h"
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
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 = malloc(strlen(hostname)+strlen(port)+2);
CHECK_ALLOC(conn, "malloc");
typedef struct FileList FileList;
struct FileList {
char *name;
struct FileList *prev;
};
static void free_file_list(FileList *fl) {
while (fl != NULL) {
FileList *prev = fl->prev;
free(fl->name);
free(fl);
fl = prev;
}
}
static FILE *err_log;
static bool systemd_invoked = false;
static const char
*socket_ext = ".socket",
*dropin_ext = ".conf",
*service_ext = ".service.d/",
*fork_unit_file = "/sslh@",
*select_unit_file = "/sslh-select@",
*fork_unit = "# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Requires=sslh@%s.socket\n"
"Conflicts=sslh-select@%s.service\n"
"PartOf=sslh@%s.socket\n",
*select_unit = "# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Requires=sslh@%s.socket\n"
"Conflicts=sslh@%s.service\n"
"PartOf=sslh@%s.socket\n";
static char *resolve_listen(const char *hostname, const char *port) {
char *conn = malloc(strlen(hostname) + strlen(port) + 2);
CHECK_ALLOC(conn, "malloc")
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",
fprintf(err_log,
"systemd-sslh-generator: %s%d%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
@ -42,16 +75,14 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
} else {
setting = config_lookup(&config, "listen");
if (setting) {
int i;
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
CHECK_ALLOC(*listen, "malloc");
for (i = 0; i < len; i++) {
CHECK_ALLOC(*listen, "malloc")
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",
if (!(config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) {
fprintf(err_log,
"systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
@ -65,95 +96,231 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
}
}
}
return len;
}
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
int i;
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"Before=sslh@%s.service\n"
"SourcePath=%s\n"
"PartOf=sslh@%s.service\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (i = 0; i < num_addr; i++) {
cfg,
source,
cfg);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
fprintf(socket,
"\n[Install]\n"
"WantedBy=sockets.target\n");
}
static int write_unit_dropin(const char *runtime_unit_dir, const bool is_fork, const char *cfg) {
const char
*unit_file = is_fork ? fork_unit_file : select_unit_file,
*unit_string = is_fork ? fork_unit : select_unit;
FILE *dropin_fd = stdout;
if (systemd_invoked) {
//Systemd drop-in configuration for the base select service sslh@%I.service
const size_t runtime_len = strlen(runtime_unit_dir);
size_t len = strlen(unit_file) + strlen(cfg) + strlen(service_ext) + strlen(dropin_ext);
char dropin_dir[runtime_len + len + 1];
strcpy(dropin_dir, runtime_unit_dir);
strcat(dropin_dir, unit_file);
strcat(dropin_dir, cfg);
strcat(dropin_dir, service_ext);
if (mkdir(dropin_dir, S_IRWXU | S_IRWXG | S_IROTH)) {
fprintf(err_log,
"systemd-sslh-generator: Could not create directory '%s' to generate drop-in configuration: %s\n",
dropin_dir,
strerror(errno));
return errno;
}
len = len + strlen(cfg) + strlen(dropin_ext);
char dropin_path[len + 1];
strcpy(dropin_path, dropin_dir);
strcat(dropin_path, cfg);
strcat(dropin_path, dropin_ext);
dropin_fd = fopen(dropin_path, "w");
if (!dropin_fd) {
fprintf(err_log,
"systemd-sslh-generator: Could not open '%s' to generate drop-in configuration: %s\n",
dropin_path,
strerror(errno));
return errno;
}
}
fprintf(dropin_fd, unit_string, cfg, cfg, cfg);
if (systemd_invoked) {
fclose(dropin_fd);
}
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
int status = 0;
const char *config_dir = "/etc/sslh/";
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
DIR *d = opendir(config_dir);
FileList *fa = NULL;
/* 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;
if (d) {
struct dirent *dir;
while ((dir = readdir(d)) != NULL) {
if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
continue;
}
FileList *lfa = malloc(sizeof(FileList));
CHECK_ALLOC(lfa, "malloc")
lfa->name = malloc(strlen(dir->d_name) + 1);
CHECK_ALLOC(lfa->name, "malloc")
strcpy(lfa->name, dir->d_name);
lfa->prev = NULL;
if (fa) {
lfa->prev = fa;
}
fa = lfa;
}
closedir(d);
} else {
fprintf(err_log,
"systemd-sslh-generator: Configuration directory '/etc/sslh/' does not exist! No units generated.\n");
//Config directory /etc/sslh/ does not exist
return 0;
}
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 && *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);
CHECK_ALLOC(runtime_conf, "malloc");
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
free(runtime_conf);
if (!fa) {
fprintf(err_log,
"systemd-sslh-generator: Configuration directory '/etc/sslh/' is empty! No units generated.\n");
//No configuration files in /etc/sslh/
return 0;
}
FileList *fa_ref = fa;
// size_t num_listen_addresses = 0;
// char **listen_addresses = NULL;
//Process all config files
do {
char *end = strstr(fa->name, ".cfg");
if (!end) {
continue;
}
//Current sslh config name
const size_t end_count = end - fa->name;
char config_name[end_count + 1];
memcpy(config_name, fa->name, end_count);
config_name[end_count] = '\0';
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
//Full path to current sslh config
char full_path[strlen(config_dir) + strlen(fa->name) + 1];
strcpy(full_path, config_dir);
strcat(full_path, fa->name);
FILE *config = fopen(full_path, "r");
if (!config) {
fprintf(err_log,
"systemd-sslh-generator: Could not open config file '%s': %s\n",
full_path,
strerror(errno));
return errno;
} else {
fclose(config);
int num_addr = get_listen_from_conf(full_path, &listen);
if (num_addr <= 0) {
fprintf(err_log,
"systemd-sslh-generator: sslh config '%s' contains no valid listen configurations!\n",
fa->name);
status |= -1;
continue;
}
FILE *socket_fd = stdout;
if (systemd_invoked) {
//Systemd socket for the current sslh config
const size_t len = strlen(runtime_unit_dir) + strlen(fork_unit_file) + strlen(config_name) + strlen(socket_ext);
char socket_path[len + 1];
strcpy(socket_path, runtime_unit_dir);
strcat(socket_path, fork_unit_file);
strcat(socket_path, config_name);
strcat(socket_path, socket_ext);
socket_fd = fopen(socket_path, "w");
if (!socket_fd) {
fprintf(err_log,
"systemd-sslh-generator: Could not open '%s' to generate socket configuration: %s\n",
socket_path,
strerror(errno));
status |= errno;
continue;
}
}
//Write socket unit
write_socket_unit(socket_fd, listen, num_addr, config_name, full_path);
//Write forking drop-in config
status |= write_unit_dropin(runtime_unit_dir, false, config_name);
//Write select drop-in config
status |= write_unit_dropin(runtime_unit_dir, true, config_name);
if (systemd_invoked) {
fclose(socket_fd);
}
// if (listen_addresses) {
// //Check for overlapping addresses
// for (size_t i = 0; i < num_listen_addresses; i++) {
// for (size_t j = 0; j < num_addr; j++) {
// if (strcmp(*(listen_addresses + i), *(listen + j)) == 0) {
// fprintf(err_log, "systemd-sslh-generator: Overlapping listen addresses across sslh configurations!");
// return -1;
// }
// }
// }
// }
// char *listen_addresses_copy[num_listen_addresses + num_addr];
// if (listen_addresses) {
// //Copy previous configurations' listen addresses into temp
// memcpy(listen_addresses_copy, listen_addresses, num_listen_addresses);
// //Free global list of listen addresses
// free(listen_addresses);
// }
// //Copy listen addresses from current configuration into temp
// memcpy(listen_addresses_copy + num_listen_addresses, listen, num_addr);
// num_listen_addresses += num_addr;
// listen_addresses = malloc(num_listen_addresses * sizeof(char *));
// CHECK_ALLOC(listen_addresses, "malloc")
// //Append all addresses to global list
// memcpy(listen_addresses, listen_addresses_copy, num_listen_addresses);
//Free all allocated listen strings
for (size_t i = 0; i < num_addr; i++) {
free(*(listen + i));
}
}
} while((fa = fa->prev));
free_file_list(fa_ref);
return status;
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
int main(int argc, char *argv[]) {
if (argc == 1 || argc == 4) {
systemd_invoked = argc == 4;
if (systemd_invoked) {
err_log = fopen("/dev/kmsg", "w");
if (!err_log) {
return -1;
}
} else {
err_log = stderr;
}
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
if (systemd_invoked) {
fclose(err_log);
}
if (!r) {
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
}
return r < 0 ? -1 : 0;
} else {
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;
}