3 * Copyright (c) 1996, 1998-2005, 2007-2012
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 #include "secure_path.h"
77 extern YYSTYPE yylval;
78 extern bool parse_error;
79 extern bool sudoers_warnings;
84 /* Default sudoers path, mode and owner (may be set via sudo.conf) */
85 const char *sudoers_file = _PATH_SUDOERS;
86 mode_t sudoers_mode = SUDOERS_MODE;
87 uid_t sudoers_uid = SUDOERS_UID;
88 gid_t sudoers_gid = SUDOERS_GID;
90 static bool continued, sawspace;
91 static int prev_state;
93 static bool _push_include(char *, bool);
94 static bool pop_include(void);
95 static char *parse_include(char *);
97 static int sudoers_trace_print(const char *msg);
98 int (*trace_print)(const char *msg) = sudoers_trace_print;
100 #define LEXRETURN(n) do { \
105 #define ECHO ignore_result(fwrite(yytext, yyleng, 1, yyout))
107 #define push_include(_p) (_push_include((_p), false))
108 #define push_includedir(_p) (_push_include((_p), true))
111 HEX16 [0-9A-Fa-f]{1,4}
112 OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
113 IPV4ADDR {OCTET}(\.{OCTET}){3}
114 IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
116 HOSTNAME [[:alnum:]_-]+
117 WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
119 PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
120 ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
134 <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
139 <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
141 <STARTDEFS>{DEFVAR} {
144 if (!fill(yytext, yyleng))
172 LEXTRACE("BEGINSTR ");
173 yylval.string = NULL;
174 prev_state = YY_START;
179 LEXTRACE("WORD(2) ");
180 if (!fill(yytext, yyleng))
187 \\[[:blank:]]*\n[[:blank:]]* {
188 /* Line continuation char followed by newline. */
197 if (yylval.string == NULL) {
198 LEXTRACE("ERROR "); /* empty string */
201 if (prev_state == INITIAL) {
202 switch (yylval.string[0]) {
204 if (yylval.string[1] == '\0' ||
205 (yylval.string[1] == ':' &&
206 yylval.string[2] == '\0')) {
207 LEXTRACE("ERROR "); /* empty group */
210 LEXTRACE("USERGROUP ");
211 LEXRETURN(USERGROUP);
213 if (yylval.string[1] == '\0') {
214 LEXTRACE("ERROR "); /* empty netgroup */
217 LEXTRACE("NETGROUP ");
221 LEXTRACE("WORD(4) ");
226 LEXTRACE("BACKSLASH ");
227 if (!append(yytext, yyleng))
232 LEXTRACE("STRBODY ");
233 if (!append(yytext, yyleng))
240 /* quoted fnmatch glob char, pass verbatim */
241 LEXTRACE("QUOTEDCHAR ");
242 if (!fill_args(yytext, 2, sawspace))
248 /* quoted sudoers special char, strip backslash */
249 LEXTRACE("QUOTEDCHAR ");
250 if (!fill_args(yytext + 1, 1, sawspace))
259 } /* end of command line args */
263 if (!fill_args(yytext, yyleng, sawspace))
266 } /* a command line arg */
269 <INITIAL>^#include[[:blank:]]+.*\n {
277 if ((path = parse_include(yytext)) == NULL)
280 LEXTRACE("INCLUDE\n");
282 /* Push current buffer and switch to include file */
283 if (!push_include(path))
287 <INITIAL>^#includedir[[:blank:]]+.*\n {
295 if ((path = parse_include(yytext)) == NULL)
298 LEXTRACE("INCLUDEDIR\n");
301 * Push current buffer and switch to include file.
302 * We simply ignore empty directories.
304 if (!push_includedir(path) && parse_error)
308 <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
317 for (n = 0; isblank((unsigned char)yytext[n]); n++)
319 n += sizeof("Defaults") - 1;
320 if ((deftype = yytext[n++]) != '\0') {
321 while (isblank((unsigned char)yytext[n]))
328 LEXTRACE("DEFAULTS_USER ");
329 LEXRETURN(DEFAULTS_USER);
332 LEXTRACE("DEFAULTS_RUNAS ");
333 LEXRETURN(DEFAULTS_RUNAS);
336 LEXTRACE("DEFAULTS_HOST ");
337 LEXRETURN(DEFAULTS_HOST);
340 LEXTRACE("DEFAULTS_CMND ");
341 LEXRETURN(DEFAULTS_CMND);
343 LEXTRACE("DEFAULTS ");
348 <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
356 for (n = 0; isblank((unsigned char)yytext[n]); n++)
360 LEXTRACE("HOSTALIAS ");
361 LEXRETURN(HOSTALIAS);
363 LEXTRACE("CMNDALIAS ");
364 LEXRETURN(CMNDALIAS);
366 LEXTRACE("USERALIAS ");
367 LEXRETURN(USERALIAS);
369 LEXTRACE("RUNASALIAS ");
370 LEXRETURN(RUNASALIAS);
374 NOPASSWD[[:blank:]]*: {
375 /* cmnd does not require passwd for this user */
376 LEXTRACE("NOPASSWD ");
380 PASSWD[[:blank:]]*: {
381 /* cmnd requires passwd for this user */
386 NOEXEC[[:blank:]]*: {
396 SETENV[[:blank:]]*: {
401 NOSETENV[[:blank:]]*: {
402 LEXTRACE("NOSETENV ");
406 LOG_OUTPUT[[:blank:]]*: {
407 LEXTRACE("LOG_OUTPUT ");
408 LEXRETURN(LOG_OUTPUT);
411 NOLOG_OUTPUT[[:blank:]]*: {
412 LEXTRACE("NOLOG_OUTPUT ");
413 LEXRETURN(NOLOG_OUTPUT);
416 LOG_INPUT[[:blank:]]*: {
417 LEXTRACE("LOG_INPUT ");
418 LEXRETURN(LOG_INPUT);
421 NOLOG_INPUT[[:blank:]]*: {
422 LEXTRACE("NOLOG_INPUT ");
423 LEXRETURN(NOLOG_INPUT);
426 <INITIAL,GOTDEFS>(\+|\%|\%:) {
427 /* empty group or netgroup */
434 if (!fill(yytext, yyleng))
436 LEXTRACE("NETGROUP ");
442 if (!fill(yytext, yyleng))
444 LEXTRACE("USERGROUP ");
445 LEXRETURN(USERGROUP);
448 {IPV4ADDR}(\/{IPV4ADDR})? {
449 if (!fill(yytext, yyleng))
451 LEXTRACE("NTWKADDR ");
455 {IPV4ADDR}\/([12]?[0-9]|3[0-2]) {
456 if (!fill(yytext, yyleng))
458 LEXTRACE("NTWKADDR ");
462 {IPV6ADDR}(\/{IPV6ADDR})? {
463 if (!ipv6_valid(yytext)) {
467 if (!fill(yytext, yyleng))
469 LEXTRACE("NTWKADDR ");
473 {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
474 if (!ipv6_valid(yytext)) {
478 if (!fill(yytext, yyleng))
480 LEXTRACE("NTWKADDR ");
508 [[:upper:]][[:upper:][:digit:]_]* {
512 if (!fill(yytext, yyleng))
518 <GOTDEFS>({PATH}|sudoedit) {
519 /* no command args allowed for Defaults!/path */
520 if (!fill_cmnd(yytext, yyleng))
522 LEXTRACE("COMMAND ");
528 LEXTRACE("COMMAND ");
529 if (!fill_cmnd(yytext, yyleng))
534 /* directories can't have args... */
535 if (yytext[yyleng - 1] == '/') {
536 LEXTRACE("COMMAND ");
537 if (!fill_cmnd(yytext, yyleng))
542 LEXTRACE("COMMAND ");
543 if (!fill_cmnd(yytext, yyleng))
548 <INITIAL,GOTDEFS>\" {
549 LEXTRACE("BEGINSTR ");
550 yylval.string = NULL;
551 prev_state = YY_START;
555 <INITIAL,GOTDEFS>({ID}|{WORD}) {
557 if (!fill(yytext, yyleng))
559 LEXTRACE("WORD(5) ");
591 LEXRETURN('!'); /* return '!' */
596 if (YY_START == INSTR) {
598 LEXRETURN(ERROR); /* line break in string */
605 } /* return newline */
607 <*>[[:blank:]]+ { /* throw away space/tabs */
608 sawspace = true; /* but remember for fill_args */
611 <*>\\[[:blank:]]*\n {
612 sawspace = true; /* remember for fill_args */
615 } /* throw away EOL after \ */
617 <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n {
623 } /* comment, not uid/gid */
631 if (YY_START != INITIAL) {
643 struct path_list *next;
646 struct include_stack {
649 struct path_list *more; /* more files in case of includedir */
655 pl_compare(const void *v1, const void *v2)
657 const struct path_list * const *p1 = v1;
658 const struct path_list * const *p2 = v2;
660 return strcmp((*p1)->path, (*p2)->path);
664 switch_dir(struct include_stack *stack, char *dirpath)
671 struct path_list *pl, *first = NULL;
672 struct path_list **sorted = NULL;
673 debug_decl(switch_dir, SUDO_DEBUG_PARSER)
675 if (!(dir = opendir(dirpath))) {
676 if (errno != ENOENT) {
678 if (asprintf(&errbuf, _("%s: %s"), dirpath, strerror(errno)) != -1) {
682 yyerror(_("unable to allocate memory"));
687 while ((dent = readdir(dir))) {
688 /* Ignore files that end in '~' or have a '.' in them. */
689 if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
690 || strchr(dent->d_name, '.') != NULL) {
693 if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
697 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
702 pl = malloc(sizeof(*pl));
715 /* Sort the list as an array. */
716 sorted = malloc(sizeof(*sorted) * count);
720 for (i = 0; i < count; i++) {
724 qsort(sorted, count, sizeof(*sorted), pl_compare);
726 /* Apply sorting to the list. */
728 sorted[count - 1]->next = NULL;
729 for (i = 1; i < count; i++)
730 sorted[i - 1]->next = sorted[i];
733 /* Pull out the first element for parsing, leave the rest for later. */
744 debug_return_str(path);
746 while (first != NULL) {
755 debug_return_str(NULL);
758 #define MAX_SUDOERS_DEPTH 128
759 #define SUDOERS_STACK_INCREMENT 16
761 static size_t istacksize, idepth;
762 static struct include_stack *istack;
763 static bool keepopen;
768 struct path_list *pl;
769 debug_decl(init_lexer, SUDO_DEBUG_PARSER)
773 while ((pl = istack[idepth].more) != NULL) {
774 istack[idepth].more = pl->next;
778 efree(istack[idepth].path);
779 if (idepth && !istack[idepth].keepopen)
780 fclose(istack[idepth].bs->yy_input_file);
781 yy_delete_buffer(istack[idepth].bs);
785 istacksize = idepth = 0;
790 prev_state = INITIAL;
796 _push_include(char *path, bool isdir)
798 struct path_list *pl;
800 debug_decl(_push_include, SUDO_DEBUG_PARSER)
802 /* push current state onto stack */
803 if (idepth >= istacksize) {
804 if (idepth > MAX_SUDOERS_DEPTH) {
805 yyerror(_("too many levels of includes"));
806 debug_return_bool(false);
808 istacksize += SUDOERS_STACK_INCREMENT;
809 istack = (struct include_stack *) realloc(istack,
810 sizeof(*istack) * istacksize);
811 if (istack == NULL) {
812 yyerror(_("unable to allocate memory"));
813 debug_return_bool(false);
818 switch (sudo_secure_dir(path, sudoers_uid, sudoers_gid, &sb)) {
819 case SUDO_PATH_MISSING:
820 debug_return_bool(false);
821 case SUDO_PATH_BAD_TYPE:
823 if (sudoers_warnings) {
826 debug_return_bool(false);
827 case SUDO_PATH_WRONG_OWNER:
828 if (sudoers_warnings) {
829 warningx(_("%s is owned by uid %u, should be %u"),
830 path, (unsigned int) sb.st_uid,
831 (unsigned int) sudoers_uid);
833 debug_return_bool(false);
834 case SUDO_PATH_WORLD_WRITABLE:
835 if (sudoers_warnings) {
836 warningx(_("%s is world writable"), path);
838 debug_return_bool(false);
839 case SUDO_PATH_GROUP_WRITABLE:
840 if (sudoers_warnings) {
841 warningx(_("%s is owned by gid %u, should be %u"),
842 path, (unsigned int) sb.st_gid,
843 (unsigned int) sudoers_gid);
845 debug_return_bool(false);
848 debug_return_bool(false);
850 if (!(path = switch_dir(&istack[idepth], path))) {
851 /* switch_dir() called yyerror() for us */
852 debug_return_bool(false);
854 while ((fp = open_sudoers(path, false, &keepopen)) == NULL) {
855 /* Unable to open path in includedir, go to next one, if any. */
857 if ((pl = istack[idepth].more) == NULL)
858 debug_return_bool(false);
860 istack[idepth].more = pl->next;
864 if ((fp = open_sudoers(path, true, &keepopen)) == NULL) {
866 if (asprintf(&errbuf, _("%s: %s"), path, strerror(errno)) != -1) {
870 yyerror(_("unable to allocate memory"));
872 debug_return_bool(false);
874 istack[idepth].more = NULL;
876 /* Push the old (current) file and open the new one. */
877 istack[idepth].path = sudoers; /* push old path */
878 istack[idepth].bs = YY_CURRENT_BUFFER;
879 istack[idepth].lineno = sudolineno;
880 istack[idepth].keepopen = keepopen;
884 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
886 debug_return_bool(true);
892 struct path_list *pl;
894 debug_decl(pop_include, SUDO_DEBUG_PARSER)
897 debug_return_bool(false);
900 fclose(YY_CURRENT_BUFFER->yy_input_file);
901 yy_delete_buffer(YY_CURRENT_BUFFER);
902 /* If we are in an include dir, move to the next file. */
903 while ((pl = istack[idepth - 1].more) != NULL) {
904 fp = open_sudoers(pl->path, false, &keepopen);
906 istack[idepth - 1].more = pl->next;
910 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
914 /* Unable to open path in include dir, go to next one. */
915 istack[idepth - 1].more = pl->next;
919 /* If no path list, just pop the last dir on the stack. */
922 yy_switch_to_buffer(istack[idepth].bs);
924 sudoers = istack[idepth].path;
925 sudolineno = istack[idepth].lineno;
926 keepopen = istack[idepth].keepopen;
928 debug_return_bool(true);
932 parse_include(char *base)
934 char *cp, *ep, *path, *pp;
935 int dirlen = 0, len = 0, subst = 0;
936 size_t shost_len = 0;
937 debug_decl(parse_include, SUDO_DEBUG_PARSER)
939 /* Pull out path from #include line. */
940 cp = base + sizeof("#include");
942 cp += 3; /* includedir */
943 while (isblank((unsigned char) *cp))
946 while (*ep != '\0' && !isspace((unsigned char) *ep)) {
947 if (ep[0] == '%' && ep[1] == 'h') {
948 shost_len = strlen(user_shost);
949 len += shost_len - 2;
955 /* Relative paths are located in the same dir as the sudoers file. */
957 char *dirend = strrchr(sudoers, '/');
959 dirlen = (int)(dirend - sudoers) + 1;
962 /* Make a copy of the fully-qualified path and return it. */
963 len += (int)(ep - cp);
964 path = pp = malloc(len + dirlen + 1);
966 yyerror(_("unable to allocate memory"));
967 debug_return_str(NULL);
970 memcpy(path, sudoers, dirlen);
974 /* substitute for %h */
976 if (cp[0] == '%' && cp[1] == 'h') {
977 memcpy(pp, user_shost, shost_len);
990 /* Push any excess characters (e.g. comment, newline) back to the lexer */
992 yyless((int)(ep - base));
994 debug_return_str(path);
999 sudoers_trace_print(const char *msg)
1001 return fputs(msg, stderr);
1005 sudoers_trace_print(const char *msg)
1007 static bool initialized;
1008 static struct lbuf lbuf;
1012 lbuf_init(&lbuf, NULL, 0, NULL, 0);
1015 lbuf_append(&lbuf, "%s", msg);
1016 /* XXX - assumes a final newline */
1017 if (strchr(msg, '\n') != NULL)
1019 sudo_debug_printf2(NULL, NULL, 0, SUDO_DEBUG_PARSER|SUDO_DEBUG_DEBUG,
1020 "%s:%d %s", sudoers, sudolineno, lbuf.buf);
1025 #endif /* TRACELEXER */