Attempt to create .deps directory every time we build objects.
[doas.git] / parse.y
blob43db82247605328652f089c0ef1064ed744562d8
1 /* $OpenBSD: parse.y,v 1.26 2017/01/02 01:40:20 tedu Exp $ */
2 /*
3 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
4 * Copyright (c) 2021-2022 Sergey Sushilin <sergeysushilin@protonmail.com>
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.
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.
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <unistd.h>
25 #include <sys/stat.h>
27 #include "compat.h"
28 #include "rule.h"
29 #include "wrappers.h"
30 #include "yystype.h"
31 #include "parse.h"
33 extern FILE *yyin;
35 static struct rule *rules = NULL;
36 static size_t n_rules = 0;
37 static size_t max_rules = 0;
39 static u_int parse_errors = 0;
41 static void yyerror(char const *fmt, ...) __nonnull((1)) __format_printf(1, 2);
42 #if !HAVE_LEX && !HAVE_FLEX
43 static int yylex(void);
44 #else
45 extern int yylex(void);
46 #endif
48 static void add_rule(struct rule const *rule) __nonnull((1));
50 struct passwd *original_pw = NULL, *target_pw = NULL;
52 extern char **environ;
54 #define assign_option(_0, _1, _2, option, name, value) \
55 do { \
56 if (_2.name != value) { \
57 if (_0.name != value) \
58 yyerror(option" option is already set"); \
59 else \
60 _0.name = _2.name; \
61 } \
62 } while (0)
66 %token TOK_PERMIT TOK_DENY TOK_FROM TOK_AS TOK_EXECUTE TOK_ELLIPSIS
67 %token TOK_INHERITENV TOK_KEEPENV TOK_SETENV TOK_UNSETENV
68 %token TOK_PERSIST TOK_NOPASS TOK_NOLOG
69 %token TOK_STRING TOK_NUMBER TOK_NAME
70 %token TOK_UNKNOWN ','
72 %left ','
76 grammar: /* Empty. */
77 | grammar '\n'
78 | grammar rule '\n'
79 | error '\n' {
80 YYERROR;
81 } | TOK_UNKNOWN '\n' {
82 YYERROR;
83 } ;
85 rule: TOK_PERMIT options ident target argv {
86 struct rule r = {
87 .permit = true,
88 .env = $2.env,
89 .keepenvlist = $2.keepenvlist,
90 .setenvlist = $2.setenvlist,
91 .unsetenvlist = $2.unsetenvlist,
92 .persist_time = $2.persist_time,
93 .inheritenv = $2.inheritenv,
94 .nopass = $2.nopass,
95 .nolog = $2.nolog,
96 .ident.pw = $3.ident.pw,
97 .ident.gr = $3.ident.gr,
98 .target.pw = $4.target.pw,
99 .argc = $5.argc,
100 .argv = $5.argv
103 add_rule(&r);
104 } | TOK_DENY ident target argv {
105 struct rule r = {
106 .permit = false,
107 .ident.pw = $2.ident.pw,
108 .ident.gr = $2.ident.gr,
109 .target.pw = $3.target.pw,
110 .argv = $4.argv
113 warnx("try to use 'permit' rather than 'deny',"
114 "since last is not secure enough");
116 add_rule(&r);
119 option: persist {
120 $$ = (YYSTYPE){ .persist_time = $1.persist_time };
121 } | TOK_NOPASS {
122 $$ = (YYSTYPE){ .nopass = true };
123 } | TOK_NOLOG {
124 $$ = (YYSTYPE){ .nolog = true };
126 options: /* None. */ {
127 $$.env = createenv();
128 defaultenv($$.env, original_pw, target_pw);
129 $$.keepenvlist = NULL;
130 $$.setenvlist = NULL;
131 $$.unsetenvlist = NULL;
132 $$.persist_time = 0;
133 $$.inheritenv = false;
134 $$.nopass = false;
135 $$.nolog = false;
136 } | options TOK_KEEPENV '{' strlist '}' {
137 $$ = $1;
138 assign_option($$, $1, $2, "keepenv", keepenvlist, NULL);
139 if (keepenv($$.env, $4.strlist) != 0)
140 parse_errors++;
141 $$.keepenvlist = $4.strlist;
142 } | options TOK_SETENV '{' strlist '}' {
143 $$ = $1;
144 assign_option($$, $1, $2, "setenv", setenvlist, NULL);
145 if (fillenv($$.env, $4.strlist) != 0)
146 parse_errors++;
147 $$.setenvlist = $4.strlist;
148 } | options TOK_UNSETENV '{' strlist '}' {
149 $$ = $1;
150 assign_option($$, $1, $2, "unsetenv", unsetenvlist, NULL);
151 if (unfillenv($$.env, $4.strlist) != 0)
152 parse_errors++;
153 $$.unsetenvlist = $4.strlist;
154 } | options TOK_INHERITENV {
155 $$ = $1;
156 assign_option($$, $1, $2, "inheritenv", inheritenv, false);
157 inheritenv($$.env);
158 $$.inheritenv = true;
159 } | options option {
160 $$ = $1;
162 assign_option($$, $1, $2, "persist", persist_time, 0);
163 assign_option($$, $1, $2, "nopass", nopass, false);
164 assign_option($$, $1, $2, "nolog", nolog, false);
166 if ($$.persist_time != 0 && $$.nopass)
167 yyerror("can not combine persist and nopass");
170 persist: TOK_PERSIST {
171 $$.persist_time = 5 * 60;
172 } | TOK_PERSIST '(' TOK_NUMBER ')' {
173 bool succeed;
174 enum { time_type_is_signed = ((time_t)-1 < (time_t)0) };
175 enum { time_type_max = time_type_is_signed ? (time_t)((size_t)(time_t)-1 >> 1) : (time_t)-1 };
177 if (time_type_is_signed)
178 $$.persist_time = safe_strtonum($3.str.buf, 0, time_type_max, &succeed);
179 else
180 $$.persist_time = safe_strtounum($3.str.buf, time_type_max, &succeed);
182 if (!succeed)
183 yyerror("too big persist time");
185 if ($$.persist_time == 0)
186 yyerror("persist time must be non-zero");
188 free($3.str.buf);
190 user: TOK_NAME {
191 $$.pw = safe_getpwnam($1.str.buf);
193 if ($$.pw == NULL)
194 parse_errors++;
196 free($1.str.buf);
197 } | TOK_NUMBER {
198 bool succeed;
199 uid_t uid = safe_strtounum($1.str.buf, UID_MAX, &succeed);
201 if (!succeed || ($$.pw = safe_getpwuid(uid)) == NULL)
202 parse_errors++;
204 free($1.str.buf);
206 group: TOK_NAME {
207 $$.gr = safe_getgrnam($1.str.buf);
209 if ($$.gr == NULL)
210 parse_errors++;
212 free($1.str.buf);
213 } | TOK_NUMBER {
214 bool succeed;
215 gid_t gid = safe_strtounum($1.str.buf, GID_MAX, &succeed);
217 if (!succeed || ($$.gr = safe_getgrgid(gid)) == NULL)
218 parse_errors++;
220 free($1.str.buf);
222 ident: user {
223 $$.ident.pw = $1.pw;
224 $$.ident.gr = NULL;
225 } | TOK_FROM group {
226 $$.ident.pw = NULL;
227 $$.ident.gr = $2.gr;
228 } | user TOK_FROM group {
229 $$.ident.pw = $1.pw;
230 $$.ident.gr = $3.gr;
232 target: TOK_AS user {
233 $$.target.pw = $2.pw;
236 argv: execute {
237 $$ = $1;
240 execute: /* Optional. */ {
241 $$.argc = 0;
242 $$.argv = NULL;
243 } | TOK_EXECUTE '{' strarray '}' {
244 $$.argc = $3.listcount;
245 $$.argv = (char const *const *const *)$3.strarray;
246 } | TOK_EXECUTE '{' TOK_ELLIPSIS '}' {
247 $$.argc = 1;
248 $$.argv = NULL;
249 } | TOK_EXECUTE '{' strarray TOK_ELLIPSIS '}' {
250 $$.argc = $3.listcount + 1;
251 $$.argv = (char const *const *const *)$3.strarray;
254 strlist: TOK_STRING {
255 $$.strlist = xmalloc(2 * sizeof(char *));
256 $$.strcount = 1;
257 $$.strlist[0] = $1.str.buf;
258 $$.strlist[1] = NULL;
259 } | strlist ',' TOK_STRING {
260 $$.strlist = xreallocarray($1.strlist, $$.strcount + 2, sizeof(char *));
261 $$.strlist[$$.strcount++] = $3.str.buf;
262 $$.strlist[$$.strcount] = NULL;
265 strarray: strlist {
266 int i;
268 $$.strarray = xmalloc(($1.strcount + 1) * sizeof(char **));
269 $$.listcount = $1.strcount;
271 for (i = 0; i < $$.strcount; i++) {
272 $$.strarray[i] = xmalloc(2 * sizeof(char *));
273 $$.strarray[i][0] = $1.strlist[i];
274 $$.strarray[i][1] = NULL;
277 $$.strarray[i] = NULL;
278 xfree($1.strlist);
279 } | '[' strlist ']' {
280 $$.strarray = xcalloc(2, sizeof(char **));
281 $$.listcount = 1;
282 $$.strarray[0] = $2.strlist;
283 $$.strarray[1] = NULL;
284 } | strarray ',' strarray {
285 int i, j;
287 $$.strarray = xreallocarray($1.strarray, $1.listcount + $3.listcount + 1, sizeof(char **));
288 $$.listcount = $1.listcount + $3.listcount;
290 for (i = $1.listcount, j = 0; i < $$.listcount; i++, j++)
291 $$.strarray[i] = $3.strarray[j];
293 $$.strarray[i] = NULL;
294 xfree($3.strarray);
295 } /*| strarray ',' strlist {
296 int i, j;
298 $$.strarray = xreallocarray($1.strarray, $1.listcount + $3.strcount + 1, sizeof(char **));
299 $$.listcount = $1.listcount + $3.listcount;
301 for (i = $1.listcount, j = 0; i < $$.listcount; i++, j++) {
302 $$.strarray[i] = xmalloc(2 * sizeof(char *));
303 $$.strarray[i][0] = $1.strlist[i];
304 $$.strarray[i][1] = NULL;
307 $$.strarray[i] = NULL;
308 xfree($3.strarray);
309 } | strarray ',' '[' strlist ']' {
310 $$.strarray = xreallocarray($1.strarray, $1.listcount + 1 + 1, sizeof(char **));
311 $$.listcount = $1.listcount + $3.listcount;
312 $$.strarray[$1.listcount++] = $4.strlist;
313 $$.strarray[$1.listcount] = NULL;
314 xfree($3.strarray);
315 }*/ ;
319 static void yyerror(char const *fmt, ...)
321 va_list va;
323 xfprintf(stderr, "%s: ", getprogname());
325 va_start(va, fmt);
326 xvfprintf(stderr, fmt, va);
327 va_end(va);
329 xfprintf(stderr, " at %u:%u\n", yylval.lineno + 1, yylval.colno + 1);
331 parse_errors++;
334 void check_permissions(char const *filename)
336 struct stat sb;
338 if (stat(filename, &sb) != 0) {
339 if (errno == ENOENT)
340 err(EXIT_FAILURE, "doas is not enabled, %s required", filename);
341 else
342 err(EXIT_FAILURE, "stat(\"%s\")", filename);
345 if (sb.st_mode & (S_IWGRP | S_IWOTH))
346 errx(EXIT_FAILURE, "%s is writable by group or other", filename);
348 if (sb.st_uid != ROOT_UID || sb.st_gid != ROOT_UID)
349 errx(EXIT_FAILURE, "%s is not owned by root", filename);
352 u_int parse_config(char const *filename)
354 free_rules();
355 memset(&yylval, '\0', sizeof(yylval));
356 parse_errors = 0;
357 yyin = fopen(filename, "r");
359 if (yyin == NULL)
360 err(EXIT_FAILURE, "could not open config file %s", filename);
362 yyparse();
363 fclose(yyin);
364 return parse_errors;
367 #define free_and_nullify(p) \
368 do { \
369 xfree(p); \
370 } while (0)
371 #define free_vector_and_nullify(v) \
372 do { \
373 if (v != NULL) { \
374 size_t i; \
376 for (i = 0; v[i] != NULL; i++) \
377 xfree(v[i]); \
379 xfree(v); \
381 } while (0)
383 void free_rules(void)
385 size_t i;
387 for (i = 0; i < n_rules; i++) {
388 int j;
389 struct rule rule = rules[i];
391 free_and_nullify(rule.ident.pw);
392 free_and_nullify(rule.ident.gr);
393 free_and_nullify(rule.target.pw);
395 if (rule.argv != NULL) {
396 for (j = 0; j < rule.argc; j++)
397 free_vector_and_nullify(rule.argv[j]);
399 free_and_nullify(rule.argv);
402 free_vector_and_nullify(rule.keepenvlist);
403 free_vector_and_nullify(rule.setenvlist);
404 free_vector_and_nullify(rule.unsetenvlist);
407 free_and_nullify(rules);
408 max_rules = n_rules = 0;
411 static void add_rule(struct rule const *r)
413 if (n_rules == max_rules) {
414 max_rules = (max_rules == 0 ? 8 : max_rules * 2);
415 rules = xreallocarray(rules, max_rules, sizeof(*rules));
418 memcpy(&rules[n_rules++], r, sizeof(struct rule));
421 const struct rule *get_rules (void)
423 return rules;
426 size_t get_n_rules (void)
428 return n_rules;
431 #if !HAVE_LEX && !HAVE_FLEX
432 struct keyword {
433 # if !HAVE_GPERF
434 char const *const word;
435 # else
436 int const word;
437 # endif
438 size_t const length;
439 int const token;
442 # if !HAVE_GPERF
443 static struct keyword const keywords[] = {
444 # include "token-table.c"
448 * gnulib efa15594e17fc20827dba66414fb391e99905394
450 * CMP(a, b) performs a three-valued comparison on a vs. b.
451 * It returns
452 * +1 if a > b
453 * 0 if a == b
454 * -1 if a < b
455 * The code (a > b) - (a < b) from Hacker's Delight para 2-9
456 * avoids conditional jumps in all GCC versions >= 3.4.
458 # define CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2)))
461 * Return values:
462 * +1 if strcmp(p1, p2) > 0
463 * 0 if strcmp(p1, p2) == 0
464 * -1 if strcmp(p1, p2) < 0
466 static inline __nonnull((1, 2)) __const int fastmemcmp(void const *p1, void const *p2, size_t n)
468 u_char const *s1 = (u_char const *)p1;
469 u_char const *s2 = (u_char const *)p2;
470 int comparison;
472 if (n == 0)
473 return 0;
475 do {
476 u_char c1 = *s1++;
477 u_char c2 = *s2++;
478 n--;
479 comparison = CMP(c1, c2);
481 if (comparison != 0)
482 return comparison;
483 } while (n != 0);
485 return comparison;
488 static inline int get_token_by_word(char const *word, size_t length)
490 size_t l = 0;
491 size_t r = countof(keywords);
493 while (l < r) {
494 size_t m = l + (r - l) / 2;
495 int d = CMP(length, keywords[m].length);
497 switch (d != 0 ? d : fastmemcmp(word, keywords[m].word, length)) {
498 case +1:
499 l = m + 1;
500 break;
501 case 0:
502 return keywords[m].token;
503 case -1:
504 r = m;
505 break;
509 yyerror("unknown keyword: %s", word);
510 return TOK_UNKNOWN;
512 # else
513 # include "get-keyword-by-word.c"
515 static inline int get_token_by_word(char const *word, size_t length)
517 struct keyword const *kw = get_keyword_by_word(word, length);
519 if (kw != NULL)
520 return kw->token;
522 yyerror("unknown keyword: %s", word);
523 return TOK_UNKNOWN;
525 # endif
527 # include <ctype.h>
529 FILE *yyin = NULL;
531 static int yylex(void)
533 char buf[256], *p = buf;
534 u_int qpos = 0;
535 bool quotes = false, escape = false;
536 int c;
537 enum { T_KEYWORD = 1, T_STRING, T_NUMBER, T_NAME } type = 0;
539 lrepeat:
540 /* Skip whitespaces first. */
541 while (c = getc(yyin), isblank((unsigned char)c) && c != EOF)
542 yylval.colno++;
544 /* Check for special one-character constructions. */
545 switch (c) {
546 case '#':
547 /* Skip comments. NUL is allowed. No continuation. */
548 while ((c = getc(yyin)) != '\n')
549 if (c == EOF)
550 goto leof;
551 fallthrough;
552 case '\n':
553 yylval.colno = 0;
554 yylval.lineno++;
555 return '\n';
556 case ',':
557 case '(':
558 case ')':
559 case '[':
560 case ']':
561 case '{':
562 case '}':
563 return c;
564 case EOF:
565 goto leof;
566 default:
567 if (c == '"')
568 type = T_STRING;
569 else if (isdigit((unsigned char)c))
570 type = T_NUMBER;
571 else if (islower((unsigned char)c) || c == '.')
572 type = T_KEYWORD;
573 else if (c == '\'')
574 type = T_NAME;
575 else
576 yyerror("unknown expression, expected string, number or keyword");
577 break;
580 /* Parsing next word. */
581 for (;; c = getc(yyin), yylval.colno++) {
582 switch (c) {
583 case '\0':
584 yyerror("unallowed character NUL (ignored)");
585 escape = false;
586 continue;
587 case '\\':
588 escape = !escape;
590 if (escape)
591 continue;
593 break;
594 case '\n':
595 if (quotes)
596 yyerror("unterminated quotes in column %u",
597 qpos + 1);
599 if (escape) {
600 escape = false;
601 yylval.colno = 0;
602 yylval.lineno++;
603 continue;
606 goto leow;
607 case EOF:
608 if (escape)
609 yyerror("unterminated escape");
611 if (quotes)
612 yyerror("unterminated quotes in column %u",
613 qpos);
615 goto leow;
616 case ',':
617 case '(':
618 case ')':
619 case '[':
620 case ']':
621 case '{':
622 case '}':
623 case '#':
624 case ' ':
625 case '\t':
626 if (!escape && !quotes)
627 goto leow;
629 break;
630 case '"':
631 if (!escape) {
632 quotes = !quotes;
634 if (quotes) {
635 type = T_STRING;
636 qpos = yylval.colno + 1;
639 continue;
641 break;
642 case '\'':
643 continue;
646 *p++ = c;
648 if (p == endof(buf)) {
649 yyerror("too long line");
650 p = buf;
653 escape = false;
656 leow:
657 *p = '\0';
658 ungetc(c == EOF ? '\n' : c, yyin);
660 if (p == buf) {
662 * There could be a number of reasons for empty buffer,
663 * and we handle all of them here, to avoid cluttering
664 * the main loop.
666 if (c == EOF) {
667 goto leof;
668 } else if (qpos == 0) {
670 * Accept, e.g., empty arguments:
671 * execute "foo" ""
673 goto lrepeat;
677 switch (type) {
678 case T_KEYWORD:
679 return get_token_by_word(buf, p - buf);
680 case T_STRING:
681 yylval.str.buf = xstrdup(buf);
682 return TOK_STRING;
683 case T_NAME:
684 yylval.str.buf = xstrdup(buf);
685 return TOK_NAME;
686 case T_NUMBER:
687 yylval.str.buf = xstrdup(buf);
688 return TOK_NUMBER;
691 yyerror("unknown token: %s", buf);
692 return TOK_UNKNOWN;
694 leof:
695 if (ferror(yyin) != 0)
696 yyerror("input error reading config");
698 return 0;
700 #endif