blob: 318606860b48c10712e70f5006ef4d3b40bb06b2 [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 Tarreaua2fcca02022-05-05 11:53:23 +020023#include <haproxy/applet.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020024#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020025#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020026#include <haproxy/errors.h>
Willy Tarreau5edca2f2022-05-27 09:25:10 +020027#include <haproxy/sc_strm.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020028#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020029#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020030#include <haproxy/ssl_sock.h>
Willy Tarreaucb086c62022-05-27 09:47:12 +020031#include <haproxy/stconn.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020032#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020033
Willy Tarreaua2fcca02022-05-05 11:53:23 +020034/* CLI context for "show ssl crt-list" or "dump ssl crt-list" */
35struct show_crtlist_ctx {
36 struct ebmb_node *crtlist_node; /* ebmb_node for the current crtlist */
37 struct crtlist_entry *entry; /* current entry */
38 int mode; /* 'd' for dump, 's' for show */
39};
William Lallemand6e9556b2020-05-12 17:52:44 +020040
Willy Tarreau6b6c3632022-05-05 13:43:49 +020041/* CLI context for "add ssl crt-list" */
42struct add_crtlist_ctx {
43 struct crtlist *crtlist;
44 struct crtlist_entry *entry;
45 struct bind_conf_list *bind_conf_node;
Willy Tarreaufa11df52022-05-05 13:48:40 +020046 enum {
47 ADDCRT_ST_INIT = 0,
48 ADDCRT_ST_GEN,
49 ADDCRT_ST_INSERT,
50 ADDCRT_ST_FIN,
51 } state;
Willy Tarreau6b6c3632022-05-05 13:43:49 +020052};
53
William Lallemand6e9556b2020-05-12 17:52:44 +020054/* release ssl bind conf */
55void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
56{
57 if (conf) {
58#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010059 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020060#endif
61#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010062 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020063#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010064 ha_free(&conf->ca_file);
65 ha_free(&conf->ca_verify_file);
66 ha_free(&conf->crl_file);
67 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050068#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010069 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020070#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010071 ha_free(&conf->curves);
72 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020073 }
74}
75
William Lallemand82f2d2f2020-09-10 19:06:43 +020076/*
77 * Allocate and copy a ssl_bind_conf structure
78 */
79struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
80{
81 struct ssl_bind_conf *dst;
82
83 if (!src)
84 return NULL;
85
86 dst = calloc(1, sizeof(*dst));
87 if (!dst)
88 return NULL;
89
90#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
91 if (src->npn_str) {
92 dst->npn_str = strdup(src->npn_str);
93 if (!dst->npn_str)
94 goto error;
95 }
96#endif
97#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
98 if (src->alpn_str) {
99 dst->alpn_str = strdup(src->alpn_str);
100 if (!dst->alpn_str)
101 goto error;
102 }
103#endif
104 if (src->ca_file) {
105 dst->ca_file = strdup(src->ca_file);
106 if (!dst->ca_file)
107 goto error;
108 }
109 if (src->ca_verify_file) {
110 dst->ca_verify_file = strdup(src->ca_verify_file);
111 if (!dst->ca_verify_file)
112 goto error;
113 }
114 if (src->crl_file) {
115 dst->crl_file = strdup(src->crl_file);
116 if (!dst->crl_file)
117 goto error;
118 }
119 if (src->ciphers) {
120 dst->ciphers = strdup(src->ciphers);
121 if (!dst->ciphers)
122 goto error;
123 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500124#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200125 if (src->ciphersuites) {
126 dst->ciphersuites = strdup(src->ciphersuites);
127 if (!dst->ciphersuites)
128 goto error;
129 }
130#endif
131 if (src->curves) {
132 dst->curves = strdup(src->curves);
133 if (!dst->curves)
134 goto error;
135 }
136 if (src->ecdhe) {
137 dst->ecdhe = strdup(src->ecdhe);
138 if (!dst->ecdhe)
139 goto error;
140 }
141 return dst;
142
143error:
144 ssl_sock_free_ssl_conf(dst);
145 free(dst);
146
147 return NULL;
148}
William Lallemand6e9556b2020-05-12 17:52:44 +0200149
150/* free sni filters */
151void crtlist_free_filters(char **args)
152{
153 int i;
154
155 if (!args)
156 return;
157
158 for (i = 0; args[i]; i++)
159 free(args[i]);
160
161 free(args);
162}
163
164/* Alloc and duplicate a char ** array */
165char **crtlist_dup_filters(char **args, int fcount)
166{
167 char **dst;
168 int i;
169
170 if (fcount == 0)
171 return NULL;
172
173 dst = calloc(fcount + 1, sizeof(*dst));
174 if (!dst)
175 return NULL;
176
177 for (i = 0; i < fcount; i++) {
178 dst[i] = strdup(args[i]);
179 if (!dst[i])
180 goto error;
181 }
182 return dst;
183
184error:
185 crtlist_free_filters(dst);
186 return NULL;
187}
188
189/*
190 * Detach and free a crtlist_entry.
191 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
192 */
193void crtlist_entry_free(struct crtlist_entry *entry)
194{
195 struct ckch_inst *inst, *inst_s;
196
197 if (entry == NULL)
198 return;
199
200 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200201 LIST_DELETE(&entry->by_crtlist);
202 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200203 crtlist_free_filters(entry->filters);
204 ssl_sock_free_ssl_conf(entry->ssl_conf);
205 free(entry->ssl_conf);
206 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
207 ckch_inst_free(inst);
208 }
209 free(entry);
210}
William Lallemand5622c452020-09-10 19:08:49 +0200211/*
212 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
213 * Return a pointer to the new entry
214 */
215struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
216{
217 struct crtlist_entry *entry;
218
219 if (src == NULL)
220 return NULL;
221
222 entry = crtlist_entry_new();
223 if (entry == NULL)
224 return NULL;
225
226 if (src->filters) {
227 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
228 if (!entry->filters)
229 goto error;
230 }
231 entry->fcount = src->fcount;
232 if (src->ssl_conf) {
233 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
234 if (!entry->ssl_conf)
235 goto error;
236 }
237 entry->crtlist = src->crtlist;
238
239 return entry;
240
241error:
242
243 crtlist_free_filters(entry->filters);
244 ssl_sock_free_ssl_conf(entry->ssl_conf);
245 free(entry->ssl_conf);
246 free(entry);
247
248 return NULL;
249}
William Lallemand6e9556b2020-05-12 17:52:44 +0200250
251/*
252 * Allocate and initialize a crtlist_entry
253 */
254struct crtlist_entry *crtlist_entry_new()
255{
256 struct crtlist_entry *entry;
257
258 entry = calloc(1, sizeof(*entry));
259 if (entry == NULL)
260 return NULL;
261
262 LIST_INIT(&entry->ckch_inst);
263
Willy Tarreau2b718102021-04-21 07:32:39 +0200264 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200265 LIST_INIT(&entry->by_crtlist);
266 LIST_INIT(&entry->by_ckch_store);
267
268 return entry;
269}
270
271/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
272void crtlist_free(struct crtlist *crtlist)
273{
274 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200275 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200276
277 if (crtlist == NULL)
278 return;
279
William Lallemand6a3168a2020-06-23 11:43:35 +0200280 bind_conf_node = crtlist->bind_conf;
281 while (bind_conf_node) {
282 struct bind_conf_list *next = bind_conf_node->next;
283 free(bind_conf_node);
284 bind_conf_node = next;
285 }
286
William Lallemand6e9556b2020-05-12 17:52:44 +0200287 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
288 crtlist_entry_free(entry);
289 }
290 ebmb_delete(&crtlist->node);
291 free(crtlist);
292}
293
294/* Alloc and initialize a struct crtlist
295 * <filename> is the key of the ebmb_node
296 * <unique> initialize the list of entries to be unique (1) or not (0)
297 */
298struct crtlist *crtlist_new(const char *filename, int unique)
299{
300 struct crtlist *newlist;
301
302 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
303 if (newlist == NULL)
304 return NULL;
305
306 memcpy(newlist->node.key, filename, strlen(filename) + 1);
307 if (unique)
308 newlist->entries = EB_ROOT_UNIQUE;
309 else
310 newlist->entries = EB_ROOT;
311
312 LIST_INIT(&newlist->ord_entries);
313
314 return newlist;
315}
316
317/*
318 * Read a single crt-list line. /!\ alter the <line> string.
319 * Fill <crt_path> and <crtlist_entry>
320 * <crtlist_entry> must be alloc and free by the caller
321 * <crtlist_entry->ssl_conf> is alloc by the function
322 * <crtlist_entry->filters> is alloc by the function
323 * <crt_path> is a ptr in <line>
324 * Return an error code
325 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100326int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, int from_cli, char **err)
William Lallemand6e9556b2020-05-12 17:52:44 +0200327{
328 int cfgerr = 0;
329 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
330 char *end;
331 char *args[MAX_CRT_ARGS + 1];
332 struct ssl_bind_conf *ssl_conf = NULL;
333
334 if (!line || !crt_path || !entry)
335 return ERR_ALERT | ERR_FATAL;
336
337 end = line + strlen(line);
338 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
339 /* Check if we reached the limit and the last char is not \n.
340 * Watch out for the last line without the terminating '\n'!
341 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200342 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
343 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200344 cfgerr |= ERR_ALERT | ERR_FATAL;
345 goto error;
346 }
347 arg = 0;
348 newarg = 1;
349 while (*line) {
350 if (isspace((unsigned char)*line)) {
351 newarg = 1;
352 *line = 0;
353 } else if (*line == '[') {
354 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200355 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200356 cfgerr |= ERR_ALERT | ERR_FATAL;
357 goto error;
358 }
359 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200360 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200361 cfgerr |= ERR_ALERT | ERR_FATAL;
362 goto error;
363 }
364 ssl_b = arg;
365 newarg = 1;
366 *line = 0;
367 } else if (*line == ']') {
368 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200369 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200370 cfgerr |= ERR_ALERT | ERR_FATAL;
371 goto error;
372 }
373 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200374 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200375 cfgerr |= ERR_ALERT | ERR_FATAL;
376 goto error;
377 }
378 ssl_e = arg;
379 newarg = 1;
380 *line = 0;
381 } else if (newarg) {
382 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200383 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200384 cfgerr |= ERR_ALERT | ERR_FATAL;
385 goto error;
386 }
387 newarg = 0;
388 args[arg++] = line;
389 }
390 line++;
391 }
392 args[arg++] = line;
393
394 /* empty line */
395 if (!*args[0]) {
396 cfgerr |= ERR_NONE;
397 goto error;
398 }
399
400 *crt_path = args[0];
401
402 if (ssl_b) {
403 ssl_conf = calloc(1, sizeof *ssl_conf);
404 if (!ssl_conf) {
405 memprintf(err, "not enough memory!");
406 cfgerr |= ERR_ALERT | ERR_FATAL;
407 goto error;
408 }
409 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100410
William Lallemand6e9556b2020-05-12 17:52:44 +0200411 cur_arg = ssl_b ? ssl_b : 1;
412 while (cur_arg < ssl_e) {
413 newarg = 0;
414 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
415 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
416 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100417 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200418 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200419 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
420 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200421 cfgerr |= ERR_ALERT | ERR_FATAL;
422 goto error;
423 }
424 cur_arg += 1 + ssl_bind_kws[i].skip;
425 break;
426 }
427 }
428 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200429 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
430 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200431 cfgerr |= ERR_ALERT | ERR_FATAL;
432 goto error;
433 }
434 }
435 entry->linenum = linenum;
436 entry->ssl_conf = ssl_conf;
437 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
438 entry->fcount = arg - cur_arg - 1;
439
440 return cfgerr;
441
442error:
443 crtlist_free_filters(entry->filters);
444 entry->filters = NULL;
445 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100446 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200447 return cfgerr;
448}
449
450
451
452/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
453 * Fill the <crtlist> argument with a pointer to a new crtlist struct
454 *
455 * This function tries to open and store certificate files.
456 */
457int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
458{
459 struct crtlist *newlist;
460 struct crtlist_entry *entry = NULL;
461 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200462 FILE *f;
463 struct stat buf;
464 int linenum = 0;
465 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200466 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200467
468 if ((f = fopen(file, "r")) == NULL) {
469 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
470 return ERR_ALERT | ERR_FATAL;
471 }
472
473 newlist = crtlist_new(file, 0);
474 if (newlist == NULL) {
475 memprintf(err, "Not enough memory!");
476 cfgerr |= ERR_ALERT | ERR_FATAL;
477 goto error;
478 }
479
480 while (fgets(thisline, sizeof(thisline), f) != NULL) {
481 char *end;
482 char *line = thisline;
483 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100484 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200485 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100486 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200487
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200488 if (missing_lf != -1) {
489 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
490 file, linenum, (missing_lf + 1));
491 cfgerr |= ERR_ALERT | ERR_FATAL;
492 missing_lf = -1;
493 break;
494 }
495
William Lallemand6e9556b2020-05-12 17:52:44 +0200496 linenum++;
497 end = line + strlen(line);
498 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
499 /* Check if we reached the limit and the last char is not \n.
500 * Watch out for the last line without the terminating '\n'!
501 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200502 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
503 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200504 cfgerr |= ERR_ALERT | ERR_FATAL;
505 break;
506 }
507
508 if (*line == '#' || *line == '\n' || *line == '\r')
509 continue;
510
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200511 if (end > line && *(end-1) == '\n') {
512 /* kill trailing LF */
513 *(end - 1) = 0;
514 }
515 else {
516 /* mark this line as truncated */
517 missing_lf = end - line;
518 }
519
William Lallemand6e9556b2020-05-12 17:52:44 +0200520 entry = crtlist_entry_new();
521 if (entry == NULL) {
522 memprintf(err, "Not enough memory!");
523 cfgerr |= ERR_ALERT | ERR_FATAL;
524 goto error;
525 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200526
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100527 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200528 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200529 goto error;
530
531 /* empty line */
532 if (!crt_path || !*crt_path) {
533 crtlist_entry_free(entry);
534 entry = NULL;
535 continue;
536 }
537
538 if (*crt_path != '/' && global_ssl.crt_base) {
Willy Tarreau393e42a2022-05-09 10:31:28 +0200539 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > sizeof(path) ||
Willy Tarreau63fc9002022-05-09 21:14:04 +0200540 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path) > sizeof(path)) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200541 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
542 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200543 cfgerr |= ERR_ALERT | ERR_FATAL;
544 goto error;
545 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200546 crt_path = path;
547 }
548
549 /* Look for a ckch_store or create one */
550 ckchs = ckchs_lookup(crt_path);
551 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200552 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100553 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200554
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200555 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200556 if (ckchs == NULL) {
557 cfgerr |= ERR_ALERT | ERR_FATAL;
558 goto error;
559 }
560
561 entry->node.key = ckchs;
562 entry->crtlist = newlist;
563 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200564 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
565 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200566
William Lallemand73404572020-11-20 18:23:40 +0100567 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200568 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200569 bundle, since 2.3 we don't support multiple
570 certificate in the same OpenSSL store, so we
571 emulate it by loading each file separately. To
572 do so we need to duplicate the entry in the
573 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200574 char fp[MAXPATHLEN+1] = {0};
575 int n = 0;
576 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
577 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
578 struct stat buf;
579 int ret;
580
William Lallemand86c2dd62020-11-20 14:23:38 +0100581 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200582 if (ret > sizeof(fp))
583 continue;
584
585 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100586 if (!ckchs) {
587 if (stat(fp, &buf) == 0) {
588 ckchs = ckchs_load_cert_file(fp, err);
589 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200590 cfgerr |= ERR_ALERT | ERR_FATAL;
591 goto error;
592 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100593 } else {
594 continue; /* didn't find this extension, skip */
595 }
596 }
597 found++;
598 linenum++; /* we duplicate the line for this entry in the bundle */
599 if (!entry_dup) { /* if the entry was used, duplicate one */
600 linenum++;
601 entry_dup = crtlist_entry_dup(entry);
602 if (!entry_dup) {
603 cfgerr |= ERR_ALERT | ERR_FATAL;
604 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200605 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100606 entry_dup->linenum = linenum;
607 }
William Lallemand47da8212020-09-10 19:13:27 +0200608
William Lallemand77e1c6f2020-11-20 18:26:09 +0100609 entry_dup->node.key = ckchs;
610 entry_dup->crtlist = newlist;
611 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200612 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
613 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200614
William Lallemand77e1c6f2020-11-20 18:26:09 +0100615 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200616 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100617#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
618 if (found) {
619 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
620 err && *err ? *err : "", crt_path);
621 cfgerr |= ERR_ALERT | ERR_FATAL;
622 }
623#endif
William Lallemand47da8212020-09-10 19:13:27 +0200624 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100625 if (!found) {
626 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
627 err && *err ? *err : "", crt_path, strerror(errno));
628 cfgerr |= ERR_ALERT | ERR_FATAL;
629 }
630
William Lallemand50c03aa2020-11-06 16:24:07 +0100631 } else {
632 entry->node.key = ckchs;
633 entry->crtlist = newlist;
634 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200635 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
636 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100637 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200638 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200639 entry = NULL;
640 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200641
642 if (missing_lf != -1) {
643 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
644 file, linenum, (missing_lf + 1));
645 cfgerr |= ERR_ALERT | ERR_FATAL;
646 }
647
William Lallemand6e9556b2020-05-12 17:52:44 +0200648 if (cfgerr & ERR_CODE)
649 goto error;
650
651 newlist->linecount = linenum;
652
653 fclose(f);
654 *crtlist = newlist;
655
656 return cfgerr;
657error:
658 crtlist_entry_free(entry);
659
660 fclose(f);
661 crtlist_free(newlist);
662 return cfgerr;
663}
664
665/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
666 * Fill the <crtlist> argument with a pointer to a new crtlist struct
667 *
668 * This function tries to open and store certificate files.
669 */
670int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
671{
672 struct crtlist *dir;
673 struct dirent **de_list;
674 int i, n;
675 struct stat buf;
676 char *end;
677 char fp[MAXPATHLEN+1];
678 int cfgerr = 0;
679 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200680
681 dir = crtlist_new(path, 1);
682 if (dir == NULL) {
683 memprintf(err, "not enough memory");
684 return ERR_ALERT | ERR_FATAL;
685 }
686
687 n = scandir(path, &de_list, 0, alphasort);
688 if (n < 0) {
689 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
690 err && *err ? *err : "", path, strerror(errno));
691 cfgerr |= ERR_ALERT | ERR_FATAL;
692 }
693 else {
694 for (i = 0; i < n; i++) {
695 struct crtlist_entry *entry;
696 struct dirent *de = de_list[i];
697
698 end = strrchr(de->d_name, '.');
William Lallemand589570d2022-05-09 10:30:51 +0200699 if (end && (de->d_name[0] == '.' ||
700 strcmp(end, ".issuer") == 0 || strcmp(end, ".ocsp") == 0 ||
701 strcmp(end, ".sctl") == 0 || strcmp(end, ".key") == 0))
William Lallemand6e9556b2020-05-12 17:52:44 +0200702 goto ignore_entry;
703
704 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
705 if (stat(fp, &buf) != 0) {
706 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
707 err && *err ? *err : "", fp, strerror(errno));
708 cfgerr |= ERR_ALERT | ERR_FATAL;
709 goto ignore_entry;
710 }
711 if (!S_ISREG(buf.st_mode))
712 goto ignore_entry;
713
714 entry = crtlist_entry_new();
715 if (entry == NULL) {
716 memprintf(err, "not enough memory '%s'", fp);
717 cfgerr |= ERR_ALERT | ERR_FATAL;
718 goto ignore_entry;
719 }
720
William Lallemand6e9556b2020-05-12 17:52:44 +0200721 ckchs = ckchs_lookup(fp);
722 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200723 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200724 if (ckchs == NULL) {
725 free(de);
726 free(entry);
727 cfgerr |= ERR_ALERT | ERR_FATAL;
728 goto end;
729 }
730 entry->node.key = ckchs;
731 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200732 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
733 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200734 ebpt_insert(&dir->entries, &entry->node);
735
736ignore_entry:
737 free(de);
738 }
739end:
740 free(de_list);
741 }
742
743 if (cfgerr & ERR_CODE) {
744 /* free the dir and entries on error */
745 crtlist_free(dir);
746 } else {
747 *crtlist = dir;
748 }
749 return cfgerr;
750
751}
752
William Lallemandc756bbd2020-05-13 17:23:59 +0200753/*
754 * Take an ssl_bind_conf structure and append the configuration line used to
755 * create it in the buffer
756 */
757static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
758{
759 int space = 0;
760
761 if (conf == NULL)
762 return;
763
764 chunk_appendf(buf, " [");
765#ifdef OPENSSL_NPN_NEGOTIATED
766 if (conf->npn_str) {
767 int len = conf->npn_len;
768 char *ptr = conf->npn_str;
769 int comma = 0;
770
771 if (space) chunk_appendf(buf, " ");
772 chunk_appendf(buf, "npn ");
773 while (len) {
774 unsigned short size;
775
776 size = *ptr;
777 ptr++;
778 if (comma)
779 chunk_memcat(buf, ",", 1);
780 chunk_memcat(buf, ptr, size);
781 ptr += size;
782 len -= size + 1;
783 comma = 1;
784 }
785 chunk_memcat(buf, "", 1); /* finish with a \0 */
786 space++;
787 }
788#endif
789#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
790 if (conf->alpn_str) {
791 int len = conf->alpn_len;
792 char *ptr = conf->alpn_str;
793 int comma = 0;
794
795 if (space) chunk_appendf(buf, " ");
796 chunk_appendf(buf, "alpn ");
797 while (len) {
798 unsigned short size;
799
800 size = *ptr;
801 ptr++;
802 if (comma)
803 chunk_memcat(buf, ",", 1);
804 chunk_memcat(buf, ptr, size);
805 ptr += size;
806 len -= size + 1;
807 comma = 1;
808 }
809 chunk_memcat(buf, "", 1); /* finish with a \0 */
810 space++;
811 }
812#endif
813 /* verify */
814 {
815 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
816 if (space) chunk_appendf(buf, " ");
817 chunk_appendf(buf, "verify none");
818 space++;
819 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
820 if (space) chunk_appendf(buf, " ");
821 chunk_appendf(buf, "verify optional");
822 space++;
823 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
824 if (space) chunk_appendf(buf, " ");
825 chunk_appendf(buf, "verify required");
826 space++;
827 }
828 }
829
830 if (conf->no_ca_names) {
831 if (space) chunk_appendf(buf, " ");
832 chunk_appendf(buf, "no-ca-names");
833 space++;
834 }
835
836 if (conf->early_data) {
837 if (space) chunk_appendf(buf, " ");
838 chunk_appendf(buf, "allow-0rtt");
839 space++;
840 }
841 if (conf->ca_file) {
842 if (space) chunk_appendf(buf, " ");
843 chunk_appendf(buf, "ca-file %s", conf->ca_file);
844 space++;
845 }
846 if (conf->crl_file) {
847 if (space) chunk_appendf(buf, " ");
848 chunk_appendf(buf, "crl-file %s", conf->crl_file);
849 space++;
850 }
851 if (conf->ciphers) {
852 if (space) chunk_appendf(buf, " ");
853 chunk_appendf(buf, "ciphers %s", conf->ciphers);
854 space++;
855 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500856#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200857 if (conf->ciphersuites) {
858 if (space) chunk_appendf(buf, " ");
859 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
860 space++;
861 }
862#endif
863 if (conf->curves) {
864 if (space) chunk_appendf(buf, " ");
865 chunk_appendf(buf, "curves %s", conf->curves);
866 space++;
867 }
868 if (conf->ecdhe) {
869 if (space) chunk_appendf(buf, " ");
870 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
871 space++;
872 }
873
874 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200875 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200876 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200877 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200878 space++;
879 }
880
William Lallemand8177ad92020-05-20 16:49:02 +0200881 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200882 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200883 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200884 space++;
885 }
886
887 chunk_appendf(buf, "]");
888
889 return;
890}
891
892/* dump a list of filters */
893static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
894{
895 int i;
896
897 if (!entry->fcount)
898 return;
899
900 for (i = 0; i < entry->fcount; i++) {
901 chunk_appendf(buf, " %s", entry->filters[i]);
902 }
903 return;
904}
905
906/************************** CLI functions ****************************/
907
908
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200909/* CLI IO handler for '(show|dump) ssl crt-list'.
910 * It uses show_crtlist_ctx for the context.
911 */
William Lallemandc756bbd2020-05-13 17:23:59 +0200912static int cli_io_handler_dump_crtlist(struct appctx *appctx)
913{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200914 struct show_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +0200915 struct buffer *trash = alloc_trash_chunk();
William Lallemandc756bbd2020-05-13 17:23:59 +0200916 struct ebmb_node *lnode;
917
918 if (trash == NULL)
919 return 1;
920
921 /* dump the list of crt-lists */
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200922 lnode = ctx->crtlist_node;
William Lallemandc756bbd2020-05-13 17:23:59 +0200923 if (lnode == NULL)
924 lnode = ebmb_first(&crtlists_tree);
925 while (lnode) {
926 chunk_appendf(trash, "%s\n", lnode->key);
Willy Tarreaud0a06d52022-05-18 15:07:19 +0200927 if (applet_putchk(appctx, trash) == -1)
William Lallemandc756bbd2020-05-13 17:23:59 +0200928 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +0200929 lnode = ebmb_next(lnode);
930 }
931 free_trash_chunk(trash);
932 return 1;
933yield:
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200934 ctx->crtlist_node = lnode;
William Lallemandc756bbd2020-05-13 17:23:59 +0200935 free_trash_chunk(trash);
936 return 0;
937}
938
939/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
940static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
941{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200942 struct show_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +0200943 struct buffer *trash = alloc_trash_chunk();
944 struct crtlist *crtlist;
William Lallemandc756bbd2020-05-13 17:23:59 +0200945 struct crtlist_entry *entry;
946
947 if (trash == NULL)
948 return 1;
949
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200950 crtlist = ebmb_entry(ctx->crtlist_node, struct crtlist, node);
William Lallemandc756bbd2020-05-13 17:23:59 +0200951
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200952 entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +0200953 if (entry == NULL) {
954 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
955 chunk_appendf(trash, "# %s\n", crtlist->node.key);
Willy Tarreaud0a06d52022-05-18 15:07:19 +0200956 if (applet_putchk(appctx, trash) == -1)
William Lallemandc756bbd2020-05-13 17:23:59 +0200957 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +0200958 }
959
960 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
961 struct ckch_store *store;
962 const char *filename;
963
964 store = entry->node.key;
965 filename = store->path;
966 chunk_appendf(trash, "%s", filename);
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200967 if (ctx->mode == 's') /* show */
William Lallemandc756bbd2020-05-13 17:23:59 +0200968 chunk_appendf(trash, ":%d", entry->linenum);
969 dump_crtlist_sslconf(trash, entry->ssl_conf);
970 dump_crtlist_filters(trash, entry);
971 chunk_appendf(trash, "\n");
972
Willy Tarreaud0a06d52022-05-18 15:07:19 +0200973 if (applet_putchk(appctx, trash) == -1)
William Lallemandc756bbd2020-05-13 17:23:59 +0200974 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +0200975 }
976 free_trash_chunk(trash);
977 return 1;
978yield:
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200979 ctx->entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +0200980 free_trash_chunk(trash);
981 return 0;
982}
983
984/* CLI argument parser for '(show|dump) ssl crt-list' */
985static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
986{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200987 struct show_crtlist_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandc756bbd2020-05-13 17:23:59 +0200988 struct ebmb_node *lnode;
989 char *filename = NULL;
990 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200991 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200992
993 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
994 return 1;
995
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100996 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200997 mode = 's';
998 filename = args[4];
999 } else {
1000 mode = 'd';
1001 filename = args[3];
1002 }
1003
1004 if (mode == 's' && !*args[4])
1005 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
1006
1007 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +02001008
1009
1010 /* strip trailing slashes, including first one */
1011 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1012 *end = 0;
1013
William Lallemandc756bbd2020-05-13 17:23:59 +02001014 lnode = ebst_lookup(&crtlists_tree, filename);
1015 if (lnode == NULL)
1016 return cli_err(appctx, "didn't find the specified filename\n");
1017
Willy Tarreaua2fcca02022-05-05 11:53:23 +02001018 ctx->crtlist_node = lnode;
William Lallemandc756bbd2020-05-13 17:23:59 +02001019 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1020 }
Willy Tarreaua2fcca02022-05-05 11:53:23 +02001021 ctx->mode = mode;
William Lallemandc756bbd2020-05-13 17:23:59 +02001022
1023 return 0;
1024}
1025
1026/* release function of the "add ssl crt-list' command, free things and unlock
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001027 * the spinlock. It uses the add_crtlist_ctx.
1028 */
William Lallemandc756bbd2020-05-13 17:23:59 +02001029static void cli_release_add_crtlist(struct appctx *appctx)
1030{
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001031 struct add_crtlist_ctx *ctx = appctx->svcctx;
1032 struct crtlist_entry *entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001033
Willy Tarreaufa11df52022-05-05 13:48:40 +02001034 if (ctx->state != ADDCRT_ST_FIN) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001035 struct ckch_inst *inst, *inst_s;
1036 /* upon error free the ckch_inst and everything inside */
1037 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001038 LIST_DELETE(&entry->by_crtlist);
1039 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001040
1041 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1042 ckch_inst_free(inst);
1043 }
1044 crtlist_free_filters(entry->filters);
1045 ssl_sock_free_ssl_conf(entry->ssl_conf);
1046 free(entry->ssl_conf);
1047 free(entry);
1048 }
1049
1050 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1051}
1052
1053
1054/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1055 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1056 *
1057 * The logic is the same as the "commit ssl cert" command but without the
1058 * freeing of the old structures, because there are none.
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001059 *
1060 * It uses the add_crtlist_ctx for the context.
William Lallemandc756bbd2020-05-13 17:23:59 +02001061 */
1062static int cli_io_handler_add_crtlist(struct appctx *appctx)
1063{
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001064 struct add_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +02001065 struct bind_conf_list *bind_conf_node;
Willy Tarreauc12b3212022-05-27 11:08:15 +02001066 struct stconn *sc = appctx_sc(appctx);
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001067 struct crtlist *crtlist = ctx->crtlist;
1068 struct crtlist_entry *entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001069 struct ckch_store *store = entry->node.key;
1070 struct buffer *trash = alloc_trash_chunk();
1071 struct ckch_inst *new_inst;
1072 char *err = NULL;
1073 int i = 0;
1074 int errcode = 0;
1075
1076 if (trash == NULL)
1077 goto error;
1078
1079 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1080 * created.
1081 */
Willy Tarreau475e4632022-05-27 10:26:46 +02001082 if (unlikely(sc_ic(sc)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandc756bbd2020-05-13 17:23:59 +02001083 goto error;
1084
Willy Tarreaufa11df52022-05-05 13:48:40 +02001085 switch (ctx->state) {
1086 case ADDCRT_ST_INIT:
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001087 /* This state just print the update message */
1088 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001089 if (applet_putchk(appctx, trash) == -1)
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001090 goto yield;
Willy Tarreaufa11df52022-05-05 13:48:40 +02001091 ctx->state = ADDCRT_ST_GEN;
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001092 /* fallthrough */
Willy Tarreaufa11df52022-05-05 13:48:40 +02001093 case ADDCRT_ST_GEN:
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001094 bind_conf_node = ctx->bind_conf_node; /* get the previous ptr from the yield */
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001095 if (bind_conf_node == NULL)
1096 bind_conf_node = crtlist->bind_conf;
1097 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1098 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1099 struct sni_ctx *sni;
1100
1101 /* yield every 10 generations */
1102 if (i > 10) {
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001103 ctx->bind_conf_node = bind_conf_node;
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001104 goto yield;
1105 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001106
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001107 /* we don't support multi-cert bundles, only simple ones */
1108 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1109 if (errcode & ERR_CODE)
1110 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001111
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001112 /* we need to initialize the SSL_CTX generated */
1113 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1114 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1115 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1116 errcode |= ssl_sock_prep_ctx_and_inst(bind_conf, new_inst->ssl_conf, sni->ctx, sni->ckch_inst, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001117 if (errcode & ERR_CODE)
1118 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001119 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001120 }
1121 /* display one dot for each new instance */
1122 chunk_appendf(trash, ".");
1123 i++;
1124 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1125 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1126 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001127 }
Willy Tarreaufa11df52022-05-05 13:48:40 +02001128 ctx->state = ADDCRT_ST_INSERT;
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001129 /* fallthrough */
Willy Tarreaufa11df52022-05-05 13:48:40 +02001130 case ADDCRT_ST_INSERT:
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001131 /* insert SNIs in bind_conf */
1132 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1133 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1134 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1135 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1136 }
1137 entry->linenum = ++crtlist->linecount;
Willy Tarreaufa11df52022-05-05 13:48:40 +02001138 ctx->state = ADDCRT_ST_FIN;
1139 /* fallthrough */
1140 default:
1141 break;
William Lallemandc756bbd2020-05-13 17:23:59 +02001142 }
1143
William Lallemandc756bbd2020-05-13 17:23:59 +02001144 chunk_appendf(trash, "\n");
1145 if (errcode & ERR_WARN)
1146 chunk_appendf(trash, "%s", err);
1147 chunk_appendf(trash, "Success!\n");
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001148 applet_putchk(appctx, trash);
William Lallemandc756bbd2020-05-13 17:23:59 +02001149 free_trash_chunk(trash);
1150 /* success: call the release function and don't come back */
1151 return 1;
1152yield:
1153 /* store the state */
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001154 applet_putchk(appctx, trash);
William Lallemandc756bbd2020-05-13 17:23:59 +02001155 free_trash_chunk(trash);
Willy Tarreau4164eb92022-05-25 15:42:03 +02001156 applet_have_more_data(appctx); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001157 return 0; /* should come back */
1158
1159error:
1160 /* spin unlock and free are done in the release function */
1161 if (trash) {
1162 chunk_appendf(trash, "\n%sFailed!\n", err);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001163 applet_putchk(appctx, trash);
William Lallemandc756bbd2020-05-13 17:23:59 +02001164 free_trash_chunk(trash);
1165 }
1166 /* error: call the release function and don't come back */
1167 return 1;
1168}
1169
1170
1171/*
1172 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001173 * Filters and option must be passed through payload.
1174 * It sets a struct add_crtlist_ctx.
William Lallemandc756bbd2020-05-13 17:23:59 +02001175 */
1176static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1177{
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001178 struct add_crtlist_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandc756bbd2020-05-13 17:23:59 +02001179 int cfgerr = 0;
1180 struct ckch_store *store;
1181 char *err = NULL;
1182 char path[MAXPATHLEN+1];
1183 char *crtlist_path;
1184 char *cert_path = NULL;
1185 struct ebmb_node *eb;
1186 struct ebpt_node *inserted;
1187 struct crtlist *crtlist;
1188 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001189 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001190
1191 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1192 return 1;
1193
1194 if (!*args[3] || (!payload && !*args[4]))
1195 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1196
1197 crtlist_path = args[3];
1198
William Lallemand99cc2182020-06-25 15:19:51 +02001199 /* strip trailing slashes, including first one */
1200 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1201 *end = 0;
1202
William Lallemandc756bbd2020-05-13 17:23:59 +02001203 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1204 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1205
1206 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1207 if (!eb) {
1208 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1209 goto error;
1210 }
1211 crtlist = ebmb_entry(eb, struct crtlist, node);
1212
1213 entry = crtlist_entry_new();
1214 if (entry == NULL) {
1215 memprintf(&err, "Not enough memory!");
1216 goto error;
1217 }
1218
1219 if (payload) {
1220 char *lf;
1221
1222 lf = strrchr(payload, '\n');
1223 if (lf) {
1224 memprintf(&err, "only one line of payload is supported!");
1225 goto error;
1226 }
1227 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001228 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001229 if (cfgerr & ERR_CODE)
1230 goto error;
1231 } else {
1232 cert_path = args[4];
1233 }
1234
1235 if (!cert_path) {
1236 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1237 cfgerr |= ERR_ALERT | ERR_FATAL;
1238 goto error;
1239 }
1240
1241 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1242 char *slash;
1243
1244 slash = strrchr(cert_path, '/');
1245 if (!slash) {
1246 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1247 goto error;
1248 }
1249 /* temporary replace / by 0 to do an strcmp */
1250 *slash = '\0';
1251 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1252 *slash = '/';
1253 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1254 goto error;
1255 }
1256 *slash = '/';
1257 }
1258
1259 if (*cert_path != '/' && global_ssl.crt_base) {
Willy Tarreau393e42a2022-05-09 10:31:28 +02001260 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > sizeof(path) ||
1261 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path) > sizeof(path)) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001262 memprintf(&err, "'%s' : path too long", cert_path);
1263 cfgerr |= ERR_ALERT | ERR_FATAL;
1264 goto error;
1265 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001266 cert_path = path;
1267 }
1268
1269 store = ckchs_lookup(cert_path);
1270 if (store == NULL) {
1271 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1272 goto error;
1273 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001274 if (store->ckch == NULL || store->ckch->cert == NULL) {
1275 memprintf(&err, "certificate '%s' is empty!", cert_path);
1276 goto error;
1277 }
1278
1279 /* check if it's possible to insert this new crtlist_entry */
1280 entry->node.key = store;
1281 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1282 if (inserted != &entry->node) {
1283 memprintf(&err, "file already exists in this directory!");
1284 goto error;
1285 }
1286
1287 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1288 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1289 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1290 goto error;
1291 }
1292
Willy Tarreau2b718102021-04-21 07:32:39 +02001293 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001294 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001295 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001296
Willy Tarreaufa11df52022-05-05 13:48:40 +02001297 ctx->state = ADDCRT_ST_INIT;
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001298 ctx->crtlist = crtlist;
1299 ctx->entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001300
1301 /* unlock is done in the release handler */
1302 return 0;
1303
1304error:
1305 crtlist_entry_free(entry);
1306 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1307 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1308 return cli_dynerr(appctx, err);
1309}
1310
1311/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1312static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1313{
1314 struct ckch_store *store;
1315 char *err = NULL;
1316 char *crtlist_path, *cert_path;
1317 struct ebmb_node *ebmb;
1318 struct ebpt_node *ebpt;
1319 struct crtlist *crtlist;
1320 struct crtlist_entry *entry = NULL;
1321 struct ckch_inst *inst, *inst_s;
1322 int linenum = 0;
1323 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001324 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001325 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001326
1327 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1328 return 1;
1329
1330 if (!*args[3] || !*args[4])
1331 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1332
1333 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1334 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1335
1336 crtlist_path = args[3];
1337 cert_path = args[4];
1338
1339 colons = strchr(cert_path, ':');
1340 if (colons) {
1341 char *endptr;
1342
1343 linenum = strtol(colons + 1, &endptr, 10);
1344 if (colons + 1 == endptr || *endptr != '\0') {
1345 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1346 goto error;
1347 }
1348 *colons = '\0';
1349 }
William Lallemand99cc2182020-06-25 15:19:51 +02001350
1351 /* strip trailing slashes, including first one */
1352 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1353 *end = 0;
1354
William Lallemandc756bbd2020-05-13 17:23:59 +02001355 /* look for crtlist */
1356 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1357 if (!ebmb) {
1358 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1359 goto error;
1360 }
1361 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1362
1363 /* look for store */
1364 store = ckchs_lookup(cert_path);
1365 if (store == NULL) {
1366 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1367 goto error;
1368 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001369 if (store->ckch == NULL || store->ckch->cert == NULL) {
1370 memprintf(&err, "certificate '%s' is empty!", cert_path);
1371 goto error;
1372 }
1373
1374 ebpt = ebpt_lookup(&crtlist->entries, store);
1375 if (!ebpt) {
1376 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1377 goto error;
1378 }
1379
1380 /* list the line number of entries for errors in err, and select the right ebpt */
1381 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1382 struct crtlist_entry *tmp;
1383
1384 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1385 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1386
1387 /* select the entry we wanted */
1388 if (linenum == 0 || tmp->linenum == linenum) {
1389 if (!entry)
1390 entry = tmp;
1391 }
1392 }
1393
1394 /* we didn't found the specified entry */
1395 if (!entry) {
1396 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);
1397 goto error;
1398 }
1399
1400 /* we didn't specified a line number but there were several entries */
1401 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1402 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1403 goto error;
1404 }
1405
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001406 /* Iterate over all the instances in order to see if any of them is a
1407 * default instance. If this is the case, the entry won't be suppressed. */
1408 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1409 if (inst->is_default && !inst->bind_conf->strict_sni) {
1410 if (!error_message_dumped) {
1411 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1412 error_message_dumped = 1;
1413 }
1414 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1415 }
1416 }
1417 if (error_message_dumped)
1418 goto error;
1419
William Lallemandc756bbd2020-05-13 17:23:59 +02001420 /* upon error free the ckch_inst and everything inside */
1421
1422 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001423 LIST_DELETE(&entry->by_crtlist);
1424 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001425
1426 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1427 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001428 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandc756bbd2020-05-13 17:23:59 +02001429
1430 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1431 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1432 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001433 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001434 SSL_CTX_free(sni->ctx);
1435 free(sni);
1436 }
1437 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001438 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001439 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1440 LIST_DELETE(&link_ref->link->list);
1441 LIST_DELETE(&link_ref->list);
1442 free(link_ref);
1443 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001444 free(inst);
1445 }
1446
1447 crtlist_free_filters(entry->filters);
1448 ssl_sock_free_ssl_conf(entry->ssl_conf);
1449 free(entry->ssl_conf);
1450 free(entry);
1451
1452 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1453 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1454 return cli_dynmsg(appctx, LOG_NOTICE, err);
1455
1456error:
1457 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1458 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1459 return cli_dynerr(appctx, err);
1460}
1461
1462
William Lallemandee8530c2020-06-23 18:19:42 +02001463/* unlink and free all crt-list and crt-list entries */
1464void crtlist_deinit()
1465{
1466 struct eb_node *node, *next;
1467 struct crtlist *crtlist;
1468
1469 node = eb_first(&crtlists_tree);
1470 while (node) {
1471 next = eb_next(node);
1472 crtlist = ebmb_entry(node, struct crtlist, node);
1473 crtlist_free(crtlist);
1474 node = next;
1475 }
1476}
1477
William Lallemandc756bbd2020-05-13 17:23:59 +02001478
1479/* register cli keywords */
1480static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001481 { { "add", "ssl", "crt-list", NULL }, "add ssl crt-list <list> <cert> [opts]* : add to crt-list file <list> a line <cert> or a payload", cli_parse_add_crtlist, cli_io_handler_add_crtlist, cli_release_add_crtlist },
1482 { { "del", "ssl", "crt-list", NULL }, "del ssl crt-list <list> <cert[:line]> : delete a line <cert> from crt-list file <list>", cli_parse_del_crtlist, NULL, NULL },
1483 { { "show", "ssl", "crt-list", NULL }, "show ssl crt-list [-n] [<list>] : show the list of crt-lists or the content of a crt-list file <list>", cli_parse_dump_crtlist, cli_io_handler_dump_crtlist, NULL },
William Lallemandc756bbd2020-05-13 17:23:59 +02001484 { { NULL }, NULL, NULL, NULL } }
1485};
1486
1487INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1488