3 * Copyright (c) 1996, 1998-2005, 2007-2011
4 * Todd C. Miller <Todd.Miller@courtesan.com>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
19 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21 * Sponsored in part by the Defense Advanced Research Projects
22 * Agency (DARPA) and Air Force Research Laboratory, Air Force
23 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
28 #include <sys/types.h>
29 #include <sys/param.h>
39 #endif /* STDC_HEADERS */
42 #endif /* HAVE_STRING_H */
45 #endif /* HAVE_STRINGS_H */
48 #endif /* HAVE_UNISTD_H */
49 #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
51 #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
54 # define NAMLEN(dirent) strlen((dirent)->d_name)
56 # define dirent direct
57 # define NAMLEN(dirent) (dirent)->d_namlen
58 # ifdef HAVE_SYS_NDIR_H
59 # include <sys/ndir.h>
61 # ifdef HAVE_SYS_DIR_H
75 extern YYSTYPE yylval;
76 extern int parse_error;
80 static int continued, prev_state, sawspace;
82 static int _push_include(char *, int);
83 static int pop_include(void);
84 static char *parse_include(char *);
87 static int sudoers_trace_print(const char *msg);
89 # define sudoers_trace_print NULL
91 int (*trace_print)(const char *msg) = sudoers_trace_print;
93 #define push_include(_p) (_push_include((_p), FALSE))
94 #define push_includedir(_p) (_push_include((_p), TRUE))
97 HEX16 [0-9A-Fa-f]{1,4}
98 OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
99 IPV4ADDR {OCTET}(\.{OCTET}){3}
100 IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
102 HOSTNAME [[:alnum:]_-]+
103 WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
105 PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
106 ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
120 <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
125 <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
127 <STARTDEFS>{DEFVAR} {
130 if (!fill(yytext, yyleng))
158 LEXTRACE("BEGINSTR ");
159 yylval.string = NULL;
160 prev_state = YY_START;
165 LEXTRACE("WORD(2) ");
166 if (!fill(yytext, yyleng))
173 \\[[:blank:]]*\n[[:blank:]]* {
174 /* Line continuation char followed by newline. */
183 if (yylval.string == NULL) {
184 LEXTRACE("ERROR "); /* empty string */
187 if (prev_state == INITIAL) {
188 switch (yylval.string[0]) {
190 if (yylval.string[1] == '\0' ||
191 (yylval.string[1] == ':' &&
192 yylval.string[2] == '\0')) {
193 LEXTRACE("ERROR "); /* empty group */
196 LEXTRACE("USERGROUP ");
199 if (yylval.string[1] == '\0') {
200 LEXTRACE("ERROR "); /* empty netgroup */
203 LEXTRACE("NETGROUP ");
207 LEXTRACE("WORD(4) ");
212 LEXTRACE("BACKSLASH ");
213 if (!append(yytext, yyleng))
218 LEXTRACE("STRBODY ");
219 if (!append(yytext, yyleng))
226 /* quoted fnmatch glob char, pass verbatim */
227 LEXTRACE("QUOTEDCHAR ");
228 if (!fill_args(yytext, 2, sawspace))
234 /* quoted sudoers special char, strip backslash */
235 LEXTRACE("QUOTEDCHAR ");
236 if (!fill_args(yytext + 1, 1, sawspace))
245 } /* end of command line args */
249 if (!fill_args(yytext, yyleng, sawspace))
252 } /* a command line arg */
255 <INITIAL>^#include[[:blank:]]+\/.*\n {
263 if ((path = parse_include(yytext)) == NULL)
266 LEXTRACE("INCLUDE\n");
268 /* Push current buffer and switch to include file */
269 if (!push_include(path))
273 <INITIAL>^#includedir[[:blank:]]+\/.*\n {
281 if ((path = parse_include(yytext)) == NULL)
284 LEXTRACE("INCLUDEDIR\n");
287 * Push current buffer and switch to include file.
288 * We simply ignore empty directories.
290 if (!push_includedir(path) && parse_error)
294 <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
303 for (n = 0; isblank((unsigned char)yytext[n]); n++)
305 n += sizeof("Defaults") - 1;
306 if ((deftype = yytext[n++]) != '\0') {
307 while (isblank((unsigned char)yytext[n]))
314 LEXTRACE("DEFAULTS_USER ");
315 return DEFAULTS_USER;
318 LEXTRACE("DEFAULTS_RUNAS ");
319 return DEFAULTS_RUNAS;
322 LEXTRACE("DEFAULTS_HOST ");
323 return DEFAULTS_HOST;
326 LEXTRACE("DEFAULTS_CMND ");
327 return DEFAULTS_CMND;
329 LEXTRACE("DEFAULTS ");
334 <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
342 for (n = 0; isblank((unsigned char)yytext[n]); n++)
346 LEXTRACE("HOSTALIAS ");
349 LEXTRACE("CMNDALIAS ");
352 LEXTRACE("USERALIAS ");
355 LEXTRACE("RUNASALIAS ");
360 NOPASSWD[[:blank:]]*: {
361 /* cmnd does not require passwd for this user */
362 LEXTRACE("NOPASSWD ");
366 PASSWD[[:blank:]]*: {
367 /* cmnd requires passwd for this user */
372 NOEXEC[[:blank:]]*: {
382 SETENV[[:blank:]]*: {
387 NOSETENV[[:blank:]]*: {
388 LEXTRACE("NOSETENV ");
392 LOG_OUTPUT[[:blank:]]*: {
393 LEXTRACE("LOG_OUTPUT ");
397 NOLOG_OUTPUT[[:blank:]]*: {
398 LEXTRACE("NOLOG_OUTPUT ");
402 LOG_INPUT[[:blank:]]*: {
403 LEXTRACE("LOG_INPUT ");
407 NOLOG_INPUT[[:blank:]]*: {
408 LEXTRACE("NOLOG_INPUT ");
412 <INITIAL,GOTDEFS>(\+|\%|\%:) {
413 /* empty group or netgroup */
420 if (!fill(yytext, yyleng))
422 LEXTRACE("NETGROUP ");
428 if (!fill(yytext, yyleng))
430 LEXTRACE("USERGROUP ");
434 {IPV4ADDR}(\/{IPV4ADDR})? {
435 if (!fill(yytext, yyleng))
437 LEXTRACE("NTWKADDR ");
441 {IPV4ADDR}\/([12]?[0-9]|3[0-2]) {
442 if (!fill(yytext, yyleng))
444 LEXTRACE("NTWKADDR ");
448 {IPV6ADDR}(\/{IPV6ADDR})? {
449 if (!ipv6_valid(yytext)) {
453 if (!fill(yytext, yyleng))
455 LEXTRACE("NTWKADDR ");
459 {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
460 if (!ipv6_valid(yytext)) {
464 if (!fill(yytext, yyleng))
466 LEXTRACE("NTWKADDR ");
494 [[:upper:]][[:upper:][:digit:]_]* {
496 if (!fill(yytext, yyleng))
502 <GOTDEFS>({PATH}|sudoedit) {
503 /* no command args allowed for Defaults!/path */
504 if (!fill_cmnd(yytext, yyleng))
506 LEXTRACE("COMMAND ");
512 LEXTRACE("COMMAND ");
513 if (!fill_cmnd(yytext, yyleng))
518 /* directories can't have args... */
519 if (yytext[yyleng - 1] == '/') {
520 LEXTRACE("COMMAND ");
521 if (!fill_cmnd(yytext, yyleng))
526 LEXTRACE("COMMAND ");
527 if (!fill_cmnd(yytext, yyleng))
532 <INITIAL,GOTDEFS>\" {
533 LEXTRACE("BEGINSTR ");
534 yylval.string = NULL;
535 prev_state = YY_START;
539 <INITIAL,GOTDEFS>({ID}|{WORD}) {
541 if (!fill(yytext, yyleng))
543 LEXTRACE("WORD(5) ");
575 return '!'; /* return '!' */
580 if (YY_START == INSTR) {
582 return ERROR; /* line break in string */
589 } /* return newline */
591 <*>[[:blank:]]+ { /* throw away space/tabs */
592 sawspace = TRUE; /* but remember for fill_args */
595 <*>\\[[:blank:]]*\n {
596 sawspace = TRUE; /* remember for fill_args */
599 } /* throw away EOL after \ */
601 <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n {
607 } /* comment, not uid/gid */
615 if (YY_START != INITIAL) {
627 struct path_list *next;
630 struct include_stack {
633 struct path_list *more; /* more files in case of includedir */
639 pl_compare(const void *v1, const void *v2)
641 const struct path_list * const *p1 = v1;
642 const struct path_list * const *p2 = v2;
644 return strcmp((*p1)->path, (*p2)->path);
648 switch_dir(struct include_stack *stack, char *dirpath)
655 struct path_list *pl, *first = NULL;
656 struct path_list **sorted = NULL;
658 if (!(dir = opendir(dirpath))) {
659 if (errno != ENOENT) {
661 if (asprintf(&errbuf, "%s: %s", dirpath, strerror(errno)) != -1) {
665 yyerror("unable to allocate memory");
670 while ((dent = readdir(dir))) {
671 /* Ignore files that end in '~' or have a '.' in them. */
672 if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
673 || strchr(dent->d_name, '.') != NULL) {
676 if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
680 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
685 pl = malloc(sizeof(*pl));
698 /* Sort the list as an array. */
699 sorted = malloc(sizeof(*sorted) * count);
703 for (i = 0; i < count; i++) {
707 qsort(sorted, count, sizeof(*sorted), pl_compare);
709 /* Apply sorting to the list. */
711 sorted[count - 1]->next = NULL;
712 for (i = 1; i < count; i++)
713 sorted[i - 1]->next = sorted[i];
716 /* Pull out the first element for parsing, leave the rest for later. */
729 while (first != NULL) {
741 #define MAX_SUDOERS_DEPTH 128
742 #define SUDOERS_STACK_INCREMENT 16
744 static size_t istacksize, idepth;
745 static struct include_stack *istack;
751 struct path_list *pl;
755 while ((pl = istack[idepth].more) != NULL) {
756 istack[idepth].more = pl->next;
760 efree(istack[idepth].path);
761 if (idepth && !istack[idepth].keepopen)
762 fclose(istack[idepth].bs->yy_input_file);
763 yy_delete_buffer(istack[idepth].bs);
767 istacksize = idepth = 0;
772 prev_state = INITIAL;
776 _push_include(char *path, int isdir)
778 struct path_list *pl;
781 /* push current state onto stack */
782 if (idepth >= istacksize) {
783 if (idepth > MAX_SUDOERS_DEPTH) {
784 yyerror("too many levels of includes");
787 istacksize += SUDOERS_STACK_INCREMENT;
788 istack = (struct include_stack *) realloc(istack,
789 sizeof(*istack) * istacksize);
790 if (istack == NULL) {
791 yyerror("unable to allocate memory");
796 if (!(path = switch_dir(&istack[idepth], path))) {
797 /* switch_dir() called yyerror() for us */
800 while ((fp = open_sudoers(path, FALSE, &keepopen)) == NULL) {
801 /* Unable to open path in includedir, go to next one, if any. */
803 if ((pl = istack[idepth].more) == NULL)
806 istack[idepth].more = pl->next;
810 if ((fp = open_sudoers(path, TRUE, &keepopen)) == NULL) {
812 if (asprintf(&errbuf, "%s: %s", path, strerror(errno)) != -1) {
816 yyerror("unable to allocate memory");
820 istack[idepth].more = NULL;
822 /* Push the old (current) file and open the new one. */
823 istack[idepth].path = sudoers; /* push old path */
824 istack[idepth].bs = YY_CURRENT_BUFFER;
825 istack[idepth].lineno = sudolineno;
826 istack[idepth].keepopen = keepopen;
830 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
838 struct path_list *pl;
845 fclose(YY_CURRENT_BUFFER->yy_input_file);
846 yy_delete_buffer(YY_CURRENT_BUFFER);
847 /* If we are in an include dir, move to the next file. */
848 while ((pl = istack[idepth - 1].more) != NULL) {
849 fp = open_sudoers(pl->path, FALSE, &keepopen);
851 istack[idepth - 1].more = pl->next;
855 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
859 /* Unable to open path in include dir, go to next one. */
860 istack[idepth - 1].more = pl->next;
864 /* If no path list, just pop the last dir on the stack. */
867 yy_switch_to_buffer(istack[idepth].bs);
869 sudoers = istack[idepth].path;
870 sudolineno = istack[idepth].lineno;
871 keepopen = istack[idepth].keepopen;
877 parse_include(char *base)
879 char *cp, *ep, *path;
880 int len = 0, subst = 0;
881 size_t shost_len = 0;
883 /* Pull out path from #include line. */
884 cp = base + sizeof("#include");
886 cp += 3; /* includedir */
887 while (isblank((unsigned char) *cp))
890 while (*ep != '\0' && !isspace((unsigned char) *ep)) {
891 if (ep[0] == '%' && ep[1] == 'h') {
892 shost_len = strlen(user_shost);
893 len += shost_len - 2;
899 /* Make a copy of path and return it. */
900 len += (int)(ep - cp);
901 if ((path = malloc(len + 1)) == NULL)
902 yyerror("unable to allocate memory");
904 /* substitute for %h */
907 if (cp[0] == '%' && cp[1] == 'h') {
908 memcpy(pp, user_shost, shost_len);
917 memcpy(path, cp, len);
921 /* Push any excess characters (e.g. comment, newline) back to the lexer */
923 yyless((int)(ep - base));
930 sudoers_trace_print(const char *msg)
932 return fputs(msg, stderr);
934 #endif /* TRACELEXER */