GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/doas/parse.y Lines: 61 74 82.4 %
Date: 2017-11-07 Branches: 51 81 63.0 %

Line Branch Exec Source
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>
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;
35
			const char *cmd;
36
			const char **cmdargs;
37
			const char **envlist;
38
		};
39
		const char **strlist;
40
		const char *str;
41
	};
42
	int lineno;
43
	int colno;
44
} yystype;
45
#define YYSTYPE yystype
46
47
FILE *yyfp;
48
49
struct rule **rules;
50
int nrules;
51
static int maxrules;
52
53
int parse_errors = 0;
54
55
static void yyerror(const char *, ...);
56
static int yylex(void);
57
58
static size_t
59
arraylen(const char **arr)
60
{
61
	size_t cnt = 0;
62
63
6228
	while (*arr) {
64
2709
		cnt++;
65
2709
		arr++;
66
	}
67
270
	return cnt;
68
}
69
70
%}
71
72
%token TPERMIT TDENY TAS TCMD TARGS
73
%token TNOPASS TPERSIST TKEEPENV TSETENV
74
%token TSTRING
75
76
%%
77
78
grammar:	/* empty */
79
		| grammar '\n'
80
		| grammar rule '\n'
81
		| error '\n'
82
		;
83
84
rule:		action ident target cmd {
85
			struct rule *r;
86
			r = calloc(1, sizeof(*r));
87
			if (!r)
88
				errx(1, "can't allocate rule");
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;
94
			r->cmd = $4.cmd;
95
			r->cmdargs = $4.cmdargs;
96
			if (nrules == maxrules) {
97
				if (maxrules == 0)
98
					maxrules = 63;
99
				else
100
					maxrules *= 2;
101
				if (!(rules = reallocarray(rules, maxrules,
102
				    sizeof(*rules))))
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;
114
			$$.options = 0;
115
			$$.envlist = NULL;
116
		} ;
117
118
options:	/* none */ {
119
			$$.options = 0;
120
			$$.envlist = NULL;
121
		} | options option {
122
			$$.options = $1.options | $2.options;
123
			$$.envlist = $1.envlist;
124
			if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) {
125
				yyerror("can't combine nopass and persist");
126
				YYERROR;
127
			}
128
			if ($2.envlist) {
129
				if ($$.envlist) {
130
					yyerror("can't have two setenv sections");
131
					YYERROR;
132
				} else
133
					$$.envlist = $2.envlist;
134
			}
135
		} ;
136
option:		TNOPASS {
137
			$$.options = NOPASS;
138
			$$.envlist = NULL;
139
		} | TPERSIST {
140
			$$.options = PERSIST;
141
			$$.envlist = NULL;
142
		} | TKEEPENV {
143
			$$.options = KEEPENV;
144
			$$.envlist = NULL;
145
		} | TSETENV '{' strlist '}' {
146
			$$.options = 0;
147
			$$.envlist = $3.strlist;
148
		} ;
149
150
strlist:	/* empty */ {
151
			if (!($$.strlist = calloc(1, sizeof(char *))))
152
				errx(1, "can't allocate strlist");
153
		} | strlist TSTRING {
154
			int nstr = arraylen($1.strlist);
155
			if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
156
			    sizeof(char *))))
157
				errx(1, "can't allocate strlist");
158
			$$.strlist[nstr] = $2.str;
159
			$$.strlist[nstr + 1] = NULL;
160
		} ;
161
162
163
ident:		TSTRING {
164
			$$.str = $1.str;
165
		} ;
166
167
target:		/* optional */ {
168
			$$.str = NULL;
169
		} | TAS TSTRING {
170
			$$.str = $2.str;
171
		} ;
172
173
cmd:		/* optional */ {
174
			$$.cmd = NULL;
175
			$$.cmdargs = NULL;
176
		} | TCMD TSTRING args {
177
			$$.cmd = $2.str;
178
			$$.cmdargs = $3.cmdargs;
179
		} ;
180
181
args:		/* empty */ {
182
			$$.cmdargs = NULL;
183
		} | TARGS strlist {
184
			$$.cmdargs = $2.strlist;
185
		} ;
186
187
%%
188
189
void
190
yyerror(const char *fmt, ...)
191
{
192
54
	va_list va;
193
194
27
	fprintf(stderr, "doas: ");
195
27
	va_start(va, fmt);
196
27
	vfprintf(stderr, fmt, va);
197
27
	va_end(va);
198
27
	fprintf(stderr, " at line %d\n", yylval.lineno + 1);
199
27
	parse_errors++;
200
27
}
201
202
static struct keyword {
203
	const char *word;
204
	int token;
205
} keywords[] = {
206
	{ "deny", TDENY },
207
	{ "permit", TPERMIT },
208
	{ "as", TAS },
209
	{ "cmd", TCMD },
210
	{ "args", TARGS },
211
	{ "nopass", TNOPASS },
212
	{ "persist", TPERSIST },
213
	{ "keepenv", TKEEPENV },
214
	{ "setenv", TSETENV },
215
};
216
217
int
218
yylex(void)
219
{
220
2070
	char buf[1024], *ebuf, *p, *str;
221
	int i, c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
222
223
1035
	p = buf;
224
1035
	ebuf = buf + sizeof(buf);
225
226
repeat:
227
	/* skip whitespace first */
228


10035
	for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
229
918
		yylval.colno++;
230
231
	/* check for special one-character constructions */
232

1260
	switch (c) {
233
		case '\n':
234
171
			yylval.colno = 0;
235
171
			yylval.lineno++;
236
			/* FALLTHROUGH */
237
		case '{':
238
		case '}':
239
189
			return c;
240
		case '#':
241
			/* skip comments; NUL is allowed; no continuation */
242
			while ((c = getc(yyfp)) != '\n')
243
				if (c == EOF)
244
					goto eof;
245
			yylval.colno = 0;
246
			yylval.lineno++;
247
			return c;
248
		case EOF:
249
			goto eof;
250
	}
251
252
	/* parsing next word */
253

20808
	for (;; c = getc(yyfp), yylval.colno++) {
254


6111
		switch (c) {
255
		case '\0':
256
			yyerror("unallowed character NUL in column %d",
257
			    yylval.colno + 1);
258
			escape = 0;
259
			continue;
260
		case '\\':
261
72
			escape = !escape;
262
72
			if (escape)
263
				continue;
264
			break;
265
		case '\n':
266
198
			if (quotes)
267
9
				yyerror("unterminated quotes in column %d",
268
9
				    qpos + 1);
269
198
			if (escape) {
270
				nonkw = 1;
271
				escape = 0;
272
63
				yylval.colno = 0;
273
63
				yylval.lineno++;
274
63
				continue;
275
			}
276
			goto eow;
277
		case EOF:
278
9
			if (escape)
279
				yyerror("unterminated escape in column %d",
280
				    yylval.colno);
281
9
			if (quotes)
282
9
				yyerror("unterminated quotes in column %d",
283
9
				    qpos + 1);
284
			goto eow;
285
			/* FALLTHROUGH */
286
		case '{':
287
		case '}':
288
		case '#':
289
		case ' ':
290
		case '\t':
291
765
			if (!escape && !quotes)
292
				goto eow;
293
			break;
294
		case '"':
295
162
			if (!escape) {
296
162
				quotes = !quotes;
297
162
				if (quotes) {
298
					nonkw = 1;
299
90
					qpos = yylval.colno;
300
90
				}
301
				continue;
302
			}
303
		}
304
4905
		*p++ = c;
305
4905
		if (p == ebuf) {
306
			yyerror("too long line");
307
			p = buf;
308
		}
309
		escape = 0;
310
4905
	}
311
312
eow:
313
882
	*p = 0;
314
882
	if (c != EOF)
315
873
		ungetc(c, yyfp);
316
882
	if (p == buf) {
317
		/*
318
		 * There could be a number of reasons for empty buffer,
319
		 * and we handle all of them here, to avoid cluttering
320
		 * the main loop.
321
		 */
322
72
		if (c == EOF)
323
			goto eof;
324
72
		else if (qpos == -1)    /* accept, e.g., empty args: cmd foo args "" */
325
			goto repeat;
326
	}
327
828
	if (!nonkw) {
328
9900
		for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
329
4563
			if (strcmp(buf, keywords[i].word) == 0)
330
315
				return keywords[i].token;
331
		}
332
	}
333
513
	if ((str = strdup(buf)) == NULL)
334
		err(1, "strdup");
335
513
	yylval.str = str;
336
513
	return TSTRING;
337
338
eof:
339

36
	if (ferror(yyfp))
340
		yyerror("input error reading config");
341
18
	return 0;
342
1035
}