3 * Copyright (c) 1996, 1998-2005, 2007-2010
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 __P((char *, int));
83 static int pop_include __P((void));
84 static char *parse_include __P((char *));
86 #define fill(a, b) fill_txt(a, b, 0)
88 #define push_include(_p) (_push_include((_p), FALSE))
89 #define push_includedir(_p) (_push_include((_p), TRUE))
92 #define LEXTRACE(msg) fputs(msg, stderr)
98 HEX16 [0-9A-Fa-f]{1,4}
99 OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
100 IPV4ADDR {OCTET}(\.{OCTET}){3}
101 IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
103 HOSTNAME [[:alnum:]_-]+
104 WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
106 PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
107 ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
121 <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
126 <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
128 <STARTDEFS>{DEFVAR} {
131 if (!fill(yytext, yyleng))
159 LEXTRACE("BEGINSTR ");
160 yylval.string = NULL;
161 prev_state = YY_START;
166 LEXTRACE("WORD(2) ");
167 if (!fill(yytext, yyleng))
174 \\[[:blank:]]*\n[[:blank:]]* {
175 /* Line continuation char followed by newline. */
184 if (yylval.string == NULL) {
185 LEXTRACE("ERROR "); /* empty string */
188 if (prev_state == INITIAL) {
189 switch (yylval.string[0]) {
191 if (yylval.string[1] == '\0' ||
192 (yylval.string[1] == ':' &&
193 yylval.string[2] == '\0')) {
194 LEXTRACE("ERROR "); /* empty group */
197 LEXTRACE("USERGROUP ");
200 if (yylval.string[1] == '\0') {
201 LEXTRACE("ERROR "); /* empty netgroup */
204 LEXTRACE("NETGROUP ");
208 LEXTRACE("WORD(4) ");
213 LEXTRACE("BACKSLASH ");
214 if (!append(yytext, yyleng))
219 LEXTRACE("STRBODY ");
220 if (!append(yytext, yyleng))
227 /* quoted fnmatch glob char, pass verbatim */
228 LEXTRACE("QUOTEDCHAR ");
229 if (!fill_args(yytext, 2, sawspace))
235 /* quoted sudoers special char, strip backslash */
236 LEXTRACE("QUOTEDCHAR ");
237 if (!fill_args(yytext + 1, 1, sawspace))
246 } /* end of command line args */
250 if (!fill_args(yytext, yyleng, sawspace))
253 } /* a command line arg */
256 <INITIAL>^#include[[:blank:]]+\/.*\n {
264 if ((path = parse_include(yytext)) == NULL)
267 LEXTRACE("INCLUDE\n");
269 /* Push current buffer and switch to include file */
270 if (!push_include(path))
274 <INITIAL>^#includedir[[:blank:]]+\/.*\n {
282 if ((path = parse_include(yytext)) == NULL)
285 LEXTRACE("INCLUDEDIR\n");
288 * Push current buffer and switch to include file.
289 * We simply ignore empty directories.
291 if (!push_includedir(path) && parse_error)
295 <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
304 for (n = 0; isblank((unsigned char)yytext[n]); n++)
306 n += sizeof("Defaults") - 1;
307 if ((deftype = yytext[n++]) != '\0') {
308 while (isblank((unsigned char)yytext[n]))
315 LEXTRACE("DEFAULTS_USER ");
316 return DEFAULTS_USER;
319 LEXTRACE("DEFAULTS_RUNAS ");
320 return DEFAULTS_RUNAS;
323 LEXTRACE("DEFAULTS_HOST ");
324 return DEFAULTS_HOST;
327 LEXTRACE("DEFAULTS_CMND ");
328 return DEFAULTS_CMND;
330 LEXTRACE("DEFAULTS ");
335 <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
343 for (n = 0; isblank((unsigned char)yytext[n]); n++)
347 LEXTRACE("HOSTALIAS ");
350 LEXTRACE("CMNDALIAS ");
353 LEXTRACE("USERALIAS ");
356 LEXTRACE("RUNASALIAS ");
361 NOPASSWD[[:blank:]]*: {
362 /* cmnd does not require passwd for this user */
363 LEXTRACE("NOPASSWD ");
367 PASSWD[[:blank:]]*: {
368 /* cmnd requires passwd for this user */
373 NOEXEC[[:blank:]]*: {
383 SETENV[[:blank:]]*: {
388 NOSETENV[[:blank:]]*: {
389 LEXTRACE("NOSETENV ");
393 LOG_OUTPUT[[:blank:]]*: {
394 LEXTRACE("LOG_OUTPUT ");
398 NOLOG_OUTPUT[[:blank:]]*: {
399 LEXTRACE("NOLOG_OUTPUT ");
403 LOG_INPUT[[:blank:]]*: {
404 LEXTRACE("LOG_INPUT ");
408 NOLOG_INPUT[[:blank:]]*: {
409 LEXTRACE("NOLOG_INPUT ");
413 <INITIAL,GOTDEFS>(\+|\%|\%:) {
414 /* empty group or netgroup */
421 if (!fill(yytext, yyleng))
423 LEXTRACE("NETGROUP ");
429 if (!fill(yytext, yyleng))
431 LEXTRACE("USERGROUP ");
435 {IPV4ADDR}(\/{IPV4ADDR})? {
436 if (!fill(yytext, yyleng))
438 LEXTRACE("NTWKADDR ");
442 {IPV4ADDR}\/([12][0-9]*|3[0-2]*) {
443 if (!fill(yytext, yyleng))
445 LEXTRACE("NTWKADDR ");
449 {IPV6ADDR}(\/{IPV6ADDR})? {
450 if (!ipv6_valid(yytext)) {
454 if (!fill(yytext, yyleng))
456 LEXTRACE("NTWKADDR ");
460 {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
461 if (!ipv6_valid(yytext)) {
465 if (!fill(yytext, yyleng))
467 LEXTRACE("NTWKADDR ");
471 [[:upper:]][[:upper:][:digit:]_]* {
472 if (strcmp(yytext, "ALL") == 0) {
477 /* XXX - restrict type/role to initial state */
478 if (strcmp(yytext, "TYPE") == 0) {
482 if (strcmp(yytext, "ROLE") == 0) {
486 #endif /* HAVE_SELINUX */
487 if (!fill(yytext, yyleng))
493 <GOTDEFS>({PATH}|sudoedit) {
494 /* no command args allowed for Defaults!/path */
495 if (!fill_cmnd(yytext, yyleng))
497 LEXTRACE("COMMAND ");
503 LEXTRACE("COMMAND ");
504 if (!fill_cmnd(yytext, yyleng))
509 /* directories can't have args... */
510 if (yytext[yyleng - 1] == '/') {
511 LEXTRACE("COMMAND ");
512 if (!fill_cmnd(yytext, yyleng))
517 LEXTRACE("COMMAND ");
518 if (!fill_cmnd(yytext, yyleng))
523 <INITIAL,GOTDEFS>\" {
524 LEXTRACE("BEGINSTR ");
525 yylval.string = NULL;
526 prev_state = YY_START;
530 <INITIAL,GOTDEFS>({ID}|{WORD}) {
532 if (!fill(yytext, yyleng))
534 LEXTRACE("WORD(5) ");
566 return '!'; /* return '!' */
571 if (YY_START == INSTR) {
573 return ERROR; /* line break in string */
580 } /* return newline */
582 <*>[[:blank:]]+ { /* throw away space/tabs */
583 sawspace = TRUE; /* but remember for fill_args */
586 <*>\\[[:blank:]]*\n {
587 sawspace = TRUE; /* remember for fill_args */
590 } /* throw away EOL after \ */
592 <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n {
598 } /* comment, not uid/gid */
606 if (YY_START != INITIAL) {
618 struct path_list *next;
621 struct include_stack {
624 struct path_list *more; /* more files in case of includedir */
634 const struct path_list * const *p1 = v1;
635 const struct path_list * const *p2 = v2;
637 return strcmp((*p1)->path, (*p2)->path);
641 switch_dir(stack, dirpath)
642 struct include_stack *stack;
650 struct path_list *pl, *first = NULL;
651 struct path_list **sorted = NULL;
653 if (!(dir = opendir(dirpath))) {
654 if (errno != ENOENT) {
656 if (asprintf(&errbuf, "%s: %s", dirpath, strerror(errno)) != -1) {
660 yyerror("unable to allocate memory");
665 while ((dent = readdir(dir))) {
666 /* Ignore files that end in '~' or have a '.' in them. */
667 if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
668 || strchr(dent->d_name, '.') != NULL) {
671 if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
675 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
680 pl = malloc(sizeof(*pl));
693 /* Sort the list as an array. */
694 sorted = malloc(sizeof(*sorted) * count);
698 for (i = 0; i < count; i++) {
702 qsort(sorted, count, sizeof(*sorted), pl_compare);
704 /* Apply sorting to the list. */
706 sorted[count - 1]->next = NULL;
707 for (i = 1; i < count; i++)
708 sorted[i - 1]->next = sorted[i];
711 /* Pull out the first element for parsing, leave the rest for later. */
724 while (first != NULL) {
736 #define MAX_SUDOERS_DEPTH 128
737 #define SUDOERS_STACK_INCREMENT 16
739 static size_t istacksize, idepth;
740 static struct include_stack *istack;
746 struct path_list *pl;
750 while ((pl = istack[idepth].more) != NULL) {
751 istack[idepth].more = pl->next;
755 efree(istack[idepth].path);
756 if (idepth && !istack[idepth].keepopen)
757 fclose(istack[idepth].bs->yy_input_file);
758 yy_delete_buffer(istack[idepth].bs);
762 istacksize = idepth = 0;
767 prev_state = INITIAL;
771 _push_include(path, isdir)
775 struct path_list *pl;
778 /* push current state onto stack */
779 if (idepth >= istacksize) {
780 if (idepth > MAX_SUDOERS_DEPTH) {
781 yyerror("too many levels of includes");
784 istacksize += SUDOERS_STACK_INCREMENT;
785 istack = (struct include_stack *) realloc(istack,
786 sizeof(*istack) * istacksize);
787 if (istack == NULL) {
788 yyerror("unable to allocate memory");
793 if (!(path = switch_dir(&istack[idepth], path))) {
794 /* switch_dir() called yyerror() for us */
797 while ((fp = open_sudoers(path, FALSE, &keepopen)) == NULL) {
798 /* Unable to open path in includedir, go to next one, if any. */
800 if ((pl = istack[idepth].more) == NULL)
803 istack[idepth].more = pl->next;
807 if ((fp = open_sudoers(path, TRUE, &keepopen)) == NULL) {
809 if (asprintf(&errbuf, "%s: %s", path, strerror(errno)) != -1) {
813 yyerror("unable to allocate memory");
817 istack[idepth].more = NULL;
819 /* Push the old (current) file and open the new one. */
820 istack[idepth].path = sudoers; /* push old path */
821 istack[idepth].bs = YY_CURRENT_BUFFER;
822 istack[idepth].lineno = sudolineno;
823 istack[idepth].keepopen = keepopen;
827 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
835 struct path_list *pl;
842 fclose(YY_CURRENT_BUFFER->yy_input_file);
843 yy_delete_buffer(YY_CURRENT_BUFFER);
844 /* If we are in an include dir, move to the next file. */
845 while ((pl = istack[idepth - 1].more) != NULL) {
846 fp = open_sudoers(pl->path, FALSE, &keepopen);
848 istack[idepth - 1].more = pl->next;
852 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
856 /* Unable to open path in include dir, go to next one. */
857 istack[idepth - 1].more = pl->next;
861 /* If no path list, just pop the last dir on the stack. */
864 yy_switch_to_buffer(istack[idepth].bs);
866 sudoers = istack[idepth].path;
867 sudolineno = istack[idepth].lineno;
868 keepopen = istack[idepth].keepopen;
877 char *cp, *ep, *path;
878 int len = 0, subst = 0;
879 size_t shost_len = 0;
881 /* Pull out path from #include line. */
882 cp = base + sizeof("#include");
884 cp += 3; /* includedir */
885 while (isblank((unsigned char) *cp))
888 while (*ep != '\0' && !isspace((unsigned char) *ep)) {
889 if (ep[0] == '%' && ep[1] == 'h') {
890 shost_len = strlen(user_shost);
891 len += shost_len - 2;
897 /* Make a copy of path and return it. */
898 len += (int)(ep - cp);
899 if ((path = malloc(len + 1)) == NULL)
900 yyerror("unable to allocate memory");
902 /* substitute for %h */
905 if (cp[0] == '%' && cp[1] == 'h') {
906 memcpy(pp, user_shost, shost_len);
915 memcpy(path, cp, len);
919 /* Push any excess characters (e.g. comment, newline) back to the lexer */
921 yyless((int)(ep - base));