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