blob: e7bbc82f6d9cdad7dec12b810b23e0c345267690 [file] [log] [blame]
William Lallemand6e9556b2020-05-12 17:52:44 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
Willy Tarreaub2551052020-06-09 09:07:15 +020011#include <sys/stat.h>
12#include <sys/types.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020013
Willy Tarreaub2551052020-06-09 09:07:15 +020014#include <dirent.h>
William Lallemand212e9932020-05-18 08:33:09 +020015#include <errno.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020016#include <stdlib.h>
17#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020018#include <syslog.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020019
20#include <import/ebpttree.h>
21#include <import/ebsttree.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020022
Willy Tarreauf1d32c42020-06-04 21:07:02 +020023#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020024#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020025#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020026#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020027#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020028#include <haproxy/ssl_sock.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020029#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020030#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020031
William Lallemand6e9556b2020-05-12 17:52:44 +020032
William Lallemand6e9556b2020-05-12 17:52:44 +020033/* release ssl bind conf */
34void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
35{
36 if (conf) {
37#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
38 free(conf->npn_str);
39 conf->npn_str = NULL;
40#endif
41#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
42 free(conf->alpn_str);
43 conf->alpn_str = NULL;
44#endif
45 free(conf->ca_file);
46 conf->ca_file = NULL;
47 free(conf->ca_verify_file);
48 conf->ca_verify_file = NULL;
49 free(conf->crl_file);
50 conf->crl_file = NULL;
51 free(conf->ciphers);
52 conf->ciphers = NULL;
53#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
54 free(conf->ciphersuites);
55 conf->ciphersuites = NULL;
56#endif
57 free(conf->curves);
58 conf->curves = NULL;
59 free(conf->ecdhe);
60 conf->ecdhe = NULL;
61 }
62}
63
William Lallemand82f2d2f2020-09-10 19:06:43 +020064/*
65 * Allocate and copy a ssl_bind_conf structure
66 */
67struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
68{
69 struct ssl_bind_conf *dst;
70
71 if (!src)
72 return NULL;
73
74 dst = calloc(1, sizeof(*dst));
75 if (!dst)
76 return NULL;
77
78#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
79 if (src->npn_str) {
80 dst->npn_str = strdup(src->npn_str);
81 if (!dst->npn_str)
82 goto error;
83 }
84#endif
85#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
86 if (src->alpn_str) {
87 dst->alpn_str = strdup(src->alpn_str);
88 if (!dst->alpn_str)
89 goto error;
90 }
91#endif
92 if (src->ca_file) {
93 dst->ca_file = strdup(src->ca_file);
94 if (!dst->ca_file)
95 goto error;
96 }
97 if (src->ca_verify_file) {
98 dst->ca_verify_file = strdup(src->ca_verify_file);
99 if (!dst->ca_verify_file)
100 goto error;
101 }
102 if (src->crl_file) {
103 dst->crl_file = strdup(src->crl_file);
104 if (!dst->crl_file)
105 goto error;
106 }
107 if (src->ciphers) {
108 dst->ciphers = strdup(src->ciphers);
109 if (!dst->ciphers)
110 goto error;
111 }
112#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
113 if (src->ciphersuites) {
114 dst->ciphersuites = strdup(src->ciphersuites);
115 if (!dst->ciphersuites)
116 goto error;
117 }
118#endif
119 if (src->curves) {
120 dst->curves = strdup(src->curves);
121 if (!dst->curves)
122 goto error;
123 }
124 if (src->ecdhe) {
125 dst->ecdhe = strdup(src->ecdhe);
126 if (!dst->ecdhe)
127 goto error;
128 }
129 return dst;
130
131error:
132 ssl_sock_free_ssl_conf(dst);
133 free(dst);
134
135 return NULL;
136}
William Lallemand6e9556b2020-05-12 17:52:44 +0200137
138/* free sni filters */
139void crtlist_free_filters(char **args)
140{
141 int i;
142
143 if (!args)
144 return;
145
146 for (i = 0; args[i]; i++)
147 free(args[i]);
148
149 free(args);
150}
151
152/* Alloc and duplicate a char ** array */
153char **crtlist_dup_filters(char **args, int fcount)
154{
155 char **dst;
156 int i;
157
158 if (fcount == 0)
159 return NULL;
160
161 dst = calloc(fcount + 1, sizeof(*dst));
162 if (!dst)
163 return NULL;
164
165 for (i = 0; i < fcount; i++) {
166 dst[i] = strdup(args[i]);
167 if (!dst[i])
168 goto error;
169 }
170 return dst;
171
172error:
173 crtlist_free_filters(dst);
174 return NULL;
175}
176
177/*
178 * Detach and free a crtlist_entry.
179 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
180 */
181void crtlist_entry_free(struct crtlist_entry *entry)
182{
183 struct ckch_inst *inst, *inst_s;
184
185 if (entry == NULL)
186 return;
187
188 ebpt_delete(&entry->node);
189 LIST_DEL(&entry->by_crtlist);
190 LIST_DEL(&entry->by_ckch_store);
191 crtlist_free_filters(entry->filters);
192 ssl_sock_free_ssl_conf(entry->ssl_conf);
193 free(entry->ssl_conf);
194 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
195 ckch_inst_free(inst);
196 }
197 free(entry);
198}
William Lallemand5622c452020-09-10 19:08:49 +0200199/*
200 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
201 * Return a pointer to the new entry
202 */
203struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
204{
205 struct crtlist_entry *entry;
206
207 if (src == NULL)
208 return NULL;
209
210 entry = crtlist_entry_new();
211 if (entry == NULL)
212 return NULL;
213
214 if (src->filters) {
215 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
216 if (!entry->filters)
217 goto error;
218 }
219 entry->fcount = src->fcount;
220 if (src->ssl_conf) {
221 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
222 if (!entry->ssl_conf)
223 goto error;
224 }
225 entry->crtlist = src->crtlist;
226
227 return entry;
228
229error:
230
231 crtlist_free_filters(entry->filters);
232 ssl_sock_free_ssl_conf(entry->ssl_conf);
233 free(entry->ssl_conf);
234 free(entry);
235
236 return NULL;
237}
William Lallemand6e9556b2020-05-12 17:52:44 +0200238
239/*
240 * Allocate and initialize a crtlist_entry
241 */
242struct crtlist_entry *crtlist_entry_new()
243{
244 struct crtlist_entry *entry;
245
246 entry = calloc(1, sizeof(*entry));
247 if (entry == NULL)
248 return NULL;
249
250 LIST_INIT(&entry->ckch_inst);
251
252 /* initialize the nodes so we can LIST_DEL in any cases */
253 LIST_INIT(&entry->by_crtlist);
254 LIST_INIT(&entry->by_ckch_store);
255
256 return entry;
257}
258
259/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
260void crtlist_free(struct crtlist *crtlist)
261{
262 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200263 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200264
265 if (crtlist == NULL)
266 return;
267
William Lallemand6a3168a2020-06-23 11:43:35 +0200268 bind_conf_node = crtlist->bind_conf;
269 while (bind_conf_node) {
270 struct bind_conf_list *next = bind_conf_node->next;
271 free(bind_conf_node);
272 bind_conf_node = next;
273 }
274
William Lallemand6e9556b2020-05-12 17:52:44 +0200275 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
276 crtlist_entry_free(entry);
277 }
278 ebmb_delete(&crtlist->node);
279 free(crtlist);
280}
281
282/* Alloc and initialize a struct crtlist
283 * <filename> is the key of the ebmb_node
284 * <unique> initialize the list of entries to be unique (1) or not (0)
285 */
286struct crtlist *crtlist_new(const char *filename, int unique)
287{
288 struct crtlist *newlist;
289
290 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
291 if (newlist == NULL)
292 return NULL;
293
294 memcpy(newlist->node.key, filename, strlen(filename) + 1);
295 if (unique)
296 newlist->entries = EB_ROOT_UNIQUE;
297 else
298 newlist->entries = EB_ROOT;
299
300 LIST_INIT(&newlist->ord_entries);
301
302 return newlist;
303}
304
305/*
306 * Read a single crt-list line. /!\ alter the <line> string.
307 * Fill <crt_path> and <crtlist_entry>
308 * <crtlist_entry> must be alloc and free by the caller
309 * <crtlist_entry->ssl_conf> is alloc by the function
310 * <crtlist_entry->filters> is alloc by the function
311 * <crt_path> is a ptr in <line>
312 * Return an error code
313 */
Remi Tricot-Le Bretonbcec63e2021-03-23 16:41:53 +0100314int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, int from_cli, char **err)
William Lallemand6e9556b2020-05-12 17:52:44 +0200315{
316 int cfgerr = 0;
317 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
318 char *end;
319 char *args[MAX_CRT_ARGS + 1];
320 struct ssl_bind_conf *ssl_conf = NULL;
321
322 if (!line || !crt_path || !entry)
323 return ERR_ALERT | ERR_FATAL;
324
325 end = line + strlen(line);
326 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
327 /* Check if we reached the limit and the last char is not \n.
328 * Watch out for the last line without the terminating '\n'!
329 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200330 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
331 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200332 cfgerr |= ERR_ALERT | ERR_FATAL;
333 goto error;
334 }
335 arg = 0;
336 newarg = 1;
337 while (*line) {
338 if (isspace((unsigned char)*line)) {
339 newarg = 1;
340 *line = 0;
341 } else if (*line == '[') {
342 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200343 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200344 cfgerr |= ERR_ALERT | ERR_FATAL;
345 goto error;
346 }
347 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200348 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200349 cfgerr |= ERR_ALERT | ERR_FATAL;
350 goto error;
351 }
352 ssl_b = arg;
353 newarg = 1;
354 *line = 0;
355 } else if (*line == ']') {
356 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200357 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200358 cfgerr |= ERR_ALERT | ERR_FATAL;
359 goto error;
360 }
361 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200362 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200363 cfgerr |= ERR_ALERT | ERR_FATAL;
364 goto error;
365 }
366 ssl_e = arg;
367 newarg = 1;
368 *line = 0;
369 } else if (newarg) {
370 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200371 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200372 cfgerr |= ERR_ALERT | ERR_FATAL;
373 goto error;
374 }
375 newarg = 0;
376 args[arg++] = line;
377 }
378 line++;
379 }
380 args[arg++] = line;
381
382 /* empty line */
383 if (!*args[0]) {
384 cfgerr |= ERR_NONE;
385 goto error;
386 }
387
388 *crt_path = args[0];
389
390 if (ssl_b) {
391 ssl_conf = calloc(1, sizeof *ssl_conf);
392 if (!ssl_conf) {
393 memprintf(err, "not enough memory!");
394 cfgerr |= ERR_ALERT | ERR_FATAL;
395 goto error;
396 }
397 }
Remi Tricot-Le Bretonbcec63e2021-03-23 16:41:53 +0100398
William Lallemand6e9556b2020-05-12 17:52:44 +0200399 cur_arg = ssl_b ? ssl_b : 1;
400 while (cur_arg < ssl_e) {
401 newarg = 0;
402 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
403 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
404 newarg = 1;
Remi Tricot-Le Bretonbcec63e2021-03-23 16:41:53 +0100405 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200406 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200407 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
408 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200409 cfgerr |= ERR_ALERT | ERR_FATAL;
410 goto error;
411 }
412 cur_arg += 1 + ssl_bind_kws[i].skip;
413 break;
414 }
415 }
416 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200417 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
418 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200419 cfgerr |= ERR_ALERT | ERR_FATAL;
420 goto error;
421 }
422 }
423 entry->linenum = linenum;
424 entry->ssl_conf = ssl_conf;
425 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
426 entry->fcount = arg - cur_arg - 1;
427
428 return cfgerr;
429
430error:
431 crtlist_free_filters(entry->filters);
432 entry->filters = NULL;
433 ssl_sock_free_ssl_conf(entry->ssl_conf);
434 free(entry->ssl_conf);
435 entry->ssl_conf = NULL;
436 return cfgerr;
437}
438
439
440
441/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
442 * Fill the <crtlist> argument with a pointer to a new crtlist struct
443 *
444 * This function tries to open and store certificate files.
445 */
446int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
447{
448 struct crtlist *newlist;
449 struct crtlist_entry *entry = NULL;
450 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200451 FILE *f;
452 struct stat buf;
453 int linenum = 0;
454 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200455 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200456
457 if ((f = fopen(file, "r")) == NULL) {
458 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
459 return ERR_ALERT | ERR_FATAL;
460 }
461
462 newlist = crtlist_new(file, 0);
463 if (newlist == NULL) {
464 memprintf(err, "Not enough memory!");
465 cfgerr |= ERR_ALERT | ERR_FATAL;
466 goto error;
467 }
468
469 while (fgets(thisline, sizeof(thisline), f) != NULL) {
470 char *end;
471 char *line = thisline;
472 char *crt_path;
William Lallemandd3324152020-11-20 14:23:38 +0100473 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200474 struct ckch_store *ckchs;
William Lallemand7993ebc2020-11-20 18:26:09 +0100475 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200476
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200477 if (missing_lf != -1) {
478 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
479 file, linenum, (missing_lf + 1));
480 cfgerr |= ERR_ALERT | ERR_FATAL;
481 missing_lf = -1;
482 break;
483 }
484
William Lallemand6e9556b2020-05-12 17:52:44 +0200485 linenum++;
486 end = line + strlen(line);
487 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
488 /* Check if we reached the limit and the last char is not \n.
489 * Watch out for the last line without the terminating '\n'!
490 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200491 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
492 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200493 cfgerr |= ERR_ALERT | ERR_FATAL;
494 break;
495 }
496
497 if (*line == '#' || *line == '\n' || *line == '\r')
498 continue;
499
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200500 if (end > line && *(end-1) == '\n') {
501 /* kill trailing LF */
502 *(end - 1) = 0;
503 }
504 else {
505 /* mark this line as truncated */
506 missing_lf = end - line;
507 }
508
William Lallemand6e9556b2020-05-12 17:52:44 +0200509 entry = crtlist_entry_new();
510 if (entry == NULL) {
511 memprintf(err, "Not enough memory!");
512 cfgerr |= ERR_ALERT | ERR_FATAL;
513 goto error;
514 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200515
Remi Tricot-Le Bretonbcec63e2021-03-23 16:41:53 +0100516 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200517 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200518 goto error;
519
520 /* empty line */
521 if (!crt_path || !*crt_path) {
522 crtlist_entry_free(entry);
523 entry = NULL;
524 continue;
525 }
526
527 if (*crt_path != '/' && global_ssl.crt_base) {
528 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200529 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
530 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200531 cfgerr |= ERR_ALERT | ERR_FATAL;
532 goto error;
533 }
534 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
535 crt_path = path;
536 }
537
538 /* Look for a ckch_store or create one */
539 ckchs = ckchs_lookup(crt_path);
540 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200541 if (stat(crt_path, &buf) == 0) {
William Lallemand7993ebc2020-11-20 18:26:09 +0100542 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200543
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200544 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200545 if (ckchs == NULL) {
546 cfgerr |= ERR_ALERT | ERR_FATAL;
547 goto error;
548 }
549
550 entry->node.key = ckchs;
551 entry->crtlist = newlist;
552 ebpt_insert(&newlist->entries, &entry->node);
553 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
554 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200555
William Lallemand69d90d72020-11-20 18:23:40 +0100556 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200557 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200558 bundle, since 2.3 we don't support multiple
559 certificate in the same OpenSSL store, so we
560 emulate it by loading each file separately. To
561 do so we need to duplicate the entry in the
562 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200563 char fp[MAXPATHLEN+1] = {0};
564 int n = 0;
565 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
566 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
567 struct stat buf;
568 int ret;
569
William Lallemandd3324152020-11-20 14:23:38 +0100570 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200571 if (ret > sizeof(fp))
572 continue;
573
574 ckchs = ckchs_lookup(fp);
William Lallemand7993ebc2020-11-20 18:26:09 +0100575 if (!ckchs) {
576 if (stat(fp, &buf) == 0) {
577 ckchs = ckchs_load_cert_file(fp, err);
578 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200579 cfgerr |= ERR_ALERT | ERR_FATAL;
580 goto error;
581 }
William Lallemand7993ebc2020-11-20 18:26:09 +0100582 } else {
583 continue; /* didn't find this extension, skip */
584 }
585 }
586 found++;
587 linenum++; /* we duplicate the line for this entry in the bundle */
588 if (!entry_dup) { /* if the entry was used, duplicate one */
589 linenum++;
590 entry_dup = crtlist_entry_dup(entry);
591 if (!entry_dup) {
592 cfgerr |= ERR_ALERT | ERR_FATAL;
593 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200594 }
William Lallemand7993ebc2020-11-20 18:26:09 +0100595 entry_dup->linenum = linenum;
596 }
William Lallemand47da8212020-09-10 19:13:27 +0200597
William Lallemand7993ebc2020-11-20 18:26:09 +0100598 entry_dup->node.key = ckchs;
599 entry_dup->crtlist = newlist;
600 ebpt_insert(&newlist->entries, &entry_dup->node);
601 LIST_ADDQ(&newlist->ord_entries, &entry_dup->by_crtlist);
602 LIST_ADDQ(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200603
William Lallemand7993ebc2020-11-20 18:26:09 +0100604 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200605 }
William Lallemand00b831e2020-12-04 15:45:02 +0100606#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
607 if (found) {
608 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
609 err && *err ? *err : "", crt_path);
610 cfgerr |= ERR_ALERT | ERR_FATAL;
611 }
612#endif
William Lallemand47da8212020-09-10 19:13:27 +0200613 }
William Lallemand7993ebc2020-11-20 18:26:09 +0100614 if (!found) {
615 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
616 err && *err ? *err : "", crt_path, strerror(errno));
617 cfgerr |= ERR_ALERT | ERR_FATAL;
618 }
619
William Lallemand689d9812020-11-06 16:24:07 +0100620 } else {
621 entry->node.key = ckchs;
622 entry->crtlist = newlist;
623 ebpt_insert(&newlist->entries, &entry->node);
624 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
625 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand7993ebc2020-11-20 18:26:09 +0100626 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200627 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200628 entry = NULL;
629 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200630
631 if (missing_lf != -1) {
632 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
633 file, linenum, (missing_lf + 1));
634 cfgerr |= ERR_ALERT | ERR_FATAL;
635 }
636
William Lallemand6e9556b2020-05-12 17:52:44 +0200637 if (cfgerr & ERR_CODE)
638 goto error;
639
640 newlist->linecount = linenum;
641
642 fclose(f);
643 *crtlist = newlist;
644
645 return cfgerr;
646error:
647 crtlist_entry_free(entry);
648
649 fclose(f);
650 crtlist_free(newlist);
651 return cfgerr;
652}
653
654/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
655 * Fill the <crtlist> argument with a pointer to a new crtlist struct
656 *
657 * This function tries to open and store certificate files.
658 */
659int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
660{
661 struct crtlist *dir;
662 struct dirent **de_list;
663 int i, n;
664 struct stat buf;
665 char *end;
666 char fp[MAXPATHLEN+1];
667 int cfgerr = 0;
668 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200669
670 dir = crtlist_new(path, 1);
671 if (dir == NULL) {
672 memprintf(err, "not enough memory");
673 return ERR_ALERT | ERR_FATAL;
674 }
675
676 n = scandir(path, &de_list, 0, alphasort);
677 if (n < 0) {
678 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
679 err && *err ? *err : "", path, strerror(errno));
680 cfgerr |= ERR_ALERT | ERR_FATAL;
681 }
682 else {
683 for (i = 0; i < n; i++) {
684 struct crtlist_entry *entry;
685 struct dirent *de = de_list[i];
686
687 end = strrchr(de->d_name, '.');
688 if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl") || !strcmp(end, ".key")))
689 goto ignore_entry;
690
691 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
692 if (stat(fp, &buf) != 0) {
693 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
694 err && *err ? *err : "", fp, strerror(errno));
695 cfgerr |= ERR_ALERT | ERR_FATAL;
696 goto ignore_entry;
697 }
698 if (!S_ISREG(buf.st_mode))
699 goto ignore_entry;
700
701 entry = crtlist_entry_new();
702 if (entry == NULL) {
703 memprintf(err, "not enough memory '%s'", fp);
704 cfgerr |= ERR_ALERT | ERR_FATAL;
705 goto ignore_entry;
706 }
707
William Lallemand6e9556b2020-05-12 17:52:44 +0200708 ckchs = ckchs_lookup(fp);
709 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200710 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200711 if (ckchs == NULL) {
712 free(de);
713 free(entry);
714 cfgerr |= ERR_ALERT | ERR_FATAL;
715 goto end;
716 }
717 entry->node.key = ckchs;
718 entry->crtlist = dir;
719 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
720 LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist);
721 ebpt_insert(&dir->entries, &entry->node);
722
723ignore_entry:
724 free(de);
725 }
726end:
727 free(de_list);
728 }
729
730 if (cfgerr & ERR_CODE) {
731 /* free the dir and entries on error */
732 crtlist_free(dir);
733 } else {
734 *crtlist = dir;
735 }
736 return cfgerr;
737
738}
739
William Lallemandc756bbd2020-05-13 17:23:59 +0200740/*
741 * Take an ssl_bind_conf structure and append the configuration line used to
742 * create it in the buffer
743 */
744static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
745{
746 int space = 0;
747
748 if (conf == NULL)
749 return;
750
751 chunk_appendf(buf, " [");
752#ifdef OPENSSL_NPN_NEGOTIATED
753 if (conf->npn_str) {
754 int len = conf->npn_len;
755 char *ptr = conf->npn_str;
756 int comma = 0;
757
758 if (space) chunk_appendf(buf, " ");
759 chunk_appendf(buf, "npn ");
760 while (len) {
761 unsigned short size;
762
763 size = *ptr;
764 ptr++;
765 if (comma)
766 chunk_memcat(buf, ",", 1);
767 chunk_memcat(buf, ptr, size);
768 ptr += size;
769 len -= size + 1;
770 comma = 1;
771 }
772 chunk_memcat(buf, "", 1); /* finish with a \0 */
773 space++;
774 }
775#endif
776#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
777 if (conf->alpn_str) {
778 int len = conf->alpn_len;
779 char *ptr = conf->alpn_str;
780 int comma = 0;
781
782 if (space) chunk_appendf(buf, " ");
783 chunk_appendf(buf, "alpn ");
784 while (len) {
785 unsigned short size;
786
787 size = *ptr;
788 ptr++;
789 if (comma)
790 chunk_memcat(buf, ",", 1);
791 chunk_memcat(buf, ptr, size);
792 ptr += size;
793 len -= size + 1;
794 comma = 1;
795 }
796 chunk_memcat(buf, "", 1); /* finish with a \0 */
797 space++;
798 }
799#endif
800 /* verify */
801 {
802 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
803 if (space) chunk_appendf(buf, " ");
804 chunk_appendf(buf, "verify none");
805 space++;
806 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
807 if (space) chunk_appendf(buf, " ");
808 chunk_appendf(buf, "verify optional");
809 space++;
810 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
811 if (space) chunk_appendf(buf, " ");
812 chunk_appendf(buf, "verify required");
813 space++;
814 }
815 }
816
817 if (conf->no_ca_names) {
818 if (space) chunk_appendf(buf, " ");
819 chunk_appendf(buf, "no-ca-names");
820 space++;
821 }
822
823 if (conf->early_data) {
824 if (space) chunk_appendf(buf, " ");
825 chunk_appendf(buf, "allow-0rtt");
826 space++;
827 }
828 if (conf->ca_file) {
829 if (space) chunk_appendf(buf, " ");
830 chunk_appendf(buf, "ca-file %s", conf->ca_file);
831 space++;
832 }
833 if (conf->crl_file) {
834 if (space) chunk_appendf(buf, " ");
835 chunk_appendf(buf, "crl-file %s", conf->crl_file);
836 space++;
837 }
838 if (conf->ciphers) {
839 if (space) chunk_appendf(buf, " ");
840 chunk_appendf(buf, "ciphers %s", conf->ciphers);
841 space++;
842 }
843#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
844 if (conf->ciphersuites) {
845 if (space) chunk_appendf(buf, " ");
846 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
847 space++;
848 }
849#endif
850 if (conf->curves) {
851 if (space) chunk_appendf(buf, " ");
852 chunk_appendf(buf, "curves %s", conf->curves);
853 space++;
854 }
855 if (conf->ecdhe) {
856 if (space) chunk_appendf(buf, " ");
857 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
858 space++;
859 }
860
861 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200862 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200863 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200864 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200865 space++;
866 }
867
William Lallemand8177ad92020-05-20 16:49:02 +0200868 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200869 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200870 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200871 space++;
872 }
873
874 chunk_appendf(buf, "]");
875
876 return;
877}
878
879/* dump a list of filters */
880static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
881{
882 int i;
883
884 if (!entry->fcount)
885 return;
886
887 for (i = 0; i < entry->fcount; i++) {
888 chunk_appendf(buf, " %s", entry->filters[i]);
889 }
890 return;
891}
892
893/************************** CLI functions ****************************/
894
895
896/* CLI IO handler for '(show|dump) ssl crt-list' */
897static int cli_io_handler_dump_crtlist(struct appctx *appctx)
898{
899 struct buffer *trash = alloc_trash_chunk();
900 struct stream_interface *si = appctx->owner;
901 struct ebmb_node *lnode;
902
903 if (trash == NULL)
904 return 1;
905
906 /* dump the list of crt-lists */
907 lnode = appctx->ctx.cli.p1;
908 if (lnode == NULL)
909 lnode = ebmb_first(&crtlists_tree);
910 while (lnode) {
911 chunk_appendf(trash, "%s\n", lnode->key);
912 if (ci_putchk(si_ic(si), trash) == -1) {
913 si_rx_room_blk(si);
914 goto yield;
915 }
916 lnode = ebmb_next(lnode);
917 }
918 free_trash_chunk(trash);
919 return 1;
920yield:
921 appctx->ctx.cli.p1 = lnode;
922 free_trash_chunk(trash);
923 return 0;
924}
925
926/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
927static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
928{
929 struct buffer *trash = alloc_trash_chunk();
930 struct crtlist *crtlist;
931 struct stream_interface *si = appctx->owner;
932 struct crtlist_entry *entry;
933
934 if (trash == NULL)
935 return 1;
936
937 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
938
939 entry = appctx->ctx.cli.p1;
940 if (entry == NULL) {
941 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
942 chunk_appendf(trash, "# %s\n", crtlist->node.key);
943 if (ci_putchk(si_ic(si), trash) == -1) {
944 si_rx_room_blk(si);
945 goto yield;
946 }
947 }
948
949 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
950 struct ckch_store *store;
951 const char *filename;
952
953 store = entry->node.key;
954 filename = store->path;
955 chunk_appendf(trash, "%s", filename);
956 if (appctx->ctx.cli.i0 == 's') /* show */
957 chunk_appendf(trash, ":%d", entry->linenum);
958 dump_crtlist_sslconf(trash, entry->ssl_conf);
959 dump_crtlist_filters(trash, entry);
960 chunk_appendf(trash, "\n");
961
962 if (ci_putchk(si_ic(si), trash) == -1) {
963 si_rx_room_blk(si);
964 goto yield;
965 }
966 }
967 free_trash_chunk(trash);
968 return 1;
969yield:
970 appctx->ctx.cli.p1 = entry;
971 free_trash_chunk(trash);
972 return 0;
973}
974
975/* CLI argument parser for '(show|dump) ssl crt-list' */
976static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
977{
978 struct ebmb_node *lnode;
979 char *filename = NULL;
980 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200981 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200982
983 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
984 return 1;
985
986 appctx->ctx.cli.p0 = NULL;
987 appctx->ctx.cli.p1 = NULL;
988
989 if (*args[3] && !strcmp(args[3], "-n")) {
990 mode = 's';
991 filename = args[4];
992 } else {
993 mode = 'd';
994 filename = args[3];
995 }
996
997 if (mode == 's' && !*args[4])
998 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
999
1000 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +02001001
1002
1003 /* strip trailing slashes, including first one */
1004 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1005 *end = 0;
1006
William Lallemandc756bbd2020-05-13 17:23:59 +02001007 lnode = ebst_lookup(&crtlists_tree, filename);
1008 if (lnode == NULL)
1009 return cli_err(appctx, "didn't find the specified filename\n");
1010
1011 appctx->ctx.cli.p0 = lnode;
1012 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1013 }
1014 appctx->ctx.cli.i0 = mode;
1015
1016 return 0;
1017}
1018
1019/* release function of the "add ssl crt-list' command, free things and unlock
1020 the spinlock */
1021static void cli_release_add_crtlist(struct appctx *appctx)
1022{
1023 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1024
1025 if (appctx->st2 != SETCERT_ST_FIN) {
1026 struct ckch_inst *inst, *inst_s;
1027 /* upon error free the ckch_inst and everything inside */
1028 ebpt_delete(&entry->node);
1029 LIST_DEL(&entry->by_crtlist);
1030 LIST_DEL(&entry->by_ckch_store);
1031
1032 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1033 ckch_inst_free(inst);
1034 }
1035 crtlist_free_filters(entry->filters);
1036 ssl_sock_free_ssl_conf(entry->ssl_conf);
1037 free(entry->ssl_conf);
1038 free(entry);
1039 }
1040
1041 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1042}
1043
1044
1045/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1046 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1047 *
1048 * The logic is the same as the "commit ssl cert" command but without the
1049 * freeing of the old structures, because there are none.
1050 */
1051static int cli_io_handler_add_crtlist(struct appctx *appctx)
1052{
1053 struct bind_conf_list *bind_conf_node;
1054 struct stream_interface *si = appctx->owner;
1055 struct crtlist *crtlist = appctx->ctx.cli.p0;
1056 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1057 struct ckch_store *store = entry->node.key;
1058 struct buffer *trash = alloc_trash_chunk();
1059 struct ckch_inst *new_inst;
1060 char *err = NULL;
1061 int i = 0;
1062 int errcode = 0;
1063
1064 if (trash == NULL)
1065 goto error;
1066
1067 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1068 * created.
1069 */
1070 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1071 goto error;
1072
1073 while (1) {
1074 switch (appctx->st2) {
1075 case SETCERT_ST_INIT:
1076 /* This state just print the update message */
1077 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1078 if (ci_putchk(si_ic(si), trash) == -1) {
1079 si_rx_room_blk(si);
1080 goto yield;
1081 }
1082 appctx->st2 = SETCERT_ST_GEN;
1083 /* fallthrough */
1084 case SETCERT_ST_GEN:
1085 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1086 if (bind_conf_node == NULL)
1087 bind_conf_node = crtlist->bind_conf;
1088 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1089 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1090 struct sni_ctx *sni;
1091
1092 /* yield every 10 generations */
1093 if (i > 10) {
1094 appctx->ctx.cli.p2 = bind_conf_node;
1095 goto yield;
1096 }
1097
1098 /* we don't support multi-cert bundles, only simple ones */
1099 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1100 if (errcode & ERR_CODE)
1101 goto error;
1102
1103 /* we need to initialize the SSL_CTX generated */
1104 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1105 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1106 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1107 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1108 if (errcode & ERR_CODE)
1109 goto error;
1110 }
1111 }
1112 /* display one dot for each new instance */
1113 chunk_appendf(trash, ".");
1114 i++;
1115 LIST_ADDQ(&store->ckch_inst, &new_inst->by_ckchs);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001116 LIST_ADDQ(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1117 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001118 }
1119 appctx->st2 = SETCERT_ST_INSERT;
1120 /* fallthrough */
1121 case SETCERT_ST_INSERT:
1122 /* insert SNIs in bind_conf */
1123 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1124 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1125 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1126 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1127 }
1128 entry->linenum = ++crtlist->linecount;
1129 appctx->st2 = SETCERT_ST_FIN;
1130 goto end;
1131 }
1132 }
1133
1134end:
1135 chunk_appendf(trash, "\n");
1136 if (errcode & ERR_WARN)
1137 chunk_appendf(trash, "%s", err);
1138 chunk_appendf(trash, "Success!\n");
1139 if (ci_putchk(si_ic(si), trash) == -1)
1140 si_rx_room_blk(si);
1141 free_trash_chunk(trash);
1142 /* success: call the release function and don't come back */
1143 return 1;
1144yield:
1145 /* store the state */
1146 if (ci_putchk(si_ic(si), trash) == -1)
1147 si_rx_room_blk(si);
1148 free_trash_chunk(trash);
1149 si_rx_endp_more(si); /* let's come back later */
1150 return 0; /* should come back */
1151
1152error:
1153 /* spin unlock and free are done in the release function */
1154 if (trash) {
1155 chunk_appendf(trash, "\n%sFailed!\n", err);
1156 if (ci_putchk(si_ic(si), trash) == -1)
1157 si_rx_room_blk(si);
1158 free_trash_chunk(trash);
1159 }
1160 /* error: call the release function and don't come back */
1161 return 1;
1162}
1163
1164
1165/*
1166 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1167 * Filters and option must be passed through payload:
1168 */
1169static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1170{
1171 int cfgerr = 0;
1172 struct ckch_store *store;
1173 char *err = NULL;
1174 char path[MAXPATHLEN+1];
1175 char *crtlist_path;
1176 char *cert_path = NULL;
1177 struct ebmb_node *eb;
1178 struct ebpt_node *inserted;
1179 struct crtlist *crtlist;
1180 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001181 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001182
1183 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1184 return 1;
1185
1186 if (!*args[3] || (!payload && !*args[4]))
1187 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1188
1189 crtlist_path = args[3];
1190
William Lallemand99cc2182020-06-25 15:19:51 +02001191 /* strip trailing slashes, including first one */
1192 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1193 *end = 0;
1194
William Lallemandc756bbd2020-05-13 17:23:59 +02001195 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1196 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1197
1198 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1199 if (!eb) {
1200 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1201 goto error;
1202 }
1203 crtlist = ebmb_entry(eb, struct crtlist, node);
1204
1205 entry = crtlist_entry_new();
1206 if (entry == NULL) {
1207 memprintf(&err, "Not enough memory!");
1208 goto error;
1209 }
1210
1211 if (payload) {
1212 char *lf;
1213
1214 lf = strrchr(payload, '\n');
1215 if (lf) {
1216 memprintf(&err, "only one line of payload is supported!");
1217 goto error;
1218 }
1219 /* cert_path is filled here */
Remi Tricot-Le Bretonbcec63e2021-03-23 16:41:53 +01001220 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001221 if (cfgerr & ERR_CODE)
1222 goto error;
1223 } else {
1224 cert_path = args[4];
1225 }
1226
1227 if (!cert_path) {
1228 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1229 cfgerr |= ERR_ALERT | ERR_FATAL;
1230 goto error;
1231 }
1232
1233 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1234 char *slash;
1235
1236 slash = strrchr(cert_path, '/');
1237 if (!slash) {
1238 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1239 goto error;
1240 }
1241 /* temporary replace / by 0 to do an strcmp */
1242 *slash = '\0';
1243 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1244 *slash = '/';
1245 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1246 goto error;
1247 }
1248 *slash = '/';
1249 }
1250
1251 if (*cert_path != '/' && global_ssl.crt_base) {
1252 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1253 memprintf(&err, "'%s' : path too long", cert_path);
1254 cfgerr |= ERR_ALERT | ERR_FATAL;
1255 goto error;
1256 }
1257 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1258 cert_path = path;
1259 }
1260
1261 store = ckchs_lookup(cert_path);
1262 if (store == NULL) {
1263 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1264 goto error;
1265 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001266 if (store->ckch == NULL || store->ckch->cert == NULL) {
1267 memprintf(&err, "certificate '%s' is empty!", cert_path);
1268 goto error;
1269 }
1270
1271 /* check if it's possible to insert this new crtlist_entry */
1272 entry->node.key = store;
1273 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1274 if (inserted != &entry->node) {
1275 memprintf(&err, "file already exists in this directory!");
1276 goto error;
1277 }
1278
1279 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1280 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1281 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1282 goto error;
1283 }
1284
1285 LIST_ADDQ(&crtlist->ord_entries, &entry->by_crtlist);
1286 entry->crtlist = crtlist;
1287 LIST_ADDQ(&store->crtlist_entry, &entry->by_ckch_store);
1288
1289 appctx->st2 = SETCERT_ST_INIT;
1290 appctx->ctx.cli.p0 = crtlist;
1291 appctx->ctx.cli.p1 = entry;
1292
1293 /* unlock is done in the release handler */
1294 return 0;
1295
1296error:
1297 crtlist_entry_free(entry);
1298 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1299 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1300 return cli_dynerr(appctx, err);
1301}
1302
1303/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1304static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1305{
1306 struct ckch_store *store;
1307 char *err = NULL;
1308 char *crtlist_path, *cert_path;
1309 struct ebmb_node *ebmb;
1310 struct ebpt_node *ebpt;
1311 struct crtlist *crtlist;
1312 struct crtlist_entry *entry = NULL;
1313 struct ckch_inst *inst, *inst_s;
1314 int linenum = 0;
1315 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001316 char *end;
Remi Tricot-Le Breton37cfc782021-03-26 10:47:50 +01001317 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001318
1319 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1320 return 1;
1321
1322 if (!*args[3] || !*args[4])
1323 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1324
1325 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1326 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1327
1328 crtlist_path = args[3];
1329 cert_path = args[4];
1330
1331 colons = strchr(cert_path, ':');
1332 if (colons) {
1333 char *endptr;
1334
1335 linenum = strtol(colons + 1, &endptr, 10);
1336 if (colons + 1 == endptr || *endptr != '\0') {
1337 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1338 goto error;
1339 }
1340 *colons = '\0';
1341 }
William Lallemand99cc2182020-06-25 15:19:51 +02001342
1343 /* strip trailing slashes, including first one */
1344 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1345 *end = 0;
1346
William Lallemandc756bbd2020-05-13 17:23:59 +02001347 /* look for crtlist */
1348 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1349 if (!ebmb) {
1350 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1351 goto error;
1352 }
1353 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1354
1355 /* look for store */
1356 store = ckchs_lookup(cert_path);
1357 if (store == NULL) {
1358 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1359 goto error;
1360 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001361 if (store->ckch == NULL || store->ckch->cert == NULL) {
1362 memprintf(&err, "certificate '%s' is empty!", cert_path);
1363 goto error;
1364 }
1365
1366 ebpt = ebpt_lookup(&crtlist->entries, store);
1367 if (!ebpt) {
1368 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1369 goto error;
1370 }
1371
1372 /* list the line number of entries for errors in err, and select the right ebpt */
1373 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1374 struct crtlist_entry *tmp;
1375
1376 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1377 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1378
1379 /* select the entry we wanted */
1380 if (linenum == 0 || tmp->linenum == linenum) {
1381 if (!entry)
1382 entry = tmp;
1383 }
1384 }
1385
1386 /* we didn't found the specified entry */
1387 if (!entry) {
1388 memprintf(&err, "found a certificate '%s' but the line number is incorrect, please specify a correct line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1389 goto error;
1390 }
1391
1392 /* we didn't specified a line number but there were several entries */
1393 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1394 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1395 goto error;
1396 }
1397
Remi Tricot-Le Breton37cfc782021-03-26 10:47:50 +01001398 /* Iterate over all the instances in order to see if any of them is a
1399 * default instance. If this is the case, the entry won't be suppressed. */
1400 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1401 if (inst->is_default && !inst->bind_conf->strict_sni) {
1402 if (!error_message_dumped) {
1403 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1404 error_message_dumped = 1;
1405 }
1406 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1407 }
1408 }
1409 if (error_message_dumped)
1410 goto error;
1411
William Lallemandc756bbd2020-05-13 17:23:59 +02001412 /* upon error free the ckch_inst and everything inside */
1413
1414 ebpt_delete(&entry->node);
1415 LIST_DEL(&entry->by_crtlist);
1416 LIST_DEL(&entry->by_ckch_store);
1417
1418 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1419 struct sni_ctx *sni, *sni_s;
1420
1421 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1422 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1423 ebmb_delete(&sni->name);
1424 LIST_DEL(&sni->by_ckch_inst);
1425 SSL_CTX_free(sni->ctx);
1426 free(sni);
1427 }
1428 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1429 LIST_DEL(&inst->by_ckchs);
1430 free(inst);
1431 }
1432
1433 crtlist_free_filters(entry->filters);
1434 ssl_sock_free_ssl_conf(entry->ssl_conf);
1435 free(entry->ssl_conf);
1436 free(entry);
1437
1438 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1439 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1440 return cli_dynmsg(appctx, LOG_NOTICE, err);
1441
1442error:
1443 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1444 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1445 return cli_dynerr(appctx, err);
1446}
1447
1448
William Lallemandee8530c2020-06-23 18:19:42 +02001449/* unlink and free all crt-list and crt-list entries */
1450void crtlist_deinit()
1451{
1452 struct eb_node *node, *next;
1453 struct crtlist *crtlist;
1454
1455 node = eb_first(&crtlists_tree);
1456 while (node) {
1457 next = eb_next(node);
1458 crtlist = ebmb_entry(node, struct crtlist, node);
1459 crtlist_free(crtlist);
1460 node = next;
1461 }
1462}
1463
William Lallemandc756bbd2020-05-13 17:23:59 +02001464
1465/* register cli keywords */
1466static struct cli_kw_list cli_kws = {{ },{
1467 { { "add", "ssl", "crt-list", NULL }, "add ssl crt-list <filename> <certfile> [options] : add a line <certfile> to a crt-list <filename>", cli_parse_add_crtlist, cli_io_handler_add_crtlist, cli_release_add_crtlist },
1468 { { "del", "ssl", "crt-list", NULL }, "del ssl crt-list <filename> <certfile[:line]> : delete a line <certfile> in a crt-list <filename>", cli_parse_del_crtlist, NULL, NULL },
1469 { { "show", "ssl", "crt-list", NULL }, "show ssl crt-list [-n] [<filename>] : show the list of crt-lists or the content of a crt-list <filename>", cli_parse_dump_crtlist, cli_io_handler_dump_crtlist, NULL },
1470 { { NULL }, NULL, NULL, NULL } }
1471};
1472
1473INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1474