blob: 9ce7115e881645e1ff2e0d399adee67f6d589e18 [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);
180 LIST_DEL(&entry->by_crtlist);
181 LIST_DEL(&entry->by_ckch_store);
182 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
243 /* initialize the nodes so we can LIST_DEL in any cases */
244 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 */
305int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err)
306{
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 }
389 cur_arg = ssl_b ? ssl_b : 1;
390 while (cur_arg < ssl_e) {
391 newarg = 0;
392 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
393 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
394 newarg = 1;
395 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, err);
396 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200397 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
398 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200399 cfgerr |= ERR_ALERT | ERR_FATAL;
400 goto error;
401 }
402 cur_arg += 1 + ssl_bind_kws[i].skip;
403 break;
404 }
405 }
406 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200407 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
408 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200409 cfgerr |= ERR_ALERT | ERR_FATAL;
410 goto error;
411 }
412 }
413 entry->linenum = linenum;
414 entry->ssl_conf = ssl_conf;
415 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
416 entry->fcount = arg - cur_arg - 1;
417
418 return cfgerr;
419
420error:
421 crtlist_free_filters(entry->filters);
422 entry->filters = NULL;
423 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100424 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200425 return cfgerr;
426}
427
428
429
430/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
431 * Fill the <crtlist> argument with a pointer to a new crtlist struct
432 *
433 * This function tries to open and store certificate files.
434 */
435int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
436{
437 struct crtlist *newlist;
438 struct crtlist_entry *entry = NULL;
439 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200440 FILE *f;
441 struct stat buf;
442 int linenum = 0;
443 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200444 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200445
446 if ((f = fopen(file, "r")) == NULL) {
447 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
448 return ERR_ALERT | ERR_FATAL;
449 }
450
451 newlist = crtlist_new(file, 0);
452 if (newlist == NULL) {
453 memprintf(err, "Not enough memory!");
454 cfgerr |= ERR_ALERT | ERR_FATAL;
455 goto error;
456 }
457
458 while (fgets(thisline, sizeof(thisline), f) != NULL) {
459 char *end;
460 char *line = thisline;
461 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100462 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200463 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100464 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200465
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200466 if (missing_lf != -1) {
467 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
468 file, linenum, (missing_lf + 1));
469 cfgerr |= ERR_ALERT | ERR_FATAL;
470 missing_lf = -1;
471 break;
472 }
473
William Lallemand6e9556b2020-05-12 17:52:44 +0200474 linenum++;
475 end = line + strlen(line);
476 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
477 /* Check if we reached the limit and the last char is not \n.
478 * Watch out for the last line without the terminating '\n'!
479 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200480 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
481 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200482 cfgerr |= ERR_ALERT | ERR_FATAL;
483 break;
484 }
485
486 if (*line == '#' || *line == '\n' || *line == '\r')
487 continue;
488
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200489 if (end > line && *(end-1) == '\n') {
490 /* kill trailing LF */
491 *(end - 1) = 0;
492 }
493 else {
494 /* mark this line as truncated */
495 missing_lf = end - line;
496 }
497
William Lallemand6e9556b2020-05-12 17:52:44 +0200498 entry = crtlist_entry_new();
499 if (entry == NULL) {
500 memprintf(err, "Not enough memory!");
501 cfgerr |= ERR_ALERT | ERR_FATAL;
502 goto error;
503 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200504
William Lallemand6e9556b2020-05-12 17:52:44 +0200505 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200506 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200507 goto error;
508
509 /* empty line */
510 if (!crt_path || !*crt_path) {
511 crtlist_entry_free(entry);
512 entry = NULL;
513 continue;
514 }
515
516 if (*crt_path != '/' && global_ssl.crt_base) {
517 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200518 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
519 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200520 cfgerr |= ERR_ALERT | ERR_FATAL;
521 goto error;
522 }
523 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
524 crt_path = path;
525 }
526
527 /* Look for a ckch_store or create one */
528 ckchs = ckchs_lookup(crt_path);
529 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200530 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100531 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200532
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200533 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200534 if (ckchs == NULL) {
535 cfgerr |= ERR_ALERT | ERR_FATAL;
536 goto error;
537 }
538
539 entry->node.key = ckchs;
540 entry->crtlist = newlist;
541 ebpt_insert(&newlist->entries, &entry->node);
542 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
543 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200544
William Lallemand73404572020-11-20 18:23:40 +0100545 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200546 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200547 bundle, since 2.3 we don't support multiple
548 certificate in the same OpenSSL store, so we
549 emulate it by loading each file separately. To
550 do so we need to duplicate the entry in the
551 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200552 char fp[MAXPATHLEN+1] = {0};
553 int n = 0;
554 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
555 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
556 struct stat buf;
557 int ret;
558
William Lallemand86c2dd62020-11-20 14:23:38 +0100559 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200560 if (ret > sizeof(fp))
561 continue;
562
563 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100564 if (!ckchs) {
565 if (stat(fp, &buf) == 0) {
566 ckchs = ckchs_load_cert_file(fp, err);
567 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200568 cfgerr |= ERR_ALERT | ERR_FATAL;
569 goto error;
570 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100571 } else {
572 continue; /* didn't find this extension, skip */
573 }
574 }
575 found++;
576 linenum++; /* we duplicate the line for this entry in the bundle */
577 if (!entry_dup) { /* if the entry was used, duplicate one */
578 linenum++;
579 entry_dup = crtlist_entry_dup(entry);
580 if (!entry_dup) {
581 cfgerr |= ERR_ALERT | ERR_FATAL;
582 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200583 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100584 entry_dup->linenum = linenum;
585 }
William Lallemand47da8212020-09-10 19:13:27 +0200586
William Lallemand77e1c6f2020-11-20 18:26:09 +0100587 entry_dup->node.key = ckchs;
588 entry_dup->crtlist = newlist;
589 ebpt_insert(&newlist->entries, &entry_dup->node);
590 LIST_ADDQ(&newlist->ord_entries, &entry_dup->by_crtlist);
591 LIST_ADDQ(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200592
William Lallemand77e1c6f2020-11-20 18:26:09 +0100593 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200594 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100595#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
596 if (found) {
597 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
598 err && *err ? *err : "", crt_path);
599 cfgerr |= ERR_ALERT | ERR_FATAL;
600 }
601#endif
William Lallemand47da8212020-09-10 19:13:27 +0200602 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100603 if (!found) {
604 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
605 err && *err ? *err : "", crt_path, strerror(errno));
606 cfgerr |= ERR_ALERT | ERR_FATAL;
607 }
608
William Lallemand50c03aa2020-11-06 16:24:07 +0100609 } else {
610 entry->node.key = ckchs;
611 entry->crtlist = newlist;
612 ebpt_insert(&newlist->entries, &entry->node);
613 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
614 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100615 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200616 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200617 entry = NULL;
618 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200619
620 if (missing_lf != -1) {
621 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
622 file, linenum, (missing_lf + 1));
623 cfgerr |= ERR_ALERT | ERR_FATAL;
624 }
625
William Lallemand6e9556b2020-05-12 17:52:44 +0200626 if (cfgerr & ERR_CODE)
627 goto error;
628
629 newlist->linecount = linenum;
630
631 fclose(f);
632 *crtlist = newlist;
633
634 return cfgerr;
635error:
636 crtlist_entry_free(entry);
637
638 fclose(f);
639 crtlist_free(newlist);
640 return cfgerr;
641}
642
643/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
644 * Fill the <crtlist> argument with a pointer to a new crtlist struct
645 *
646 * This function tries to open and store certificate files.
647 */
648int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
649{
650 struct crtlist *dir;
651 struct dirent **de_list;
652 int i, n;
653 struct stat buf;
654 char *end;
655 char fp[MAXPATHLEN+1];
656 int cfgerr = 0;
657 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200658
659 dir = crtlist_new(path, 1);
660 if (dir == NULL) {
661 memprintf(err, "not enough memory");
662 return ERR_ALERT | ERR_FATAL;
663 }
664
665 n = scandir(path, &de_list, 0, alphasort);
666 if (n < 0) {
667 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
668 err && *err ? *err : "", path, strerror(errno));
669 cfgerr |= ERR_ALERT | ERR_FATAL;
670 }
671 else {
672 for (i = 0; i < n; i++) {
673 struct crtlist_entry *entry;
674 struct dirent *de = de_list[i];
675
676 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100677 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 +0200678 goto ignore_entry;
679
680 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
681 if (stat(fp, &buf) != 0) {
682 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
683 err && *err ? *err : "", fp, strerror(errno));
684 cfgerr |= ERR_ALERT | ERR_FATAL;
685 goto ignore_entry;
686 }
687 if (!S_ISREG(buf.st_mode))
688 goto ignore_entry;
689
690 entry = crtlist_entry_new();
691 if (entry == NULL) {
692 memprintf(err, "not enough memory '%s'", fp);
693 cfgerr |= ERR_ALERT | ERR_FATAL;
694 goto ignore_entry;
695 }
696
William Lallemand6e9556b2020-05-12 17:52:44 +0200697 ckchs = ckchs_lookup(fp);
698 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200699 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200700 if (ckchs == NULL) {
701 free(de);
702 free(entry);
703 cfgerr |= ERR_ALERT | ERR_FATAL;
704 goto end;
705 }
706 entry->node.key = ckchs;
707 entry->crtlist = dir;
708 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
709 LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist);
710 ebpt_insert(&dir->entries, &entry->node);
711
712ignore_entry:
713 free(de);
714 }
715end:
716 free(de_list);
717 }
718
719 if (cfgerr & ERR_CODE) {
720 /* free the dir and entries on error */
721 crtlist_free(dir);
722 } else {
723 *crtlist = dir;
724 }
725 return cfgerr;
726
727}
728
William Lallemandc756bbd2020-05-13 17:23:59 +0200729/*
730 * Take an ssl_bind_conf structure and append the configuration line used to
731 * create it in the buffer
732 */
733static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
734{
735 int space = 0;
736
737 if (conf == NULL)
738 return;
739
740 chunk_appendf(buf, " [");
741#ifdef OPENSSL_NPN_NEGOTIATED
742 if (conf->npn_str) {
743 int len = conf->npn_len;
744 char *ptr = conf->npn_str;
745 int comma = 0;
746
747 if (space) chunk_appendf(buf, " ");
748 chunk_appendf(buf, "npn ");
749 while (len) {
750 unsigned short size;
751
752 size = *ptr;
753 ptr++;
754 if (comma)
755 chunk_memcat(buf, ",", 1);
756 chunk_memcat(buf, ptr, size);
757 ptr += size;
758 len -= size + 1;
759 comma = 1;
760 }
761 chunk_memcat(buf, "", 1); /* finish with a \0 */
762 space++;
763 }
764#endif
765#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
766 if (conf->alpn_str) {
767 int len = conf->alpn_len;
768 char *ptr = conf->alpn_str;
769 int comma = 0;
770
771 if (space) chunk_appendf(buf, " ");
772 chunk_appendf(buf, "alpn ");
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 /* verify */
790 {
791 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
792 if (space) chunk_appendf(buf, " ");
793 chunk_appendf(buf, "verify none");
794 space++;
795 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
796 if (space) chunk_appendf(buf, " ");
797 chunk_appendf(buf, "verify optional");
798 space++;
799 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
800 if (space) chunk_appendf(buf, " ");
801 chunk_appendf(buf, "verify required");
802 space++;
803 }
804 }
805
806 if (conf->no_ca_names) {
807 if (space) chunk_appendf(buf, " ");
808 chunk_appendf(buf, "no-ca-names");
809 space++;
810 }
811
812 if (conf->early_data) {
813 if (space) chunk_appendf(buf, " ");
814 chunk_appendf(buf, "allow-0rtt");
815 space++;
816 }
817 if (conf->ca_file) {
818 if (space) chunk_appendf(buf, " ");
819 chunk_appendf(buf, "ca-file %s", conf->ca_file);
820 space++;
821 }
822 if (conf->crl_file) {
823 if (space) chunk_appendf(buf, " ");
824 chunk_appendf(buf, "crl-file %s", conf->crl_file);
825 space++;
826 }
827 if (conf->ciphers) {
828 if (space) chunk_appendf(buf, " ");
829 chunk_appendf(buf, "ciphers %s", conf->ciphers);
830 space++;
831 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500832#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200833 if (conf->ciphersuites) {
834 if (space) chunk_appendf(buf, " ");
835 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
836 space++;
837 }
838#endif
839 if (conf->curves) {
840 if (space) chunk_appendf(buf, " ");
841 chunk_appendf(buf, "curves %s", conf->curves);
842 space++;
843 }
844 if (conf->ecdhe) {
845 if (space) chunk_appendf(buf, " ");
846 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
847 space++;
848 }
849
850 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200851 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200852 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200853 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200854 space++;
855 }
856
William Lallemand8177ad92020-05-20 16:49:02 +0200857 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200858 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200859 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200860 space++;
861 }
862
863 chunk_appendf(buf, "]");
864
865 return;
866}
867
868/* dump a list of filters */
869static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
870{
871 int i;
872
873 if (!entry->fcount)
874 return;
875
876 for (i = 0; i < entry->fcount; i++) {
877 chunk_appendf(buf, " %s", entry->filters[i]);
878 }
879 return;
880}
881
882/************************** CLI functions ****************************/
883
884
885/* CLI IO handler for '(show|dump) ssl crt-list' */
886static int cli_io_handler_dump_crtlist(struct appctx *appctx)
887{
888 struct buffer *trash = alloc_trash_chunk();
889 struct stream_interface *si = appctx->owner;
890 struct ebmb_node *lnode;
891
892 if (trash == NULL)
893 return 1;
894
895 /* dump the list of crt-lists */
896 lnode = appctx->ctx.cli.p1;
897 if (lnode == NULL)
898 lnode = ebmb_first(&crtlists_tree);
899 while (lnode) {
900 chunk_appendf(trash, "%s\n", lnode->key);
901 if (ci_putchk(si_ic(si), trash) == -1) {
902 si_rx_room_blk(si);
903 goto yield;
904 }
905 lnode = ebmb_next(lnode);
906 }
907 free_trash_chunk(trash);
908 return 1;
909yield:
910 appctx->ctx.cli.p1 = lnode;
911 free_trash_chunk(trash);
912 return 0;
913}
914
915/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
916static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
917{
918 struct buffer *trash = alloc_trash_chunk();
919 struct crtlist *crtlist;
920 struct stream_interface *si = appctx->owner;
921 struct crtlist_entry *entry;
922
923 if (trash == NULL)
924 return 1;
925
926 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
927
928 entry = appctx->ctx.cli.p1;
929 if (entry == NULL) {
930 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
931 chunk_appendf(trash, "# %s\n", crtlist->node.key);
932 if (ci_putchk(si_ic(si), trash) == -1) {
933 si_rx_room_blk(si);
934 goto yield;
935 }
936 }
937
938 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
939 struct ckch_store *store;
940 const char *filename;
941
942 store = entry->node.key;
943 filename = store->path;
944 chunk_appendf(trash, "%s", filename);
945 if (appctx->ctx.cli.i0 == 's') /* show */
946 chunk_appendf(trash, ":%d", entry->linenum);
947 dump_crtlist_sslconf(trash, entry->ssl_conf);
948 dump_crtlist_filters(trash, entry);
949 chunk_appendf(trash, "\n");
950
951 if (ci_putchk(si_ic(si), trash) == -1) {
952 si_rx_room_blk(si);
953 goto yield;
954 }
955 }
956 free_trash_chunk(trash);
957 return 1;
958yield:
959 appctx->ctx.cli.p1 = entry;
960 free_trash_chunk(trash);
961 return 0;
962}
963
964/* CLI argument parser for '(show|dump) ssl crt-list' */
965static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
966{
967 struct ebmb_node *lnode;
968 char *filename = NULL;
969 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200970 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200971
972 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
973 return 1;
974
975 appctx->ctx.cli.p0 = NULL;
976 appctx->ctx.cli.p1 = NULL;
977
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100978 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200979 mode = 's';
980 filename = args[4];
981 } else {
982 mode = 'd';
983 filename = args[3];
984 }
985
986 if (mode == 's' && !*args[4])
987 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
988
989 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200990
991
992 /* strip trailing slashes, including first one */
993 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
994 *end = 0;
995
William Lallemandc756bbd2020-05-13 17:23:59 +0200996 lnode = ebst_lookup(&crtlists_tree, filename);
997 if (lnode == NULL)
998 return cli_err(appctx, "didn't find the specified filename\n");
999
1000 appctx->ctx.cli.p0 = lnode;
1001 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1002 }
1003 appctx->ctx.cli.i0 = mode;
1004
1005 return 0;
1006}
1007
1008/* release function of the "add ssl crt-list' command, free things and unlock
1009 the spinlock */
1010static void cli_release_add_crtlist(struct appctx *appctx)
1011{
1012 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1013
1014 if (appctx->st2 != SETCERT_ST_FIN) {
1015 struct ckch_inst *inst, *inst_s;
1016 /* upon error free the ckch_inst and everything inside */
1017 ebpt_delete(&entry->node);
1018 LIST_DEL(&entry->by_crtlist);
1019 LIST_DEL(&entry->by_ckch_store);
1020
1021 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1022 ckch_inst_free(inst);
1023 }
1024 crtlist_free_filters(entry->filters);
1025 ssl_sock_free_ssl_conf(entry->ssl_conf);
1026 free(entry->ssl_conf);
1027 free(entry);
1028 }
1029
1030 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1031}
1032
1033
1034/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1035 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1036 *
1037 * The logic is the same as the "commit ssl cert" command but without the
1038 * freeing of the old structures, because there are none.
1039 */
1040static int cli_io_handler_add_crtlist(struct appctx *appctx)
1041{
1042 struct bind_conf_list *bind_conf_node;
1043 struct stream_interface *si = appctx->owner;
1044 struct crtlist *crtlist = appctx->ctx.cli.p0;
1045 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1046 struct ckch_store *store = entry->node.key;
1047 struct buffer *trash = alloc_trash_chunk();
1048 struct ckch_inst *new_inst;
1049 char *err = NULL;
1050 int i = 0;
1051 int errcode = 0;
1052
1053 if (trash == NULL)
1054 goto error;
1055
1056 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1057 * created.
1058 */
1059 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1060 goto error;
1061
1062 while (1) {
1063 switch (appctx->st2) {
1064 case SETCERT_ST_INIT:
1065 /* This state just print the update message */
1066 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1067 if (ci_putchk(si_ic(si), trash) == -1) {
1068 si_rx_room_blk(si);
1069 goto yield;
1070 }
1071 appctx->st2 = SETCERT_ST_GEN;
1072 /* fallthrough */
1073 case SETCERT_ST_GEN:
1074 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1075 if (bind_conf_node == NULL)
1076 bind_conf_node = crtlist->bind_conf;
1077 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1078 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1079 struct sni_ctx *sni;
1080
1081 /* yield every 10 generations */
1082 if (i > 10) {
1083 appctx->ctx.cli.p2 = bind_conf_node;
1084 goto yield;
1085 }
1086
1087 /* we don't support multi-cert bundles, only simple ones */
1088 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1089 if (errcode & ERR_CODE)
1090 goto error;
1091
1092 /* we need to initialize the SSL_CTX generated */
1093 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1094 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1095 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1096 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1097 if (errcode & ERR_CODE)
1098 goto error;
1099 }
1100 }
1101 /* display one dot for each new instance */
1102 chunk_appendf(trash, ".");
1103 i++;
1104 LIST_ADDQ(&store->ckch_inst, &new_inst->by_ckchs);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001105 LIST_ADDQ(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1106 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001107 }
1108 appctx->st2 = SETCERT_ST_INSERT;
1109 /* fallthrough */
1110 case SETCERT_ST_INSERT:
1111 /* insert SNIs in bind_conf */
1112 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1113 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1114 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1115 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1116 }
1117 entry->linenum = ++crtlist->linecount;
1118 appctx->st2 = SETCERT_ST_FIN;
1119 goto end;
1120 }
1121 }
1122
1123end:
1124 chunk_appendf(trash, "\n");
1125 if (errcode & ERR_WARN)
1126 chunk_appendf(trash, "%s", err);
1127 chunk_appendf(trash, "Success!\n");
1128 if (ci_putchk(si_ic(si), trash) == -1)
1129 si_rx_room_blk(si);
1130 free_trash_chunk(trash);
1131 /* success: call the release function and don't come back */
1132 return 1;
1133yield:
1134 /* store the state */
1135 if (ci_putchk(si_ic(si), trash) == -1)
1136 si_rx_room_blk(si);
1137 free_trash_chunk(trash);
1138 si_rx_endp_more(si); /* let's come back later */
1139 return 0; /* should come back */
1140
1141error:
1142 /* spin unlock and free are done in the release function */
1143 if (trash) {
1144 chunk_appendf(trash, "\n%sFailed!\n", err);
1145 if (ci_putchk(si_ic(si), trash) == -1)
1146 si_rx_room_blk(si);
1147 free_trash_chunk(trash);
1148 }
1149 /* error: call the release function and don't come back */
1150 return 1;
1151}
1152
1153
1154/*
1155 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1156 * Filters and option must be passed through payload:
1157 */
1158static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1159{
1160 int cfgerr = 0;
1161 struct ckch_store *store;
1162 char *err = NULL;
1163 char path[MAXPATHLEN+1];
1164 char *crtlist_path;
1165 char *cert_path = NULL;
1166 struct ebmb_node *eb;
1167 struct ebpt_node *inserted;
1168 struct crtlist *crtlist;
1169 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001170 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001171
1172 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1173 return 1;
1174
1175 if (!*args[3] || (!payload && !*args[4]))
1176 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1177
1178 crtlist_path = args[3];
1179
William Lallemand99cc2182020-06-25 15:19:51 +02001180 /* strip trailing slashes, including first one */
1181 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1182 *end = 0;
1183
William Lallemandc756bbd2020-05-13 17:23:59 +02001184 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1185 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1186
1187 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1188 if (!eb) {
1189 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1190 goto error;
1191 }
1192 crtlist = ebmb_entry(eb, struct crtlist, node);
1193
1194 entry = crtlist_entry_new();
1195 if (entry == NULL) {
1196 memprintf(&err, "Not enough memory!");
1197 goto error;
1198 }
1199
1200 if (payload) {
1201 char *lf;
1202
1203 lf = strrchr(payload, '\n');
1204 if (lf) {
1205 memprintf(&err, "only one line of payload is supported!");
1206 goto error;
1207 }
1208 /* cert_path is filled here */
1209 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, &err);
1210 if (cfgerr & ERR_CODE)
1211 goto error;
1212 } else {
1213 cert_path = args[4];
1214 }
1215
1216 if (!cert_path) {
1217 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1218 cfgerr |= ERR_ALERT | ERR_FATAL;
1219 goto error;
1220 }
1221
1222 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1223 char *slash;
1224
1225 slash = strrchr(cert_path, '/');
1226 if (!slash) {
1227 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1228 goto error;
1229 }
1230 /* temporary replace / by 0 to do an strcmp */
1231 *slash = '\0';
1232 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1233 *slash = '/';
1234 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1235 goto error;
1236 }
1237 *slash = '/';
1238 }
1239
1240 if (*cert_path != '/' && global_ssl.crt_base) {
1241 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1242 memprintf(&err, "'%s' : path too long", cert_path);
1243 cfgerr |= ERR_ALERT | ERR_FATAL;
1244 goto error;
1245 }
1246 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1247 cert_path = path;
1248 }
1249
1250 store = ckchs_lookup(cert_path);
1251 if (store == NULL) {
1252 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1253 goto error;
1254 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001255 if (store->ckch == NULL || store->ckch->cert == NULL) {
1256 memprintf(&err, "certificate '%s' is empty!", cert_path);
1257 goto error;
1258 }
1259
1260 /* check if it's possible to insert this new crtlist_entry */
1261 entry->node.key = store;
1262 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1263 if (inserted != &entry->node) {
1264 memprintf(&err, "file already exists in this directory!");
1265 goto error;
1266 }
1267
1268 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1269 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1270 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1271 goto error;
1272 }
1273
1274 LIST_ADDQ(&crtlist->ord_entries, &entry->by_crtlist);
1275 entry->crtlist = crtlist;
1276 LIST_ADDQ(&store->crtlist_entry, &entry->by_ckch_store);
1277
1278 appctx->st2 = SETCERT_ST_INIT;
1279 appctx->ctx.cli.p0 = crtlist;
1280 appctx->ctx.cli.p1 = entry;
1281
1282 /* unlock is done in the release handler */
1283 return 0;
1284
1285error:
1286 crtlist_entry_free(entry);
1287 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1288 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1289 return cli_dynerr(appctx, err);
1290}
1291
1292/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1293static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1294{
1295 struct ckch_store *store;
1296 char *err = NULL;
1297 char *crtlist_path, *cert_path;
1298 struct ebmb_node *ebmb;
1299 struct ebpt_node *ebpt;
1300 struct crtlist *crtlist;
1301 struct crtlist_entry *entry = NULL;
1302 struct ckch_inst *inst, *inst_s;
1303 int linenum = 0;
1304 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001305 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001306
1307 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1308 return 1;
1309
1310 if (!*args[3] || !*args[4])
1311 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1312
1313 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1314 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1315
1316 crtlist_path = args[3];
1317 cert_path = args[4];
1318
1319 colons = strchr(cert_path, ':');
1320 if (colons) {
1321 char *endptr;
1322
1323 linenum = strtol(colons + 1, &endptr, 10);
1324 if (colons + 1 == endptr || *endptr != '\0') {
1325 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1326 goto error;
1327 }
1328 *colons = '\0';
1329 }
William Lallemand99cc2182020-06-25 15:19:51 +02001330
1331 /* strip trailing slashes, including first one */
1332 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1333 *end = 0;
1334
William Lallemandc756bbd2020-05-13 17:23:59 +02001335 /* look for crtlist */
1336 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1337 if (!ebmb) {
1338 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1339 goto error;
1340 }
1341 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1342
1343 /* look for store */
1344 store = ckchs_lookup(cert_path);
1345 if (store == NULL) {
1346 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1347 goto error;
1348 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001349 if (store->ckch == NULL || store->ckch->cert == NULL) {
1350 memprintf(&err, "certificate '%s' is empty!", cert_path);
1351 goto error;
1352 }
1353
1354 ebpt = ebpt_lookup(&crtlist->entries, store);
1355 if (!ebpt) {
1356 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1357 goto error;
1358 }
1359
1360 /* list the line number of entries for errors in err, and select the right ebpt */
1361 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1362 struct crtlist_entry *tmp;
1363
1364 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1365 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1366
1367 /* select the entry we wanted */
1368 if (linenum == 0 || tmp->linenum == linenum) {
1369 if (!entry)
1370 entry = tmp;
1371 }
1372 }
1373
1374 /* we didn't found the specified entry */
1375 if (!entry) {
1376 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);
1377 goto error;
1378 }
1379
1380 /* we didn't specified a line number but there were several entries */
1381 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1382 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1383 goto error;
1384 }
1385
1386 /* upon error free the ckch_inst and everything inside */
1387
1388 ebpt_delete(&entry->node);
1389 LIST_DEL(&entry->by_crtlist);
1390 LIST_DEL(&entry->by_ckch_store);
1391
1392 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1393 struct sni_ctx *sni, *sni_s;
1394
1395 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1396 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1397 ebmb_delete(&sni->name);
1398 LIST_DEL(&sni->by_ckch_inst);
1399 SSL_CTX_free(sni->ctx);
1400 free(sni);
1401 }
1402 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1403 LIST_DEL(&inst->by_ckchs);
1404 free(inst);
1405 }
1406
1407 crtlist_free_filters(entry->filters);
1408 ssl_sock_free_ssl_conf(entry->ssl_conf);
1409 free(entry->ssl_conf);
1410 free(entry);
1411
1412 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1413 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1414 return cli_dynmsg(appctx, LOG_NOTICE, err);
1415
1416error:
1417 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1418 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1419 return cli_dynerr(appctx, err);
1420}
1421
1422
William Lallemandee8530c2020-06-23 18:19:42 +02001423/* unlink and free all crt-list and crt-list entries */
1424void crtlist_deinit()
1425{
1426 struct eb_node *node, *next;
1427 struct crtlist *crtlist;
1428
1429 node = eb_first(&crtlists_tree);
1430 while (node) {
1431 next = eb_next(node);
1432 crtlist = ebmb_entry(node, struct crtlist, node);
1433 crtlist_free(crtlist);
1434 node = next;
1435 }
1436}
1437
William Lallemandc756bbd2020-05-13 17:23:59 +02001438
1439/* register cli keywords */
1440static struct cli_kw_list cli_kws = {{ },{
1441 { { "add", "ssl", "crt-list", NULL }, "add ssl crt-list <filename> <certfile> [options] : add a line <certfile> to a crt-list <filename>", cli_parse_add_crtlist, cli_io_handler_add_crtlist, cli_release_add_crtlist },
1442 { { "del", "ssl", "crt-list", NULL }, "del ssl crt-list <filename> <certfile[:line]> : delete a line <certfile> in a crt-list <filename>", cli_parse_del_crtlist, NULL, NULL },
1443 { { "show", "ssl", "crt-list", NULL }, "show ssl crt-list [-n] [<filename>] : show the list of crt-lists or the content of a crt-list <filename>", cli_parse_dump_crtlist, cli_io_handler_dump_crtlist, NULL },
1444 { { NULL }, NULL, NULL, NULL } }
1445};
1446
1447INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1448