Annotation of src/usr.bin/doas/parse.y, Revision 1.31
1.31 ! deraadt 1: /* $OpenBSD: parse.y,v 1.30 2021/11/30 20:08:15 tobias Exp $ */
1.1 tedu 2: /*
3: * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17:
18: %{
19: #include <sys/types.h>
20: #include <ctype.h>
1.30 tobias 21: #include <limits.h>
1.1 tedu 22: #include <unistd.h>
23: #include <stdint.h>
24: #include <stdarg.h>
25: #include <stdio.h>
26: #include <string.h>
27: #include <err.h>
28:
29: #include "doas.h"
30:
31: typedef struct {
32: union {
33: struct {
34: int action;
35: int options;
1.7 zhuk 36: const char *cmd;
37: const char **cmdargs;
1.1 tedu 38: const char **envlist;
39: };
1.26 tedu 40: const char **strlist;
1.1 tedu 41: const char *str;
42: };
1.30 tobias 43: unsigned long lineno;
44: unsigned long colno;
1.1 tedu 45: } yystype;
46: #define YYSTYPE yystype
47:
48: FILE *yyfp;
49:
50: struct rule **rules;
1.29 millert 51: size_t nrules;
52: static size_t maxrules;
1.22 deraadt 53:
1.30 tobias 54: int parse_error = 0;
1.1 tedu 55:
1.22 deraadt 56: static void yyerror(const char *, ...);
57: static int yylex(void);
1.23 tedu 58:
59: static size_t
60: arraylen(const char **arr)
61: {
62: size_t cnt = 0;
63:
64: while (*arr) {
65: cnt++;
66: arr++;
67: }
68: return cnt;
69: }
1.4 nicm 70:
1.1 tedu 71: %}
72:
1.7 zhuk 73: %token TPERMIT TDENY TAS TCMD TARGS
1.28 kn 74: %token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV
1.1 tedu 75: %token TSTRING
76:
77: %%
78:
79: grammar: /* empty */
80: | grammar '\n'
81: | grammar rule '\n'
1.10 zhuk 82: | error '\n'
1.1 tedu 83: ;
84:
85: rule: action ident target cmd {
86: struct rule *r;
1.31 ! deraadt 87:
1.1 tedu 88: r = calloc(1, sizeof(*r));
1.2 nicm 89: if (!r)
90: errx(1, "can't allocate rule");
1.1 tedu 91: r->action = $1.action;
92: r->options = $1.options;
93: r->envlist = $1.envlist;
94: r->ident = $2.str;
95: r->target = $3.str;
1.7 zhuk 96: r->cmd = $4.cmd;
97: r->cmdargs = $4.cmdargs;
1.1 tedu 98: if (nrules == maxrules) {
99: if (maxrules == 0)
1.29 millert 100: maxrules = 32;
101: rules = reallocarray(rules, maxrules,
102: 2 * sizeof(*rules));
103: if (!rules)
1.1 tedu 104: errx(1, "can't allocate rules");
1.29 millert 105: maxrules *= 2;
1.1 tedu 106: }
107: rules[nrules++] = r;
108: } ;
109:
110: action: TPERMIT options {
111: $$.action = PERMIT;
112: $$.options = $2.options;
113: $$.envlist = $2.envlist;
114: } | TDENY {
115: $$.action = DENY;
1.19 tedu 116: $$.options = 0;
117: $$.envlist = NULL;
1.1 tedu 118: } ;
119:
1.19 tedu 120: options: /* none */ {
121: $$.options = 0;
122: $$.envlist = NULL;
123: } | options option {
1.1 tedu 124: $$.options = $1.options | $2.options;
125: $$.envlist = $1.envlist;
1.21 tedu 126: if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
127: yyerror("can't combine nopass and persist");
128: YYERROR;
129: }
1.1 tedu 130: if ($2.envlist) {
1.10 zhuk 131: if ($$.envlist) {
1.19 tedu 132: yyerror("can't have two setenv sections");
1.10 zhuk 133: YYERROR;
134: } else
1.1 tedu 135: $$.envlist = $2.envlist;
136: }
137: } ;
138: option: TNOPASS {
139: $$.options = NOPASS;
1.19 tedu 140: $$.envlist = NULL;
1.28 kn 141: } | TNOLOG {
142: $$.options = NOLOG;
143: $$.envlist = NULL;
1.20 tedu 144: } | TPERSIST {
145: $$.options = PERSIST;
146: $$.envlist = NULL;
1.1 tedu 147: } | TKEEPENV {
148: $$.options = KEEPENV;
1.19 tedu 149: $$.envlist = NULL;
1.26 tedu 150: } | TSETENV '{' strlist '}' {
1.19 tedu 151: $$.options = 0;
1.26 tedu 152: $$.envlist = $3.strlist;
1.1 tedu 153: } ;
154:
1.26 tedu 155: strlist: /* empty */ {
156: if (!($$.strlist = calloc(1, sizeof(char *))))
157: errx(1, "can't allocate strlist");
158: } | strlist TSTRING {
159: int nstr = arraylen($1.strlist);
1.31 ! deraadt 160:
1.26 tedu 161: if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
1.6 benno 162: sizeof(char *))))
1.26 tedu 163: errx(1, "can't allocate strlist");
164: $$.strlist[nstr] = $2.str;
165: $$.strlist[nstr + 1] = NULL;
1.24 tedu 166: } ;
1.16 djm 167:
168:
1.1 tedu 169: ident: TSTRING {
170: $$.str = $1.str;
171: } ;
172:
173: target: /* optional */ {
174: $$.str = NULL;
175: } | TAS TSTRING {
176: $$.str = $2.str;
177: } ;
178:
179: cmd: /* optional */ {
1.7 zhuk 180: $$.cmd = NULL;
181: $$.cmdargs = NULL;
182: } | TCMD TSTRING args {
183: $$.cmd = $2.str;
184: $$.cmdargs = $3.cmdargs;
185: } ;
186:
187: args: /* empty */ {
188: $$.cmdargs = NULL;
1.26 tedu 189: } | TARGS strlist {
190: $$.cmdargs = $2.strlist;
1.1 tedu 191: } ;
192:
193: %%
194:
195: void
196: yyerror(const char *fmt, ...)
197: {
198: va_list va;
199:
1.15 gsoares 200: fprintf(stderr, "doas: ");
1.1 tedu 201: va_start(va, fmt);
1.10 zhuk 202: vfprintf(stderr, fmt, va);
203: va_end(va);
1.30 tobias 204: fprintf(stderr, " at line %lu\n", yylval.lineno + 1);
205: parse_error = 1;
1.1 tedu 206: }
207:
1.22 deraadt 208: static struct keyword {
1.1 tedu 209: const char *word;
210: int token;
211: } keywords[] = {
212: { "deny", TDENY },
213: { "permit", TPERMIT },
214: { "as", TAS },
215: { "cmd", TCMD },
1.7 zhuk 216: { "args", TARGS },
1.1 tedu 217: { "nopass", TNOPASS },
1.28 kn 218: { "nolog", TNOLOG },
1.20 tedu 219: { "persist", TPERSIST },
1.1 tedu 220: { "keepenv", TKEEPENV },
1.19 tedu 221: { "setenv", TSETENV },
1.1 tedu 222: };
223:
224: int
225: yylex(void)
226: {
227: char buf[1024], *ebuf, *p, *str;
1.30 tobias 228: int c, quoted = 0, quotes = 0, qerr = 0, escape = 0, nonkw = 0;
229: unsigned long qpos = 0;
1.29 millert 230: size_t i;
1.1 tedu 231:
232: p = buf;
233: ebuf = buf + sizeof(buf);
1.9 zhuk 234:
1.5 benno 235: repeat:
1.9 zhuk 236: /* skip whitespace first */
237: for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
1.10 zhuk 238: yylval.colno++;
1.9 zhuk 239:
240: /* check for special one-character constructions */
1.1 tedu 241: switch (c) {
1.9 zhuk 242: case '\n':
1.10 zhuk 243: yylval.colno = 0;
244: yylval.lineno++;
1.9 zhuk 245: /* FALLTHROUGH */
246: case '{':
247: case '}':
248: return c;
249: case '#':
250: /* skip comments; NUL is allowed; no continuation */
251: while ((c = getc(yyfp)) != '\n')
252: if (c == EOF)
1.14 tedu 253: goto eof;
1.10 zhuk 254: yylval.colno = 0;
255: yylval.lineno++;
1.9 zhuk 256: return c;
257: case EOF:
1.14 tedu 258: goto eof;
1.1 tedu 259: }
1.9 zhuk 260:
261: /* parsing next word */
1.10 zhuk 262: for (;; c = getc(yyfp), yylval.colno++) {
1.3 zhuk 263: switch (c) {
1.9 zhuk 264: case '\0':
1.30 tobias 265: yyerror("unallowed character NUL in column %lu",
1.11 deraadt 266: yylval.colno + 1);
1.9 zhuk 267: escape = 0;
268: continue;
269: case '\\':
270: escape = !escape;
271: if (escape)
272: continue;
273: break;
1.3 zhuk 274: case '\n':
1.30 tobias 275: if (quotes && !qerr) {
276: yyerror("unterminated quotes in column %lu",
1.10 zhuk 277: qpos + 1);
1.30 tobias 278: qerr = 1;
279: }
1.9 zhuk 280: if (escape) {
281: nonkw = 1;
282: escape = 0;
1.30 tobias 283: yylval.colno = ULONG_MAX;
1.12 mikeb 284: yylval.lineno++;
1.9 zhuk 285: continue;
286: }
287: goto eow;
288: case EOF:
289: if (escape)
1.30 tobias 290: yyerror("unterminated escape in column %lu",
1.10 zhuk 291: yylval.colno);
1.30 tobias 292: if (quotes && !qerr)
293: yyerror("unterminated quotes in column %lu",
1.10 zhuk 294: qpos + 1);
295: goto eow;
1.3 zhuk 296: case '{':
297: case '}':
298: case '#':
299: case ' ':
300: case '\t':
1.9 zhuk 301: if (!escape && !quotes)
302: goto eow;
303: break;
304: case '"':
305: if (!escape) {
1.30 tobias 306: quoted = 1;
1.9 zhuk 307: quotes = !quotes;
308: if (quotes) {
309: nonkw = 1;
1.30 tobias 310: qerr = 0;
1.10 zhuk 311: qpos = yylval.colno;
1.9 zhuk 312: }
313: continue;
314: }
1.3 zhuk 315: }
1.1 tedu 316: *p++ = c;
1.13 tedu 317: if (p == ebuf) {
1.10 zhuk 318: yyerror("too long line");
1.13 tedu 319: p = buf;
320: }
1.9 zhuk 321: escape = 0;
1.1 tedu 322: }
1.9 zhuk 323:
1.3 zhuk 324: eow:
1.1 tedu 325: *p = 0;
326: if (c != EOF)
327: ungetc(c, yyfp);
1.9 zhuk 328: if (p == buf) {
329: /*
1.11 deraadt 330: * There could be a number of reasons for empty buffer,
331: * and we handle all of them here, to avoid cluttering
332: * the main loop.
1.9 zhuk 333: */
334: if (c == EOF)
1.14 tedu 335: goto eof;
1.30 tobias 336: else if (!quoted) /* accept, e.g., empty args: cmd foo args "" */
1.9 zhuk 337: goto repeat;
338: }
339: if (!nonkw) {
340: for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
341: if (strcmp(buf, keywords[i].word) == 0)
342: return keywords[i].token;
343: }
1.1 tedu 344: }
345: if ((str = strdup(buf)) == NULL)
1.27 krw 346: err(1, "%s", __func__);
1.1 tedu 347: yylval.str = str;
348: return TSTRING;
1.14 tedu 349:
350: eof:
351: if (ferror(yyfp))
352: yyerror("input error reading config");
353: return 0;
1.1 tedu 354: }