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