blob: 6b400e2e1bfa816ca5729f2a8992249347773cd6 [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)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010038 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020039#endif
40#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010041 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020042#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010043 ha_free(&conf->ca_file);
44 ha_free(&conf->ca_verify_file);
45 ha_free(&conf->crl_file);
46 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050047#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010048 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020049#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010050 ha_free(&conf->curves);
51 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020052 }
53}
54
William Lallemand82f2d2f2020-09-10 19:06:43 +020055/*
56 * Allocate and copy a ssl_bind_conf structure
57 */
58struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
59{
60 struct ssl_bind_conf *dst;
61
62 if (!src)
63 return NULL;
64
65 dst = calloc(1, sizeof(*dst));
66 if (!dst)
67 return NULL;
68
69#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
70 if (src->npn_str) {
71 dst->npn_str = strdup(src->npn_str);
72 if (!dst->npn_str)
73 goto error;
74 }
75#endif
76#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
77 if (src->alpn_str) {
78 dst->alpn_str = strdup(src->alpn_str);
79 if (!dst->alpn_str)
80 goto error;
81 }
82#endif
83 if (src->ca_file) {
84 dst->ca_file = strdup(src->ca_file);
85 if (!dst->ca_file)
86 goto error;
87 }
88 if (src->ca_verify_file) {
89 dst->ca_verify_file = strdup(src->ca_verify_file);
90 if (!dst->ca_verify_file)
91 goto error;
92 }
93 if (src->crl_file) {
94 dst->crl_file = strdup(src->crl_file);
95 if (!dst->crl_file)
96 goto error;
97 }
98 if (src->ciphers) {
99 dst->ciphers = strdup(src->ciphers);
100 if (!dst->ciphers)
101 goto error;
102 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500103#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200104 if (src->ciphersuites) {
105 dst->ciphersuites = strdup(src->ciphersuites);
106 if (!dst->ciphersuites)
107 goto error;
108 }
109#endif
110 if (src->curves) {
111 dst->curves = strdup(src->curves);
112 if (!dst->curves)
113 goto error;
114 }
115 if (src->ecdhe) {
116 dst->ecdhe = strdup(src->ecdhe);
117 if (!dst->ecdhe)
118 goto error;
119 }
120 return dst;
121
122error:
123 ssl_sock_free_ssl_conf(dst);
124 free(dst);
125
126 return NULL;
127}
William Lallemand6e9556b2020-05-12 17:52:44 +0200128
129/* free sni filters */
130void crtlist_free_filters(char **args)
131{
132 int i;
133
134 if (!args)
135 return;
136
137 for (i = 0; args[i]; i++)
138 free(args[i]);
139
140 free(args);
141}
142
143/* Alloc and duplicate a char ** array */
144char **crtlist_dup_filters(char **args, int fcount)
145{
146 char **dst;
147 int i;
148
149 if (fcount == 0)
150 return NULL;
151
152 dst = calloc(fcount + 1, sizeof(*dst));
153 if (!dst)
154 return NULL;
155
156 for (i = 0; i < fcount; i++) {
157 dst[i] = strdup(args[i]);
158 if (!dst[i])
159 goto error;
160 }
161 return dst;
162
163error:
164 crtlist_free_filters(dst);
165 return NULL;
166}
167
168/*
169 * Detach and free a crtlist_entry.
170 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
171 */
172void crtlist_entry_free(struct crtlist_entry *entry)
173{
174 struct ckch_inst *inst, *inst_s;
175
176 if (entry == NULL)
177 return;
178
179 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200180 LIST_DELETE(&entry->by_crtlist);
181 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200182 crtlist_free_filters(entry->filters);
183 ssl_sock_free_ssl_conf(entry->ssl_conf);
184 free(entry->ssl_conf);
185 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
186 ckch_inst_free(inst);
187 }
188 free(entry);
189}
William Lallemand5622c452020-09-10 19:08:49 +0200190/*
191 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
192 * Return a pointer to the new entry
193 */
194struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
195{
196 struct crtlist_entry *entry;
197
198 if (src == NULL)
199 return NULL;
200
201 entry = crtlist_entry_new();
202 if (entry == NULL)
203 return NULL;
204
205 if (src->filters) {
206 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
207 if (!entry->filters)
208 goto error;
209 }
210 entry->fcount = src->fcount;
211 if (src->ssl_conf) {
212 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
213 if (!entry->ssl_conf)
214 goto error;
215 }
216 entry->crtlist = src->crtlist;
217
218 return entry;
219
220error:
221
222 crtlist_free_filters(entry->filters);
223 ssl_sock_free_ssl_conf(entry->ssl_conf);
224 free(entry->ssl_conf);
225 free(entry);
226
227 return NULL;
228}
William Lallemand6e9556b2020-05-12 17:52:44 +0200229
230/*
231 * Allocate and initialize a crtlist_entry
232 */
233struct crtlist_entry *crtlist_entry_new()
234{
235 struct crtlist_entry *entry;
236
237 entry = calloc(1, sizeof(*entry));
238 if (entry == NULL)
239 return NULL;
240
241 LIST_INIT(&entry->ckch_inst);
242
Willy Tarreau2b718102021-04-21 07:32:39 +0200243 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200244 LIST_INIT(&entry->by_crtlist);
245 LIST_INIT(&entry->by_ckch_store);
246
247 return entry;
248}
249
250/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
251void crtlist_free(struct crtlist *crtlist)
252{
253 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200254 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200255
256 if (crtlist == NULL)
257 return;
258
William Lallemand6a3168a2020-06-23 11:43:35 +0200259 bind_conf_node = crtlist->bind_conf;
260 while (bind_conf_node) {
261 struct bind_conf_list *next = bind_conf_node->next;
262 free(bind_conf_node);
263 bind_conf_node = next;
264 }
265
William Lallemand6e9556b2020-05-12 17:52:44 +0200266 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
267 crtlist_entry_free(entry);
268 }
269 ebmb_delete(&crtlist->node);
270 free(crtlist);
271}
272
273/* Alloc and initialize a struct crtlist
274 * <filename> is the key of the ebmb_node
275 * <unique> initialize the list of entries to be unique (1) or not (0)
276 */
277struct crtlist *crtlist_new(const char *filename, int unique)
278{
279 struct crtlist *newlist;
280
281 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
282 if (newlist == NULL)
283 return NULL;
284
285 memcpy(newlist->node.key, filename, strlen(filename) + 1);
286 if (unique)
287 newlist->entries = EB_ROOT_UNIQUE;
288 else
289 newlist->entries = EB_ROOT;
290
291 LIST_INIT(&newlist->ord_entries);
292
293 return newlist;
294}
295
296/*
297 * Read a single crt-list line. /!\ alter the <line> string.
298 * Fill <crt_path> and <crtlist_entry>
299 * <crtlist_entry> must be alloc and free by the caller
300 * <crtlist_entry->ssl_conf> is alloc by the function
301 * <crtlist_entry->filters> is alloc by the function
302 * <crt_path> is a ptr in <line>
303 * Return an error code
304 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100305int 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 +0200306{
307 int cfgerr = 0;
308 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
309 char *end;
310 char *args[MAX_CRT_ARGS + 1];
311 struct ssl_bind_conf *ssl_conf = NULL;
312
313 if (!line || !crt_path || !entry)
314 return ERR_ALERT | ERR_FATAL;
315
316 end = line + strlen(line);
317 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
318 /* Check if we reached the limit and the last char is not \n.
319 * Watch out for the last line without the terminating '\n'!
320 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200321 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
322 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200323 cfgerr |= ERR_ALERT | ERR_FATAL;
324 goto error;
325 }
326 arg = 0;
327 newarg = 1;
328 while (*line) {
329 if (isspace((unsigned char)*line)) {
330 newarg = 1;
331 *line = 0;
332 } else if (*line == '[') {
333 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200334 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200335 cfgerr |= ERR_ALERT | ERR_FATAL;
336 goto error;
337 }
338 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200339 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200340 cfgerr |= ERR_ALERT | ERR_FATAL;
341 goto error;
342 }
343 ssl_b = arg;
344 newarg = 1;
345 *line = 0;
346 } else if (*line == ']') {
347 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200348 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200349 cfgerr |= ERR_ALERT | ERR_FATAL;
350 goto error;
351 }
352 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200353 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200354 cfgerr |= ERR_ALERT | ERR_FATAL;
355 goto error;
356 }
357 ssl_e = arg;
358 newarg = 1;
359 *line = 0;
360 } else if (newarg) {
361 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200362 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200363 cfgerr |= ERR_ALERT | ERR_FATAL;
364 goto error;
365 }
366 newarg = 0;
367 args[arg++] = line;
368 }
369 line++;
370 }
371 args[arg++] = line;
372
373 /* empty line */
374 if (!*args[0]) {
375 cfgerr |= ERR_NONE;
376 goto error;
377 }
378
379 *crt_path = args[0];
380
381 if (ssl_b) {
382 ssl_conf = calloc(1, sizeof *ssl_conf);
383 if (!ssl_conf) {
384 memprintf(err, "not enough memory!");
385 cfgerr |= ERR_ALERT | ERR_FATAL;
386 goto error;
387 }
388 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100389
William Lallemand6e9556b2020-05-12 17:52:44 +0200390 cur_arg = ssl_b ? ssl_b : 1;
391 while (cur_arg < ssl_e) {
392 newarg = 0;
393 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
394 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
395 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100396 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200397 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200398 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
399 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200400 cfgerr |= ERR_ALERT | ERR_FATAL;
401 goto error;
402 }
403 cur_arg += 1 + ssl_bind_kws[i].skip;
404 break;
405 }
406 }
407 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200408 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
409 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200410 cfgerr |= ERR_ALERT | ERR_FATAL;
411 goto error;
412 }
413 }
414 entry->linenum = linenum;
415 entry->ssl_conf = ssl_conf;
416 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
417 entry->fcount = arg - cur_arg - 1;
418
419 return cfgerr;
420
421error:
422 crtlist_free_filters(entry->filters);
423 entry->filters = NULL;
424 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100425 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200426 return cfgerr;
427}
428
429
430
431/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
432 * Fill the <crtlist> argument with a pointer to a new crtlist struct
433 *
434 * This function tries to open and store certificate files.
435 */
436int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
437{
438 struct crtlist *newlist;
439 struct crtlist_entry *entry = NULL;
440 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200441 FILE *f;
442 struct stat buf;
443 int linenum = 0;
444 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200445 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200446
447 if ((f = fopen(file, "r")) == NULL) {
448 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
449 return ERR_ALERT | ERR_FATAL;
450 }
451
452 newlist = crtlist_new(file, 0);
453 if (newlist == NULL) {
454 memprintf(err, "Not enough memory!");
455 cfgerr |= ERR_ALERT | ERR_FATAL;
456 goto error;
457 }
458
459 while (fgets(thisline, sizeof(thisline), f) != NULL) {
460 char *end;
461 char *line = thisline;
462 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100463 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200464 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100465 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200466
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200467 if (missing_lf != -1) {
468 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
469 file, linenum, (missing_lf + 1));
470 cfgerr |= ERR_ALERT | ERR_FATAL;
471 missing_lf = -1;
472 break;
473 }
474
William Lallemand6e9556b2020-05-12 17:52:44 +0200475 linenum++;
476 end = line + strlen(line);
477 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
478 /* Check if we reached the limit and the last char is not \n.
479 * Watch out for the last line without the terminating '\n'!
480 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200481 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
482 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200483 cfgerr |= ERR_ALERT | ERR_FATAL;
484 break;
485 }
486
487 if (*line == '#' || *line == '\n' || *line == '\r')
488 continue;
489
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200490 if (end > line && *(end-1) == '\n') {
491 /* kill trailing LF */
492 *(end - 1) = 0;
493 }
494 else {
495 /* mark this line as truncated */
496 missing_lf = end - line;
497 }
498
William Lallemand6e9556b2020-05-12 17:52:44 +0200499 entry = crtlist_entry_new();
500 if (entry == NULL) {
501 memprintf(err, "Not enough memory!");
502 cfgerr |= ERR_ALERT | ERR_FATAL;
503 goto error;
504 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200505
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100506 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200507 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200508 goto error;
509
510 /* empty line */
511 if (!crt_path || !*crt_path) {
512 crtlist_entry_free(entry);
513 entry = NULL;
514 continue;
515 }
516
517 if (*crt_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +0200518 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > sizeof(path) ||
Willy Tarreau6c3cc562022-05-09 21:14:04 +0200519 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path) > sizeof(path)) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200520 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
521 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200522 cfgerr |= ERR_ALERT | ERR_FATAL;
523 goto error;
524 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200525 crt_path = path;
526 }
527
528 /* Look for a ckch_store or create one */
529 ckchs = ckchs_lookup(crt_path);
530 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200531 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100532 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200533
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200534 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200535 if (ckchs == NULL) {
536 cfgerr |= ERR_ALERT | ERR_FATAL;
537 goto error;
538 }
539
540 entry->node.key = ckchs;
541 entry->crtlist = newlist;
542 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200543 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
544 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200545
William Lallemand73404572020-11-20 18:23:40 +0100546 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200547 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200548 bundle, since 2.3 we don't support multiple
549 certificate in the same OpenSSL store, so we
550 emulate it by loading each file separately. To
551 do so we need to duplicate the entry in the
552 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200553 char fp[MAXPATHLEN+1] = {0};
554 int n = 0;
555 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
556 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
557 struct stat buf;
558 int ret;
559
William Lallemand86c2dd62020-11-20 14:23:38 +0100560 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200561 if (ret > sizeof(fp))
562 continue;
563
564 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100565 if (!ckchs) {
566 if (stat(fp, &buf) == 0) {
567 ckchs = ckchs_load_cert_file(fp, err);
568 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200569 cfgerr |= ERR_ALERT | ERR_FATAL;
570 goto error;
571 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100572 } else {
573 continue; /* didn't find this extension, skip */
574 }
575 }
576 found++;
577 linenum++; /* we duplicate the line for this entry in the bundle */
578 if (!entry_dup) { /* if the entry was used, duplicate one */
579 linenum++;
580 entry_dup = crtlist_entry_dup(entry);
581 if (!entry_dup) {
582 cfgerr |= ERR_ALERT | ERR_FATAL;
583 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200584 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100585 entry_dup->linenum = linenum;
586 }
William Lallemand47da8212020-09-10 19:13:27 +0200587
William Lallemand77e1c6f2020-11-20 18:26:09 +0100588 entry_dup->node.key = ckchs;
589 entry_dup->crtlist = newlist;
590 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200591 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
592 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200593
William Lallemand77e1c6f2020-11-20 18:26:09 +0100594 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200595 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100596#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
597 if (found) {
598 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
599 err && *err ? *err : "", crt_path);
600 cfgerr |= ERR_ALERT | ERR_FATAL;
601 }
602#endif
William Lallemand47da8212020-09-10 19:13:27 +0200603 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100604 if (!found) {
605 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
606 err && *err ? *err : "", crt_path, strerror(errno));
607 cfgerr |= ERR_ALERT | ERR_FATAL;
608 }
609
William Lallemand50c03aa2020-11-06 16:24:07 +0100610 } else {
611 entry->node.key = ckchs;
612 entry->crtlist = newlist;
613 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200614 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
615 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100616 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200617 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200618 entry = NULL;
619 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200620
621 if (missing_lf != -1) {
622 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
623 file, linenum, (missing_lf + 1));
624 cfgerr |= ERR_ALERT | ERR_FATAL;
625 }
626
William Lallemand6e9556b2020-05-12 17:52:44 +0200627 if (cfgerr & ERR_CODE)
628 goto error;
629
630 newlist->linecount = linenum;
631
632 fclose(f);
633 *crtlist = newlist;
634
635 return cfgerr;
636error:
637 crtlist_entry_free(entry);
638
639 fclose(f);
640 crtlist_free(newlist);
641 return cfgerr;
642}
643
644/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
645 * Fill the <crtlist> argument with a pointer to a new crtlist struct
646 *
647 * This function tries to open and store certificate files.
648 */
649int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
650{
651 struct crtlist *dir;
652 struct dirent **de_list;
653 int i, n;
654 struct stat buf;
655 char *end;
656 char fp[MAXPATHLEN+1];
657 int cfgerr = 0;
658 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200659
660 dir = crtlist_new(path, 1);
661 if (dir == NULL) {
662 memprintf(err, "not enough memory");
663 return ERR_ALERT | ERR_FATAL;
664 }
665
666 n = scandir(path, &de_list, 0, alphasort);
667 if (n < 0) {
668 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
669 err && *err ? *err : "", path, strerror(errno));
670 cfgerr |= ERR_ALERT | ERR_FATAL;
671 }
672 else {
673 for (i = 0; i < n; i++) {
674 struct crtlist_entry *entry;
675 struct dirent *de = de_list[i];
676
677 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100678 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 +0200679 goto ignore_entry;
680
681 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
682 if (stat(fp, &buf) != 0) {
683 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
684 err && *err ? *err : "", fp, strerror(errno));
685 cfgerr |= ERR_ALERT | ERR_FATAL;
686 goto ignore_entry;
687 }
688 if (!S_ISREG(buf.st_mode))
689 goto ignore_entry;
690
691 entry = crtlist_entry_new();
692 if (entry == NULL) {
693 memprintf(err, "not enough memory '%s'", fp);
694 cfgerr |= ERR_ALERT | ERR_FATAL;
695 goto ignore_entry;
696 }
697
William Lallemand6e9556b2020-05-12 17:52:44 +0200698 ckchs = ckchs_lookup(fp);
699 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200700 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200701 if (ckchs == NULL) {
702 free(de);
703 free(entry);
704 cfgerr |= ERR_ALERT | ERR_FATAL;
705 goto end;
706 }
707 entry->node.key = ckchs;
708 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200709 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
710 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200711 ebpt_insert(&dir->entries, &entry->node);
712
713ignore_entry:
714 free(de);
715 }
716end:
717 free(de_list);
718 }
719
720 if (cfgerr & ERR_CODE) {
721 /* free the dir and entries on error */
722 crtlist_free(dir);
723 } else {
724 *crtlist = dir;
725 }
726 return cfgerr;
727
728}
729
William Lallemandc756bbd2020-05-13 17:23:59 +0200730/*
731 * Take an ssl_bind_conf structure and append the configuration line used to
732 * create it in the buffer
733 */
734static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
735{
736 int space = 0;
737
738 if (conf == NULL)
739 return;
740
741 chunk_appendf(buf, " [");
742#ifdef OPENSSL_NPN_NEGOTIATED
743 if (conf->npn_str) {
744 int len = conf->npn_len;
745 char *ptr = conf->npn_str;
746 int comma = 0;
747
748 if (space) chunk_appendf(buf, " ");
749 chunk_appendf(buf, "npn ");
750 while (len) {
751 unsigned short size;
752
753 size = *ptr;
754 ptr++;
755 if (comma)
756 chunk_memcat(buf, ",", 1);
757 chunk_memcat(buf, ptr, size);
758 ptr += size;
759 len -= size + 1;
760 comma = 1;
761 }
762 chunk_memcat(buf, "", 1); /* finish with a \0 */
763 space++;
764 }
765#endif
766#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
767 if (conf->alpn_str) {
768 int len = conf->alpn_len;
769 char *ptr = conf->alpn_str;
770 int comma = 0;
771
772 if (space) chunk_appendf(buf, " ");
773 chunk_appendf(buf, "alpn ");
774 while (len) {
775 unsigned short size;
776
777 size = *ptr;
778 ptr++;
779 if (comma)
780 chunk_memcat(buf, ",", 1);
781 chunk_memcat(buf, ptr, size);
782 ptr += size;
783 len -= size + 1;
784 comma = 1;
785 }
786 chunk_memcat(buf, "", 1); /* finish with a \0 */
787 space++;
788 }
789#endif
790 /* verify */
791 {
792 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
793 if (space) chunk_appendf(buf, " ");
794 chunk_appendf(buf, "verify none");
795 space++;
796 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
797 if (space) chunk_appendf(buf, " ");
798 chunk_appendf(buf, "verify optional");
799 space++;
800 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
801 if (space) chunk_appendf(buf, " ");
802 chunk_appendf(buf, "verify required");
803 space++;
804 }
805 }
806
807 if (conf->no_ca_names) {
808 if (space) chunk_appendf(buf, " ");
809 chunk_appendf(buf, "no-ca-names");
810 space++;
811 }
812
813 if (conf->early_data) {
814 if (space) chunk_appendf(buf, " ");
815 chunk_appendf(buf, "allow-0rtt");
816 space++;
817 }
818 if (conf->ca_file) {
819 if (space) chunk_appendf(buf, " ");
820 chunk_appendf(buf, "ca-file %s", conf->ca_file);
821 space++;
822 }
823 if (conf->crl_file) {
824 if (space) chunk_appendf(buf, " ");
825 chunk_appendf(buf, "crl-file %s", conf->crl_file);
826 space++;
827 }
828 if (conf->ciphers) {
829 if (space) chunk_appendf(buf, " ");
830 chunk_appendf(buf, "ciphers %s", conf->ciphers);
831 space++;
832 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500833#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200834 if (conf->ciphersuites) {
835 if (space) chunk_appendf(buf, " ");
836 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
837 space++;
838 }
839#endif
840 if (conf->curves) {
841 if (space) chunk_appendf(buf, " ");
842 chunk_appendf(buf, "curves %s", conf->curves);
843 space++;
844 }
845 if (conf->ecdhe) {
846 if (space) chunk_appendf(buf, " ");
847 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
848 space++;
849 }
850
851 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200852 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200853 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200854 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200855 space++;
856 }
857
William Lallemand8177ad92020-05-20 16:49:02 +0200858 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200859 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200860 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200861 space++;
862 }
863
864 chunk_appendf(buf, "]");
865
866 return;
867}
868
869/* dump a list of filters */
870static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
871{
872 int i;
873
874 if (!entry->fcount)
875 return;
876
877 for (i = 0; i < entry->fcount; i++) {
878 chunk_appendf(buf, " %s", entry->filters[i]);
879 }
880 return;
881}
882
883/************************** CLI functions ****************************/
884
885
886/* CLI IO handler for '(show|dump) ssl crt-list' */
887static int cli_io_handler_dump_crtlist(struct appctx *appctx)
888{
889 struct buffer *trash = alloc_trash_chunk();
890 struct stream_interface *si = appctx->owner;
891 struct ebmb_node *lnode;
892
893 if (trash == NULL)
894 return 1;
895
896 /* dump the list of crt-lists */
897 lnode = appctx->ctx.cli.p1;
898 if (lnode == NULL)
899 lnode = ebmb_first(&crtlists_tree);
900 while (lnode) {
901 chunk_appendf(trash, "%s\n", lnode->key);
902 if (ci_putchk(si_ic(si), trash) == -1) {
903 si_rx_room_blk(si);
904 goto yield;
905 }
906 lnode = ebmb_next(lnode);
907 }
908 free_trash_chunk(trash);
909 return 1;
910yield:
911 appctx->ctx.cli.p1 = lnode;
912 free_trash_chunk(trash);
913 return 0;
914}
915
916/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
917static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
918{
919 struct buffer *trash = alloc_trash_chunk();
920 struct crtlist *crtlist;
921 struct stream_interface *si = appctx->owner;
922 struct crtlist_entry *entry;
923
924 if (trash == NULL)
925 return 1;
926
927 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
928
929 entry = appctx->ctx.cli.p1;
930 if (entry == NULL) {
931 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
932 chunk_appendf(trash, "# %s\n", crtlist->node.key);
933 if (ci_putchk(si_ic(si), trash) == -1) {
934 si_rx_room_blk(si);
935 goto yield;
936 }
937 }
938
939 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
940 struct ckch_store *store;
941 const char *filename;
942
943 store = entry->node.key;
944 filename = store->path;
945 chunk_appendf(trash, "%s", filename);
946 if (appctx->ctx.cli.i0 == 's') /* show */
947 chunk_appendf(trash, ":%d", entry->linenum);
948 dump_crtlist_sslconf(trash, entry->ssl_conf);
949 dump_crtlist_filters(trash, entry);
950 chunk_appendf(trash, "\n");
951
952 if (ci_putchk(si_ic(si), trash) == -1) {
953 si_rx_room_blk(si);
954 goto yield;
955 }
956 }
957 free_trash_chunk(trash);
958 return 1;
959yield:
960 appctx->ctx.cli.p1 = entry;
961 free_trash_chunk(trash);
962 return 0;
963}
964
965/* CLI argument parser for '(show|dump) ssl crt-list' */
966static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
967{
968 struct ebmb_node *lnode;
969 char *filename = NULL;
970 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200971 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200972
973 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
974 return 1;
975
976 appctx->ctx.cli.p0 = NULL;
977 appctx->ctx.cli.p1 = NULL;
978
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100979 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200980 mode = 's';
981 filename = args[4];
982 } else {
983 mode = 'd';
984 filename = args[3];
985 }
986
987 if (mode == 's' && !*args[4])
988 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
989
990 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200991
992
993 /* strip trailing slashes, including first one */
994 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
995 *end = 0;
996
William Lallemandc756bbd2020-05-13 17:23:59 +0200997 lnode = ebst_lookup(&crtlists_tree, filename);
998 if (lnode == NULL)
999 return cli_err(appctx, "didn't find the specified filename\n");
1000
1001 appctx->ctx.cli.p0 = lnode;
1002 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1003 }
1004 appctx->ctx.cli.i0 = mode;
1005
1006 return 0;
1007}
1008
1009/* release function of the "add ssl crt-list' command, free things and unlock
1010 the spinlock */
1011static void cli_release_add_crtlist(struct appctx *appctx)
1012{
1013 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1014
1015 if (appctx->st2 != SETCERT_ST_FIN) {
1016 struct ckch_inst *inst, *inst_s;
1017 /* upon error free the ckch_inst and everything inside */
1018 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001019 LIST_DELETE(&entry->by_crtlist);
1020 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001021
1022 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1023 ckch_inst_free(inst);
1024 }
1025 crtlist_free_filters(entry->filters);
1026 ssl_sock_free_ssl_conf(entry->ssl_conf);
1027 free(entry->ssl_conf);
1028 free(entry);
1029 }
1030
1031 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1032}
1033
1034
1035/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1036 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1037 *
1038 * The logic is the same as the "commit ssl cert" command but without the
1039 * freeing of the old structures, because there are none.
1040 */
1041static int cli_io_handler_add_crtlist(struct appctx *appctx)
1042{
1043 struct bind_conf_list *bind_conf_node;
1044 struct stream_interface *si = appctx->owner;
1045 struct crtlist *crtlist = appctx->ctx.cli.p0;
1046 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1047 struct ckch_store *store = entry->node.key;
1048 struct buffer *trash = alloc_trash_chunk();
1049 struct ckch_inst *new_inst;
1050 char *err = NULL;
1051 int i = 0;
1052 int errcode = 0;
1053
1054 if (trash == NULL)
1055 goto error;
1056
1057 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1058 * created.
1059 */
1060 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1061 goto error;
1062
1063 while (1) {
1064 switch (appctx->st2) {
1065 case SETCERT_ST_INIT:
1066 /* This state just print the update message */
1067 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1068 if (ci_putchk(si_ic(si), trash) == -1) {
1069 si_rx_room_blk(si);
1070 goto yield;
1071 }
1072 appctx->st2 = SETCERT_ST_GEN;
1073 /* fallthrough */
1074 case SETCERT_ST_GEN:
1075 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1076 if (bind_conf_node == NULL)
1077 bind_conf_node = crtlist->bind_conf;
1078 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1079 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1080 struct sni_ctx *sni;
1081
1082 /* yield every 10 generations */
1083 if (i > 10) {
1084 appctx->ctx.cli.p2 = bind_conf_node;
1085 goto yield;
1086 }
1087
1088 /* we don't support multi-cert bundles, only simple ones */
1089 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1090 if (errcode & ERR_CODE)
1091 goto error;
1092
1093 /* we need to initialize the SSL_CTX generated */
1094 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1095 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1096 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1097 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1098 if (errcode & ERR_CODE)
1099 goto error;
1100 }
1101 }
1102 /* display one dot for each new instance */
1103 chunk_appendf(trash, ".");
1104 i++;
Willy Tarreau2b718102021-04-21 07:32:39 +02001105 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1106 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001107 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001108 }
1109 appctx->st2 = SETCERT_ST_INSERT;
1110 /* fallthrough */
1111 case SETCERT_ST_INSERT:
1112 /* insert SNIs in bind_conf */
1113 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1114 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1115 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1116 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1117 }
1118 entry->linenum = ++crtlist->linecount;
1119 appctx->st2 = SETCERT_ST_FIN;
1120 goto end;
1121 }
1122 }
1123
1124end:
1125 chunk_appendf(trash, "\n");
1126 if (errcode & ERR_WARN)
1127 chunk_appendf(trash, "%s", err);
1128 chunk_appendf(trash, "Success!\n");
1129 if (ci_putchk(si_ic(si), trash) == -1)
1130 si_rx_room_blk(si);
1131 free_trash_chunk(trash);
1132 /* success: call the release function and don't come back */
1133 return 1;
1134yield:
1135 /* store the state */
1136 if (ci_putchk(si_ic(si), trash) == -1)
1137 si_rx_room_blk(si);
1138 free_trash_chunk(trash);
1139 si_rx_endp_more(si); /* let's come back later */
1140 return 0; /* should come back */
1141
1142error:
1143 /* spin unlock and free are done in the release function */
1144 if (trash) {
1145 chunk_appendf(trash, "\n%sFailed!\n", err);
1146 if (ci_putchk(si_ic(si), trash) == -1)
1147 si_rx_room_blk(si);
1148 free_trash_chunk(trash);
1149 }
1150 /* error: call the release function and don't come back */
1151 return 1;
1152}
1153
1154
1155/*
1156 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1157 * Filters and option must be passed through payload:
1158 */
1159static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1160{
1161 int cfgerr = 0;
1162 struct ckch_store *store;
1163 char *err = NULL;
1164 char path[MAXPATHLEN+1];
1165 char *crtlist_path;
1166 char *cert_path = NULL;
1167 struct ebmb_node *eb;
1168 struct ebpt_node *inserted;
1169 struct crtlist *crtlist;
1170 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001171 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001172
1173 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1174 return 1;
1175
1176 if (!*args[3] || (!payload && !*args[4]))
1177 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1178
1179 crtlist_path = args[3];
1180
William Lallemand99cc2182020-06-25 15:19:51 +02001181 /* strip trailing slashes, including first one */
1182 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1183 *end = 0;
1184
William Lallemandc756bbd2020-05-13 17:23:59 +02001185 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1186 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1187
1188 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1189 if (!eb) {
1190 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1191 goto error;
1192 }
1193 crtlist = ebmb_entry(eb, struct crtlist, node);
1194
1195 entry = crtlist_entry_new();
1196 if (entry == NULL) {
1197 memprintf(&err, "Not enough memory!");
1198 goto error;
1199 }
1200
1201 if (payload) {
1202 char *lf;
1203
1204 lf = strrchr(payload, '\n');
1205 if (lf) {
1206 memprintf(&err, "only one line of payload is supported!");
1207 goto error;
1208 }
1209 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001210 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001211 if (cfgerr & ERR_CODE)
1212 goto error;
1213 } else {
1214 cert_path = args[4];
1215 }
1216
1217 if (!cert_path) {
1218 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1219 cfgerr |= ERR_ALERT | ERR_FATAL;
1220 goto error;
1221 }
1222
1223 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1224 char *slash;
1225
1226 slash = strrchr(cert_path, '/');
1227 if (!slash) {
1228 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1229 goto error;
1230 }
1231 /* temporary replace / by 0 to do an strcmp */
1232 *slash = '\0';
1233 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1234 *slash = '/';
1235 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1236 goto error;
1237 }
1238 *slash = '/';
1239 }
1240
1241 if (*cert_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +02001242 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > sizeof(path) ||
1243 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path) > sizeof(path)) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001244 memprintf(&err, "'%s' : path too long", cert_path);
1245 cfgerr |= ERR_ALERT | ERR_FATAL;
1246 goto error;
1247 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001248 cert_path = path;
1249 }
1250
1251 store = ckchs_lookup(cert_path);
1252 if (store == NULL) {
1253 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1254 goto error;
1255 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001256 if (store->ckch == NULL || store->ckch->cert == NULL) {
1257 memprintf(&err, "certificate '%s' is empty!", cert_path);
1258 goto error;
1259 }
1260
1261 /* check if it's possible to insert this new crtlist_entry */
1262 entry->node.key = store;
1263 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1264 if (inserted != &entry->node) {
1265 memprintf(&err, "file already exists in this directory!");
1266 goto error;
1267 }
1268
1269 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1270 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1271 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1272 goto error;
1273 }
1274
Willy Tarreau2b718102021-04-21 07:32:39 +02001275 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001276 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001277 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001278
1279 appctx->st2 = SETCERT_ST_INIT;
1280 appctx->ctx.cli.p0 = crtlist;
1281 appctx->ctx.cli.p1 = entry;
1282
1283 /* unlock is done in the release handler */
1284 return 0;
1285
1286error:
1287 crtlist_entry_free(entry);
1288 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1289 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1290 return cli_dynerr(appctx, err);
1291}
1292
1293/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1294static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1295{
1296 struct ckch_store *store;
1297 char *err = NULL;
1298 char *crtlist_path, *cert_path;
1299 struct ebmb_node *ebmb;
1300 struct ebpt_node *ebpt;
1301 struct crtlist *crtlist;
1302 struct crtlist_entry *entry = NULL;
1303 struct ckch_inst *inst, *inst_s;
1304 int linenum = 0;
1305 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001306 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001307 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001308
1309 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1310 return 1;
1311
1312 if (!*args[3] || !*args[4])
1313 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1314
1315 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1316 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1317
1318 crtlist_path = args[3];
1319 cert_path = args[4];
1320
1321 colons = strchr(cert_path, ':');
1322 if (colons) {
1323 char *endptr;
1324
1325 linenum = strtol(colons + 1, &endptr, 10);
1326 if (colons + 1 == endptr || *endptr != '\0') {
1327 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1328 goto error;
1329 }
1330 *colons = '\0';
1331 }
William Lallemand99cc2182020-06-25 15:19:51 +02001332
1333 /* strip trailing slashes, including first one */
1334 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1335 *end = 0;
1336
William Lallemandc756bbd2020-05-13 17:23:59 +02001337 /* look for crtlist */
1338 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1339 if (!ebmb) {
1340 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1341 goto error;
1342 }
1343 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1344
1345 /* look for store */
1346 store = ckchs_lookup(cert_path);
1347 if (store == NULL) {
1348 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1349 goto error;
1350 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001351 if (store->ckch == NULL || store->ckch->cert == NULL) {
1352 memprintf(&err, "certificate '%s' is empty!", cert_path);
1353 goto error;
1354 }
1355
1356 ebpt = ebpt_lookup(&crtlist->entries, store);
1357 if (!ebpt) {
1358 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1359 goto error;
1360 }
1361
1362 /* list the line number of entries for errors in err, and select the right ebpt */
1363 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1364 struct crtlist_entry *tmp;
1365
1366 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1367 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1368
1369 /* select the entry we wanted */
1370 if (linenum == 0 || tmp->linenum == linenum) {
1371 if (!entry)
1372 entry = tmp;
1373 }
1374 }
1375
1376 /* we didn't found the specified entry */
1377 if (!entry) {
1378 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);
1379 goto error;
1380 }
1381
1382 /* we didn't specified a line number but there were several entries */
1383 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1384 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1385 goto error;
1386 }
1387
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001388 /* Iterate over all the instances in order to see if any of them is a
1389 * default instance. If this is the case, the entry won't be suppressed. */
1390 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1391 if (inst->is_default && !inst->bind_conf->strict_sni) {
1392 if (!error_message_dumped) {
1393 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1394 error_message_dumped = 1;
1395 }
1396 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1397 }
1398 }
1399 if (error_message_dumped)
1400 goto error;
1401
William Lallemandc756bbd2020-05-13 17:23:59 +02001402 /* upon error free the ckch_inst and everything inside */
1403
1404 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001405 LIST_DELETE(&entry->by_crtlist);
1406 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001407
1408 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1409 struct sni_ctx *sni, *sni_s;
1410
1411 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1412 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1413 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001414 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001415 SSL_CTX_free(sni->ctx);
1416 free(sni);
1417 }
1418 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001419 LIST_DELETE(&inst->by_ckchs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001420 free(inst);
1421 }
1422
1423 crtlist_free_filters(entry->filters);
1424 ssl_sock_free_ssl_conf(entry->ssl_conf);
1425 free(entry->ssl_conf);
1426 free(entry);
1427
1428 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1429 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1430 return cli_dynmsg(appctx, LOG_NOTICE, err);
1431
1432error:
1433 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1434 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1435 return cli_dynerr(appctx, err);
1436}
1437
1438
William Lallemandee8530c2020-06-23 18:19:42 +02001439/* unlink and free all crt-list and crt-list entries */
1440void crtlist_deinit()
1441{
1442 struct eb_node *node, *next;
1443 struct crtlist *crtlist;
1444
1445 node = eb_first(&crtlists_tree);
1446 while (node) {
1447 next = eb_next(node);
1448 crtlist = ebmb_entry(node, struct crtlist, node);
1449 crtlist_free(crtlist);
1450 node = next;
1451 }
1452}
1453
William Lallemandc756bbd2020-05-13 17:23:59 +02001454
1455/* register cli keywords */
1456static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001457 { { "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 },
1458 { { "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 },
1459 { { "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 +02001460 { { NULL }, NULL, NULL, NULL } }
1461};
1462
1463INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1464