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