blob: 65ca2891d229db540f2b36bd3b5524af8999e739 [file] [log] [blame]
William Lallemand6e9556b2020-05-12 17:52:44 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
Willy Tarreaub2551052020-06-09 09:07:15 +020011#include <sys/stat.h>
12#include <sys/types.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020013
Willy Tarreaub2551052020-06-09 09:07:15 +020014#include <dirent.h>
William Lallemand212e9932020-05-18 08:33:09 +020015#include <errno.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020016#include <stdlib.h>
17#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020018#include <syslog.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020019
20#include <import/ebpttree.h>
21#include <import/ebsttree.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020022
Willy Tarreauf1d32c42020-06-04 21:07:02 +020023#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020024#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020025#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020026#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020027#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020028#include <haproxy/ssl_sock.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020029#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020030#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020031
William Lallemand6e9556b2020-05-12 17:52:44 +020032
William Lallemand6e9556b2020-05-12 17:52:44 +020033/* release ssl bind conf */
34void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
35{
36 if (conf) {
37#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
38 free(conf->npn_str);
39 conf->npn_str = NULL;
40#endif
41#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
42 free(conf->alpn_str);
43 conf->alpn_str = NULL;
44#endif
45 free(conf->ca_file);
46 conf->ca_file = NULL;
47 free(conf->ca_verify_file);
48 conf->ca_verify_file = NULL;
49 free(conf->crl_file);
50 conf->crl_file = NULL;
51 free(conf->ciphers);
52 conf->ciphers = NULL;
53#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
54 free(conf->ciphersuites);
55 conf->ciphersuites = NULL;
56#endif
57 free(conf->curves);
58 conf->curves = NULL;
59 free(conf->ecdhe);
60 conf->ecdhe = NULL;
61 }
62}
63
William Lallemand82f2d2f2020-09-10 19:06:43 +020064/*
65 * Allocate and copy a ssl_bind_conf structure
66 */
67struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
68{
69 struct ssl_bind_conf *dst;
70
71 if (!src)
72 return NULL;
73
74 dst = calloc(1, sizeof(*dst));
75 if (!dst)
76 return NULL;
77
78#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
79 if (src->npn_str) {
80 dst->npn_str = strdup(src->npn_str);
81 if (!dst->npn_str)
82 goto error;
83 }
84#endif
85#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
86 if (src->alpn_str) {
87 dst->alpn_str = strdup(src->alpn_str);
88 if (!dst->alpn_str)
89 goto error;
90 }
91#endif
92 if (src->ca_file) {
93 dst->ca_file = strdup(src->ca_file);
94 if (!dst->ca_file)
95 goto error;
96 }
97 if (src->ca_verify_file) {
98 dst->ca_verify_file = strdup(src->ca_verify_file);
99 if (!dst->ca_verify_file)
100 goto error;
101 }
102 if (src->crl_file) {
103 dst->crl_file = strdup(src->crl_file);
104 if (!dst->crl_file)
105 goto error;
106 }
107 if (src->ciphers) {
108 dst->ciphers = strdup(src->ciphers);
109 if (!dst->ciphers)
110 goto error;
111 }
112#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)
113 if (src->ciphersuites) {
114 dst->ciphersuites = strdup(src->ciphersuites);
115 if (!dst->ciphersuites)
116 goto error;
117 }
118#endif
119 if (src->curves) {
120 dst->curves = strdup(src->curves);
121 if (!dst->curves)
122 goto error;
123 }
124 if (src->ecdhe) {
125 dst->ecdhe = strdup(src->ecdhe);
126 if (!dst->ecdhe)
127 goto error;
128 }
129 return dst;
130
131error:
132 ssl_sock_free_ssl_conf(dst);
133 free(dst);
134
135 return NULL;
136}
William Lallemand6e9556b2020-05-12 17:52:44 +0200137
138/* free sni filters */
139void crtlist_free_filters(char **args)
140{
141 int i;
142
143 if (!args)
144 return;
145
146 for (i = 0; args[i]; i++)
147 free(args[i]);
148
149 free(args);
150}
151
152/* Alloc and duplicate a char ** array */
153char **crtlist_dup_filters(char **args, int fcount)
154{
155 char **dst;
156 int i;
157
158 if (fcount == 0)
159 return NULL;
160
161 dst = calloc(fcount + 1, sizeof(*dst));
162 if (!dst)
163 return NULL;
164
165 for (i = 0; i < fcount; i++) {
166 dst[i] = strdup(args[i]);
167 if (!dst[i])
168 goto error;
169 }
170 return dst;
171
172error:
173 crtlist_free_filters(dst);
174 return NULL;
175}
176
177/*
178 * Detach and free a crtlist_entry.
179 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
180 */
181void crtlist_entry_free(struct crtlist_entry *entry)
182{
183 struct ckch_inst *inst, *inst_s;
184
185 if (entry == NULL)
186 return;
187
188 ebpt_delete(&entry->node);
189 LIST_DEL(&entry->by_crtlist);
190 LIST_DEL(&entry->by_ckch_store);
191 crtlist_free_filters(entry->filters);
192 ssl_sock_free_ssl_conf(entry->ssl_conf);
193 free(entry->ssl_conf);
194 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
195 ckch_inst_free(inst);
196 }
197 free(entry);
198}
William Lallemand5622c452020-09-10 19:08:49 +0200199/*
200 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
201 * Return a pointer to the new entry
202 */
203struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
204{
205 struct crtlist_entry *entry;
206
207 if (src == NULL)
208 return NULL;
209
210 entry = crtlist_entry_new();
211 if (entry == NULL)
212 return NULL;
213
214 if (src->filters) {
215 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
216 if (!entry->filters)
217 goto error;
218 }
219 entry->fcount = src->fcount;
220 if (src->ssl_conf) {
221 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
222 if (!entry->ssl_conf)
223 goto error;
224 }
225 entry->crtlist = src->crtlist;
226
227 return entry;
228
229error:
230
231 crtlist_free_filters(entry->filters);
232 ssl_sock_free_ssl_conf(entry->ssl_conf);
233 free(entry->ssl_conf);
234 free(entry);
235
236 return NULL;
237}
William Lallemand6e9556b2020-05-12 17:52:44 +0200238
239/*
240 * Allocate and initialize a crtlist_entry
241 */
242struct crtlist_entry *crtlist_entry_new()
243{
244 struct crtlist_entry *entry;
245
246 entry = calloc(1, sizeof(*entry));
247 if (entry == NULL)
248 return NULL;
249
250 LIST_INIT(&entry->ckch_inst);
251
252 /* initialize the nodes so we can LIST_DEL in any cases */
253 LIST_INIT(&entry->by_crtlist);
254 LIST_INIT(&entry->by_ckch_store);
255
256 return entry;
257}
258
259/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
260void crtlist_free(struct crtlist *crtlist)
261{
262 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200263 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200264
265 if (crtlist == NULL)
266 return;
267
William Lallemand6a3168a2020-06-23 11:43:35 +0200268 bind_conf_node = crtlist->bind_conf;
269 while (bind_conf_node) {
270 struct bind_conf_list *next = bind_conf_node->next;
271 free(bind_conf_node);
272 bind_conf_node = next;
273 }
274
William Lallemand6e9556b2020-05-12 17:52:44 +0200275 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
276 crtlist_entry_free(entry);
277 }
278 ebmb_delete(&crtlist->node);
279 free(crtlist);
280}
281
282/* Alloc and initialize a struct crtlist
283 * <filename> is the key of the ebmb_node
284 * <unique> initialize the list of entries to be unique (1) or not (0)
285 */
286struct crtlist *crtlist_new(const char *filename, int unique)
287{
288 struct crtlist *newlist;
289
290 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
291 if (newlist == NULL)
292 return NULL;
293
294 memcpy(newlist->node.key, filename, strlen(filename) + 1);
295 if (unique)
296 newlist->entries = EB_ROOT_UNIQUE;
297 else
298 newlist->entries = EB_ROOT;
299
300 LIST_INIT(&newlist->ord_entries);
301
302 return newlist;
303}
304
305/*
306 * Read a single crt-list line. /!\ alter the <line> string.
307 * Fill <crt_path> and <crtlist_entry>
308 * <crtlist_entry> must be alloc and free by the caller
309 * <crtlist_entry->ssl_conf> is alloc by the function
310 * <crtlist_entry->filters> is alloc by the function
311 * <crt_path> is a ptr in <line>
312 * Return an error code
313 */
314int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, char **err)
315{
316 int cfgerr = 0;
317 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
318 char *end;
319 char *args[MAX_CRT_ARGS + 1];
320 struct ssl_bind_conf *ssl_conf = NULL;
321
322 if (!line || !crt_path || !entry)
323 return ERR_ALERT | ERR_FATAL;
324
325 end = line + strlen(line);
326 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
327 /* Check if we reached the limit and the last char is not \n.
328 * Watch out for the last line without the terminating '\n'!
329 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200330 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
331 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200332 cfgerr |= ERR_ALERT | ERR_FATAL;
333 goto error;
334 }
335 arg = 0;
336 newarg = 1;
337 while (*line) {
338 if (isspace((unsigned char)*line)) {
339 newarg = 1;
340 *line = 0;
341 } else if (*line == '[') {
342 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200343 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200344 cfgerr |= ERR_ALERT | ERR_FATAL;
345 goto error;
346 }
347 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200348 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200349 cfgerr |= ERR_ALERT | ERR_FATAL;
350 goto error;
351 }
352 ssl_b = arg;
353 newarg = 1;
354 *line = 0;
355 } else if (*line == ']') {
356 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200357 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200358 cfgerr |= ERR_ALERT | ERR_FATAL;
359 goto error;
360 }
361 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200362 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200363 cfgerr |= ERR_ALERT | ERR_FATAL;
364 goto error;
365 }
366 ssl_e = arg;
367 newarg = 1;
368 *line = 0;
369 } else if (newarg) {
370 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200371 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200372 cfgerr |= ERR_ALERT | ERR_FATAL;
373 goto error;
374 }
375 newarg = 0;
376 args[arg++] = line;
377 }
378 line++;
379 }
380 args[arg++] = line;
381
382 /* empty line */
383 if (!*args[0]) {
384 cfgerr |= ERR_NONE;
385 goto error;
386 }
387
388 *crt_path = args[0];
389
390 if (ssl_b) {
391 ssl_conf = calloc(1, sizeof *ssl_conf);
392 if (!ssl_conf) {
393 memprintf(err, "not enough memory!");
394 cfgerr |= ERR_ALERT | ERR_FATAL;
395 goto error;
396 }
397 }
398 cur_arg = ssl_b ? ssl_b : 1;
399 while (cur_arg < ssl_e) {
400 newarg = 0;
401 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
402 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
403 newarg = 1;
404 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, err);
405 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200406 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
407 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200408 cfgerr |= ERR_ALERT | ERR_FATAL;
409 goto error;
410 }
411 cur_arg += 1 + ssl_bind_kws[i].skip;
412 break;
413 }
414 }
415 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200416 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
417 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200418 cfgerr |= ERR_ALERT | ERR_FATAL;
419 goto error;
420 }
421 }
422 entry->linenum = linenum;
423 entry->ssl_conf = ssl_conf;
424 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
425 entry->fcount = arg - cur_arg - 1;
426
427 return cfgerr;
428
429error:
430 crtlist_free_filters(entry->filters);
431 entry->filters = NULL;
432 ssl_sock_free_ssl_conf(entry->ssl_conf);
433 free(entry->ssl_conf);
434 entry->ssl_conf = NULL;
435 return cfgerr;
436}
437
438
439
440/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
441 * Fill the <crtlist> argument with a pointer to a new crtlist struct
442 *
443 * This function tries to open and store certificate files.
444 */
445int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
446{
447 struct crtlist *newlist;
448 struct crtlist_entry *entry = NULL;
449 char thisline[CRT_LINESIZE];
450 char path[MAXPATHLEN+1];
451 FILE *f;
452 struct stat buf;
453 int linenum = 0;
454 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200455 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200456
457 if ((f = fopen(file, "r")) == NULL) {
458 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
459 return ERR_ALERT | ERR_FATAL;
460 }
461
462 newlist = crtlist_new(file, 0);
463 if (newlist == NULL) {
464 memprintf(err, "Not enough memory!");
465 cfgerr |= ERR_ALERT | ERR_FATAL;
466 goto error;
467 }
468
469 while (fgets(thisline, sizeof(thisline), f) != NULL) {
470 char *end;
471 char *line = thisline;
472 char *crt_path;
473 struct ckch_store *ckchs;
474
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200475 if (missing_lf != -1) {
476 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
477 file, linenum, (missing_lf + 1));
478 cfgerr |= ERR_ALERT | ERR_FATAL;
479 missing_lf = -1;
480 break;
481 }
482
William Lallemand6e9556b2020-05-12 17:52:44 +0200483 linenum++;
484 end = line + strlen(line);
485 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
486 /* Check if we reached the limit and the last char is not \n.
487 * Watch out for the last line without the terminating '\n'!
488 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200489 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
490 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200491 cfgerr |= ERR_ALERT | ERR_FATAL;
492 break;
493 }
494
495 if (*line == '#' || *line == '\n' || *line == '\r')
496 continue;
497
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200498 if (end > line && *(end-1) == '\n') {
499 /* kill trailing LF */
500 *(end - 1) = 0;
501 }
502 else {
503 /* mark this line as truncated */
504 missing_lf = end - line;
505 }
506
William Lallemand6e9556b2020-05-12 17:52:44 +0200507 entry = crtlist_entry_new();
508 if (entry == NULL) {
509 memprintf(err, "Not enough memory!");
510 cfgerr |= ERR_ALERT | ERR_FATAL;
511 goto error;
512 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200513
William Lallemand6e9556b2020-05-12 17:52:44 +0200514 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200515 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200516 goto error;
517
518 /* empty line */
519 if (!crt_path || !*crt_path) {
520 crtlist_entry_free(entry);
521 entry = NULL;
522 continue;
523 }
524
525 if (*crt_path != '/' && global_ssl.crt_base) {
526 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200527 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
528 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200529 cfgerr |= ERR_ALERT | ERR_FATAL;
530 goto error;
531 }
532 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
533 crt_path = path;
534 }
535
536 /* Look for a ckch_store or create one */
537 ckchs = ckchs_lookup(crt_path);
538 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200539 if (stat(crt_path, &buf) == 0) {
540
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200541 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200542 if (ckchs == NULL) {
543 cfgerr |= ERR_ALERT | ERR_FATAL;
544 goto error;
545 }
546
547 entry->node.key = ckchs;
548 entry->crtlist = newlist;
549 ebpt_insert(&newlist->entries, &entry->node);
550 LIST_ADDQ(&newlist->ord_entries, &entry->by_crtlist);
551 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200552
William Lallemand47da8212020-09-10 19:13:27 +0200553 } else {
554 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200555 bundle, since 2.3 we don't support multiple
556 certificate in the same OpenSSL store, so we
557 emulate it by loading each file separately. To
558 do so we need to duplicate the entry in the
559 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200560 char fp[MAXPATHLEN+1] = {0};
561 int n = 0;
562 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
563 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
564 struct stat buf;
565 int ret;
566
567 ret = snprintf(fp, sizeof(fp), "%s.%s", path, SSL_SOCK_KEYTYPE_NAMES[n]);
568 if (ret > sizeof(fp))
569 continue;
570
571 ckchs = ckchs_lookup(fp);
572 if (!ckchs && stat(fp, &buf) == 0) {
573
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200574 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand47da8212020-09-10 19:13:27 +0200575 if (ckchs == NULL) {
576 cfgerr |= ERR_ALERT | ERR_FATAL;
577 goto error;
578 }
579
580 linenum++; /* we duplicate the line for this entry in the bundle */
581 if (!entry_dup) { /* if the entry was used, duplicate one */
582 linenum++;
583 entry_dup = crtlist_entry_dup(entry);
584 if (!entry_dup) {
585 cfgerr |= ERR_ALERT | ERR_FATAL;
586 goto error;
587 }
588 entry_dup->linenum = linenum;
589 }
590
591 entry_dup->node.key = ckchs;
592 entry_dup->crtlist = newlist;
593 ebpt_insert(&newlist->entries, &entry_dup->node);
594 LIST_ADDQ(&newlist->ord_entries, &entry_dup->by_crtlist);
595 LIST_ADDQ(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
596
597 entry_dup = NULL; /* the entry was used, we need a new one next round */
598 }
599 }
600 }
601 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200602 entry = NULL;
603 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200604
605 if (missing_lf != -1) {
606 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
607 file, linenum, (missing_lf + 1));
608 cfgerr |= ERR_ALERT | ERR_FATAL;
609 }
610
William Lallemand6e9556b2020-05-12 17:52:44 +0200611 if (cfgerr & ERR_CODE)
612 goto error;
613
614 newlist->linecount = linenum;
615
616 fclose(f);
617 *crtlist = newlist;
618
619 return cfgerr;
620error:
621 crtlist_entry_free(entry);
622
623 fclose(f);
624 crtlist_free(newlist);
625 return cfgerr;
626}
627
628/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
629 * Fill the <crtlist> argument with a pointer to a new crtlist struct
630 *
631 * This function tries to open and store certificate files.
632 */
633int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
634{
635 struct crtlist *dir;
636 struct dirent **de_list;
637 int i, n;
638 struct stat buf;
639 char *end;
640 char fp[MAXPATHLEN+1];
641 int cfgerr = 0;
642 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200643
644 dir = crtlist_new(path, 1);
645 if (dir == NULL) {
646 memprintf(err, "not enough memory");
647 return ERR_ALERT | ERR_FATAL;
648 }
649
650 n = scandir(path, &de_list, 0, alphasort);
651 if (n < 0) {
652 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
653 err && *err ? *err : "", path, strerror(errno));
654 cfgerr |= ERR_ALERT | ERR_FATAL;
655 }
656 else {
657 for (i = 0; i < n; i++) {
658 struct crtlist_entry *entry;
659 struct dirent *de = de_list[i];
660
661 end = strrchr(de->d_name, '.');
662 if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl") || !strcmp(end, ".key")))
663 goto ignore_entry;
664
665 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
666 if (stat(fp, &buf) != 0) {
667 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
668 err && *err ? *err : "", fp, strerror(errno));
669 cfgerr |= ERR_ALERT | ERR_FATAL;
670 goto ignore_entry;
671 }
672 if (!S_ISREG(buf.st_mode))
673 goto ignore_entry;
674
675 entry = crtlist_entry_new();
676 if (entry == NULL) {
677 memprintf(err, "not enough memory '%s'", fp);
678 cfgerr |= ERR_ALERT | ERR_FATAL;
679 goto ignore_entry;
680 }
681
William Lallemand6e9556b2020-05-12 17:52:44 +0200682 ckchs = ckchs_lookup(fp);
683 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200684 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200685 if (ckchs == NULL) {
686 free(de);
687 free(entry);
688 cfgerr |= ERR_ALERT | ERR_FATAL;
689 goto end;
690 }
691 entry->node.key = ckchs;
692 entry->crtlist = dir;
693 LIST_ADDQ(&ckchs->crtlist_entry, &entry->by_ckch_store);
694 LIST_ADDQ(&dir->ord_entries, &entry->by_crtlist);
695 ebpt_insert(&dir->entries, &entry->node);
696
697ignore_entry:
698 free(de);
699 }
700end:
701 free(de_list);
702 }
703
704 if (cfgerr & ERR_CODE) {
705 /* free the dir and entries on error */
706 crtlist_free(dir);
707 } else {
708 *crtlist = dir;
709 }
710 return cfgerr;
711
712}
713
William Lallemandc756bbd2020-05-13 17:23:59 +0200714/*
715 * Take an ssl_bind_conf structure and append the configuration line used to
716 * create it in the buffer
717 */
718static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
719{
720 int space = 0;
721
722 if (conf == NULL)
723 return;
724
725 chunk_appendf(buf, " [");
726#ifdef OPENSSL_NPN_NEGOTIATED
727 if (conf->npn_str) {
728 int len = conf->npn_len;
729 char *ptr = conf->npn_str;
730 int comma = 0;
731
732 if (space) chunk_appendf(buf, " ");
733 chunk_appendf(buf, "npn ");
734 while (len) {
735 unsigned short size;
736
737 size = *ptr;
738 ptr++;
739 if (comma)
740 chunk_memcat(buf, ",", 1);
741 chunk_memcat(buf, ptr, size);
742 ptr += size;
743 len -= size + 1;
744 comma = 1;
745 }
746 chunk_memcat(buf, "", 1); /* finish with a \0 */
747 space++;
748 }
749#endif
750#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
751 if (conf->alpn_str) {
752 int len = conf->alpn_len;
753 char *ptr = conf->alpn_str;
754 int comma = 0;
755
756 if (space) chunk_appendf(buf, " ");
757 chunk_appendf(buf, "alpn ");
758 while (len) {
759 unsigned short size;
760
761 size = *ptr;
762 ptr++;
763 if (comma)
764 chunk_memcat(buf, ",", 1);
765 chunk_memcat(buf, ptr, size);
766 ptr += size;
767 len -= size + 1;
768 comma = 1;
769 }
770 chunk_memcat(buf, "", 1); /* finish with a \0 */
771 space++;
772 }
773#endif
774 /* verify */
775 {
776 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
777 if (space) chunk_appendf(buf, " ");
778 chunk_appendf(buf, "verify none");
779 space++;
780 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
781 if (space) chunk_appendf(buf, " ");
782 chunk_appendf(buf, "verify optional");
783 space++;
784 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
785 if (space) chunk_appendf(buf, " ");
786 chunk_appendf(buf, "verify required");
787 space++;
788 }
789 }
790
791 if (conf->no_ca_names) {
792 if (space) chunk_appendf(buf, " ");
793 chunk_appendf(buf, "no-ca-names");
794 space++;
795 }
796
797 if (conf->early_data) {
798 if (space) chunk_appendf(buf, " ");
799 chunk_appendf(buf, "allow-0rtt");
800 space++;
801 }
802 if (conf->ca_file) {
803 if (space) chunk_appendf(buf, " ");
804 chunk_appendf(buf, "ca-file %s", conf->ca_file);
805 space++;
806 }
807 if (conf->crl_file) {
808 if (space) chunk_appendf(buf, " ");
809 chunk_appendf(buf, "crl-file %s", conf->crl_file);
810 space++;
811 }
812 if (conf->ciphers) {
813 if (space) chunk_appendf(buf, " ");
814 chunk_appendf(buf, "ciphers %s", conf->ciphers);
815 space++;
816 }
817#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
818 if (conf->ciphersuites) {
819 if (space) chunk_appendf(buf, " ");
820 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
821 space++;
822 }
823#endif
824 if (conf->curves) {
825 if (space) chunk_appendf(buf, " ");
826 chunk_appendf(buf, "curves %s", conf->curves);
827 space++;
828 }
829 if (conf->ecdhe) {
830 if (space) chunk_appendf(buf, " ");
831 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
832 space++;
833 }
834
835 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200836 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200837 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200838 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200839 space++;
840 }
841
William Lallemand8177ad92020-05-20 16:49:02 +0200842 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200843 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200844 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200845 space++;
846 }
847
848 chunk_appendf(buf, "]");
849
850 return;
851}
852
853/* dump a list of filters */
854static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
855{
856 int i;
857
858 if (!entry->fcount)
859 return;
860
861 for (i = 0; i < entry->fcount; i++) {
862 chunk_appendf(buf, " %s", entry->filters[i]);
863 }
864 return;
865}
866
867/************************** CLI functions ****************************/
868
869
870/* CLI IO handler for '(show|dump) ssl crt-list' */
871static int cli_io_handler_dump_crtlist(struct appctx *appctx)
872{
873 struct buffer *trash = alloc_trash_chunk();
874 struct stream_interface *si = appctx->owner;
875 struct ebmb_node *lnode;
876
877 if (trash == NULL)
878 return 1;
879
880 /* dump the list of crt-lists */
881 lnode = appctx->ctx.cli.p1;
882 if (lnode == NULL)
883 lnode = ebmb_first(&crtlists_tree);
884 while (lnode) {
885 chunk_appendf(trash, "%s\n", lnode->key);
886 if (ci_putchk(si_ic(si), trash) == -1) {
887 si_rx_room_blk(si);
888 goto yield;
889 }
890 lnode = ebmb_next(lnode);
891 }
892 free_trash_chunk(trash);
893 return 1;
894yield:
895 appctx->ctx.cli.p1 = lnode;
896 free_trash_chunk(trash);
897 return 0;
898}
899
900/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
901static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
902{
903 struct buffer *trash = alloc_trash_chunk();
904 struct crtlist *crtlist;
905 struct stream_interface *si = appctx->owner;
906 struct crtlist_entry *entry;
907
908 if (trash == NULL)
909 return 1;
910
911 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
912
913 entry = appctx->ctx.cli.p1;
914 if (entry == NULL) {
915 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
916 chunk_appendf(trash, "# %s\n", crtlist->node.key);
917 if (ci_putchk(si_ic(si), trash) == -1) {
918 si_rx_room_blk(si);
919 goto yield;
920 }
921 }
922
923 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
924 struct ckch_store *store;
925 const char *filename;
926
927 store = entry->node.key;
928 filename = store->path;
929 chunk_appendf(trash, "%s", filename);
930 if (appctx->ctx.cli.i0 == 's') /* show */
931 chunk_appendf(trash, ":%d", entry->linenum);
932 dump_crtlist_sslconf(trash, entry->ssl_conf);
933 dump_crtlist_filters(trash, entry);
934 chunk_appendf(trash, "\n");
935
936 if (ci_putchk(si_ic(si), trash) == -1) {
937 si_rx_room_blk(si);
938 goto yield;
939 }
940 }
941 free_trash_chunk(trash);
942 return 1;
943yield:
944 appctx->ctx.cli.p1 = entry;
945 free_trash_chunk(trash);
946 return 0;
947}
948
949/* CLI argument parser for '(show|dump) ssl crt-list' */
950static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
951{
952 struct ebmb_node *lnode;
953 char *filename = NULL;
954 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200955 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200956
957 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
958 return 1;
959
960 appctx->ctx.cli.p0 = NULL;
961 appctx->ctx.cli.p1 = NULL;
962
963 if (*args[3] && !strcmp(args[3], "-n")) {
964 mode = 's';
965 filename = args[4];
966 } else {
967 mode = 'd';
968 filename = args[3];
969 }
970
971 if (mode == 's' && !*args[4])
972 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
973
974 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200975
976
977 /* strip trailing slashes, including first one */
978 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
979 *end = 0;
980
William Lallemandc756bbd2020-05-13 17:23:59 +0200981 lnode = ebst_lookup(&crtlists_tree, filename);
982 if (lnode == NULL)
983 return cli_err(appctx, "didn't find the specified filename\n");
984
985 appctx->ctx.cli.p0 = lnode;
986 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
987 }
988 appctx->ctx.cli.i0 = mode;
989
990 return 0;
991}
992
993/* release function of the "add ssl crt-list' command, free things and unlock
994 the spinlock */
995static void cli_release_add_crtlist(struct appctx *appctx)
996{
997 struct crtlist_entry *entry = appctx->ctx.cli.p1;
998
999 if (appctx->st2 != SETCERT_ST_FIN) {
1000 struct ckch_inst *inst, *inst_s;
1001 /* upon error free the ckch_inst and everything inside */
1002 ebpt_delete(&entry->node);
1003 LIST_DEL(&entry->by_crtlist);
1004 LIST_DEL(&entry->by_ckch_store);
1005
1006 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1007 ckch_inst_free(inst);
1008 }
1009 crtlist_free_filters(entry->filters);
1010 ssl_sock_free_ssl_conf(entry->ssl_conf);
1011 free(entry->ssl_conf);
1012 free(entry);
1013 }
1014
1015 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1016}
1017
1018
1019/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1020 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1021 *
1022 * The logic is the same as the "commit ssl cert" command but without the
1023 * freeing of the old structures, because there are none.
1024 */
1025static int cli_io_handler_add_crtlist(struct appctx *appctx)
1026{
1027 struct bind_conf_list *bind_conf_node;
1028 struct stream_interface *si = appctx->owner;
1029 struct crtlist *crtlist = appctx->ctx.cli.p0;
1030 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1031 struct ckch_store *store = entry->node.key;
1032 struct buffer *trash = alloc_trash_chunk();
1033 struct ckch_inst *new_inst;
1034 char *err = NULL;
1035 int i = 0;
1036 int errcode = 0;
1037
1038 if (trash == NULL)
1039 goto error;
1040
1041 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1042 * created.
1043 */
1044 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1045 goto error;
1046
1047 while (1) {
1048 switch (appctx->st2) {
1049 case SETCERT_ST_INIT:
1050 /* This state just print the update message */
1051 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1052 if (ci_putchk(si_ic(si), trash) == -1) {
1053 si_rx_room_blk(si);
1054 goto yield;
1055 }
1056 appctx->st2 = SETCERT_ST_GEN;
1057 /* fallthrough */
1058 case SETCERT_ST_GEN:
1059 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1060 if (bind_conf_node == NULL)
1061 bind_conf_node = crtlist->bind_conf;
1062 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1063 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1064 struct sni_ctx *sni;
1065
1066 /* yield every 10 generations */
1067 if (i > 10) {
1068 appctx->ctx.cli.p2 = bind_conf_node;
1069 goto yield;
1070 }
1071
1072 /* we don't support multi-cert bundles, only simple ones */
1073 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1074 if (errcode & ERR_CODE)
1075 goto error;
1076
1077 /* we need to initialize the SSL_CTX generated */
1078 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1079 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1080 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1081 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &err);
1082 if (errcode & ERR_CODE)
1083 goto error;
1084 }
1085 }
1086 /* display one dot for each new instance */
1087 chunk_appendf(trash, ".");
1088 i++;
1089 LIST_ADDQ(&store->ckch_inst, &new_inst->by_ckchs);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001090 LIST_ADDQ(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1091 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001092 }
1093 appctx->st2 = SETCERT_ST_INSERT;
1094 /* fallthrough */
1095 case SETCERT_ST_INSERT:
1096 /* insert SNIs in bind_conf */
1097 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1098 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1099 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1100 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1101 }
1102 entry->linenum = ++crtlist->linecount;
1103 appctx->st2 = SETCERT_ST_FIN;
1104 goto end;
1105 }
1106 }
1107
1108end:
1109 chunk_appendf(trash, "\n");
1110 if (errcode & ERR_WARN)
1111 chunk_appendf(trash, "%s", err);
1112 chunk_appendf(trash, "Success!\n");
1113 if (ci_putchk(si_ic(si), trash) == -1)
1114 si_rx_room_blk(si);
1115 free_trash_chunk(trash);
1116 /* success: call the release function and don't come back */
1117 return 1;
1118yield:
1119 /* store the state */
1120 if (ci_putchk(si_ic(si), trash) == -1)
1121 si_rx_room_blk(si);
1122 free_trash_chunk(trash);
1123 si_rx_endp_more(si); /* let's come back later */
1124 return 0; /* should come back */
1125
1126error:
1127 /* spin unlock and free are done in the release function */
1128 if (trash) {
1129 chunk_appendf(trash, "\n%sFailed!\n", err);
1130 if (ci_putchk(si_ic(si), trash) == -1)
1131 si_rx_room_blk(si);
1132 free_trash_chunk(trash);
1133 }
1134 /* error: call the release function and don't come back */
1135 return 1;
1136}
1137
1138
1139/*
1140 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1141 * Filters and option must be passed through payload:
1142 */
1143static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1144{
1145 int cfgerr = 0;
1146 struct ckch_store *store;
1147 char *err = NULL;
1148 char path[MAXPATHLEN+1];
1149 char *crtlist_path;
1150 char *cert_path = NULL;
1151 struct ebmb_node *eb;
1152 struct ebpt_node *inserted;
1153 struct crtlist *crtlist;
1154 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001155 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001156
1157 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1158 return 1;
1159
1160 if (!*args[3] || (!payload && !*args[4]))
1161 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1162
1163 crtlist_path = args[3];
1164
William Lallemand99cc2182020-06-25 15:19:51 +02001165 /* strip trailing slashes, including first one */
1166 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1167 *end = 0;
1168
William Lallemandc756bbd2020-05-13 17:23:59 +02001169 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1170 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1171
1172 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1173 if (!eb) {
1174 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1175 goto error;
1176 }
1177 crtlist = ebmb_entry(eb, struct crtlist, node);
1178
1179 entry = crtlist_entry_new();
1180 if (entry == NULL) {
1181 memprintf(&err, "Not enough memory!");
1182 goto error;
1183 }
1184
1185 if (payload) {
1186 char *lf;
1187
1188 lf = strrchr(payload, '\n');
1189 if (lf) {
1190 memprintf(&err, "only one line of payload is supported!");
1191 goto error;
1192 }
1193 /* cert_path is filled here */
1194 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, &err);
1195 if (cfgerr & ERR_CODE)
1196 goto error;
1197 } else {
1198 cert_path = args[4];
1199 }
1200
1201 if (!cert_path) {
1202 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1203 cfgerr |= ERR_ALERT | ERR_FATAL;
1204 goto error;
1205 }
1206
1207 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1208 char *slash;
1209
1210 slash = strrchr(cert_path, '/');
1211 if (!slash) {
1212 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1213 goto error;
1214 }
1215 /* temporary replace / by 0 to do an strcmp */
1216 *slash = '\0';
1217 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1218 *slash = '/';
1219 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1220 goto error;
1221 }
1222 *slash = '/';
1223 }
1224
1225 if (*cert_path != '/' && global_ssl.crt_base) {
1226 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1227 memprintf(&err, "'%s' : path too long", cert_path);
1228 cfgerr |= ERR_ALERT | ERR_FATAL;
1229 goto error;
1230 }
1231 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1232 cert_path = path;
1233 }
1234
1235 store = ckchs_lookup(cert_path);
1236 if (store == NULL) {
1237 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1238 goto error;
1239 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001240 if (store->ckch == NULL || store->ckch->cert == NULL) {
1241 memprintf(&err, "certificate '%s' is empty!", cert_path);
1242 goto error;
1243 }
1244
1245 /* check if it's possible to insert this new crtlist_entry */
1246 entry->node.key = store;
1247 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1248 if (inserted != &entry->node) {
1249 memprintf(&err, "file already exists in this directory!");
1250 goto error;
1251 }
1252
1253 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1254 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1255 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1256 goto error;
1257 }
1258
1259 LIST_ADDQ(&crtlist->ord_entries, &entry->by_crtlist);
1260 entry->crtlist = crtlist;
1261 LIST_ADDQ(&store->crtlist_entry, &entry->by_ckch_store);
1262
1263 appctx->st2 = SETCERT_ST_INIT;
1264 appctx->ctx.cli.p0 = crtlist;
1265 appctx->ctx.cli.p1 = entry;
1266
1267 /* unlock is done in the release handler */
1268 return 0;
1269
1270error:
1271 crtlist_entry_free(entry);
1272 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1273 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1274 return cli_dynerr(appctx, err);
1275}
1276
1277/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1278static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1279{
1280 struct ckch_store *store;
1281 char *err = NULL;
1282 char *crtlist_path, *cert_path;
1283 struct ebmb_node *ebmb;
1284 struct ebpt_node *ebpt;
1285 struct crtlist *crtlist;
1286 struct crtlist_entry *entry = NULL;
1287 struct ckch_inst *inst, *inst_s;
1288 int linenum = 0;
1289 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001290 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001291
1292 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1293 return 1;
1294
1295 if (!*args[3] || !*args[4])
1296 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1297
1298 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1299 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1300
1301 crtlist_path = args[3];
1302 cert_path = args[4];
1303
1304 colons = strchr(cert_path, ':');
1305 if (colons) {
1306 char *endptr;
1307
1308 linenum = strtol(colons + 1, &endptr, 10);
1309 if (colons + 1 == endptr || *endptr != '\0') {
1310 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1311 goto error;
1312 }
1313 *colons = '\0';
1314 }
William Lallemand99cc2182020-06-25 15:19:51 +02001315
1316 /* strip trailing slashes, including first one */
1317 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1318 *end = 0;
1319
William Lallemandc756bbd2020-05-13 17:23:59 +02001320 /* look for crtlist */
1321 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1322 if (!ebmb) {
1323 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1324 goto error;
1325 }
1326 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1327
1328 /* look for store */
1329 store = ckchs_lookup(cert_path);
1330 if (store == NULL) {
1331 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1332 goto error;
1333 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001334 if (store->ckch == NULL || store->ckch->cert == NULL) {
1335 memprintf(&err, "certificate '%s' is empty!", cert_path);
1336 goto error;
1337 }
1338
1339 ebpt = ebpt_lookup(&crtlist->entries, store);
1340 if (!ebpt) {
1341 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1342 goto error;
1343 }
1344
1345 /* list the line number of entries for errors in err, and select the right ebpt */
1346 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1347 struct crtlist_entry *tmp;
1348
1349 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1350 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1351
1352 /* select the entry we wanted */
1353 if (linenum == 0 || tmp->linenum == linenum) {
1354 if (!entry)
1355 entry = tmp;
1356 }
1357 }
1358
1359 /* we didn't found the specified entry */
1360 if (!entry) {
1361 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);
1362 goto error;
1363 }
1364
1365 /* we didn't specified a line number but there were several entries */
1366 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1367 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1368 goto error;
1369 }
1370
1371 /* upon error free the ckch_inst and everything inside */
1372
1373 ebpt_delete(&entry->node);
1374 LIST_DEL(&entry->by_crtlist);
1375 LIST_DEL(&entry->by_ckch_store);
1376
1377 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1378 struct sni_ctx *sni, *sni_s;
1379
1380 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1381 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1382 ebmb_delete(&sni->name);
1383 LIST_DEL(&sni->by_ckch_inst);
1384 SSL_CTX_free(sni->ctx);
1385 free(sni);
1386 }
1387 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1388 LIST_DEL(&inst->by_ckchs);
1389 free(inst);
1390 }
1391
1392 crtlist_free_filters(entry->filters);
1393 ssl_sock_free_ssl_conf(entry->ssl_conf);
1394 free(entry->ssl_conf);
1395 free(entry);
1396
1397 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1398 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1399 return cli_dynmsg(appctx, LOG_NOTICE, err);
1400
1401error:
1402 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1403 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1404 return cli_dynerr(appctx, err);
1405}
1406
1407
William Lallemandee8530c2020-06-23 18:19:42 +02001408/* unlink and free all crt-list and crt-list entries */
1409void crtlist_deinit()
1410{
1411 struct eb_node *node, *next;
1412 struct crtlist *crtlist;
1413
1414 node = eb_first(&crtlists_tree);
1415 while (node) {
1416 next = eb_next(node);
1417 crtlist = ebmb_entry(node, struct crtlist, node);
1418 crtlist_free(crtlist);
1419 node = next;
1420 }
1421}
1422
William Lallemandc756bbd2020-05-13 17:23:59 +02001423
1424/* register cli keywords */
1425static struct cli_kw_list cli_kws = {{ },{
1426 { { "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 },
1427 { { "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 },
1428 { { "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 },
1429 { { NULL }, NULL, NULL, NULL } }
1430};
1431
1432INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1433