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 int (*trace_print)(const char *msg) = sudoers_trace_print;
99 #define LEXRETURN(n) do { \
104 #define ECHO ignore_result(fwrite(yytext, yyleng, 1, yyout))
106 #define push_include(_p) (_push_include((_p), false))
107 #define push_includedir(_p) (_push_include((_p), true))
110 HEX16 [0-9A-Fa-f]{1,4}
111 OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
112 IPV4ADDR {OCTET}(\.{OCTET}){3}
113 IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
115 HOSTNAME [[:alnum:]_-]+
116 WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
118 PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
119 ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
133 <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
138 <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
140 <STARTDEFS>{DEFVAR} {
143 if (!fill(yytext, yyleng))
171 LEXTRACE("BEGINSTR ");
172 yylval.string = NULL;
173 prev_state = YY_START;
178 LEXTRACE("WORD(2) ");
179 if (!fill(yytext, yyleng))
186 \\[[:blank:]]*\n[[:blank:]]* {
187 /* Line continuation char followed by newline. */
196 if (yylval.string == NULL) {
197 LEXTRACE("ERROR "); /* empty string */
200 if (prev_state == INITIAL) {
201 switch (yylval.string[0]) {
203 if (yylval.string[1] == '\0' ||
204 (yylval.string[1] == ':' &&
205 yylval.string[2] == '\0')) {
206 LEXTRACE("ERROR "); /* empty group */
209 LEXTRACE("USERGROUP ");
210 LEXRETURN(USERGROUP);
212 if (yylval.string[1] == '\0') {
213 LEXTRACE("ERROR "); /* empty netgroup */
216 LEXTRACE("NETGROUP ");
220 LEXTRACE("WORD(4) ");
225 LEXTRACE("BACKSLASH ");
226 if (!append(yytext, yyleng))
231 LEXTRACE("STRBODY ");
232 if (!append(yytext, yyleng))
239 /* quoted fnmatch glob char, pass verbatim */
240 LEXTRACE("QUOTEDCHAR ");
241 if (!fill_args(yytext, 2, sawspace))
247 /* quoted sudoers special char, strip backslash */
248 LEXTRACE("QUOTEDCHAR ");
249 if (!fill_args(yytext + 1, 1, sawspace))
258 } /* end of command line args */
262 if (!fill_args(yytext, yyleng, sawspace))
265 } /* a command line arg */
268 <INITIAL>^#include[[:blank:]]+.*\n {
276 if ((path = parse_include(yytext)) == NULL)
279 LEXTRACE("INCLUDE\n");
281 /* Push current buffer and switch to include file */
282 if (!push_include(path))
286 <INITIAL>^#includedir[[:blank:]]+.*\n {
294 if ((path = parse_include(yytext)) == NULL)
297 LEXTRACE("INCLUDEDIR\n");
300 * Push current buffer and switch to include file.
301 * We simply ignore empty directories.
303 if (!push_includedir(path) && parse_error)
307 <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
316 for (n = 0; isblank((unsigned char)yytext[n]); n++)
318 n += sizeof("Defaults") - 1;
319 if ((deftype = yytext[n++]) != '\0') {
320 while (isblank((unsigned char)yytext[n]))
327 LEXTRACE("DEFAULTS_USER ");
328 LEXRETURN(DEFAULTS_USER);
331 LEXTRACE("DEFAULTS_RUNAS ");
332 LEXRETURN(DEFAULTS_RUNAS);
335 LEXTRACE("DEFAULTS_HOST ");
336 LEXRETURN(DEFAULTS_HOST);
339 LEXTRACE("DEFAULTS_CMND ");
340 LEXRETURN(DEFAULTS_CMND);
342 LEXTRACE("DEFAULTS ");
347 <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
355 for (n = 0; isblank((unsigned char)yytext[n]); n++)
359 LEXTRACE("HOSTALIAS ");
360 LEXRETURN(HOSTALIAS);
362 LEXTRACE("CMNDALIAS ");
363 LEXRETURN(CMNDALIAS);
365 LEXTRACE("USERALIAS ");
366 LEXRETURN(USERALIAS);
368 LEXTRACE("RUNASALIAS ");
369 LEXRETURN(RUNASALIAS);
373 NOPASSWD[[:blank:]]*: {
374 /* cmnd does not require passwd for this user */
375 LEXTRACE("NOPASSWD ");
379 PASSWD[[:blank:]]*: {
380 /* cmnd requires passwd for this user */
385 NOEXEC[[:blank:]]*: {
395 SETENV[[:blank:]]*: {
400 NOSETENV[[:blank:]]*: {
401 LEXTRACE("NOSETENV ");
405 LOG_OUTPUT[[:blank:]]*: {
406 LEXTRACE("LOG_OUTPUT ");
407 LEXRETURN(LOG_OUTPUT);
410 NOLOG_OUTPUT[[:blank:]]*: {
411 LEXTRACE("NOLOG_OUTPUT ");
412 LEXRETURN(NOLOG_OUTPUT);
415 LOG_INPUT[[:blank:]]*: {
416 LEXTRACE("LOG_INPUT ");
417 LEXRETURN(LOG_INPUT);
420 NOLOG_INPUT[[:blank:]]*: {
421 LEXTRACE("NOLOG_INPUT ");
422 LEXRETURN(NOLOG_INPUT);
425 <INITIAL,GOTDEFS>(\+|\%|\%:) {
426 /* empty group or netgroup */
433 if (!fill(yytext, yyleng))
435 LEXTRACE("NETGROUP ");
441 if (!fill(yytext, yyleng))
443 LEXTRACE("USERGROUP ");
444 LEXRETURN(USERGROUP);
447 {IPV4ADDR}(\/{IPV4ADDR})? {
448 if (!fill(yytext, yyleng))
450 LEXTRACE("NTWKADDR ");
454 {IPV4ADDR}\/([12]?[0-9]|3[0-2]) {
455 if (!fill(yytext, yyleng))
457 LEXTRACE("NTWKADDR ");
461 {IPV6ADDR}(\/{IPV6ADDR})? {
462 if (!ipv6_valid(yytext)) {
466 if (!fill(yytext, yyleng))
468 LEXTRACE("NTWKADDR ");
472 {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
473 if (!ipv6_valid(yytext)) {
477 if (!fill(yytext, yyleng))
479 LEXTRACE("NTWKADDR ");
515 <INITIAL>LIMITPRIVS {
517 LEXTRACE("LIMITPRIVS ");
518 LEXRETURN(LIMITPRIVS);
524 [[:upper:]][[:upper:][:digit:]_]* {
526 if (!fill(yytext, yyleng))
532 <GOTDEFS>({PATH}|sudoedit) {
533 /* no command args allowed for Defaults!/path */
534 if (!fill_cmnd(yytext, yyleng))
536 LEXTRACE("COMMAND ");
542 LEXTRACE("COMMAND ");
543 if (!fill_cmnd(yytext, yyleng))
548 /* directories can't have args... */
549 if (yytext[yyleng - 1] == '/') {
550 LEXTRACE("COMMAND ");
551 if (!fill_cmnd(yytext, yyleng))
556 LEXTRACE("COMMAND ");
557 if (!fill_cmnd(yytext, yyleng))
562 <INITIAL,GOTDEFS>\" {
563 LEXTRACE("BEGINSTR ");
564 yylval.string = NULL;
565 prev_state = YY_START;
569 <INITIAL,GOTDEFS>({ID}|{WORD}) {
571 if (!fill(yytext, yyleng))
573 LEXTRACE("WORD(5) ");
605 LEXRETURN('!'); /* return '!' */
610 if (YY_START == INSTR) {
612 LEXRETURN(ERROR); /* line break in string */
619 } /* return newline */
621 <*>[[:blank:]]+ { /* throw away space/tabs */
622 sawspace = true; /* but remember for fill_args */
625 <*>\\[[:blank:]]*\n {
626 sawspace = true; /* remember for fill_args */
629 } /* throw away EOL after \ */
631 <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n {
637 } /* comment, not uid/gid */
645 if (YY_START != INITIAL) {
657 struct path_list *next;
660 struct include_stack {
663 struct path_list *more; /* more files in case of includedir */
669 pl_compare(const void *v1, const void *v2)
671 const struct path_list * const *p1 = v1;
672 const struct path_list * const *p2 = v2;
674 return strcmp((*p1)->path, (*p2)->path);
678 switch_dir(struct include_stack *stack, char *dirpath)
685 struct path_list *pl, *first = NULL;
686 struct path_list **sorted = NULL;
687 debug_decl(switch_dir, SUDO_DEBUG_PARSER)
689 if (!(dir = opendir(dirpath))) {
690 if (errno != ENOENT) {
692 if (asprintf(&errbuf, _("%s: %s"), dirpath, strerror(errno)) != -1) {
696 yyerror(_("unable to allocate memory"));
701 while ((dent = readdir(dir))) {
702 /* Ignore files that end in '~' or have a '.' in them. */
703 if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
704 || strchr(dent->d_name, '.') != NULL) {
707 if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
711 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
716 pl = malloc(sizeof(*pl));
729 /* Sort the list as an array. */
730 sorted = malloc(sizeof(*sorted) * count);
734 for (i = 0; i < count; i++) {
738 qsort(sorted, count, sizeof(*sorted), pl_compare);
740 /* Apply sorting to the list. */
742 sorted[count - 1]->next = NULL;
743 for (i = 1; i < count; i++)
744 sorted[i - 1]->next = sorted[i];
747 /* Pull out the first element for parsing, leave the rest for later. */
758 debug_return_str(path);
760 while (first != NULL) {
769 debug_return_str(NULL);
772 #define MAX_SUDOERS_DEPTH 128
773 #define SUDOERS_STACK_INCREMENT 16
775 static size_t istacksize, idepth;
776 static struct include_stack *istack;
777 static bool keepopen;
782 struct path_list *pl;
783 debug_decl(init_lexer, SUDO_DEBUG_PARSER)
787 while ((pl = istack[idepth].more) != NULL) {
788 istack[idepth].more = pl->next;
792 efree(istack[idepth].path);
793 if (idepth && !istack[idepth].keepopen)
794 fclose(istack[idepth].bs->yy_input_file);
795 yy_delete_buffer(istack[idepth].bs);
799 istacksize = idepth = 0;
804 prev_state = INITIAL;
810 _push_include(char *path, bool isdir)
812 struct path_list *pl;
814 debug_decl(_push_include, SUDO_DEBUG_PARSER)
816 /* push current state onto stack */
817 if (idepth >= istacksize) {
818 if (idepth > MAX_SUDOERS_DEPTH) {
819 yyerror(_("too many levels of includes"));
820 debug_return_bool(false);
822 istacksize += SUDOERS_STACK_INCREMENT;
823 istack = (struct include_stack *) realloc(istack,
824 sizeof(*istack) * istacksize);
825 if (istack == NULL) {
826 yyerror(_("unable to allocate memory"));
827 debug_return_bool(false);
832 switch (sudo_secure_dir(path, sudoers_uid, sudoers_gid, &sb)) {
833 case SUDO_PATH_SECURE:
835 case SUDO_PATH_MISSING:
836 debug_return_bool(false);
837 case SUDO_PATH_BAD_TYPE:
839 if (sudoers_warnings) {
842 debug_return_bool(false);
843 case SUDO_PATH_WRONG_OWNER:
844 if (sudoers_warnings) {
845 warningx(_("%s is owned by uid %u, should be %u"),
846 path, (unsigned int) sb.st_uid,
847 (unsigned int) sudoers_uid);
849 debug_return_bool(false);
850 case SUDO_PATH_WORLD_WRITABLE:
851 if (sudoers_warnings) {
852 warningx(_("%s is world writable"), path);
854 debug_return_bool(false);
855 case SUDO_PATH_GROUP_WRITABLE:
856 if (sudoers_warnings) {
857 warningx(_("%s is owned by gid %u, should be %u"),
858 path, (unsigned int) sb.st_gid,
859 (unsigned int) sudoers_gid);
861 debug_return_bool(false);
864 debug_return_bool(false);
866 if (!(path = switch_dir(&istack[idepth], path))) {
867 /* switch_dir() called yyerror() for us */
868 debug_return_bool(false);
870 while ((fp = open_sudoers(path, false, &keepopen)) == NULL) {
871 /* Unable to open path in includedir, go to next one, if any. */
873 if ((pl = istack[idepth].more) == NULL)
874 debug_return_bool(false);
876 istack[idepth].more = pl->next;
880 if ((fp = open_sudoers(path, true, &keepopen)) == NULL) {
881 /* The error was already printed by open_sudoers() */
883 debug_return_bool(false);
885 istack[idepth].more = NULL;
887 /* Push the old (current) file and open the new one. */
888 istack[idepth].path = sudoers; /* push old path */
889 istack[idepth].bs = YY_CURRENT_BUFFER;
890 istack[idepth].lineno = sudolineno;
891 istack[idepth].keepopen = keepopen;
895 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
897 debug_return_bool(true);
903 struct path_list *pl;
905 debug_decl(pop_include, SUDO_DEBUG_PARSER)
908 debug_return_bool(false);
911 fclose(YY_CURRENT_BUFFER->yy_input_file);
912 yy_delete_buffer(YY_CURRENT_BUFFER);
913 /* If we are in an include dir, move to the next file. */
914 while ((pl = istack[idepth - 1].more) != NULL) {
915 fp = open_sudoers(pl->path, false, &keepopen);
917 istack[idepth - 1].more = pl->next;
921 yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
925 /* Unable to open path in include dir, go to next one. */
926 istack[idepth - 1].more = pl->next;
930 /* If no path list, just pop the last dir on the stack. */
933 yy_switch_to_buffer(istack[idepth].bs);
935 sudoers = istack[idepth].path;
936 sudolineno = istack[idepth].lineno;
937 keepopen = istack[idepth].keepopen;
939 debug_return_bool(true);
943 parse_include(char *base)
945 char *cp, *ep, *path, *pp;
946 int dirlen = 0, len = 0, subst = 0;
947 size_t shost_len = 0;
948 debug_decl(parse_include, SUDO_DEBUG_PARSER)
950 /* Pull out path from #include line. */
951 cp = base + sizeof("#include");
953 cp += 3; /* includedir */
954 while (isblank((unsigned char) *cp))
957 while (*ep != '\0' && !isspace((unsigned char) *ep)) {
958 if (ep[0] == '%' && ep[1] == 'h') {
959 shost_len = strlen(user_shost);
960 len += shost_len - 2;
966 /* Relative paths are located in the same dir as the sudoers file. */
968 char *dirend = strrchr(sudoers, '/');
970 dirlen = (int)(dirend - sudoers) + 1;
973 /* Make a copy of the fully-qualified path and return it. */
974 len += (int)(ep - cp);
975 path = pp = malloc(len + dirlen + 1);
977 yyerror(_("unable to allocate memory"));
978 debug_return_str(NULL);
981 memcpy(path, sudoers, dirlen);
985 /* substitute for %h */
987 if (cp[0] == '%' && cp[1] == 'h') {
988 memcpy(pp, user_shost, shost_len);
1001 /* Push any excess characters (e.g. comment, newline) back to the lexer */
1003 yyless((int)(ep - base));
1005 debug_return_str(path);
1010 sudoers_trace_print(const char *msg)
1012 return fputs(msg, stderr);
1016 sudoers_trace_print(const char *msg)
1018 static bool initialized;
1019 static struct lbuf lbuf;
1023 lbuf_init(&lbuf, NULL, 0, NULL, 0);
1026 lbuf_append(&lbuf, "%s", msg);
1027 /* XXX - assumes a final newline */
1028 if (strchr(msg, '\n') != NULL)
1030 sudo_debug_printf2(NULL, NULL, 0, SUDO_DEBUG_PARSER|SUDO_DEBUG_DEBUG,
1031 "%s:%d %s", sudoers, sudolineno, lbuf.buf);
1036 #endif /* TRACELEXER */