Blob


1 /*
2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
3 * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
4 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
5 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
6 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
7 * Copyright (c) 2001 Markus Friedl. All rights reserved.
8 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
9 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
10 *
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 */
24 %{
25 #include "compat.h"
27 #include <sys/stat.h>
29 #include <ctype.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <event.h>
33 #include <inttypes.h>
34 #include <limits.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <syslog.h>
40 #include <unistd.h>
42 #include "log.h"
43 #include "kamid.h"
44 #include "table.h"
45 #include "utils.h"
47 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
48 static struct file {
49 TAILQ_ENTRY(file) entry;
50 FILE *stream;
51 char *name;
52 size_t ungetpos;
53 size_t ungetsize;
54 u_char *ungetbuf;
55 int eof_reached;
56 int lineno;
57 int errors;
58 } *file, *topfile;
59 struct file *pushfile(const char *, int);
60 int popfile(void);
61 int check_file_secrecy(int, const char *);
62 int yyparse(void);
63 int yylex(void);
64 int yyerror(const char *, ...)
65 __attribute__((__format__ (printf, 1, 2)))
66 __attribute__((__nonnull__ (1)));
67 int kw_cmp(const void *, const void *);
68 int lookup(char *);
69 int igetc(void);
70 int lgetc(int);
71 void lungetc(int);
72 int findeol(void);
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 };
83 int symset(const char *, const char *, int);
84 char *symget(const char *);
86 void clear_config(struct kd_conf *xconf);
88 static void add_table(const char *, const char *, const char *);
89 static struct table *findtable(const char *name);
90 static void add_cert(const char *, const char *);
91 static void add_key(const char *, const char *);
92 static void add_listen(const char *, uint32_t, const char *, struct table *);
94 static uint32_t counter;
95 static struct table *table;
96 static struct kd_conf *conf;
97 static int errors;
99 typedef struct {
100 union {
101 int64_t number;
102 char *string;
103 struct table *table;
104 } v;
105 int lineno;
106 } YYSTYPE;
108 %}
110 %token AUTH
111 %token CERT
112 %token ERROR
113 %token INCLUDE
114 %token KEY
115 %token LISTEN
116 %token NO
117 %token ON
118 %token PKI PORT
119 %token TABLE TLS
120 %token YES
122 %token <v.string> STRING
123 %token <v.number> NUMBER
124 %type <v.number> yesno
125 %type <v.string> string
126 %type <v.table> tableref
128 %%
130 grammar : /* empty */
131 | grammar include '\n'
132 | grammar '\n'
133 | grammar table '\n'
134 | grammar pki '\n'
135 | grammar listen '\n'
136 | grammar varset '\n'
137 | grammar error '\n' { file->errors++; }
140 include : INCLUDE STRING {
141 struct file *nfile;
143 if ((nfile = pushfile($2, 0)) == NULL) {
144 yyerror("failed to include file %s", $2);
145 free($2);
146 YYERROR;
148 free($2);
150 file = nfile;
151 lungetc('\n');
155 string : string STRING {
156 if (asprintf(&$$, "%s %s", $1, $2) == -1) {
157 free($1);
158 free($2);
159 yyerror("string: asprintf");
160 YYERROR;
162 free($1);
163 free($2);
165 | STRING
168 yesno : YES { $$ = 1; }
169 | NO { $$ = 0; }
172 optnl : '\n' optnl /* zero or more newlines */
173 | /*empty*/
176 nl : '\n' optnl /* one or more newlines */
179 arrow : '=' '>' ;
181 comma : ',' optnl
182 | nl
185 varset : STRING '=' string {
186 char *s = $1;
187 if (verbose)
188 printf("%s = \"%s\"\n", $1, $3);
189 while (*s++) {
190 if (isspace((unsigned char)*s)) {
191 yyerror("macro name cannot contain "
192 "whitespace");
193 free($1);
194 free($3);
195 YYERROR;
198 if (symset($1, $3, 0) == -1)
199 fatal("cannot store variable");
200 free($1);
201 free($3);
205 pki : PKI STRING CERT STRING { add_cert($2, $4); }
206 | PKI STRING KEY STRING { add_key($2, $4); }
209 table_kp : string arrow string {
210 if (table_add(table, $1, $3) == -1)
211 yyerror("can't add to table %s",
212 table->t_name);
213 free($1);
214 free($3);
218 table_kps : table_kp
219 | table_kp comma table_kps
222 stringel : STRING {
223 if (table_add(table, $1, NULL) == -1)
224 yyerror("can't add to table %s",
225 table->t_name);
226 free($1);
230 string_list : stringel
231 | stringel comma string_list
234 table_vals : table_kps
235 | string_list
238 table : TABLE STRING STRING {
239 char *p;
241 if ((p = strchr($3, ':')) == NULL) {
242 yyerror("invalid table %s", $2);
243 YYERROR;
246 *p = '\0';
247 add_table($2, $3, p+1);
248 free($2);
249 free($3);
251 | TABLE STRING {
252 add_table($2, "static", NULL);
253 } '{' table_vals '}' {
254 table = NULL;
258 tableref : '<' STRING '>' {
259 $$ = findtable($2);
260 free($2);
261 if ($$ == NULL)
262 YYERROR;
266 listen : LISTEN ON STRING PORT NUMBER TLS PKI STRING /* AUTH tableref */ {
267 char *iface = $3;
268 uint32_t port = $5;
269 char *pki = $8;
270 struct table *auth = NULL; /* $10 */
272 add_listen(iface, port, pki, auth);
273 free(iface);
274 free(pki);
278 %%
280 struct keywords {
281 const char *k_name;
282 int k_val;
283 };
285 int
286 yyerror(const char *fmt, ...)
288 va_list ap;
289 char *msg;
291 file->errors++;
292 va_start(ap, fmt);
293 if (vasprintf(&msg, fmt, ap) == -1)
294 fatalx("yyerror vasprintf");
295 va_end(ap);
296 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
297 free(msg);
298 return 0;
301 int
302 kw_cmp(const void *k, const void *e)
304 return strcmp(k, ((const struct keywords *)e)->k_name);
307 int
308 lookup(char *s)
310 /* This has to be sorted always. */
311 static const struct keywords keywords[] = {
312 {"auth", AUTH},
313 {"cert", CERT},
314 {"include", INCLUDE},
315 {"key", KEY},
316 {"listen", LISTEN},
317 {"no", NO},
318 {"on", ON},
319 {"pki", PKI},
320 {"port", PORT},
321 {"table", TABLE},
322 {"tls", TLS},
323 {"yes", YES},
324 };
325 const struct keywords *p;
327 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
328 sizeof(keywords[0]), kw_cmp);
330 if (p)
331 return p->k_val;
332 else
333 return STRING;
336 #define START_EXPAND 1
337 #define DONE_EXPAND 2
339 static int expanding;
341 int
342 igetc(void)
344 int c;
346 while (1) {
347 if (file->ungetpos > 0)
348 c = file->ungetbuf[--file->ungetpos];
349 else
350 c = getc(file->stream);
352 if (c == START_EXPAND)
353 expanding = 1;
354 else if (c == DONE_EXPAND)
355 expanding = 0;
356 else
357 break;
359 return c;
362 int
363 lgetc(int quotec)
365 int c, next;
367 if (quotec) {
368 if ((c = igetc()) == EOF) {
369 yyerror("reached end of file while parsing "
370 "quoted string");
371 if (file == topfile || popfile() == EOF)
372 return EOF;
373 return quotec;
375 return c;
378 while ((c = igetc()) == '\\') {
379 next = igetc();
380 if (next != '\n') {
381 c = next;
382 break;
384 yylval.lineno = file->lineno;
385 file->lineno++;
388 if (c == EOF) {
389 /*
390 * Fake EOL when hit EOF for the first time. This gets line
391 * count right if last line in included file is syntactically
392 * invalid and has no newline.
393 */
394 if (file->eof_reached == 0) {
395 file->eof_reached = 1;
396 return '\n';
398 while (c == EOF) {
399 if (file == topfile || popfile() == EOF)
400 return EOF;
401 c = igetc();
404 return c;
407 void
408 lungetc(int c)
410 if (c == EOF)
411 return;
413 if (file->ungetpos >= file->ungetsize) {
414 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
415 if (p == NULL)
416 err(1, "lungetc");
417 file->ungetbuf = p;
418 file->ungetsize *= 2;
420 file->ungetbuf[file->ungetpos++] = c;
423 int
424 findeol(void)
426 int c;
428 /* Skip to either EOF or the first real EOL. */
429 while (1) {
430 c = lgetc(0);
431 if (c == '\n') {
432 file->lineno++;
433 break;
435 if (c == EOF)
436 break;
438 return ERROR;
441 int
442 yylex(void)
444 unsigned char buf[8096];
445 unsigned char *p, *val;
446 int quotec, next, c;
447 int token;
449 top:
450 p = buf;
451 while ((c = lgetc(0)) == ' ' || c == '\t')
452 ; /* nothing */
454 yylval.lineno = file->lineno;
455 if (c == '#')
456 while ((c = lgetc(0)) != '\n' && c != EOF)
457 ; /* nothing */
458 if (c == '$' && !expanding) {
459 while (1) {
460 if ((c = lgetc(0)) == EOF)
461 return 0;
463 if (p + 1 >= buf + sizeof(buf) - 1) {
464 yyerror("string too long");
465 return findeol();
467 if (isalnum(c) || c == '_') {
468 *p++ = c;
469 continue;
471 *p = '\0';
472 lungetc(c);
473 break;
475 val = symget(buf);
476 if (val == NULL) {
477 yyerror("macro '%s' not defined", buf);
478 return findeol();
480 p = val + strlen(val) - 1;
481 lungetc(DONE_EXPAND);
482 while (p >= val) {
483 lungetc(*p);
484 p--;
486 lungetc(START_EXPAND);
487 goto top;
490 switch (c) {
491 case '\'':
492 case '"':
493 quotec = c;
494 while (1) {
495 if ((c = lgetc(quotec)) == EOF)
496 return 0;
497 if (c == '\n') {
498 file->lineno++;
499 continue;
500 } else if (c == '\\') {
501 if ((next = lgetc(quotec)) == EOF)
502 return (0);
503 if (next == quotec || next == ' ' ||
504 next == '\t')
505 c = next;
506 else if (next == '\n') {
507 file->lineno++;
508 continue;
509 } else
510 lungetc(next);
511 } else if (c == quotec) {
512 *p = '\0';
513 break;
514 } else if (c == '\0') {
515 yyerror("syntax error");
516 return findeol();
518 if (p + 1 >= buf + sizeof(buf) - 1) {
519 yyerror("string too long");
520 return findeol();
522 *p++ = c;
524 yylval.v.string = strdup(buf);
525 if (yylval.v.string == NULL)
526 err(1, "yylex: strdup");
527 return STRING;
530 #define allowed_to_end_number(x) \
531 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
533 if (c == '-' || isdigit(c)) {
534 do {
535 *p++ = c;
536 if ((size_t)(p-buf) >= sizeof(buf)) {
537 yyerror("string too long");
538 return findeol();
540 } while ((c = lgetc(0)) != EOF && isdigit(c));
541 lungetc(c);
542 if (p == buf + 1 && buf[0] == '-')
543 goto nodigits;
544 if (c == EOF || allowed_to_end_number(c)) {
545 const char *errstr = NULL;
547 *p = '\0';
548 yylval.v.number = strtonum(buf, LLONG_MIN,
549 LLONG_MAX, &errstr);
550 if (errstr) {
551 yyerror("\"%s\" invalid number: %s",
552 buf, errstr);
553 return findeol();
555 return NUMBER;
556 } else {
557 nodigits:
558 while (p > buf + 1)
559 lungetc(*--p);
560 c = *--p;
561 if (c == '-')
562 return c;
566 #define allowed_in_string(x) \
567 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
568 x != '{' && x != '}' && \
569 x != '!' && x != '=' && x != '#' && \
570 x != ','))
572 if (isalnum(c) || c == ':' || c == '_') {
573 do {
574 *p++ = c;
575 if ((size_t)(p-buf) >= sizeof(buf)) {
576 yyerror("string too long");
577 return findeol();
579 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
580 lungetc(c);
581 *p = '\0';
582 if ((token = lookup(buf)) == STRING)
583 if ((yylval.v.string = strdup(buf)) == NULL)
584 err(1, "yylex: strdup");
585 return token;
587 if (c == '\n') {
588 yylval.lineno = file->lineno;
589 file->lineno++;
591 if (c == EOF)
592 return 0;
593 return c;
596 int
597 check_file_secrecy(int fd, const char *fname)
599 struct stat st;
601 if (fstat(fd, &st)) {
602 log_warn("cannot stat %s", fname);
603 return -1;
605 if (st.st_uid != 0 && st.st_uid != getuid()) {
606 log_warnx("%s: owner not root or current user", fname);
607 return -1;
609 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
610 log_warnx("%s: group writable or world read/writable", fname);
611 return -1;
613 return 0;
616 struct file *
617 pushfile(const char *name, int secret)
619 struct file *nfile;
621 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
622 log_warn("calloc");
623 return NULL;
625 if ((nfile->name = strdup(name)) == NULL) {
626 log_warn("strdup");
627 free(nfile);
628 return NULL;
630 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
631 log_warn("%s", nfile->name);
632 free(nfile->name);
633 free(nfile);
634 return NULL;
635 } else if (secret &&
636 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
637 fclose(nfile->stream);
638 free(nfile->name);
639 free(nfile);
640 return NULL;
642 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
643 nfile->ungetsize = 16;
644 nfile->ungetbuf = malloc(nfile->ungetsize);
645 if (nfile->ungetbuf == NULL) {
646 log_warn("malloc");
647 fclose(nfile->stream);
648 free(nfile->name);
649 free(nfile);
650 return NULL;
652 TAILQ_INSERT_TAIL(&files, nfile, entry);
653 return nfile;
656 int
657 popfile(void)
659 struct file *prev;
661 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
662 prev->errors += file->errors;
664 TAILQ_REMOVE(&files, file, entry);
665 fclose(file->stream);
666 free(file->name);
667 free(file->ungetbuf);
668 free(file);
669 file = prev;
670 return file ? 0 : EOF;
673 struct kd_conf *
674 parse_config(const char *filename)
676 struct sym *sym, *next;
678 counter = 0;
679 conf = config_new_empty();
681 file = pushfile(filename, 0);
682 if (file == NULL) {
683 free(conf);
684 return NULL;
686 topfile = file;
688 yyparse();
689 errors = file->errors;
690 popfile();
692 /* Free macros and check which have not been used. */
693 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
694 if (verbose && !sym->used)
695 fprintf(stderr, "warning: macro '%s' not used\n",
696 sym->nam);
697 if (!sym->persist) {
698 free(sym->nam);
699 free(sym->val);
700 TAILQ_REMOVE(&symhead, sym, entry);
701 free(sym);
705 if (errors) {
706 clear_config(conf);
707 return NULL;
710 return conf;
713 int
714 symset(const char *nam, const char *val, int persist)
716 struct sym *sym;
718 TAILQ_FOREACH(sym, &symhead, entry) {
719 if (strcmp(nam, sym->nam) == 0)
720 break;
723 if (sym != NULL) {
724 if (sym->persist == 1)
725 return 0;
726 else {
727 free(sym->nam);
728 free(sym->val);
729 TAILQ_REMOVE(&symhead, sym, entry);
730 free(sym);
733 if ((sym = calloc(1, sizeof(*sym))) == NULL)
734 return -1;
736 sym->nam = strdup(nam);
737 if (sym->nam == NULL) {
738 free(sym);
739 return -1;
741 sym->val = strdup(val);
742 if (sym->val == NULL) {
743 free(sym->nam);
744 free(sym);
745 return -1;
747 sym->used = 0;
748 sym->persist = persist;
749 TAILQ_INSERT_TAIL(&symhead, sym, entry);
750 return 0;
753 int
754 cmdline_symset(char *s)
756 char *sym, *val;
757 int ret;
759 if ((val = strrchr(s, '=')) == NULL)
760 return -1;
761 sym = strndup(s, val - s);
762 if (sym == NULL)
763 errx(1, "%s: strndup", __func__);
764 ret = symset(sym, val + 1, 1);
765 free(sym);
767 return ret;
770 char *
771 symget(const char *nam)
773 struct sym *sym;
775 TAILQ_FOREACH(sym, &symhead, entry) {
776 if (strcmp(nam, sym->nam) == 0) {
777 sym->used = 1;
778 return sym->val;
781 return NULL;
784 void
785 clear_config(struct kd_conf *xconf)
787 /* free stuff? */
789 free(xconf);
792 static void
793 add_table(const char *name, const char *type, const char *path)
795 if (table_open(conf, name, type, path) == -1)
796 yyerror("can't initialize table %s", name);
797 table = SIMPLEQ_FIRST(&conf->table_head)->table;
800 static struct table *
801 findtable(const char *name)
803 struct kd_tables_conf *i;
805 SIMPLEQ_FOREACH(i, &conf->table_head, entry) {
806 if (!strcmp(i->table->t_name, name))
807 return i->table;
810 yyerror("unknown table %s", name);
811 return NULL;
814 static void
815 add_cert(const char *name, const char *path)
817 struct kd_pki_conf *pki;
819 SIMPLEQ_FOREACH(pki, &conf->pki_head, entry) {
820 if (strcmp(name, pki->name) != 0)
821 continue;
823 if (pki->cert != NULL) {
824 yyerror("duplicate `pki %s cert'", name);
825 return;
828 goto set;
831 pki = xcalloc(1, sizeof(*pki));
832 strlcpy(pki->name, name, sizeof(pki->name));
833 SIMPLEQ_INSERT_HEAD(&conf->pki_head, pki, entry);
835 set:
836 if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
837 fatal(NULL);
840 static void
841 add_key(const char *name, const char *path)
843 struct kd_pki_conf *pki;
845 SIMPLEQ_FOREACH(pki, &conf->pki_head, entry) {
846 if (strcmp(name, pki->name) != 0)
847 continue;
849 if (pki->key != NULL) {
850 yyerror("duplicate `pki %s key'", name);
851 return;
854 goto set;
857 pki = xcalloc(1, sizeof(*pki));
858 strlcpy(pki->name, name, sizeof(pki->name));
859 SIMPLEQ_INSERT_HEAD(&conf->pki_head, pki, entry);
861 set:
862 if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
863 fatal(NULL);
866 static void
867 add_listen(const char *iface, uint32_t portno, const char *pki, struct table *auth)
869 struct kd_listen_conf *l;
871 if (portno >= UINT16_MAX)
872 fatalx("invalid port number: %"PRIu32, portno);
874 l = xcalloc(1, sizeof(*l));
876 l->id = counter++;
877 l->fd = -1;
878 strlcpy(l->iface, iface, sizeof(l->iface));
879 l->port = portno;
880 l->auth_table = auth;
881 strlcpy(l->pki, pki, sizeof(l->pki));
883 SIMPLEQ_INSERT_HEAD(&conf->listen_head, l, entry);