Blob


1 /*
2 * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
3 * Copyright (c) 2007-2016 Reyk Floeter <reyk@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 <sys/types.h>
26 #include <sys/queue.h>
27 #include <sys/tree.h>
28 #include <sys/uio.h>
30 #include <err.h>
31 #include <event.h>
32 #include <inttypes.h>
33 #include <netdb.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <limits.h>
37 #include <stdarg.h>
38 #include <stdint.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <ctype.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <pwd.h>
45 #include <imsg.h>
47 #include "log.h"
48 #include "proc.h"
50 #include "galileo.h"
52 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
53 static struct file {
54 TAILQ_ENTRY(file) entry;
55 FILE *stream;
56 char *name;
57 size_t ungetpos;
58 size_t ungetsize;
59 u_char *ungetbuf;
60 int eof_reached;
61 int lineno;
62 int errors;
63 } *file, *topfile;
64 struct file *pushfile(const char *, int);
65 int popfile(void);
66 int yyparse(void);
67 int yylex(void);
68 int yyerror(const char *, ...)
69 __attribute__((__format__ (printf, 1, 2)))
70 __attribute__((__nonnull__ (1)));
71 int kw_cmp(const void *, const void *);
72 int lookup(char *);
73 int igetc(void);
74 int lgetc(int);
75 void lungetc(int);
76 int findeol(void);
78 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
79 struct sym {
80 TAILQ_ENTRY(sym) entry;
81 int used;
82 int persist;
83 char *nam;
84 char *val;
85 };
86 int symset(const char *, const char *, int);
87 char *symget(const char *);
89 int getservice(const char *);
91 static struct galileo *conf = NULL;
92 static struct proxy *pr = NULL;
93 static int errors;
95 typedef struct {
96 union {
97 int64_t number;
98 char *string;
99 } v;
100 int lineno;
101 } YYSTYPE;
103 %}
105 %token INCLUDE ERROR
106 %token BAR CHROOT FOOTER HOSTNAME IMAGE NAVIGATION NO PORT
107 %token PREFORK PREVIEW PROXY SOURCE STYLESHEET TLS
108 %token <v.number> NUMBER
109 %token <v.string> STRING
110 %type <v.number> port
111 %type <v.string> string
113 %%
115 grammar : /* empty */
116 | grammar include '\n'
117 | grammar '\n'
118 | grammar varset '\n'
119 | grammar main '\n'
120 | grammar proxy '\n'
121 | grammar error '\n' { file->errors++; }
124 include : INCLUDE string {
125 struct file *nfile;
127 if ((nfile = pushfile($2, 0)) == NULL) {
128 yyerror("failed to include file %s", $2);
129 free($2);
130 YYERROR;
132 free($2);
134 file = nfile;
135 lungetc('\n');
139 varset : STRING '=' STRING {
140 char *s = $1;
141 while (*s++) {
142 if (isspace((unsigned char)*s)) {
143 yyerror("macro name cannot contain "
144 "whitespace");
145 free($1);
146 free($3);
147 YYERROR;
150 if (symset($1, $3, 0) == -1)
151 fatalx("cannot store variable");
152 free($1);
153 free($3);
157 main : PREFORK NUMBER {
158 if ($2 <= 0 || $2 > PROC_MAX_INSTANCES) {
159 yyerror("invalid number of preforked "
160 "proxies: %"PRId64, $2);
161 YYERROR;
163 conf->sc_prefork = $2;
165 | CHROOT STRING {
166 size_t n;
168 n = strlcpy(conf->sc_chroot, $2,
169 sizeof(conf->sc_chroot));
170 if (n >= sizeof(conf->sc_chroot))
171 yyerror("chroot path too long!");
172 free($2);
176 proxy : PROXY STRING {
177 struct proxy *p;
178 size_t n;
180 if ((p = calloc(1, sizeof(*p))) == NULL)
181 fatal("calloc");
183 n = strlcpy(p->pr_conf.host, $2,
184 sizeof(p->pr_conf.host));
185 if (n >= sizeof(p->pr_conf.host)) {
186 yyerror("server name too long");
187 free($2);
188 free(p);
189 YYERROR;
191 free($2);
193 pr = p;
194 } '{' optnl proxyopts_l '}' {
195 /* check if duplicate */
196 if (proxy_match(conf, pr->pr_conf.host) != NULL)
197 yyerror("duplicate proxy `%s'",
198 pr->pr_conf.host);
200 TAILQ_INSERT_TAIL(&conf->sc_proxies, pr, pr_entry);
202 if (*pr->pr_conf.proxy_addr == '\0')
203 yyerror("missing source in proxy block `%s'",
204 pr->pr_conf.host);
206 pr = NULL;
210 proxyopts_l : proxyopts_l proxyoptsl nl
211 | proxyoptsl optnl
214 proxyoptsl : SOURCE STRING proxyport {
215 size_t n;
217 n = strlcpy(pr->pr_conf.proxy_addr, $2,
218 sizeof(pr->pr_conf.proxy_addr));
219 if (n >= sizeof(pr->pr_conf.proxy_addr))
220 yyerror("proxy source too long!");
222 if (*pr->pr_conf.proxy_name == '\0') {
223 n = strlcpy(pr->pr_conf.proxy_name, $2,
224 sizeof(pr->pr_conf.proxy_name));
225 if (n >= sizeof(pr->pr_conf.proxy_name))
226 yyerror("proxy hostname too long!");
229 free($2);
231 | HOSTNAME STRING {
232 size_t n;
234 n = strlcpy(pr->pr_conf.proxy_name, $2,
235 sizeof(pr->pr_conf.proxy_name));
236 if (n >= sizeof(pr->pr_conf.proxy_name))
237 yyerror("proxy hostname too long!");
238 free($2);
240 | STYLESHEET string {
241 size_t n;
243 n = strlcpy(pr->pr_conf.stylesheet, $2,
244 sizeof(pr->pr_conf.stylesheet));
245 if (n >= sizeof(pr->pr_conf.stylesheet))
246 yyerror("stylesheet path too long!");
247 free($2);
249 | NO FOOTER {
250 pr->pr_conf.flags |= PROXY_NO_FOOTER;
252 | NO IMAGE PREVIEW {
253 pr->pr_conf.flags |= PROXY_NO_IMGPRV;
255 | NO NAVIGATION BAR {
256 pr->pr_conf.flags |= PROXY_NO_NAVBAR;
258 | NO TLS {
259 pr->pr_conf.flags |= PROXY_NO_TLS;
263 proxyport : /* empty */ {
264 strlcpy(pr->pr_conf.proxy_port, "1965",
265 sizeof(pr->pr_conf.proxy_port));
267 | PORT port {
268 size_t len;
269 int n;
271 len = sizeof(pr->pr_conf.proxy_port);
272 n = snprintf(pr->pr_conf.proxy_port, len,
273 "%"PRId64, $2);
274 if (n < 0 || (size_t)n >= len)
275 fatal("port number too long?");
276 };
278 port : NUMBER {
279 if ($1 <= 0 || $1 > (int)USHRT_MAX) {
280 yyerror("invalid port: %"PRId64, $1);
281 YYERROR;
283 $$ = $1;
285 | STRING {
286 int val;
288 if ((val = getservice($1)) == -1) {
289 yyerror("invalid port: %s", $1);
290 free($1);
291 YYERROR;
293 free($1);
294 $$ = val;
298 string : STRING string {
299 if (asprintf(&$$, "%s%s", $1, $2) == -1)
300 fatal("asprintf string");
301 free($1);
302 free($2);
304 | STRING
307 optnl : '\n' optnl
311 nl : '\n' optnl
314 %%
316 struct keywords {
317 const char *k_name;
318 int k_val;
319 };
321 int
322 yyerror(const char *fmt, ...)
324 va_list ap;
325 char *msg;
327 file->errors++;
328 va_start(ap, fmt);
329 if (vasprintf(&msg, fmt, ap) == -1)
330 fatal("yyerror vasprintf");
331 va_end(ap);
332 log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
333 free(msg);
334 return (0);
337 int
338 kw_cmp(const void *k, const void *e)
340 return (strcmp(k, ((const struct keywords *)e)->k_name));
343 int
344 lookup(char *s)
346 /* this has to be sorted always */
347 static const struct keywords keywords[] = {
348 { "bar", BAR },
349 { "chroot", CHROOT },
350 { "footer", FOOTER },
351 { "hostname", HOSTNAME },
352 { "image", IMAGE },
353 { "include", INCLUDE },
354 { "navigation", NAVIGATION },
355 { "no", NO },
356 { "port", PORT },
357 { "prefork", PREFORK },
358 { "preview", PREVIEW },
359 { "proxy", PROXY },
360 { "source", SOURCE },
361 { "stylesheet", STYLESHEET},
362 { "tls", TLS },
363 };
364 const struct keywords *p;
366 p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]),
367 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++;
426 if (c == '\t' || c == ' ') {
427 /* Compress blanks to a single space. */
428 do {
429 c = getc(file->stream);
430 } while (c == '\t' || c == ' ');
431 ungetc(c, file->stream);
432 c = ' ';
435 if (c == EOF) {
436 /*
437 * Fake EOL when hit EOF for the first time. This gets line
438 * count right if last line in included file is syntactically
439 * invalid and has no newline.
440 */
441 if (file->eof_reached == 0) {
442 file->eof_reached = 1;
443 return ('\n');
445 while (c == EOF) {
446 if (file == topfile || popfile() == EOF)
447 return (EOF);
448 c = igetc();
451 return (c);
454 void
455 lungetc(int c)
457 if (c == EOF)
458 return;
460 if (file->ungetpos >= file->ungetsize) {
461 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
462 if (p == NULL)
463 err(1, "%s", __func__);
464 file->ungetbuf = p;
465 file->ungetsize *= 2;
467 file->ungetbuf[file->ungetpos++] = c;
470 int
471 findeol(void)
473 int c;
475 /* skip to either EOF or the first real EOL */
476 while (1) {
477 c = lgetc(0);
478 if (c == '\n') {
479 file->lineno++;
480 break;
482 if (c == EOF)
483 break;
485 return (ERROR);
488 int
489 yylex(void)
491 char buf[8096];
492 char *p, *val;
493 int quotec, next, c;
494 int token;
496 top:
497 p = buf;
498 while ((c = lgetc(0)) == ' ' || c == '\t')
499 ; /* nothing */
501 yylval.lineno = file->lineno;
502 if (c == '#')
503 while ((c = lgetc(0)) != '\n' && c != EOF)
504 ; /* nothing */
505 if (c == '$' && !expanding) {
506 while (1) {
507 if ((c = lgetc(0)) == EOF)
508 return (0);
510 if (p + 1 >= buf + sizeof(buf) - 1) {
511 yyerror("string too long");
512 return (findeol());
514 if (isalnum(c) || c == '_') {
515 *p++ = c;
516 continue;
518 *p = '\0';
519 lungetc(c);
520 break;
522 val = symget(buf);
523 if (val == NULL) {
524 yyerror("macro '%s' not defined", buf);
525 return (findeol());
527 p = val + strlen(val) - 1;
528 lungetc(DONE_EXPAND);
529 while (p >= val) {
530 lungetc((unsigned char)*p);
531 p--;
533 lungetc(START_EXPAND);
534 goto top;
537 switch (c) {
538 case '\'':
539 case '"':
540 quotec = c;
541 while (1) {
542 if ((c = lgetc(quotec)) == EOF)
543 return (0);
544 if (c == '\n') {
545 file->lineno++;
546 continue;
547 } else if (c == '\\') {
548 if ((next = lgetc(quotec)) == EOF)
549 return (0);
550 if (next == quotec || next == ' ' ||
551 next == '\t')
552 c = next;
553 else if (next == '\n') {
554 file->lineno++;
555 continue;
556 } else
557 lungetc(next);
558 } else if (c == quotec) {
559 *p = '\0';
560 break;
561 } else if (c == '\0') {
562 yyerror("syntax error");
563 return (findeol());
565 if (p + 1 >= buf + sizeof(buf) - 1) {
566 yyerror("string too long");
567 return (findeol());
569 *p++ = c;
571 yylval.v.string = strdup(buf);
572 if (yylval.v.string == NULL)
573 fatal("yylex: strdup");
574 return (STRING);
577 #define allowed_to_end_number(x) \
578 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
580 if (c == '-' || isdigit(c)) {
581 do {
582 *p++ = c;
583 if ((size_t)(p-buf) >= sizeof(buf)) {
584 yyerror("string too long");
585 return (findeol());
587 } while ((c = lgetc(0)) != EOF && isdigit(c));
588 lungetc(c);
589 if (p == buf + 1 && buf[0] == '-')
590 goto nodigits;
591 if (c == EOF || allowed_to_end_number(c)) {
592 const char *errstr = NULL;
594 *p = '\0';
595 yylval.v.number = strtonum(buf, LLONG_MIN,
596 LLONG_MAX, &errstr);
597 if (errstr) {
598 yyerror("\"%s\" invalid number: %s",
599 buf, errstr);
600 return (findeol());
602 return (NUMBER);
603 } else {
604 nodigits:
605 while (p > buf + 1)
606 lungetc((unsigned char)*--p);
607 c = (unsigned char)*--p;
608 if (c == '-')
609 return (c);
613 #define allowed_in_string(x) \
614 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
615 x != '{' && x != '}' && \
616 x != '!' && x != '=' && x != '#' && \
617 x != ','))
619 if (isalnum(c) || c == ':' || c == '_' || c == '/') {
620 do {
621 *p++ = c;
622 if ((size_t)(p-buf) >= sizeof(buf)) {
623 yyerror("string too long");
624 return (findeol());
626 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
627 lungetc(c);
628 *p = '\0';
629 if ((token = lookup(buf)) == STRING)
630 if ((yylval.v.string = strdup(buf)) == NULL)
631 fatal("yylex: strdup");
632 return (token);
634 if (c == '\n') {
635 yylval.lineno = file->lineno;
636 file->lineno++;
638 if (c == EOF)
639 return (0);
640 return (c);
643 struct file *
644 pushfile(const char *name, int secret)
646 struct file *nfile;
648 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
649 log_warn("%s", __func__);
650 return (NULL);
652 if ((nfile->name = strdup(name)) == NULL) {
653 log_warn("%s", __func__);
654 free(nfile);
655 return (NULL);
657 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
658 free(nfile->name);
659 free(nfile);
660 return (NULL);
662 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
663 nfile->ungetsize = 16;
664 nfile->ungetbuf = malloc(nfile->ungetsize);
665 if (nfile->ungetbuf == NULL) {
666 log_warn("%s", __func__);
667 fclose(nfile->stream);
668 free(nfile->name);
669 free(nfile);
670 return (NULL);
672 TAILQ_INSERT_TAIL(&files, nfile, entry);
673 return (nfile);
676 int
677 popfile(void)
679 struct file *prev;
681 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
682 prev->errors += file->errors;
684 TAILQ_REMOVE(&files, file, entry);
685 fclose(file->stream);
686 free(file->name);
687 free(file->ungetbuf);
688 free(file);
689 file = prev;
690 return (file ? 0 : EOF);
693 int
694 parse_config(const char *filename, struct galileo *env)
696 struct sym *sym, *next;
697 size_t n;
699 conf = env;
701 n = strlcpy(conf->sc_conffile, filename, sizeof(conf->sc_conffile));
702 if (n >= sizeof(conf->sc_conffile)) {
703 log_warn("path too long: %s", filename);
704 return (-1);
707 if ((file = pushfile(filename, 0)) == NULL) {
708 log_warn("failed to open %s", filename);
709 return (-1);
711 topfile = file;
712 setservent(1);
714 yyparse();
715 if (TAILQ_EMPTY(&conf->sc_proxies))
716 yyerror("no proxies defined");
717 errors = file->errors;
718 popfile();
720 endservent();
722 /* Free macros and check which have not been used. */
723 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
724 if (!sym->used)
725 fprintf(stderr, "warning: macro `%s' not used\n",
726 sym->nam);
727 if (!sym->persist) {
728 free(sym->nam);
729 free(sym->val);
730 TAILQ_REMOVE(&symhead, sym, entry);
731 free(sym);
735 if (errors)
736 return (-1);
738 return (0);
741 int
742 symset(const char *nam, const char *val, int persist)
744 struct sym *sym;
746 TAILQ_FOREACH(sym, &symhead, entry) {
747 if (strcmp(nam, sym->nam) == 0)
748 break;
751 if (sym != NULL) {
752 if (sym->persist == 1)
753 return (0);
754 else {
755 free(sym->nam);
756 free(sym->val);
757 TAILQ_REMOVE(&symhead, sym, entry);
758 free(sym);
761 if ((sym = calloc(1, sizeof(*sym))) == NULL)
762 return (-1);
764 sym->nam = strdup(nam);
765 if (sym->nam == NULL) {
766 free(sym);
767 return (-1);
769 sym->val = strdup(val);
770 if (sym->val == NULL) {
771 free(sym->nam);
772 free(sym);
773 return (-1);
775 sym->used = 0;
776 sym->persist = persist;
777 TAILQ_INSERT_TAIL(&symhead, sym, entry);
778 return (0);
781 int
782 cmdline_symset(char *s)
784 char *sym, *val;
785 int ret;
787 if ((val = strrchr(s, '=')) == NULL)
788 return (-1);
789 sym = strndup(s, val - s);
790 if (sym == NULL)
791 fatal("%s: strndup", __func__);
792 ret = symset(sym, val + 1, 1);
793 free(sym);
795 return (ret);
798 char *
799 symget(const char *nam)
801 struct sym *sym;
803 TAILQ_FOREACH(sym, &symhead, entry) {
804 if (strcmp(nam, sym->nam) == 0) {
805 sym->used = 1;
806 return (sym->val);
809 return (NULL);
812 int
813 getservice(const char *n)
815 struct servent *s;
816 const char *errstr;
817 long long llval;
819 llval = strtonum(n, 0, UINT16_MAX, &errstr);
820 if (errstr) {
821 s = getservbyname(n, "tcp");
822 if (s == NULL)
823 s = getservbyname(n, "udp");
824 if (s == NULL)
825 return (-1);
826 return (ntohs(s->s_port));
829 return ((unsigned short)llval);