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