1 /* $OpenBSD: parse.y,v 1.45 2022/12/15 08:06:13 florian Exp $ */
2
3 /*
4 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Copyright (c) 2016 Sebastian Benoit <benno@openbsd.org>
6 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
7 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
8 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
9 * Copyright (c) 2001 Markus Friedl. All rights reserved.
10 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
11 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
12 *
13 * Permission to use, copy, modify, and distribute this software for any
14 * purpose with or without fee is hereby granted, provided that the above
15 * copyright notice and this permission notice appear in all copies.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
18 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
20 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
21 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
22 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
23 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 */
25
26 %{
27 #include <sys/types.h>
28 #include <sys/queue.h>
29 #include <sys/stat.h>
30 #include <ctype.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39
40 #include "parse.h"
41 #include "extern.h"
42
43 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
44 static struct file {
45 TAILQ_ENTRY(file) entry;
46 FILE *stream;
47 char *name;
48 size_t ungetpos;
49 size_t ungetsize;
50 u_char *ungetbuf;
51 int eof_reached;
52 int lineno;
53 int errors;
54 } *file, *topfile;
55 struct file *pushfile(const char *);
56 int popfile(void);
57 int yyparse(void);
58 int yylex(void);
59 int yyerror(const char *, ...)
60 __attribute__((__format__ (printf, 1, 2)))
61 __attribute__((__nonnull__ (1)));
62 int kw_cmp(const void *, const void *);
63 int lookup(char *);
64 int igetc(void);
65 int lgetc(int);
66 void lungetc(int);
67 int findeol(void);
68
69 struct authority_c *conf_new_authority(struct acme_conf *, char *);
70 struct domain_c *conf_new_domain(struct acme_conf *, char *);
71 struct keyfile *conf_new_keyfile(struct acme_conf *, char *);
72 void clear_config(struct acme_conf *);
73 const char* kt2txt(enum keytype);
74 void print_config(struct acme_conf *);
75 int conf_check_file(char *);
76
77 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
78 struct sym {
79 TAILQ_ENTRY(sym) entry;
80 int used;
81 int persist;
82 char *nam;
83 char *val;
84 };
85 int symset(const char *, const char *, int);
86 char *symget(const char *);
87
88 static struct acme_conf *conf;
89 static struct authority_c *auth;
90 static struct domain_c *domain;
91 static int errors = 0;
92
93 typedef struct {
94 union {
95 int64_t number;
96 char *string;
97 } v;
98 int lineno;
99 } YYSTYPE;
100
101 %}
102
103 %token AUTHORITY URL API ACCOUNT CONTACT
104 %token DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH CHALLENGEDIR
105 %token YES NO
106 %token INCLUDE
107 %token ERROR
108 %token RSA ECDSA
109 %token <v.string> STRING
110 %token <v.number> NUMBER
111 %type <v.string> string
112 %type <v.number> keytype
113
114 %%
115
116 grammar : /* empty */
117 | grammar include '\n'
118 | grammar varset '\n'
119 | grammar '\n'
120 | grammar authority '\n'
121 | grammar domain '\n'
122 | grammar error '\n' { file->errors++; }
123 ;
124
125 include : INCLUDE STRING {
126 struct file *nfile;
127
128 if ((nfile = pushfile($2)) == NULL) {
129 yyerror("failed to include file %s", $2);
130 free($2);
131 YYERROR;
132 }
133 free($2);
134
135 file = nfile;
136 lungetc('\n');
137 }
138 ;
139
140 string : string STRING {
141 if (asprintf(&$$, "%s %s", $1, $2) == -1) {
142 free($1);
143 free($2);
144 yyerror("string: asprintf");
145 YYERROR;
146 }
147 free($1);
148 free($2);
149 }
150 | STRING
151 ;
152
153 varset : STRING '=' string {
154 char *s = $1;
155 if (conf->opts & ACME_OPT_VERBOSE)
156 printf("%s = \"%s\"\n", $1, $3);
157 while (*s++) {
158 if (isspace((unsigned char)*s)) {
159 yyerror("macro name cannot contain "
160 "whitespace");
161 free($1);
162 free($3);
163 YYERROR;
164 }
165 }
166 if (symset($1, $3, 0) == -1)
167 errx(EXIT_FAILURE, "cannot store variable");
168 free($1);
169 free($3);
170 }
171 ;
172
173 optnl : '\n' optnl
174 |
175 ;
176
177 nl : '\n' optnl /* one newline or more */
178 ;
179
180 optcommanl : ',' optnl
181 | optnl
182 ;
183
184 authority : AUTHORITY STRING {
185 char *s;
186 if ((s = strdup($2)) == NULL)
187 err(EXIT_FAILURE, "strdup");
188 if ((auth = conf_new_authority(conf, s)) == NULL) {
189 free(s);
190 yyerror("authority already defined");
191 YYERROR;
192 }
193 } '{' optnl authorityopts_l '}' {
194 if (auth->api == NULL) {
195 yyerror("authority %s: no api URL specified",
196 auth->name);
197 YYERROR;
198 }
199 if (auth->account == NULL) {
200 yyerror("authority %s: no account key file "
201 "specified", auth->name);
202 YYERROR;
203 }
204 auth = NULL;
205 }
206 ;
207
208 authorityopts_l : authorityopts_l authorityoptsl nl
209 | authorityoptsl optnl
210 ;
211
212 authorityoptsl : API URL STRING {
213 char *s;
214 if (auth->api != NULL) {
215 yyerror("duplicate api");
216 YYERROR;
217 }
218 if ((s = strdup($3)) == NULL)
219 err(EXIT_FAILURE, "strdup");
220 auth->api = s;
221 }
222 | ACCOUNT KEY STRING keytype{
223 char *s;
224 if (auth->account != NULL) {
225 yyerror("duplicate account");
226 YYERROR;
227 }
228 if ((s = strdup($3)) == NULL)
229 err(EXIT_FAILURE, "strdup");
230 auth->account = s;
231 auth->keytype = $4;
232 }
233 | CONTACT STRING {
234 char *s;
235 if (auth->contact != NULL) {
236 yyerror("duplicate contact");
237 YYERROR;
238 }
239 if ((s = strdup($2)) == NULL)
240 err(EXIT_FAILURE, "strdup");
241 auth->contact = s;
242 }
243 ;
244
245 domain : DOMAIN STRING {
246 char *s;
247 if ((s = strdup($2)) == NULL)
248 err(EXIT_FAILURE, "strdup");
249 if (!domain_valid(s)) {
250 yyerror("%s: bad domain syntax", s);
251 free(s);
252 YYERROR;
253 }
254 if ((domain = conf_new_domain(conf, s)) == NULL) {
255 free(s);
256 yyerror("domain already defined");
257 YYERROR;
258 }
259 } '{' optnl domainopts_l '}' {
260 if (domain->domain == NULL) {
261 if ((domain->domain = strdup(domain->handle))
262 == NULL)
263 err(EXIT_FAILURE, "strdup");
264 }
265 /* enforce minimum config here */
266 if (domain->key == NULL) {
267 yyerror("no domain key file specified for "
268 "domain %s", domain->domain);
269 YYERROR;
270 }
271 if (domain->cert == NULL && domain->fullchain == NULL) {
272 yyerror("at least certificate file or full "
273 "certificate chain file must be specified "
274 "for domain %s", domain->domain);
275 YYERROR;
276 }
277 domain = NULL;
278 }
279 ;
280
281 keytype : RSA { $$ = KT_RSA; }
282 | ECDSA { $$ = KT_ECDSA; }
283 | { $$ = KT_RSA; }
284 ;
285
286 domainopts_l : domainopts_l domainoptsl nl
287 | domainoptsl optnl
288 ;
289
290 domainoptsl : ALTERNATIVE NAMES '{' optnl altname_l '}'
291 | DOMAIN NAME STRING {
292 char *s;
293 if (domain->domain != NULL) {
294 yyerror("duplicate domain name");
295 YYERROR;
296 }
297 if ((s = strdup($3)) == NULL)
298 err(EXIT_FAILURE, "strdup");
299 domain->domain = s;
300 }
301 | DOMAIN KEY STRING keytype {
302 char *s;
303 if (domain->key != NULL) {
304 yyerror("duplicate key");
305 YYERROR;
306 }
307 if ((s = strdup($3)) == NULL)
308 err(EXIT_FAILURE, "strdup");
309 if (!conf_check_file(s)) {
310 free(s);
311 YYERROR;
312 }
313 if ((conf_new_keyfile(conf, s)) == NULL) {
314 free(s);
315 yyerror("domain key file already used");
316 YYERROR;
317 }
318 domain->key = s;
319 domain->keytype = $4;
320 }
321 | DOMAIN CERT STRING {
322 char *s;
323 if (domain->cert != NULL) {
324 yyerror("duplicate cert");
325 YYERROR;
326 }
327 if ((s = strdup($3)) == NULL)
328 err(EXIT_FAILURE, "strdup");
329 if (s[0] != '/') {
330 free(s);
331 yyerror("not an absolute path");
332 YYERROR;
333 }
334 if ((conf_new_keyfile(conf, s)) == NULL) {
335 free(s);
336 yyerror("domain cert file already used");
337 YYERROR;
338 }
339 domain->cert = s;
340 }
341 | DOMAIN CHAIN CERT STRING {
342 char *s;
343 if (domain->chain != NULL) {
344 yyerror("duplicate chain");
345 YYERROR;
346 }
347 if ((s = strdup($4)) == NULL)
348 err(EXIT_FAILURE, "strdup");
349 if ((conf_new_keyfile(conf, s)) == NULL) {
350 free(s);
351 yyerror("domain chain file already used");
352 YYERROR;
353 }
354 domain->chain = s;
355 }
356 | DOMAIN FULL CHAIN CERT STRING {
357 char *s;
358 if (domain->fullchain != NULL) {
359 yyerror("duplicate full chain");
360 YYERROR;
361 }
362 if ((s = strdup($5)) == NULL)
363 err(EXIT_FAILURE, "strdup");
364 if ((conf_new_keyfile(conf, s)) == NULL) {
365 free(s);
366 yyerror("domain full chain file already used");
367 YYERROR;
368 }
369 domain->fullchain = s;
370 }
371 | SIGN WITH STRING {
372 char *s;
373 if (domain->auth != NULL) {
374 yyerror("duplicate sign with");
375 YYERROR;
376 }
377 if ((s = strdup($3)) == NULL)
378 err(EXIT_FAILURE, "strdup");
379 if (authority_find(conf, s) == NULL) {
380 yyerror("sign with: unknown authority");
381 free(s);
382 YYERROR;
383 }
384 domain->auth = s;
385 }
386 | CHALLENGEDIR STRING {
387 char *s;
388 if (domain->challengedir != NULL) {
389 yyerror("duplicate challengedir");
390 YYERROR;
391 }
392 if ((s = strdup($2)) == NULL)
393 err(EXIT_FAILURE, "strdup");
394 domain->challengedir = s;
395 }
396 ;
397
398 altname_l : altname optcommanl altname_l
399 | altname optnl
400 ;
401
402 altname : STRING {
403 char *s;
404 struct altname_c *ac;
405 if (!domain_valid($1)) {
406 yyerror("bad domain syntax");
407 YYERROR;
408 }
409 if ((ac = calloc(1, sizeof(struct altname_c))) == NULL)
410 err(EXIT_FAILURE, "calloc");
411 if ((s = strdup($1)) == NULL) {
412 free(ac);
413 err(EXIT_FAILURE, "strdup");
414 }
415 ac->domain = s;
416 TAILQ_INSERT_TAIL(&domain->altname_list, ac, entry);
417 domain->altname_count++;
418 /*
419 * XXX we could check if altname is duplicate
420 * or identical to domain->domain
421 */
422 }
423
424 %%
425
426 struct keywords {
427 const char *k_name;
428 int k_val;
429 };
430
431 int
yyerror(const char * fmt,...)432 yyerror(const char *fmt, ...)
433 {
434 va_list ap;
435 char *msg;
436
437 file->errors++;
438 va_start(ap, fmt);
439 if (vasprintf(&msg, fmt, ap) == -1)
440 err(EXIT_FAILURE, "yyerror vasprintf");
441 va_end(ap);
442 fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg);
443 free(msg);
444 return (0);
445 }
446
447 int
kw_cmp(const void * k,const void * e)448 kw_cmp(const void *k, const void *e)
449 {
450 return strcmp(k, ((const struct keywords *)e)->k_name);
451 }
452
453 int
lookup(char * s)454 lookup(char *s)
455 {
456 /* this has to be sorted always */
457 static const struct keywords keywords[] = {
458 {"account", ACCOUNT},
459 {"alternative", ALTERNATIVE},
460 {"api", API},
461 {"authority", AUTHORITY},
462 {"certificate", CERT},
463 {"chain", CHAIN},
464 {"challengedir", CHALLENGEDIR},
465 {"contact", CONTACT},
466 {"domain", DOMAIN},
467 {"ecdsa", ECDSA},
468 {"full", FULL},
469 {"include", INCLUDE},
470 {"key", KEY},
471 {"name", NAME},
472 {"names", NAMES},
473 {"rsa", RSA},
474 {"sign", SIGN},
475 {"url", URL},
476 {"with", WITH},
477 };
478 const struct keywords *p;
479
480 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
481 sizeof(keywords[0]), kw_cmp);
482
483 if (p != NULL)
484 return p->k_val;
485 else
486 return STRING;
487 }
488
489 #define START_EXPAND 1
490 #define DONE_EXPAND 2
491
492 static int expanding;
493
494 int
igetc(void)495 igetc(void)
496 {
497 int c;
498
499 while (1) {
500 if (file->ungetpos > 0)
501 c = file->ungetbuf[--file->ungetpos];
502 else
503 c = getc(file->stream);
504
505 if (c == START_EXPAND)
506 expanding = 1;
507 else if (c == DONE_EXPAND)
508 expanding = 0;
509 else
510 break;
511 }
512 return c;
513 }
514
515 int
lgetc(int quotec)516 lgetc(int quotec)
517 {
518 int c, next;
519
520 if (quotec) {
521 if ((c = igetc()) == EOF) {
522 yyerror("reached end of file while parsing "
523 "quoted string");
524 if (file == topfile || popfile() == EOF)
525 return (EOF);
526 return quotec;
527 }
528 return c;
529 }
530
531 while ((c = igetc()) == '\\') {
532 next = igetc();
533 if (next != '\n') {
534 c = next;
535 break;
536 }
537 yylval.lineno = file->lineno;
538 file->lineno++;
539 }
540
541 if (c == EOF) {
542 /*
543 * Fake EOL when hit EOF for the first time. This gets line
544 * count right if last line in included file is syntactically
545 * invalid and has no newline.
546 */
547 if (file->eof_reached == 0) {
548 file->eof_reached = 1;
549 return '\n';
550 }
551 while (c == EOF) {
552 if (file == topfile || popfile() == EOF)
553 return (EOF);
554 c = igetc();
555 }
556 }
557 return c;
558 }
559
560 void
lungetc(int c)561 lungetc(int c)
562 {
563 if (c == EOF)
564 return;
565
566 if (file->ungetpos >= file->ungetsize) {
567 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
568 if (p == NULL)
569 err(1, "%s", __func__);
570 file->ungetbuf = p;
571 file->ungetsize *= 2;
572 }
573 file->ungetbuf[file->ungetpos++] = c;
574 }
575
576 int
findeol(void)577 findeol(void)
578 {
579 int c;
580
581 /* skip to either EOF or the first real EOL */
582 while (1) {
583 c = lgetc(0);
584 if (c == '\n') {
585 file->lineno++;
586 break;
587 }
588 if (c == EOF)
589 break;
590 }
591 return ERROR;
592 }
593
594 int
yylex(void)595 yylex(void)
596 {
597 char buf[8096];
598 char *p, *val;
599 int quotec, next, c;
600 int token;
601
602 top:
603 p = buf;
604 while ((c = lgetc(0)) == ' ' || c == '\t')
605 ; /* nothing */
606
607 yylval.lineno = file->lineno;
608 if (c == '#')
609 while ((c = lgetc(0)) != '\n' && c != EOF)
610 ; /* nothing */
611 if (c == '$' && !expanding) {
612 while (1) {
613 if ((c = lgetc(0)) == EOF)
614 return 0;
615
616 if (p + 1 >= buf + sizeof(buf) - 1) {
617 yyerror("string too long");
618 return findeol();
619 }
620 if (isalnum(c) || c == '_') {
621 *p++ = c;
622 continue;
623 }
624 *p = '\0';
625 lungetc(c);
626 break;
627 }
628 val = symget(buf);
629 if (val == NULL) {
630 yyerror("macro '%s' not defined", buf);
631 return findeol();
632 }
633 p = val + strlen(val) - 1;
634 lungetc(DONE_EXPAND);
635 while (p >= val) {
636 lungetc((unsigned char)*p);
637 p--;
638 }
639 lungetc(START_EXPAND);
640 goto top;
641 }
642
643 switch (c) {
644 case '\'':
645 case '"':
646 quotec = c;
647 while (1) {
648 if ((c = lgetc(quotec)) == EOF)
649 return 0;
650 if (c == '\n') {
651 file->lineno++;
652 continue;
653 } else if (c == '\\') {
654 if ((next = lgetc(quotec)) == EOF)
655 return 0;
656 if (next == quotec || next == ' ' ||
657 next == '\t')
658 c = next;
659 else if (next == '\n') {
660 file->lineno++;
661 continue;
662 } else
663 lungetc(next);
664 } else if (c == quotec) {
665 *p = '\0';
666 break;
667 } else if (c == '\0') {
668 yyerror("syntax error");
669 return findeol();
670 }
671 if (p + 1 >= buf + sizeof(buf) - 1) {
672 yyerror("string too long");
673 return findeol();
674 }
675 *p++ = c;
676 }
677 yylval.v.string = strdup(buf);
678 if (yylval.v.string == NULL)
679 err(EXIT_FAILURE, "%s", __func__);
680 return STRING;
681 }
682
683 #define allowed_to_end_number(x) \
684 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
685
686 if (c == '-' || isdigit(c)) {
687 do {
688 *p++ = c;
689 if ((size_t)(p-buf) >= sizeof(buf)) {
690 yyerror("string too long");
691 return findeol();
692 }
693 } while ((c = lgetc(0)) != EOF && isdigit(c));
694 lungetc(c);
695 if (p == buf + 1 && buf[0] == '-')
696 goto nodigits;
697 if (c == EOF || allowed_to_end_number(c)) {
698 const char *errstr = NULL;
699
700 *p = '\0';
701 yylval.v.number = strtonum(buf, LLONG_MIN,
702 LLONG_MAX, &errstr);
703 if (errstr != NULL) {
704 yyerror("\"%s\" invalid number: %s",
705 buf, errstr);
706 return (findeol());
707 }
708 return NUMBER;
709 } else {
710 nodigits:
711 while (p > buf + 1)
712 lungetc((unsigned char)*--p);
713 c = (unsigned char)*--p;
714 if (c == '-')
715 return c;
716 }
717 }
718
719 #define allowed_in_string(x) \
720 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
721 x != '{' && x != '}' && \
722 x != '!' && x != '=' && x != '#' && \
723 x != ','))
724
725 if (isalnum(c) || c == ':' || c == '_') {
726 do {
727 *p++ = c;
728 if ((size_t)(p-buf) >= sizeof(buf)) {
729 yyerror("string too long");
730 return (findeol());
731 }
732 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
733 lungetc(c);
734 *p = '\0';
735 if ((token = lookup(buf)) == STRING) {
736 if ((yylval.v.string = strdup(buf)) == NULL)
737 err(EXIT_FAILURE, "%s", __func__);
738 }
739 return token;
740 }
741 if (c == '\n') {
742 yylval.lineno = file->lineno;
743 file->lineno++;
744 }
745 if (c == EOF)
746 return 0;
747 return c;
748 }
749
750 struct file *
pushfile(const char * name)751 pushfile(const char *name)
752 {
753 struct file *nfile;
754
755 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
756 warn("%s", __func__);
757 return NULL;
758 }
759 if ((nfile->name = strdup(name)) == NULL) {
760 warn("%s", __func__);
761 free(nfile);
762 return NULL;
763 }
764 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
765 warn("%s: %s", __func__, nfile->name);
766 free(nfile->name);
767 free(nfile);
768 return NULL;
769 }
770 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
771 nfile->ungetsize = 16;
772 nfile->ungetbuf = malloc(nfile->ungetsize);
773 if (nfile->ungetbuf == NULL) {
774 warn("%s", __func__);
775 fclose(nfile->stream);
776 free(nfile->name);
777 free(nfile);
778 return NULL;
779 }
780 TAILQ_INSERT_TAIL(&files, nfile, entry);
781 return nfile;
782 }
783
784 int
popfile(void)785 popfile(void)
786 {
787 struct file *prev;
788
789 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
790 prev->errors += file->errors;
791
792 TAILQ_REMOVE(&files, file, entry);
793 fclose(file->stream);
794 free(file->name);
795 free(file->ungetbuf);
796 free(file);
797 file = prev;
798 return (file ? 0 : EOF);
799 }
800
801 struct acme_conf *
parse_config(const char * filename,int opts)802 parse_config(const char *filename, int opts)
803 {
804 struct sym *sym, *next;
805
806 if ((conf = calloc(1, sizeof(struct acme_conf))) == NULL)
807 err(EXIT_FAILURE, "%s", __func__);
808 conf->opts = opts;
809
810 if ((file = pushfile(filename)) == NULL) {
811 free(conf);
812 return NULL;
813 }
814 topfile = file;
815
816 TAILQ_INIT(&conf->authority_list);
817 TAILQ_INIT(&conf->domain_list);
818
819 yyparse();
820 errors = file->errors;
821 popfile();
822
823 /* Free macros and check which have not been used. */
824 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
825 if ((conf->opts & ACME_OPT_VERBOSE) && !sym->used)
826 fprintf(stderr, "warning: macro '%s' not "
827 "used\n", sym->nam);
828 if (!sym->persist) {
829 free(sym->nam);
830 free(sym->val);
831 TAILQ_REMOVE(&symhead, sym, entry);
832 free(sym);
833 }
834 }
835
836 if (errors != 0) {
837 clear_config(conf);
838 return NULL;
839 }
840
841 if (opts & ACME_OPT_CHECK) {
842 if (opts & ACME_OPT_VERBOSE)
843 print_config(conf);
844 exit(0);
845 }
846
847
848 return conf;
849 }
850
851 int
symset(const char * nam,const char * val,int persist)852 symset(const char *nam, const char *val, int persist)
853 {
854 struct sym *sym;
855
856 TAILQ_FOREACH(sym, &symhead, entry) {
857 if (strcmp(nam, sym->nam) == 0)
858 break;
859 }
860
861 if (sym != NULL) {
862 if (sym->persist == 1)
863 return (0);
864 else {
865 free(sym->nam);
866 free(sym->val);
867 TAILQ_REMOVE(&symhead, sym, entry);
868 free(sym);
869 }
870 }
871 if ((sym = calloc(1, sizeof(*sym))) == NULL)
872 return -1;
873
874 sym->nam = strdup(nam);
875 if (sym->nam == NULL) {
876 free(sym);
877 return -1;
878 }
879 sym->val = strdup(val);
880 if (sym->val == NULL) {
881 free(sym->nam);
882 free(sym);
883 return -1;
884 }
885 sym->used = 0;
886 sym->persist = persist;
887 TAILQ_INSERT_TAIL(&symhead, sym, entry);
888 return 0;
889 }
890
891 int
cmdline_symset(char * s)892 cmdline_symset(char *s)
893 {
894 char *sym, *val;
895 int ret;
896
897 if ((val = strrchr(s, '=')) == NULL)
898 return -1;
899 sym = strndup(s, val - s);
900 if (sym == NULL)
901 errx(EXIT_FAILURE, "%s: strndup", __func__);
902 ret = symset(sym, val + 1, 1);
903 free(sym);
904
905 return ret;
906 }
907
908 char *
symget(const char * nam)909 symget(const char *nam)
910 {
911 struct sym *sym;
912
913 TAILQ_FOREACH(sym, &symhead, entry) {
914 if (strcmp(nam, sym->nam) == 0) {
915 sym->used = 1;
916 return sym->val;
917 }
918 }
919 return NULL;
920 }
921
922 struct authority_c *
conf_new_authority(struct acme_conf * c,char * s)923 conf_new_authority(struct acme_conf *c, char *s)
924 {
925 struct authority_c *a;
926
927 a = authority_find(c, s);
928 if (a != NULL)
929 return NULL;
930 if ((a = calloc(1, sizeof(struct authority_c))) == NULL)
931 err(EXIT_FAILURE, "%s", __func__);
932 TAILQ_INSERT_TAIL(&c->authority_list, a, entry);
933
934 a->name = s;
935 return a;
936 }
937
938 struct authority_c *
authority_find(struct acme_conf * c,char * s)939 authority_find(struct acme_conf *c, char *s)
940 {
941 struct authority_c *a;
942
943 TAILQ_FOREACH(a, &c->authority_list, entry) {
944 if (strncmp(a->name, s, AUTH_MAXLEN) == 0) {
945 return a;
946 }
947 }
948 return NULL;
949 }
950
951 struct authority_c *
authority_find0(struct acme_conf * c)952 authority_find0(struct acme_conf *c)
953 {
954 return (TAILQ_FIRST(&c->authority_list));
955 }
956
957 struct domain_c *
conf_new_domain(struct acme_conf * c,char * s)958 conf_new_domain(struct acme_conf *c, char *s)
959 {
960 struct domain_c *d;
961
962 d = domain_find_handle(c, s);
963 if (d != NULL)
964 return (NULL);
965 if ((d = calloc(1, sizeof(struct domain_c))) == NULL)
966 err(EXIT_FAILURE, "%s", __func__);
967 TAILQ_INSERT_TAIL(&c->domain_list, d, entry);
968
969 d->handle = s;
970 TAILQ_INIT(&d->altname_list);
971
972 return d;
973 }
974
975 struct domain_c *
domain_find_handle(struct acme_conf * c,char * s)976 domain_find_handle(struct acme_conf *c, char *s)
977 {
978 struct domain_c *d;
979
980 TAILQ_FOREACH(d, &c->domain_list, entry) {
981 if (strncmp(d->handle, s, DOMAIN_MAXLEN) == 0) {
982 return d;
983 }
984 }
985 return NULL;
986 }
987
988 struct keyfile *
conf_new_keyfile(struct acme_conf * c,char * s)989 conf_new_keyfile(struct acme_conf *c, char *s)
990 {
991 struct keyfile *k;
992
993 LIST_FOREACH(k, &c->used_key_list, entry) {
994 if (strncmp(k->name, s, PATH_MAX) == 0) {
995 return NULL;
996 }
997 }
998
999 if ((k = calloc(1, sizeof(struct keyfile))) == NULL)
1000 err(EXIT_FAILURE, "%s", __func__);
1001 LIST_INSERT_HEAD(&c->used_key_list, k, entry);
1002
1003 k->name = s;
1004 return k;
1005 }
1006
1007 void
clear_config(struct acme_conf * xconf)1008 clear_config(struct acme_conf *xconf)
1009 {
1010 struct authority_c *a;
1011 struct domain_c *d;
1012 struct altname_c *ac;
1013
1014 while ((a = TAILQ_FIRST(&xconf->authority_list)) != NULL) {
1015 TAILQ_REMOVE(&xconf->authority_list, a, entry);
1016 free(a);
1017 }
1018 while ((d = TAILQ_FIRST(&xconf->domain_list)) != NULL) {
1019 while ((ac = TAILQ_FIRST(&d->altname_list)) != NULL) {
1020 TAILQ_REMOVE(&d->altname_list, ac, entry);
1021 free(ac);
1022 }
1023 TAILQ_REMOVE(&xconf->domain_list, d, entry);
1024 free(d);
1025 }
1026 free(xconf);
1027 }
1028
1029 const char*
kt2txt(enum keytype kt)1030 kt2txt(enum keytype kt)
1031 {
1032 switch (kt) {
1033 case KT_RSA:
1034 return "rsa";
1035 case KT_ECDSA:
1036 return "ecdsa";
1037 default:
1038 return "<unknown>";
1039 }
1040 }
1041
1042 void
print_config(struct acme_conf * xconf)1043 print_config(struct acme_conf *xconf)
1044 {
1045 struct authority_c *a;
1046 struct domain_c *d;
1047 struct altname_c *ac;
1048 int f;
1049
1050 TAILQ_FOREACH(a, &xconf->authority_list, entry) {
1051 printf("authority %s {\n", a->name);
1052 if (a->api != NULL)
1053 printf("\tapi url \"%s\"\n", a->api);
1054 if (a->account != NULL)
1055 printf("\taccount key \"%s\" %s\n", a->account,
1056 kt2txt(a->keytype));
1057 printf("}\n\n");
1058 }
1059 TAILQ_FOREACH(d, &xconf->domain_list, entry) {
1060 f = 0;
1061 printf("domain %s {\n", d->handle);
1062 if (d->domain != NULL)
1063 printf("\tdomain name \"%s\"\n", d->domain);
1064 TAILQ_FOREACH(ac, &d->altname_list, entry) {
1065 if (!f)
1066 printf("\talternative names {");
1067 if (ac->domain != NULL) {
1068 printf("%s%s", f ? ", " : " ", ac->domain);
1069 f = 1;
1070 }
1071 }
1072 if (f)
1073 printf(" }\n");
1074 if (d->key != NULL)
1075 printf("\tdomain key \"%s\" %s\n", d->key, kt2txt(
1076 d->keytype));
1077 if (d->cert != NULL)
1078 printf("\tdomain certificate \"%s\"\n", d->cert);
1079 if (d->chain != NULL)
1080 printf("\tdomain chain certificate \"%s\"\n", d->chain);
1081 if (d->fullchain != NULL)
1082 printf("\tdomain full chain certificate \"%s\"\n",
1083 d->fullchain);
1084 if (d->auth != NULL)
1085 printf("\tsign with \"%s\"\n", d->auth);
1086 if (d->challengedir != NULL)
1087 printf("\tchallengedir \"%s\"\n", d->challengedir);
1088 printf("}\n\n");
1089 }
1090 }
1091
1092 /*
1093 * This isn't RFC1035 compliant, but does the bare minimum in making
1094 * sure that we don't get bogus domain names on the command line, which
1095 * might otherwise screw up our directory structure.
1096 * Returns zero on failure, non-zero on success.
1097 */
1098 int
domain_valid(const char * cp)1099 domain_valid(const char *cp)
1100 {
1101
1102 for ( ; *cp != '\0'; cp++)
1103 if (!(*cp == '.' || *cp == '-' ||
1104 *cp == '_' || isalnum((unsigned char)*cp)))
1105 return 0;
1106 return 1;
1107 }
1108
1109 int
conf_check_file(char * s)1110 conf_check_file(char *s)
1111 {
1112 struct stat st;
1113
1114 if (s[0] != '/') {
1115 warnx("%s: not an absolute path", s);
1116 return 0;
1117 }
1118 if (stat(s, &st)) {
1119 if (errno == ENOENT)
1120 return 1;
1121 warn("cannot stat %s", s);
1122 return 0;
1123 }
1124 if (st.st_mode & (S_IRWXG | S_IRWXO)) {
1125 warnx("%s: group read/writable or world read/writable", s);
1126 return 0;
1127 }
1128 return 1;
1129 }
1130