]> git.armaanb.net Git - opendoas.git/blob - parse.y
15c00c128ce11a1a9b9a5e693b358cc7b73393d6
[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 **strlist;
43                 const char *str;
44         };
45         int lineno;
46         int colno;
47 } yystype;
48 #define YYSTYPE yystype
49
50 FILE *yyfp;
51
52 struct rule **rules;
53 int nrules;
54 static int maxrules;
55
56 int parse_errors = 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 TNOLOG 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                 } | TNOLOG {
143                         $$.options = NOLOG;
144                         $$.envlist = NULL;
145                 } | TPERSIST {
146                         $$.options = PERSIST;
147                         $$.envlist = NULL;
148                 } | TKEEPENV {
149                         $$.options = KEEPENV;
150                         $$.envlist = NULL;
151                 } | TSETENV '{' strlist '}' {
152                         $$.options = 0;
153                         $$.envlist = $3.strlist;
154                 } ;
155
156 strlist:        /* empty */ {
157                         if (!($$.strlist = calloc(1, sizeof(char *))))
158                                 errx(1, "can't allocate strlist");
159                 } | strlist TSTRING {
160                         int nstr = arraylen($1.strlist);
161                         if (!($$.strlist = reallocarray($1.strlist, nstr + 2,
162                             sizeof(char *))))
163                                 errx(1, "can't allocate strlist");
164                         $$.strlist[nstr] = $2.str;
165                         $$.strlist[nstr + 1] = NULL;
166                 } ;
167
168
169 ident:          TSTRING {
170                         $$.str = $1.str;
171                 } ;
172
173 target:         /* optional */ {
174                         $$.str = NULL;
175                 } | TAS TSTRING {
176                         $$.str = $2.str;
177                 } ;
178
179 cmd:            /* optional */ {
180                         $$.cmd = NULL;
181                         $$.cmdargs = NULL;
182                 } | TCMD TSTRING args {
183                         $$.cmd = $2.str;
184                         $$.cmdargs = $3.cmdargs;
185                 } ;
186
187 args:           /* empty */ {
188                         $$.cmdargs = NULL;
189                 } | TARGS strlist {
190                         $$.cmdargs = $2.strlist;
191                 } ;
192
193 %%
194
195 void
196 yyerror(const char *fmt, ...)
197 {
198         va_list va;
199
200         fprintf(stderr, "doas: ");
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 static 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         { "nolog", TNOLOG },
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, "%s", __func__);
339         yylval.str = str;
340         return TSTRING;
341 }