#include #include #include #include #include #include #include #include #include #include "common.h" #define print_message(sink, format, file, line) fprintf(stderr, format, file, line) 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 = calloc(1, 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; config_init(&config); if (config_read_file(&config, filename) == CONFIG_FALSE) { if (config_error_line(&config) != 0) { fprintf(err_log, "systemd-sslh-generator: %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); *listen = malloc(len * sizeof(**listen)); 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(err_log, "systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n", config_setting_source_line(addr)); return -1; } else { (*listen)[i] = resolve_listen(hostname, port); } } } } return len; } 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@%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", 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) { int status = 0; const char *config_dir = "/etc/sslh/"; char **listen; DIR *d = opendir(config_dir); FileList *fa = NULL; 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; } 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'; //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[]) { 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; } }