Annotation of src/usr.bin/doas/parse.y, Revision 1.29
1.29 ! millert 1: /* $OpenBSD: parse.y,v 1.28 2020/10/09 07:43:38 kn 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: };
1.26 tedu 39: const char **strlist;
1.1 tedu 40: const char *str;
41: };
1.10 zhuk 42: int lineno;
43: int colno;
1.1 tedu 44: } yystype;
45: #define YYSTYPE yystype
46:
47: FILE *yyfp;
48:
49: struct rule **rules;
1.29 ! millert 50: size_t nrules;
! 51: static size_t maxrules;
1.22 deraadt 52:
1.10 zhuk 53: int parse_errors = 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.28 kn 73: %token TNOPASS TNOLOG 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)
1.29 ! millert 98: maxrules = 32;
! 99: rules = reallocarray(rules, maxrules,
! 100: 2 * sizeof(*rules));
! 101: if (!rules)
1.1 tedu 102: errx(1, "can't allocate rules");
1.29 ! millert 103: maxrules *= 2;
1.1 tedu 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.28 kn 139: } | TNOLOG {
140: $$.options = NOLOG;
141: $$.envlist = NULL;
1.20 tedu 142: } | TPERSIST {
143: $$.options = PERSIST;
144: $$.envlist = NULL;
1.1 tedu 145: } | TKEEPENV {
146: $$.options = KEEPENV;
1.19 tedu 147: $$.envlist = NULL;
1.26 tedu 148: } | TSETENV '{' strlist '}' {
1.19 tedu 149: $$.options = 0;
1.26 tedu 150: $$.envlist = $3.strlist;
1.1 tedu 151: } ;
152:
1.26 tedu 153: strlist: /* empty */ {
154: if (!($$.strlist = calloc(1, sizeof(char *))))
155: errx(1, "can't allocate strlist");
156: } | strlist TSTRING {
157: int nstr = arraylen($1.strlist);
158: if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
1.6 benno 159: sizeof(char *))))
1.26 tedu 160: errx(1, "can't allocate strlist");
161: $$.strlist[nstr] = $2.str;
162: $$.strlist[nstr + 1] = NULL;
1.24 tedu 163: } ;
1.16 djm 164:
165:
1.1 tedu 166: ident: TSTRING {
167: $$.str = $1.str;
168: } ;
169:
170: target: /* optional */ {
171: $$.str = NULL;
172: } | TAS TSTRING {
173: $$.str = $2.str;
174: } ;
175:
176: cmd: /* optional */ {
1.7 zhuk 177: $$.cmd = NULL;
178: $$.cmdargs = NULL;
179: } | TCMD TSTRING args {
180: $$.cmd = $2.str;
181: $$.cmdargs = $3.cmdargs;
182: } ;
183:
184: args: /* empty */ {
185: $$.cmdargs = NULL;
1.26 tedu 186: } | TARGS strlist {
187: $$.cmdargs = $2.strlist;
1.1 tedu 188: } ;
189:
190: %%
191:
192: void
193: yyerror(const char *fmt, ...)
194: {
195: va_list va;
196:
1.15 gsoares 197: fprintf(stderr, "doas: ");
1.1 tedu 198: va_start(va, fmt);
1.10 zhuk 199: vfprintf(stderr, fmt, va);
200: va_end(va);
201: fprintf(stderr, " at line %d\n", yylval.lineno + 1);
202: parse_errors++;
1.1 tedu 203: }
204:
1.22 deraadt 205: static struct keyword {
1.1 tedu 206: const char *word;
207: int token;
208: } keywords[] = {
209: { "deny", TDENY },
210: { "permit", TPERMIT },
211: { "as", TAS },
212: { "cmd", TCMD },
1.7 zhuk 213: { "args", TARGS },
1.1 tedu 214: { "nopass", TNOPASS },
1.28 kn 215: { "nolog", TNOLOG },
1.20 tedu 216: { "persist", TPERSIST },
1.1 tedu 217: { "keepenv", TKEEPENV },
1.19 tedu 218: { "setenv", TSETENV },
1.1 tedu 219: };
220:
221: int
222: yylex(void)
223: {
224: char buf[1024], *ebuf, *p, *str;
1.29 ! millert 225: int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
! 226: size_t i;
1.1 tedu 227:
228: p = buf;
229: ebuf = buf + sizeof(buf);
1.9 zhuk 230:
1.5 benno 231: repeat:
1.9 zhuk 232: /* skip whitespace first */
233: for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
1.10 zhuk 234: yylval.colno++;
1.9 zhuk 235:
236: /* check for special one-character constructions */
1.1 tedu 237: switch (c) {
1.9 zhuk 238: case '\n':
1.10 zhuk 239: yylval.colno = 0;
240: yylval.lineno++;
1.9 zhuk 241: /* FALLTHROUGH */
242: case '{':
243: case '}':
244: return c;
245: case '#':
246: /* skip comments; NUL is allowed; no continuation */
247: while ((c = getc(yyfp)) != '\n')
248: if (c == EOF)
1.14 tedu 249: goto eof;
1.10 zhuk 250: yylval.colno = 0;
251: yylval.lineno++;
1.9 zhuk 252: return c;
253: case EOF:
1.14 tedu 254: goto eof;
1.1 tedu 255: }
1.9 zhuk 256:
257: /* parsing next word */
1.10 zhuk 258: for (;; c = getc(yyfp), yylval.colno++) {
1.3 zhuk 259: switch (c) {
1.9 zhuk 260: case '\0':
1.11 deraadt 261: yyerror("unallowed character NUL in column %d",
262: yylval.colno + 1);
1.9 zhuk 263: escape = 0;
264: continue;
265: case '\\':
266: escape = !escape;
267: if (escape)
268: continue;
269: break;
1.3 zhuk 270: case '\n':
1.9 zhuk 271: if (quotes)
1.10 zhuk 272: yyerror("unterminated quotes in column %d",
273: qpos + 1);
1.9 zhuk 274: if (escape) {
275: nonkw = 1;
276: escape = 0;
1.12 mikeb 277: yylval.colno = 0;
278: yylval.lineno++;
1.9 zhuk 279: continue;
280: }
281: goto eow;
282: case EOF:
283: if (escape)
1.10 zhuk 284: yyerror("unterminated escape in column %d",
285: yylval.colno);
1.9 zhuk 286: if (quotes)
1.10 zhuk 287: yyerror("unterminated quotes in column %d",
288: qpos + 1);
289: goto eow;
1.9 zhuk 290: /* FALLTHROUGH */
1.3 zhuk 291: case '{':
292: case '}':
293: case '#':
294: case ' ':
295: case '\t':
1.9 zhuk 296: if (!escape && !quotes)
297: goto eow;
298: break;
299: case '"':
300: if (!escape) {
301: quotes = !quotes;
302: if (quotes) {
303: nonkw = 1;
1.10 zhuk 304: qpos = yylval.colno;
1.9 zhuk 305: }
306: continue;
307: }
1.3 zhuk 308: }
1.1 tedu 309: *p++ = c;
1.13 tedu 310: if (p == ebuf) {
1.10 zhuk 311: yyerror("too long line");
1.13 tedu 312: p = buf;
313: }
1.9 zhuk 314: escape = 0;
1.1 tedu 315: }
1.9 zhuk 316:
1.3 zhuk 317: eow:
1.1 tedu 318: *p = 0;
319: if (c != EOF)
320: ungetc(c, yyfp);
1.9 zhuk 321: if (p == buf) {
322: /*
1.11 deraadt 323: * There could be a number of reasons for empty buffer,
324: * and we handle all of them here, to avoid cluttering
325: * the main loop.
1.9 zhuk 326: */
327: if (c == EOF)
1.14 tedu 328: goto eof;
1.10 zhuk 329: else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
1.9 zhuk 330: goto repeat;
331: }
332: if (!nonkw) {
333: for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
334: if (strcmp(buf, keywords[i].word) == 0)
335: return keywords[i].token;
336: }
1.1 tedu 337: }
338: if ((str = strdup(buf)) == NULL)
1.27 krw 339: err(1, "%s", __func__);
1.1 tedu 340: yylval.str = str;
341: return TSTRING;
1.14 tedu 342:
343: eof:
344: if (ferror(yyfp))
345: yyerror("input error reading config");
346: return 0;
1.1 tedu 347: }