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