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