Imported Debian patch 1.6.8p9-2
[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                 |       ALIAS {
681                             aliasinfo *aip;
682
683                             if (printmatches == TRUE) {
684                                 if (in_alias == TRUE)
685                                     append_entries($1, ", ");
686                                 else if (host_matches == TRUE &&
687                                     user_matches == TRUE) {
688                                     append_cmnd($1, NULL);
689                                     expand_match_list();
690                                 }
691                             }
692
693                             if ((aip = find_alias($1, CMND_ALIAS)))
694                                 $$ = aip->val;
695                             else {
696                                 if (pedantic) {
697                                     (void) fprintf(stderr,
698                                         "%s: undeclared Cmnd_Alias `%s' referenced near line %d\n",
699                                         (pedantic == 1) ? "Warning" : "Error", $1, sudolineno);
700                                     if (pedantic > 1) {
701                                         yyerror(NULL);
702                                         YYERROR;
703                                     }
704                                 }
705                                 $$ = NOMATCH;
706                             }
707                             free($1);
708                         }
709                 |        COMMAND {
710                             if (printmatches == TRUE) {
711                                 if (in_alias == TRUE) {
712                                     append_entries($1.cmnd, ", ");
713                                     if ($1.args)
714                                         append_entries($1.args, " ");
715                                 }
716                                 if (host_matches == TRUE &&
717                                     user_matches == TRUE)  {
718                                     append_cmnd($1.cmnd, NULL);
719                                     if ($1.args)
720                                         append_cmnd($1.args, " ");
721                                     expand_match_list();
722                                 }
723                             }
724
725                             if (command_matches($1.cmnd, $1.args))
726                                 $$ = TRUE;
727                             else
728                                 $$ = NOMATCH;
729
730                             free($1.cmnd);
731                             if ($1.args)
732                                 free($1.args);
733                         }
734                 ;
735
736 hostaliases     :       hostalias
737                 |       hostaliases ':' hostalias
738                 ;
739
740 hostalias       :       ALIAS { push; } '=' hostlist {
741                             if ((MATCHED(host_matches) || pedantic) &&
742                                 !add_alias($1, HOST_ALIAS, host_matches)) {
743                                 yyerror(NULL);
744                                 YYERROR;
745                             }
746                             pop;
747                         }
748                 ;
749
750 hostlist        :       ophost
751                 |       hostlist ',' ophost
752                 ;
753
754 cmndaliases     :       cmndalias
755                 |       cmndaliases ':' cmndalias
756                 ;
757
758 cmndalias       :       ALIAS {
759                             push;
760                             if (printmatches == TRUE) {
761                                 in_alias = TRUE;
762                                 /* Allocate space for ga_list if necessary. */
763                                 expand_ga_list();
764                                 ga_list[ga_list_len-1].type = CMND_ALIAS;
765                                 ga_list[ga_list_len-1].alias = estrdup($1);
766                              }
767                         } '=' cmndlist {
768                             if ((MATCHED(cmnd_matches) || pedantic) &&
769                                 !add_alias($1, CMND_ALIAS, cmnd_matches)) {
770                                 yyerror(NULL);
771                                 YYERROR;
772                             }
773                             pop;
774                             free($1);
775
776                             if (printmatches == TRUE)
777                                 in_alias = FALSE;
778                         }
779                 ;
780
781 cmndlist        :       opcmnd { ; }
782                 |       cmndlist ',' opcmnd
783                 ;
784
785 runasaliases    :       runasalias
786                 |       runasaliases ':' runasalias
787                 ;
788
789 runasalias      :       ALIAS {
790                             if (printmatches == TRUE) {
791                                 in_alias = TRUE;
792                                 /* Allocate space for ga_list if necessary. */
793                                 expand_ga_list();
794                                 ga_list[ga_list_len-1].type = RUNAS_ALIAS;
795                                 ga_list[ga_list_len-1].alias = estrdup($1);
796                             }
797                         } '=' runaslist {
798                             if (($4 != NOMATCH || pedantic) &&
799                                 !add_alias($1, RUNAS_ALIAS, $4)) {
800                                 yyerror(NULL);
801                                 YYERROR;
802                             }
803                             free($1);
804
805                             if (printmatches == TRUE)
806                                 in_alias = FALSE;
807                         }
808                 ;
809
810 useraliases     :       useralias
811                 |       useraliases ':' useralias
812                 ;
813
814 useralias       :       ALIAS { push; } '=' userlist {
815                             if ((MATCHED(user_matches) || pedantic) &&
816                                 !add_alias($1, USER_ALIAS, user_matches)) {
817                                 yyerror(NULL);
818                                 YYERROR;
819                             }
820                             pop;
821                             free($1);
822                         }
823                 ;
824
825 userlist        :       opuser
826                 |       userlist ',' opuser
827                 ;
828
829 opuser          :       user {
830                             SETMATCH(user_matches, $1);
831                         }
832                 |       '!' user {
833                             SETNMATCH(user_matches, $2);
834                         }
835                 ;
836
837 user            :       WORD {
838                             if (userpw_matches($1, user_name, sudo_user.pw))
839                                 $$ = TRUE;
840                             else
841                                 $$ = NOMATCH;
842                             free($1);
843                         }
844                 |       USERGROUP {
845                             if (usergr_matches($1, user_name, sudo_user.pw))
846                                 $$ = TRUE;
847                             else
848                                 $$ = NOMATCH;
849                             free($1);
850                         }
851                 |       NETGROUP {
852                             if (netgr_matches($1, NULL, NULL, user_name))
853                                 $$ = TRUE;
854                             else
855                                 $$ = NOMATCH;
856                             free($1);
857                         }
858                 |       ALIAS {
859                             aliasinfo *aip = find_alias($1, USER_ALIAS);
860
861                             /* could be an all-caps username */
862                             if (aip)
863                                 $$ = aip->val;
864                             else if (strcmp($1, user_name) == 0)
865                                 $$ = TRUE;
866                             else {
867                                 if (pedantic) {
868                                     (void) fprintf(stderr,
869                                         "%s: undeclared User_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                             free($1);
879                         }
880                 |       ALL {
881                             $$ = TRUE;
882                         }
883                 ;
884
885 %%
886
887 #define MOREALIASES (32)
888 aliasinfo *aliases = NULL;
889 size_t naliases = 0;
890 size_t nslots = 0;
891
892
893 /*
894  * Compare two aliasinfo structures, strcmp() style.
895  * Note that we do *not* compare their values.
896  */
897 static int
898 aliascmp(a1, a2)
899     const VOID *a1, *a2;
900 {
901     int r;
902     aliasinfo *ai1, *ai2;
903
904     ai1 = (aliasinfo *) a1;
905     ai2 = (aliasinfo *) a2;
906     if ((r = strcmp(ai1->name, ai2->name)) == 0)
907         r = ai1->type - ai2->type;
908
909     return(r);
910 }
911
912 /*
913  * Compare two generic_alias structures, strcmp() style.
914  */
915 static int
916 genaliascmp(entry, key)
917     const VOID *entry, *key;
918 {
919     int r;
920     struct generic_alias *ga1, *ga2;
921
922     ga1 = (struct generic_alias *) key;
923     ga2 = (struct generic_alias *) entry;
924     if ((r = strcmp(ga1->alias, ga2->alias)) == 0)
925         r = ga1->type - ga2->type;
926
927     return(r);
928 }
929
930
931 /*
932  * Adds the named alias of the specified type to the aliases list.
933  */
934 static int
935 add_alias(alias, type, val)
936     char *alias;
937     int type;
938     int val;
939 {
940     aliasinfo ai, *aip;
941     size_t onaliases;
942     char s[512];
943
944     if (naliases >= nslots && !more_aliases()) {
945         (void) snprintf(s, sizeof(s), "Out of memory defining alias `%s'",
946                         alias);
947         yyerror(s);
948         return(FALSE);
949     }
950
951     ai.type = type;
952     ai.val = val;
953     ai.name = estrdup(alias);
954     onaliases = naliases;
955
956     aip = (aliasinfo *) lsearch((VOID *)&ai, (VOID *)aliases, &naliases,
957                                 sizeof(ai), aliascmp);
958     if (aip == NULL) {
959         (void) snprintf(s, sizeof(s), "Aliases corrupted defining alias `%s'",
960                         alias);
961         yyerror(s);
962         return(FALSE);
963     }
964     if (onaliases == naliases) {
965         (void) snprintf(s, sizeof(s), "Alias `%s' already defined", alias);
966         yyerror(s);
967         return(FALSE);
968     }
969
970     return(TRUE);
971 }
972
973 /*
974  * Searches for the named alias of the specified type.
975  */
976 static aliasinfo *
977 find_alias(alias, type)
978     char *alias;
979     int type;
980 {
981     aliasinfo ai;
982
983     ai.name = alias;
984     ai.type = type;
985
986     return((aliasinfo *) lfind((VOID *)&ai, (VOID *)aliases, &naliases,
987                  sizeof(ai), aliascmp));
988 }
989
990 /*
991  * Allocates more space for the aliases list.
992  */
993 static int
994 more_aliases()
995 {
996
997     nslots += MOREALIASES;
998     if (nslots == MOREALIASES)
999         aliases = (aliasinfo *) malloc(nslots * sizeof(aliasinfo));
1000     else
1001         aliases = (aliasinfo *) realloc(aliases, nslots * sizeof(aliasinfo));
1002
1003     return(aliases != NULL);
1004 }
1005
1006 /*
1007  * Lists the contents of the aliases list.
1008  */
1009 void
1010 dumpaliases()
1011 {
1012     size_t n;
1013
1014     for (n = 0; n < naliases; n++) {
1015         if (aliases[n].val == -1)
1016             continue;
1017
1018         switch (aliases[n].type) {
1019         case HOST_ALIAS:
1020             (void) puts("HOST_ALIAS");
1021             break;
1022
1023         case CMND_ALIAS:
1024             (void) puts("CMND_ALIAS");
1025             break;
1026
1027         case USER_ALIAS:
1028             (void) puts("USER_ALIAS");
1029             break;
1030
1031         case RUNAS_ALIAS:
1032             (void) puts("RUNAS_ALIAS");
1033             break;
1034         }
1035         (void) printf("\t%s: %d\n", aliases[n].name, aliases[n].val);
1036     }
1037 }
1038
1039 /*
1040  * Lists the contents of cm_list and ga_list for `sudo -l'.
1041  */
1042 void
1043 list_matches()
1044 {
1045     size_t count;
1046     char *p;
1047     struct generic_alias *ga, key;
1048
1049     (void) printf("User %s may run the following commands on this host:\n",
1050         user_name);
1051     for (count = 0; count < cm_list_len; count++) {
1052
1053         /* Print the runas list. */
1054         (void) fputs("    ", stdout);
1055         if (cm_list[count].runas) {
1056             (void) putchar('(');
1057             p = strtok(cm_list[count].runas, ", ");
1058             do {
1059                 if (p != cm_list[count].runas)
1060                     (void) fputs(", ", stdout);
1061
1062                 key.alias = p;
1063                 key.type = RUNAS_ALIAS;
1064                 if ((ga = (struct generic_alias *) lfind((VOID *) &key,
1065                     (VOID *) &ga_list[0], &ga_list_len, sizeof(key), genaliascmp)))
1066                     (void) fputs(ga->entries, stdout);
1067                 else
1068                     (void) fputs(p, stdout);
1069             } while ((p = strtok(NULL, ", ")));
1070             (void) fputs(") ", stdout);
1071         } else {
1072             (void) printf("(%s) ", def_runas_default);
1073         }
1074
1075         /* Is execve(2) disabled? */
1076         if (cm_list[count].noexecve == TRUE && !def_noexec)
1077             (void) fputs("NOEXEC: ", stdout);
1078         else if (cm_list[count].noexecve == FALSE && def_noexec)
1079             (void) fputs("EXEC: ", stdout);
1080
1081         /* Is a password required? */
1082         if (cm_list[count].nopasswd == TRUE && def_authenticate)
1083             (void) fputs("NOPASSWD: ", stdout);
1084         else if (cm_list[count].nopasswd == FALSE && !def_authenticate)
1085             (void) fputs("PASSWD: ", stdout);
1086
1087         /* Print the actual command or expanded Cmnd_Alias. */
1088         key.alias = cm_list[count].cmnd;
1089         key.type = CMND_ALIAS;
1090         if ((ga = (struct generic_alias *) lfind((VOID *) &key,
1091             (VOID *) &ga_list[0], &ga_list_len, sizeof(key), genaliascmp)))
1092             (void) puts(ga->entries);
1093         else
1094             (void) puts(cm_list[count].cmnd);
1095     }
1096
1097     /* Be nice and free up space now that we are done. */
1098     for (count = 0; count < ga_list_len; count++) {
1099         free(ga_list[count].alias);
1100         free(ga_list[count].entries);
1101     }
1102     free(ga_list);
1103     ga_list = NULL;
1104
1105     for (count = 0; count < cm_list_len; count++) {
1106         free(cm_list[count].runas);
1107         free(cm_list[count].cmnd);
1108     }
1109     free(cm_list);
1110     cm_list = NULL;
1111     cm_list_len = 0;
1112     cm_list_size = 0;
1113 }
1114
1115 /*
1116  * Appends a source string to the destination, optionally prefixing a separator.
1117  */
1118 static void
1119 append(src, dstp, dst_len, dst_size, separator)
1120     char *src, **dstp;
1121     size_t *dst_len, *dst_size;
1122     char *separator;
1123 {
1124     size_t src_len = strlen(src);
1125     char *dst = *dstp;
1126
1127     /*
1128      * Only add the separator if there is something to separate from.
1129      * If the last char is a '!', don't apply the separator (XXX).
1130      */
1131     if (separator && dst && dst[*dst_len - 1] != '!')
1132         src_len += strlen(separator);
1133     else
1134         separator = NULL;
1135
1136     /* Assumes dst will be NULL if not set. */
1137     if (dst == NULL) {
1138         dst = (char *) emalloc(BUFSIZ);
1139         *dst = '\0';
1140         *dst_size = BUFSIZ;
1141         *dst_len = 0;
1142         *dstp = dst;
1143     }
1144
1145     /* Allocate more space if necessary. */
1146     if (*dst_size <= *dst_len + src_len) {
1147         while (*dst_size <= *dst_len + src_len)
1148             *dst_size += BUFSIZ;
1149
1150         dst = (char *) erealloc(dst, *dst_size);
1151         *dstp = dst;
1152     }
1153
1154     /* Copy src -> dst adding a separator if appropriate and adjust len. */
1155     if (separator)
1156         (void) strlcat(dst, separator, *dst_size);
1157     (void) strlcat(dst, src, *dst_size);
1158     *dst_len += src_len;
1159 }
1160
1161 /*
1162  * Frees up space used by the aliases list and resets the associated counters.
1163  */
1164 void
1165 reset_aliases()
1166 {
1167     size_t n;
1168
1169     if (aliases) {
1170         for (n = 0; n < naliases; n++)
1171             free(aliases[n].name);
1172         free(aliases);
1173         aliases = NULL;
1174     }
1175     naliases = nslots = 0;
1176 }
1177
1178 /*
1179  * Increments ga_list_len, allocating more space as necessary.
1180  */
1181 static void
1182 expand_ga_list()
1183 {
1184
1185     if (++ga_list_len >= ga_list_size) {
1186         while ((ga_list_size += STACKINCREMENT) < ga_list_len)
1187             ;
1188         ga_list = (struct generic_alias *)
1189             erealloc3(ga_list, ga_list_size, sizeof(struct generic_alias));
1190     }
1191
1192     ga_list[ga_list_len - 1].entries = NULL;
1193 }
1194
1195 /*
1196  * Increments cm_list_len, allocating more space as necessary.
1197  */
1198 static void
1199 expand_match_list()
1200 {
1201
1202     if (++cm_list_len >= cm_list_size) {
1203         while ((cm_list_size += STACKINCREMENT) < cm_list_len)
1204             ;
1205         if (cm_list == NULL)
1206             cm_list_len = 0;            /* start at 0 since it is a subscript */
1207         cm_list = (struct command_match *)
1208             erealloc3(cm_list, cm_list_size, sizeof(struct command_match));
1209     }
1210
1211     cm_list[cm_list_len].runas = cm_list[cm_list_len].cmnd = NULL;
1212     cm_list[cm_list_len].nopasswd = FALSE;
1213     cm_list[cm_list_len].noexecve = FALSE;
1214 }
1215
1216 /*
1217  * Frees up spaced used by a previous parser run and allocates new space
1218  * for various data structures.
1219  */
1220 void
1221 init_parser()
1222 {
1223
1224     /* Free up old data structures if we run the parser more than once. */
1225     if (match) {
1226         free(match);
1227         match = NULL;
1228         top = 0;
1229         parse_error = FALSE;
1230         used_runas = FALSE;
1231         errorlineno = -1;
1232         sudolineno = 1;
1233     }
1234
1235     /* Allocate space for the matching stack. */
1236     stacksize = STACKINCREMENT;
1237     match = (struct matchstack *) emalloc2(stacksize, sizeof(struct matchstack));
1238
1239     /* Allocate space for the match list (for `sudo -l'). */
1240     if (printmatches == TRUE)
1241         expand_match_list();
1242 }