Updated license file.
[doas.git] / parse.y
blob27fb29008479dc2fd198010f032d5eb2dc6cb638
1 /* $OpenBSD: parse.y,v 1.26 2017/01/02 01:40:20 tedu Exp $ */
2 /*
3 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
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.
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.
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 <stdlib.h>
26 #include <string.h>
27 #include <err.h>
29 #include "doas.h"
31 typedef struct {
32 union {
33 struct {
34 int action;
35 int options;
36 const char *cmd;
37 const char **cmdargs;
38 const char **envlist;
40 const char **strlist;
41 const char *str;
43 int lineno;
44 int colno;
45 } yystype;
46 #define YYSTYPE yystype
48 FILE *yyfp;
50 struct rule **rules;
51 int nrules;
52 static int maxrules;
54 int parse_errors = 0;
56 static void yyerror(const char *, ...);
57 static int yylex(void);
59 static size_t
60 arraylen(const char **arr)
62 size_t cnt = 0;
64 while (*arr) {
65 cnt++;
66 arr++;
68 return cnt;
73 %token TPERMIT TDENY TAS TCMD TARGS
74 %token TNOPASS TPERSIST TKEEPENV TSETENV
75 %token TSTRING
79 grammar: /* empty */
80 | grammar '\n'
81 | grammar rule '\n'
82 | error '\n'
85 rule: action ident target cmd {
86 struct rule *r;
87 r = calloc(1, sizeof(*r));
88 if (!r)
89 errx(1, "can't allocate rule");
90 r->action = $1.action;
91 r->options = $1.options;
92 r->envlist = $1.envlist;
93 r->ident = $2.str;
94 r->target = $3.str;
95 r->cmd = $4.cmd;
96 r->cmdargs = $4.cmdargs;
97 if (nrules == maxrules) {
98 if (maxrules == 0)
99 maxrules = 63;
100 else
101 maxrules *= 2;
102 if (!(rules = reallocarray(rules, maxrules,
103 sizeof(*rules))))
104 errx(1, "can't allocate rules");
106 rules[nrules++] = r;
109 action: TPERMIT options {
110 $$.action = PERMIT;
111 $$.options = $2.options;
112 $$.envlist = $2.envlist;
113 } | TDENY {
114 $$.action = DENY;
115 $$.options = 0;
116 $$.envlist = NULL;
119 options: /* none */ {
120 $$.options = 0;
121 $$.envlist = NULL;
122 } | options option {
123 $$.options = $1.options | $2.options;
124 $$.envlist = $1.envlist;
125 if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
126 yyerror("can't combine nopass and persist");
127 YYERROR;
129 if ($2.envlist) {
130 if ($$.envlist) {
131 yyerror("can't have two setenv sections");
132 YYERROR;
133 } else
134 $$.envlist = $2.envlist;
137 option: TNOPASS {
138 $$.options = NOPASS;
139 $$.envlist = NULL;
140 } | TPERSIST {
141 $$.options = PERSIST;
142 $$.envlist = NULL;
143 } | TKEEPENV {
144 $$.options = KEEPENV;
145 $$.envlist = NULL;
146 } | TSETENV '{' strlist '}' {
147 $$.options = 0;
148 $$.envlist = $3.strlist;
151 strlist: /* empty */ {
152 if (!($$.strlist = calloc(1, sizeof(char *))))
153 errx(1, "can't allocate strlist");
154 } | strlist TSTRING {
155 int nstr = arraylen($1.strlist);
156 if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
157 sizeof(char *))))
158 errx(1, "can't allocate strlist");
159 $$.strlist[nstr] = $2.str;
160 $$.strlist[nstr + 1] = NULL;
164 ident: TSTRING {
165 $$.str = $1.str;
168 target: /* optional */ {
169 $$.str = NULL;
170 } | TAS TSTRING {
171 $$.str = $2.str;
174 cmd: /* optional */ {
175 $$.cmd = NULL;
176 $$.cmdargs = NULL;
177 } | TCMD TSTRING args {
178 $$.cmd = $2.str;
179 $$.cmdargs = $3.cmdargs;
182 args: /* empty */ {
183 $$.cmdargs = NULL;
184 } | TARGS strlist {
185 $$.cmdargs = $2.strlist;
190 void
191 yyerror(const char *fmt, ...)
193 va_list va;
195 fprintf(stderr, "doas: ");
196 va_start(va, fmt);
197 vfprintf(stderr, fmt, va);
198 va_end(va);
199 fprintf(stderr, " at line %d\n", yylval.lineno + 1);
200 parse_errors++;
203 static struct keyword {
204 const char *word;
205 int token;
206 } keywords[] = {
207 { "deny", TDENY },
208 { "permit", TPERMIT },
209 { "as", TAS },
210 { "cmd", TCMD },
211 { "args", TARGS },
212 { "nopass", TNOPASS },
213 { "persist", TPERSIST },
214 { "keepenv", TKEEPENV },
215 { "setenv", TSETENV },
219 yylex(void)
221 char buf[1024], *ebuf, *p, *str;
222 int i, c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
224 p = buf;
225 ebuf = buf + sizeof(buf);
227 repeat:
228 /* skip whitespace first */
229 for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
230 yylval.colno++;
232 /* check for special one-character constructions */
233 switch (c) {
234 case '\n':
235 yylval.colno = 0;
236 yylval.lineno++;
237 /* FALLTHROUGH */
238 case '{':
239 case '}':
240 return c;
241 case '#':
242 /* skip comments; NUL is allowed; no continuation */
243 while ((c = getc(yyfp)) != '\n')
244 if (c == EOF)
245 goto eof;
246 yylval.colno = 0;
247 yylval.lineno++;
248 return c;
249 case EOF:
250 goto eof;
253 /* parsing next word */
254 for (;; c = getc(yyfp), yylval.colno++) {
255 switch (c) {
256 case '\0':
257 yyerror("unallowed character NUL in column %d",
258 yylval.colno + 1);
259 escape = 0;
260 continue;
261 case '\\':
262 escape = !escape;
263 if (escape)
264 continue;
265 break;
266 case '\n':
267 if (quotes)
268 yyerror("unterminated quotes in column %d",
269 qpos + 1);
270 if (escape) {
271 nonkw = 1;
272 escape = 0;
273 yylval.colno = 0;
274 yylval.lineno++;
275 continue;
277 goto eow;
278 case EOF:
279 if (escape)
280 yyerror("unterminated escape in column %d",
281 yylval.colno);
282 if (quotes)
283 yyerror("unterminated quotes in column %d",
284 qpos + 1);
285 goto eow;
286 /* FALLTHROUGH */
287 case '{':
288 case '}':
289 case '#':
290 case ' ':
291 case '\t':
292 if (!escape && !quotes)
293 goto eow;
294 break;
295 case '"':
296 if (!escape) {
297 quotes = !quotes;
298 if (quotes) {
299 nonkw = 1;
300 qpos = yylval.colno;
302 continue;
305 *p++ = c;
306 if (p == ebuf) {
307 yyerror("too long line");
308 p = buf;
310 escape = 0;
313 eow:
314 *p = 0;
315 if (c != EOF)
316 ungetc(c, yyfp);
317 if (p == buf) {
319 * There could be a number of reasons for empty buffer,
320 * and we handle all of them here, to avoid cluttering
321 * the main loop.
323 if (c == EOF)
324 goto eof;
325 else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
326 goto repeat;
328 if (!nonkw) {
329 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
330 if (strcmp(buf, keywords[i].word) == 0)
331 return keywords[i].token;
334 if ((str = strdup(buf)) == NULL)
335 err(1, "strdup");
336 yylval.str = str;
337 return TSTRING;
339 eof:
340 if (ferror(yyfp))
341 yyerror("input error reading config");
342 return 0;