From 6e9556b6354abcac6191e944b07b5def808b8073 Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Tue, 12 May 2020 17:52:44 +0200 Subject: [PATCH] REORG: ssl: move crtlist functions to src/ssl_crtlist.c Move the crtlist functions to src/ssl_crtlist.c and their definitions to proto/ssl_crtlist.h. The following functions were moved: /* crt-list entry functions */ void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf); char **crtlist_dup_filters(char **args, int fcount); void crtlist_free_filters(char **args); void crtlist_entry_free(struct crtlist_entry *entry); struct crtlist_entry *crtlist_entry_new(); /* crt-list functions */ void crtlist_free(struct crtlist *crtlist); struct crtlist *crtlist_new(const char *filename, int unique); /* file loading */ int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err); int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err); int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err); --- Makefile | 2 +- include/proto/ssl_crtlist.h | 45 +++ src/ssl_crtlist.c | 575 ++++++++++++++++++++++++++++++++++++ src/ssl_sock.c | 543 +--------------------------------- 4 files changed, 622 insertions(+), 543 deletions(-) create mode 100644 include/proto/ssl_crtlist.h create mode 100644 src/ssl_crtlist.c diff --git a/Makefile b/Makefile index 1e4213989..cba041824 100644 --- a/Makefile +++ b/Makefile @@ -542,7 +542,7 @@ OPTIONS_LDFLAGS += $(if $(SSL_LIB),-L$(SSL_LIB)) -lssl -lcrypto ifneq ($(USE_DL),) OPTIONS_LDFLAGS += -ldl endif -OPTIONS_OBJS += src/ssl_sock.o +OPTIONS_OBJS += src/ssl_sock.o src/ssl_crtlist.o endif # The private cache option affect the way the shctx is built diff --git a/include/proto/ssl_crtlist.h b/include/proto/ssl_crtlist.h new file mode 100644 index 000000000..5b75ac7aa --- /dev/null +++ b/include/proto/ssl_crtlist.h @@ -0,0 +1,45 @@ +/* + * include/proto/ssl_crtlist.h + * crt-list function prototyes + * + * Copyright (C) 2020 HAProxy Technologies, William Lallemand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PROTO_SSL_CRTLIST_H +#define _PROTO_SSL_CRTLIST_H +#ifdef USE_OPENSSL + +#include + + +/* crt-list entry functions */ +void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf); +char **crtlist_dup_filters(char **args, int fcount); +void crtlist_free_filters(char **args); +void crtlist_entry_free(struct crtlist_entry *entry); +struct crtlist_entry *crtlist_entry_new(); + +/* crt-list functions */ +void crtlist_free(struct crtlist *crtlist); +struct crtlist *crtlist_new(const char *filename, int unique); + +/* file loading */ +int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err); +int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err); +int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err); +#endif /* USE_OPENSSL */ +#endif /* _PROTO_SSL_CRTLIST_H */ diff --git a/src/ssl_crtlist.c b/src/ssl_crtlist.c new file mode 100644 index 000000000..cf3382f9d --- /dev/null +++ b/src/ssl_crtlist.c @@ -0,0 +1,575 @@ +/* + * + * Copyright (C) 2020 HAProxy Technologies, William Lallemand + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +/* release ssl bind conf */ +void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf) +{ + if (conf) { +#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) + free(conf->npn_str); + conf->npn_str = NULL; +#endif +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + free(conf->alpn_str); + conf->alpn_str = NULL; +#endif + free(conf->ca_file); + conf->ca_file = NULL; + free(conf->ca_verify_file); + conf->ca_verify_file = NULL; + free(conf->crl_file); + conf->crl_file = NULL; + free(conf->ciphers); + conf->ciphers = NULL; +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) + free(conf->ciphersuites); + conf->ciphersuites = NULL; +#endif + free(conf->curves); + conf->curves = NULL; + free(conf->ecdhe); + conf->ecdhe = NULL; + } +} + + +/* free sni filters */ +void crtlist_free_filters(char **args) +{ + int i; + + if (!args) + return; + + for (i = 0; args[i]; i++) + free(args[i]); + + free(args); +} + +/* Alloc and duplicate a char ** array */ +char **crtlist_dup_filters(char **args, int fcount) +{ + char **dst; + int i; + + if (fcount == 0) + return NULL; + + dst = calloc(fcount + 1, sizeof(*dst)); + if (!dst) + return NULL; + + for (i = 0; i < fcount; i++) { + dst[i] = strdup(args[i]); + if (!dst[i]) + goto error; + } + return dst; + +error: + crtlist_free_filters(dst); + return NULL; +} + +/* + * Detach and free a crtlist_entry. + * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst + */ +void crtlist_entry_free(struct crtlist_entry *entry) +{ + struct ckch_inst *inst, *inst_s; + + if (entry == NULL) + return; + + ebpt_delete(&entry->node); + LIST_DEL(&entry->by_crtlist); + LIST_DEL(&entry->by_ckch_store); + crtlist_free_filters(entry->filters); + ssl_sock_free_ssl_conf(entry->ssl_conf); + free(entry->ssl_conf); + list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) { + ckch_inst_free(inst); + } + free(entry); +} + +/* + * Allocate and initialize a crtlist_entry + */ +struct crtlist_entry *crtlist_entry_new() +{ + struct crtlist_entry *entry; + + entry = calloc(1, sizeof(*entry)); + if (entry == NULL) + return NULL; + + LIST_INIT(&entry->ckch_inst); + + /* initialize the nodes so we can LIST_DEL in any cases */ + LIST_INIT(&entry->by_crtlist); + LIST_INIT(&entry->by_ckch_store); + + return entry; +} + +/* Free a crtlist, from the crt_entry to the content of the ssl_conf */ +void crtlist_free(struct crtlist *crtlist) +{ + struct crtlist_entry *entry, *s_entry; + + if (crtlist == NULL) + return; + + list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) { + crtlist_entry_free(entry); + } + ebmb_delete(&crtlist->node); + free(crtlist); +} + +/* Alloc and initialize a struct crtlist + * is the key of the ebmb_node + * initialize the list of entries to be unique (1) or not (0) + */ +struct crtlist *crtlist_new(const char *filename, int unique) +{ + struct crtlist *newlist; + + newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1); + if (newlist == NULL) + return NULL; + + memcpy(newlist->node.key, filename, strlen(filename) + 1); + if (unique) + newlist->entries = EB_ROOT_UNIQUE; + else + newlist->entries = EB_ROOT; + + LIST_INIT(&newlist->ord_entries); + + return newlist; +} + +/* + * Read a single crt-list line. /!\ alter the string. + * Fill and + * must be alloc and free by the caller + * ssl_conf> is alloc by the function + * filters> is alloc by the function + * is a ptr in + * Return an error code + */ +int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err) +{ + int cfgerr = 0; + int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0; + char *end; + char *args[MAX_CRT_ARGS + 1]; + struct ssl_bind_conf *ssl_conf = NULL; + + if (!line || !crt_path || !entry) + return ERR_ALERT | ERR_FATAL; + + end = line + strlen(line); + if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') { + /* Check if we reached the limit and the last char is not \n. + * Watch out for the last line without the terminating '\n'! + */ + memprintf(err, "line %d too long in file '%s', limit is %d characters", + linenum, file, CRT_LINESIZE-1); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + arg = 0; + newarg = 1; + while (*line) { + if (isspace((unsigned char)*line)) { + newarg = 1; + *line = 0; + } else if (*line == '[') { + if (ssl_b) { + memprintf(err, "too many '[' on line %d in file '%s'.", linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + if (!arg) { + memprintf(err, "file must start with a cert on line %d in file '%s'", linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + ssl_b = arg; + newarg = 1; + *line = 0; + } else if (*line == ']') { + if (ssl_e) { + memprintf(err, "too many ']' on line %d in file '%s'.", linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + if (!ssl_b) { + memprintf(err, "missing '[' in line %d in file '%s'.", linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + ssl_e = arg; + newarg = 1; + *line = 0; + } else if (newarg) { + if (arg == MAX_CRT_ARGS) { + memprintf(err, "too many args on line %d in file '%s'.", linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + newarg = 0; + args[arg++] = line; + } + line++; + } + args[arg++] = line; + + /* empty line */ + if (!*args[0]) { + cfgerr |= ERR_NONE; + goto error; + } + + *crt_path = args[0]; + + if (ssl_b) { + ssl_conf = calloc(1, sizeof *ssl_conf); + if (!ssl_conf) { + memprintf(err, "not enough memory!"); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + } + cur_arg = ssl_b ? ssl_b : 1; + while (cur_arg < ssl_e) { + newarg = 0; + for (i = 0; ssl_bind_kws[i].kw != NULL; i++) { + if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) { + newarg = 1; + cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, err); + if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) { + memprintf(err, "ssl args out of '[]' for %s on line %d in file '%s'", + args[cur_arg], linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + cur_arg += 1 + ssl_bind_kws[i].skip; + break; + } + } + if (!cfgerr && !newarg) { + memprintf(err, "unknown ssl keyword %s on line %d in file '%s'.", + args[cur_arg], linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + } + entry->linenum = linenum; + entry->ssl_conf = ssl_conf; + entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1); + entry->fcount = arg - cur_arg - 1; + + return cfgerr; + +error: + crtlist_free_filters(entry->filters); + entry->filters = NULL; + ssl_sock_free_ssl_conf(entry->ssl_conf); + free(entry->ssl_conf); + entry->ssl_conf = NULL; + return cfgerr; +} + + + +/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure + * Fill the argument with a pointer to a new crtlist struct + * + * This function tries to open and store certificate files. + */ +int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err) +{ + struct crtlist *newlist; + struct crtlist_entry *entry = NULL; + char thisline[CRT_LINESIZE]; + char path[MAXPATHLEN+1]; + FILE *f; + struct stat buf; + int linenum = 0; + int cfgerr = 0; + + if ((f = fopen(file, "r")) == NULL) { + memprintf(err, "cannot open file '%s' : %s", file, strerror(errno)); + return ERR_ALERT | ERR_FATAL; + } + + newlist = crtlist_new(file, 0); + if (newlist == NULL) { + memprintf(err, "Not enough memory!"); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + + while (fgets(thisline, sizeof(thisline), f) != NULL) { + char *end; + char *line = thisline; + char *crt_path; + struct ckch_store *ckchs; + + linenum++; + end = line + strlen(line); + if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') { + /* Check if we reached the limit and the last char is not \n. + * Watch out for the last line without the terminating '\n'! + */ + memprintf(err, "line %d too long in file '%s', limit is %d characters", + linenum, file, (int)sizeof(thisline)-1); + cfgerr |= ERR_ALERT | ERR_FATAL; + break; + } + + if (*line == '#' || *line == '\n' || *line == '\r') + continue; + + entry = crtlist_entry_new(); + if (entry == NULL) { + memprintf(err, "Not enough memory!"); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + + *(end - 1) = '\0'; /* line parser mustn't receive any \n */ + cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, err); + if (cfgerr) + goto error; + + /* empty line */ + if (!crt_path || !*crt_path) { + crtlist_entry_free(entry); + entry = NULL; + continue; + } + + if (*crt_path != '/' && global_ssl.crt_base) { + if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) { + memprintf(err, "'%s' : path too long on line %d in file '%s'", + crt_path, linenum, file); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto error; + } + snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path); + crt_path = path; + } + + /* Look for a ckch_store or create one */ + ckchs = ckchs_lookup(crt_path); + if (ckchs == NULL) { + if (stat(crt_path, &buf) == 0) + ckchs = ckchs_load_cert_file(crt_path, 0, err); + else + ckchs = ckchs_load_cert_file(crt_path, 1, err); + } + if (ckchs == NULL) + cfgerr |= ERR_ALERT | ERR_FATAL; + + if (cfgerr & ERR_CODE) + goto error; + + entry->node.key = ckchs; + entry->crtlist = newlist; + ebpt_insert(&newlist->entries, &entry->node); + LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist); + LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store); + + entry = NULL; + } + if (cfgerr & ERR_CODE) + goto error; + + newlist->linecount = linenum; + + fclose(f); + *crtlist = newlist; + + return cfgerr; +error: + crtlist_entry_free(entry); + + fclose(f); + crtlist_free(newlist); + return cfgerr; +} + +/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure + * Fill the argument with a pointer to a new crtlist struct + * + * This function tries to open and store certificate files. + */ +int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err) +{ + struct crtlist *dir; + struct dirent **de_list; + int i, n; + struct stat buf; + char *end; + char fp[MAXPATHLEN+1]; + int cfgerr = 0; + struct ckch_store *ckchs; +#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL + int is_bundle; + int j; +#endif + + dir = crtlist_new(path, 1); + if (dir == NULL) { + memprintf(err, "not enough memory"); + return ERR_ALERT | ERR_FATAL; + } + + n = scandir(path, &de_list, 0, alphasort); + if (n < 0) { + memprintf(err, "%sunable to scan directory '%s' : %s.\n", + err && *err ? *err : "", path, strerror(errno)); + cfgerr |= ERR_ALERT | ERR_FATAL; + } + else { + for (i = 0; i < n; i++) { + struct crtlist_entry *entry; + struct dirent *de = de_list[i]; + + end = strrchr(de->d_name, '.'); + if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl") || !strcmp(end, ".key"))) + goto ignore_entry; + + snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name); + if (stat(fp, &buf) != 0) { + memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", + err && *err ? *err : "", fp, strerror(errno)); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto ignore_entry; + } + if (!S_ISREG(buf.st_mode)) + goto ignore_entry; + + entry = crtlist_entry_new(); + if (entry == NULL) { + memprintf(err, "not enough memory '%s'", fp); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto ignore_entry; + } + +#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL + is_bundle = 0; + /* Check if current entry in directory is part of a multi-cert bundle */ + + if ((global_ssl.extra_files & SSL_GF_BUNDLE) && end) { + for (j = 0; j < SSL_SOCK_NUM_KEYTYPES; j++) { + if (!strcmp(end + 1, SSL_SOCK_KEYTYPE_NAMES[j])) { + is_bundle = 1; + break; + } + } + + if (is_bundle) { + int dp_len; + + dp_len = end - de->d_name; + + /* increment i and free de until we get to a non-bundle cert + * Note here that we look at de_list[i + 1] before freeing de + * this is important since ignore_entry will free de. This also + * guarantees that de->d_name continues to hold the same prefix. + */ + while (i + 1 < n && !strncmp(de_list[i + 1]->d_name, de->d_name, dp_len)) { + free(de); + i++; + de = de_list[i]; + } + + snprintf(fp, sizeof(fp), "%s/%.*s", path, dp_len, de->d_name); + ckchs = ckchs_lookup(fp); + if (ckchs == NULL) + ckchs = ckchs_load_cert_file(fp, 1, err); + if (ckchs == NULL) { + free(de); + free(entry); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto end; + } + entry->node.key = ckchs; + entry->crtlist = dir; + LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store); + LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist); + ebpt_insert(&dir->entries, &entry->node); + + /* Successfully processed the bundle */ + goto ignore_entry; + } + } + +#endif + ckchs = ckchs_lookup(fp); + if (ckchs == NULL) + ckchs = ckchs_load_cert_file(fp, 0, err); + if (ckchs == NULL) { + free(de); + free(entry); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto end; + } + entry->node.key = ckchs; + entry->crtlist = dir; + LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store); + LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist); + ebpt_insert(&dir->entries, &entry->node); + +ignore_entry: + free(de); + } +end: + free(de_list); + } + + if (cfgerr & ERR_CODE) { + /* free the dir and entries on error */ + crtlist_free(dir); + } else { + *crtlist = dir; + } + return cfgerr; + +} + diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 0da24c48c..266b7d5dd 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -85,6 +85,7 @@ #include #include #include +#include #include #include #include @@ -2876,37 +2877,6 @@ int ssl_sock_load_global_dh_param_from_file(const char *filename) } #endif -/* release ssl bind conf */ -void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf) -{ - if (conf) { -#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) - free(conf->npn_str); - conf->npn_str = NULL; -#endif -#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - free(conf->alpn_str); - conf->alpn_str = NULL; -#endif - free(conf->ca_file); - conf->ca_file = NULL; - free(conf->ca_verify_file); - conf->ca_verify_file = NULL; - free(conf->crl_file); - conf->crl_file = NULL; - free(conf->ciphers); - conf->ciphers = NULL; -#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) - free(conf->ciphersuites); - conf->ciphersuites = NULL; -#endif - free(conf->curves); - conf->curves = NULL; - free(conf->ecdhe); - conf->ecdhe = NULL; - } -} - /* unlink a ckch_inst, free all SNIs, free the ckch_inst */ /* The caller must use the lock of the bind_conf if used with inserted SNIs */ void ckch_inst_free(struct ckch_inst *inst) @@ -2943,125 +2913,6 @@ struct ckch_inst *ckch_inst_new() return ckch_inst; } -/* free sni filters */ -static void crtlist_free_filters(char **args) -{ - int i; - - if (!args) - return; - - for (i = 0; args[i]; i++) - free(args[i]); - - free(args); -} - -/* Alloc and duplicate a char ** array */ -static char **crtlist_dup_filters(char **args, int fcount) -{ - char **dst; - int i; - - if (fcount == 0) - return NULL; - - dst = calloc(fcount + 1, sizeof(*dst)); - if (!dst) - return NULL; - - for (i = 0; i < fcount; i++) { - dst[i] = strdup(args[i]); - if (!dst[i]) - goto error; - } - return dst; - -error: - crtlist_free_filters(dst); - return NULL; -} - -/* - * Detach and free a crtlist_entry. - * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst - */ -static void crtlist_entry_free(struct crtlist_entry *entry) -{ - struct ckch_inst *inst, *inst_s; - - if (entry == NULL) - return; - - ebpt_delete(&entry->node); - LIST_DEL(&entry->by_crtlist); - LIST_DEL(&entry->by_ckch_store); - crtlist_free_filters(entry->filters); - ssl_sock_free_ssl_conf(entry->ssl_conf); - free(entry->ssl_conf); - list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) { - ckch_inst_free(inst); - } - free(entry); -} - -/* - * Allocate and initialize a crtlist_entry - */ -static struct crtlist_entry *crtlist_entry_new() -{ - struct crtlist_entry *entry; - - entry = calloc(1, sizeof(*entry)); - if (entry == NULL) - return NULL; - - LIST_INIT(&entry->ckch_inst); - - /* initialize the nodes so we can LIST_DEL in any cases */ - LIST_INIT(&entry->by_crtlist); - LIST_INIT(&entry->by_ckch_store); - - return entry; -} - -/* Free a crtlist, from the crt_entry to the content of the ssl_conf */ -static void crtlist_free(struct crtlist *crtlist) -{ - struct crtlist_entry *entry, *s_entry; - - if (crtlist == NULL) - return; - - list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) { - crtlist_entry_free(entry); - } - ebmb_delete(&crtlist->node); - free(crtlist); -} - -/* Alloc and initialize a struct crtlist - * is the key of the ebmb_node - * initialize the list of entries to be unique (1) or not (0) - */ -static struct crtlist *crtlist_new(const char *filename, int unique) -{ - struct crtlist *newlist; - - newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1); - if (newlist == NULL) - return NULL; - - memcpy(newlist->node.key, filename, strlen(filename) + 1); - if (unique) - newlist->entries = EB_ROOT_UNIQUE; - else - newlist->entries = EB_ROOT; - - LIST_INIT(&newlist->ord_entries); - - return newlist; -} /* This function allocates a sni_ctx and adds it to the ckch_inst */ static int ckch_inst_add_cert_sni(SSL_CTX *ctx, struct ckch_inst *ckch_inst, @@ -4536,398 +4387,6 @@ static int ssl_initialize_random() return random_initialized; } - -/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure - * Fill the argument with a pointer to a new crtlist struct - * - * This function tries to open and store certificate files. - */ -static int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err) -{ - struct crtlist *dir; - struct dirent **de_list; - int i, n; - struct stat buf; - char *end; - char fp[MAXPATHLEN+1]; - int cfgerr = 0; - struct ckch_store *ckchs; -#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL - int is_bundle; - int j; -#endif - - dir = crtlist_new(path, 1); - if (dir == NULL) { - memprintf(err, "not enough memory"); - return ERR_ALERT | ERR_FATAL; - } - - n = scandir(path, &de_list, 0, alphasort); - if (n < 0) { - memprintf(err, "%sunable to scan directory '%s' : %s.\n", - err && *err ? *err : "", path, strerror(errno)); - cfgerr |= ERR_ALERT | ERR_FATAL; - } - else { - for (i = 0; i < n; i++) { - struct crtlist_entry *entry; - struct dirent *de = de_list[i]; - - end = strrchr(de->d_name, '.'); - if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl") || !strcmp(end, ".key"))) - goto ignore_entry; - - snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name); - if (stat(fp, &buf) != 0) { - memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", - err && *err ? *err : "", fp, strerror(errno)); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto ignore_entry; - } - if (!S_ISREG(buf.st_mode)) - goto ignore_entry; - - entry = crtlist_entry_new(); - if (entry == NULL) { - memprintf(err, "not enough memory '%s'", fp); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto ignore_entry; - } - -#if HA_OPENSSL_VERSION_NUMBER >= 0x1000200fL - is_bundle = 0; - /* Check if current entry in directory is part of a multi-cert bundle */ - - if ((global_ssl.extra_files & SSL_GF_BUNDLE) && end) { - for (j = 0; j < SSL_SOCK_NUM_KEYTYPES; j++) { - if (!strcmp(end + 1, SSL_SOCK_KEYTYPE_NAMES[j])) { - is_bundle = 1; - break; - } - } - - if (is_bundle) { - int dp_len; - - dp_len = end - de->d_name; - - /* increment i and free de until we get to a non-bundle cert - * Note here that we look at de_list[i + 1] before freeing de - * this is important since ignore_entry will free de. This also - * guarantees that de->d_name continues to hold the same prefix. - */ - while (i + 1 < n && !strncmp(de_list[i + 1]->d_name, de->d_name, dp_len)) { - free(de); - i++; - de = de_list[i]; - } - - snprintf(fp, sizeof(fp), "%s/%.*s", path, dp_len, de->d_name); - ckchs = ckchs_lookup(fp); - if (ckchs == NULL) - ckchs = ckchs_load_cert_file(fp, 1, err); - if (ckchs == NULL) { - free(de); - free(entry); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto end; - } - entry->node.key = ckchs; - entry->crtlist = dir; - LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store); - LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist); - ebpt_insert(&dir->entries, &entry->node); - - /* Successfully processed the bundle */ - goto ignore_entry; - } - } - -#endif - ckchs = ckchs_lookup(fp); - if (ckchs == NULL) - ckchs = ckchs_load_cert_file(fp, 0, err); - if (ckchs == NULL) { - free(de); - free(entry); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto end; - } - entry->node.key = ckchs; - entry->crtlist = dir; - LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store); - LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist); - ebpt_insert(&dir->entries, &entry->node); - -ignore_entry: - free(de); - } -end: - free(de_list); - } - - if (cfgerr & ERR_CODE) { - /* free the dir and entries on error */ - crtlist_free(dir); - } else { - *crtlist = dir; - } - return cfgerr; - -} - -/* - * Read a single crt-list line. /!\ alter the string. - * Fill and - * must be alloc and free by the caller - * ssl_conf> is alloc by the function - * filters> is alloc by the function - * is a ptr in - * Return an error code - */ -static int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err) -{ - int cfgerr = 0; - int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0; - char *end; - char *args[MAX_CRT_ARGS + 1]; - struct ssl_bind_conf *ssl_conf = NULL; - - if (!line || !crt_path || !entry) - return ERR_ALERT | ERR_FATAL; - - end = line + strlen(line); - if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') { - /* Check if we reached the limit and the last char is not \n. - * Watch out for the last line without the terminating '\n'! - */ - memprintf(err, "line %d too long in file '%s', limit is %d characters", - linenum, file, CRT_LINESIZE-1); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - arg = 0; - newarg = 1; - while (*line) { - if (isspace((unsigned char)*line)) { - newarg = 1; - *line = 0; - } else if (*line == '[') { - if (ssl_b) { - memprintf(err, "too many '[' on line %d in file '%s'.", linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - if (!arg) { - memprintf(err, "file must start with a cert on line %d in file '%s'", linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - ssl_b = arg; - newarg = 1; - *line = 0; - } else if (*line == ']') { - if (ssl_e) { - memprintf(err, "too many ']' on line %d in file '%s'.", linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - if (!ssl_b) { - memprintf(err, "missing '[' in line %d in file '%s'.", linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - ssl_e = arg; - newarg = 1; - *line = 0; - } else if (newarg) { - if (arg == MAX_CRT_ARGS) { - memprintf(err, "too many args on line %d in file '%s'.", linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - newarg = 0; - args[arg++] = line; - } - line++; - } - args[arg++] = line; - - /* empty line */ - if (!*args[0]) { - cfgerr |= ERR_NONE; - goto error; - } - - *crt_path = args[0]; - - if (ssl_b) { - ssl_conf = calloc(1, sizeof *ssl_conf); - if (!ssl_conf) { - memprintf(err, "not enough memory!"); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - } - cur_arg = ssl_b ? ssl_b : 1; - while (cur_arg < ssl_e) { - newarg = 0; - for (i = 0; ssl_bind_kws[i].kw != NULL; i++) { - if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) { - newarg = 1; - cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, err); - if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) { - memprintf(err, "ssl args out of '[]' for %s on line %d in file '%s'", - args[cur_arg], linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - cur_arg += 1 + ssl_bind_kws[i].skip; - break; - } - } - if (!cfgerr && !newarg) { - memprintf(err, "unknown ssl keyword %s on line %d in file '%s'.", - args[cur_arg], linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - } - entry->linenum = linenum; - entry->ssl_conf = ssl_conf; - entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1); - entry->fcount = arg - cur_arg - 1; - - return cfgerr; - -error: - crtlist_free_filters(entry->filters); - entry->filters = NULL; - ssl_sock_free_ssl_conf(entry->ssl_conf); - free(entry->ssl_conf); - entry->ssl_conf = NULL; - return cfgerr; -} - -/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure - * Fill the argument with a pointer to a new crtlist struct - * - * This function tries to open and store certificate files. - */ -static int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err) -{ - struct crtlist *newlist; - struct crtlist_entry *entry = NULL; - char thisline[CRT_LINESIZE]; - char path[MAXPATHLEN+1]; - FILE *f; - struct stat buf; - int linenum = 0; - int cfgerr = 0; - - if ((f = fopen(file, "r")) == NULL) { - memprintf(err, "cannot open file '%s' : %s", file, strerror(errno)); - return ERR_ALERT | ERR_FATAL; - } - - newlist = crtlist_new(file, 0); - if (newlist == NULL) { - memprintf(err, "Not enough memory!"); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - - while (fgets(thisline, sizeof(thisline), f) != NULL) { - char *end; - char *line = thisline; - char *crt_path; - struct ckch_store *ckchs; - - linenum++; - end = line + strlen(line); - if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') { - /* Check if we reached the limit and the last char is not \n. - * Watch out for the last line without the terminating '\n'! - */ - memprintf(err, "line %d too long in file '%s', limit is %d characters", - linenum, file, (int)sizeof(thisline)-1); - cfgerr |= ERR_ALERT | ERR_FATAL; - break; - } - - if (*line == '#' || *line == '\n' || *line == '\r') - continue; - - entry = crtlist_entry_new(); - if (entry == NULL) { - memprintf(err, "Not enough memory!"); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - - *(end - 1) = '\0'; /* line parser mustn't receive any \n */ - cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, err); - if (cfgerr) - goto error; - - /* empty line */ - if (!crt_path || !*crt_path) { - crtlist_entry_free(entry); - entry = NULL; - continue; - } - - if (*crt_path != '/' && global_ssl.crt_base) { - if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) { - memprintf(err, "'%s' : path too long on line %d in file '%s'", - crt_path, linenum, file); - cfgerr |= ERR_ALERT | ERR_FATAL; - goto error; - } - snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path); - crt_path = path; - } - - /* Look for a ckch_store or create one */ - ckchs = ckchs_lookup(crt_path); - if (ckchs == NULL) { - if (stat(crt_path, &buf) == 0) - ckchs = ckchs_load_cert_file(crt_path, 0, err); - else - ckchs = ckchs_load_cert_file(crt_path, 1, err); - } - if (ckchs == NULL) - cfgerr |= ERR_ALERT | ERR_FATAL; - - if (cfgerr & ERR_CODE) - goto error; - - entry->node.key = ckchs; - entry->crtlist = newlist; - ebpt_insert(&newlist->entries, &entry->node); - LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist); - LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store); - - entry = NULL; - } - if (cfgerr & ERR_CODE) - goto error; - - newlist->linecount = linenum; - - fclose(f); - *crtlist = newlist; - - return cfgerr; -error: - crtlist_entry_free(entry); - - fclose(f); - crtlist_free(newlist); - return cfgerr; -} - /* Load a crt-list file, this is done in 2 parts: * - store the content of the file in a crtlist structure with crtlist_entry structures * - generate the instances by iterating on entries in the crtlist struct