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