]> git.armaanb.net Git - opendoas.git/blob - parse.y
it has been six months and two days... remove keepenv { obsolete } syntax
[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;
53 static int maxrules;
54
55 int parse_errors = 0;
56
57 static void yyerror(const char *, ...);
58 static int yylex(void);
59
60 static size_t
61 arraylen(const char **arr)
62 {
63         size_t cnt = 0;
64
65         while (*arr) {
66                 cnt++;
67                 arr++;
68         }
69         return cnt;
70 }
71
72 %}
73
74 %token TPERMIT TDENY TAS TCMD TARGS
75 %token TNOPASS TPERSIST TKEEPENV TSETENV
76 %token TSTRING
77
78 %%
79
80 grammar:        /* empty */
81                 | grammar '\n'
82                 | grammar rule '\n'
83                 | error '\n'
84                 ;
85
86 rule:           action ident target cmd {
87                         struct rule *r;
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 = 63;
101                                 else
102                                         maxrules *= 2;
103                                 if (!(rules = reallocarray(rules, maxrules,
104                                     sizeof(*rules))))
105                                         errx(1, "can't allocate rules");
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                 } | TPERSIST {
142                         $$.options = PERSIST;
143                         $$.envlist = NULL;
144                 } | TKEEPENV {
145                         $$.options = KEEPENV;
146                         $$.envlist = NULL;
147                 } | TSETENV '{' envlist '}' {
148                         $$.options = 0;
149                         $$.envlist = $3.envlist;
150                 } ;
151
152 envlist:        /* empty */ {
153                         $$.envlist = NULL;
154                 } | envlist TSTRING {
155                         int nenv = arraylen($1.envlist);
156                         if (!($$.envlist = reallocarray($1.envlist, nenv + 2,
157                             sizeof(char *))))
158                                 errx(1, "can't allocate envlist");
159                         $$.envlist[nenv] = $2.str;
160                         $$.envlist[nenv + 1] = NULL;
161                 } ;
162
163
164 ident:          TSTRING {
165                         $$.str = $1.str;
166                 } ;
167
168 target:         /* optional */ {
169                         $$.str = NULL;
170                 } | TAS TSTRING {
171                         $$.str = $2.str;
172                 } ;
173
174 cmd:            /* optional */ {
175                         $$.cmd = NULL;
176                         $$.cmdargs = NULL;
177                 } | TCMD TSTRING args {
178                         $$.cmd = $2.str;
179                         $$.cmdargs = $3.cmdargs;
180                 } ;
181
182 args:           /* empty */ {
183                         $$.cmdargs = NULL;
184                 } | TARGS argslist {
185                         $$.cmdargs = $2.cmdargs;
186                 } ;
187
188 argslist:       /* empty */ {
189                         $$.cmdargs = NULL;
190                 } | argslist TSTRING {
191                         int nargs = arraylen($1.cmdargs);
192                         if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2,
193                             sizeof(char *))))
194                                 errx(1, "can't allocate args");
195                         $$.cmdargs[nargs] = $2.str;
196                         $$.cmdargs[nargs + 1] = NULL;
197                 } ;
198
199 %%
200
201 void
202 yyerror(const char *fmt, ...)
203 {
204         va_list va;
205
206         va_start(va, fmt);
207         vfprintf(stderr, fmt, va);
208         va_end(va);
209         fprintf(stderr, " at line %d\n", yylval.lineno + 1);
210         parse_errors++;
211 }
212
213 static struct keyword {
214         const char *word;
215         int token;
216 } keywords[] = {
217         { "deny", TDENY },
218         { "permit", TPERMIT },
219         { "as", TAS },
220         { "cmd", TCMD },
221         { "args", TARGS },
222         { "nopass", TNOPASS },
223         { "persist", TPERSIST },
224         { "keepenv", TKEEPENV },
225         { "setenv", TSETENV },
226 };
227
228 int
229 yylex(void)
230 {
231         char buf[1024], *ebuf, *p, *str;
232         int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
233
234         p = buf;
235         ebuf = buf + sizeof(buf);
236
237 repeat:
238         /* skip whitespace first */
239         for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
240                 yylval.colno++;
241
242         /* check for special one-character constructions */
243         switch (c) {
244                 case '\n':
245                         yylval.colno = 0;
246                         yylval.lineno++;
247                         /* FALLTHROUGH */
248                 case '{':
249                 case '}':
250                         return c;
251                 case '#':
252                         /* skip comments; NUL is allowed; no continuation */
253                         while ((c = getc(yyfp)) != '\n')
254                                 if (c == EOF)
255                                         return 0;
256                         yylval.colno = 0;
257                         yylval.lineno++;
258                         return c;
259                 case EOF:
260                         return 0;
261         }
262
263         /* parsing next word */
264         for (;; c = getc(yyfp), yylval.colno++) {
265                 switch (c) {
266                 case '\0':
267                         yyerror("unallowed character NUL in column %d",
268                             yylval.colno + 1);
269                         escape = 0;
270                         continue;
271                 case '\\':
272                         escape = !escape;
273                         if (escape)
274                                 continue;
275                         break;
276                 case '\n':
277                         if (quotes)
278                                 yyerror("unterminated quotes in column %d",
279                                     qpos + 1);
280                         if (escape) {
281                                 nonkw = 1;
282                                 escape = 0;
283                                 continue;
284                         }
285                         goto eow;
286                 case EOF:
287                         if (escape)
288                                 yyerror("unterminated escape in column %d",
289                                     yylval.colno);
290                         if (quotes)
291                                 yyerror("unterminated quotes in column %d",
292                                     qpos + 1);
293                         goto eow;
294                         /* FALLTHROUGH */
295                 case '{':
296                 case '}':
297                 case '#':
298                 case ' ':
299                 case '\t':
300                         if (!escape && !quotes)
301                                 goto eow;
302                         break;
303                 case '"':
304                         if (!escape) {
305                                 quotes = !quotes;
306                                 if (quotes) {
307                                         nonkw = 1;
308                                         qpos = yylval.colno;
309                                 }
310                                 continue;
311                         }
312                 }
313                 *p++ = c;
314                 if (p == ebuf)
315                         yyerror("too long line");
316                 escape = 0;
317         }
318
319 eow:
320         *p = 0;
321         if (c != EOF)
322                 ungetc(c, yyfp);
323         if (p == buf) {
324                 /*
325                  * There could be a number of reasons for empty buffer,
326                  * and we handle all of them here, to avoid cluttering
327                  * the main loop.
328                  */
329                 if (c == EOF)
330                         return 0;
331                 else if (qpos == -1)    /* accept, e.g., empty args: cmd foo args "" */
332                         goto repeat;
333         }
334         if (!nonkw) {
335                 size_t i;
336                 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
337                         if (strcmp(buf, keywords[i].word) == 0)
338                                 return keywords[i].token;
339                 }
340         }
341         if ((str = strdup(buf)) == NULL)
342                 err(1, "strdup");
343         yylval.str = str;
344         return TSTRING;
345 }