3 * Copyright (c) 1996, 1998-2005, 2007-2013
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>
38 #endif /* STDC_HEADERS */
41 #endif /* HAVE_STRING_H */
44 #endif /* HAVE_STRINGS_H */
45 #if defined(HAVE_STDINT_H)
47 #elif defined(HAVE_INTTYPES_H)
48 # include <inttypes.h>
52 #endif /* HAVE_UNISTD_H */
53 #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
55 #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
58 # define NAMLEN(dirent) strlen((dirent)->d_name)
60 # define dirent direct
61 # define NAMLEN(dirent) (dirent)->d_namlen
62 # ifdef HAVE_SYS_NDIR_H
63 # include <sys/ndir.h>
65 # ifdef HAVE_SYS_DIR_H
80 #include "secure_path.h"
82 extern YYSTYPE sudoerslval;
83 extern bool parse_error;
84 extern bool sudoers_warnings;
89 /* Default sudoers path, mode and owner (may be set via sudo.conf) */
90 const char *sudoers_file = _PATH_SUDOERS;
91 mode_t sudoers_mode = SUDOERS_MODE;
92 uid_t sudoers_uid = SUDOERS_UID;
93 gid_t sudoers_gid = SUDOERS_GID;
95 static bool continued, sawspace;
96 static int prev_state;
97 static int digest_len;
99 static bool _push_include(char *, bool);
100 static bool pop_include(void);
101 static char *parse_include(char *);
103 int (*trace_print)(const char *msg) = sudoers_trace_print;
105 #define LEXRETURN(n) do { \
110 #define ECHO ignore_result(fwrite(sudoerstext, sudoersleng, 1, sudoersout))
112 #define push_include(_p) (_push_include((_p), false))
113 #define push_includedir(_p) (_push_include((_p), true))
116 HEX16 [0-9A-Fa-f]{1,4}
117 OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
118 IPV4ADDR {OCTET}(\.{OCTET}){3}
119 IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
121 HOSTNAME [[:alnum:]_-]+
122 WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
124 PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
125 ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
131 %option prefix="sudoers"
141 <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
146 <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
148 <STARTDEFS>{DEFVAR} {
151 if (!fill(sudoerstext, sudoersleng))
179 LEXTRACE("BEGINSTR ");
180 sudoerslval.string = NULL;
181 prev_state = YY_START;
186 LEXTRACE("WORD(2) ");
187 if (!fill(sudoerstext, sudoersleng))
194 \\[[:blank:]]*\n[[:blank:]]* {
195 /* Line continuation char followed by newline. */
204 if (sudoerslval.string == NULL) {
205 LEXTRACE("ERROR "); /* empty string */
208 if (prev_state == INITIAL) {
209 switch (sudoerslval.string[0]) {
211 if (sudoerslval.string[1] == '\0' ||
212 (sudoerslval.string[1] == ':' &&
213 sudoerslval.string[2] == '\0')) {
214 LEXTRACE("ERROR "); /* empty group */
217 LEXTRACE("USERGROUP ");
218 LEXRETURN(USERGROUP);
220 if (sudoerslval.string[1] == '\0') {
221 LEXTRACE("ERROR "); /* empty netgroup */
224 LEXTRACE("NETGROUP ");
228 LEXTRACE("WORD(4) ");
233 LEXTRACE("BACKSLASH ");
234 if (!append(sudoerstext, sudoersleng))
239 LEXTRACE("STRBODY ");
240 if (!append(sudoerstext, sudoersleng))
247 /* quoted fnmatch glob char, pass verbatim */
248 LEXTRACE("QUOTEDCHAR ");
249 if (!fill_args(sudoerstext, 2, sawspace))
255 /* quoted sudoers special char, strip backslash */
256 LEXTRACE("QUOTEDCHAR ");
257 if (!fill_args(sudoerstext + 1, 1, sawspace))
266 } /* end of command line args */
270 if (!fill_args(sudoerstext, sudoersleng, sawspace))
273 } /* a command line arg */
276 <WANTDIGEST>[[:xdigit:]]+ {
277 /* Only return DIGEST if the length is correct. */
278 if (sudoersleng == digest_len * 2) {
279 if (!fill(sudoerstext, sudoersleng))
289 <WANTDIGEST>[A-Za-z0-9\+/=]+ {
290 /* Only return DIGEST if the length is correct. */
292 if (sudoerstext[sudoersleng - 1] == '=') {
294 len = 4 * ((digest_len + 2) / 3);
297 len = (4 * digest_len + 2) / 3;
299 if (sudoersleng == len) {
300 if (!fill(sudoerstext, sudoersleng))
308 } /* base64 digest */
310 <INITIAL>^#include[[:blank:]]+.*\n {
318 if ((path = parse_include(sudoerstext)) == NULL)
321 LEXTRACE("INCLUDE\n");
323 /* Push current buffer and switch to include file */
324 if (!push_include(path))
328 <INITIAL>^#includedir[[:blank:]]+.*\n {
336 if ((path = parse_include(sudoerstext)) == NULL)
339 LEXTRACE("INCLUDEDIR\n");
342 * Push current buffer and switch to include file.
343 * We simply ignore empty directories.
345 if (!push_includedir(path) && parse_error)
349 <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
358 for (n = 0; isblank((unsigned char)sudoerstext[n]); n++)
360 n += sizeof("Defaults") - 1;
361 if ((deftype = sudoerstext[n++]) != '\0') {
362 while (isblank((unsigned char)sudoerstext[n]))
369 LEXTRACE("DEFAULTS_USER ");
370 LEXRETURN(DEFAULTS_USER);
373 LEXTRACE("DEFAULTS_RUNAS ");
374 LEXRETURN(DEFAULTS_RUNAS);
377 LEXTRACE("DEFAULTS_HOST ");
378 LEXRETURN(DEFAULTS_HOST);
381 LEXTRACE("DEFAULTS_CMND ");
382 LEXRETURN(DEFAULTS_CMND);
384 LEXTRACE("DEFAULTS ");
389 <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
397 for (n = 0; isblank((unsigned char)sudoerstext[n]); n++)
399 switch (sudoerstext[n]) {
401 LEXTRACE("HOSTALIAS ");
402 LEXRETURN(HOSTALIAS);
404 LEXTRACE("CMNDALIAS ");
405 LEXRETURN(CMNDALIAS);
407 LEXTRACE("USERALIAS ");
408 LEXRETURN(USERALIAS);
410 LEXTRACE("RUNASALIAS ");
411 LEXRETURN(RUNASALIAS);
415 NOPASSWD[[:blank:]]*: {
416 /* cmnd does not require passwd for this user */
417 LEXTRACE("NOPASSWD ");
421 PASSWD[[:blank:]]*: {
422 /* cmnd requires passwd for this user */
427 NOEXEC[[:blank:]]*: {
437 SETENV[[:blank:]]*: {
442 NOSETENV[[:blank:]]*: {
443 LEXTRACE("NOSETENV ");
447 LOG_OUTPUT[[:blank:]]*: {
448 LEXTRACE("LOG_OUTPUT ");
449 LEXRETURN(LOG_OUTPUT);
452 NOLOG_OUTPUT[[:blank:]]*: {
453 LEXTRACE("NOLOG_OUTPUT ");
454 LEXRETURN(NOLOG_OUTPUT);
457 LOG_INPUT[[:blank:]]*: {
458 LEXTRACE("LOG_INPUT ");
459 LEXRETURN(LOG_INPUT);
462 NOLOG_INPUT[[:blank:]]*: {
463 LEXTRACE("NOLOG_INPUT ");
464 LEXRETURN(NOLOG_INPUT);
467 <INITIAL,GOTDEFS>(\+|\%|\%:) {
468 /* empty group or netgroup */
475 if (!fill(sudoerstext, sudoersleng))
477 LEXTRACE("NETGROUP ");
483 if (!fill(sudoerstext, sudoersleng))
485 LEXTRACE("USERGROUP ");
486 LEXRETURN(USERGROUP);
489 {IPV4ADDR}(\/{IPV4ADDR})? {
490 if (!fill(sudoerstext, sudoersleng))
492 LEXTRACE("NTWKADDR ");
496 {IPV4ADDR}\/([12]?[0-9]|3[0-2]) {
497 if (!fill(sudoerstext, sudoersleng))
499 LEXTRACE("NTWKADDR ");
503 {IPV6ADDR}(\/{IPV6ADDR})? {
504 if (!ipv6_valid(sudoerstext)) {
508 if (!fill(sudoerstext, sudoersleng))
510 LEXTRACE("NTWKADDR ");
514 {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
515 if (!ipv6_valid(sudoerstext)) {
519 if (!fill(sudoerstext, sudoersleng))
521 LEXTRACE("NTWKADDR ");
557 <INITIAL>LIMITPRIVS {
559 LEXTRACE("LIMITPRIVS ");
560 LEXRETURN(LIMITPRIVS);
566 [[:upper:]][[:upper:][:digit:]_]* {
568 if (!fill(sudoerstext, sudoersleng))
574 <GOTDEFS>({PATH}|sudoedit) {
575 /* XXX - no way to specify digest for command */
576 /* no command args allowed for Defaults!/path */
577 if (!fill_cmnd(sudoerstext, sudoersleng))
579 LEXTRACE("COMMAND ");
584 digest_len = SHA224_DIGEST_LENGTH;
591 digest_len = SHA256_DIGEST_LENGTH;
598 digest_len = SHA384_DIGEST_LENGTH;
605 digest_len = SHA512_DIGEST_LENGTH;
613 LEXTRACE("COMMAND ");
614 if (!fill_cmnd(sudoerstext, sudoersleng))
619 /* directories can't have args... */
620 if (sudoerstext[sudoersleng - 1] == '/') {
621 LEXTRACE("COMMAND ");
622 if (!fill_cmnd(sudoerstext, sudoersleng))
627 LEXTRACE("COMMAND ");
628 if (!fill_cmnd(sudoerstext, sudoersleng))
633 <INITIAL,GOTDEFS>\" {
634 LEXTRACE("BEGINSTR ");
635 sudoerslval.string = NULL;
636 prev_state = YY_START;
640 <INITIAL,GOTDEFS>({ID}|{WORD}) {
642 if (!fill(sudoerstext, sudoersleng))
644 LEXTRACE("WORD(5) ");
674 if (sudoersleng & 1) {
676 LEXRETURN('!'); /* return '!' */
681 if (YY_START == INSTR) {
683 LEXRETURN(ERROR); /* line break in string */
690 } /* return newline */
692 <*>[[:blank:]]+ { /* throw away space/tabs */
693 sawspace = true; /* but remember for fill_args */
696 <*>\\[[:blank:]]*\n {
697 sawspace = true; /* remember for fill_args */
700 } /* throw away EOL after \ */
702 <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n {
708 } /* comment, not uid/gid */
716 if (YY_START != INITIAL) {
728 struct path_list *next;
731 struct include_stack {
734 struct path_list *more; /* more files in case of includedir */
740 pl_compare(const void *v1, const void *v2)
742 const struct path_list * const *p1 = v1;
743 const struct path_list * const *p2 = v2;
745 return strcmp((*p1)->path, (*p2)->path);
749 switch_dir(struct include_stack *stack, char *dirpath)
756 struct path_list *pl, *first = NULL;
757 struct path_list **sorted = NULL;
758 debug_decl(switch_dir, SUDO_DEBUG_PARSER)
760 if (!(dir = opendir(dirpath))) {
761 if (errno != ENOENT) {
762 warning("%s", dirpath);
767 while ((dent = readdir(dir))) {
768 /* Ignore files that end in '~' or have a '.' in them. */
769 if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
770 || strchr(dent->d_name, '.') != NULL) {
773 if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
777 if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
782 pl = malloc(sizeof(*pl));
796 /* Sort the list as an array. */
797 sorted = malloc(sizeof(*sorted) * count);
801 for (i = 0; i < count; i++) {
805 qsort(sorted, count, sizeof(*sorted), pl_compare);
807 /* Apply sorting to the list. */
809 sorted[count - 1]->next = NULL;
810 for (i = 1; i < count; i++)
811 sorted[i - 1]->next = sorted[i];
814 /* Pull out the first element for parsing, leave the rest for later. */
825 debug_return_str(path);
827 while (first != NULL) {
836 debug_return_str(NULL);
839 #define MAX_SUDOERS_DEPTH 128
840 #define SUDOERS_STACK_INCREMENT 16
842 static size_t istacksize, idepth;
843 static struct include_stack *istack;
844 static bool keepopen;
849 struct path_list *pl;
850 debug_decl(init_lexer, SUDO_DEBUG_PARSER)
854 while ((pl = istack[idepth].more) != NULL) {
855 istack[idepth].more = pl->next;
859 efree(istack[idepth].path);
860 if (idepth && !istack[idepth].keepopen)
861 fclose(istack[idepth].bs->yy_input_file);
862 sudoers_delete_buffer(istack[idepth].bs);
866 istacksize = idepth = 0;
871 prev_state = INITIAL;
877 _push_include(char *path, bool isdir)
879 struct path_list *pl;
881 debug_decl(_push_include, SUDO_DEBUG_PARSER)
883 /* push current state onto stack */
884 if (idepth >= istacksize) {
885 if (idepth > MAX_SUDOERS_DEPTH) {
886 sudoerserror(N_("too many levels of includes"));
887 debug_return_bool(false);
889 istacksize += SUDOERS_STACK_INCREMENT;
890 istack = (struct include_stack *) realloc(istack,
891 sizeof(*istack) * istacksize);
892 if (istack == NULL) {
895 debug_return_bool(false);
900 switch (sudo_secure_dir(path, sudoers_uid, sudoers_gid, &sb)) {
901 case SUDO_PATH_SECURE:
903 case SUDO_PATH_MISSING:
904 debug_return_bool(false);
905 case SUDO_PATH_BAD_TYPE:
907 if (sudoers_warnings) {
910 debug_return_bool(false);
911 case SUDO_PATH_WRONG_OWNER:
912 if (sudoers_warnings) {
913 warningx(_("%s is owned by uid %u, should be %u"),
914 path, (unsigned int) sb.st_uid,
915 (unsigned int) sudoers_uid);
917 debug_return_bool(false);
918 case SUDO_PATH_WORLD_WRITABLE:
919 if (sudoers_warnings) {
920 warningx(_("%s is world writable"), path);
922 debug_return_bool(false);
923 case SUDO_PATH_GROUP_WRITABLE:
924 if (sudoers_warnings) {
925 warningx(_("%s is owned by gid %u, should be %u"),
926 path, (unsigned int) sb.st_gid,
927 (unsigned int) sudoers_gid);
929 debug_return_bool(false);
932 debug_return_bool(false);
934 if (!(path = switch_dir(&istack[idepth], path))) {
935 /* switch_dir() called sudoerserror() for us */
936 debug_return_bool(false);
938 while ((fp = open_sudoers(path, false, &keepopen)) == NULL) {
939 /* Unable to open path in includedir, go to next one, if any. */
941 if ((pl = istack[idepth].more) == NULL)
942 debug_return_bool(false);
944 istack[idepth].more = pl->next;
948 if ((fp = open_sudoers(path, true, &keepopen)) == NULL) {
949 /* The error was already printed by open_sudoers() */
951 debug_return_bool(false);
953 istack[idepth].more = NULL;
955 /* Push the old (current) file and open the new one. */
956 istack[idepth].path = sudoers; /* push old path */
957 istack[idepth].bs = YY_CURRENT_BUFFER;
958 istack[idepth].lineno = sudolineno;
959 istack[idepth].keepopen = keepopen;
963 sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE));
965 debug_return_bool(true);
971 struct path_list *pl;
973 debug_decl(pop_include, SUDO_DEBUG_PARSER)
976 debug_return_bool(false);
979 fclose(YY_CURRENT_BUFFER->yy_input_file);
980 sudoers_delete_buffer(YY_CURRENT_BUFFER);
981 /* If we are in an include dir, move to the next file. */
982 while ((pl = istack[idepth - 1].more) != NULL) {
983 fp = open_sudoers(pl->path, false, &keepopen);
985 istack[idepth - 1].more = pl->next;
989 sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE));
993 /* Unable to open path in include dir, go to next one. */
994 istack[idepth - 1].more = pl->next;
998 /* If no path list, just pop the last dir on the stack. */
1001 sudoers_switch_to_buffer(istack[idepth].bs);
1003 sudoers = istack[idepth].path;
1004 sudolineno = istack[idepth].lineno;
1005 keepopen = istack[idepth].keepopen;
1007 debug_return_bool(true);
1011 parse_include(char *base)
1013 char *cp, *ep, *path, *pp;
1014 int dirlen = 0, len = 0, subst = 0;
1015 size_t shost_len = 0;
1016 debug_decl(parse_include, SUDO_DEBUG_PARSER)
1018 /* Pull out path from #include line. */
1019 cp = base + sizeof("#include");
1021 cp += 3; /* includedir */
1022 while (isblank((unsigned char) *cp))
1025 while (*ep != '\0' && !isspace((unsigned char) *ep)) {
1026 if (ep[0] == '%' && ep[1] == 'h') {
1027 shost_len = strlen(user_shost);
1028 len += shost_len - 2;
1034 /* Relative paths are located in the same dir as the sudoers file. */
1036 char *dirend = strrchr(sudoers, '/');
1038 dirlen = (int)(dirend - sudoers) + 1;
1041 /* Make a copy of the fully-qualified path and return it. */
1042 len += (int)(ep - cp);
1043 path = pp = malloc(len + dirlen + 1);
1047 debug_return_str(NULL);
1050 memcpy(path, sudoers, dirlen);
1054 /* substitute for %h */
1056 if (cp[0] == '%' && cp[1] == 'h') {
1057 memcpy(pp, user_shost, shost_len);
1066 memcpy(pp, cp, len);
1070 /* Push any excess characters (e.g. comment, newline) back to the lexer */
1072 yyless((int)(ep - base));
1074 debug_return_str(path);
1079 sudoers_trace_print(const char *msg)
1081 return fputs(msg, stderr);
1085 sudoers_trace_print(const char *msg)
1087 static bool initialized;
1088 static struct lbuf lbuf;
1092 lbuf_init(&lbuf, NULL, 0, NULL, 0);
1095 lbuf_append(&lbuf, "%s", msg);
1096 /* XXX - assumes a final newline */
1097 if (strchr(msg, '\n') != NULL)
1099 sudo_debug_printf2(NULL, NULL, 0, SUDO_DEBUG_PARSER|SUDO_DEBUG_DEBUG,
1100 "%s:%d %s", sudoers, sudolineno, lbuf.buf);
1105 #endif /* TRACELEXER */