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