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