blob: 8e9e5a11f3e6f264ff1362af7379f8089b361143 [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 */
314int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err)
315{
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 }
398 cur_arg = ssl_b ? ssl_b : 1;
399 while (cur_arg < ssl_e) {
400 newarg = 0;
401 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
402 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
403 newarg = 1;
404 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, err);
405 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200406 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
407 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200408 cfgerr |= ERR_ALERT | ERR_FATAL;
409 goto error;
410 }
411 cur_arg += 1 + ssl_bind_kws[i].skip;
412 break;
413 }
414 }
415 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200416 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
417 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200418 cfgerr |= ERR_ALERT | ERR_FATAL;
419 goto error;
420 }
421 }
422 entry->linenum = linenum;
423 entry->ssl_conf = ssl_conf;
424 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
425 entry->fcount = arg - cur_arg - 1;
426
427 return cfgerr;
428
429error:
430 crtlist_free_filters(entry->filters);
431 entry->filters = NULL;
432 ssl_sock_free_ssl_conf(entry->ssl_conf);
433 free(entry->ssl_conf);
434 entry->ssl_conf = NULL;
435 return cfgerr;
436}
437
438
439
440/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
441 * Fill the <crtlist> argument with a pointer to a new crtlist struct
442 *
443 * This function tries to open and store certificate files.
444 */
445int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
446{
447 struct crtlist *newlist;
448 struct crtlist_entry *entry = NULL;
449 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200450 FILE *f;
451 struct stat buf;
452 int linenum = 0;
453 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200454 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200455
456 if ((f = fopen(file, "r")) == NULL) {
457 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
458 return ERR_ALERT | ERR_FATAL;
459 }
460
461 newlist = crtlist_new(file, 0);
462 if (newlist == NULL) {
463 memprintf(err, "Not enough memory!");
464 cfgerr |= ERR_ALERT | ERR_FATAL;
465 goto error;
466 }
467
468 while (fgets(thisline, sizeof(thisline), f) != NULL) {
469 char *end;
470 char *line = thisline;
471 char *crt_path;
William Lallemandd3324152020-11-20 14:23:38 +0100472 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200473 struct ckch_store *ckchs;
474
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200475 if (missing_lf != -1) {
476 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
477 file, linenum, (missing_lf + 1));
478 cfgerr |= ERR_ALERT | ERR_FATAL;
479 missing_lf = -1;
480 break;
481 }
482
William Lallemand6e9556b2020-05-12 17:52:44 +0200483 linenum++;
484 end = line + strlen(line);
485 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
486 /* Check if we reached the limit and the last char is not \n.
487 * Watch out for the last line without the terminating '\n'!
488 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200489 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
490 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200491 cfgerr |= ERR_ALERT | ERR_FATAL;
492 break;
493 }
494
495 if (*line == '#' || *line == '\n' || *line == '\r')
496 continue;
497
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200498 if (end > line && *(end-1) == '\n') {
499 /* kill trailing LF */
500 *(end - 1) = 0;
501 }
502 else {
503 /* mark this line as truncated */
504 missing_lf = end - line;
505 }
506
William Lallemand6e9556b2020-05-12 17:52:44 +0200507 entry = crtlist_entry_new();
508 if (entry == NULL) {
509 memprintf(err, "Not enough memory!");
510 cfgerr |= ERR_ALERT | ERR_FATAL;
511 goto error;
512 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200513
William Lallemand6e9556b2020-05-12 17:52:44 +0200514 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200515 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200516 goto error;
517
518 /* empty line */
519 if (!crt_path || !*crt_path) {
520 crtlist_entry_free(entry);
521 entry = NULL;
522 continue;
523 }
524
525 if (*crt_path != '/' && global_ssl.crt_base) {
526 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200527 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
528 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200529 cfgerr |= ERR_ALERT | ERR_FATAL;
530 goto error;
531 }
532 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
533 crt_path = path;
534 }
535
536 /* Look for a ckch_store or create one */
537 ckchs = ckchs_lookup(crt_path);
538 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200539 if (stat(crt_path, &buf) == 0) {
540
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200541 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200542 if (ckchs == NULL) {
543 cfgerr |= ERR_ALERT | ERR_FATAL;
544 goto error;
545 }
546
547 entry->node.key = ckchs;
548 entry->crtlist = newlist;
549 ebpt_insert(&newlist->entries, &entry->node);
550 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
551 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200552
William Lallemand69d90d72020-11-20 18:23:40 +0100553 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200554 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200555 bundle, since 2.3 we don't support multiple
556 certificate in the same OpenSSL store, so we
557 emulate it by loading each file separately. To
558 do so we need to duplicate the entry in the
559 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200560 char fp[MAXPATHLEN+1] = {0};
561 int n = 0;
562 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
563 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
564 struct stat buf;
565 int ret;
566
William Lallemandd3324152020-11-20 14:23:38 +0100567 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200568 if (ret > sizeof(fp))
569 continue;
570
571 ckchs = ckchs_lookup(fp);
572 if (!ckchs && stat(fp, &buf) == 0) {
573
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200574 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand47da8212020-09-10 19:13:27 +0200575 if (ckchs == NULL) {
576 cfgerr |= ERR_ALERT | ERR_FATAL;
577 goto error;
578 }
579
580 linenum++; /* we duplicate the line for this entry in the bundle */
581 if (!entry_dup) { /* if the entry was used, duplicate one */
582 linenum++;
583 entry_dup = crtlist_entry_dup(entry);
584 if (!entry_dup) {
585 cfgerr |= ERR_ALERT | ERR_FATAL;
586 goto error;
587 }
588 entry_dup->linenum = linenum;
589 }
590
591 entry_dup->node.key = ckchs;
592 entry_dup->crtlist = newlist;
593 ebpt_insert(&newlist->entries, &entry_dup->node);
594 LIST_ADDQ(&newlist->ord_entries, &entry_dup->by_crtlist);
595 LIST_ADDQ(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
596
597 entry_dup = NULL; /* the entry was used, we need a new one next round */
598 }
599 }
600 }
William Lallemand689d9812020-11-06 16:24:07 +0100601 } else {
602 entry->node.key = ckchs;
603 entry->crtlist = newlist;
604 ebpt_insert(&newlist->entries, &entry->node);
605 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
606 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200607 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200608 entry = NULL;
609 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200610
611 if (missing_lf != -1) {
612 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
613 file, linenum, (missing_lf + 1));
614 cfgerr |= ERR_ALERT | ERR_FATAL;
615 }
616
William Lallemand6e9556b2020-05-12 17:52:44 +0200617 if (cfgerr & ERR_CODE)
618 goto error;
619
620 newlist->linecount = linenum;
621
622 fclose(f);
623 *crtlist = newlist;
624
625 return cfgerr;
626error:
627 crtlist_entry_free(entry);
628
629 fclose(f);
630 crtlist_free(newlist);
631 return cfgerr;
632}
633
634/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
635 * Fill the <crtlist> argument with a pointer to a new crtlist struct
636 *
637 * This function tries to open and store certificate files.
638 */
639int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
640{
641 struct crtlist *dir;
642 struct dirent **de_list;
643 int i, n;
644 struct stat buf;
645 char *end;
646 char fp[MAXPATHLEN+1];
647 int cfgerr = 0;
648 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200649
650 dir = crtlist_new(path, 1);
651 if (dir == NULL) {
652 memprintf(err, "not enough memory");
653 return ERR_ALERT | ERR_FATAL;
654 }
655
656 n = scandir(path, &de_list, 0, alphasort);
657 if (n < 0) {
658 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
659 err && *err ? *err : "", path, strerror(errno));
660 cfgerr |= ERR_ALERT | ERR_FATAL;
661 }
662 else {
663 for (i = 0; i < n; i++) {
664 struct crtlist_entry *entry;
665 struct dirent *de = de_list[i];
666
667 end = strrchr(de->d_name, '.');
668 if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl") || !strcmp(end, ".key")))
669 goto ignore_entry;
670
671 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
672 if (stat(fp, &buf) != 0) {
673 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
674 err && *err ? *err : "", fp, strerror(errno));
675 cfgerr |= ERR_ALERT | ERR_FATAL;
676 goto ignore_entry;
677 }
678 if (!S_ISREG(buf.st_mode))
679 goto ignore_entry;
680
681 entry = crtlist_entry_new();
682 if (entry == NULL) {
683 memprintf(err, "not enough memory '%s'", fp);
684 cfgerr |= ERR_ALERT | ERR_FATAL;
685 goto ignore_entry;
686 }
687
William Lallemand6e9556b2020-05-12 17:52:44 +0200688 ckchs = ckchs_lookup(fp);
689 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200690 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200691 if (ckchs == NULL) {
692 free(de);
693 free(entry);
694 cfgerr |= ERR_ALERT | ERR_FATAL;
695 goto end;
696 }
697 entry->node.key = ckchs;
698 entry->crtlist = dir;
699 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
700 LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist);
701 ebpt_insert(&dir->entries, &entry->node);
702
703ignore_entry:
704 free(de);
705 }
706end:
707 free(de_list);
708 }
709
710 if (cfgerr & ERR_CODE) {
711 /* free the dir and entries on error */
712 crtlist_free(dir);
713 } else {
714 *crtlist = dir;
715 }
716 return cfgerr;
717
718}
719
William Lallemandc756bbd2020-05-13 17:23:59 +0200720/*
721 * Take an ssl_bind_conf structure and append the configuration line used to
722 * create it in the buffer
723 */
724static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
725{
726 int space = 0;
727
728 if (conf == NULL)
729 return;
730
731 chunk_appendf(buf, " [");
732#ifdef OPENSSL_NPN_NEGOTIATED
733 if (conf->npn_str) {
734 int len = conf->npn_len;
735 char *ptr = conf->npn_str;
736 int comma = 0;
737
738 if (space) chunk_appendf(buf, " ");
739 chunk_appendf(buf, "npn ");
740 while (len) {
741 unsigned short size;
742
743 size = *ptr;
744 ptr++;
745 if (comma)
746 chunk_memcat(buf, ",", 1);
747 chunk_memcat(buf, ptr, size);
748 ptr += size;
749 len -= size + 1;
750 comma = 1;
751 }
752 chunk_memcat(buf, "", 1); /* finish with a \0 */
753 space++;
754 }
755#endif
756#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
757 if (conf->alpn_str) {
758 int len = conf->alpn_len;
759 char *ptr = conf->alpn_str;
760 int comma = 0;
761
762 if (space) chunk_appendf(buf, " ");
763 chunk_appendf(buf, "alpn ");
764 while (len) {
765 unsigned short size;
766
767 size = *ptr;
768 ptr++;
769 if (comma)
770 chunk_memcat(buf, ",", 1);
771 chunk_memcat(buf, ptr, size);
772 ptr += size;
773 len -= size + 1;
774 comma = 1;
775 }
776 chunk_memcat(buf, "", 1); /* finish with a \0 */
777 space++;
778 }
779#endif
780 /* verify */
781 {
782 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
783 if (space) chunk_appendf(buf, " ");
784 chunk_appendf(buf, "verify none");
785 space++;
786 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
787 if (space) chunk_appendf(buf, " ");
788 chunk_appendf(buf, "verify optional");
789 space++;
790 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
791 if (space) chunk_appendf(buf, " ");
792 chunk_appendf(buf, "verify required");
793 space++;
794 }
795 }
796
797 if (conf->no_ca_names) {
798 if (space) chunk_appendf(buf, " ");
799 chunk_appendf(buf, "no-ca-names");
800 space++;
801 }
802
803 if (conf->early_data) {
804 if (space) chunk_appendf(buf, " ");
805 chunk_appendf(buf, "allow-0rtt");
806 space++;
807 }
808 if (conf->ca_file) {
809 if (space) chunk_appendf(buf, " ");
810 chunk_appendf(buf, "ca-file %s", conf->ca_file);
811 space++;
812 }
813 if (conf->crl_file) {
814 if (space) chunk_appendf(buf, " ");
815 chunk_appendf(buf, "crl-file %s", conf->crl_file);
816 space++;
817 }
818 if (conf->ciphers) {
819 if (space) chunk_appendf(buf, " ");
820 chunk_appendf(buf, "ciphers %s", conf->ciphers);
821 space++;
822 }
823#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
824 if (conf->ciphersuites) {
825 if (space) chunk_appendf(buf, " ");
826 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
827 space++;
828 }
829#endif
830 if (conf->curves) {
831 if (space) chunk_appendf(buf, " ");
832 chunk_appendf(buf, "curves %s", conf->curves);
833 space++;
834 }
835 if (conf->ecdhe) {
836 if (space) chunk_appendf(buf, " ");
837 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
838 space++;
839 }
840
841 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200842 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200843 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200844 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200845 space++;
846 }
847
William Lallemand8177ad92020-05-20 16:49:02 +0200848 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200849 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200850 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200851 space++;
852 }
853
854 chunk_appendf(buf, "]");
855
856 return;
857}
858
859/* dump a list of filters */
860static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
861{
862 int i;
863
864 if (!entry->fcount)
865 return;
866
867 for (i = 0; i < entry->fcount; i++) {
868 chunk_appendf(buf, " %s", entry->filters[i]);
869 }
870 return;
871}
872
873/************************** CLI functions ****************************/
874
875
876/* CLI IO handler for '(show|dump) ssl crt-list' */
877static int cli_io_handler_dump_crtlist(struct appctx *appctx)
878{
879 struct buffer *trash = alloc_trash_chunk();
880 struct stream_interface *si = appctx->owner;
881 struct ebmb_node *lnode;
882
883 if (trash == NULL)
884 return 1;
885
886 /* dump the list of crt-lists */
887 lnode = appctx->ctx.cli.p1;
888 if (lnode == NULL)
889 lnode = ebmb_first(&crtlists_tree);
890 while (lnode) {
891 chunk_appendf(trash, "%s\n", lnode->key);
892 if (ci_putchk(si_ic(si), trash) == -1) {
893 si_rx_room_blk(si);
894 goto yield;
895 }
896 lnode = ebmb_next(lnode);
897 }
898 free_trash_chunk(trash);
899 return 1;
900yield:
901 appctx->ctx.cli.p1 = lnode;
902 free_trash_chunk(trash);
903 return 0;
904}
905
906/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
907static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
908{
909 struct buffer *trash = alloc_trash_chunk();
910 struct crtlist *crtlist;
911 struct stream_interface *si = appctx->owner;
912 struct crtlist_entry *entry;
913
914 if (trash == NULL)
915 return 1;
916
917 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
918
919 entry = appctx->ctx.cli.p1;
920 if (entry == NULL) {
921 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
922 chunk_appendf(trash, "# %s\n", crtlist->node.key);
923 if (ci_putchk(si_ic(si), trash) == -1) {
924 si_rx_room_blk(si);
925 goto yield;
926 }
927 }
928
929 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
930 struct ckch_store *store;
931 const char *filename;
932
933 store = entry->node.key;
934 filename = store->path;
935 chunk_appendf(trash, "%s", filename);
936 if (appctx->ctx.cli.i0 == 's') /* show */
937 chunk_appendf(trash, ":%d", entry->linenum);
938 dump_crtlist_sslconf(trash, entry->ssl_conf);
939 dump_crtlist_filters(trash, entry);
940 chunk_appendf(trash, "\n");
941
942 if (ci_putchk(si_ic(si), trash) == -1) {
943 si_rx_room_blk(si);
944 goto yield;
945 }
946 }
947 free_trash_chunk(trash);
948 return 1;
949yield:
950 appctx->ctx.cli.p1 = entry;
951 free_trash_chunk(trash);
952 return 0;
953}
954
955/* CLI argument parser for '(show|dump) ssl crt-list' */
956static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
957{
958 struct ebmb_node *lnode;
959 char *filename = NULL;
960 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200961 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200962
963 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
964 return 1;
965
966 appctx->ctx.cli.p0 = NULL;
967 appctx->ctx.cli.p1 = NULL;
968
969 if (*args[3] && !strcmp(args[3], "-n")) {
970 mode = 's';
971 filename = args[4];
972 } else {
973 mode = 'd';
974 filename = args[3];
975 }
976
977 if (mode == 's' && !*args[4])
978 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
979
980 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200981
982
983 /* strip trailing slashes, including first one */
984 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
985 *end = 0;
986
William Lallemandc756bbd2020-05-13 17:23:59 +0200987 lnode = ebst_lookup(&crtlists_tree, filename);
988 if (lnode == NULL)
989 return cli_err(appctx, "didn't find the specified filename\n");
990
991 appctx->ctx.cli.p0 = lnode;
992 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
993 }
994 appctx->ctx.cli.i0 = mode;
995
996 return 0;
997}
998
999/* release function of the "add ssl crt-list' command, free things and unlock
1000 the spinlock */
1001static void cli_release_add_crtlist(struct appctx *appctx)
1002{
1003 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1004
1005 if (appctx->st2 != SETCERT_ST_FIN) {
1006 struct ckch_inst *inst, *inst_s;
1007 /* upon error free the ckch_inst and everything inside */
1008 ebpt_delete(&entry->node);
1009 LIST_DEL(&entry->by_crtlist);
1010 LIST_DEL(&entry->by_ckch_store);
1011
1012 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1013 ckch_inst_free(inst);
1014 }
1015 crtlist_free_filters(entry->filters);
1016 ssl_sock_free_ssl_conf(entry->ssl_conf);
1017 free(entry->ssl_conf);
1018 free(entry);
1019 }
1020
1021 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1022}
1023
1024
1025/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1026 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1027 *
1028 * The logic is the same as the "commit ssl cert" command but without the
1029 * freeing of the old structures, because there are none.
1030 */
1031static int cli_io_handler_add_crtlist(struct appctx *appctx)
1032{
1033 struct bind_conf_list *bind_conf_node;
1034 struct stream_interface *si = appctx->owner;
1035 struct crtlist *crtlist = appctx->ctx.cli.p0;
1036 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1037 struct ckch_store *store = entry->node.key;
1038 struct buffer *trash = alloc_trash_chunk();
1039 struct ckch_inst *new_inst;
1040 char *err = NULL;
1041 int i = 0;
1042 int errcode = 0;
1043
1044 if (trash == NULL)
1045 goto error;
1046
1047 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1048 * created.
1049 */
1050 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1051 goto error;
1052
1053 while (1) {
1054 switch (appctx->st2) {
1055 case SETCERT_ST_INIT:
1056 /* This state just print the update message */
1057 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1058 if (ci_putchk(si_ic(si), trash) == -1) {
1059 si_rx_room_blk(si);
1060 goto yield;
1061 }
1062 appctx->st2 = SETCERT_ST_GEN;
1063 /* fallthrough */
1064 case SETCERT_ST_GEN:
1065 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1066 if (bind_conf_node == NULL)
1067 bind_conf_node = crtlist->bind_conf;
1068 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1069 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1070 struct sni_ctx *sni;
1071
1072 /* yield every 10 generations */
1073 if (i > 10) {
1074 appctx->ctx.cli.p2 = bind_conf_node;
1075 goto yield;
1076 }
1077
1078 /* we don't support multi-cert bundles, only simple ones */
1079 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1080 if (errcode & ERR_CODE)
1081 goto error;
1082
1083 /* we need to initialize the SSL_CTX generated */
1084 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1085 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1086 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1087 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1088 if (errcode & ERR_CODE)
1089 goto error;
1090 }
1091 }
1092 /* display one dot for each new instance */
1093 chunk_appendf(trash, ".");
1094 i++;
1095 LIST_ADDQ(&store->ckch_inst, &new_inst->by_ckchs);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001096 LIST_ADDQ(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1097 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001098 }
1099 appctx->st2 = SETCERT_ST_INSERT;
1100 /* fallthrough */
1101 case SETCERT_ST_INSERT:
1102 /* insert SNIs in bind_conf */
1103 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1104 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1105 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1106 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1107 }
1108 entry->linenum = ++crtlist->linecount;
1109 appctx->st2 = SETCERT_ST_FIN;
1110 goto end;
1111 }
1112 }
1113
1114end:
1115 chunk_appendf(trash, "\n");
1116 if (errcode & ERR_WARN)
1117 chunk_appendf(trash, "%s", err);
1118 chunk_appendf(trash, "Success!\n");
1119 if (ci_putchk(si_ic(si), trash) == -1)
1120 si_rx_room_blk(si);
1121 free_trash_chunk(trash);
1122 /* success: call the release function and don't come back */
1123 return 1;
1124yield:
1125 /* store the state */
1126 if (ci_putchk(si_ic(si), trash) == -1)
1127 si_rx_room_blk(si);
1128 free_trash_chunk(trash);
1129 si_rx_endp_more(si); /* let's come back later */
1130 return 0; /* should come back */
1131
1132error:
1133 /* spin unlock and free are done in the release function */
1134 if (trash) {
1135 chunk_appendf(trash, "\n%sFailed!\n", err);
1136 if (ci_putchk(si_ic(si), trash) == -1)
1137 si_rx_room_blk(si);
1138 free_trash_chunk(trash);
1139 }
1140 /* error: call the release function and don't come back */
1141 return 1;
1142}
1143
1144
1145/*
1146 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1147 * Filters and option must be passed through payload:
1148 */
1149static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1150{
1151 int cfgerr = 0;
1152 struct ckch_store *store;
1153 char *err = NULL;
1154 char path[MAXPATHLEN+1];
1155 char *crtlist_path;
1156 char *cert_path = NULL;
1157 struct ebmb_node *eb;
1158 struct ebpt_node *inserted;
1159 struct crtlist *crtlist;
1160 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001161 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001162
1163 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1164 return 1;
1165
1166 if (!*args[3] || (!payload && !*args[4]))
1167 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1168
1169 crtlist_path = args[3];
1170
William Lallemand99cc2182020-06-25 15:19:51 +02001171 /* strip trailing slashes, including first one */
1172 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1173 *end = 0;
1174
William Lallemandc756bbd2020-05-13 17:23:59 +02001175 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1176 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1177
1178 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1179 if (!eb) {
1180 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1181 goto error;
1182 }
1183 crtlist = ebmb_entry(eb, struct crtlist, node);
1184
1185 entry = crtlist_entry_new();
1186 if (entry == NULL) {
1187 memprintf(&err, "Not enough memory!");
1188 goto error;
1189 }
1190
1191 if (payload) {
1192 char *lf;
1193
1194 lf = strrchr(payload, '\n');
1195 if (lf) {
1196 memprintf(&err, "only one line of payload is supported!");
1197 goto error;
1198 }
1199 /* cert_path is filled here */
1200 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, &err);
1201 if (cfgerr & ERR_CODE)
1202 goto error;
1203 } else {
1204 cert_path = args[4];
1205 }
1206
1207 if (!cert_path) {
1208 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1209 cfgerr |= ERR_ALERT | ERR_FATAL;
1210 goto error;
1211 }
1212
1213 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1214 char *slash;
1215
1216 slash = strrchr(cert_path, '/');
1217 if (!slash) {
1218 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1219 goto error;
1220 }
1221 /* temporary replace / by 0 to do an strcmp */
1222 *slash = '\0';
1223 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1224 *slash = '/';
1225 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1226 goto error;
1227 }
1228 *slash = '/';
1229 }
1230
1231 if (*cert_path != '/' && global_ssl.crt_base) {
1232 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1233 memprintf(&err, "'%s' : path too long", cert_path);
1234 cfgerr |= ERR_ALERT | ERR_FATAL;
1235 goto error;
1236 }
1237 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1238 cert_path = path;
1239 }
1240
1241 store = ckchs_lookup(cert_path);
1242 if (store == NULL) {
1243 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1244 goto error;
1245 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001246 if (store->ckch == NULL || store->ckch->cert == NULL) {
1247 memprintf(&err, "certificate '%s' is empty!", cert_path);
1248 goto error;
1249 }
1250
1251 /* check if it's possible to insert this new crtlist_entry */
1252 entry->node.key = store;
1253 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1254 if (inserted != &entry->node) {
1255 memprintf(&err, "file already exists in this directory!");
1256 goto error;
1257 }
1258
1259 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1260 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1261 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1262 goto error;
1263 }
1264
1265 LIST_ADDQ(&crtlist->ord_entries, &entry->by_crtlist);
1266 entry->crtlist = crtlist;
1267 LIST_ADDQ(&store->crtlist_entry, &entry->by_ckch_store);
1268
1269 appctx->st2 = SETCERT_ST_INIT;
1270 appctx->ctx.cli.p0 = crtlist;
1271 appctx->ctx.cli.p1 = entry;
1272
1273 /* unlock is done in the release handler */
1274 return 0;
1275
1276error:
1277 crtlist_entry_free(entry);
1278 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1279 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1280 return cli_dynerr(appctx, err);
1281}
1282
1283/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1284static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1285{
1286 struct ckch_store *store;
1287 char *err = NULL;
1288 char *crtlist_path, *cert_path;
1289 struct ebmb_node *ebmb;
1290 struct ebpt_node *ebpt;
1291 struct crtlist *crtlist;
1292 struct crtlist_entry *entry = NULL;
1293 struct ckch_inst *inst, *inst_s;
1294 int linenum = 0;
1295 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001296 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001297
1298 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1299 return 1;
1300
1301 if (!*args[3] || !*args[4])
1302 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1303
1304 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1305 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1306
1307 crtlist_path = args[3];
1308 cert_path = args[4];
1309
1310 colons = strchr(cert_path, ':');
1311 if (colons) {
1312 char *endptr;
1313
1314 linenum = strtol(colons + 1, &endptr, 10);
1315 if (colons + 1 == endptr || *endptr != '\0') {
1316 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1317 goto error;
1318 }
1319 *colons = '\0';
1320 }
William Lallemand99cc2182020-06-25 15:19:51 +02001321
1322 /* strip trailing slashes, including first one */
1323 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1324 *end = 0;
1325
William Lallemandc756bbd2020-05-13 17:23:59 +02001326 /* look for crtlist */
1327 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1328 if (!ebmb) {
1329 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1330 goto error;
1331 }
1332 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1333
1334 /* look for store */
1335 store = ckchs_lookup(cert_path);
1336 if (store == NULL) {
1337 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1338 goto error;
1339 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001340 if (store->ckch == NULL || store->ckch->cert == NULL) {
1341 memprintf(&err, "certificate '%s' is empty!", cert_path);
1342 goto error;
1343 }
1344
1345 ebpt = ebpt_lookup(&crtlist->entries, store);
1346 if (!ebpt) {
1347 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1348 goto error;
1349 }
1350
1351 /* list the line number of entries for errors in err, and select the right ebpt */
1352 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1353 struct crtlist_entry *tmp;
1354
1355 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1356 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1357
1358 /* select the entry we wanted */
1359 if (linenum == 0 || tmp->linenum == linenum) {
1360 if (!entry)
1361 entry = tmp;
1362 }
1363 }
1364
1365 /* we didn't found the specified entry */
1366 if (!entry) {
1367 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);
1368 goto error;
1369 }
1370
1371 /* we didn't specified a line number but there were several entries */
1372 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1373 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1374 goto error;
1375 }
1376
1377 /* upon error free the ckch_inst and everything inside */
1378
1379 ebpt_delete(&entry->node);
1380 LIST_DEL(&entry->by_crtlist);
1381 LIST_DEL(&entry->by_ckch_store);
1382
1383 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1384 struct sni_ctx *sni, *sni_s;
1385
1386 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1387 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1388 ebmb_delete(&sni->name);
1389 LIST_DEL(&sni->by_ckch_inst);
1390 SSL_CTX_free(sni->ctx);
1391 free(sni);
1392 }
1393 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1394 LIST_DEL(&inst->by_ckchs);
1395 free(inst);
1396 }
1397
1398 crtlist_free_filters(entry->filters);
1399 ssl_sock_free_ssl_conf(entry->ssl_conf);
1400 free(entry->ssl_conf);
1401 free(entry);
1402
1403 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1404 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1405 return cli_dynmsg(appctx, LOG_NOTICE, err);
1406
1407error:
1408 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1409 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1410 return cli_dynerr(appctx, err);
1411}
1412
1413
William Lallemandee8530c2020-06-23 18:19:42 +02001414/* unlink and free all crt-list and crt-list entries */
1415void crtlist_deinit()
1416{
1417 struct eb_node *node, *next;
1418 struct crtlist *crtlist;
1419
1420 node = eb_first(&crtlists_tree);
1421 while (node) {
1422 next = eb_next(node);
1423 crtlist = ebmb_entry(node, struct crtlist, node);
1424 crtlist_free(crtlist);
1425 node = next;
1426 }
1427}
1428
William Lallemandc756bbd2020-05-13 17:23:59 +02001429
1430/* register cli keywords */
1431static struct cli_kw_list cli_kws = {{ },{
1432 { { "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 },
1433 { { "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 },
1434 { { "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 },
1435 { { NULL }, NULL, NULL, NULL } }
1436};
1437
1438INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1439