1 %{
2 /*
3  * Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
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  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
17  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18  *
19  * Sponsored in part by the Defense Advanced Research Projects
20  * Agency (DARPA) and Air Force Research Laboratory, Air Force
21  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22  */
23 
24 /*
25  * XXX - the whole opFOO naming thing is somewhat bogus.
26  *
27  * XXX - the way things are stored for printmatches is stupid,
28  *       they should be stored as elements in an array and then
29  *       list_matches() can format things the way it wants.
30  */
31 
32 #include "config.h"
33 
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <stdio.h>
37 #ifdef STDC_HEADERS
38 # include <stdlib.h>
39 # include <stddef.h>
40 #else
41 # ifdef HAVE_STDLIB_H
42 #  include <stdlib.h>
43 # endif
44 #endif /* STDC_HEADERS */
45 #ifdef HAVE_STRING_H
46 # include <string.h>
47 #else
48 # ifdef HAVE_STRINGS_H
49 #  include <strings.h>
50 # endif
51 #endif /* HAVE_STRING_H */
52 #ifdef HAVE_UNISTD_H
53 # include <unistd.h>
54 #endif /* HAVE_UNISTD_H */
55 #include <pwd.h>
56 #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
57 # include <malloc.h>
58 #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
59 #if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__)
60 # include <alloca.h>
61 #endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */
62 #ifdef HAVE_LSEARCH
63 # include <search.h>
64 #endif /* HAVE_LSEARCH */
65 
66 #include "sudo.h"
67 #include "parse.h"
68 
69 #ifndef HAVE_LSEARCH
70 #include "emul/search.h"
71 #endif /* HAVE_LSEARCH */
72 
73 #ifndef lint
74 static const char rcsid[] = "$Sudo: parse.yacc,v 1.204 2004/08/11 18:29:10 millert Exp $";
75 #endif /* lint */
76 
77 /*
78  * Globals
79  */
80 extern int sudolineno, parse_error;
81 int errorlineno = -1;
82 int clearaliases = TRUE;
83 int printmatches = FALSE;
84 int pedantic = FALSE;
85 int keepall = FALSE;
86 int quiet = FALSE;
87 int used_runas = FALSE;
88 
89 /*
90  * Alias types
91  */
92 #define HOST_ALIAS		 1
93 #define CMND_ALIAS		 2
94 #define USER_ALIAS		 3
95 #define RUNAS_ALIAS		 4
96 
97 #define SETMATCH(_var, _val)	do { \
98 	if ((_var) == UNSPEC || (_val) != NOMATCH) \
99 	    (_var) = (_val); \
100 } while (0)
101 
102 #define SETNMATCH(_var, _val)	do { \
103 	if ((_val) != NOMATCH) \
104 	    (_var) = ! (_val); \
105 	else if ((_var) == UNSPEC) \
106 	    (_var) = NOMATCH; \
107 } while (0)
108 
109 /*
110  * The matching stack, initial space allocated in init_parser().
111  */
112 struct matchstack *match;
113 int top = 0, stacksize = 0;
114 
115 #define push \
116     do { \
117 	if (top >= stacksize) { \
118 	    while ((stacksize += STACKINCREMENT) < top); \
119 	    match = (struct matchstack *) erealloc3(match, stacksize, sizeof(struct matchstack)); \
120 	} \
121 	match[top].user   = UNSPEC; \
122 	match[top].cmnd   = UNSPEC; \
123 	match[top].host   = UNSPEC; \
124 	match[top].runas  = UNSPEC; \
125 	match[top].nopass = def_authenticate ? UNSPEC : TRUE; \
126 	match[top].noexec = def_noexec ? TRUE : UNSPEC; \
127 	top++; \
128     } while (0)
129 
130 #define pushcp \
131     do { \
132 	if (top >= stacksize) { \
133 	    while ((stacksize += STACKINCREMENT) < top); \
134 	    match = (struct matchstack *) erealloc3(match, stacksize, sizeof(struct matchstack)); \
135 	} \
136 	match[top].user   = match[top-1].user; \
137 	match[top].cmnd   = match[top-1].cmnd; \
138 	match[top].host   = match[top-1].host; \
139 	match[top].runas  = match[top-1].runas; \
140 	match[top].nopass = match[top-1].nopass; \
141 	match[top].noexec = match[top-1].noexec; \
142 	top++; \
143     } while (0)
144 
145 #define pop \
146     do { \
147 	if (top == 0) \
148 	    yyerror("matching stack underflow"); \
149 	else \
150 	    top--; \
151     } while (0)
152 
153 
154 /*
155  * For testing if foo_matches variable was set to TRUE or FALSE
156  */
157 #define	MATCHED(_v)	((_v) >= 0)
158 
159 /*
160  * Shortcuts for append()
161  */
162 #define append_cmnd(s, p) append(s, &cm_list[cm_list_len].cmnd, \
163 	&cm_list[cm_list_len].cmnd_len, &cm_list[cm_list_len].cmnd_size, p)
164 
165 #define append_runas(s, p) append(s, &cm_list[cm_list_len].runas, \
166 	&cm_list[cm_list_len].runas_len, &cm_list[cm_list_len].runas_size, p)
167 
168 #define append_entries(s, p) append(s, &ga_list[ga_list_len-1].entries, \
169 	&ga_list[ga_list_len-1].entries_len, \
170 	&ga_list[ga_list_len-1].entries_size, p)
171 
172 /*
173  * The stack for printmatches.  A list of allowed commands for the user.
174  */
175 static struct command_match *cm_list = NULL;
176 static size_t cm_list_len = 0, cm_list_size = 0;
177 
178 /*
179  * List of Cmnd_Aliases and expansions for `sudo -l'
180  */
181 static int in_alias = FALSE;
182 static size_t ga_list_len = 0, ga_list_size = 0;
183 static struct generic_alias *ga_list = NULL;
184 
185 /*
186  * Does this Defaults list pertain to this user?
187  */
188 static int defaults_matches = FALSE;
189 
190 /*
191  * Local protoypes
192  */
193 static int  add_alias		__P((char *, int, int));
194 static void append		__P((char *, char **, size_t *, size_t *, char *));
195 static void expand_ga_list	__P((void));
196 static void expand_match_list	__P((void));
197 static aliasinfo *find_alias	__P((char *, int));
198 static int  more_aliases	__P((void));
199        void init_parser		__P((void));
200        void yyerror		__P((char *));
201 
202 void
yyerror(s)203 yyerror(s)
204     char *s;
205 {
206     /* Save the line the first error occurred on. */
207     if (errorlineno == -1)
208 	errorlineno = sudolineno ? sudolineno - 1 : 0;
209     if (s && !quiet) {
210 #ifndef TRACELEXER
211 	(void) fprintf(stderr, ">>> sudoers file: %s, line %d <<<\n", s,
212 	    sudolineno ? sudolineno - 1 : 0);
213 #else
214 	(void) fprintf(stderr, "<*> ");
215 #endif
216     }
217     parse_error = TRUE;
218 }
219 %}
220 
221 %union {
222     char *string;
223     int BOOLEAN;
224     struct sudo_command command;
225     int tok;
226 }
227 
228 %start file				/* special start symbol */
229 %token <command> COMMAND		/* absolute pathname w/ optional args */
230 %token <string>  ALIAS			/* an UPPERCASE alias name */
231 %token <string>	 DEFVAR			/* a Defaults variable name */
232 %token <string>  NTWKADDR		/* w.x.y.z */
233 %token <string>  NETGROUP		/* a netgroup (+NAME) */
234 %token <string>  USERGROUP		/* a usergroup (%NAME) */
235 %token <string>  WORD			/* a word */
236 %token <tok>	 DEFAULTS		/* Defaults entry */
237 %token <tok>	 DEFAULTS_HOST		/* Host-specific defaults entry */
238 %token <tok>	 DEFAULTS_USER		/* User-specific defaults entry */
239 %token <tok>	 DEFAULTS_RUNAS		/* Runas-specific defaults entry */
240 %token <tok> 	 RUNAS			/* ( runas_list ) */
241 %token <tok> 	 NOPASSWD		/* no passwd req for command */
242 %token <tok> 	 PASSWD			/* passwd req for command (default) */
243 %token <tok> 	 NOEXEC			/* preload dummy execve() for cmnd */
244 %token <tok> 	 EXEC			/* don't preload dummy execve() */
245 %token <tok>	 ALL			/* ALL keyword */
246 %token <tok>	 COMMENT		/* comment and/or carriage return */
247 %token <tok>	 HOSTALIAS		/* Host_Alias keyword */
248 %token <tok>	 CMNDALIAS		/* Cmnd_Alias keyword */
249 %token <tok>	 USERALIAS		/* User_Alias keyword */
250 %token <tok>	 RUNASALIAS		/* Runas_Alias keyword */
251 %token <tok>	 ':' '=' ',' '!' '+' '-' /* union member tokens */
252 %token <tok>	 ERROR
253 
254 /*
255  * NOTE: these are not true booleans as there are actually 4 possible values:
256  *        1) TRUE (positive match)
257  *        0) FALSE (negative match due to a '!' somewhere)
258  *       -1) NOMATCH (don't change the value of *_matches)
259  *       -2) UNSPEC (uninitialized value)
260  */
261 %type <BOOLEAN>	 cmnd
262 %type <BOOLEAN>	 host
263 %type <BOOLEAN>	 runasuser
264 %type <BOOLEAN>	 oprunasuser
265 %type <BOOLEAN>	 runaslist
266 %type <BOOLEAN>	 user
267 
268 %%
269 
270 file		:	entry
271 		|	file entry
272 		;
273 
274 entry		:	COMMENT
275 			    { ; }
276                 |       error COMMENT
277 			    { yyerrok; }
278 		|	{ push; } userlist privileges {
279 			    while (top && user_matches != TRUE)
280 				pop;
281 			}
282 		|	USERALIAS useraliases
283 			    { ; }
284 		|	HOSTALIAS hostaliases
285 			    { ; }
286 		|	CMNDALIAS cmndaliases
287 			    { ; }
288 		|	RUNASALIAS runasaliases
289 			    { ; }
290 		|	defaults_line
291 			    { ; }
292 		;
293 
294 defaults_line	:	defaults_type defaults_list
295 		;
296 
297 defaults_type	:	DEFAULTS {
298 			    defaults_matches = TRUE;
299 			}
300 		|	DEFAULTS_USER { push; } userlist {
301 			    defaults_matches = user_matches;
302 			    pop;
303 			}
304 		|	DEFAULTS_RUNAS { push; } runaslist {
305 			    defaults_matches = $3 == TRUE;
306 			    pop;
307 			}
308 		|	DEFAULTS_HOST { push; } hostlist {
309 			    defaults_matches = host_matches;
310 			    pop;
311 			}
312 		;
313 
314 defaults_list	:	defaults_entry
315 		|	defaults_entry ',' defaults_list
316 		;
317 
318 defaults_entry	:	DEFVAR {
319 			    if (defaults_matches == TRUE &&
320 				!set_default($1, NULL, TRUE)) {
321 				yyerror(NULL);
322 				YYERROR;
323 			    }
324 			    free($1);
325 			}
326 		|	'!' DEFVAR {
327 			    if (defaults_matches == TRUE &&
328 				!set_default($2, NULL, FALSE)) {
329 				yyerror(NULL);
330 				YYERROR;
331 			    }
332 			    free($2);
333 			}
334 		|	DEFVAR '=' WORD {
335 			    if (defaults_matches == TRUE &&
336 				!set_default($1, $3, TRUE)) {
337 				yyerror(NULL);
338 				YYERROR;
339 			    }
340 			    free($1);
341 			    free($3);
342 			}
343 		|	DEFVAR '+' WORD {
344 			    if (defaults_matches == TRUE &&
345 				!set_default($1, $3, '+')) {
346 				yyerror(NULL);
347 				YYERROR;
348 			    }
349 			    free($1);
350 			    free($3);
351 			}
352 		|	DEFVAR '-' WORD {
353 			    if (defaults_matches == TRUE &&
354 				!set_default($1, $3, '-')) {
355 				yyerror(NULL);
356 				YYERROR;
357 			    }
358 			    free($1);
359 			    free($3);
360 			}
361 		;
362 
363 privileges	:	privilege
364 		|	privileges ':' privilege
365 		;
366 
367 privilege	:	hostlist '=' cmndspeclist {
368 			    /*
369 			     * We already did a push if necessary in
370 			     * cmndspec so just reset some values so
371 			     * the next 'privilege' gets a clean slate.
372 			     */
373 			    host_matches = UNSPEC;
374 			    runas_matches = UNSPEC;
375 			    no_passwd = def_authenticate ? UNSPEC : TRUE;
376 			    no_execve = def_noexec ? TRUE : UNSPEC;
377 			}
378 		;
379 
380 ophost		:	host {
381 			    SETMATCH(host_matches, $1);
382 			}
383 		|	'!' host {
384 			    SETNMATCH(host_matches, $2);
385 			}
386 		;
387 
388 host		:	ALL {
389 			    $$ = TRUE;
390 			}
391 		|	NTWKADDR {
392 			    if (addr_matches($1))
393 				$$ = TRUE;
394 			    else
395 				$$ = NOMATCH;
396 			    free($1);
397 			}
398 		|	NETGROUP {
399 			    if (netgr_matches($1, user_host, user_shost, NULL))
400 				$$ = TRUE;
401 			    else
402 				$$ = NOMATCH;
403 			    free($1);
404 			}
405 		|	WORD {
406 			    if (hostname_matches(user_shost, user_host, $1) == 0)
407 				$$ = TRUE;
408 			    else
409 				$$ = NOMATCH;
410 			    free($1);
411 			}
412 		|	ALIAS {
413 			    aliasinfo *aip = find_alias($1, HOST_ALIAS);
414 
415 			    /* could be an all-caps hostname */
416 			    if (aip)
417 				$$ = aip->val;
418 			    else if (strcasecmp(user_shost, $1) == 0)
419 				$$ = TRUE;
420 			    else {
421 				if (pedantic) {
422 				    (void) fprintf(stderr,
423 					"%s: undeclared Host_Alias `%s' referenced near line %d\n",
424 					(pedantic == 1) ? "Warning" : "Error", $1, sudolineno);
425 				    if (pedantic > 1) {
426 					yyerror(NULL);
427 					YYERROR;
428 				    }
429 				}
430 				$$ = NOMATCH;
431 			    }
432 			    free($1);
433 			}
434 		;
435 
436 cmndspeclist	:	cmndspec
437 		|	cmndspeclist ',' cmndspec
438 		;
439 
440 cmndspec	:	runasspec cmndtag opcmnd {
441 			    /*
442 			     * Push the entry onto the stack if it is worth
443 			     * saving and reset cmnd_matches for next cmnd.
444 			     *
445 			     * We need to save at least one entry on
446 			     * the stack so sudoers_lookup() can tell that
447 			     * the user was listed in sudoers.  Also, we
448 			     * need to be able to tell whether or not a
449 			     * user was listed for this specific host.
450 			     *
451 			     * If keepall is set and the user matches then
452 			     * we need to keep entries around too...
453 			     */
454 			    if (MATCHED(user_matches) &&
455 				MATCHED(host_matches) &&
456 				MATCHED(cmnd_matches) &&
457 				MATCHED(runas_matches))
458 				pushcp;
459 			    else if (MATCHED(user_matches) && (top == 1 ||
460 				(top == 2 && MATCHED(host_matches) &&
461 				!MATCHED(match[0].host))))
462 				pushcp;
463 			    else if (user_matches == TRUE && keepall)
464 				pushcp;
465 			    cmnd_matches = UNSPEC;
466 			}
467 		;
468 
469 opcmnd		:	cmnd {
470 			    SETMATCH(cmnd_matches, $1);
471 			}
472 		|	'!' {
473 			    if (printmatches == TRUE) {
474 				if (in_alias == TRUE)
475 				    append_entries("!", ", ");
476 				else if (host_matches == TRUE &&
477 				    user_matches == TRUE)
478 				    append_cmnd("!", NULL);
479 			    }
480 			} cmnd {
481 			    SETNMATCH(cmnd_matches, $3);
482 			}
483 		;
484 
485 runasspec	:	/* empty */ {
486 			    if (printmatches == TRUE && host_matches == TRUE &&
487 				user_matches == TRUE) {
488 				if (runas_matches == UNSPEC) {
489 				    cm_list[cm_list_len].runas_len = 0;
490 				} else {
491 				    /* Inherit runas data. */
492 				    cm_list[cm_list_len].runas =
493 					estrdup(cm_list[cm_list_len-1].runas);
494 				    cm_list[cm_list_len].runas_len =
495 					cm_list[cm_list_len-1].runas_len;
496 				    cm_list[cm_list_len].runas_size =
497 					cm_list[cm_list_len-1].runas_size;
498 				}
499 			    }
500 			    /*
501 			     * If this is the first entry in a command list
502 			     * then check against default runas user.
503 			     */
504 			    if (runas_matches == UNSPEC) {
505 				runas_matches =
506 				    userpw_matches(def_runas_default,
507 					*user_runas, runas_pw);
508 			    }
509 			}
510 		|	RUNAS runaslist {
511 			    runas_matches = $2;
512 			}
513 		;
514 
515 runaslist	:	oprunasuser { ; }
516 		|	runaslist ',' oprunasuser {
517 			    /* Later entries override earlier ones. */
518 			    if ($3 != NOMATCH)
519 				$$ = $3;
520 			    else
521 				$$ = $1;
522 			}
523 		;
524 
525 oprunasuser	:	runasuser { ; }
526 		|	'!' {
527 			    if (printmatches == TRUE) {
528 				if (in_alias == TRUE)
529 				    append_entries("!", ", ");
530 				else if (host_matches == TRUE &&
531 				    user_matches == TRUE)
532 				    append_runas("!", ", ");
533 			    }
534 			} runasuser {
535 			    /* Set $$ to the negation of runasuser */
536 			    $$ = ($3 == NOMATCH ? NOMATCH : ! $3);
537 			}
538 		;
539 
540 runasuser	:	WORD {
541 			    if (printmatches == TRUE) {
542 				if (in_alias == TRUE)
543 				    append_entries($1, ", ");
544 				else if (host_matches == TRUE &&
545 				    user_matches == TRUE)
546 				    append_runas($1, ", ");
547 			    }
548 			    if (userpw_matches($1, *user_runas, runas_pw))
549 				$$ = TRUE;
550 			    else
551 				$$ = NOMATCH;
552 			    free($1);
553 			    used_runas = TRUE;
554 			}
555 		|	USERGROUP {
556 			    if (printmatches == TRUE) {
557 				if (in_alias == TRUE)
558 				    append_entries($1, ", ");
559 				else if (host_matches == TRUE &&
560 				    user_matches == TRUE)
561 				    append_runas($1, ", ");
562 			    }
563 			    if (usergr_matches($1, *user_runas, runas_pw))
564 				$$ = TRUE;
565 			    else
566 				$$ = NOMATCH;
567 			    free($1);
568 			    used_runas = TRUE;
569 			}
570 		|	NETGROUP {
571 			    if (printmatches == TRUE) {
572 				if (in_alias == TRUE)
573 				    append_entries($1, ", ");
574 				else if (host_matches == TRUE &&
575 				    user_matches == TRUE)
576 				    append_runas($1, ", ");
577 			    }
578 			    if (netgr_matches($1, NULL, NULL, *user_runas))
579 				$$ = TRUE;
580 			    else
581 				$$ = NOMATCH;
582 			    free($1);
583 			    used_runas = TRUE;
584 			}
585 		|	ALIAS {
586 			    aliasinfo *aip = find_alias($1, RUNAS_ALIAS);
587 
588 			    if (printmatches == TRUE) {
589 				if (in_alias == TRUE)
590 				    append_entries($1, ", ");
591 				else if (host_matches == TRUE &&
592 				    user_matches == TRUE)
593 				    append_runas($1, ", ");
594 			    }
595 			    /* could be an all-caps username */
596 			    if (aip)
597 				$$ = aip->val;
598 			    else if (strcmp($1, *user_runas) == 0)
599 				$$ = TRUE;
600 			    else {
601 				if (pedantic) {
602 				    (void) fprintf(stderr,
603 					"%s: undeclared Runas_Alias `%s' referenced near line %d\n",
604 					(pedantic == 1) ? "Warning" : "Error", $1, sudolineno);
605 				    if (pedantic > 1) {
606 					yyerror(NULL);
607 					YYERROR;
608 				    }
609 				}
610 				$$ = NOMATCH;
611 			    }
612 			    free($1);
613 			    used_runas = TRUE;
614 			}
615 		|	ALL {
616 			    if (printmatches == TRUE) {
617 				if (in_alias == TRUE)
618 				    append_entries("ALL", ", ");
619 				else if (host_matches == TRUE &&
620 				    user_matches == TRUE)
621 				    append_runas("ALL", ", ");
622 			    }
623 			    $$ = TRUE;
624 			}
625 		;
626 
627 cmndtag		:	/* empty */ {
628 			    /* Inherit {NOPASSWD,PASSWD,NOEXEC,EXEC} status. */
629 			    if (printmatches == TRUE && host_matches == TRUE &&
630 				user_matches == TRUE) {
631 				if (no_passwd == TRUE)
632 				    cm_list[cm_list_len].nopasswd = TRUE;
633 				else
634 				    cm_list[cm_list_len].nopasswd = FALSE;
635 				if (no_execve == TRUE)
636 				    cm_list[cm_list_len].noexecve = TRUE;
637 				else
638 				    cm_list[cm_list_len].noexecve = FALSE;
639 			    }
640 			}
641 		|	cmndtag NOPASSWD {
642 			    no_passwd = TRUE;
643 			    if (printmatches == TRUE && host_matches == TRUE &&
644 				user_matches == TRUE)
645 				cm_list[cm_list_len].nopasswd = TRUE;
646 			}
647 		|	cmndtag PASSWD {
648 			    no_passwd = FALSE;
649 			    if (printmatches == TRUE && host_matches == TRUE &&
650 				user_matches == TRUE)
651 				cm_list[cm_list_len].nopasswd = FALSE;
652 			}
653 		|	cmndtag NOEXEC {
654 			    no_execve = TRUE;
655 			    if (printmatches == TRUE && host_matches == TRUE &&
656 				user_matches == TRUE)
657 				cm_list[cm_list_len].noexecve = TRUE;
658 			}
659 		|	cmndtag EXEC {
660 			    no_execve = FALSE;
661 			    if (printmatches == TRUE && host_matches == TRUE &&
662 				user_matches == TRUE)
663 				cm_list[cm_list_len].noexecve = FALSE;
664 			}
665 		;
666 
667 cmnd		:	ALL {
668 			    if (printmatches == TRUE) {
669 				if (in_alias == TRUE)
670 				    append_entries("ALL", ", ");
671 				else if (host_matches == TRUE &&
672 				    user_matches == TRUE) {
673 				    append_cmnd("ALL", NULL);
674 				    expand_match_list();
675 				}
676 			    }
677 
678 			    $$ = TRUE;
679 			}
680 		|	ALIAS {
681 			    aliasinfo *aip;
682 
683 			    if (printmatches == TRUE) {
684 				if (in_alias == TRUE)
685 				    append_entries($1, ", ");
686 				else if (host_matches == TRUE &&
687 				    user_matches == TRUE) {
688 				    append_cmnd($1, NULL);
689 				    expand_match_list();
690 				}
691 			    }
692 
693 			    if ((aip = find_alias($1, CMND_ALIAS)))
694 				$$ = aip->val;
695 			    else {
696 				if (pedantic) {
697 				    (void) fprintf(stderr,
698 					"%s: undeclared Cmnd_Alias `%s' referenced near line %d\n",
699 					(pedantic == 1) ? "Warning" : "Error", $1, sudolineno);
700 				    if (pedantic > 1) {
701 					yyerror(NULL);
702 					YYERROR;
703 				    }
704 				}
705 				$$ = NOMATCH;
706 			    }
707 			    free($1);
708 			}
709 		|	 COMMAND {
710 			    if (printmatches == TRUE) {
711 				if (in_alias == TRUE) {
712 				    append_entries($1.cmnd, ", ");
713 				    if ($1.args)
714 					append_entries($1.args, " ");
715 				}
716 				if (host_matches == TRUE &&
717 				    user_matches == TRUE)  {
718 				    append_cmnd($1.cmnd, NULL);
719 				    if ($1.args)
720 					append_cmnd($1.args, " ");
721 				    expand_match_list();
722 				}
723 			    }
724 
725 			    if (command_matches($1.cmnd, $1.args))
726 				$$ = TRUE;
727 			    else
728 				$$ = NOMATCH;
729 
730 			    free($1.cmnd);
731 			    if ($1.args)
732 				free($1.args);
733 			}
734 		;
735 
736 hostaliases	:	hostalias
737 		|	hostaliases ':' hostalias
738 		;
739 
740 hostalias	:	ALIAS { push; } '=' hostlist {
741 			    if ((MATCHED(host_matches) || pedantic) &&
742 				!add_alias($1, HOST_ALIAS, host_matches)) {
743 				yyerror(NULL);
744 				YYERROR;
745 			    }
746 			    pop;
747 			}
748 		;
749 
750 hostlist	:	ophost
751 		|	hostlist ',' ophost
752 		;
753 
754 cmndaliases	:	cmndalias
755 		|	cmndaliases ':' cmndalias
756 		;
757 
758 cmndalias	:	ALIAS {
759 			    push;
760 			    if (printmatches == TRUE) {
761 				in_alias = TRUE;
762 				/* Allocate space for ga_list if necessary. */
763 				expand_ga_list();
764 				ga_list[ga_list_len-1].type = CMND_ALIAS;
765 				ga_list[ga_list_len-1].alias = estrdup($1);
766 			     }
767 			} '=' cmndlist {
768 			    if ((MATCHED(cmnd_matches) || pedantic) &&
769 				!add_alias($1, CMND_ALIAS, cmnd_matches)) {
770 				yyerror(NULL);
771 				YYERROR;
772 			    }
773 			    pop;
774 			    free($1);
775 
776 			    if (printmatches == TRUE)
777 				in_alias = FALSE;
778 			}
779 		;
780 
781 cmndlist	:	opcmnd { ; }
782 		|	cmndlist ',' opcmnd
783 		;
784 
785 runasaliases	:	runasalias
786 		|	runasaliases ':' runasalias
787 		;
788 
789 runasalias	:	ALIAS {
790 			    if (printmatches == TRUE) {
791 				in_alias = TRUE;
792 				/* Allocate space for ga_list if necessary. */
793 				expand_ga_list();
794 				ga_list[ga_list_len-1].type = RUNAS_ALIAS;
795 				ga_list[ga_list_len-1].alias = estrdup($1);
796 			    }
797 			} '=' runaslist {
798 			    if (($4 != NOMATCH || pedantic) &&
799 				!add_alias($1, RUNAS_ALIAS, $4)) {
800 				yyerror(NULL);
801 				YYERROR;
802 			    }
803 			    free($1);
804 
805 			    if (printmatches == TRUE)
806 				in_alias = FALSE;
807 			}
808 		;
809 
810 useraliases	:	useralias
811 		|	useraliases ':' useralias
812 		;
813 
814 useralias	:	ALIAS { push; }	'=' userlist {
815 			    if ((MATCHED(user_matches) || pedantic) &&
816 				!add_alias($1, USER_ALIAS, user_matches)) {
817 				yyerror(NULL);
818 				YYERROR;
819 			    }
820 			    pop;
821 			    free($1);
822 			}
823 		;
824 
825 userlist	:	opuser
826 		|	userlist ',' opuser
827 		;
828 
829 opuser		:	user {
830 			    SETMATCH(user_matches, $1);
831 			}
832 		|	'!' user {
833 			    SETNMATCH(user_matches, $2);
834 			}
835 		;
836 
837 user		:	WORD {
838 			    if (userpw_matches($1, user_name, sudo_user.pw))
839 				$$ = TRUE;
840 			    else
841 				$$ = NOMATCH;
842 			    free($1);
843 			}
844 		|	USERGROUP {
845 			    if (usergr_matches($1, user_name, sudo_user.pw))
846 				$$ = TRUE;
847 			    else
848 				$$ = NOMATCH;
849 			    free($1);
850 			}
851 		|	NETGROUP {
852 			    if (netgr_matches($1, NULL, NULL, user_name))
853 				$$ = TRUE;
854 			    else
855 				$$ = NOMATCH;
856 			    free($1);
857 			}
858 		|	ALIAS {
859 			    aliasinfo *aip = find_alias($1, USER_ALIAS);
860 
861 			    /* could be an all-caps username */
862 			    if (aip)
863 				$$ = aip->val;
864 			    else if (strcmp($1, user_name) == 0)
865 				$$ = TRUE;
866 			    else {
867 				if (pedantic) {
868 				    (void) fprintf(stderr,
869 					"%s: undeclared User_Alias `%s' referenced near line %d\n",
870 					(pedantic == 1) ? "Warning" : "Error", $1, sudolineno);
871 				    if (pedantic > 1) {
872 					yyerror(NULL);
873 					YYERROR;
874 				    }
875 				}
876 				$$ = NOMATCH;
877 			    }
878 			    free($1);
879 			}
880 		|	ALL {
881 			    $$ = TRUE;
882 			}
883 		;
884 
885 %%
886 
887 #define MOREALIASES (32)
888 aliasinfo *aliases = NULL;
889 size_t naliases = 0;
890 size_t nslots = 0;
891 
892 
893 /*
894  * Compare two aliasinfo structures, strcmp() style.
895  * Note that we do *not* compare their values.
896  */
897 static int
aliascmp(a1,a2)898 aliascmp(a1, a2)
899     const VOID *a1, *a2;
900 {
901     int r;
902     aliasinfo *ai1, *ai2;
903 
904     ai1 = (aliasinfo *) a1;
905     ai2 = (aliasinfo *) a2;
906     if ((r = strcmp(ai1->name, ai2->name)) == 0)
907 	r = ai1->type - ai2->type;
908 
909     return(r);
910 }
911 
912 /*
913  * Compare two generic_alias structures, strcmp() style.
914  */
915 static int
genaliascmp(entry,key)916 genaliascmp(entry, key)
917     const VOID *entry, *key;
918 {
919     int r;
920     struct generic_alias *ga1, *ga2;
921 
922     ga1 = (struct generic_alias *) key;
923     ga2 = (struct generic_alias *) entry;
924     if ((r = strcmp(ga1->alias, ga2->alias)) == 0)
925 	r = ga1->type - ga2->type;
926 
927     return(r);
928 }
929 
930 
931 /*
932  * Adds the named alias of the specified type to the aliases list.
933  */
934 static int
add_alias(alias,type,val)935 add_alias(alias, type, val)
936     char *alias;
937     int type;
938     int val;
939 {
940     aliasinfo ai, *aip;
941     size_t onaliases;
942     char s[512];
943 
944     if (naliases >= nslots && !more_aliases()) {
945 	(void) snprintf(s, sizeof(s), "Out of memory defining alias `%s'",
946 			alias);
947 	yyerror(s);
948 	return(FALSE);
949     }
950 
951     ai.type = type;
952     ai.val = val;
953     ai.name = estrdup(alias);
954     onaliases = naliases;
955 
956     aip = (aliasinfo *) lsearch((VOID *)&ai, (VOID *)aliases, &naliases,
957 				sizeof(ai), aliascmp);
958     if (aip == NULL) {
959 	(void) snprintf(s, sizeof(s), "Aliases corrupted defining alias `%s'",
960 			alias);
961 	yyerror(s);
962 	return(FALSE);
963     }
964     if (onaliases == naliases) {
965 	(void) snprintf(s, sizeof(s), "Alias `%s' already defined", alias);
966 	yyerror(s);
967 	return(FALSE);
968     }
969 
970     return(TRUE);
971 }
972 
973 /*
974  * Searches for the named alias of the specified type.
975  */
976 static aliasinfo *
find_alias(alias,type)977 find_alias(alias, type)
978     char *alias;
979     int type;
980 {
981     aliasinfo ai;
982 
983     ai.name = alias;
984     ai.type = type;
985 
986     return((aliasinfo *) lfind((VOID *)&ai, (VOID *)aliases, &naliases,
987 		 sizeof(ai), aliascmp));
988 }
989 
990 /*
991  * Allocates more space for the aliases list.
992  */
993 static int
more_aliases()994 more_aliases()
995 {
996 
997     nslots += MOREALIASES;
998     if (nslots == MOREALIASES)
999 	aliases = (aliasinfo *) malloc(nslots * sizeof(aliasinfo));
1000     else
1001 	aliases = (aliasinfo *) realloc(aliases, nslots * sizeof(aliasinfo));
1002 
1003     return(aliases != NULL);
1004 }
1005 
1006 /*
1007  * Lists the contents of the aliases list.
1008  */
1009 void
dumpaliases()1010 dumpaliases()
1011 {
1012     size_t n;
1013 
1014     for (n = 0; n < naliases; n++) {
1015 	if (aliases[n].val == -1)
1016 	    continue;
1017 
1018 	switch (aliases[n].type) {
1019 	case HOST_ALIAS:
1020 	    (void) puts("HOST_ALIAS");
1021 	    break;
1022 
1023 	case CMND_ALIAS:
1024 	    (void) puts("CMND_ALIAS");
1025 	    break;
1026 
1027 	case USER_ALIAS:
1028 	    (void) puts("USER_ALIAS");
1029 	    break;
1030 
1031 	case RUNAS_ALIAS:
1032 	    (void) puts("RUNAS_ALIAS");
1033 	    break;
1034 	}
1035 	(void) printf("\t%s: %d\n", aliases[n].name, aliases[n].val);
1036     }
1037 }
1038 
1039 /*
1040  * Lists the contents of cm_list and ga_list for `sudo -l'.
1041  */
1042 void
list_matches()1043 list_matches()
1044 {
1045     size_t count;
1046     char *p;
1047     struct generic_alias *ga, key;
1048 
1049     (void) printf("User %s may run the following commands on this host:\n",
1050 	user_name);
1051     for (count = 0; count < cm_list_len; count++) {
1052 
1053 	/* Print the runas list. */
1054 	(void) fputs("    ", stdout);
1055 	if (cm_list[count].runas) {
1056 	    (void) putchar('(');
1057 	    p = strtok(cm_list[count].runas, ", ");
1058 	    do {
1059 		if (p != cm_list[count].runas)
1060 		    (void) fputs(", ", stdout);
1061 
1062 		key.alias = p;
1063 		key.type = RUNAS_ALIAS;
1064 		if ((ga = (struct generic_alias *) lfind((VOID *) &key,
1065 		    (VOID *) &ga_list[0], &ga_list_len, sizeof(key), genaliascmp)))
1066 		    (void) fputs(ga->entries, stdout);
1067 		else
1068 		    (void) fputs(p, stdout);
1069 	    } while ((p = strtok(NULL, ", ")));
1070 	    (void) fputs(") ", stdout);
1071 	} else {
1072 	    (void) printf("(%s) ", def_runas_default);
1073 	}
1074 
1075 	/* Is execve(2) disabled? */
1076 	if (cm_list[count].noexecve == TRUE && !def_noexec)
1077 	    (void) fputs("NOEXEC: ", stdout);
1078 	else if (cm_list[count].noexecve == FALSE && def_noexec)
1079 	    (void) fputs("EXEC: ", stdout);
1080 
1081 	/* Is a password required? */
1082 	if (cm_list[count].nopasswd == TRUE && def_authenticate)
1083 	    (void) fputs("NOPASSWD: ", stdout);
1084 	else if (cm_list[count].nopasswd == FALSE && !def_authenticate)
1085 	    (void) fputs("PASSWD: ", stdout);
1086 
1087 	/* Print the actual command or expanded Cmnd_Alias. */
1088 	key.alias = cm_list[count].cmnd;
1089 	key.type = CMND_ALIAS;
1090 	if ((ga = (struct generic_alias *) lfind((VOID *) &key,
1091 	    (VOID *) &ga_list[0], &ga_list_len, sizeof(key), genaliascmp)))
1092 	    (void) puts(ga->entries);
1093 	else
1094 	    (void) puts(cm_list[count].cmnd);
1095     }
1096 
1097     /* Be nice and free up space now that we are done. */
1098     for (count = 0; count < ga_list_len; count++) {
1099 	free(ga_list[count].alias);
1100 	free(ga_list[count].entries);
1101     }
1102     free(ga_list);
1103     ga_list = NULL;
1104 
1105     for (count = 0; count < cm_list_len; count++) {
1106 	free(cm_list[count].runas);
1107 	free(cm_list[count].cmnd);
1108     }
1109     free(cm_list);
1110     cm_list = NULL;
1111     cm_list_len = 0;
1112     cm_list_size = 0;
1113 }
1114 
1115 /*
1116  * Appends a source string to the destination, optionally prefixing a separator.
1117  */
1118 static void
append(src,dstp,dst_len,dst_size,separator)1119 append(src, dstp, dst_len, dst_size, separator)
1120     char *src, **dstp;
1121     size_t *dst_len, *dst_size;
1122     char *separator;
1123 {
1124     size_t src_len = strlen(src);
1125     char *dst = *dstp;
1126 
1127     /*
1128      * Only add the separator if there is something to separate from.
1129      * If the last char is a '!', don't apply the separator (XXX).
1130      */
1131     if (separator && dst && dst[*dst_len - 1] != '!')
1132 	src_len += strlen(separator);
1133     else
1134 	separator = NULL;
1135 
1136     /* Assumes dst will be NULL if not set. */
1137     if (dst == NULL) {
1138 	dst = (char *) emalloc(BUFSIZ);
1139 	*dst = '\0';
1140 	*dst_size = BUFSIZ;
1141 	*dst_len = 0;
1142 	*dstp = dst;
1143     }
1144 
1145     /* Allocate more space if necessary. */
1146     if (*dst_size <= *dst_len + src_len) {
1147 	while (*dst_size <= *dst_len + src_len)
1148 	    *dst_size += BUFSIZ;
1149 
1150 	dst = (char *) erealloc(dst, *dst_size);
1151 	*dstp = dst;
1152     }
1153 
1154     /* Copy src -> dst adding a separator if appropriate and adjust len. */
1155     if (separator)
1156 	(void) strlcat(dst, separator, *dst_size);
1157     (void) strlcat(dst, src, *dst_size);
1158     *dst_len += src_len;
1159 }
1160 
1161 /*
1162  * Frees up space used by the aliases list and resets the associated counters.
1163  */
1164 void
reset_aliases()1165 reset_aliases()
1166 {
1167     size_t n;
1168 
1169     if (aliases) {
1170 	for (n = 0; n < naliases; n++)
1171 	    free(aliases[n].name);
1172 	free(aliases);
1173 	aliases = NULL;
1174     }
1175     naliases = nslots = 0;
1176 }
1177 
1178 /*
1179  * Increments ga_list_len, allocating more space as necessary.
1180  */
1181 static void
expand_ga_list()1182 expand_ga_list()
1183 {
1184 
1185     if (++ga_list_len >= ga_list_size) {
1186 	while ((ga_list_size += STACKINCREMENT) < ga_list_len)
1187 	    ;
1188 	ga_list = (struct generic_alias *)
1189 	    erealloc3(ga_list, ga_list_size, sizeof(struct generic_alias));
1190     }
1191 
1192     ga_list[ga_list_len - 1].entries = NULL;
1193 }
1194 
1195 /*
1196  * Increments cm_list_len, allocating more space as necessary.
1197  */
1198 static void
expand_match_list()1199 expand_match_list()
1200 {
1201 
1202     if (++cm_list_len >= cm_list_size) {
1203 	while ((cm_list_size += STACKINCREMENT) < cm_list_len)
1204 	    ;
1205 	if (cm_list == NULL)
1206 	    cm_list_len = 0;		/* start at 0 since it is a subscript */
1207 	cm_list = (struct command_match *)
1208 	    erealloc3(cm_list, cm_list_size, sizeof(struct command_match));
1209     }
1210 
1211     cm_list[cm_list_len].runas = cm_list[cm_list_len].cmnd = NULL;
1212     cm_list[cm_list_len].nopasswd = FALSE;
1213     cm_list[cm_list_len].noexecve = FALSE;
1214 }
1215 
1216 /*
1217  * Frees up spaced used by a previous parser run and allocates new space
1218  * for various data structures.
1219  */
1220 void
init_parser()1221 init_parser()
1222 {
1223 
1224     /* Free up old data structures if we run the parser more than once. */
1225     if (match) {
1226 	free(match);
1227 	match = NULL;
1228 	top = 0;
1229 	parse_error = FALSE;
1230 	used_runas = FALSE;
1231 	errorlineno = -1;
1232 	sudolineno = 1;
1233     }
1234 
1235     /* Allocate space for the matching stack. */
1236     stacksize = STACKINCREMENT;
1237     match = (struct matchstack *) emalloc2(stacksize, sizeof(struct matchstack));
1238 
1239     /* Allocate space for the match list (for `sudo -l'). */
1240     if (printmatches == TRUE)
1241 	expand_match_list();
1242 }
1243