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