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