blob: ba097999784f614edc9a35f8f8022a5bb0c133e4 [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;
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050053#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand6e9556b2020-05-12 17:52:44 +020054 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 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500112#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200113 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 Lallemand86c2dd62020-11-20 14:23:38 +0100472 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200473 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100474 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200475
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200476 if (missing_lf != -1) {
477 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
478 file, linenum, (missing_lf + 1));
479 cfgerr |= ERR_ALERT | ERR_FATAL;
480 missing_lf = -1;
481 break;
482 }
483
William Lallemand6e9556b2020-05-12 17:52:44 +0200484 linenum++;
485 end = line + strlen(line);
486 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
487 /* Check if we reached the limit and the last char is not \n.
488 * Watch out for the last line without the terminating '\n'!
489 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200490 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
491 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200492 cfgerr |= ERR_ALERT | ERR_FATAL;
493 break;
494 }
495
496 if (*line == '#' || *line == '\n' || *line == '\r')
497 continue;
498
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200499 if (end > line && *(end-1) == '\n') {
500 /* kill trailing LF */
501 *(end - 1) = 0;
502 }
503 else {
504 /* mark this line as truncated */
505 missing_lf = end - line;
506 }
507
William Lallemand6e9556b2020-05-12 17:52:44 +0200508 entry = crtlist_entry_new();
509 if (entry == NULL) {
510 memprintf(err, "Not enough memory!");
511 cfgerr |= ERR_ALERT | ERR_FATAL;
512 goto error;
513 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200514
William Lallemand6e9556b2020-05-12 17:52:44 +0200515 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200516 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200517 goto error;
518
519 /* empty line */
520 if (!crt_path || !*crt_path) {
521 crtlist_entry_free(entry);
522 entry = NULL;
523 continue;
524 }
525
526 if (*crt_path != '/' && global_ssl.crt_base) {
527 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200528 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
529 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200530 cfgerr |= ERR_ALERT | ERR_FATAL;
531 goto error;
532 }
533 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
534 crt_path = path;
535 }
536
537 /* Look for a ckch_store or create one */
538 ckchs = ckchs_lookup(crt_path);
539 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200540 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100541 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200542
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200543 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200544 if (ckchs == NULL) {
545 cfgerr |= ERR_ALERT | ERR_FATAL;
546 goto error;
547 }
548
549 entry->node.key = ckchs;
550 entry->crtlist = newlist;
551 ebpt_insert(&newlist->entries, &entry->node);
552 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
553 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200554
William Lallemand73404572020-11-20 18:23:40 +0100555 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200556 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200557 bundle, since 2.3 we don't support multiple
558 certificate in the same OpenSSL store, so we
559 emulate it by loading each file separately. To
560 do so we need to duplicate the entry in the
561 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200562 char fp[MAXPATHLEN+1] = {0};
563 int n = 0;
564 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
565 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
566 struct stat buf;
567 int ret;
568
William Lallemand86c2dd62020-11-20 14:23:38 +0100569 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200570 if (ret > sizeof(fp))
571 continue;
572
573 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100574 if (!ckchs) {
575 if (stat(fp, &buf) == 0) {
576 ckchs = ckchs_load_cert_file(fp, err);
577 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200578 cfgerr |= ERR_ALERT | ERR_FATAL;
579 goto error;
580 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100581 } else {
582 continue; /* didn't find this extension, skip */
583 }
584 }
585 found++;
586 linenum++; /* we duplicate the line for this entry in the bundle */
587 if (!entry_dup) { /* if the entry was used, duplicate one */
588 linenum++;
589 entry_dup = crtlist_entry_dup(entry);
590 if (!entry_dup) {
591 cfgerr |= ERR_ALERT | ERR_FATAL;
592 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200593 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100594 entry_dup->linenum = linenum;
595 }
William Lallemand47da8212020-09-10 19:13:27 +0200596
William Lallemand77e1c6f2020-11-20 18:26:09 +0100597 entry_dup->node.key = ckchs;
598 entry_dup->crtlist = newlist;
599 ebpt_insert(&newlist->entries, &entry_dup->node);
600 LIST_ADDQ(&newlist->ord_entries, &entry_dup->by_crtlist);
601 LIST_ADDQ(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200602
William Lallemand77e1c6f2020-11-20 18:26:09 +0100603 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200604 }
605 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100606 if (!found) {
607 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
608 err && *err ? *err : "", crt_path, strerror(errno));
609 cfgerr |= ERR_ALERT | ERR_FATAL;
610 }
611
William Lallemand50c03aa2020-11-06 16:24:07 +0100612 } else {
613 entry->node.key = ckchs;
614 entry->crtlist = newlist;
615 ebpt_insert(&newlist->entries, &entry->node);
616 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
617 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100618 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200619 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200620 entry = NULL;
621 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200622
623 if (missing_lf != -1) {
624 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
625 file, linenum, (missing_lf + 1));
626 cfgerr |= ERR_ALERT | ERR_FATAL;
627 }
628
William Lallemand6e9556b2020-05-12 17:52:44 +0200629 if (cfgerr & ERR_CODE)
630 goto error;
631
632 newlist->linecount = linenum;
633
634 fclose(f);
635 *crtlist = newlist;
636
637 return cfgerr;
638error:
639 crtlist_entry_free(entry);
640
641 fclose(f);
642 crtlist_free(newlist);
643 return cfgerr;
644}
645
646/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
647 * Fill the <crtlist> argument with a pointer to a new crtlist struct
648 *
649 * This function tries to open and store certificate files.
650 */
651int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
652{
653 struct crtlist *dir;
654 struct dirent **de_list;
655 int i, n;
656 struct stat buf;
657 char *end;
658 char fp[MAXPATHLEN+1];
659 int cfgerr = 0;
660 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200661
662 dir = crtlist_new(path, 1);
663 if (dir == NULL) {
664 memprintf(err, "not enough memory");
665 return ERR_ALERT | ERR_FATAL;
666 }
667
668 n = scandir(path, &de_list, 0, alphasort);
669 if (n < 0) {
670 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
671 err && *err ? *err : "", path, strerror(errno));
672 cfgerr |= ERR_ALERT | ERR_FATAL;
673 }
674 else {
675 for (i = 0; i < n; i++) {
676 struct crtlist_entry *entry;
677 struct dirent *de = de_list[i];
678
679 end = strrchr(de->d_name, '.');
680 if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl") || !strcmp(end, ".key")))
681 goto ignore_entry;
682
683 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
684 if (stat(fp, &buf) != 0) {
685 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
686 err && *err ? *err : "", fp, strerror(errno));
687 cfgerr |= ERR_ALERT | ERR_FATAL;
688 goto ignore_entry;
689 }
690 if (!S_ISREG(buf.st_mode))
691 goto ignore_entry;
692
693 entry = crtlist_entry_new();
694 if (entry == NULL) {
695 memprintf(err, "not enough memory '%s'", fp);
696 cfgerr |= ERR_ALERT | ERR_FATAL;
697 goto ignore_entry;
698 }
699
William Lallemand6e9556b2020-05-12 17:52:44 +0200700 ckchs = ckchs_lookup(fp);
701 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200702 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200703 if (ckchs == NULL) {
704 free(de);
705 free(entry);
706 cfgerr |= ERR_ALERT | ERR_FATAL;
707 goto end;
708 }
709 entry->node.key = ckchs;
710 entry->crtlist = dir;
711 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
712 LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist);
713 ebpt_insert(&dir->entries, &entry->node);
714
715ignore_entry:
716 free(de);
717 }
718end:
719 free(de_list);
720 }
721
722 if (cfgerr & ERR_CODE) {
723 /* free the dir and entries on error */
724 crtlist_free(dir);
725 } else {
726 *crtlist = dir;
727 }
728 return cfgerr;
729
730}
731
William Lallemandc756bbd2020-05-13 17:23:59 +0200732/*
733 * Take an ssl_bind_conf structure and append the configuration line used to
734 * create it in the buffer
735 */
736static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
737{
738 int space = 0;
739
740 if (conf == NULL)
741 return;
742
743 chunk_appendf(buf, " [");
744#ifdef OPENSSL_NPN_NEGOTIATED
745 if (conf->npn_str) {
746 int len = conf->npn_len;
747 char *ptr = conf->npn_str;
748 int comma = 0;
749
750 if (space) chunk_appendf(buf, " ");
751 chunk_appendf(buf, "npn ");
752 while (len) {
753 unsigned short size;
754
755 size = *ptr;
756 ptr++;
757 if (comma)
758 chunk_memcat(buf, ",", 1);
759 chunk_memcat(buf, ptr, size);
760 ptr += size;
761 len -= size + 1;
762 comma = 1;
763 }
764 chunk_memcat(buf, "", 1); /* finish with a \0 */
765 space++;
766 }
767#endif
768#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
769 if (conf->alpn_str) {
770 int len = conf->alpn_len;
771 char *ptr = conf->alpn_str;
772 int comma = 0;
773
774 if (space) chunk_appendf(buf, " ");
775 chunk_appendf(buf, "alpn ");
776 while (len) {
777 unsigned short size;
778
779 size = *ptr;
780 ptr++;
781 if (comma)
782 chunk_memcat(buf, ",", 1);
783 chunk_memcat(buf, ptr, size);
784 ptr += size;
785 len -= size + 1;
786 comma = 1;
787 }
788 chunk_memcat(buf, "", 1); /* finish with a \0 */
789 space++;
790 }
791#endif
792 /* verify */
793 {
794 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
795 if (space) chunk_appendf(buf, " ");
796 chunk_appendf(buf, "verify none");
797 space++;
798 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
799 if (space) chunk_appendf(buf, " ");
800 chunk_appendf(buf, "verify optional");
801 space++;
802 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
803 if (space) chunk_appendf(buf, " ");
804 chunk_appendf(buf, "verify required");
805 space++;
806 }
807 }
808
809 if (conf->no_ca_names) {
810 if (space) chunk_appendf(buf, " ");
811 chunk_appendf(buf, "no-ca-names");
812 space++;
813 }
814
815 if (conf->early_data) {
816 if (space) chunk_appendf(buf, " ");
817 chunk_appendf(buf, "allow-0rtt");
818 space++;
819 }
820 if (conf->ca_file) {
821 if (space) chunk_appendf(buf, " ");
822 chunk_appendf(buf, "ca-file %s", conf->ca_file);
823 space++;
824 }
825 if (conf->crl_file) {
826 if (space) chunk_appendf(buf, " ");
827 chunk_appendf(buf, "crl-file %s", conf->crl_file);
828 space++;
829 }
830 if (conf->ciphers) {
831 if (space) chunk_appendf(buf, " ");
832 chunk_appendf(buf, "ciphers %s", conf->ciphers);
833 space++;
834 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500835#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200836 if (conf->ciphersuites) {
837 if (space) chunk_appendf(buf, " ");
838 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
839 space++;
840 }
841#endif
842 if (conf->curves) {
843 if (space) chunk_appendf(buf, " ");
844 chunk_appendf(buf, "curves %s", conf->curves);
845 space++;
846 }
847 if (conf->ecdhe) {
848 if (space) chunk_appendf(buf, " ");
849 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
850 space++;
851 }
852
853 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200854 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200855 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200856 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200857 space++;
858 }
859
William Lallemand8177ad92020-05-20 16:49:02 +0200860 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200861 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200862 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200863 space++;
864 }
865
866 chunk_appendf(buf, "]");
867
868 return;
869}
870
871/* dump a list of filters */
872static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
873{
874 int i;
875
876 if (!entry->fcount)
877 return;
878
879 for (i = 0; i < entry->fcount; i++) {
880 chunk_appendf(buf, " %s", entry->filters[i]);
881 }
882 return;
883}
884
885/************************** CLI functions ****************************/
886
887
888/* CLI IO handler for '(show|dump) ssl crt-list' */
889static int cli_io_handler_dump_crtlist(struct appctx *appctx)
890{
891 struct buffer *trash = alloc_trash_chunk();
892 struct stream_interface *si = appctx->owner;
893 struct ebmb_node *lnode;
894
895 if (trash == NULL)
896 return 1;
897
898 /* dump the list of crt-lists */
899 lnode = appctx->ctx.cli.p1;
900 if (lnode == NULL)
901 lnode = ebmb_first(&crtlists_tree);
902 while (lnode) {
903 chunk_appendf(trash, "%s\n", lnode->key);
904 if (ci_putchk(si_ic(si), trash) == -1) {
905 si_rx_room_blk(si);
906 goto yield;
907 }
908 lnode = ebmb_next(lnode);
909 }
910 free_trash_chunk(trash);
911 return 1;
912yield:
913 appctx->ctx.cli.p1 = lnode;
914 free_trash_chunk(trash);
915 return 0;
916}
917
918/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
919static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
920{
921 struct buffer *trash = alloc_trash_chunk();
922 struct crtlist *crtlist;
923 struct stream_interface *si = appctx->owner;
924 struct crtlist_entry *entry;
925
926 if (trash == NULL)
927 return 1;
928
929 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
930
931 entry = appctx->ctx.cli.p1;
932 if (entry == NULL) {
933 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
934 chunk_appendf(trash, "# %s\n", crtlist->node.key);
935 if (ci_putchk(si_ic(si), trash) == -1) {
936 si_rx_room_blk(si);
937 goto yield;
938 }
939 }
940
941 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
942 struct ckch_store *store;
943 const char *filename;
944
945 store = entry->node.key;
946 filename = store->path;
947 chunk_appendf(trash, "%s", filename);
948 if (appctx->ctx.cli.i0 == 's') /* show */
949 chunk_appendf(trash, ":%d", entry->linenum);
950 dump_crtlist_sslconf(trash, entry->ssl_conf);
951 dump_crtlist_filters(trash, entry);
952 chunk_appendf(trash, "\n");
953
954 if (ci_putchk(si_ic(si), trash) == -1) {
955 si_rx_room_blk(si);
956 goto yield;
957 }
958 }
959 free_trash_chunk(trash);
960 return 1;
961yield:
962 appctx->ctx.cli.p1 = entry;
963 free_trash_chunk(trash);
964 return 0;
965}
966
967/* CLI argument parser for '(show|dump) ssl crt-list' */
968static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
969{
970 struct ebmb_node *lnode;
971 char *filename = NULL;
972 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200973 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200974
975 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
976 return 1;
977
978 appctx->ctx.cli.p0 = NULL;
979 appctx->ctx.cli.p1 = NULL;
980
981 if (*args[3] && !strcmp(args[3], "-n")) {
982 mode = 's';
983 filename = args[4];
984 } else {
985 mode = 'd';
986 filename = args[3];
987 }
988
989 if (mode == 's' && !*args[4])
990 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
991
992 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200993
994
995 /* strip trailing slashes, including first one */
996 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
997 *end = 0;
998
William Lallemandc756bbd2020-05-13 17:23:59 +0200999 lnode = ebst_lookup(&crtlists_tree, filename);
1000 if (lnode == NULL)
1001 return cli_err(appctx, "didn't find the specified filename\n");
1002
1003 appctx->ctx.cli.p0 = lnode;
1004 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1005 }
1006 appctx->ctx.cli.i0 = mode;
1007
1008 return 0;
1009}
1010
1011/* release function of the "add ssl crt-list' command, free things and unlock
1012 the spinlock */
1013static void cli_release_add_crtlist(struct appctx *appctx)
1014{
1015 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1016
1017 if (appctx->st2 != SETCERT_ST_FIN) {
1018 struct ckch_inst *inst, *inst_s;
1019 /* upon error free the ckch_inst and everything inside */
1020 ebpt_delete(&entry->node);
1021 LIST_DEL(&entry->by_crtlist);
1022 LIST_DEL(&entry->by_ckch_store);
1023
1024 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1025 ckch_inst_free(inst);
1026 }
1027 crtlist_free_filters(entry->filters);
1028 ssl_sock_free_ssl_conf(entry->ssl_conf);
1029 free(entry->ssl_conf);
1030 free(entry);
1031 }
1032
1033 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1034}
1035
1036
1037/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1038 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1039 *
1040 * The logic is the same as the "commit ssl cert" command but without the
1041 * freeing of the old structures, because there are none.
1042 */
1043static int cli_io_handler_add_crtlist(struct appctx *appctx)
1044{
1045 struct bind_conf_list *bind_conf_node;
1046 struct stream_interface *si = appctx->owner;
1047 struct crtlist *crtlist = appctx->ctx.cli.p0;
1048 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1049 struct ckch_store *store = entry->node.key;
1050 struct buffer *trash = alloc_trash_chunk();
1051 struct ckch_inst *new_inst;
1052 char *err = NULL;
1053 int i = 0;
1054 int errcode = 0;
1055
1056 if (trash == NULL)
1057 goto error;
1058
1059 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1060 * created.
1061 */
1062 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1063 goto error;
1064
1065 while (1) {
1066 switch (appctx->st2) {
1067 case SETCERT_ST_INIT:
1068 /* This state just print the update message */
1069 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1070 if (ci_putchk(si_ic(si), trash) == -1) {
1071 si_rx_room_blk(si);
1072 goto yield;
1073 }
1074 appctx->st2 = SETCERT_ST_GEN;
1075 /* fallthrough */
1076 case SETCERT_ST_GEN:
1077 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1078 if (bind_conf_node == NULL)
1079 bind_conf_node = crtlist->bind_conf;
1080 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1081 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1082 struct sni_ctx *sni;
1083
1084 /* yield every 10 generations */
1085 if (i > 10) {
1086 appctx->ctx.cli.p2 = bind_conf_node;
1087 goto yield;
1088 }
1089
1090 /* we don't support multi-cert bundles, only simple ones */
1091 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1092 if (errcode & ERR_CODE)
1093 goto error;
1094
1095 /* we need to initialize the SSL_CTX generated */
1096 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1097 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1098 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1099 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1100 if (errcode & ERR_CODE)
1101 goto error;
1102 }
1103 }
1104 /* display one dot for each new instance */
1105 chunk_appendf(trash, ".");
1106 i++;
1107 LIST_ADDQ(&store->ckch_inst, &new_inst->by_ckchs);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001108 LIST_ADDQ(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1109 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001110 }
1111 appctx->st2 = SETCERT_ST_INSERT;
1112 /* fallthrough */
1113 case SETCERT_ST_INSERT:
1114 /* insert SNIs in bind_conf */
1115 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1116 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1117 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1118 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1119 }
1120 entry->linenum = ++crtlist->linecount;
1121 appctx->st2 = SETCERT_ST_FIN;
1122 goto end;
1123 }
1124 }
1125
1126end:
1127 chunk_appendf(trash, "\n");
1128 if (errcode & ERR_WARN)
1129 chunk_appendf(trash, "%s", err);
1130 chunk_appendf(trash, "Success!\n");
1131 if (ci_putchk(si_ic(si), trash) == -1)
1132 si_rx_room_blk(si);
1133 free_trash_chunk(trash);
1134 /* success: call the release function and don't come back */
1135 return 1;
1136yield:
1137 /* store the state */
1138 if (ci_putchk(si_ic(si), trash) == -1)
1139 si_rx_room_blk(si);
1140 free_trash_chunk(trash);
1141 si_rx_endp_more(si); /* let's come back later */
1142 return 0; /* should come back */
1143
1144error:
1145 /* spin unlock and free are done in the release function */
1146 if (trash) {
1147 chunk_appendf(trash, "\n%sFailed!\n", err);
1148 if (ci_putchk(si_ic(si), trash) == -1)
1149 si_rx_room_blk(si);
1150 free_trash_chunk(trash);
1151 }
1152 /* error: call the release function and don't come back */
1153 return 1;
1154}
1155
1156
1157/*
1158 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1159 * Filters and option must be passed through payload:
1160 */
1161static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1162{
1163 int cfgerr = 0;
1164 struct ckch_store *store;
1165 char *err = NULL;
1166 char path[MAXPATHLEN+1];
1167 char *crtlist_path;
1168 char *cert_path = NULL;
1169 struct ebmb_node *eb;
1170 struct ebpt_node *inserted;
1171 struct crtlist *crtlist;
1172 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001173 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001174
1175 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1176 return 1;
1177
1178 if (!*args[3] || (!payload && !*args[4]))
1179 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1180
1181 crtlist_path = args[3];
1182
William Lallemand99cc2182020-06-25 15:19:51 +02001183 /* strip trailing slashes, including first one */
1184 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1185 *end = 0;
1186
William Lallemandc756bbd2020-05-13 17:23:59 +02001187 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1188 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1189
1190 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1191 if (!eb) {
1192 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1193 goto error;
1194 }
1195 crtlist = ebmb_entry(eb, struct crtlist, node);
1196
1197 entry = crtlist_entry_new();
1198 if (entry == NULL) {
1199 memprintf(&err, "Not enough memory!");
1200 goto error;
1201 }
1202
1203 if (payload) {
1204 char *lf;
1205
1206 lf = strrchr(payload, '\n');
1207 if (lf) {
1208 memprintf(&err, "only one line of payload is supported!");
1209 goto error;
1210 }
1211 /* cert_path is filled here */
1212 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, &err);
1213 if (cfgerr & ERR_CODE)
1214 goto error;
1215 } else {
1216 cert_path = args[4];
1217 }
1218
1219 if (!cert_path) {
1220 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1221 cfgerr |= ERR_ALERT | ERR_FATAL;
1222 goto error;
1223 }
1224
1225 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1226 char *slash;
1227
1228 slash = strrchr(cert_path, '/');
1229 if (!slash) {
1230 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1231 goto error;
1232 }
1233 /* temporary replace / by 0 to do an strcmp */
1234 *slash = '\0';
1235 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1236 *slash = '/';
1237 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1238 goto error;
1239 }
1240 *slash = '/';
1241 }
1242
1243 if (*cert_path != '/' && global_ssl.crt_base) {
1244 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1245 memprintf(&err, "'%s' : path too long", cert_path);
1246 cfgerr |= ERR_ALERT | ERR_FATAL;
1247 goto error;
1248 }
1249 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1250 cert_path = path;
1251 }
1252
1253 store = ckchs_lookup(cert_path);
1254 if (store == NULL) {
1255 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1256 goto error;
1257 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001258 if (store->ckch == NULL || store->ckch->cert == NULL) {
1259 memprintf(&err, "certificate '%s' is empty!", cert_path);
1260 goto error;
1261 }
1262
1263 /* check if it's possible to insert this new crtlist_entry */
1264 entry->node.key = store;
1265 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1266 if (inserted != &entry->node) {
1267 memprintf(&err, "file already exists in this directory!");
1268 goto error;
1269 }
1270
1271 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1272 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1273 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1274 goto error;
1275 }
1276
1277 LIST_ADDQ(&crtlist->ord_entries, &entry->by_crtlist);
1278 entry->crtlist = crtlist;
1279 LIST_ADDQ(&store->crtlist_entry, &entry->by_ckch_store);
1280
1281 appctx->st2 = SETCERT_ST_INIT;
1282 appctx->ctx.cli.p0 = crtlist;
1283 appctx->ctx.cli.p1 = entry;
1284
1285 /* unlock is done in the release handler */
1286 return 0;
1287
1288error:
1289 crtlist_entry_free(entry);
1290 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1291 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1292 return cli_dynerr(appctx, err);
1293}
1294
1295/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1296static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1297{
1298 struct ckch_store *store;
1299 char *err = NULL;
1300 char *crtlist_path, *cert_path;
1301 struct ebmb_node *ebmb;
1302 struct ebpt_node *ebpt;
1303 struct crtlist *crtlist;
1304 struct crtlist_entry *entry = NULL;
1305 struct ckch_inst *inst, *inst_s;
1306 int linenum = 0;
1307 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001308 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001309
1310 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1311 return 1;
1312
1313 if (!*args[3] || !*args[4])
1314 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1315
1316 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1317 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1318
1319 crtlist_path = args[3];
1320 cert_path = args[4];
1321
1322 colons = strchr(cert_path, ':');
1323 if (colons) {
1324 char *endptr;
1325
1326 linenum = strtol(colons + 1, &endptr, 10);
1327 if (colons + 1 == endptr || *endptr != '\0') {
1328 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1329 goto error;
1330 }
1331 *colons = '\0';
1332 }
William Lallemand99cc2182020-06-25 15:19:51 +02001333
1334 /* strip trailing slashes, including first one */
1335 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1336 *end = 0;
1337
William Lallemandc756bbd2020-05-13 17:23:59 +02001338 /* look for crtlist */
1339 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1340 if (!ebmb) {
1341 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1342 goto error;
1343 }
1344 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1345
1346 /* look for store */
1347 store = ckchs_lookup(cert_path);
1348 if (store == NULL) {
1349 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1350 goto error;
1351 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001352 if (store->ckch == NULL || store->ckch->cert == NULL) {
1353 memprintf(&err, "certificate '%s' is empty!", cert_path);
1354 goto error;
1355 }
1356
1357 ebpt = ebpt_lookup(&crtlist->entries, store);
1358 if (!ebpt) {
1359 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1360 goto error;
1361 }
1362
1363 /* list the line number of entries for errors in err, and select the right ebpt */
1364 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1365 struct crtlist_entry *tmp;
1366
1367 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1368 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1369
1370 /* select the entry we wanted */
1371 if (linenum == 0 || tmp->linenum == linenum) {
1372 if (!entry)
1373 entry = tmp;
1374 }
1375 }
1376
1377 /* we didn't found the specified entry */
1378 if (!entry) {
1379 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);
1380 goto error;
1381 }
1382
1383 /* we didn't specified a line number but there were several entries */
1384 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1385 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1386 goto error;
1387 }
1388
1389 /* upon error free the ckch_inst and everything inside */
1390
1391 ebpt_delete(&entry->node);
1392 LIST_DEL(&entry->by_crtlist);
1393 LIST_DEL(&entry->by_ckch_store);
1394
1395 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1396 struct sni_ctx *sni, *sni_s;
1397
1398 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1399 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1400 ebmb_delete(&sni->name);
1401 LIST_DEL(&sni->by_ckch_inst);
1402 SSL_CTX_free(sni->ctx);
1403 free(sni);
1404 }
1405 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1406 LIST_DEL(&inst->by_ckchs);
1407 free(inst);
1408 }
1409
1410 crtlist_free_filters(entry->filters);
1411 ssl_sock_free_ssl_conf(entry->ssl_conf);
1412 free(entry->ssl_conf);
1413 free(entry);
1414
1415 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1416 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1417 return cli_dynmsg(appctx, LOG_NOTICE, err);
1418
1419error:
1420 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1421 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1422 return cli_dynerr(appctx, err);
1423}
1424
1425
William Lallemandee8530c2020-06-23 18:19:42 +02001426/* unlink and free all crt-list and crt-list entries */
1427void crtlist_deinit()
1428{
1429 struct eb_node *node, *next;
1430 struct crtlist *crtlist;
1431
1432 node = eb_first(&crtlists_tree);
1433 while (node) {
1434 next = eb_next(node);
1435 crtlist = ebmb_entry(node, struct crtlist, node);
1436 crtlist_free(crtlist);
1437 node = next;
1438 }
1439}
1440
William Lallemandc756bbd2020-05-13 17:23:59 +02001441
1442/* register cli keywords */
1443static struct cli_kw_list cli_kws = {{ },{
1444 { { "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 },
1445 { { "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 },
1446 { { "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 },
1447 { { NULL }, NULL, NULL, NULL } }
1448};
1449
1450INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1451