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