blob: c51fa1179b499f35e70ed097597f9ee68516eed1 [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 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100605#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
606 if (found) {
607 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
608 err && *err ? *err : "", crt_path);
609 cfgerr |= ERR_ALERT | ERR_FATAL;
610 }
611#endif
William Lallemand47da8212020-09-10 19:13:27 +0200612 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100613 if (!found) {
614 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
615 err && *err ? *err : "", crt_path, strerror(errno));
616 cfgerr |= ERR_ALERT | ERR_FATAL;
617 }
618
William Lallemand50c03aa2020-11-06 16:24:07 +0100619 } else {
620 entry->node.key = ckchs;
621 entry->crtlist = newlist;
622 ebpt_insert(&newlist->entries, &entry->node);
623 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
624 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100625 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200626 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200627 entry = NULL;
628 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200629
630 if (missing_lf != -1) {
631 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
632 file, linenum, (missing_lf + 1));
633 cfgerr |= ERR_ALERT | ERR_FATAL;
634 }
635
William Lallemand6e9556b2020-05-12 17:52:44 +0200636 if (cfgerr & ERR_CODE)
637 goto error;
638
639 newlist->linecount = linenum;
640
641 fclose(f);
642 *crtlist = newlist;
643
644 return cfgerr;
645error:
646 crtlist_entry_free(entry);
647
648 fclose(f);
649 crtlist_free(newlist);
650 return cfgerr;
651}
652
653/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
654 * Fill the <crtlist> argument with a pointer to a new crtlist struct
655 *
656 * This function tries to open and store certificate files.
657 */
658int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
659{
660 struct crtlist *dir;
661 struct dirent **de_list;
662 int i, n;
663 struct stat buf;
664 char *end;
665 char fp[MAXPATHLEN+1];
666 int cfgerr = 0;
667 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200668
669 dir = crtlist_new(path, 1);
670 if (dir == NULL) {
671 memprintf(err, "not enough memory");
672 return ERR_ALERT | ERR_FATAL;
673 }
674
675 n = scandir(path, &de_list, 0, alphasort);
676 if (n < 0) {
677 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
678 err && *err ? *err : "", path, strerror(errno));
679 cfgerr |= ERR_ALERT | ERR_FATAL;
680 }
681 else {
682 for (i = 0; i < n; i++) {
683 struct crtlist_entry *entry;
684 struct dirent *de = de_list[i];
685
686 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100687 if (end && (strcmp(end, ".issuer") == 0 || strcmp(end, ".ocsp") == 0 || strcmp(end, ".sctl") == 0 || strcmp(end, ".key") == 0))
William Lallemand6e9556b2020-05-12 17:52:44 +0200688 goto ignore_entry;
689
690 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
691 if (stat(fp, &buf) != 0) {
692 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
693 err && *err ? *err : "", fp, strerror(errno));
694 cfgerr |= ERR_ALERT | ERR_FATAL;
695 goto ignore_entry;
696 }
697 if (!S_ISREG(buf.st_mode))
698 goto ignore_entry;
699
700 entry = crtlist_entry_new();
701 if (entry == NULL) {
702 memprintf(err, "not enough memory '%s'", fp);
703 cfgerr |= ERR_ALERT | ERR_FATAL;
704 goto ignore_entry;
705 }
706
William Lallemand6e9556b2020-05-12 17:52:44 +0200707 ckchs = ckchs_lookup(fp);
708 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200709 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200710 if (ckchs == NULL) {
711 free(de);
712 free(entry);
713 cfgerr |= ERR_ALERT | ERR_FATAL;
714 goto end;
715 }
716 entry->node.key = ckchs;
717 entry->crtlist = dir;
718 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
719 LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist);
720 ebpt_insert(&dir->entries, &entry->node);
721
722ignore_entry:
723 free(de);
724 }
725end:
726 free(de_list);
727 }
728
729 if (cfgerr & ERR_CODE) {
730 /* free the dir and entries on error */
731 crtlist_free(dir);
732 } else {
733 *crtlist = dir;
734 }
735 return cfgerr;
736
737}
738
William Lallemandc756bbd2020-05-13 17:23:59 +0200739/*
740 * Take an ssl_bind_conf structure and append the configuration line used to
741 * create it in the buffer
742 */
743static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
744{
745 int space = 0;
746
747 if (conf == NULL)
748 return;
749
750 chunk_appendf(buf, " [");
751#ifdef OPENSSL_NPN_NEGOTIATED
752 if (conf->npn_str) {
753 int len = conf->npn_len;
754 char *ptr = conf->npn_str;
755 int comma = 0;
756
757 if (space) chunk_appendf(buf, " ");
758 chunk_appendf(buf, "npn ");
759 while (len) {
760 unsigned short size;
761
762 size = *ptr;
763 ptr++;
764 if (comma)
765 chunk_memcat(buf, ",", 1);
766 chunk_memcat(buf, ptr, size);
767 ptr += size;
768 len -= size + 1;
769 comma = 1;
770 }
771 chunk_memcat(buf, "", 1); /* finish with a \0 */
772 space++;
773 }
774#endif
775#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
776 if (conf->alpn_str) {
777 int len = conf->alpn_len;
778 char *ptr = conf->alpn_str;
779 int comma = 0;
780
781 if (space) chunk_appendf(buf, " ");
782 chunk_appendf(buf, "alpn ");
783 while (len) {
784 unsigned short size;
785
786 size = *ptr;
787 ptr++;
788 if (comma)
789 chunk_memcat(buf, ",", 1);
790 chunk_memcat(buf, ptr, size);
791 ptr += size;
792 len -= size + 1;
793 comma = 1;
794 }
795 chunk_memcat(buf, "", 1); /* finish with a \0 */
796 space++;
797 }
798#endif
799 /* verify */
800 {
801 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
802 if (space) chunk_appendf(buf, " ");
803 chunk_appendf(buf, "verify none");
804 space++;
805 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
806 if (space) chunk_appendf(buf, " ");
807 chunk_appendf(buf, "verify optional");
808 space++;
809 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
810 if (space) chunk_appendf(buf, " ");
811 chunk_appendf(buf, "verify required");
812 space++;
813 }
814 }
815
816 if (conf->no_ca_names) {
817 if (space) chunk_appendf(buf, " ");
818 chunk_appendf(buf, "no-ca-names");
819 space++;
820 }
821
822 if (conf->early_data) {
823 if (space) chunk_appendf(buf, " ");
824 chunk_appendf(buf, "allow-0rtt");
825 space++;
826 }
827 if (conf->ca_file) {
828 if (space) chunk_appendf(buf, " ");
829 chunk_appendf(buf, "ca-file %s", conf->ca_file);
830 space++;
831 }
832 if (conf->crl_file) {
833 if (space) chunk_appendf(buf, " ");
834 chunk_appendf(buf, "crl-file %s", conf->crl_file);
835 space++;
836 }
837 if (conf->ciphers) {
838 if (space) chunk_appendf(buf, " ");
839 chunk_appendf(buf, "ciphers %s", conf->ciphers);
840 space++;
841 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500842#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200843 if (conf->ciphersuites) {
844 if (space) chunk_appendf(buf, " ");
845 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
846 space++;
847 }
848#endif
849 if (conf->curves) {
850 if (space) chunk_appendf(buf, " ");
851 chunk_appendf(buf, "curves %s", conf->curves);
852 space++;
853 }
854 if (conf->ecdhe) {
855 if (space) chunk_appendf(buf, " ");
856 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
857 space++;
858 }
859
860 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200861 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200862 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200863 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200864 space++;
865 }
866
William Lallemand8177ad92020-05-20 16:49:02 +0200867 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200868 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200869 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200870 space++;
871 }
872
873 chunk_appendf(buf, "]");
874
875 return;
876}
877
878/* dump a list of filters */
879static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
880{
881 int i;
882
883 if (!entry->fcount)
884 return;
885
886 for (i = 0; i < entry->fcount; i++) {
887 chunk_appendf(buf, " %s", entry->filters[i]);
888 }
889 return;
890}
891
892/************************** CLI functions ****************************/
893
894
895/* CLI IO handler for '(show|dump) ssl crt-list' */
896static int cli_io_handler_dump_crtlist(struct appctx *appctx)
897{
898 struct buffer *trash = alloc_trash_chunk();
899 struct stream_interface *si = appctx->owner;
900 struct ebmb_node *lnode;
901
902 if (trash == NULL)
903 return 1;
904
905 /* dump the list of crt-lists */
906 lnode = appctx->ctx.cli.p1;
907 if (lnode == NULL)
908 lnode = ebmb_first(&crtlists_tree);
909 while (lnode) {
910 chunk_appendf(trash, "%s\n", lnode->key);
911 if (ci_putchk(si_ic(si), trash) == -1) {
912 si_rx_room_blk(si);
913 goto yield;
914 }
915 lnode = ebmb_next(lnode);
916 }
917 free_trash_chunk(trash);
918 return 1;
919yield:
920 appctx->ctx.cli.p1 = lnode;
921 free_trash_chunk(trash);
922 return 0;
923}
924
925/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
926static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
927{
928 struct buffer *trash = alloc_trash_chunk();
929 struct crtlist *crtlist;
930 struct stream_interface *si = appctx->owner;
931 struct crtlist_entry *entry;
932
933 if (trash == NULL)
934 return 1;
935
936 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
937
938 entry = appctx->ctx.cli.p1;
939 if (entry == NULL) {
940 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
941 chunk_appendf(trash, "# %s\n", crtlist->node.key);
942 if (ci_putchk(si_ic(si), trash) == -1) {
943 si_rx_room_blk(si);
944 goto yield;
945 }
946 }
947
948 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
949 struct ckch_store *store;
950 const char *filename;
951
952 store = entry->node.key;
953 filename = store->path;
954 chunk_appendf(trash, "%s", filename);
955 if (appctx->ctx.cli.i0 == 's') /* show */
956 chunk_appendf(trash, ":%d", entry->linenum);
957 dump_crtlist_sslconf(trash, entry->ssl_conf);
958 dump_crtlist_filters(trash, entry);
959 chunk_appendf(trash, "\n");
960
961 if (ci_putchk(si_ic(si), trash) == -1) {
962 si_rx_room_blk(si);
963 goto yield;
964 }
965 }
966 free_trash_chunk(trash);
967 return 1;
968yield:
969 appctx->ctx.cli.p1 = entry;
970 free_trash_chunk(trash);
971 return 0;
972}
973
974/* CLI argument parser for '(show|dump) ssl crt-list' */
975static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
976{
977 struct ebmb_node *lnode;
978 char *filename = NULL;
979 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200980 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200981
982 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
983 return 1;
984
985 appctx->ctx.cli.p0 = NULL;
986 appctx->ctx.cli.p1 = NULL;
987
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100988 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200989 mode = 's';
990 filename = args[4];
991 } else {
992 mode = 'd';
993 filename = args[3];
994 }
995
996 if (mode == 's' && !*args[4])
997 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
998
999 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +02001000
1001
1002 /* strip trailing slashes, including first one */
1003 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1004 *end = 0;
1005
William Lallemandc756bbd2020-05-13 17:23:59 +02001006 lnode = ebst_lookup(&crtlists_tree, filename);
1007 if (lnode == NULL)
1008 return cli_err(appctx, "didn't find the specified filename\n");
1009
1010 appctx->ctx.cli.p0 = lnode;
1011 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1012 }
1013 appctx->ctx.cli.i0 = mode;
1014
1015 return 0;
1016}
1017
1018/* release function of the "add ssl crt-list' command, free things and unlock
1019 the spinlock */
1020static void cli_release_add_crtlist(struct appctx *appctx)
1021{
1022 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1023
1024 if (appctx->st2 != SETCERT_ST_FIN) {
1025 struct ckch_inst *inst, *inst_s;
1026 /* upon error free the ckch_inst and everything inside */
1027 ebpt_delete(&entry->node);
1028 LIST_DEL(&entry->by_crtlist);
1029 LIST_DEL(&entry->by_ckch_store);
1030
1031 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1032 ckch_inst_free(inst);
1033 }
1034 crtlist_free_filters(entry->filters);
1035 ssl_sock_free_ssl_conf(entry->ssl_conf);
1036 free(entry->ssl_conf);
1037 free(entry);
1038 }
1039
1040 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1041}
1042
1043
1044/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1045 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1046 *
1047 * The logic is the same as the "commit ssl cert" command but without the
1048 * freeing of the old structures, because there are none.
1049 */
1050static int cli_io_handler_add_crtlist(struct appctx *appctx)
1051{
1052 struct bind_conf_list *bind_conf_node;
1053 struct stream_interface *si = appctx->owner;
1054 struct crtlist *crtlist = appctx->ctx.cli.p0;
1055 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1056 struct ckch_store *store = entry->node.key;
1057 struct buffer *trash = alloc_trash_chunk();
1058 struct ckch_inst *new_inst;
1059 char *err = NULL;
1060 int i = 0;
1061 int errcode = 0;
1062
1063 if (trash == NULL)
1064 goto error;
1065
1066 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1067 * created.
1068 */
1069 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1070 goto error;
1071
1072 while (1) {
1073 switch (appctx->st2) {
1074 case SETCERT_ST_INIT:
1075 /* This state just print the update message */
1076 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1077 if (ci_putchk(si_ic(si), trash) == -1) {
1078 si_rx_room_blk(si);
1079 goto yield;
1080 }
1081 appctx->st2 = SETCERT_ST_GEN;
1082 /* fallthrough */
1083 case SETCERT_ST_GEN:
1084 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1085 if (bind_conf_node == NULL)
1086 bind_conf_node = crtlist->bind_conf;
1087 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1088 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1089 struct sni_ctx *sni;
1090
1091 /* yield every 10 generations */
1092 if (i > 10) {
1093 appctx->ctx.cli.p2 = bind_conf_node;
1094 goto yield;
1095 }
1096
1097 /* we don't support multi-cert bundles, only simple ones */
1098 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1099 if (errcode & ERR_CODE)
1100 goto error;
1101
1102 /* we need to initialize the SSL_CTX generated */
1103 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1104 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1105 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1106 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1107 if (errcode & ERR_CODE)
1108 goto error;
1109 }
1110 }
1111 /* display one dot for each new instance */
1112 chunk_appendf(trash, ".");
1113 i++;
1114 LIST_ADDQ(&store->ckch_inst, &new_inst->by_ckchs);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001115 LIST_ADDQ(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1116 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001117 }
1118 appctx->st2 = SETCERT_ST_INSERT;
1119 /* fallthrough */
1120 case SETCERT_ST_INSERT:
1121 /* insert SNIs in bind_conf */
1122 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1123 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1124 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1125 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1126 }
1127 entry->linenum = ++crtlist->linecount;
1128 appctx->st2 = SETCERT_ST_FIN;
1129 goto end;
1130 }
1131 }
1132
1133end:
1134 chunk_appendf(trash, "\n");
1135 if (errcode & ERR_WARN)
1136 chunk_appendf(trash, "%s", err);
1137 chunk_appendf(trash, "Success!\n");
1138 if (ci_putchk(si_ic(si), trash) == -1)
1139 si_rx_room_blk(si);
1140 free_trash_chunk(trash);
1141 /* success: call the release function and don't come back */
1142 return 1;
1143yield:
1144 /* store the state */
1145 if (ci_putchk(si_ic(si), trash) == -1)
1146 si_rx_room_blk(si);
1147 free_trash_chunk(trash);
1148 si_rx_endp_more(si); /* let's come back later */
1149 return 0; /* should come back */
1150
1151error:
1152 /* spin unlock and free are done in the release function */
1153 if (trash) {
1154 chunk_appendf(trash, "\n%sFailed!\n", err);
1155 if (ci_putchk(si_ic(si), trash) == -1)
1156 si_rx_room_blk(si);
1157 free_trash_chunk(trash);
1158 }
1159 /* error: call the release function and don't come back */
1160 return 1;
1161}
1162
1163
1164/*
1165 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1166 * Filters and option must be passed through payload:
1167 */
1168static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1169{
1170 int cfgerr = 0;
1171 struct ckch_store *store;
1172 char *err = NULL;
1173 char path[MAXPATHLEN+1];
1174 char *crtlist_path;
1175 char *cert_path = NULL;
1176 struct ebmb_node *eb;
1177 struct ebpt_node *inserted;
1178 struct crtlist *crtlist;
1179 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001180 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001181
1182 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1183 return 1;
1184
1185 if (!*args[3] || (!payload && !*args[4]))
1186 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1187
1188 crtlist_path = args[3];
1189
William Lallemand99cc2182020-06-25 15:19:51 +02001190 /* strip trailing slashes, including first one */
1191 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1192 *end = 0;
1193
William Lallemandc756bbd2020-05-13 17:23:59 +02001194 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1195 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1196
1197 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1198 if (!eb) {
1199 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1200 goto error;
1201 }
1202 crtlist = ebmb_entry(eb, struct crtlist, node);
1203
1204 entry = crtlist_entry_new();
1205 if (entry == NULL) {
1206 memprintf(&err, "Not enough memory!");
1207 goto error;
1208 }
1209
1210 if (payload) {
1211 char *lf;
1212
1213 lf = strrchr(payload, '\n');
1214 if (lf) {
1215 memprintf(&err, "only one line of payload is supported!");
1216 goto error;
1217 }
1218 /* cert_path is filled here */
1219 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, &err);
1220 if (cfgerr & ERR_CODE)
1221 goto error;
1222 } else {
1223 cert_path = args[4];
1224 }
1225
1226 if (!cert_path) {
1227 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1228 cfgerr |= ERR_ALERT | ERR_FATAL;
1229 goto error;
1230 }
1231
1232 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1233 char *slash;
1234
1235 slash = strrchr(cert_path, '/');
1236 if (!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 /* temporary replace / by 0 to do an strcmp */
1241 *slash = '\0';
1242 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1243 *slash = '/';
1244 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1245 goto error;
1246 }
1247 *slash = '/';
1248 }
1249
1250 if (*cert_path != '/' && global_ssl.crt_base) {
1251 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1252 memprintf(&err, "'%s' : path too long", cert_path);
1253 cfgerr |= ERR_ALERT | ERR_FATAL;
1254 goto error;
1255 }
1256 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1257 cert_path = path;
1258 }
1259
1260 store = ckchs_lookup(cert_path);
1261 if (store == NULL) {
1262 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1263 goto error;
1264 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001265 if (store->ckch == NULL || store->ckch->cert == NULL) {
1266 memprintf(&err, "certificate '%s' is empty!", cert_path);
1267 goto error;
1268 }
1269
1270 /* check if it's possible to insert this new crtlist_entry */
1271 entry->node.key = store;
1272 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1273 if (inserted != &entry->node) {
1274 memprintf(&err, "file already exists in this directory!");
1275 goto error;
1276 }
1277
1278 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1279 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1280 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1281 goto error;
1282 }
1283
1284 LIST_ADDQ(&crtlist->ord_entries, &entry->by_crtlist);
1285 entry->crtlist = crtlist;
1286 LIST_ADDQ(&store->crtlist_entry, &entry->by_ckch_store);
1287
1288 appctx->st2 = SETCERT_ST_INIT;
1289 appctx->ctx.cli.p0 = crtlist;
1290 appctx->ctx.cli.p1 = entry;
1291
1292 /* unlock is done in the release handler */
1293 return 0;
1294
1295error:
1296 crtlist_entry_free(entry);
1297 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1298 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1299 return cli_dynerr(appctx, err);
1300}
1301
1302/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1303static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1304{
1305 struct ckch_store *store;
1306 char *err = NULL;
1307 char *crtlist_path, *cert_path;
1308 struct ebmb_node *ebmb;
1309 struct ebpt_node *ebpt;
1310 struct crtlist *crtlist;
1311 struct crtlist_entry *entry = NULL;
1312 struct ckch_inst *inst, *inst_s;
1313 int linenum = 0;
1314 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001315 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001316
1317 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1318 return 1;
1319
1320 if (!*args[3] || !*args[4])
1321 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1322
1323 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1324 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1325
1326 crtlist_path = args[3];
1327 cert_path = args[4];
1328
1329 colons = strchr(cert_path, ':');
1330 if (colons) {
1331 char *endptr;
1332
1333 linenum = strtol(colons + 1, &endptr, 10);
1334 if (colons + 1 == endptr || *endptr != '\0') {
1335 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1336 goto error;
1337 }
1338 *colons = '\0';
1339 }
William Lallemand99cc2182020-06-25 15:19:51 +02001340
1341 /* strip trailing slashes, including first one */
1342 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1343 *end = 0;
1344
William Lallemandc756bbd2020-05-13 17:23:59 +02001345 /* look for crtlist */
1346 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1347 if (!ebmb) {
1348 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1349 goto error;
1350 }
1351 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1352
1353 /* look for store */
1354 store = ckchs_lookup(cert_path);
1355 if (store == NULL) {
1356 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1357 goto error;
1358 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001359 if (store->ckch == NULL || store->ckch->cert == NULL) {
1360 memprintf(&err, "certificate '%s' is empty!", cert_path);
1361 goto error;
1362 }
1363
1364 ebpt = ebpt_lookup(&crtlist->entries, store);
1365 if (!ebpt) {
1366 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1367 goto error;
1368 }
1369
1370 /* list the line number of entries for errors in err, and select the right ebpt */
1371 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1372 struct crtlist_entry *tmp;
1373
1374 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1375 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1376
1377 /* select the entry we wanted */
1378 if (linenum == 0 || tmp->linenum == linenum) {
1379 if (!entry)
1380 entry = tmp;
1381 }
1382 }
1383
1384 /* we didn't found the specified entry */
1385 if (!entry) {
1386 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);
1387 goto error;
1388 }
1389
1390 /* we didn't specified a line number but there were several entries */
1391 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1392 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1393 goto error;
1394 }
1395
1396 /* upon error free the ckch_inst and everything inside */
1397
1398 ebpt_delete(&entry->node);
1399 LIST_DEL(&entry->by_crtlist);
1400 LIST_DEL(&entry->by_ckch_store);
1401
1402 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1403 struct sni_ctx *sni, *sni_s;
1404
1405 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1406 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1407 ebmb_delete(&sni->name);
1408 LIST_DEL(&sni->by_ckch_inst);
1409 SSL_CTX_free(sni->ctx);
1410 free(sni);
1411 }
1412 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1413 LIST_DEL(&inst->by_ckchs);
1414 free(inst);
1415 }
1416
1417 crtlist_free_filters(entry->filters);
1418 ssl_sock_free_ssl_conf(entry->ssl_conf);
1419 free(entry->ssl_conf);
1420 free(entry);
1421
1422 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1423 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1424 return cli_dynmsg(appctx, LOG_NOTICE, err);
1425
1426error:
1427 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1428 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1429 return cli_dynerr(appctx, err);
1430}
1431
1432
William Lallemandee8530c2020-06-23 18:19:42 +02001433/* unlink and free all crt-list and crt-list entries */
1434void crtlist_deinit()
1435{
1436 struct eb_node *node, *next;
1437 struct crtlist *crtlist;
1438
1439 node = eb_first(&crtlists_tree);
1440 while (node) {
1441 next = eb_next(node);
1442 crtlist = ebmb_entry(node, struct crtlist, node);
1443 crtlist_free(crtlist);
1444 node = next;
1445 }
1446}
1447
William Lallemandc756bbd2020-05-13 17:23:59 +02001448
1449/* register cli keywords */
1450static struct cli_kw_list cli_kws = {{ },{
1451 { { "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 },
1452 { { "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 },
1453 { { "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 },
1454 { { NULL }, NULL, NULL, NULL } }
1455};
1456
1457INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1458