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