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