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 USERDATA
122 %token VIRTUAL
123 %token YES
125 %token <v.string> STRING
126 %token <v.number> NUMBER
127 %type <v.number> yesno
128 %type <v.string> string
129 %type <v.table> tableref
131 %%
133 grammar : /* empty */
134 | grammar include '\n'
135 | grammar '\n'
136 | grammar table '\n'
137 | grammar pki '\n'
138 | grammar listen '\n'
139 | grammar varset '\n'
140 | grammar error '\n' { file->errors++; }
143 include : INCLUDE STRING {
144 struct file *nfile;
146 if ((nfile = pushfile($2, 0)) == NULL) {
147 yyerror("failed to include file %s", $2);
148 free($2);
149 YYERROR;
151 free($2);
153 file = nfile;
154 lungetc('\n');
158 string : string STRING {
159 if (asprintf(&$$, "%s %s", $1, $2) == -1) {
160 free($1);
161 free($2);
162 yyerror("string: asprintf");
163 YYERROR;
165 free($1);
166 free($2);
168 | STRING
171 yesno : YES { $$ = 1; }
172 | NO { $$ = 0; }
175 optnl : '\n' optnl /* zero or more newlines */
176 | /*empty*/
179 nl : '\n' optnl /* one or more newlines */
182 arrow : '=' '>' ;
184 comma : ',' optnl
187 varset : STRING '=' string {
188 char *s = $1;
189 if (verbose)
190 printf("%s = \"%s\"\n", $1, $3);
191 while (*s++) {
192 if (isspace((unsigned char)*s)) {
193 yyerror("macro name cannot contain "
194 "whitespace");
195 free($1);
196 free($3);
197 YYERROR;
200 if (symset($1, $3, 0) == -1)
201 fatal("cannot store variable");
202 free($1);
203 free($3);
207 pki : PKI STRING CERT STRING { add_cert($2, $4); }
208 | PKI STRING KEY STRING { add_key($2, $4); }
211 table_kp : string arrow string optnl {
212 if (table_add(table, $1, $3) == -1)
213 yyerror("can't add to table %s",
214 table->t_name);
215 free($1);
216 free($3);
220 table_kps : table_kp
221 | table_kp comma table_kps
224 stringel : STRING {
225 if (table_add(table, $1, NULL) == -1)
226 yyerror("can't add to table %s",
227 table->t_name);
228 free($1);
232 string_list : stringel
233 | stringel comma string_list
236 table_vals : table_kps
237 | string_list
240 table : TABLE STRING STRING {
241 char *p;
243 if ((p = strchr($3, ':')) == NULL) {
244 yyerror("invalid table %s", $2);
245 YYERROR;
248 *p = '\0';
249 add_table($2, $3, p+1);
250 free($2);
251 free($3);
253 | TABLE STRING {
254 add_table($2, "static", NULL);
255 } '{' optnl table_vals '}' {
256 table = NULL;
260 tableref : '<' STRING '>' {
261 struct table *t;
263 t = findtable($2);
264 free($2);
265 if (t == NULL)
266 YYERROR;
267 $$ = t;
271 listen : LISTEN { listener = listen_new(); }
272 listen_opts {
273 if (listener->auth_table == NULL)
274 yyerror("missing auth table");
275 if (!(listener->flags & L_TLS))
276 yyerror("can't define a non-tls listener");
277 listener = NULL;
281 listen_opts : listen_opt
282 | listen_opt listen_opts
285 listen_opt : ON STRING PORT NUMBER {
286 if (*listener->iface != '\0')
287 yyerror("listen address and port already"
288 " defined");
289 strlcpy(listener->iface, $2, sizeof(listener->iface));
290 listener->port = $4;
292 | TLS PKI STRING {
293 if (*listener->pki != '\0')
294 yyerror("listen tls pki already defined");
295 listener->flags |= L_TLS;
296 strlcpy(listener->pki, $3, sizeof(listener->pki));
298 | AUTH tableref {
299 if (listener->auth_table != NULL)
300 yyerror("listen auth already defined");
301 listener->auth_table = $2;
303 | USERDATA tableref {
304 if (listener->userdata_table != NULL)
305 yyerror("userdata table already defined");
306 listener->userdata_table = $2;
308 | VIRTUAL tableref {
309 if (listener->virtual_table != NULL)
310 yyerror("virtual table already defined");
311 listener->virtual_table = $2;
315 %%
317 struct keywords {
318 const char *k_name;
319 int k_val;
320 };
322 int
323 yyerror(const char *fmt, ...)
325 va_list ap;
326 char *msg;
328 file->errors++;
329 va_start(ap, fmt);
330 if (vasprintf(&msg, fmt, ap) == -1)
331 fatalx("yyerror vasprintf");
332 va_end(ap);
333 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
334 free(msg);
335 return 0;
338 int
339 kw_cmp(const void *k, const void *e)
341 return strcmp(k, ((const struct keywords *)e)->k_name);
344 int
345 lookup(char *s)
347 /* This has to be sorted always. */
348 static const struct keywords keywords[] = {
349 {"auth", AUTH},
350 {"cert", CERT},
351 {"include", INCLUDE},
352 {"key", KEY},
353 {"listen", LISTEN},
354 {"no", NO},
355 {"on", ON},
356 {"pki", PKI},
357 {"port", PORT},
358 {"table", TABLE},
359 {"tls", TLS},
360 {"userdata", USERDATA},
361 {"virtual", VIRTUAL},
362 {"yes", YES},
363 };
364 const struct keywords *p;
366 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
367 sizeof(keywords[0]), kw_cmp);
369 if (p)
370 return p->k_val;
371 else
372 return STRING;
375 #define START_EXPAND 1
376 #define DONE_EXPAND 2
378 static int expanding;
380 int
381 igetc(void)
383 int c;
385 while (1) {
386 if (file->ungetpos > 0)
387 c = file->ungetbuf[--file->ungetpos];
388 else
389 c = getc(file->stream);
391 if (c == START_EXPAND)
392 expanding = 1;
393 else if (c == DONE_EXPAND)
394 expanding = 0;
395 else
396 break;
398 return c;
401 int
402 lgetc(int quotec)
404 int c, next;
406 if (quotec) {
407 if ((c = igetc()) == EOF) {
408 yyerror("reached end of file while parsing "
409 "quoted string");
410 if (file == topfile || popfile() == EOF)
411 return EOF;
412 return quotec;
414 return c;
417 while ((c = igetc()) == '\\') {
418 next = igetc();
419 if (next != '\n') {
420 c = next;
421 break;
423 yylval.lineno = file->lineno;
424 file->lineno++;
427 if (c == EOF) {
428 /*
429 * Fake EOL when hit EOF for the first time. This gets line
430 * count right if last line in included file is syntactically
431 * invalid and has no newline.
432 */
433 if (file->eof_reached == 0) {
434 file->eof_reached = 1;
435 return '\n';
437 while (c == EOF) {
438 if (file == topfile || popfile() == EOF)
439 return EOF;
440 c = igetc();
443 return c;
446 void
447 lungetc(int c)
449 if (c == EOF)
450 return;
452 if (file->ungetpos >= file->ungetsize) {
453 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
454 if (p == NULL)
455 err(1, "lungetc");
456 file->ungetbuf = p;
457 file->ungetsize *= 2;
459 file->ungetbuf[file->ungetpos++] = c;
462 int
463 findeol(void)
465 int c;
467 /* Skip to either EOF or the first real EOL. */
468 while (1) {
469 c = lgetc(0);
470 if (c == '\n') {
471 file->lineno++;
472 break;
474 if (c == EOF)
475 break;
477 return ERROR;
480 #if 0
481 int my_yylex(void);
483 int
484 yylex(void)
486 int x;
488 switch (x = my_yylex()) {
489 case AUTH:
490 puts("auth");
491 break;
492 case CERT:
493 puts("cert");
494 break;
495 case ERROR:
496 puts("error");
497 break;
498 case INCLUDE:
499 puts("include");
500 break;
501 case KEY:
502 puts("key");
503 break;
504 case LISTEN:
505 puts("listen");
506 break;
507 case NO:
508 puts("no");
509 break;
510 case ON:
511 puts("on");
512 break;
513 case PKI:
514 puts("pki");
515 break;
516 case PORT:
517 puts("port");
518 break;
519 case TABLE:
520 puts("table");
521 break;
522 case TLS:
523 puts("tls");
524 break;
525 case YES:
526 puts("yes");
527 break;
528 case STRING:
529 printf("string \"%s\"\n", yylval.v.string);
530 break;
531 case NUMBER:
532 printf("number %"PRIi64"\n", yylval.v.number);
533 default:
534 printf("character ");
535 if (x == '\n')
536 printf("\\n");
537 else
538 printf("%c", x);
539 printf(" [0x%x]", x);
540 printf("\n");
541 break;
544 return x;
547 int
548 my_yylex(void)
549 #else
550 int
551 yylex(void)
552 #endif
554 char buf[8096];
555 char *p, *val;
556 int quotec, next, c;
557 int token;
559 top:
560 p = buf;
561 while ((c = lgetc(0)) == ' ' || c == '\t')
562 ; /* nothing */
564 yylval.lineno = file->lineno;
565 if (c == '#')
566 while ((c = lgetc(0)) != '\n' && c != EOF)
567 ; /* nothing */
568 if (c == '$' && !expanding) {
569 while (1) {
570 if ((c = lgetc(0)) == EOF)
571 return 0;
573 if (p + 1 >= buf + sizeof(buf) - 1) {
574 yyerror("string too long");
575 return findeol();
577 if (isalnum(c) || c == '_') {
578 *p++ = c;
579 continue;
581 *p = '\0';
582 lungetc(c);
583 break;
585 val = symget(buf);
586 if (val == NULL) {
587 yyerror("macro '%s' not defined", buf);
588 return findeol();
590 p = val + strlen(val) - 1;
591 lungetc(DONE_EXPAND);
592 while (p >= val) {
593 lungetc((unsigned char)*p);
594 p--;
596 lungetc(START_EXPAND);
597 goto top;
600 switch (c) {
601 case '\'':
602 case '"':
603 quotec = c;
604 while (1) {
605 if ((c = lgetc(quotec)) == EOF)
606 return 0;
607 if (c == '\n') {
608 file->lineno++;
609 continue;
610 } else if (c == '\\') {
611 if ((next = lgetc(quotec)) == EOF)
612 return (0);
613 if (next == quotec || next == ' ' ||
614 next == '\t')
615 c = next;
616 else if (next == '\n') {
617 file->lineno++;
618 continue;
619 } else
620 lungetc(next);
621 } else if (c == quotec) {
622 *p = '\0';
623 break;
624 } else if (c == '\0') {
625 yyerror("syntax error");
626 return findeol();
628 if (p + 1 >= buf + sizeof(buf) - 1) {
629 yyerror("string too long");
630 return findeol();
632 *p++ = c;
634 yylval.v.string = strdup(buf);
635 if (yylval.v.string == NULL)
636 err(1, "yylex: strdup");
637 return STRING;
640 #define allowed_to_end_number(x) \
641 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
643 if (c == '-' || isdigit(c)) {
644 do {
645 *p++ = c;
646 if ((size_t)(p-buf) >= sizeof(buf)) {
647 yyerror("string too long");
648 return findeol();
650 } while ((c = lgetc(0)) != EOF && isdigit(c));
651 lungetc(c);
652 if (p == buf + 1 && buf[0] == '-')
653 goto nodigits;
654 if (c == EOF || allowed_to_end_number(c)) {
655 const char *errstr = NULL;
657 *p = '\0';
658 yylval.v.number = strtonum(buf, LLONG_MIN,
659 LLONG_MAX, &errstr);
660 if (errstr) {
661 yyerror("\"%s\" invalid number: %s",
662 buf, errstr);
663 return findeol();
665 return NUMBER;
666 } else {
667 nodigits:
668 while (p > buf + 1)
669 lungetc((unsigned char)*--p);
670 c = (unsigned char)*--p;
671 if (c == '-')
672 return c;
676 #define allowed_in_string(x) \
677 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
678 x != '{' && x != '}' && \
679 x != '!' && x != '=' && x != '#' && \
680 x != ',' && x != '>'))
682 if (isalnum(c) || c == ':' || c == '_') {
683 do {
684 *p++ = c;
685 if ((size_t)(p-buf) >= sizeof(buf)) {
686 yyerror("string too long");
687 return findeol();
689 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
690 lungetc(c);
691 *p = '\0';
692 if ((token = lookup(buf)) == STRING)
693 if ((yylval.v.string = strdup(buf)) == NULL)
694 err(1, "yylex: strdup");
695 return token;
697 if (c == '\n') {
698 yylval.lineno = file->lineno;
699 file->lineno++;
701 if (c == EOF)
702 return 0;
703 return c;
706 int
707 check_file_secrecy(int fd, const char *fname)
709 struct stat st;
711 if (fstat(fd, &st)) {
712 log_warn("cannot stat %s", fname);
713 return -1;
715 if (st.st_uid != 0 && st.st_uid != getuid()) {
716 log_warnx("%s: owner not root or current user", fname);
717 return -1;
719 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
720 log_warnx("%s: group writable or world read/writable", fname);
721 return -1;
723 return 0;
726 struct file *
727 pushfile(const char *name, int secret)
729 struct file *nfile;
731 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
732 log_warn("calloc");
733 return NULL;
735 if ((nfile->name = strdup(name)) == NULL) {
736 log_warn("strdup");
737 free(nfile);
738 return NULL;
740 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
741 log_warn("%s", nfile->name);
742 free(nfile->name);
743 free(nfile);
744 return NULL;
745 } else if (secret &&
746 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
747 fclose(nfile->stream);
748 free(nfile->name);
749 free(nfile);
750 return NULL;
752 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
753 nfile->ungetsize = 16;
754 nfile->ungetbuf = malloc(nfile->ungetsize);
755 if (nfile->ungetbuf == NULL) {
756 log_warn("malloc");
757 fclose(nfile->stream);
758 free(nfile->name);
759 free(nfile);
760 return NULL;
762 TAILQ_INSERT_TAIL(&files, nfile, entry);
763 return nfile;
766 int
767 popfile(void)
769 struct file *prev;
771 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
772 prev->errors += file->errors;
774 TAILQ_REMOVE(&files, file, entry);
775 fclose(file->stream);
776 free(file->name);
777 free(file->ungetbuf);
778 free(file);
779 file = prev;
780 return file ? 0 : EOF;
783 struct kd_conf *
784 parse_config(const char *filename)
786 struct sym *sym, *next;
788 counter = 0;
789 conf = config_new_empty();
791 file = pushfile(filename, 0);
792 if (file == NULL) {
793 free(conf);
794 return NULL;
796 topfile = file;
798 yyparse();
799 errors = file->errors;
800 popfile();
802 /* Free macros and check which have not been used. */
803 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
804 if (verbose && !sym->used)
805 fprintf(stderr, "warning: macro '%s' not used\n",
806 sym->nam);
807 if (!sym->persist) {
808 free(sym->nam);
809 free(sym->val);
810 TAILQ_REMOVE(&symhead, sym, entry);
811 free(sym);
815 if (errors) {
816 clear_config(conf);
817 return NULL;
820 return conf;
823 int
824 symset(const char *nam, const char *val, int persist)
826 struct sym *sym;
828 TAILQ_FOREACH(sym, &symhead, entry) {
829 if (strcmp(nam, sym->nam) == 0)
830 break;
833 if (sym != NULL) {
834 if (sym->persist == 1)
835 return 0;
836 else {
837 free(sym->nam);
838 free(sym->val);
839 TAILQ_REMOVE(&symhead, sym, entry);
840 free(sym);
843 if ((sym = calloc(1, sizeof(*sym))) == NULL)
844 return -1;
846 sym->nam = strdup(nam);
847 if (sym->nam == NULL) {
848 free(sym);
849 return -1;
851 sym->val = strdup(val);
852 if (sym->val == NULL) {
853 free(sym->nam);
854 free(sym);
855 return -1;
857 sym->used = 0;
858 sym->persist = persist;
859 TAILQ_INSERT_TAIL(&symhead, sym, entry);
860 return 0;
863 int
864 cmdline_symset(char *s)
866 char *sym, *val;
867 int ret;
869 if ((val = strrchr(s, '=')) == NULL)
870 return -1;
871 sym = strndup(s, val - s);
872 if (sym == NULL)
873 errx(1, "%s: strndup", __func__);
874 ret = symset(sym, val + 1, 1);
875 free(sym);
877 return ret;
880 char *
881 symget(const char *nam)
883 struct sym *sym;
885 TAILQ_FOREACH(sym, &symhead, entry) {
886 if (strcmp(nam, sym->nam) == 0) {
887 sym->used = 1;
888 return sym->val;
891 return NULL;
894 void
895 clear_config(struct kd_conf *xconf)
897 /* free stuff? */
899 free(xconf);
902 static void
903 add_table(const char *name, const char *type, const char *path)
905 if (table_open(conf, name, type, path) == -1)
906 yyerror("can't initialize table %s", name);
907 table = STAILQ_FIRST(&conf->table_head)->table;
910 static struct table *
911 findtable(const char *name)
913 struct kd_tables_conf *i;
915 STAILQ_FOREACH(i, &conf->table_head, entry) {
916 if (!strcmp(i->table->t_name, name))
917 return i->table;
920 yyerror("unknown table %s", name);
921 return NULL;
924 static void
925 add_cert(const char *name, const char *path)
927 struct kd_pki_conf *pki;
929 STAILQ_FOREACH(pki, &conf->pki_head, entry) {
930 if (strcmp(name, pki->name) != 0)
931 continue;
933 if (pki->cert != NULL) {
934 yyerror("duplicate `pki %s cert'", name);
935 return;
938 goto set;
941 pki = xcalloc(1, sizeof(*pki));
942 strlcpy(pki->name, name, sizeof(pki->name));
943 STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
945 set:
946 if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
947 fatal(NULL);
950 static void
951 add_key(const char *name, const char *path)
953 struct kd_pki_conf *pki;
955 STAILQ_FOREACH(pki, &conf->pki_head, entry) {
956 if (strcmp(name, pki->name) != 0)
957 continue;
959 if (pki->key != NULL) {
960 yyerror("duplicate `pki %s key'", name);
961 return;
964 goto set;
967 pki = xcalloc(1, sizeof(*pki));
968 strlcpy(pki->name, name, sizeof(pki->name));
969 STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
971 set:
972 if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
973 fatal(NULL);
976 static struct kd_listen_conf *
977 listen_new(void)
979 struct kd_listen_conf *l;
981 l = xcalloc(1, sizeof(*l));
982 l->id = counter++;
983 l->fd = -1;
985 STAILQ_INSERT_HEAD(&conf->listen_head, l, entry);
986 return l;