From 080d729f31b01a99bd3694cdde511b86d1a32e4c Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner <lorenz.stechauner@necronda.net> Date: Wed, 6 Jan 2021 15:49:52 +0100 Subject: [PATCH] Add config file --- src/cache.c | 6 +- src/client.c | 47 ++++++------ src/config.c | 171 +++++++++++++++++++++++++++++++++++++++++- src/config.h | 19 ++++- src/necronda-server.c | 81 +++++++++----------- src/necronda-server.h | 6 +- 6 files changed, 249 insertions(+), 81 deletions(-) diff --git a/src/cache.c b/src/cache.c index 70c569a..1748803 100644 --- a/src/cache.c +++ b/src/cache.c @@ -204,9 +204,13 @@ int cache_init() { int cache_unload() { int shm_id = shmget(SHM_KEY_CACHE, 0, 0); if (shm_id < 0) { - fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno)); + fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno)); + shmdt(cache); + return -1; } else if (shmctl(shm_id, IPC_RMID, NULL) < 0) { fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno)); + shmdt(cache); + return -1; } shmdt(cache); return 0; diff --git a/src/client.c b/src/client.c index c7420dd..aef55ae 100644 --- a/src/client.c +++ b/src/client.c @@ -19,23 +19,13 @@ char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0}; -char *get_webroot(const char *http_host) { - char *webroot = malloc(strlen(webroot_base) + strlen(http_host) + 1); - unsigned long len = strlen(webroot_base); - while (webroot_base[len - 1] == '/') len--; - long pos = strchr(http_host, ':') - http_host; - sprintf(webroot, "%.*s/%.*s", (int) len, webroot_base, (int) (pos < 0 ? strlen(http_host) : pos), http_host); - return path_is_directory(webroot) ? webroot : NULL; -} - -int get_dir_mode(const char *webroot) { - char buf[256]; - struct stat statbuf; - sprintf(buf, "%s/.necronda-server/dir_mode_info", webroot); - if (stat(buf, &statbuf) == 0) return URI_DIR_MODE_INFO; - sprintf(buf, "%s/.necronda-server/dir_mode_list", webroot); - if (stat(buf, &statbuf) == 0) return URI_DIR_MODE_LIST; - return URI_DIR_MODE_FORBIDDEN; +host_config *get_host_config(const char *host) { + for (int i = 0; i < MAX_HOST_CONFIG; i++) { + host_config *hc = &config[i]; + if (hc->type == CONFIG_TYPE_UNSET) break; + if (strcmp(hc->name, host) == 0) return hc; + } + return NULL; } void client_terminate() { @@ -49,12 +39,13 @@ int client_websocket_handler() { int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) { struct timespec begin, end; - int ret, client_keep_alive, dir_mode; + int ret, client_keep_alive; char buf0[1024], buf1[1024]; char msg_buf[4096], msg_pre_buf[4096], err_msg[256]; char buffer[CHUNK_SIZE]; err_msg[0] = 0; - char host[256], *host_ptr, *hdr_connection, *webroot; + char host[256], *host_ptr, *hdr_connection; + host_config *conf; long content_length = 0; FILE *file = NULL; msg_buf[0] = 0; @@ -133,18 +124,22 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int log_prefix = log_req_prefix; print(BLD_STR "%s %s" CLR_STR, req.method, req.uri); - // TODO Reverse Proxy - webroot = get_webroot(host); - if (webroot == NULL) { + conf = get_host_config(host); + if (conf == NULL) { res.status = http_get_status(307); sprintf(buf0, "https://%s%s", DEFAULT_HOST, req.uri); http_add_header_field(&res.hdr, "Location", buf0); goto respond; } - dir_mode = get_dir_mode(webroot); + if (conf->type != CONFIG_TYPE_LOCAL) { + // TODO Reverse Proxy + res.status = http_get_status(501); + goto respond; + } + http_uri uri; - ret = uri_init(&uri, webroot, req.uri, dir_mode); + ret = uri_init(&uri, conf->local.webroot, req.uri, conf->local.dir_mode); if (ret != 0) { if (ret == 1) { sprintf(err_msg, "Invalid URI: has to start with slash."); @@ -183,7 +178,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int } else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) { res.status = http_get_status(404); goto respond; - } else if (strlen(uri.pathinfo) != 0 && dir_mode != URI_DIR_MODE_INFO) { + } else if (strlen(uri.pathinfo) != 0 && conf->local.dir_mode != URI_DIR_MODE_INFO) { res.status = http_get_status(404); goto respond; } @@ -460,7 +455,7 @@ int client_connection_handler(sock *client, unsigned long client_num) { clock_gettime(CLOCK_MONOTONIC, &begin); - if (dns_server != NULL) { + if (dns_server[0] != 0) { sprintf(buf, "dig @%s +short +time=1 -x %s", dns_server, client_addr_str); FILE *dig = popen(buf, "r"); if (dig == NULL) { diff --git a/src/config.c b/src/config.c index 95cbe4f..1118b05 100644 --- a/src/config.c +++ b/src/config.c @@ -6,12 +6,179 @@ */ #include "config.h" - +#include "uri.h" +#include <sys/ipc.h> +#include <sys/shm.h> int config_init() { + int shm_id = shmget(SHM_KEY_CONFIG, MAX_HOST_CONFIG * sizeof(host_config), IPC_CREAT | IPC_EXCL | 0640); + if (shm_id < 0) { + fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno)); + return -1; + } + + void *shm = shmat(shm_id, NULL, SHM_RDONLY); + if (shm == (void *) -1) { + fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno)); + return -2; + } + config = shm; + + void *shm_rw = shmat(shm_id, NULL, 0); + if (shm_rw == (void *) -1) { + fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno)); + return -3; + } + config = shm_rw; + memset(config, 0, MAX_HOST_CONFIG * sizeof(host_config)); + shmdt(shm_rw); + config = shm; return 0; } -int config_load() { +int config_unload() { + int shm_id = shmget(SHM_KEY_CONFIG, 0, 0); + if (shm_id < 0) { + fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno)); + shmdt(config); + return -1; + } else if (shmctl(shm_id, IPC_RMID, NULL) < 0) { + fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno)); + shmdt(config); + return -1; + } + shmdt(config); + return 0; +} + +int config_load(const char *filename) { + FILE *file = fopen(filename, "r"); + if (file == NULL) { + fprintf(stderr, ERR_STR "Unable to open config file: %s" CLR_STR "\n", strerror(errno)); + return -1; + } + + fseek(file, 0, SEEK_END); + unsigned long len = ftell(file); + fseek(file, 0, SEEK_SET); + char *conf = malloc(len); + fread(conf, 1, len, file); + fclose(file); + + host_config *tmp_config = malloc(MAX_HOST_CONFIG * sizeof(host_config)); + memset(tmp_config, 0, MAX_HOST_CONFIG * sizeof(host_config)); + + int i = 0; + int mode = 0; + char *ptr = NULL; + char host[256], *source, *target; + host[0] = 0; + while ((ptr = strtok(ptr == NULL ? conf : NULL, "\n")) != NULL) { + char *comment = strchr(ptr, '#'); + if (comment != NULL) comment[0] = 0; + len = strlen(ptr); + if (ptr[0] == '[') { + if (ptr[len - 1] != ']') goto err; + strncpy(tmp_config[i].name, ptr + 1, len - 2); + i++; + continue; + } else if (i == 0) { + if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) { + source = ptr + 11; + target = cert_file; + } else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) { + source = ptr + 11; + target = key_file; + } else if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) { + source = ptr + 9; + target = geoip_dir; + } else if (len > 11 && strncmp(ptr, "dns_server", 10) == 0 && (ptr[10] == ' ' || ptr[10] == '\t')) { + source = ptr + 10; + target = dns_server; + } + } else { + host_config *hc = &tmp_config[i - 1]; + if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) { + source = ptr + 7; + target = hc->local.webroot; + if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) { + goto err; + } else { + hc->type = CONFIG_TYPE_LOCAL; + } + } else if (len > 9 && strncmp(ptr, "dir_mode", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) { + source = ptr + 8; + target = NULL; + mode = 1; + if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) { + goto err; + } else { + hc->type = CONFIG_TYPE_LOCAL; + } + } else if (len > 9 && strncmp(ptr, "hostname", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) { + source = ptr + 8; + target = hc->rev_proxy.hostname; + if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) { + goto err; + } else { + hc->type = CONFIG_TYPE_REVERSE_PROXY; + } + } else if (len > 5 && strncmp(ptr, "port", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) { + source = ptr + 4; + target = NULL; + mode = 2; + if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) { + goto err; + } else { + hc->type = CONFIG_TYPE_REVERSE_PROXY; + } + } + } + char *end_ptr = source + strlen(source) - 1; + while (source[0] == ' ' || source[0] == '\t') source++; + while (end_ptr[0] == ' ' || end_ptr[0] == '\t') end_ptr--; + if (end_ptr <= source) { + err: + free(conf); + free(tmp_config); + fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n"); + return -2; + } + end_ptr[1] = 0; + if (target != NULL) { + strcpy(target, source); + } else if (mode == 1) { + if (strcmp(source, "forbidden") == 0) { + tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN; + } else if (strcmp(source, "info") == 0) { + tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_INFO; + } else if (strcmp(source, "list") == 0) { + tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_LIST; + } else { + goto err; + } + } else if (mode == 2) { + tmp_config[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10); + } + } + + free(conf); + + int shm_id = shmget(SHM_KEY_CONFIG, 0, 0); + if (shm_id < 0) { + fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno)); + shmdt(config); + return -3; + } + + void *shm_rw = shmat(shm_id, NULL, 0); + if (shm_rw == (void *) -1) { + free(tmp_config); + fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno)); + return -4; + } + memcpy(shm_rw, tmp_config, MAX_HOST_CONFIG * sizeof(host_config)); + free(tmp_config); + shmdt(shm_rw); return 0; } diff --git a/src/config.h b/src/config.h index 5dda945..fc7b144 100644 --- a/src/config.h +++ b/src/config.h @@ -9,24 +9,35 @@ #ifndef NECRONDA_SERVER_CONFIG_H #define NECRONDA_SERVER_CONFIG_H +#define CONFIG_TYPE_UNSET 0 +#define CONFIG_TYPE_LOCAL 1 +#define CONFIG_TYPE_REVERSE_PROXY 2 + + typedef struct { int type; char name[256]; union { struct { - char address[256]; + char hostname[256]; unsigned short port; - }; + } rev_proxy; struct { char webroot[256]; unsigned char dir_mode; - }; + } local; }; } host_config; +host_config *config; +char cert_file[256], key_file[256], geoip_dir[256], dns_server[256]; + + int config_init(); -int config_load(); +int config_load(const char *filename); + +int config_unload(); #endif //NECRONDA_SERVER_CONFIG_H diff --git a/src/necronda-server.c b/src/necronda-server.c index 8524966..539f07d 100644 --- a/src/necronda-server.c +++ b/src/necronda-server.c @@ -19,6 +19,7 @@ int active = 1; +const char *config_file; void openssl_init() { @@ -88,6 +89,7 @@ void destroy() { fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills); } cache_unload(); + config_unload(); exit(2); } @@ -156,6 +158,7 @@ void terminate() { fprintf(stderr, "Goodbye\n"); } cache_unload(); + config_unload(); exit(0); } @@ -190,68 +193,42 @@ int main(int argc, const char *argv[]) { } printf("Necronda Web Server\n"); + ret = config_init(); + if (ret != 0) { + return 1; + } + + config_file = NULL; for (int i = 1; i < argc; i++) { const char *arg = argv[i]; - unsigned long len = strlen(arg); if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { - printf("Usage: necronda-server [-h] -w <PATH> -c <CERT-FILE> -p <KEY-FILE> [-g <DB-DIR>] [-d <DNS-SERVER>]\n" + printf("Usage: necronda-server [-h] [-c <CONFIG-FILE>]\n" "\n" "Options:\n" - " -c, --cert <CERT-FILE> path to the full chain certificate file\n" - " -d, --dns <DNS-SERVER> ip address or hostname of a DNS server for dig\n" - " -g, --geoip <DB-DIR> path to a Maxmind GeoIP Database file\n" - " -h, --help print this dialogue\n" - " -p, --privkey <KEY-FILE> path to the private key file\n" - " -w, --webroot <PATH> path to the web root directory\n"); + " -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n" + " -h, --help print this dialogue\n"); + config_unload(); return 0; - } else if (strcmp(arg, "-w") == 0 || strcmp(arg, "--webroot") == 0) { + } else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) { if (i == argc - 1) { - fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --webroot <WEBROOT>" CLR_STR "\n", arg); + fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --config <CONFIG-FILE>" CLR_STR "\n", arg); + config_unload(); return 1; } - webroot_base = argv[++i]; - } else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--cert") == 0) { - if (i == argc - 1) { - fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --cert <CERT-FILE>" CLR_STR "\n", arg); - return 1; - } - cert_file = argv[++i]; - } else if (strcmp(arg, "-p") == 0 || strcmp(arg, "--privkey") == 0) { - if (i == argc - 1) { - fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --privkey <KEY-FILE>" CLR_STR "\n", arg); - return 1; - } - key_file = argv[++i]; - } else if (strcmp(arg, "-g") == 0 || strcmp(arg, "--geoip") == 0) { - if (i == argc - 1) { - fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --geoip <DB-DIR>" CLR_STR "\n", arg); - return 1; - } - geoip_dir = argv[++i]; - } else if (strcmp(arg, "-d") == 0 || strcmp(arg, "--dns") == 0) { - if (i == argc - 1) { - fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --dns <DNS-SERVER>" CLR_STR "\n", arg); - return 1; - } - dns_server = argv[++i]; + config_file = argv[++i]; } else { fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg); + config_unload(); return 1; } } - if (webroot_base == NULL) { - fprintf(stderr, ERR_STR "Error: --webroot is missing" CLR_STR "\n"); - return 1; - } - if (cert_file == NULL) { - fprintf(stderr, ERR_STR "Error: --cert is missing" CLR_STR "\n"); - return 1; - } - if (key_file == NULL) { - fprintf(stderr, ERR_STR "Error: --privkey is missing" CLR_STR "\n"); + ret = config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file); + if (ret != 0) { + config_unload(); return 1; } + printf("%s %s\n", cert_file, key_file); sockets[0] = socket(AF_INET6, SOCK_STREAM, 0); if (sockets[0] < 0) goto socket_err; @@ -259,12 +236,14 @@ int main(int argc, const char *argv[]) { if (sockets[1] < 0) { socket_err: fprintf(stderr, ERR_STR "Unable to create socket: %s" CLR_STR "\n", strerror(errno)); + config_unload(); return 1; } for (int i = 0; i < NUM_SOCKETS; i++) { if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) { fprintf(stderr, ERR_STR "Unable to set options for socket %i: %s" CLR_STR "\n", i, strerror(errno)); + config_unload(); return 1; } } @@ -273,16 +252,18 @@ int main(int argc, const char *argv[]) { if (bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) < 0) { bind_err: fprintf(stderr, ERR_STR "Unable to bind socket to address: %s" CLR_STR "\n", strerror(errno)); + config_unload(); return 1; } signal(SIGINT, terminate); signal(SIGTERM, terminate); - if (geoip_dir != NULL) { + if (geoip_dir[0] != 0) { DIR *geoip = opendir(geoip_dir); if (geoip == NULL) { fprintf(stderr, ERR_STR "Unable to open GeoIP dir: %s" CLR_STR "\n", strerror(errno)); + config_unload(); return 1; } struct dirent *dir; @@ -291,18 +272,21 @@ int main(int argc, const char *argv[]) { if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".mmdb") != 0) continue; if (i >= MAX_MMDB) { fprintf(stderr, ERR_STR "Too many .mmdb files" CLR_STR "\n"); + config_unload(); return 1; } sprintf(buf, "%s/%s", geoip_dir, dir->d_name); ret = MMDB_open(buf, 0, &mmdbs[i]); if (ret != MMDB_SUCCESS) { fprintf(stderr, ERR_STR "Unable to open .mmdb file: %s" CLR_STR "\n", MMDB_strerror(ret)); + config_unload(); return 1; } i++; } if (i == 0) { fprintf(stderr, ERR_STR "No .mmdb files found in %s" CLR_STR "\n", geoip_dir); + config_unload(); return 1; } closedir(geoip); @@ -324,17 +308,20 @@ int main(int argc, const char *argv[]) { if (SSL_CTX_use_certificate_chain_file(client.ctx, cert_file) != 1) { fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n", ERR_reason_error_string(ERR_get_error()), cert_file); + config_unload(); return 1; } if (SSL_CTX_use_PrivateKey_file(client.ctx, key_file, SSL_FILETYPE_PEM) != 1) { fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n", ERR_reason_error_string(ERR_get_error()), key_file); + config_unload(); return 1; } for (int i = 0; i < NUM_SOCKETS; i++) { if (listen(sockets[i], LISTEN_BACKLOG) < 0) { fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno)); + config_unload(); return 1; } } @@ -349,6 +336,7 @@ int main(int argc, const char *argv[]) { ret = cache_init(); if (ret < 0) { + config_unload(); return 1; } else if (ret != 0) { return 0; @@ -363,6 +351,7 @@ int main(int argc, const char *argv[]) { ready_sockets_num = select(max_socket_fd + 1, &read_socket_fds, NULL, NULL, &timeout); if (ready_sockets_num < 0) { fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno)); + terminate(); return 1; } diff --git a/src/necronda-server.h b/src/necronda-server.h index 0a586c4..3415433 100644 --- a/src/necronda-server.h +++ b/src/necronda-server.h @@ -32,6 +32,7 @@ #define NUM_SOCKETS 2 #define MAX_CHILDREN 1024 #define MAX_MMDB 3 +#define MAX_HOST_CONFIG 64 #define LISTEN_BACKLOG 16 #define REQ_PER_CONNECTION 100 #define CLIENT_TIMEOUT 3600 @@ -70,13 +71,14 @@ #ifndef PHP_FPM_SOCKET #define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock" #endif +#ifndef DEFAULT_CONFIG_FILE +#define DEFAULT_CONFIG_FILE "/etc/necronda-server/necronda-server.conf" +#endif int sockets[NUM_SOCKETS]; pid_t children[MAX_CHILDREN]; MMDB_s mmdbs[MAX_MMDB]; -const char *cert_file, *key_file, *webroot_base, *geoip_dir, *dns_server; - typedef struct { unsigned int enc:1; int socket;