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