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