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 %{
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/stat.h>
30 #include <ctype.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <event.h>
34 #include <inttypes.h>
35 #include <limits.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <syslog.h>
41 #include <unistd.h>
42 #include <imsg.h>
44 #include "log.h"
45 #include "kamid.h"
46 #include "table.h"
47 #include "utils.h"
49 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
50 static struct file {
51 TAILQ_ENTRY(file) entry;
52 FILE *stream;
53 char *name;
54 size_t ungetpos;
55 size_t ungetsize;
56 u_char *ungetbuf;
57 int eof_reached;
58 int lineno;
59 int errors;
60 } *file, *topfile;
61 struct file *pushfile(const char *, int);
62 int popfile(void);
63 int check_file_secrecy(int, const char *);
64 int yyparse(void);
65 int yylex(void);
66 int yyerror(const char *, ...)
67 __attribute__((__format__ (printf, 1, 2)))
68 __attribute__((__nonnull__ (1)));
69 int kw_cmp(const void *, const void *);
70 int lookup(char *);
71 int igetc(void);
72 int lgetc(int);
73 void lungetc(int);
74 int findeol(void);
76 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
77 struct sym {
78 TAILQ_ENTRY(sym) entry;
79 int used;
80 int persist;
81 char *nam;
82 char *val;
83 };
85 int symset(const char *, const char *, int);
86 char *symget(const char *);
88 void clear_config(struct kd_conf *xconf);
90 static void add_table(const char *, const char *, const char *);
91 static struct table *findtable(const char *name);
92 static void add_cert(const char *, const char *);
93 static void add_key(const char *, const char *);
94 static struct kd_listen_conf *listen_new(void);
96 static uint32_t counter;
97 static struct table *table;
98 static struct kd_listen_conf *listener;
99 static struct kd_conf *conf;
100 static int errors;
102 typedef struct {
103 union {
104 int64_t number;
105 char *string;
106 struct table *table;
107 } v;
108 int lineno;
109 } YYSTYPE;
111 %}
113 %token AUTH
114 %token CERT
115 %token ERROR
116 %token INCLUDE
117 %token KEY
118 %token LISTEN
119 %token NO
120 %token ON
121 %token PKI PORT
122 %token TABLE TLS
123 %token USERDATA
124 %token VIRTUAL
125 %token YES
127 %token <v.string> STRING
128 %token <v.number> NUMBER
129 %type <v.number> yesno
130 %type <v.string> string
131 %type <v.table> tableref
133 %%
135 grammar : /* empty */
136 | grammar include '\n'
137 | grammar '\n'
138 | grammar table '\n'
139 | grammar pki '\n'
140 | grammar listen '\n'
141 | grammar varset '\n'
142 | grammar error '\n' { file->errors++; }
145 include : INCLUDE STRING {
146 struct file *nfile;
148 if ((nfile = pushfile($2, 0)) == NULL) {
149 yyerror("failed to include file %s", $2);
150 free($2);
151 YYERROR;
153 free($2);
155 file = nfile;
156 lungetc('\n');
160 string : string STRING {
161 if (asprintf(&$$, "%s %s", $1, $2) == -1) {
162 free($1);
163 free($2);
164 yyerror("string: asprintf");
165 YYERROR;
167 free($1);
168 free($2);
170 | STRING
173 yesno : YES { $$ = 1; }
174 | NO { $$ = 0; }
177 optnl : '\n' optnl /* zero or more newlines */
178 | /*empty*/
181 nl : '\n' optnl /* one or more newlines */
184 arrow : '=' '>' ;
186 comma : ',' optnl
189 varset : STRING '=' string {
190 char *s = $1;
191 if (verbose)
192 printf("%s = \"%s\"\n", $1, $3);
193 while (*s++) {
194 if (isspace((unsigned char)*s)) {
195 yyerror("macro name cannot contain "
196 "whitespace");
197 free($1);
198 free($3);
199 YYERROR;
202 if (symset($1, $3, 0) == -1)
203 fatal("cannot store variable");
204 free($1);
205 free($3);
209 pki : PKI STRING CERT STRING { add_cert($2, $4); }
210 | PKI STRING KEY STRING { add_key($2, $4); }
213 table_kp : string arrow string optnl {
214 if (table_add(table, $1, $3) == -1)
215 yyerror("can't add to table %s",
216 table->t_name);
217 free($1);
218 free($3);
222 table_kps : table_kp
223 | table_kp comma table_kps
226 stringel : STRING {
227 if (table_add(table, $1, NULL) == -1)
228 yyerror("can't add to table %s",
229 table->t_name);
230 free($1);
234 string_list : stringel
235 | stringel comma string_list
238 table_vals : table_kps
239 | string_list
242 table : TABLE STRING STRING {
243 char *p;
245 if ((p = strchr($3, ':')) == NULL) {
246 yyerror("invalid table %s", $2);
247 YYERROR;
250 *p = '\0';
251 add_table($2, $3, p+1);
252 free($2);
253 free($3);
255 | TABLE STRING {
256 add_table($2, "static", NULL);
257 } '{' optnl table_vals '}' {
258 table = NULL;
262 tableref : '<' STRING '>' {
263 struct table *t;
265 t = findtable($2);
266 free($2);
267 if (t == NULL)
268 YYERROR;
269 $$ = t;
273 listen : LISTEN { listener = listen_new(); }
274 listen_opts {
275 if (listener->auth_table == NULL)
276 yyerror("missing auth table");
277 if (!(listener->flags & L_TLS))
278 yyerror("can't define a non-tls listener");
279 listener = NULL;
283 listen_opts : listen_opt
284 | listen_opt listen_opts
287 listen_opt : ON STRING PORT NUMBER {
288 if (*listener->iface != '\0')
289 yyerror("listen address and port already"
290 " defined");
291 strlcpy(listener->iface, $2, sizeof(listener->iface));
292 listener->port = $4;
294 | TLS PKI STRING {
295 if (*listener->pki != '\0')
296 yyerror("listen tls pki already defined");
297 listener->flags |= L_TLS;
298 strlcpy(listener->pki, $3, sizeof(listener->pki));
300 | AUTH tableref {
301 if (listener->auth_table != NULL)
302 yyerror("listen auth already defined");
303 listener->auth_table = $2;
305 | USERDATA tableref {
306 if (listener->userdata_table != NULL)
307 yyerror("userdata table already defined");
308 listener->userdata_table = $2;
310 | VIRTUAL tableref {
311 if (listener->virtual_table != NULL)
312 yyerror("virtual table already defined");
313 listener->virtual_table = $2;
317 %%
319 struct keywords {
320 const char *k_name;
321 int k_val;
322 };
324 int
325 yyerror(const char *fmt, ...)
327 va_list ap;
328 char *msg;
330 file->errors++;
331 va_start(ap, fmt);
332 if (vasprintf(&msg, fmt, ap) == -1)
333 fatalx("yyerror vasprintf");
334 va_end(ap);
335 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
336 free(msg);
337 return 0;
340 int
341 kw_cmp(const void *k, const void *e)
343 return strcmp(k, ((const struct keywords *)e)->k_name);
346 int
347 lookup(char *s)
349 /* This has to be sorted always. */
350 static const struct keywords keywords[] = {
351 {"auth", AUTH},
352 {"cert", CERT},
353 {"include", INCLUDE},
354 {"key", KEY},
355 {"listen", LISTEN},
356 {"no", NO},
357 {"on", ON},
358 {"pki", PKI},
359 {"port", PORT},
360 {"table", TABLE},
361 {"tls", TLS},
362 {"userdata", USERDATA},
363 {"virtual", VIRTUAL},
364 {"yes", YES},
365 };
366 const struct keywords *p;
368 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
369 sizeof(keywords[0]), kw_cmp);
371 if (p)
372 return p->k_val;
373 else
374 return STRING;
377 #define START_EXPAND 1
378 #define DONE_EXPAND 2
380 static int expanding;
382 int
383 igetc(void)
385 int c;
387 while (1) {
388 if (file->ungetpos > 0)
389 c = file->ungetbuf[--file->ungetpos];
390 else
391 c = getc(file->stream);
393 if (c == START_EXPAND)
394 expanding = 1;
395 else if (c == DONE_EXPAND)
396 expanding = 0;
397 else
398 break;
400 return c;
403 int
404 lgetc(int quotec)
406 int c, next;
408 if (quotec) {
409 if ((c = igetc()) == EOF) {
410 yyerror("reached end of file while parsing "
411 "quoted string");
412 if (file == topfile || popfile() == EOF)
413 return EOF;
414 return quotec;
416 return c;
419 while ((c = igetc()) == '\\') {
420 next = igetc();
421 if (next != '\n') {
422 c = next;
423 break;
425 yylval.lineno = file->lineno;
426 file->lineno++;
429 if (c == EOF) {
430 /*
431 * Fake EOL when hit EOF for the first time. This gets line
432 * count right if last line in included file is syntactically
433 * invalid and has no newline.
434 */
435 if (file->eof_reached == 0) {
436 file->eof_reached = 1;
437 return '\n';
439 while (c == EOF) {
440 if (file == topfile || popfile() == EOF)
441 return EOF;
442 c = igetc();
445 return c;
448 void
449 lungetc(int c)
451 if (c == EOF)
452 return;
454 if (file->ungetpos >= file->ungetsize) {
455 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
456 if (p == NULL)
457 err(1, "lungetc");
458 file->ungetbuf = p;
459 file->ungetsize *= 2;
461 file->ungetbuf[file->ungetpos++] = c;
464 int
465 findeol(void)
467 int c;
469 /* Skip to either EOF or the first real EOL. */
470 while (1) {
471 c = lgetc(0);
472 if (c == '\n') {
473 file->lineno++;
474 break;
476 if (c == EOF)
477 break;
479 return ERROR;
482 #if 0
483 int my_yylex(void);
485 int
486 yylex(void)
488 int x;
490 switch (x = my_yylex()) {
491 case AUTH:
492 puts("auth");
493 break;
494 case CERT:
495 puts("cert");
496 break;
497 case ERROR:
498 puts("error");
499 break;
500 case INCLUDE:
501 puts("include");
502 break;
503 case KEY:
504 puts("key");
505 break;
506 case LISTEN:
507 puts("listen");
508 break;
509 case NO:
510 puts("no");
511 break;
512 case ON:
513 puts("on");
514 break;
515 case PKI:
516 puts("pki");
517 break;
518 case PORT:
519 puts("port");
520 break;
521 case TABLE:
522 puts("table");
523 break;
524 case TLS:
525 puts("tls");
526 break;
527 case YES:
528 puts("yes");
529 break;
530 case STRING:
531 printf("string \"%s\"\n", yylval.v.string);
532 break;
533 case NUMBER:
534 printf("number %"PRIi64"\n", yylval.v.number);
535 default:
536 printf("character ");
537 if (x == '\n')
538 printf("\\n");
539 else
540 printf("%c", x);
541 printf(" [0x%x]", x);
542 printf("\n");
543 break;
546 return x;
549 int
550 my_yylex(void)
551 #else
552 int
553 yylex(void)
554 #endif
556 char buf[8096];
557 char *p, *val;
558 int quotec, next, c;
559 int token;
561 top:
562 p = buf;
563 while ((c = lgetc(0)) == ' ' || c == '\t')
564 ; /* nothing */
566 yylval.lineno = file->lineno;
567 if (c == '#')
568 while ((c = lgetc(0)) != '\n' && c != EOF)
569 ; /* nothing */
570 if (c == '$' && !expanding) {
571 while (1) {
572 if ((c = lgetc(0)) == EOF)
573 return 0;
575 if (p + 1 >= buf + sizeof(buf) - 1) {
576 yyerror("string too long");
577 return findeol();
579 if (isalnum(c) || c == '_') {
580 *p++ = c;
581 continue;
583 *p = '\0';
584 lungetc(c);
585 break;
587 val = symget(buf);
588 if (val == NULL) {
589 yyerror("macro '%s' not defined", buf);
590 return findeol();
592 p = val + strlen(val) - 1;
593 lungetc(DONE_EXPAND);
594 while (p >= val) {
595 lungetc((unsigned char)*p);
596 p--;
598 lungetc(START_EXPAND);
599 goto top;
602 switch (c) {
603 case '\'':
604 case '"':
605 quotec = c;
606 while (1) {
607 if ((c = lgetc(quotec)) == EOF)
608 return 0;
609 if (c == '\n') {
610 file->lineno++;
611 continue;
612 } else if (c == '\\') {
613 if ((next = lgetc(quotec)) == EOF)
614 return (0);
615 if (next == quotec || next == ' ' ||
616 next == '\t')
617 c = next;
618 else if (next == '\n') {
619 file->lineno++;
620 continue;
621 } else
622 lungetc(next);
623 } else if (c == quotec) {
624 *p = '\0';
625 break;
626 } else if (c == '\0') {
627 yyerror("syntax error");
628 return findeol();
630 if (p + 1 >= buf + sizeof(buf) - 1) {
631 yyerror("string too long");
632 return findeol();
634 *p++ = c;
636 yylval.v.string = strdup(buf);
637 if (yylval.v.string == NULL)
638 err(1, "yylex: strdup");
639 return STRING;
642 #define allowed_to_end_number(x) \
643 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
645 if (c == '-' || isdigit(c)) {
646 do {
647 *p++ = c;
648 if ((size_t)(p-buf) >= sizeof(buf)) {
649 yyerror("string too long");
650 return findeol();
652 } while ((c = lgetc(0)) != EOF && isdigit(c));
653 lungetc(c);
654 if (p == buf + 1 && buf[0] == '-')
655 goto nodigits;
656 if (c == EOF || allowed_to_end_number(c)) {
657 const char *errstr = NULL;
659 *p = '\0';
660 yylval.v.number = strtonum(buf, LLONG_MIN,
661 LLONG_MAX, &errstr);
662 if (errstr) {
663 yyerror("\"%s\" invalid number: %s",
664 buf, errstr);
665 return findeol();
667 return NUMBER;
668 } else {
669 nodigits:
670 while (p > buf + 1)
671 lungetc((unsigned char)*--p);
672 c = (unsigned char)*--p;
673 if (c == '-')
674 return c;
678 #define allowed_in_string(x) \
679 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
680 x != '{' && x != '}' && \
681 x != '!' && x != '=' && x != '#' && \
682 x != ',' && x != '>'))
684 if (isalnum(c) || c == ':' || c == '_') {
685 do {
686 *p++ = c;
687 if ((size_t)(p-buf) >= sizeof(buf)) {
688 yyerror("string too long");
689 return findeol();
691 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
692 lungetc(c);
693 *p = '\0';
694 if ((token = lookup(buf)) == STRING)
695 if ((yylval.v.string = strdup(buf)) == NULL)
696 err(1, "yylex: strdup");
697 return token;
699 if (c == '\n') {
700 yylval.lineno = file->lineno;
701 file->lineno++;
703 if (c == EOF)
704 return 0;
705 return c;
708 int
709 check_file_secrecy(int fd, const char *fname)
711 struct stat st;
713 if (fstat(fd, &st)) {
714 log_warn("cannot stat %s", fname);
715 return -1;
717 if (st.st_uid != 0 && st.st_uid != getuid()) {
718 log_warnx("%s: owner not root or current user", fname);
719 return -1;
721 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
722 log_warnx("%s: group writable or world read/writable", fname);
723 return -1;
725 return 0;
728 struct file *
729 pushfile(const char *name, int secret)
731 struct file *nfile;
733 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
734 log_warn("calloc");
735 return NULL;
737 if ((nfile->name = strdup(name)) == NULL) {
738 log_warn("strdup");
739 free(nfile);
740 return NULL;
742 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
743 log_warn("%s", nfile->name);
744 free(nfile->name);
745 free(nfile);
746 return NULL;
747 } else if (secret &&
748 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
749 fclose(nfile->stream);
750 free(nfile->name);
751 free(nfile);
752 return NULL;
754 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
755 nfile->ungetsize = 16;
756 nfile->ungetbuf = malloc(nfile->ungetsize);
757 if (nfile->ungetbuf == NULL) {
758 log_warn("malloc");
759 fclose(nfile->stream);
760 free(nfile->name);
761 free(nfile);
762 return NULL;
764 TAILQ_INSERT_TAIL(&files, nfile, entry);
765 return nfile;
768 int
769 popfile(void)
771 struct file *prev;
773 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
774 prev->errors += file->errors;
776 TAILQ_REMOVE(&files, file, entry);
777 fclose(file->stream);
778 free(file->name);
779 free(file->ungetbuf);
780 free(file);
781 file = prev;
782 return file ? 0 : EOF;
785 struct kd_conf *
786 parse_config(const char *filename)
788 struct sym *sym, *next;
790 counter = 0;
791 conf = config_new_empty();
793 file = pushfile(filename, 0);
794 if (file == NULL) {
795 free(conf);
796 return NULL;
798 topfile = file;
800 yyparse();
801 errors = file->errors;
802 popfile();
804 /* Free macros and check which have not been used. */
805 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
806 if (verbose && !sym->used)
807 fprintf(stderr, "warning: macro '%s' not used\n",
808 sym->nam);
809 if (!sym->persist) {
810 free(sym->nam);
811 free(sym->val);
812 TAILQ_REMOVE(&symhead, sym, entry);
813 free(sym);
817 if (errors) {
818 clear_config(conf);
819 return NULL;
822 return conf;
825 int
826 symset(const char *nam, const char *val, int persist)
828 struct sym *sym;
830 TAILQ_FOREACH(sym, &symhead, entry) {
831 if (strcmp(nam, sym->nam) == 0)
832 break;
835 if (sym != NULL) {
836 if (sym->persist == 1)
837 return 0;
838 else {
839 free(sym->nam);
840 free(sym->val);
841 TAILQ_REMOVE(&symhead, sym, entry);
842 free(sym);
845 if ((sym = calloc(1, sizeof(*sym))) == NULL)
846 return -1;
848 sym->nam = strdup(nam);
849 if (sym->nam == NULL) {
850 free(sym);
851 return -1;
853 sym->val = strdup(val);
854 if (sym->val == NULL) {
855 free(sym->nam);
856 free(sym);
857 return -1;
859 sym->used = 0;
860 sym->persist = persist;
861 TAILQ_INSERT_TAIL(&symhead, sym, entry);
862 return 0;
865 int
866 cmdline_symset(char *s)
868 char *sym, *val;
869 int ret;
871 if ((val = strrchr(s, '=')) == NULL)
872 return -1;
873 sym = strndup(s, val - s);
874 if (sym == NULL)
875 errx(1, "%s: strndup", __func__);
876 ret = symset(sym, val + 1, 1);
877 free(sym);
879 return ret;
882 char *
883 symget(const char *nam)
885 struct sym *sym;
887 TAILQ_FOREACH(sym, &symhead, entry) {
888 if (strcmp(nam, sym->nam) == 0) {
889 sym->used = 1;
890 return sym->val;
893 return NULL;
896 void
897 clear_config(struct kd_conf *xconf)
899 /* free stuff? */
901 free(xconf);
904 static void
905 add_table(const char *name, const char *type, const char *path)
907 if (table_open(conf, name, type, path) == -1)
908 yyerror("can't initialize table %s", name);
909 table = STAILQ_FIRST(&conf->table_head)->table;
912 static struct table *
913 findtable(const char *name)
915 struct kd_tables_conf *i;
917 STAILQ_FOREACH(i, &conf->table_head, entry) {
918 if (!strcmp(i->table->t_name, name))
919 return i->table;
922 yyerror("unknown table %s", name);
923 return NULL;
926 static void
927 add_cert(const char *name, const char *path)
929 struct kd_pki_conf *pki;
931 STAILQ_FOREACH(pki, &conf->pki_head, entry) {
932 if (strcmp(name, pki->name) != 0)
933 continue;
935 if (pki->cert != NULL) {
936 yyerror("duplicate `pki %s cert'", name);
937 return;
940 goto set;
943 pki = xcalloc(1, sizeof(*pki));
944 strlcpy(pki->name, name, sizeof(pki->name));
945 STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
947 set:
948 if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
949 fatal(NULL);
952 static void
953 add_key(const char *name, const char *path)
955 struct kd_pki_conf *pki;
957 STAILQ_FOREACH(pki, &conf->pki_head, entry) {
958 if (strcmp(name, pki->name) != 0)
959 continue;
961 if (pki->key != NULL) {
962 yyerror("duplicate `pki %s key'", name);
963 return;
966 goto set;
969 pki = xcalloc(1, sizeof(*pki));
970 strlcpy(pki->name, name, sizeof(pki->name));
971 STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry);
973 set:
974 if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
975 fatal(NULL);
978 static struct kd_listen_conf *
979 listen_new(void)
981 struct kd_listen_conf *l;
983 l = xcalloc(1, sizeof(*l));
984 l->id = counter++;
985 l->fd = -1;
987 STAILQ_INSERT_HEAD(&conf->listen_head, l, entry);
988 return l;