]> git.armaanb.net Git - opendoas.git/blob - parse.y
Implement quoting support in doas.conf. Now you can pass environment
[opendoas.git] / parse.y
1 /* $OpenBSD: parse.y,v 1.8 2015/07/21 16:12:04 tedu 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 <unistd.h>
22 #include <stdint.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <err.h>
27
28 #include "doas.h"
29
30 typedef struct {
31         union {
32                 struct {
33                         int action;
34                         int options;
35                         const char *cmd;
36                         const char **cmdargs;
37                         const char **envlist;
38                 };
39                 const char *str;
40         };
41 } yystype;
42 #define YYSTYPE yystype
43
44 FILE *yyfp;
45
46 struct rule **rules;
47 int nrules, maxrules;
48
49 void yyerror(const char *, ...);
50 int yylex(void);
51 int yyparse(void);
52
53 %}
54
55 %token TPERMIT TDENY TAS TCMD TARGS
56 %token TNOPASS TKEEPENV
57 %token TSTRING
58
59 %%
60
61 grammar:        /* empty */
62                 | grammar '\n'
63                 | grammar rule '\n'
64                 ;
65
66 rule:           action ident target cmd {
67                         struct rule *r;
68                         r = calloc(1, sizeof(*r));
69                         if (!r)
70                                 errx(1, "can't allocate rule");
71                         r->action = $1.action;
72                         r->options = $1.options;
73                         r->envlist = $1.envlist;
74                         r->ident = $2.str;
75                         r->target = $3.str;
76                         r->cmd = $4.cmd;
77                         r->cmdargs = $4.cmdargs;
78                         if (nrules == maxrules) {
79                                 if (maxrules == 0)
80                                         maxrules = 63;
81                                 else
82                                         maxrules *= 2;
83                                 if (!(rules = reallocarray(rules, maxrules,
84                                     sizeof(*rules))))
85                                         errx(1, "can't allocate rules");
86                         }
87                         rules[nrules++] = r;
88                 } ;
89
90 action:         TPERMIT options {
91                         $$.action = PERMIT;
92                         $$.options = $2.options;
93                         $$.envlist = $2.envlist;
94                 } | TDENY {
95                         $$.action = DENY;
96                 } ;
97
98 options:        /* none */
99                 | options option {
100                         $$.options = $1.options | $2.options;
101                         $$.envlist = $1.envlist;
102                         if ($2.envlist) {
103                                 if ($$.envlist)
104                                         errx(1, "can't have two keepenv sections");
105                                 else
106                                         $$.envlist = $2.envlist;
107                         }
108                 } ;
109 option:         TNOPASS {
110                         $$.options = NOPASS;
111                 } | TKEEPENV {
112                         $$.options = KEEPENV;
113                 } | TKEEPENV '{' envlist '}' {
114                         $$.options = KEEPENV;
115                         $$.envlist = $3.envlist;
116                 } ;
117
118 envlist:        /* empty */ {
119                         if (!($$.envlist = calloc(1, sizeof(char *))))
120                                 errx(1, "can't allocate envlist");
121                 } | envlist TSTRING {
122                         int nenv = arraylen($1.envlist);
123                         if (!($$.envlist = reallocarray($1.envlist, nenv + 2,
124                             sizeof(char *))))
125                                 errx(1, "can't allocate envlist");
126                         $$.envlist[nenv] = $2.str;
127                         $$.envlist[nenv + 1] = NULL;
128                 }
129
130
131 ident:          TSTRING {
132                         $$.str = $1.str;
133                 } ;
134
135 target:         /* optional */ {
136                         $$.str = NULL;
137                 } | TAS TSTRING {
138                         $$.str = $2.str;
139                 } ;
140
141 cmd:            /* optional */ {
142                         $$.cmd = NULL;
143                         $$.cmdargs = NULL;
144                 } | TCMD TSTRING args {
145                         $$.cmd = $2.str;
146                         $$.cmdargs = $3.cmdargs;
147                 } ;
148
149 args:           /* empty */ {
150                         $$.cmdargs = NULL;
151                 } | TARGS argslist {
152                         $$.cmdargs = $2.cmdargs;
153                 } ;
154
155 argslist:       /* empty */ {
156                         if (!($$.cmdargs = calloc(1, sizeof(char *))))
157                                 errx(1, "can't allocate args");
158                 } | argslist TSTRING {
159                         int nargs = arraylen($1.cmdargs);
160                         if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2, sizeof(char *))))
161                                 errx(1, "can't allocate args");
162                         $$.cmdargs[nargs] = $2.str;
163                         $$.cmdargs[nargs + 1] = NULL;
164                 } ;
165
166 %%
167
168 void
169 yyerror(const char *fmt, ...)
170 {
171         va_list va;
172
173         va_start(va, fmt);
174         verrx(1, fmt, va);
175 }
176
177 struct keyword {
178         const char *word;
179         int token;
180 } keywords[] = {
181         { "deny", TDENY },
182         { "permit", TPERMIT },
183         { "as", TAS },
184         { "cmd", TCMD },
185         { "args", TARGS },
186         { "nopass", TNOPASS },
187         { "keepenv", TKEEPENV },
188 };
189
190 int
191 yylex(void)
192 {
193         static int colno = 1, lineno = 1;
194
195         char buf[1024], *ebuf, *p, *str;
196         int i, c, quotes = 0, escape = 0, qpos = 0, nonkw = 0;
197
198         p = buf;
199         ebuf = buf + sizeof(buf);
200
201 repeat:
202         /* skip whitespace first */
203         for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp))
204                 colno++;
205
206         /* check for special one-character constructions */
207         switch (c) {
208                 case '\n':
209                         colno = 1;
210                         lineno++;
211                         /* FALLTHROUGH */
212                 case '{':
213                 case '}':
214                         return c;
215                 case '#':
216                         /* skip comments; NUL is allowed; no continuation */
217                         while ((c = getc(yyfp)) != '\n')
218                                 if (c == EOF)
219                                         return 0;
220                         colno = 1;
221                         lineno++;
222                         return c;
223                 case EOF:
224                         return 0;
225         }
226
227         /* parsing next word */
228         for (;; c = getc(yyfp), colno++) {
229                 switch (c) {
230                 case '\0':
231                         yyerror("unallowed character NUL at "
232                             "line %d, column %d", lineno, colno);
233                         escape = 0;
234                         continue;
235                 case '\\':
236                         escape = !escape;
237                         if (escape)
238                                 continue;
239                         break;
240                 case '\n':
241                         if (quotes)
242                                 yyerror("unterminated quotes at line %d, column %d",
243                                     lineno, qpos);
244                         if (escape) {
245                                 nonkw = 1;
246                                 escape = 0;
247                                 continue;
248                         }
249                         goto eow;
250                 case EOF:
251                         if (escape)
252                                 yyerror("unterminated escape at line %d, column %d",
253                                     lineno, colno - 1);
254                         if (quotes)
255                                 yyerror("unterminated quotes at line %d, column %d",
256                                     lineno, qpos);
257                         /* FALLTHROUGH */
258                 case '{':
259                 case '}':
260                 case '#':
261                 case ' ':
262                 case '\t':
263                         if (!escape && !quotes)
264                                 goto eow;
265                         break;
266                 case '"':
267                         if (!escape) {
268                                 quotes = !quotes;
269                                 if (quotes) {
270                                         nonkw = 1;
271                                         qpos = colno;
272                                 }
273                                 continue;
274                         }
275                 }
276                 *p++ = c;
277                 if (p == ebuf)
278                         yyerror("too long line %d", lineno);
279                 escape = 0;
280         }
281
282 eow:
283         *p = 0;
284         if (c != EOF)
285                 ungetc(c, yyfp);
286         if (p == buf) {
287                 /*
288                  * There could be a number of reasons for empty buffer, and we handle
289                  * all of them here, to avoid cluttering the main loop.
290                  */
291                 if (c == EOF)
292                         return 0;
293                 else if (!qpos)    /* accept, e.g., empty args: cmd foo args "" */
294                         goto repeat;
295         }
296         if (!nonkw) {
297                 for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
298                         if (strcmp(buf, keywords[i].word) == 0)
299                                 return keywords[i].token;
300                 }
301         }
302         if ((str = strdup(buf)) == NULL)
303                 err(1, "strdup");
304         yylval.str = str;
305         return TSTRING;
306 }