Blob


1 %{
3 /*
4 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
19 #include <ctype.h>
20 #include <errno.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "gmid.h"
28 FILE *yyfp;
30 typedef struct {
31 union {
32 char *str;
33 int num;
34 };
35 int lineno;
36 int colno;
37 } yystype;
38 #define YYSTYPE yystype
40 /*
41 * #define YYDEBUG 1
42 * int yydebug = 1;
43 */
45 struct vhost *host;
46 struct location *loc;
48 static int goterror;
50 static struct vhost *new_vhost(void);
51 static struct location *new_location(void);
53 void yyerror(const char*, ...);
54 static int yylex(void);
55 int parse_portno(const char*);
56 void parse_conf(const char*);
57 char *ensure_absolute_path(char*);
58 int check_block_code(int);
59 char *check_block_fmt(char*);
60 int check_strip_no(int);
61 int check_prefork_num(int);
62 void advance_loc(void);
63 void only_once(const void*, const char*);
64 void only_oncei(int, const char*);
65 int fastcgi_conf(char *, char *, char *);
66 void add_param(char *, char *, int);
68 %}
70 /* for bison: */
71 /* %define parse.error verbose */
73 %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER
74 %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO
75 %token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP
76 %token TFASTCGI TSPAWN TPARAM
78 %token TERR
80 %token <str> TSTRING
81 %token <num> TNUM
82 %token <num> TBOOL
84 %%
86 conf : options vhosts ;
88 options : /* empty */
89 | options option
90 ;
92 option : TCHROOT TSTRING { conf.chroot = $2; }
93 | TIPV6 TBOOL { conf.ipv6 = $2; }
94 | TMIME TSTRING TSTRING { add_mime(&conf.mime, $2, $3); }
95 | TPORT TNUM { conf.port = $2; }
96 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
97 | TPROTOCOLS TSTRING {
98 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
99 yyerror("invalid protocols string \"%s\"", $2);
101 | TUSER TSTRING { conf.user = $2; }
104 vhosts : /* empty */
105 | vhosts vhost
108 vhost : TSERVER TSTRING {
109 host = new_vhost();
110 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
112 loc = new_location();
113 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
115 loc->match = xstrdup("*");
116 host->domain = $2;
118 if (strstr($2, "xn--") != NULL) {
119 warnx("%s:%d:%d \"%s\" looks like punycode: "
120 "you should use the decoded hostname.",
121 config_path, yylval.lineno+1, yylval.colno,
122 $2);
124 } '{' servopts locations '}' {
126 if (host->cert == NULL || host->key == NULL)
127 yyerror("invalid vhost definition: %s", $2);
129 | error '}' { yyerror("error in server directive"); }
132 servopts : /* empty */
133 | servopts servopt
136 servopt : TALIAS TSTRING {
137 struct alist *a;
139 a = xcalloc(1, sizeof(*a));
140 a->alias = $2;
141 if (TAILQ_EMPTY(&host->aliases))
142 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
143 else
144 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
146 | TCERT TSTRING {
147 only_once(host->cert, "cert");
148 host->cert = ensure_absolute_path($2);
150 | TCGI TSTRING {
151 only_once(host->cgi, "cgi");
152 /* drop the starting '/', if any */
153 if (*$2 == '/')
154 memmove($2, $2+1, strlen($2));
155 host->cgi = $2;
157 | TENTRYPOINT TSTRING {
158 only_once(host->entrypoint, "entrypoint");
159 while (*$2 == '/')
160 memmove($2, $2+1, strlen($2));
161 host->entrypoint = $2;
163 | TENV TSTRING TSTRING {
164 add_param($2, $3, 1);
166 | TKEY TSTRING {
167 only_once(host->key, "key");
168 host->key = ensure_absolute_path($2);
170 | TPARAM TSTRING TSTRING {
171 add_param($2, $3, 0);
173 | locopt
176 locations : /* empty */
177 | locations location
180 location : TLOCATION { advance_loc(); } TSTRING '{' locopts '}' {
181 /* drop the starting '/' if any */
182 if (*$3 == '/')
183 memmove($3, $3+1, strlen($3));
184 loc->match = $3;
186 | error '}'
189 locopts : /* empty */
190 | locopts locopt
193 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
194 | TBLOCK TRETURN TNUM TSTRING {
195 only_once(loc->block_fmt, "block");
196 loc->block_fmt = check_block_fmt($4);
197 loc->block_code = check_block_code($3);
199 | TBLOCK TRETURN TNUM {
200 only_once(loc->block_fmt, "block");
201 loc->block_fmt = xstrdup("temporary failure");
202 loc->block_code = check_block_code($3);
203 if ($3 >= 30 && $3 < 40)
204 yyerror("missing `meta' for block return %d", $3);
206 | TBLOCK {
207 only_once(loc->block_fmt, "block");
208 loc->block_fmt = xstrdup("temporary failure");
209 loc->block_code = 40;
211 | TDEFAULT TTYPE TSTRING {
212 only_once(loc->default_mime, "default type");
213 loc->default_mime = $3;
215 | TFASTCGI fastcgi
216 | TINDEX TSTRING {
217 only_once(loc->index, "index");
218 loc->index = $2;
220 | TLANG TSTRING {
221 only_once(loc->lang, "lang");
222 loc->lang = $2;
224 | TLOG TBOOL { loc->disable_log = !$2; }
225 | TREQUIRE TCLIENT TCA TSTRING {
226 only_once(loc->reqca, "require client ca");
227 ensure_absolute_path($4);
228 if ((loc->reqca = load_ca($4)) == NULL)
229 yyerror("couldn't load ca cert: %s", $4);
230 free($4);
232 | TROOT TSTRING {
233 only_once(loc->dir, "root");
234 loc->dir = ensure_absolute_path($2);
236 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
239 fastcgi : TSPAWN TSTRING {
240 only_oncei(loc->fcgi, "fastcgi");
241 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
243 | TSTRING {
244 only_oncei(loc->fcgi, "fastcgi");
245 loc->fcgi = fastcgi_conf($1, NULL, NULL);
247 | TTCP TSTRING TNUM {
248 char *c;
249 if (asprintf(&c, "%d", $3) == -1)
250 err(1, "asprintf");
251 only_oncei(loc->fcgi, "fastcgi");
252 loc->fcgi = fastcgi_conf($2, c, NULL);
254 | TTCP TSTRING {
255 only_oncei(loc->fcgi, "fastcgi");
256 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
258 | TTCP TSTRING TSTRING {
259 only_oncei(loc->fcgi, "fastcgi");
260 loc->fcgi = fastcgi_conf($2, $3, NULL);
264 %%
266 static struct vhost *
267 new_vhost(void)
269 return xcalloc(1, sizeof(struct vhost));
272 static struct location *
273 new_location(void)
275 struct location *l;
277 l = xcalloc(1, sizeof(*l));
278 l->dirfd = -1;
279 l->fcgi = -1;
280 return l;
283 void
284 yyerror(const char *msg, ...)
286 va_list ap;
288 goterror = 1;
290 va_start(ap, msg);
291 fprintf(stderr, "%s:%d: ", config_path, yylval.lineno);
292 vfprintf(stderr, msg, ap);
293 fprintf(stderr, "\n");
294 va_end(ap);
297 static struct keyword {
298 const char *word;
299 int token;
300 } keywords[] = {
301 {"alias", TALIAS},
302 {"auto", TAUTO},
303 {"block", TBLOCK},
304 {"ca", TCA},
305 {"cert", TCERT},
306 {"cgi", TCGI},
307 {"chroot", TCHROOT},
308 {"client", TCLIENT},
309 {"default", TDEFAULT},
310 {"entrypoint", TENTRYPOINT},
311 {"env", TENV},
312 {"fastcgi", TFASTCGI},
313 {"index", TINDEX},
314 {"ipv6", TIPV6},
315 {"key", TKEY},
316 {"lang", TLANG},
317 {"location", TLOCATION},
318 {"log", TLOG},
319 {"mime", TMIME},
320 {"param", TPARAM},
321 {"port", TPORT},
322 {"prefork", TPREFORK},
323 {"protocols", TPROTOCOLS},
324 {"require", TREQUIRE},
325 {"return", TRETURN},
326 {"root", TROOT},
327 {"server", TSERVER},
328 {"spawn", TSPAWN},
329 {"strip", TSTRIP},
330 {"tcp", TTCP},
331 {"type", TTYPE},
332 {"user", TUSER},
333 };
335 /*
336 * Taken an adapted from doas' parse.y
337 */
338 static int
339 yylex(void)
341 char buf[1024], *ebuf, *p, *str;
342 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
343 size_t i;
345 p = buf;
346 ebuf = buf + sizeof(buf);
348 repeat:
349 /* skip whitespace first */
350 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
351 yylval.colno++;
352 if (c == '\n') {
353 yylval.lineno++;
354 yylval.colno = 0;
358 /* check for special one-character constructions */
359 switch (c) {
360 case '{':
361 case '}':
362 return c;
363 case '#':
364 /* skip comments; NUL is allowed; no continuation */
365 while ((c = getc(yyfp)) != '\n')
366 if (c == EOF)
367 goto eof;
368 yylval.colno = 0;
369 yylval.lineno++;
370 goto repeat;
371 case EOF:
372 goto eof;
375 /* parsing next word */
376 for (;; c = getc(yyfp), yylval.colno++) {
377 switch (c) {
378 case '\0':
379 yyerror("unallowed character NULL in column %d",
380 yylval.colno+1);
381 escape = 0;
382 continue;
383 case '\\':
384 escape = !escape;
385 if (escape)
386 continue;
387 break;
388 case '\n':
389 if (quotes)
390 yyerror("unterminated quotes in column %d",
391 yylval.colno+1);
392 if (escape) {
393 nonkw = 1;
394 escape = 0;
395 yylval.colno = 0;
396 yylval.lineno++;
398 goto eow;
399 case EOF:
400 if (escape)
401 yyerror("unterminated escape in column %d",
402 yylval.colno);
403 if (quotes)
404 yyerror("unterminated quotes in column %d",
405 qpos+1);
406 goto eow;
407 case '{':
408 case '}':
409 case '#':
410 case ' ':
411 case '\t':
412 if (!escape && !quotes)
413 goto eow;
414 break;
415 case '"':
416 if (!escape) {
417 quotes = !quotes;
418 if (quotes) {
419 nonkw = 1;
420 qpos = yylval.colno;
422 continue;
425 *p++ = c;
426 if (p == ebuf) {
427 yyerror("line too long");
428 p = buf;
430 escape = 0;
433 eow:
434 *p = 0;
435 if (c != EOF)
436 ungetc(c, yyfp);
437 if (p == buf) {
438 /*
439 * There could be a number of reason for empty buffer,
440 * and we handle all of them here, to avoid cluttering
441 * the main loop.
442 */
443 if (c == EOF)
444 goto eof;
445 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
446 goto repeat;
448 if (!nonkw) {
449 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
450 if (!strcmp(buf, keywords[i].word))
451 return keywords[i].token;
454 c = *buf;
455 if (!nonkw && (c == '-' || isdigit(c))) {
456 yylval.num = parse_portno(buf);
457 return TNUM;
459 if (!nonkw && !strcmp(buf, "on")) {
460 yylval.num = 1;
461 return TBOOL;
463 if (!nonkw && !strcmp(buf, "off")) {
464 yylval.num = 0;
465 return TBOOL;
467 if ((str = strdup(buf)) == NULL)
468 err(1, "%s", __func__);
469 yylval.str = str;
470 return TSTRING;
472 eof:
473 if (ferror(yyfp))
474 yyerror("input error reading config");
475 return 0;
478 int
479 parse_portno(const char *p)
481 const char *errstr;
482 int n;
484 n = strtonum(p, 0, UINT16_MAX, &errstr);
485 if (errstr != NULL)
486 yyerror("port number is %s: %s", errstr, p);
487 return n;
490 void
491 parse_conf(const char *path)
493 config_path = path;
494 if ((yyfp = fopen(path, "r")) == NULL)
495 err(1, "cannot open config: %s", path);
496 yyparse();
497 fclose(yyfp);
499 if (goterror)
500 exit(1);
502 if (TAILQ_FIRST(&hosts)->domain == NULL)
503 errx(1, "no vhost defined in %s", path);
506 char *
507 ensure_absolute_path(char *path)
509 if (path == NULL || *path != '/')
510 yyerror("not an absolute path: %s", path);
511 return path;
514 int
515 check_block_code(int n)
517 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
518 yyerror("invalid block code %d", n);
519 return n;
522 char *
523 check_block_fmt(char *fmt)
525 char *s;
527 for (s = fmt; *s; ++s) {
528 if (*s != '%')
529 continue;
530 switch (*++s) {
531 case '%':
532 case 'p':
533 case 'q':
534 case 'P':
535 case 'N':
536 break;
537 default:
538 yyerror("invalid format specifier %%%c", *s);
542 return fmt;
545 int
546 check_strip_no(int n)
548 if (n <= 0)
549 yyerror("invalid strip number %d", n);
550 return n;
553 int
554 check_prefork_num(int n)
556 if (n <= 0 || n >= PROC_MAX)
557 yyerror("invalid prefork number %d", n);
558 return n;
561 void
562 advance_loc(void)
564 loc = new_location();
565 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
568 void
569 only_once(const void *ptr, const char *name)
571 if (ptr != NULL)
572 yyerror("`%s' specified more than once", name);
575 void
576 only_oncei(int i, const char *name)
578 if (i != -1)
579 yyerror("`%s' specified more than once", name);
582 int
583 fastcgi_conf(char *path, char *port, char *prog)
585 struct fcgi *f;
586 int i;
588 for (i = 0; i < FCGI_MAX; ++i) {
589 f = &fcgi[i];
591 if (f->path == NULL) {
592 f->id = i;
593 f->path = path;
594 f->port = port;
595 f->prog = prog;
596 return i;
599 /* XXX: what to do with prog? */
600 if (!strcmp(f->path, path) &&
601 ((port == NULL && f->port == NULL) ||
602 !strcmp(f->port, port))) {
603 free(path);
604 free(port);
605 return i;
609 yyerror("too much `fastcgi' rules defined.");
610 return -1;
613 void
614 add_param(char *name, char *val, int env)
616 struct envlist *e;
617 struct envhead *h;
619 if (env)
620 h = &host->env;
621 else
622 h = &host->params;
624 e = xcalloc(1, sizeof(*e));
625 e->name = name;
626 e->value = val;
627 if (TAILQ_EMPTY(h))
628 TAILQ_INSERT_HEAD(h, e, envs);
629 else
630 TAILQ_INSERT_TAIL(h, e, envs);