1 /* $OpenBSD: parse.y,v 1.26 2017/01/02 01:40:20 tedu Exp $ */
3 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
4 * Copyright (c) 2021-2022 Sergey Sushilin <sergeysushilin@protonmail.com>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 static struct rule
*rules
= NULL
;
36 static size_t n_rules
= 0;
37 static size_t max_rules
= 0;
39 static u_int parse_errors
= 0;
41 static void yyerror(char const *fmt
, ...
) __nonnull
((1)) __format_printf
(1, 2);
42 #if !HAVE_LEX && !HAVE_FLEX
43 static int yylex(void);
45 extern
int yylex(void);
48 static void add_rule
(struct rule
const *rule
) __nonnull
((1));
50 struct passwd
*original_pw
= NULL
, *target_pw
= NULL
;
52 extern
char **environ
;
54 #define assign_option(_0, _1, _2, option, name, value) \
56 if
(_2.name
!= value
) { \
57 if
(_0.name
!= value
) \
58 yyerror(option
" option is already set"); \
66 %token TOK_PERMIT TOK_DENY TOK_FROM TOK_AS TOK_EXECUTE TOK_ELLIPSIS
67 %token TOK_INHERITENV TOK_KEEPENV TOK_SETENV TOK_UNSETENV
68 %token TOK_PERSIST TOK_NOPASS TOK_NOLOG
69 %token TOK_STRING TOK_NUMBER TOK_NAME
70 %token TOK_UNKNOWN
','
81 } | TOK_UNKNOWN
'\n' {
85 rule: TOK_PERMIT options ident target argv
{
89 .keepenvlist
= $2.keepenvlist
,
90 .setenvlist
= $2.setenvlist
,
91 .unsetenvlist
= $2.unsetenvlist
,
92 .persist_time
= $2.persist_time
,
93 .inheritenv
= $2.inheritenv
,
96 .ident.pw
= $3.ident.pw
,
97 .ident.gr
= $3.ident.gr
,
98 .target.pw
= $4.target.pw
,
104 } | TOK_DENY ident target argv
{
107 .ident.pw
= $2.ident.pw
,
108 .ident.gr
= $2.ident.gr
,
109 .target.pw
= $3.target.pw
,
113 warnx
("try to use 'permit' rather than 'deny',"
114 "since last is not secure enough");
120 $$
= (YYSTYPE){ .persist_time
= $1.persist_time
};
122 $$
= (YYSTYPE){ .nopass
= true
};
124 $$
= (YYSTYPE){ .nolog
= true
};
126 options: /* None. */ {
127 $$.env
= createenv
();
128 defaultenv
($$.env
, original_pw
, target_pw
);
129 $$.keepenvlist
= NULL
;
130 $$.setenvlist
= NULL
;
131 $$.unsetenvlist
= NULL
;
133 $$.inheritenv
= false
;
136 } | options TOK_KEEPENV
'{' strlist
'}' {
138 assign_option
($$
, $1, $2, "keepenv", keepenvlist
, NULL
);
139 if
(keepenv
($$.env
, $4.strlist
) != 0)
141 $$.keepenvlist
= $4.strlist
;
142 } | options TOK_SETENV
'{' strlist
'}' {
144 assign_option
($$
, $1, $2, "setenv", setenvlist
, NULL
);
145 if
(fillenv
($$.env
, $4.strlist
) != 0)
147 $$.setenvlist
= $4.strlist
;
148 } | options TOK_UNSETENV
'{' strlist
'}' {
150 assign_option
($$
, $1, $2, "unsetenv", unsetenvlist
, NULL
);
151 if
(unfillenv
($$.env
, $4.strlist
) != 0)
153 $$.unsetenvlist
= $4.strlist
;
154 } | options TOK_INHERITENV
{
156 assign_option
($$
, $1, $2, "inheritenv", inheritenv
, false
);
158 $$.inheritenv
= true
;
162 assign_option
($$
, $1, $2, "persist", persist_time
, 0);
163 assign_option
($$
, $1, $2, "nopass", nopass
, false
);
164 assign_option
($$
, $1, $2, "nolog", nolog
, false
);
166 if
($$.persist_time
!= 0 && $$.nopass
)
167 yyerror("can not combine persist and nopass");
170 persist: TOK_PERSIST
{
171 $$.persist_time
= 5 * 60;
172 } | TOK_PERSIST
'(' TOK_NUMBER
')' {
174 enum { time_type_is_signed
= ((time_t)-1 < (time_t)0) };
175 enum { time_type_max
= time_type_is_signed ?
(time_t)((size_t)(time_t)-1 >> 1) : (time_t)-1 };
177 if
(time_type_is_signed
)
178 $$.persist_time
= safe_strtonum
($3.str.buf
, 0, time_type_max
, &succeed
);
180 $$.persist_time
= safe_strtounum
($3.str.buf
, time_type_max
, &succeed
);
183 yyerror("too big persist time");
185 if
($$.persist_time
== 0)
186 yyerror("persist time must be non-zero");
191 $$.pw
= safe_getpwnam
($1.str.buf
);
199 uid_t uid
= safe_strtounum
($1.str.buf
, UID_MAX
, &succeed
);
201 if
(!succeed ||
($$.pw
= safe_getpwuid
(uid
)) == NULL
)
207 $$.gr
= safe_getgrnam
($1.str.buf
);
215 gid_t gid
= safe_strtounum
($1.str.buf
, GID_MAX
, &succeed
);
217 if
(!succeed ||
($$.gr
= safe_getgrgid
(gid
)) == NULL
)
228 } | user TOK_FROM group
{
232 target: TOK_AS user
{
233 $$.target.pw
= $2.pw
;
240 execute: /* Optional. */ {
243 } | TOK_EXECUTE
'{' strarray
'}' {
244 $$.argc
= $3.listcount
;
245 $$.argv
= (char const *const *const *)$3.strarray
;
246 } | TOK_EXECUTE
'{' TOK_ELLIPSIS
'}' {
249 } | TOK_EXECUTE
'{' strarray TOK_ELLIPSIS
'}' {
250 $$.argc
= $3.listcount
+ 1;
251 $$.argv
= (char const *const *const *)$3.strarray
;
254 strlist: TOK_STRING
{
255 $$.strlist
= xmalloc
(2 * sizeof
(char *));
257 $$.strlist
[0] = $1.str.buf
;
258 $$.strlist
[1] = NULL
;
259 } | strlist
',' TOK_STRING
{
260 $$.strlist
= xreallocarray
($1.strlist
, $$.strcount
+ 2, sizeof
(char *));
261 $$.strlist
[$$.strcount
++] = $3.str.buf
;
262 $$.strlist
[$$.strcount
] = NULL
;
268 $$.strarray
= xmalloc
(($1.strcount
+ 1) * sizeof
(char **));
269 $$.listcount
= $1.strcount
;
271 for
(i
= 0; i
< $$.strcount
; i
++) {
272 $$.strarray
[i
] = xmalloc
(2 * sizeof
(char *));
273 $$.strarray
[i
][0] = $1.strlist
[i
];
274 $$.strarray
[i
][1] = NULL
;
277 $$.strarray
[i
] = NULL
;
279 } |
'[' strlist
']' {
280 $$.strarray
= xcalloc
(2, sizeof
(char **));
282 $$.strarray
[0] = $2.strlist
;
283 $$.strarray
[1] = NULL
;
284 } | strarray
',' strarray
{
287 $$.strarray
= xreallocarray
($1.strarray
, $1.listcount
+ $3.listcount
+ 1, sizeof
(char **));
288 $$.listcount
= $1.listcount
+ $3.listcount
;
290 for
(i
= $1.listcount
, j
= 0; i
< $$.listcount
; i
++, j
++)
291 $$.strarray
[i
] = $3.strarray
[j
];
293 $$.strarray
[i
] = NULL
;
295 } /*| strarray ',' strlist {
298 $$.strarray = xreallocarray($1.strarray, $1.listcount + $3.strcount + 1, sizeof(char **));
299 $$.listcount = $1.listcount + $3.listcount;
301 for (i = $1.listcount, j = 0; i < $$.listcount; i++, j++) {
302 $$.strarray[i] = xmalloc(2 * sizeof(char *));
303 $$.strarray[i][0] = $1.strlist[i];
304 $$.strarray[i][1] = NULL;
307 $$.strarray[i] = NULL;
309 } | strarray ',' '[' strlist ']' {
310 $$.strarray = xreallocarray($1.strarray, $1.listcount + 1 + 1, sizeof(char **));
311 $$.listcount = $1.listcount + $3.listcount;
312 $$.strarray[$1.listcount++] = $4.strlist;
313 $$.strarray[$1.listcount] = NULL;
319 static void yyerror(char const *fmt
, ...
)
323 xfprintf
(stderr
, "%s: ", getprogname
());
326 xvfprintf
(stderr
, fmt
, va
);
329 xfprintf
(stderr
, " at %u:%u\n", yylval.lineno
+ 1, yylval.colno
+ 1);
334 void check_permissions
(char const *filename
)
338 if
(stat
(filename
, &sb
) != 0) {
340 err
(EXIT_FAILURE
, "doas is not enabled, %s required", filename
);
342 err
(EXIT_FAILURE
, "stat(\"%s\")", filename
);
345 if
(sb.st_mode
& (S_IWGRP | S_IWOTH
))
346 errx
(EXIT_FAILURE
, "%s is writable by group or other", filename
);
348 if
(sb.st_uid
!= ROOT_UID || sb.st_gid
!= ROOT_UID
)
349 errx
(EXIT_FAILURE
, "%s is not owned by root", filename
);
352 u_int parse_config
(char const *filename
)
355 memset
(&yylval, '\0', sizeof
(yylval));
357 yyin
= fopen
(filename
, "r");
360 err
(EXIT_FAILURE
, "could not open config file %s", filename
);
367 #define free_and_nullify(p) \
371 #define free_vector_and_nullify(v) \
376 for
(i
= 0; v
[i
] != NULL
; i
++) \
383 void free_rules
(void)
387 for
(i
= 0; i
< n_rules
; i
++) {
389 struct rule rule
= rules
[i
];
391 free_and_nullify
(rule.ident.pw
);
392 free_and_nullify
(rule.ident.gr
);
393 free_and_nullify
(rule.target.pw
);
395 if
(rule.argv
!= NULL
) {
396 for
(j
= 0; j
< rule.argc
; j
++)
397 free_vector_and_nullify
(rule.argv
[j
]);
399 free_and_nullify
(rule.argv
);
402 free_vector_and_nullify
(rule.keepenvlist
);
403 free_vector_and_nullify
(rule.setenvlist
);
404 free_vector_and_nullify
(rule.unsetenvlist
);
407 free_and_nullify
(rules
);
408 max_rules
= n_rules
= 0;
411 static void add_rule
(struct rule
const *r
)
413 if
(n_rules
== max_rules
) {
414 max_rules
= (max_rules
== 0 ?
8 : max_rules
* 2);
415 rules
= xreallocarray
(rules
, max_rules
, sizeof
(*rules
));
418 memcpy
(&rules
[n_rules
++], r
, sizeof
(struct rule
));
421 const struct rule
*get_rules
(void)
426 size_t get_n_rules
(void)
431 #if !HAVE_LEX && !HAVE_FLEX
434 char const *const word
;
443 static struct keyword
const keywords
[] = {
444 # include "token-table.c"
448 * gnulib efa15594e17fc20827dba66414fb391e99905394
450 * CMP(a, b) performs a three-valued comparison on a vs. b.
455 * The code (a > b) - (a < b) from Hacker's Delight para 2-9
456 * avoids conditional jumps in all GCC versions >= 3.4.
458 # define CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2)))
462 * +1 if strcmp(p1, p2) > 0
463 * 0 if strcmp(p1, p2) == 0
464 * -1 if strcmp(p1, p2) < 0
466 static inline __nonnull
((1, 2)) __const
int fastmemcmp
(void const *p1
, void const *p2
, size_t n
)
468 u_char
const *s1
= (u_char
const *)p1
;
469 u_char
const *s2
= (u_char
const *)p2
;
479 comparison
= CMP
(c1
, c2
);
488 static inline
int get_token_by_word
(char const *word
, size_t length
)
491 size_t r
= countof
(keywords
);
494 size_t m
= l
+ (r
- l
) / 2;
495 int d
= CMP
(length
, keywords
[m
].length
);
497 switch
(d
!= 0 ? d
: fastmemcmp
(word
, keywords
[m
].word
, length
)) {
502 return keywords
[m
].token
;
509 yyerror("unknown keyword: %s", word
);
513 # include "get-keyword-by-word.c"
515 static inline
int get_token_by_word
(char const *word
, size_t length
)
517 struct keyword
const *kw
= get_keyword_by_word
(word
, length
);
522 yyerror("unknown keyword: %s", word
);
531 static int yylex(void)
533 char buf
[256], *p
= buf
;
535 bool quotes
= false
, escape
= false
;
537 enum { T_KEYWORD
= 1, T_STRING
, T_NUMBER
, T_NAME
} type
= 0;
540 /* Skip whitespaces first. */
541 while
(c
= getc
(yyin
), isblank
((unsigned char)c
) && c
!= EOF
)
544 /* Check for special one-character constructions. */
547 /* Skip comments. NUL is allowed. No continuation. */
548 while
((c
= getc
(yyin
)) != '\n')
569 else if
(isdigit
((unsigned char)c
))
571 else if
(islower
((unsigned char)c
) || c
== '.')
576 yyerror("unknown expression, expected string, number or keyword");
580 /* Parsing next word. */
581 for
(;; c
= getc
(yyin
), yylval.colno
++) {
584 yyerror("unallowed character NUL (ignored)");
596 yyerror("unterminated quotes in column %u",
609 yyerror("unterminated escape");
612 yyerror("unterminated quotes in column %u",
626 if
(!escape
&& !quotes
)
636 qpos
= yylval.colno
+ 1;
648 if
(p
== endof
(buf
)) {
649 yyerror("too long line");
658 ungetc
(c
== EOF ?
'\n' : c
, yyin
);
662 * There could be a number of reasons for empty buffer,
663 * and we handle all of them here, to avoid cluttering
668 } else if
(qpos
== 0) {
670 * Accept, e.g., empty arguments:
679 return get_token_by_word
(buf
, p
- buf
);
681 yylval.str.buf
= xstrdup
(buf
);
684 yylval.str.buf
= xstrdup
(buf
);
687 yylval.str.buf
= xstrdup
(buf
);
691 yyerror("unknown token: %s", buf
);
695 if
(ferror
(yyin
) != 0)
696 yyerror("input error reading config");