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