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