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