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