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 struct kd_listen_conf *listen_new(void);
94 static uint32_t counter;
95 static struct table *table;
96 static struct kd_listen_conf *listener;
97 static struct kd_conf *conf;
98 static int errors;
100 typedef struct {
101 union {
102 int64_t number;
103 char *string;
104 struct table *table;
105 } v;
106 int lineno;
107 } YYSTYPE;
109 %}
111 %token AUTH
112 %token CERT
113 %token ERROR
114 %token INCLUDE
115 %token KEY
116 %token LISTEN
117 %token NO
118 %token ON
119 %token PKI PORT
120 %token TABLE TLS
121 %token YES
123 %token <v.string> STRING
124 %token <v.number> NUMBER
125 %type <v.number> yesno
126 %type <v.string> string
127 %type <v.table> tableref
129 %%
131 grammar : /* empty */
132 | grammar include '\n'
133 | grammar '\n'
134 | grammar table '\n'
135 | grammar pki '\n'
136 | grammar listen '\n'
137 | grammar varset '\n'
138 | grammar error '\n' { file->errors++; }
141 include : INCLUDE STRING {
142 struct file *nfile;
144 if ((nfile = pushfile($2, 0)) == NULL) {
145 yyerror("failed to include file %s", $2);
146 free($2);
147 YYERROR;
149 free($2);
151 file = nfile;
152 lungetc('\n');
156 string : string STRING {
157 if (asprintf(&$$, "%s %s", $1, $2) == -1) {
158 free($1);
159 free($2);
160 yyerror("string: asprintf");
161 YYERROR;
163 free($1);
164 free($2);
166 | STRING
169 yesno : YES { $$ = 1; }
170 | NO { $$ = 0; }
173 optnl : '\n' optnl /* zero or more newlines */
174 | /*empty*/
177 nl : '\n' optnl /* one or more newlines */
180 arrow : '=' '>' ;
182 comma : ',' optnl
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 optnl {
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 } '{' optnl table_vals '}' {
254 table = NULL;
258 tableref : '<' STRING '>' {
259 struct table *t;
261 t = findtable($2);
262 free($2);
263 if (t == NULL)
264 YYERROR;
265 $$ = t;
269 listen : LISTEN { listener = listen_new(); }
270 listen_opts {
271 if (listener->auth_table == NULL)
272 yyerror("missing auth table");
273 if (!(listener->flags & L_TLS))
274 yyerror("can't define a non-tls listener");
275 listener = NULL;
279 listen_opts : listen_opt
280 | listen_opt listen_opts
283 listen_opt : ON STRING PORT NUMBER {
284 if (*listener->iface != '\0')
285 yyerror("listen address and port already"
286 " defined");
287 strlcpy(listener->iface, $2, sizeof(listener->iface));
288 listener->port = $4;
290 | TLS PKI STRING {
291 if (*listener->pki != '\0')
292 yyerror("listen tls pki already defined");
293 listener->flags |= L_TLS;
294 strlcpy(listener->pki, $3, sizeof(listener->pki));
296 | AUTH tableref {
297 if (listener->auth_table != NULL)
298 yyerror("listen auth already defined");
299 listener->auth_table = $2;
303 %%
305 struct keywords {
306 const char *k_name;
307 int k_val;
308 };
310 int
311 yyerror(const char *fmt, ...)
313 va_list ap;
314 char *msg;
316 file->errors++;
317 va_start(ap, fmt);
318 if (vasprintf(&msg, fmt, ap) == -1)
319 fatalx("yyerror vasprintf");
320 va_end(ap);
321 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
322 free(msg);
323 return 0;
326 int
327 kw_cmp(const void *k, const void *e)
329 return strcmp(k, ((const struct keywords *)e)->k_name);
332 int
333 lookup(char *s)
335 /* This has to be sorted always. */
336 static const struct keywords keywords[] = {
337 {"auth", AUTH},
338 {"cert", CERT},
339 {"include", INCLUDE},
340 {"key", KEY},
341 {"listen", LISTEN},
342 {"no", NO},
343 {"on", ON},
344 {"pki", PKI},
345 {"port", PORT},
346 {"table", TABLE},
347 {"tls", TLS},
348 {"yes", YES},
349 };
350 const struct keywords *p;
352 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
353 sizeof(keywords[0]), kw_cmp);
355 if (p)
356 return p->k_val;
357 else
358 return STRING;
361 #define START_EXPAND 1
362 #define DONE_EXPAND 2
364 static int expanding;
366 int
367 igetc(void)
369 int c;
371 while (1) {
372 if (file->ungetpos > 0)
373 c = file->ungetbuf[--file->ungetpos];
374 else
375 c = getc(file->stream);
377 if (c == START_EXPAND)
378 expanding = 1;
379 else if (c == DONE_EXPAND)
380 expanding = 0;
381 else
382 break;
384 return c;
387 int
388 lgetc(int quotec)
390 int c, next;
392 if (quotec) {
393 if ((c = igetc()) == EOF) {
394 yyerror("reached end of file while parsing "
395 "quoted string");
396 if (file == topfile || popfile() == EOF)
397 return EOF;
398 return quotec;
400 return c;
403 while ((c = igetc()) == '\\') {
404 next = igetc();
405 if (next != '\n') {
406 c = next;
407 break;
409 yylval.lineno = file->lineno;
410 file->lineno++;
413 if (c == EOF) {
414 /*
415 * Fake EOL when hit EOF for the first time. This gets line
416 * count right if last line in included file is syntactically
417 * invalid and has no newline.
418 */
419 if (file->eof_reached == 0) {
420 file->eof_reached = 1;
421 return '\n';
423 while (c == EOF) {
424 if (file == topfile || popfile() == EOF)
425 return EOF;
426 c = igetc();
429 return c;
432 void
433 lungetc(int c)
435 if (c == EOF)
436 return;
438 if (file->ungetpos >= file->ungetsize) {
439 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
440 if (p == NULL)
441 err(1, "lungetc");
442 file->ungetbuf = p;
443 file->ungetsize *= 2;
445 file->ungetbuf[file->ungetpos++] = c;
448 int
449 findeol(void)
451 int c;
453 /* Skip to either EOF or the first real EOL. */
454 while (1) {
455 c = lgetc(0);
456 if (c == '\n') {
457 file->lineno++;
458 break;
460 if (c == EOF)
461 break;
463 return ERROR;
466 #if 0
467 int my_yylex(void);
469 int
470 yylex(void)
472 int x;
474 switch (x = my_yylex()) {
475 case AUTH:
476 puts("auth");
477 break;
478 case CERT:
479 puts("cert");
480 break;
481 case ERROR:
482 puts("error");
483 break;
484 case INCLUDE:
485 puts("include");
486 break;
487 case KEY:
488 puts("key");
489 break;
490 case LISTEN:
491 puts("listen");
492 break;
493 case NO:
494 puts("no");
495 break;
496 case ON:
497 puts("on");
498 break;
499 case PKI:
500 puts("pki");
501 break;
502 case PORT:
503 puts("port");
504 break;
505 case TABLE:
506 puts("table");
507 break;
508 case TLS:
509 puts("tls");
510 break;
511 case YES:
512 puts("yes");
513 break;
514 case STRING:
515 printf("string \"%s\"\n", yylval.v.string);
516 break;
517 case NUMBER:
518 printf("number %"PRIi64"\n", yylval.v.number);
519 default:
520 printf("character ");
521 if (x == '\n')
522 printf("\\n");
523 else
524 printf("%c", x);
525 printf(" [0x%x]", x);
526 printf("\n");
527 break;
530 return x;
533 int
534 my_yylex(void)
535 #else
536 int
537 yylex(void)
538 #endif
540 char buf[8096];
541 char *p, *val;
542 int quotec, next, c;
543 int token;
545 top:
546 p = buf;
547 while ((c = lgetc(0)) == ' ' || c == '\t')
548 ; /* nothing */
550 yylval.lineno = file->lineno;
551 if (c == '#')
552 while ((c = lgetc(0)) != '\n' && c != EOF)
553 ; /* nothing */
554 if (c == '$' && !expanding) {
555 while (1) {
556 if ((c = lgetc(0)) == EOF)
557 return 0;
559 if (p + 1 >= buf + sizeof(buf) - 1) {
560 yyerror("string too long");
561 return findeol();
563 if (isalnum(c) || c == '_') {
564 *p++ = c;
565 continue;
567 *p = '\0';
568 lungetc(c);
569 break;
571 val = symget(buf);
572 if (val == NULL) {
573 yyerror("macro '%s' not defined", buf);
574 return findeol();
576 p = val + strlen(val) - 1;
577 lungetc(DONE_EXPAND);
578 while (p >= val) {
579 lungetc((unsigned char)*p);
580 p--;
582 lungetc(START_EXPAND);
583 goto top;
586 switch (c) {
587 case '\'':
588 case '"':
589 quotec = c;
590 while (1) {
591 if ((c = lgetc(quotec)) == EOF)
592 return 0;
593 if (c == '\n') {
594 file->lineno++;
595 continue;
596 } else if (c == '\\') {
597 if ((next = lgetc(quotec)) == EOF)
598 return (0);
599 if (next == quotec || next == ' ' ||
600 next == '\t')
601 c = next;
602 else if (next == '\n') {
603 file->lineno++;
604 continue;
605 } else
606 lungetc(next);
607 } else if (c == quotec) {
608 *p = '\0';
609 break;
610 } else if (c == '\0') {
611 yyerror("syntax error");
612 return findeol();
614 if (p + 1 >= buf + sizeof(buf) - 1) {
615 yyerror("string too long");
616 return findeol();
618 *p++ = c;
620 yylval.v.string = strdup(buf);
621 if (yylval.v.string == NULL)
622 err(1, "yylex: strdup");
623 return STRING;
626 #define allowed_to_end_number(x) \
627 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
629 if (c == '-' || isdigit(c)) {
630 do {
631 *p++ = c;
632 if ((size_t)(p-buf) >= sizeof(buf)) {
633 yyerror("string too long");
634 return findeol();
636 } while ((c = lgetc(0)) != EOF && isdigit(c));
637 lungetc(c);
638 if (p == buf + 1 && buf[0] == '-')
639 goto nodigits;
640 if (c == EOF || allowed_to_end_number(c)) {
641 const char *errstr = NULL;
643 *p = '\0';
644 yylval.v.number = strtonum(buf, LLONG_MIN,
645 LLONG_MAX, &errstr);
646 if (errstr) {
647 yyerror("\"%s\" invalid number: %s",
648 buf, errstr);
649 return findeol();
651 return NUMBER;
652 } else {
653 nodigits:
654 while (p > buf + 1)
655 lungetc((unsigned char)*--p);
656 c = (unsigned char)*--p;
657 if (c == '-')
658 return c;
662 #define allowed_in_string(x) \
663 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
664 x != '{' && x != '}' && \
665 x != '!' && x != '=' && x != '#' && \
666 x != ',' && x != '>'))
668 if (isalnum(c) || c == ':' || c == '_') {
669 do {
670 *p++ = c;
671 if ((size_t)(p-buf) >= sizeof(buf)) {
672 yyerror("string too long");
673 return findeol();
675 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
676 lungetc(c);
677 *p = '\0';
678 if ((token = lookup(buf)) == STRING)
679 if ((yylval.v.string = strdup(buf)) == NULL)
680 err(1, "yylex: strdup");
681 return token;
683 if (c == '\n') {
684 yylval.lineno = file->lineno;
685 file->lineno++;
687 if (c == EOF)
688 return 0;
689 return c;
692 int
693 check_file_secrecy(int fd, const char *fname)
695 struct stat st;
697 if (fstat(fd, &st)) {
698 log_warn("cannot stat %s", fname);
699 return -1;
701 if (st.st_uid != 0 && st.st_uid != getuid()) {
702 log_warnx("%s: owner not root or current user", fname);
703 return -1;
705 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
706 log_warnx("%s: group writable or world read/writable", fname);
707 return -1;
709 return 0;
712 struct file *
713 pushfile(const char *name, int secret)
715 struct file *nfile;
717 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
718 log_warn("calloc");
719 return NULL;
721 if ((nfile->name = strdup(name)) == NULL) {
722 log_warn("strdup");
723 free(nfile);
724 return NULL;
726 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
727 log_warn("%s", nfile->name);
728 free(nfile->name);
729 free(nfile);
730 return NULL;
731 } else if (secret &&
732 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
733 fclose(nfile->stream);
734 free(nfile->name);
735 free(nfile);
736 return NULL;
738 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
739 nfile->ungetsize = 16;
740 nfile->ungetbuf = malloc(nfile->ungetsize);
741 if (nfile->ungetbuf == NULL) {
742 log_warn("malloc");
743 fclose(nfile->stream);
744 free(nfile->name);
745 free(nfile);
746 return NULL;
748 TAILQ_INSERT_TAIL(&files, nfile, entry);
749 return nfile;
752 int
753 popfile(void)
755 struct file *prev;
757 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
758 prev->errors += file->errors;
760 TAILQ_REMOVE(&files, file, entry);
761 fclose(file->stream);
762 free(file->name);
763 free(file->ungetbuf);
764 free(file);
765 file = prev;
766 return file ? 0 : EOF;
769 struct kd_conf *
770 parse_config(const char *filename)
772 struct sym *sym, *next;
774 counter = 0;
775 conf = config_new_empty();
777 file = pushfile(filename, 0);
778 if (file == NULL) {
779 free(conf);
780 return NULL;
782 topfile = file;
784 yyparse();
785 errors = file->errors;
786 popfile();
788 /* Free macros and check which have not been used. */
789 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
790 if (verbose && !sym->used)
791 fprintf(stderr, "warning: macro '%s' not used\n",
792 sym->nam);
793 if (!sym->persist) {
794 free(sym->nam);
795 free(sym->val);
796 TAILQ_REMOVE(&symhead, sym, entry);
797 free(sym);
801 if (errors) {
802 clear_config(conf);
803 return NULL;
806 return conf;
809 int
810 symset(const char *nam, const char *val, int persist)
812 struct sym *sym;
814 TAILQ_FOREACH(sym, &symhead, entry) {
815 if (strcmp(nam, sym->nam) == 0)
816 break;
819 if (sym != NULL) {
820 if (sym->persist == 1)
821 return 0;
822 else {
823 free(sym->nam);
824 free(sym->val);
825 TAILQ_REMOVE(&symhead, sym, entry);
826 free(sym);
829 if ((sym = calloc(1, sizeof(*sym))) == NULL)
830 return -1;
832 sym->nam = strdup(nam);
833 if (sym->nam == NULL) {
834 free(sym);
835 return -1;
837 sym->val = strdup(val);
838 if (sym->val == NULL) {
839 free(sym->nam);
840 free(sym);
841 return -1;
843 sym->used = 0;
844 sym->persist = persist;
845 TAILQ_INSERT_TAIL(&symhead, sym, entry);
846 return 0;
849 int
850 cmdline_symset(char *s)
852 char *sym, *val;
853 int ret;
855 if ((val = strrchr(s, '=')) == NULL)
856 return -1;
857 sym = strndup(s, val - s);
858 if (sym == NULL)
859 errx(1, "%s: strndup", __func__);
860 ret = symset(sym, val + 1, 1);
861 free(sym);
863 return ret;
866 char *
867 symget(const char *nam)
869 struct sym *sym;
871 TAILQ_FOREACH(sym, &symhead, entry) {
872 if (strcmp(nam, sym->nam) == 0) {
873 sym->used = 1;
874 return sym->val;
877 return NULL;
880 void
881 clear_config(struct kd_conf *xconf)
883 /* free stuff? */
885 free(xconf);
888 static void
889 add_table(const char *name, const char *type, const char *path)
891 if (table_open(conf, name, type, path) == -1)
892 yyerror("can't initialize table %s", name);
893 table = STAILQ_FIRST(&conf->table_head)->table;
896 static struct table *
897 findtable(const char *name)
899 struct kd_tables_conf *i;
901 STAILQ_FOREACH(i, &conf->table_head, entry) {
902 if (!strcmp(i->table->t_name, name))
903 return i->table;
906 yyerror("unknown table %s", name);
907 return NULL;
910 static void
911 add_cert(const char *name, const char *path)
913 struct kd_pki_conf *pki;
915 STAILQ_FOREACH(pki, &conf->pki_head, entry) {
916 if (strcmp(name, pki->name) != 0)
917 continue;
919 if (pki->cert != NULL) {
920 yyerror("duplicate `pki %s cert'", name);
921 return;
924 goto set;
927 pki = xcalloc(1, sizeof(*pki));
928 strlcpy(pki->name, name, sizeof(pki->name));
929 STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
931 set:
932 if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
933 fatal(NULL);
936 static void
937 add_key(const char *name, const char *path)
939 struct kd_pki_conf *pki;
941 STAILQ_FOREACH(pki, &conf->pki_head, entry) {
942 if (strcmp(name, pki->name) != 0)
943 continue;
945 if (pki->key != NULL) {
946 yyerror("duplicate `pki %s key'", name);
947 return;
950 goto set;
953 pki = xcalloc(1, sizeof(*pki));
954 strlcpy(pki->name, name, sizeof(pki->name));
955 STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
957 set:
958 if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
959 fatal(NULL);
962 static struct kd_listen_conf *
963 listen_new(void)
965 struct kd_listen_conf *l;
967 l = xcalloc(1, sizeof(*l));
968 l->id = counter++;
969 l->fd = -1;
971 STAILQ_INSERT_HEAD(&conf->listen_head, l, entry);
972 return l;