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
76 extern YYSTYPE yylval;
77 extern bool parse_error;
82 static bool continued, sawspace;
83 static int prev_state;
85 static bool _push_include(char *, bool);
86 static bool pop_include(void);
87 static char *parse_include(char *);
89 static int sudoers_trace_print(const char *msg);
90 int (*trace_print)(const char *msg) = sudoers_trace_print;
92 #define LEXRETURN(n) do { \
97 #define push_include(_p) (_push_include((_p), false))
98 #define push_includedir(_p) (_push_include((_p), true))
101 HEX16 [0-9A-Fa-f]{1,4}
102 OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
103 IPV4ADDR {OCTET}(\.{OCTET}){3}
104 IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
106 HOSTNAME [[:alnum:]_-]+
107 WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
109 PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
110 ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
124 <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
129 <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
131 <STARTDEFS>{DEFVAR} {
134 if (!fill(yytext, yyleng))
162 LEXTRACE("BEGINSTR ");
163 yylval.string = NULL;
164 prev_state = YY_START;
169 LEXTRACE("WORD(2) ");
170 if (!fill(yytext, yyleng))
177 \\[[:blank:]]*\n[[:blank:]]* {
178 /* Line continuation char followed by newline. */
187 if (yylval.string == NULL) {
188 LEXTRACE("ERROR "); /* empty string */
191 if (prev_state == INITIAL) {
192 switch (yylval.string[0]) {
194 if (yylval.string[1] == '\0' ||
195 (yylval.string[1] == ':' &&
196 yylval.string[2] == '\0')) {
197 LEXTRACE("ERROR "); /* empty group */
200 LEXTRACE("USERGROUP ");
201 LEXRETURN(USERGROUP);
203 if (yylval.string[1] == '\0') {
204 LEXTRACE("ERROR "); /* empty netgroup */
207 LEXTRACE("NETGROUP ");
211 LEXTRACE("WORD(4) ");
216 LEXTRACE("BACKSLASH ");
217 if (!append(yytext, yyleng))
222 LEXTRACE("STRBODY ");
223 if (!append(yytext, yyleng))
230 /* quoted fnmatch glob char, pass verbatim */
231 LEXTRACE("QUOTEDCHAR ");
232 if (!fill_args(yytext, 2, sawspace))
238 /* quoted sudoers special char, strip backslash */
239 LEXTRACE("QUOTEDCHAR ");
240 if (!fill_args(yytext + 1, 1, sawspace))
249 } /* end of command line args */
253 if (!fill_args(yytext, yyleng, sawspace))
256 } /* a command line arg */
259 <INITIAL>^#include[[:blank:]]+.*\n {
267 if ((path = parse_include(yytext)) == NULL)
270 LEXTRACE("INCLUDE\n");
272 /* Push current buffer and switch to include file */
273 if (!push_include(path))
277 <INITIAL>^#includedir[[:blank:]]+.*\n {
285 if ((path = parse_include(yytext)) == NULL)
288 LEXTRACE("INCLUDEDIR\n");
291 * Push current buffer and switch to include file.
292 * We simply ignore empty directories.
294 if (!push_includedir(path) && parse_error)
298 <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
307 for (n = 0; isblank((unsigned char)yytext[n]); n++)
309 n += sizeof("Defaults") - 1;
310 if ((deftype = yytext[n++]) != '\0') {
311 while (isblank((unsigned char)yytext[n]))
318 LEXTRACE("DEFAULTS_USER ");
319 LEXRETURN(DEFAULTS_USER);
322 LEXTRACE("DEFAULTS_RUNAS ");
323 LEXRETURN(DEFAULTS_RUNAS);
326 LEXTRACE("DEFAULTS_HOST ");
327 LEXRETURN(DEFAULTS_HOST);
330 LEXTRACE("DEFAULTS_CMND ");
331 LEXRETURN(DEFAULTS_CMND);
333 LEXTRACE("DEFAULTS ");
338 <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
346 for (n = 0; isblank((unsigned char)yytext[n]); n++)
350 LEXTRACE("HOSTALIAS ");
351 LEXRETURN(HOSTALIAS);
353 LEXTRACE("CMNDALIAS ");
354 LEXRETURN(CMNDALIAS);
356 LEXTRACE("USERALIAS ");
357 LEXRETURN(USERALIAS);
359 LEXTRACE("RUNASALIAS ");
360 LEXRETURN(RUNASALIAS);
364 NOPASSWD[[:blank:]]*: {
365 /* cmnd does not require passwd for this user */
366 LEXTRACE("NOPASSWD ");
370 PASSWD[[:blank:]]*: {
371 /* cmnd requires passwd for this user */
376 NOEXEC[[:blank:]]*: {
386 SETENV[[:blank:]]*: {
391 NOSETENV[[:blank:]]*: {
392 LEXTRACE("NOSETENV ");
396 LOG_OUTPUT[[:blank:]]*: {
397 LEXTRACE("LOG_OUTPUT ");
398 LEXRETURN(LOG_OUTPUT);
401 NOLOG_OUTPUT[[:blank:]]*: {
402 LEXTRACE("NOLOG_OUTPUT ");
403 LEXRETURN(NOLOG_OUTPUT);
406 LOG_INPUT[[:blank:]]*: {
407 LEXTRACE("LOG_INPUT ");
408 LEXRETURN(LOG_INPUT);
411 NOLOG_INPUT[[:blank:]]*: {
412 LEXTRACE("NOLOG_INPUT ");
413 LEXRETURN(NOLOG_INPUT);
416 <INITIAL,GOTDEFS>(\+|\%|\%:) {
417 /* empty group or netgroup */
424 if (!fill(yytext, yyleng))
426 LEXTRACE("NETGROUP ");
432 if (!fill(yytext, yyleng))
434 LEXTRACE("USERGROUP ");
435 LEXRETURN(USERGROUP);
438 {IPV4ADDR}(\/{IPV4ADDR})? {
439 if (!fill(yytext, yyleng))
441 LEXTRACE("NTWKADDR ");
445 {IPV4ADDR}\/([12]?[0-9]|3[0-2]) {
446 if (!fill(yytext, yyleng))
448 LEXTRACE("NTWKADDR ");
452 {IPV6ADDR}(\/{IPV6ADDR})? {
453 if (!ipv6_valid(yytext)) {
457 if (!fill(yytext, yyleng))
459 LEXTRACE("NTWKADDR ");
463 {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
464 if (!ipv6_valid(yytext)) {
468 if (!fill(yytext, yyleng))
470 LEXTRACE("NTWKADDR ");
498 [[:upper:]][[:upper:][:digit:]_]* {
502 if (!fill(yytext, yyleng))
508 <GOTDEFS>({PATH}|sudoedit) {
509 /* no command args allowed for Defaults!/path */
510 if (!fill_cmnd(yytext, yyleng))
512 LEXTRACE("COMMAND ");
518 LEXTRACE("COMMAND ");
519 if (!fill_cmnd(yytext, yyleng))
524 /* directories can't have args... */
525 if (yytext[yyleng - 1] == '/') {
526 LEXTRACE("COMMAND ");
527 if (!fill_cmnd(yytext, yyleng))
532 LEXTRACE("COMMAND ");
533 if (!fill_cmnd(yytext, yyleng))
538 <INITIAL,GOTDEFS>\" {
539 LEXTRACE("BEGINSTR ");
540 yylval.string = NULL;
541 prev_state = YY_START;
545 <INITIAL,GOTDEFS>({ID}|{WORD}) {
547 if (!fill(yytext, yyleng))
549 LEXTRACE("WORD(5) ");
581 LEXRETURN('!'); /* return '!' */
586 if (YY_START == INSTR) {
588 LEXRETURN(ERROR); /* line break in string */
595 } /* return newline */
597 <*>[[:blank:]]+ { /* throw away space/tabs */
598 sawspace = true; /* but remember for fill_args */
601 <*>\\[[:blank:]]*\n {
602 sawspace = true; /* remember for fill_args */
605 } /* throw away EOL after \ */
607 <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n {
613 } /* comment, not uid/gid */
621 if (YY_START != INITIAL) {
633 struct path_list *next;
636 struct include_stack {
639 struct path_list *more; /* more files in case of includedir */
645 pl_compare(const void *v1, const void *v2)
647 const struct path_list * const *p1 = v1;
648 const struct path_list * const *p2 = v2;
650 return strcmp((*p1)->path, (*p2)->path);
654 switch_dir(struct include_stack *stack, char *dirpath)
661 struct path_list *pl, *first = NULL;
662 struct path_list **sorted = NULL;
663 debug_decl(switch_dir, SUDO_DEBUG_PARSER)
665 if (!(dir = opendir(dirpath))) {
666 if (errno != ENOENT) {
668 if (asprintf(&errbuf, _("%s: %s"), dirpath, strerror(errno)) != -1) {
672 yyerror(_("unable to allocate memory"));
677 while ((dent = readdir(dir))) {
678 /* Ignore files that end in '~' or have a '.' in them. */
679 if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
680 || strchr(dent->d_name, '.') != NULL) {
683 if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
687 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
692 pl = malloc(sizeof(*pl));
705 /* Sort the list as an array. */
706 sorted = malloc(sizeof(*sorted) * count);
710 for (i = 0; i < count; i++) {
714 qsort(sorted, count, sizeof(*sorted), pl_compare);
716 /* Apply sorting to the list. */
718 sorted[count - 1]->next = NULL;
719 for (i = 1; i < count; i++)
720 sorted[i - 1]->next = sorted[i];
723 /* Pull out the first element for parsing, leave the rest for later. */
734 debug_return_str(path);
736 while (first != NULL) {
745 debug_return_str(NULL);
748 #define MAX_SUDOERS_DEPTH 128
749 #define SUDOERS_STACK_INCREMENT 16
751 static size_t istacksize, idepth;
752 static struct include_stack *istack;
753 static bool keepopen;
758 struct path_list *pl;
759 debug_decl(init_lexer, SUDO_DEBUG_PARSER)
763 while ((pl = istack[idepth].more) != NULL) {
764 istack[idepth].more = pl->next;
768 efree(istack[idepth].path);
769 if (idepth && !istack[idepth].keepopen)
770 fclose(istack[idepth].bs->yy_input_file);
771 yy_delete_buffer(istack[idepth].bs);
775 istacksize = idepth = 0;
780 prev_state = INITIAL;
786 _push_include(char *path, bool isdir)
788 struct path_list *pl;
790 debug_decl(_push_include, SUDO_DEBUG_PARSER)
792 /* push current state onto stack */
793 if (idepth >= istacksize) {
794 if (idepth > MAX_SUDOERS_DEPTH) {
795 yyerror(_("too many levels of includes"));
796 debug_return_bool(false);
798 istacksize += SUDOERS_STACK_INCREMENT;
799 istack = (struct include_stack *) realloc(istack,
800 sizeof(*istack) * istacksize);
801 if (istack == NULL) {
802 yyerror(_("unable to allocate memory"));
803 debug_return_bool(false);
807 if (!(path = switch_dir(&istack[idepth], path))) {
808 /* switch_dir() called yyerror() for us */
809 debug_return_bool(false);
811 while ((fp = open_sudoers(path, false, &keepopen)) == NULL) {
812 /* Unable to open path in includedir, go to next one, if any. */
814 if ((pl = istack[idepth].more) == NULL)
815 debug_return_bool(false);
817 istack[idepth].more = pl->next;
821 if ((fp = open_sudoers(path, true, &keepopen)) == NULL) {
823 if (asprintf(&errbuf, _("%s: %s"), path, strerror(errno)) != -1) {
827 yyerror(_("unable to allocate memory"));
829 debug_return_bool(false);
831 istack[idepth].more = NULL;
833 /* Push the old (current) file and open the new one. */
834 istack[idepth].path = sudoers; /* push old path */
835 istack[idepth].bs = YY_CURRENT_BUFFER;
836 istack[idepth].lineno = sudolineno;
837 istack[idepth].keepopen = keepopen;
841 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
843 debug_return_bool(true);
849 struct path_list *pl;
851 debug_decl(pop_include, SUDO_DEBUG_PARSER)
854 debug_return_bool(false);
857 fclose(YY_CURRENT_BUFFER->yy_input_file);
858 yy_delete_buffer(YY_CURRENT_BUFFER);
859 /* If we are in an include dir, move to the next file. */
860 while ((pl = istack[idepth - 1].more) != NULL) {
861 fp = open_sudoers(pl->path, false, &keepopen);
863 istack[idepth - 1].more = pl->next;
867 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
871 /* Unable to open path in include dir, go to next one. */
872 istack[idepth - 1].more = pl->next;
876 /* If no path list, just pop the last dir on the stack. */
879 yy_switch_to_buffer(istack[idepth].bs);
881 sudoers = istack[idepth].path;
882 sudolineno = istack[idepth].lineno;
883 keepopen = istack[idepth].keepopen;
885 debug_return_bool(true);
889 parse_include(char *base)
891 char *cp, *ep, *path, *pp;
892 int dirlen = 0, len = 0, subst = 0;
893 size_t shost_len = 0;
894 debug_decl(parse_include, SUDO_DEBUG_PARSER)
896 /* Pull out path from #include line. */
897 cp = base + sizeof("#include");
899 cp += 3; /* includedir */
900 while (isblank((unsigned char) *cp))
903 while (*ep != '\0' && !isspace((unsigned char) *ep)) {
904 if (ep[0] == '%' && ep[1] == 'h') {
905 shost_len = strlen(user_shost);
906 len += shost_len - 2;
912 /* Relative paths are located in the same dir as the sudoers file. */
914 char *dirend = strrchr(sudoers, '/');
916 dirlen = (int)(dirend - sudoers) + 1;
919 /* Make a copy of the fully-qualified path and return it. */
920 len += (int)(ep - cp);
921 path = pp = malloc(len + dirlen + 1);
923 yyerror(_("unable to allocate memory"));
924 debug_return_str(NULL);
927 memcpy(path, sudoers, dirlen);
931 /* substitute for %h */
933 if (cp[0] == '%' && cp[1] == 'h') {
934 memcpy(pp, user_shost, shost_len);
947 /* Push any excess characters (e.g. comment, newline) back to the lexer */
949 yyless((int)(ep - base));
951 debug_return_str(path);
956 sudoers_trace_print(const char *msg)
958 return fputs(msg, stderr);
962 sudoers_trace_print(const char *msg)
964 static bool initialized;
965 static struct lbuf lbuf;
969 lbuf_init(&lbuf, NULL, 0, NULL, 0);
972 lbuf_append(&lbuf, "%s", msg);
973 /* XXX - assumes a final newline */
974 if (strchr(msg, '\n') != NULL)
976 sudo_debug_printf2(SUDO_DEBUG_PARSER|SUDO_DEBUG_DEBUG, "%s:%d %s",
977 sudoers, sudolineno, lbuf.buf);
982 #endif /* TRACELEXER */