From 9eaa644fa1f45b5850a1423ffdf60d61c1dfa721 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Mon, 27 Dec 2021 16:16:41 +0100 Subject: [PATCH] Add SNI --- src/client.c | 2 +- src/lib/config.c | 96 +++++++++++++++++++++++++++++++------------ src/lib/config.h | 18 +++++++- src/necronda-server.c | 67 +++++++++++++++++++++--------- 4 files changed, 134 insertions(+), 49 deletions(-) diff --git a/src/client.c b/src/client.c index 3b2b4a5..fd00d4b 100644 --- a/src/client.c +++ b/src/client.c @@ -34,7 +34,7 @@ struct timeval client_timeout; host_config *get_host_config(const char *host) { for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) { - host_config *hc = &config[i]; + host_config *hc = &config->hosts[i]; if (hc->type == CONFIG_TYPE_UNSET) break; if (strcmp(hc->name, host) == 0) return hc; } diff --git a/src/lib/config.c b/src/lib/config.c index 15dad5c..880c26d 100644 --- a/src/lib/config.c +++ b/src/lib/config.c @@ -14,11 +14,11 @@ #include #include -host_config *config; -char cert_file[256], key_file[256], geoip_dir[256], dns_server[256]; +t_config *config; +char geoip_dir[256], dns_server[256]; int config_init() { - int shm_id = shmget(CONFIG_SHM_KEY, CONFIG_MAX_HOST_CONFIG * sizeof(host_config), IPC_CREAT | IPC_EXCL | 0640); + int shm_id = shmget(CONFIG_SHM_KEY, sizeof(t_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; @@ -37,7 +37,7 @@ int config_init() { return -3; } config = shm_rw; - memset(config, 0, CONFIG_MAX_HOST_CONFIG * sizeof(host_config)); + memset(config, 0, sizeof(t_config)); shmdt(shm_rw); config = shm; return 0; @@ -72,11 +72,13 @@ int config_load(const char *filename) { fread(conf, 1, len, file); fclose(file); - host_config *tmp_config = malloc(CONFIG_MAX_HOST_CONFIG * sizeof(host_config)); - memset(tmp_config, 0, CONFIG_MAX_HOST_CONFIG * sizeof(host_config)); + t_config *tmp_config = malloc(sizeof(t_config)); + memset(tmp_config, 0, sizeof(t_config)); int i = 0; + int j = 0; int mode = 0; + char section = 0; char *ptr = NULL; char *source, *target; while ((ptr = strtok(ptr == NULL ? conf : NULL, "\n")) != NULL) { @@ -85,25 +87,46 @@ int config_load(const char *filename) { len = strlen(ptr); if (ptr[0] == '[') { if (ptr[len - 1] != ']') goto err; - snprintf(tmp_config[i].name, sizeof(tmp_config[i].name), "%.*s", (int) len - 2, ptr + 1); - i++; + int l = 0; + if (strncmp(ptr, "host", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) { + ptr += 4; + while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++; + while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++; + if (l == 0) goto err; + snprintf(tmp_config->hosts[i].name, sizeof(tmp_config->hosts[i].name), "%.*s", l, ptr); + i++; + section = 'h'; + } else if (strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) { + ptr += 4; + while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++; + while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++; + if (l == 0) goto err; + snprintf(tmp_config->certs[j].name, sizeof(tmp_config->certs[j].name), "%.*s", l, ptr); + j++; + section = 'c'; + } else { + goto err; + } 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')) { + } else if (section == 0) { + 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]; + } else if (section == 'c') { + cert_config *cc = &tmp_config->certs[j - 1]; + if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) { + source = ptr + 11; + target = cc->full_chain; + } else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) { + source = ptr + 11; + target = cc->priv_key; + } + } else if (section == 'h') { + host_config *hc = &tmp_config->hosts[i - 1]; if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) { source = ptr + 7; target = hc->local.webroot; @@ -112,6 +135,9 @@ int config_load(const char *filename) { } else { hc->type = CONFIG_TYPE_LOCAL; } + } else if (len > 5 && strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) { + source = ptr + 4; + target = hc->cert_name; } else if (len > 9 && strncmp(ptr, "dir_mode", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) { source = ptr + 8; target = NULL; @@ -155,6 +181,8 @@ int config_load(const char *filename) { } continue; } + } else { + goto err; } char *end_ptr = source + strlen(source) - 1; while (source[0] == ' ' || source[0] == '\t') source++; @@ -171,27 +199,43 @@ int config_load(const char *filename) { strcpy(target, source); } else if (mode == 1) { if (strcmp(source, "forbidden") == 0) { - tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN; + tmp_config->hosts[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; + tmp_config->hosts[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; + tmp_config->hosts[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); + tmp_config->hosts[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10); } } free(conf); - for (int j = 0; j < i; j++) { - if (tmp_config[j].type == CONFIG_TYPE_LOCAL) { - char *webroot = tmp_config[j].local.webroot; + for (int k = 0; k < i; k++) { + host_config *hc = &tmp_config->hosts[k]; + if (hc->type == CONFIG_TYPE_LOCAL) { + char *webroot = tmp_config->hosts[k].local.webroot; if (webroot[strlen(webroot) - 1] == '/') { webroot[strlen(webroot) - 1] = 0; } } + if (hc->cert_name[0] == 0) goto err2; + int found = 0; + for (int m = 0; m < j; m++) { + if (strcmp(tmp_config->certs[m].name, hc->cert_name) == 0) { + hc->cert = m; + found = 1; + break; + } + } + if (!found) { + err2: + free(tmp_config); + fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n"); + return -2; + } } int shm_id = shmget(CONFIG_SHM_KEY, 0, 0); @@ -207,7 +251,7 @@ int config_load(const char *filename) { fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno)); return -4; } - memcpy(shm_rw, tmp_config, CONFIG_MAX_HOST_CONFIG * sizeof(host_config)); + memcpy(shm_rw, tmp_config, sizeof(t_config)); free(tmp_config); shmdt(shm_rw); return 0; diff --git a/src/lib/config.h b/src/lib/config.h index b7aa7e5..6006cd0 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -12,6 +12,7 @@ #define CONFIG_SHM_KEY 255642 #define CONFIG_MAX_HOST_CONFIG 64 +#define CONFIG_MAX_CERT_CONFIG 64 #define CONFIG_TYPE_UNSET 0 #define CONFIG_TYPE_LOCAL 1 @@ -25,6 +26,8 @@ typedef struct { int type; char name[256]; + char cert_name[256]; + int cert; union { struct { char hostname[256]; @@ -38,8 +41,19 @@ typedef struct { }; } host_config; -extern host_config *config; -extern char cert_file[256], key_file[256], geoip_dir[256], dns_server[256]; +typedef struct { + char name[256]; + char full_chain[256]; + char priv_key[256]; +} cert_config; + +typedef struct { + host_config hosts[CONFIG_MAX_HOST_CONFIG]; + cert_config certs[CONFIG_MAX_CERT_CONFIG]; +} t_config; + +extern t_config *config; +extern char geoip_dir[256], dns_server[256]; int config_init(); diff --git a/src/necronda-server.c b/src/necronda-server.c index 08ea17b..0d3802c 100644 --- a/src/necronda-server.c +++ b/src/necronda-server.c @@ -38,6 +38,7 @@ const char *config_file; int sockets[NUM_SOCKETS]; pid_t children[MAX_CHILDREN]; MMDB_s mmdbs[MAX_MMDB]; +SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG]; void openssl_init() { SSL_library_init(); @@ -46,6 +47,21 @@ void openssl_init() { OpenSSL_add_all_algorithms(); } +static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) { + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername != NULL) { + for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) { + const host_config *conf = &config->hosts[i]; + if (conf->type == CONFIG_TYPE_UNSET) break; + if (strcmp(conf->name, servername) == 0) { + SSL_set_SSL_CTX(ssl, contexts[conf->cert]); + break; + } + } + } + return SSL_TLSEXT_ERR_OK; +} + void destroy() { fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n"); int status = 0; @@ -290,29 +306,40 @@ int main(int argc, const char *argv[]) { client.buf = NULL; client.buf_len = 0; client.buf_off = 0; - client.ctx = SSL_CTX_new(TLS_server_method()); - SSL_CTX_set_options(client.ctx, SSL_OP_SINGLE_DH_USE); - SSL_CTX_set_verify(client.ctx, SSL_VERIFY_NONE, NULL); - SSL_CTX_set_min_proto_version(client.ctx, TLS1_2_VERSION); - SSL_CTX_set_mode(client.ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); - SSL_CTX_set_cipher_list(client.ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"); - SSL_CTX_set_ecdh_auto(client.ctx, 1); + + for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) { + const cert_config *conf = &config->certs[i]; + if (conf->name[0] == 0) break; + + contexts[i] = SSL_CTX_new(TLS_server_method()); + SSL_CTX *ctx = contexts[i]; + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"); + SSL_CTX_set_ecdh_auto(ctx, 1); + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); + + if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) { + fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n", + ERR_reason_error_string(ERR_get_error()), conf->full_chain); + config_unload(); + return 1; + } + if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, 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()), conf->priv_key); + config_unload(); + return 1; + } + } + + client.ctx = contexts[0]; + rev_proxy_preload(); - 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));