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 /*
31 * #define YYDEBUG 1
32 * int yydebug = 1;
33 */
35 struct vhost *host;
36 struct location *loc;
38 static int goterror;
39 static int lineno, colno;
41 static struct vhost *new_vhost(void);
42 static struct location *new_location(void);
44 void yyerror(const char*, ...);
45 static int yylex(void);
46 int parse_portno(const char*);
47 void parse_conf(const char*);
48 char *ensure_absolute_path(char*);
49 int check_block_code(int);
50 char *check_block_fmt(char*);
51 int check_strip_no(int);
52 int check_prefork_num(int);
53 void advance_loc(void);
54 void only_once(const void*, const char*);
55 void only_oncei(int, const char*);
56 int fastcgi_conf(char *, char *, char *);
57 void add_param(char *, char *, int);
59 %}
61 /* for bison: */
62 /* %define parse.error verbose */
64 %union {
65 char *str;
66 int num;
67 }
69 %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER
70 %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO
71 %token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP
72 %token TFASTCGI TSPAWN TPARAM
74 %token TERR
76 %token <str> TSTRING
77 %token <num> TNUM
78 %token <num> TBOOL
80 %%
82 conf : options vhosts ;
84 options : /* empty */
85 | options option
86 ;
88 option : TCHROOT TSTRING { conf.chroot = $2; }
89 | TIPV6 TBOOL { conf.ipv6 = $2; }
90 | TMIME TSTRING TSTRING { add_mime(&conf.mime, $2, $3); }
91 | TPORT TNUM { conf.port = $2; }
92 | TPREFORK TNUM { conf.prefork = check_prefork_num($2); }
93 | TPROTOCOLS TSTRING {
94 if (tls_config_parse_protocols(&conf.protos, $2) == -1)
95 yyerror("invalid protocols string \"%s\"", $2);
96 }
97 | TUSER TSTRING { conf.user = $2; }
98 ;
100 vhosts : /* empty */
101 | vhosts vhost
104 vhost : TSERVER TSTRING {
105 host = new_vhost();
106 TAILQ_INSERT_HEAD(&hosts, host, vhosts);
108 loc = new_location();
109 TAILQ_INSERT_HEAD(&host->locations, loc, locations);
111 loc->match = xstrdup("*");
112 host->domain = $2;
114 if (strstr($2, "xn--") != NULL) {
115 warnx("%s:%d \"%s\" looks like punycode: "
116 "you should use the decoded hostname.",
117 config_path, lineno, $2);
119 } '{' servopts locations '}' {
121 if (host->cert == NULL || host->key == NULL)
122 yyerror("invalid vhost definition: %s", $2);
124 | error '}' { yyerror("error in server directive"); }
127 servopts : /* empty */
128 | servopts servopt
131 servopt : TALIAS TSTRING {
132 struct alist *a;
134 a = xcalloc(1, sizeof(*a));
135 a->alias = $2;
136 if (TAILQ_EMPTY(&host->aliases))
137 TAILQ_INSERT_HEAD(&host->aliases, a, aliases);
138 else
139 TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
141 | TCERT TSTRING {
142 only_once(host->cert, "cert");
143 host->cert = ensure_absolute_path($2);
145 | TCGI TSTRING {
146 only_once(host->cgi, "cgi");
147 /* drop the starting '/', if any */
148 if (*$2 == '/')
149 memmove($2, $2+1, strlen($2));
150 host->cgi = $2;
152 | TENTRYPOINT TSTRING {
153 only_once(host->entrypoint, "entrypoint");
154 while (*$2 == '/')
155 memmove($2, $2+1, strlen($2));
156 host->entrypoint = $2;
158 | TENV TSTRING TSTRING {
159 add_param($2, $3, 1);
161 | TKEY TSTRING {
162 only_once(host->key, "key");
163 host->key = ensure_absolute_path($2);
165 | TPARAM TSTRING TSTRING {
166 add_param($2, $3, 0);
168 | locopt
171 locations : /* empty */
172 | locations location
175 location : TLOCATION { advance_loc(); } TSTRING '{' locopts '}' {
176 /* drop the starting '/' if any */
177 if (*$3 == '/')
178 memmove($3, $3+1, strlen($3));
179 loc->match = $3;
181 | error '}'
184 locopts : /* empty */
185 | locopts locopt
188 locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
189 | TBLOCK TRETURN TNUM TSTRING {
190 only_once(loc->block_fmt, "block");
191 loc->block_fmt = check_block_fmt($4);
192 loc->block_code = check_block_code($3);
194 | TBLOCK TRETURN TNUM {
195 only_once(loc->block_fmt, "block");
196 loc->block_fmt = xstrdup("temporary failure");
197 loc->block_code = check_block_code($3);
198 if ($3 >= 30 && $3 < 40)
199 yyerror("missing `meta' for block return %d", $3);
201 | TBLOCK {
202 only_once(loc->block_fmt, "block");
203 loc->block_fmt = xstrdup("temporary failure");
204 loc->block_code = 40;
206 | TDEFAULT TTYPE TSTRING {
207 only_once(loc->default_mime, "default type");
208 loc->default_mime = $3;
210 | TFASTCGI fastcgi
211 | TINDEX TSTRING {
212 only_once(loc->index, "index");
213 loc->index = $2;
215 | TLANG TSTRING {
216 only_once(loc->lang, "lang");
217 loc->lang = $2;
219 | TLOG TBOOL { loc->disable_log = !$2; }
220 | TREQUIRE TCLIENT TCA TSTRING {
221 only_once(loc->reqca, "require client ca");
222 ensure_absolute_path($4);
223 if ((loc->reqca = load_ca($4)) == NULL)
224 yyerror("couldn't load ca cert: %s", $4);
225 free($4);
227 | TROOT TSTRING {
228 only_once(loc->dir, "root");
229 loc->dir = ensure_absolute_path($2);
231 | TSTRIP TNUM { loc->strip = check_strip_no($2); }
234 fastcgi : TSPAWN TSTRING {
235 only_oncei(loc->fcgi, "fastcgi");
236 loc->fcgi = fastcgi_conf(NULL, NULL, $2);
238 | TSTRING {
239 only_oncei(loc->fcgi, "fastcgi");
240 loc->fcgi = fastcgi_conf($1, NULL, NULL);
242 | TTCP TSTRING TNUM {
243 char *c;
244 if (asprintf(&c, "%d", $3) == -1)
245 err(1, "asprintf");
246 only_oncei(loc->fcgi, "fastcgi");
247 loc->fcgi = fastcgi_conf($2, c, NULL);
249 | TTCP TSTRING {
250 only_oncei(loc->fcgi, "fastcgi");
251 loc->fcgi = fastcgi_conf($2, xstrdup("9000"), NULL);
253 | TTCP TSTRING TSTRING {
254 only_oncei(loc->fcgi, "fastcgi");
255 loc->fcgi = fastcgi_conf($2, $3, NULL);
259 %%
261 static struct vhost *
262 new_vhost(void)
264 return xcalloc(1, sizeof(struct vhost));
267 static struct location *
268 new_location(void)
270 struct location *l;
272 l = xcalloc(1, sizeof(*l));
273 l->dirfd = -1;
274 l->fcgi = -1;
275 return l;
278 void
279 yyerror(const char *msg, ...)
281 va_list ap;
283 goterror = 1;
285 va_start(ap, msg);
286 fprintf(stderr, "%s:%d: ", config_path, lineno);
287 vfprintf(stderr, msg, ap);
288 fprintf(stderr, "\n");
289 va_end(ap);
292 static struct keyword {
293 const char *word;
294 int token;
295 } keywords[] = {
296 {"alias", TALIAS},
297 {"auto", TAUTO},
298 {"block", TBLOCK},
299 {"ca", TCA},
300 {"cert", TCERT},
301 {"cgi", TCGI},
302 {"chroot", TCHROOT},
303 {"client", TCLIENT},
304 {"default", TDEFAULT},
305 {"entrypoint", TENTRYPOINT},
306 {"env", TENV},
307 {"fastcgi", TFASTCGI},
308 {"index", TINDEX},
309 {"ipv6", TIPV6},
310 {"key", TKEY},
311 {"lang", TLANG},
312 {"location", TLOCATION},
313 {"log", TLOG},
314 {"mime", TMIME},
315 {"param", TPARAM},
316 {"port", TPORT},
317 {"prefork", TPREFORK},
318 {"protocols", TPROTOCOLS},
319 {"require", TREQUIRE},
320 {"return", TRETURN},
321 {"root", TROOT},
322 {"server", TSERVER},
323 {"spawn", TSPAWN},
324 {"strip", TSTRIP},
325 {"tcp", TTCP},
326 {"type", TTYPE},
327 {"user", TUSER},
328 };
330 /*
331 * Taken an adapted from doas' parse.y
332 */
333 static int
334 yylex(void)
336 char buf[1024], *ebuf, *p, *str;
337 int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
338 size_t i;
340 p = buf;
341 ebuf = buf + sizeof(buf);
343 repeat:
344 /* skip whitespace first */
345 for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
346 colno++;
347 if (c == '\n') {
348 lineno++;
349 colno = 0;
353 /* check for special one-character constructions */
354 switch (c) {
355 case '{':
356 case '}':
357 return c;
358 case '#':
359 /* skip comments; NUL is allowed; no continuation */
360 while ((c = getc(yyfp)) != '\n')
361 if (c == EOF)
362 goto eof;
363 colno = 0;
364 lineno++;
365 goto repeat;
366 case EOF:
367 goto eof;
370 /* parsing next word */
371 for (;; c = getc(yyfp), colno++) {
372 switch (c) {
373 case '\0':
374 yyerror("unallowed character NULL in column %d",
375 colno+1);
376 escape = 0;
377 continue;
378 case '\\':
379 escape = !escape;
380 if (escape)
381 continue;
382 break;
383 case '\n':
384 if (quotes)
385 yyerror("unterminated quotes in column %d",
386 colno+1);
387 if (escape) {
388 nonkw = 1;
389 escape = 0;
390 colno = 0;
391 lineno++;
393 goto eow;
394 case EOF:
395 if (escape)
396 yyerror("unterminated escape in column %d",
397 colno);
398 if (quotes)
399 yyerror("unterminated quotes in column %d",
400 qpos+1);
401 goto eow;
402 case '{':
403 case '}':
404 case '#':
405 case ' ':
406 case '\t':
407 if (!escape && !quotes)
408 goto eow;
409 break;
410 case '"':
411 if (!escape) {
412 quotes = !quotes;
413 if (quotes) {
414 nonkw = 1;
415 qpos = colno;
417 continue;
420 *p++ = c;
421 if (p == ebuf) {
422 yyerror("line too long");
423 p = buf;
425 escape = 0;
428 eow:
429 *p = 0;
430 if (c != EOF)
431 ungetc(c, yyfp);
432 if (p == buf) {
433 /*
434 * There could be a number of reason for empty buffer,
435 * and we handle all of them here, to avoid cluttering
436 * the main loop.
437 */
438 if (c == EOF)
439 goto eof;
440 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
441 goto repeat;
443 if (!nonkw) {
444 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
445 if (!strcmp(buf, keywords[i].word))
446 return keywords[i].token;
449 c = *buf;
450 if (!nonkw && (c == '-' || isdigit(c))) {
451 yylval.num = parse_portno(buf);
452 return TNUM;
454 if (!nonkw && !strcmp(buf, "on")) {
455 yylval.num = 1;
456 return TBOOL;
458 if (!nonkw && !strcmp(buf, "off")) {
459 yylval.num = 0;
460 return TBOOL;
462 if ((str = strdup(buf)) == NULL)
463 err(1, "%s", __func__);
464 yylval.str = str;
465 return TSTRING;
467 eof:
468 if (ferror(yyfp))
469 yyerror("input error reading config");
470 return 0;
473 int
474 parse_portno(const char *p)
476 const char *errstr;
477 int n;
479 n = strtonum(p, 0, UINT16_MAX, &errstr);
480 if (errstr != NULL)
481 yyerror("port number is %s: %s", errstr, p);
482 return n;
485 void
486 parse_conf(const char *path)
488 config_path = path;
489 if ((yyfp = fopen(path, "r")) == NULL)
490 err(1, "cannot open config: %s", path);
491 yyparse();
492 fclose(yyfp);
494 if (goterror)
495 exit(1);
497 if (TAILQ_FIRST(&hosts)->domain == NULL)
498 errx(1, "no vhost defined in %s", path);
501 char *
502 ensure_absolute_path(char *path)
504 if (path == NULL || *path != '/')
505 yyerror("not an absolute path: %s", path);
506 return path;
509 int
510 check_block_code(int n)
512 if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
513 yyerror("invalid block code %d", n);
514 return n;
517 char *
518 check_block_fmt(char *fmt)
520 char *s;
522 for (s = fmt; *s; ++s) {
523 if (*s != '%')
524 continue;
525 switch (*++s) {
526 case '%':
527 case 'p':
528 case 'q':
529 case 'P':
530 case 'N':
531 break;
532 default:
533 yyerror("invalid format specifier %%%c", *s);
537 return fmt;
540 int
541 check_strip_no(int n)
543 if (n <= 0)
544 yyerror("invalid strip number %d", n);
545 return n;
548 int
549 check_prefork_num(int n)
551 if (n <= 0 || n >= PROC_MAX)
552 yyerror("invalid prefork number %d", n);
553 return n;
556 void
557 advance_loc(void)
559 loc = new_location();
560 TAILQ_INSERT_TAIL(&host->locations, loc, locations);
563 void
564 only_once(const void *ptr, const char *name)
566 if (ptr != NULL)
567 yyerror("`%s' specified more than once", name);
570 void
571 only_oncei(int i, const char *name)
573 if (i != -1)
574 yyerror("`%s' specified more than once", name);
577 int
578 fastcgi_conf(char *path, char *port, char *prog)
580 struct fcgi *f;
581 int i;
583 for (i = 0; i < FCGI_MAX; ++i) {
584 f = &fcgi[i];
586 if (f->path == NULL) {
587 f->id = i;
588 f->path = path;
589 f->port = port;
590 f->prog = prog;
591 return i;
594 /* XXX: what to do with prog? */
595 if (!strcmp(f->path, path) &&
596 ((port == NULL && f->port == NULL) ||
597 !strcmp(f->port, port))) {
598 free(path);
599 free(port);
600 return i;
604 yyerror("too much `fastcgi' rules defined.");
605 return -1;
608 void
609 add_param(char *name, char *val, int env)
611 struct envlist *e;
612 struct envhead *h;
614 if (env)
615 h = &host->env;
616 else
617 h = &host->params;
619 e = xcalloc(1, sizeof(*e));
620 e->name = name;
621 e->value = val;
622 if (TAILQ_EMPTY(h))
623 TAILQ_INSERT_HEAD(h, e, envs);
624 else
625 TAILQ_INSERT_TAIL(h, e, envs);