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