Makefile.in | 5 +- UPGRADE | 12 +++ aclocal.m4 | 16 +--- auth/pam.c | 23 +++++- check.c | 18 ++++ config.h.in | 3 + configure.in | 35 ++++++--- defaults.c | 3 +- logging.c | 5 +- parse.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++----- parse.yacc | 13 +++ pathnames.h.in | 7 ++ rpminst.sudoers | 19 +++++ sample.pam | 33 +------- sudo.c | 92 ++-------------------- sudo.control | 17 ++++ sudo.h | 1 - sudo.pod | 15 ++-- sudoers | 22 +++--- sudoers.control | 19 +++++ sudoers.man.in | 2 +- sudoers.pod | 9 +- tgetpass.c | 7 +- visudo.man.in | 2 +- visudo.pod | 5 +- 25 files changed, 425 insertions(+), 192 deletions(-) diff --git a/Makefile.in b/Makefile.in index 893ac81..f0a6772 100644 --- a/Makefile.in +++ b/Makefile.in @@ -88,9 +88,10 @@ install_gid = 0 sudoers_uid = @SUDOERS_UID@ sudoers_gid = @SUDOERS_GID@ sudoers_mode = @SUDOERS_MODE@ +sudoers_dir_mode = @SUDOERS_DIR_MODE@ # Pass in paths and uid/gid + OS dependent defined -DEFS = @OSDEFS@ -D_PATH_SUDOERS=\"$(sudoersdir)/sudoers\" -D_PATH_SUDOERS_TMP=\"$(sudoersdir)/sudoers.tmp\" -DSUDOERS_UID=$(sudoers_uid) -DSUDOERS_GID=$(sudoers_gid) -DSUDOERS_MODE=$(sudoers_mode) +DEFS = @OSDEFS@ -D_PATH_SUDOERS=\"$(sudoersdir)/sudoers\" -D_PATH_SUDOERS_TMP=\"$(sudoersdir)/sudoers.tmp\" -DSUDOERS_UID=$(sudoers_uid) -DSUDOERS_GID=$(sudoers_gid) -DSUDOERS_MODE=$(sudoers_mode) -DSUDOERS_DIR_MODE=$(sudoers_dir_mode) #### End of system configuration section. #### @@ -181,7 +182,7 @@ testsudoers: $(TESTOBJS) $(LIBOBJS) $(CC) -o $@ $(TESTOBJS) $(LIBOBJS) $(LDFLAGS) $(LIBS) $(NET_LIBS) sudo_noexec.la: sudo_noexec.lo - $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir) + $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -o $@ sudo_noexec.lo -avoid-version -module -rpath $(noexecdir) # Uncomment the following if you want "make distclean" to clean the parser @DEV@PARSESRCS = sudo.tab.h sudo.tab.c lex.yy.c def_data.c def_data.h diff --git a/UPGRADE b/UPGRADE index c0e73af..2880a28 100644 --- a/UPGRADE +++ b/UPGRADE @@ -1,6 +1,18 @@ Notes on upgrading from an older release ======================================== +o Upgrading from a version prior to 1.7 + + Starting with sudo 1.7, if an OS supports a modular authentication + method such as PAM, it will be used by default. + + Prior to version 1.7, sudo would preserve the user's environment, + pruning out potentially dangerous variables. Starting with sudo + 1.7 the envionment is reset to a default set of values. To + preserve specific environment variables, add them to the "env_keep" + list in sudoers. The old behavior can be restored by negating the + "env_reset" option in sudoers. + o Upgrading from a version prior to 1.6.8: Prior to sudo 1.6.8, if /var/run did not exist, sudo would put diff --git a/aclocal.m4 b/aclocal.m4 index e6c2994..b8206a2 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -214,21 +214,15 @@ dnl dnl check for working fnmatch(3) dnl AC_DEFUN(SUDO_FUNC_FNMATCH, -[AC_MSG_CHECKING(for working fnmatch with FNM_CASEFOLD) +[AC_MSG_CHECKING([for working fnmatch with FNM_CASEFOLD]) AC_CACHE_VAL(sudo_cv_func_fnmatch, [rm -f conftestdata; > conftestdata AC_TRY_RUN([#include -main() { exit(fnmatch("/*/bin/echo *", "/usr/bin/echo just a test", FNM_CASEFOLD)); } -], sudo_cv_func_fnmatch=yes, sudo_cv_func_fnmatch=no, - sudo_cv_func_fnmatch=no) -rm -f core core.* *.core])dnl +main() { exit(fnmatch("/*/bin/echo *", "/usr/bin/echo just a test", FNM_CASEFOLD)); }], [sudo_cv_func_fnmatch=yes], [sudo_cv_func_fnmatch=no], + [sudo_cv_func_fnmatch=no]) +rm -f core core.* *.core]) AC_MSG_RESULT($sudo_cv_func_fnmatch) -if test $sudo_cv_func_fnmatch = yes; then - [$1] -else - [$2] -fi -]) +AS_IF([test $sudo_cv_func_fnmatch = yes], [$1], [$2])]) dnl dnl check for isblank(3) diff --git a/auth/pam.c b/auth/pam.c index d289a06..d0c9194 100644 --- a/auth/pam.c +++ b/auth/pam.c @@ -56,7 +56,7 @@ #include "sudo_auth.h" /* Only OpenPAM and Linux PAM use const qualifiers. */ -#if defined(_OPENPAM) || defined(__LIBPAM_VERSION) +#if defined(_OPENPAM) || defined(__LIBPAM_VERSION) || defined(__LINUX_PAM__) # define PAM_CONST const #else # define PAM_CONST @@ -175,6 +175,8 @@ int pam_prep_user(pw) struct passwd *pw; { + int eval; + if (pamh == NULL) pam_init(pw, NULL, NULL); @@ -195,6 +197,18 @@ pam_prep_user(pw) */ (void) pam_setcred(pamh, PAM_ESTABLISH_CRED); + /* + * To fully utilize PAM sessions we would need to keep a + * sudo process around until the command exits. However, we + * can at least cause pam_limits to be run by opening and then + * immediately closing the session. + */ + if ((eval = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + (void) pam_end(pamh, eval | PAM_DATA_SILENT); + return(AUTH_FAILURE); + } + (void) pam_close_session(pamh, 0); + if (pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT) == PAM_SUCCESS) return(AUTH_SUCCESS); else @@ -235,7 +249,12 @@ sudo_conv(num_msg, msg, response, appdata_ptr) p = pm->msg; /* Read the password. */ pass = tgetpass(p, def_passwd_timeout * 60, flags); - pr->resp = estrdup(pass ? pass : ""); + if (pass == NULL) { + /* We got ^C instead of a password; abort quickly. */ + nil_pw = 1; + return(PAM_CONV_ERR); + } + pr->resp = estrdup(pass); if (*pr->resp == '\0') nil_pw = 1; /* empty password */ else diff --git a/check.c b/check.c index b8bd988..c118d88 100644 --- a/check.c +++ b/check.c @@ -299,6 +299,24 @@ user_is_exempt() return(TRUE); } +#ifdef HAVE_GETGROUPLIST + { + gid_t *grouplist, grouptmp; + int i, n_groups = 1; + + if (getgrouplist(user_name, user_gid, &grouptmp, &n_groups) == -1) { + grouplist = (gid_t *) emalloc2(sizeof(gid_t), (n_groups + 1)); + if (getgrouplist(user_name, user_gid, grouplist, &n_groups) > 0) + for (i = 0; i < n_groups; ++i) + if (grouplist[i] == grp->gr_gid) { + free(grouplist); + return(TRUE); + } + free(grouplist); + } + } +#endif + return(FALSE); } diff --git a/config.h.in b/config.h.in index 5decf69..676d187 100644 --- a/config.h.in +++ b/config.h.in @@ -122,6 +122,9 @@ /* Define to 1 if you have the `getdomainname' function. */ #undef HAVE_GETDOMAINNAME +/* Define to 1 if you have the `getgrouplist' function. */ +#undef HAVE_GETGROUPLIST + /* Define to 1 if you have the `getifaddrs' function. */ #undef HAVE_GETIFADDRS diff --git a/configure.in b/configure.in index a963b48..642ec55 100644 --- a/configure.in +++ b/configure.in @@ -29,6 +29,7 @@ AC_SUBST(AUTH_OBJS)dnl AC_SUBST(MANTYPE)dnl AC_SUBST(MAN_POSTINSTALL)dnl AC_SUBST(SUDOERS_MODE)dnl +AC_SUBST(SUDOERS_DIR_MODE)dnl AC_SUBST(SUDOERS_UID)dnl AC_SUBST(SUDOERS_GID)dnl AC_SUBST(DEV) @@ -102,9 +103,10 @@ PROGS="sudo visudo" test -n "$MANTYPE" || MANTYPE="man" test -n "$mansrcdir" || mansrcdir="." test -n "$SUDOERS_MODE" || SUDOERS_MODE=0440 +test -n "$SUDOERS_DIR_MODE" || SUDOERS_DIR_MODE=0700 test -n "$SUDOERS_UID" || SUDOERS_UID=0 test -n "$SUDOERS_GID" || SUDOERS_GID=0 -DEV="#" +DEV="" dnl dnl Other vaiables @@ -668,6 +670,20 @@ AC_ARG_WITH(sudoers-mode, [ --with-sudoers-mode mode of sudoers file (defau ;; 0*) SUDOERS_MODE=$with_sudoers_mode ;; + *) AC_MSG_ERROR(["you must use a numeric uid, not a name."]) + ;; +esac]) + +AC_ARG_WITH(sudoers-dir-mode, [ --with-sudoers-dir-mode mode of sudoers directory (defaults to 0700)], +[case $with_sudoers_dir_mode in + yes) AC_MSG_ERROR(["must give --with-sudoers-dir-mode an argument."]) + ;; + no) AC_MSG_ERROR(["--without-sudoers-dir-mode not supported."]) + ;; + [[1-9]]*) SUDOERS_DIR_MODE=0${with_sudoers_dir_mode} + ;; + 0*) SUDOERS_DIR_MODE=$with_sudoers_dir_mode + ;; *) AC_MSG_ERROR(["you must use an octal mode, not a name."]) ;; esac]) @@ -1152,6 +1168,7 @@ fi dnl dnl C compiler checks dnl +AC_GNU_SOURCE AC_ISC_POSIX AC_PROG_CC_STDC AC_PROG_CPP @@ -1609,7 +1626,8 @@ if test "$CHECKSHADOW" = "true"; then AC_CHECK_FUNCS(getspnam, [CHECKSHADOW="false"], [AC_CHECK_LIB(gen, getspnam, AC_DEFINE(HAVE_GETSPNAM) [SUDO_LIBS="${SUDO_LIBS} -lgen"; LIBS="${LIBS} -lgen"])]) fi if test "$CHECKSHADOW" = "true"; then - AC_CHECK_FUNC(getprpwnam, [AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1], AC_CHECK_LIB(sec, getprpwnam, AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1; SUDO_LIBS="${SUDO_LIBS} -lsec"; LIBS="${LIBS} -lsec"], AC_CHECK_LIB(security, getprpwnam, AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1; SUDO_LIBS="${SUDO_LIBS} -lsecurity"; LIBS="${LIBS} -lsecurity"], AC_CHECK_LIB(prot, getprpwnam, AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1; SUDO_LIBS="${SUDO_LIBS} -lprot"; LIBS="${LIBS} -lprot"])))]) + AC_CHECK_FUNC(getprpwnam, [AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1] + AC_CHECK_LIB(sec, getprpwnam, AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1; SUDO_LIBS="${SUDO_LIBS} -lsec"; LIBS="${LIBS} -lsec"], AC_CHECK_LIB(security, getprpwnam, AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1; SUDO_LIBS="${SUDO_LIBS} -lsecurity"; LIBS="${LIBS} -lsecurity"], AC_CHECK_LIB(prot, getprpwnam, AC_DEFINE(HAVE_GETPRPWNAM) [CHECKSHADOW="false"; SECUREWARE=1; SUDO_LIBS="${SUDO_LIBS} -lprot"; LIBS="${LIBS} -lprot"])))]) fi dnl @@ -1683,7 +1701,7 @@ dnl dnl Function checks dnl AC_CHECK_FUNCS(strchr strrchr memchr memcpy memset sysconf tzset \ - strftime setrlimit initgroups fstat gettimeofday) + strftime setrlimit initgroups fstat getgrouplist gettimeofday) AC_CHECK_FUNCS(seteuid, , [AC_DEFINE(NO_SAVED_IDS)]) if test -z "$SKIP_SETRESUID"; then AC_CHECK_FUNCS(setresuid, [SKIP_SETREUID=yes]) @@ -1703,9 +1721,10 @@ fi AC_CHECK_FUNCS(lockf flock, [break]) AC_CHECK_FUNCS(waitpid wait3, [break]) AC_CHECK_FUNCS(innetgr _innetgr, [AC_CHECK_FUNCS(getdomainname) [break]]) -AC_CHECK_FUNCS(lsearch, , [AC_CHECK_LIB(compat, lsearch, AC_CHECK_HEADER(search.h, AC_DEFINE(HAVE_LSEARCH) [LIBS="${LIBS} -lcompat"], AC_LIBOBJ(lsearch), -), AC_LIBOBJ(lsearch))]) +AC_CHECK_FUNCS(lsearch, , [AC_CHECK_LIB([compat], [lsearch], [AC_CHECK_HEADER([search.h], [AC_DEFINE(HAVE_LSEARCH)] +[LIBS="${LIBS} -lcompat"], [AC_LIBOBJ(lsearch)], [-])], [AC_LIBOBJ(lsearch)])]) AC_CHECK_FUNCS(utimes, [AC_CHECK_FUNCS(futimes futimesat, [break])], [AC_CHECK_FUNCS(futime) AC_LIBOBJ(utimes)]) -SUDO_FUNC_FNMATCH(AC_DEFINE(HAVE_FNMATCH), AC_LIBOBJ(fnmatch)) +AC_FUNC_FNMATCH_GNU SUDO_FUNC_ISBLANK AC_REPLACE_FUNCS(strerror strcasecmp sigaction strlcpy strlcat closefrom) AC_CHECK_FUNCS(snprintf vsnprintf asprintf vasprintf, , [NEED_SNPRINTF=1]) @@ -2299,12 +2318,6 @@ AH_TEMPLATE(sig_atomic_t, [Define to `int' if does not define.]) dnl dnl Bits to copy verbatim into config.h.in dnl -AH_VERBATIM([_GNU_SOURCE], -[/* Enable GNU extensions on systems that have them. */ -#ifndef _GNU_SOURCE -# define _GNU_SOURCE 1 -#endif]) - AH_VERBATIM([_ALL_SOURCE], [/* Enable non-POSIX extensions on AIX. */ #ifndef _ALL_SOURCE diff --git a/defaults.c b/defaults.c index 667f44a..287430c 100644 --- a/defaults.c +++ b/defaults.c @@ -438,6 +438,7 @@ init_defaults() #ifdef ENV_EDITOR def_env_editor = TRUE; #endif + def_env_reset = TRUE; def_set_logname = TRUE; /* Syslog options need special care since they both strings and ints */ @@ -450,7 +451,7 @@ init_defaults() #endif /* Password flags also have a string and integer component. */ - (void) store_tuple("any", &sudo_defs_table[I_LISTPW], TRUE); + (void) store_tuple("all", &sudo_defs_table[I_LISTPW], TRUE); (void) store_tuple("all", &sudo_defs_table[I_VERIFYPW], TRUE); /* Then initialize the int-like things. */ diff --git a/logging.c b/logging.c index 1e1b997..cb5f55a 100644 --- a/logging.c +++ b/logging.c @@ -102,9 +102,9 @@ mysyslog(pri, fmt, va_alist) va_start(ap); #endif #ifdef LOG_NFACILITIES - openlog("sudo", 0, def_syslog); + openlog("sudo", LOG_PID, def_syslog); #else - openlog("sudo", 0); + openlog("sudo", LOG_PID); #endif vsnprintf(buf, sizeof(buf), fmt, ap); #ifdef BROKEN_SYSLOG @@ -120,7 +120,6 @@ mysyslog(pri, fmt, va_alist) syslog(pri, "%s", buf); #endif /* BROKEN_SYSLOG */ va_end(ap); - closelog(); } /* diff --git a/parse.c b/parse.c index c045b9c..5afdc6a 100644 --- a/parse.c +++ b/parse.c @@ -72,6 +72,12 @@ # include # endif #endif +#ifdef HAVE_ERR_H +# include +#else +# include "emul/err.h" +#endif /* HAVE_ERR_H */ +#include #include "sudo.h" #include "parse.h" @@ -91,12 +97,217 @@ static const char rcsid[] = "$Sudo: parse.c,v 1.161 2004/08/24 18:01:13 millert int parse_error = FALSE; extern int keepall; extern FILE *yyin, *yyout; +extern int errorlineno; /* * Prototypes */ static int has_meta __P((char *)); void init_parser __P((void)); + void reset_parser __P((void)); + +/* + * Sanity check sudoers mode/owner/type. + * Leaves a file pointer to the sudoers file open in ``fp''. + */ +static FILE * +check_sudoers(const char *path) +{ + FILE *fp = NULL; + struct stat statbuf; + int rootstat, i; + char c; + + /* + * Fix the mode and group on sudoers file from old default. + * Only works if file system is readable/writable by root. + */ + if ((rootstat = stat_sudoers(path, &statbuf)) == 0 && + SUDOERS_UID == statbuf.st_uid && SUDOERS_MODE != 0400 && + (statbuf.st_mode & 0007777) == 0400) { + + if (chmod(path, SUDOERS_MODE) == 0) { + warnx("fixed mode on %s", path); + SET(statbuf.st_mode, SUDOERS_MODE); + if (statbuf.st_gid != SUDOERS_GID) { + if (!chown(path,(uid_t) -1,SUDOERS_GID)) { + warnx("set group on %s", path); + statbuf.st_gid = SUDOERS_GID; + } else + warn("unable to set group on %s", path); + } + } else + warn("unable to fix mode on %s", path); + } + + /* + * Sanity checks on sudoers file. Must be done as sudoers + * file owner. We already did a stat as root, so use that + * data if we can't stat as sudoers file owner. + */ + set_perms(PERM_SUDOERS); + + if (rootstat != 0 && stat_sudoers(path, &statbuf) != 0) + log_error(USE_ERRNO, "can't stat %s", path); + else if (!S_ISREG(statbuf.st_mode)) + log_error(0, "%s is not a regular file", path); + else if (statbuf.st_size == 0) + log_error(0, "%s is zero length", path); + else if ((statbuf.st_mode & 07777) != SUDOERS_MODE) + log_error(0, "%s is mode 0%o, should be 0%o", path, + (statbuf.st_mode & 07777), SUDOERS_MODE); + else if (statbuf.st_uid != SUDOERS_UID) + log_error(0, "%s is owned by uid %lu, should be %lu", path, + (unsigned long) statbuf.st_uid, (unsigned long) SUDOERS_UID); + else if (statbuf.st_gid != SUDOERS_GID) + log_error(0, "%s is owned by gid %lu, should be %lu", path, + (unsigned long) statbuf.st_gid, (unsigned long) SUDOERS_GID); + else { + /* Solaris sometimes returns EAGAIN so try 10 times */ + for (i = 0; i < 10 ; i++) { + errno = 0; + if ((fp = fopen(path, "r")) == NULL || + fread(&c, sizeof(c), 1, fp) != 1) { + fp = NULL; + if (errno != EAGAIN && errno != EWOULDBLOCK) + break; + } else + break; + sleep(1); + } + if (fp == NULL) + log_error(USE_ERRNO, "can't open %s", path); + } + + set_perms(PERM_ROOT); /* change back to root */ + return fp; +} + +static int +skip_sudoers(const char *s) +{ + char allowed[] = "_-"; + + for (; *s; ++s) { + if (!isalnum(*s) && !strchr(allowed, *s)) + return TRUE; + } + + return FALSE; +} + +/* + * Load given sudoers file. + */ +static int +load_sudoers(const char *path) +{ + FILE *fp = check_sudoers(path); /* check mode/owner */ + int error; + + if (!fp) + return 1; + + rewind(fp); + yyin = fp; + yyout = stdout; + + /* Reset data structures in the parser. */ + reset_parser(); + + /* Need to be runas user while stat'ing things in the parser. */ + set_perms(PERM_RUNAS); + error = yyparse(); + set_perms(PERM_ROOT); + + /* Close the sudoers file now that we are done with it. */ + (void) fclose(fp); + fp = NULL; + + if (error || parse_error) { + log_error(0, "parse error in %s near line %d", path, errorlineno); + return 1; + } + + return 0; +} + +/* + * Load all sudoers files. + */ +static int +load_all_sudoers(void) +{ + int error, rc; + struct stat statbuf; + + /* check mode/owner and parse */ + if ((error = load_sudoers(_PATH_SUDOERS))) + return error; + + /* + * Sanity checks on sudoers directory. Must be done as sudoers + * file owner. We already did a stat as root, so use that + * data if we can't stat as sudoers file owner. + */ + set_perms(PERM_SUDOERS); + rc = stat_sudoers(_PATH_SUDOERS_DIR, &statbuf); + set_perms(PERM_ROOT); + + if (rc) { + if (ENOENT == errno) + return error; /* do not complain if sudoers directory is missing */ + log_error(USE_ERRNO, "can't stat %s", _PATH_SUDOERS_DIR); + error = 1; + } + else if (!S_ISDIR(statbuf.st_mode)) { + log_error(0, "%s is not a directory", _PATH_SUDOERS_DIR); + error = 1; + } + else if ((statbuf.st_mode & 07777) != SUDOERS_DIR_MODE) { + log_error(0, "%s is mode 0%o, should be 0%o", _PATH_SUDOERS_DIR, + (statbuf.st_mode & 07777), SUDOERS_DIR_MODE); + error = 1; + } + else if (statbuf.st_uid != SUDOERS_UID) { + log_error(0, "%s is owned by uid %lu, should be %lu", _PATH_SUDOERS_DIR, + (unsigned long) statbuf.st_uid, SUDOERS_UID); + error = 1; + } + else if (statbuf.st_gid != SUDOERS_GID) { + log_error(0, "%s is owned by gid %lu, should be %lu", _PATH_SUDOERS_DIR, + (unsigned long) statbuf.st_gid, SUDOERS_GID); + error = 1; + } + else { + DIR *dirp; + struct dirent *dent; + + set_perms(PERM_SUDOERS); + dirp = opendir(_PATH_SUDOERS_DIR); + set_perms(PERM_ROOT); + + if (!dirp) { + log_error(USE_ERRNO, "can't open %s", _PATH_SUDOERS_DIR); + error = 1; + } + else { + while ((dent = readdir(dirp))) { + char *fname; + + if (skip_sudoers(dent->d_name)) + continue; + + easprintf(&fname, "%s/%s", _PATH_SUDOERS_DIR, dent->d_name); + error |= load_sudoers(fname); + free(fname); + } + closedir(dirp); + } + } + + return error; +} /* * Look up the user in the sudoers file and check to see if they are @@ -109,11 +320,6 @@ sudoers_lookup(pwflag) int error, nopass; enum def_tupple pwcheck; - /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */ - rewind(sudoers_fp); - yyin = sudoers_fp; - yyout = stdout; - /* Allocate space for data structures in the parser. */ init_parser(); @@ -121,16 +327,10 @@ sudoers_lookup(pwflag) if (pwflag > 0) keepall = TRUE; - /* Need to be runas user while stat'ing things in the parser. */ - set_perms(PERM_RUNAS); - error = yyparse(); - - /* Close the sudoers file now that we are done with it. */ - (void) fclose(sudoers_fp); - sudoers_fp = NULL; + /* Check mode/owner and parse all sudoers files */ + error = load_all_sudoers(); - if (error || parse_error) { - set_perms(PERM_ROOT); + if (error) { return(VALIDATE_ERROR); } @@ -184,7 +384,6 @@ sudoers_lookup(pwflag) top--; } if (found) { - set_perms(PERM_ROOT); if (nopass == -1) nopass = 0; return(VALIDATE_OK | nopass); @@ -197,7 +396,6 @@ sudoers_lookup(pwflag) /* * User was granted access to cmnd on host as user. */ - set_perms(PERM_ROOT); return(VALIDATE_OK | (no_passwd == TRUE ? FLAG_NOPASS : 0) | (no_execve == TRUE ? FLAG_NOEXEC : 0)); @@ -206,7 +404,6 @@ sudoers_lookup(pwflag) /* * User was explicitly denied access to cmnd on host. */ - set_perms(PERM_ROOT); return(VALIDATE_NOT_OK | (no_passwd == TRUE ? FLAG_NOPASS : 0) | (no_execve == TRUE ? FLAG_NOEXEC : 0)); @@ -215,7 +412,6 @@ sudoers_lookup(pwflag) top--; } } - set_perms(PERM_ROOT); /* * The user was neither explicitly granted nor denied access. @@ -240,7 +436,7 @@ command_matches(sudoers_cmnd, sudoers_args) DIR *dirp; /* Check for pseudo-commands */ - if (strchr(user_cmnd, '/') == NULL) { + if (sudoers_cmnd[0] != '/') { /* * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND * a) there are no args in sudoers OR diff --git a/parse.yacc b/parse.yacc index 46dce22..1fbd685 100644 --- a/parse.yacc +++ b/parse.yacc @@ -1240,3 +1240,16 @@ init_parser() if (printmatches == TRUE) expand_match_list(); } + +/* + * Resets data structures used by a previous parser run. + */ +void +reset_parser() +{ + /* Reset data structures if we run the parser more than once. */ + parse_error = FALSE; + used_runas = FALSE; + errorlineno = -1; + sudolineno = 1; +} diff --git a/pathnames.h.in b/pathnames.h.in index 14f4adf..e7eef8a 100644 --- a/pathnames.h.in +++ b/pathnames.h.in @@ -58,6 +58,13 @@ #endif /* _PATH_SUDOERS_TMP */ /* + * NOTE: _PATH_SUDOERS_DIR is usually overriden by the Makefile. + */ +#ifndef _PATH_SUDOERS_DIR +#define _PATH_SUDOERS_DIR "/etc/sudo.d" +#endif /* _PATH_SUDOERS_DIR */ + +/* * The following paths are controlled via the configure script. */ diff --git a/rpminst.sudoers b/rpminst.sudoers new file mode 100644 index 0000000..5339dfb --- /dev/null +++ b/rpminst.sudoers @@ -0,0 +1,19 @@ +# This example sudoers file allows members of rpminst group to execute /bin/rpm +# Beware, ability to execute rpm with root permissions de facto means full +# root privileges granted to the user. +# +# This file MUST be edited with the 'visudo' command as root. +# +# See the sudoers man page for the details on how to write a sudoers file. + +# User alias specification +User_Alias RPMINST_USER = %rpminst + +# Runas alias specification +Runas_Alias RPMINST_RUN_AS = root + +# Cmnd alias specification +Cmnd_Alias RPMINST_CMD = /bin/rpm, /usr/bin/rpmi + +# User privilege specification +RPMINST_USER ALL = (RPMINST_RUN_AS) RPMINST_CMD diff --git a/sample.pam b/sample.pam index 603fded..de450a3 100644 --- a/sample.pam +++ b/sample.pam @@ -1,30 +1,5 @@ #%PAM-1.0 -# Sample /etc/pam.d/sudo file for RedHat 9 / Fedora Core. -# For other Linux distributions you may want to -# use /etc/pam.d/sshd or /etc/pam.d/su as a guide. -# -# There are two basic ways to configure PAM, either via pam_stack -# or by explicitly specifying the various methods to use. -# -# Here we use pam_stack -auth required pam_stack.so service=system-auth -account required pam_stack.so service=system-auth -password required pam_stack.so service=system-auth -session required pam_stack.so service=system-auth -# -# Alternately, you can specify the authentication method directly. -# Here we use pam_unix for normal password authentication. -#auth required pam_env.so -#auth sufficient pam_unix.so -#account required pam_unix.so -#password required pam_cracklib.so retry=3 type= -#password required pam_unix.so nullok use_authtok md5 shadow -#session required pam_limits.so -#session required pam_unix.so -# -# Another option is to use SMB for authentication. -#auth required pam_env.so -#auth sufficient pam_smb_auth.so -#account required pam_smb_auth.so -#password required pam_smb_auth.so -#session required pam_limits.so +auth include system-auth +account include system-auth +password required pam_deny.so +session include system-auth diff --git a/sudo.c b/sudo.c index 3313b00..3f2257d 100644 --- a/sudo.c +++ b/sudo.c @@ -101,7 +101,6 @@ static const char rcsid[] = "$Sudo: sudo.c,v 1.370 2004/08/24 18:01:13 millert E */ static int init_vars __P((int)); static int parse_args __P((int, char **)); -static void check_sudoers __P((void)); static void initial_setup __P((void)); static void set_loginclass __P((struct passwd *)); static void usage __P((int)); @@ -123,12 +122,10 @@ char **Argv, **NewArgv; char *prev_user; struct sudo_user sudo_user; struct passwd *auth_pw; -FILE *sudoers_fp; struct interface *interfaces; int num_interfaces; int tgetpass_flags; uid_t timestamp_uid; -extern int errorlineno; #if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL) static struct rlimit corelimit; #endif /* RLIMIT_CORE && !SUDO_DEVEL */ @@ -203,6 +200,12 @@ main(argc, argv, envp) /* Setup defaults data structures. */ init_defaults(); +#ifdef LOG_NFACILITIES + openlog("sudo", LOG_PID, def_syslog); +#else + openlog("sudo", LOG_PID); +#endif + /* Load the list of local ip addresses and netmasks. */ load_interfaces(); @@ -260,8 +263,6 @@ main(argc, argv, envp) else if (ISSET(validated, VALIDATE_OK) && !printmatches); /* skips */ else if (ISSET(validated, VALIDATE_OK) && printmatches) { - check_sudoers(); /* check mode/owner on _PATH_SUDOERS */ - /* User is found in LDAP and we want a list of all sudo commands the * user can do, so consult sudoers but throw away result. */ @@ -270,8 +271,6 @@ main(argc, argv, envp) else #endif { - check_sudoers(); /* check mode/owner on _PATH_SUDOERS */ - /* Validate the user but don't search for pseudo-commands. */ validated = sudoers_lookup(pwflag); } @@ -316,10 +315,6 @@ main(argc, argv, envp) exit(0); } - if (ISSET(validated, VALIDATE_ERROR)) - log_error(0, "parse error in %s near line %d", _PATH_SUDOERS, - errorlineno); - /* Is root even allowed to run sudo? */ if (user_uid == 0 && !def_root_sudo) { (void) fprintf(stderr, @@ -845,81 +840,6 @@ parse_args(argc, argv) } /* - * Sanity check sudoers mode/owner/type. - * Leaves a file pointer to the sudoers file open in ``fp''. - */ -static void -check_sudoers() -{ - struct stat statbuf; - int rootstat, i; - char c; - - /* - * Fix the mode and group on sudoers file from old default. - * Only works if file system is readable/writable by root. - */ - if ((rootstat = stat_sudoers(_PATH_SUDOERS, &statbuf)) == 0 && - SUDOERS_UID == statbuf.st_uid && SUDOERS_MODE != 0400 && - (statbuf.st_mode & 0007777) == 0400) { - - if (chmod(_PATH_SUDOERS, SUDOERS_MODE) == 0) { - warnx("fixed mode on %s", _PATH_SUDOERS); - SET(statbuf.st_mode, SUDOERS_MODE); - if (statbuf.st_gid != SUDOERS_GID) { - if (!chown(_PATH_SUDOERS,(uid_t) -1,SUDOERS_GID)) { - warnx("set group on %s", _PATH_SUDOERS); - statbuf.st_gid = SUDOERS_GID; - } else - warn("unable to set group on %s", _PATH_SUDOERS); - } - } else - warn("unable to fix mode on %s", _PATH_SUDOERS); - } - - /* - * Sanity checks on sudoers file. Must be done as sudoers - * file owner. We already did a stat as root, so use that - * data if we can't stat as sudoers file owner. - */ - set_perms(PERM_SUDOERS); - - if (rootstat != 0 && stat_sudoers(_PATH_SUDOERS, &statbuf) != 0) - log_error(USE_ERRNO, "can't stat %s", _PATH_SUDOERS); - else if (!S_ISREG(statbuf.st_mode)) - log_error(0, "%s is not a regular file", _PATH_SUDOERS); - else if (statbuf.st_size == 0) - log_error(0, "%s is zero length", _PATH_SUDOERS); - else if ((statbuf.st_mode & 07777) != SUDOERS_MODE) - log_error(0, "%s is mode 0%o, should be 0%o", _PATH_SUDOERS, - (statbuf.st_mode & 07777), SUDOERS_MODE); - else if (statbuf.st_uid != SUDOERS_UID) - log_error(0, "%s is owned by uid %lu, should be %lu", _PATH_SUDOERS, - (unsigned long) statbuf.st_uid, (unsigned long) SUDOERS_UID); - else if (statbuf.st_gid != SUDOERS_GID) - log_error(0, "%s is owned by gid %lu, should be %lu", _PATH_SUDOERS, - (unsigned long) statbuf.st_gid, (unsigned long) SUDOERS_GID); - else { - /* Solaris sometimes returns EAGAIN so try 10 times */ - for (i = 0; i < 10 ; i++) { - errno = 0; - if ((sudoers_fp = fopen(_PATH_SUDOERS, "r")) == NULL || - fread(&c, sizeof(c), 1, sudoers_fp) != 1) { - sudoers_fp = NULL; - if (errno != EAGAIN && errno != EWOULDBLOCK) - break; - } else - break; - sleep(1); - } - if (sudoers_fp == NULL) - log_error(USE_ERRNO, "can't open %s", _PATH_SUDOERS); - } - - set_perms(PERM_ROOT); /* change back to root */ -} - -/* * Close all open files (except std*) and turn off core dumps. * Also sets the set_perms() pointer to the correct function. */ diff --git a/sudo.control b/sudo.control new file mode 100644 index 0000000..66e46ba --- /dev/null +++ b/sudo.control @@ -0,0 +1,17 @@ +#!/bin/sh + +. /etc/control.d/functions + +BINARY=/usr/bin/sudo + +new_fmode public 4711 root root +new_fmode wheelonly 4710 root wheel +new_fmode restricted 700 root root + +new_help public "Any user can execute $BINARY" +new_help wheelonly "Only \"wheel\" group members can execute $BINARY" +new_help restricted "Only root can execute $BINARY" + +new_summary 'Execute a command as another user' + +control_fmode "$BINARY" "$*" || exit 1 diff --git a/sudo.h b/sudo.h index 51dc51f..b189d38 100644 --- a/sudo.h +++ b/sudo.h @@ -248,7 +248,6 @@ YY_DECL; extern struct sudo_user sudo_user; extern struct passwd *auth_pw; -extern FILE *sudoers_fp; extern int tgetpass_flags; extern uid_t timestamp_uid; diff --git a/sudo.pod b/sudo.pod index 3b913d4..a04d044 100644 --- a/sudo.pod +++ b/sudo.pod @@ -40,7 +40,7 @@ file [...] =head1 DESCRIPTION B allows a permitted user to execute a I as the -superuser or another user, as specified in the I file. +superuser or another user, as specified in the I files. The real and effective uid and gid are set to match those of the target user as specified in the passwd file and the group vector is initialized based on the group file (unless the B<-P> option was @@ -57,15 +57,16 @@ When invoked as B, the B<-e> option (described below), is implied. B determines who is an authorized user by consulting the file -F<@sysconfdir@/sudoers>. By giving B the B<-v> flag a user +F<@sysconfdir@/sudoers> and all files with valid names from +F<@sysconfdir@/sudo.d> directory. By giving B the B<-v> flag a user can update the time stamp without running a I The password prompt itself will also time out if the user's password is not entered within C<@password_timeout@> minutes (unless overridden via I). -If a user who is not listed in the I file tries to run a +If a user who is not listed in the I files tries to run a command via B, mail is sent to the proper authorities, as -defined at configure time or in the I file (defaults to +defined at configure time or in the I files (defaults to C<@mailto@>). Note that the mail will not be sent if an unauthorized user tries to run sudo with the B<-l> or B<-v> flags. This allows users to determine for themselves whether or not they are allowed @@ -82,7 +83,7 @@ root, not the user specified by C. B can log both successful and unsuccessful attempts (as well as errors) to syslog(3), a log file, or both. By default B will log via syslog(3) but this is changeable at configure time -or via the I file. +or via the I files. =head1 OPTIONS @@ -95,6 +96,7 @@ B accepts the following command line options: The B<-H> (I) option sets the C environment variable to the homedir of the target user (root by default) as specified in passwd(@mansectform@). By default, B does not modify C +unless B<-s> (I) option is also given (see I and I in L). =item -K @@ -405,7 +407,8 @@ B utilizes the following environment variables: =head1 FILES - @sysconfdir@/sudoers List of who can run what + @sysconfdir@/sudoers, + @sysconfdir@/sudo.d/* List of who can run what @timedir@ Directory containing timestamps =head1 EXAMPLES diff --git a/sudoers b/sudoers index 36f78b3..6b81a34 100644 --- a/sudoers +++ b/sudoers @@ -8,22 +8,22 @@ # Host alias specification # User alias specification +User_Alias WHEEL_USERS = %wheel +User_Alias XGRP_USERS = %xgrp # Cmnd alias specification # Defaults specification -# Runas alias specification +# If env_reset is disabled, sudo will NOT reset the environment +# to only contain the fixed list of variables. +# See sudoers(5) for details. +#Defaults:WHEEL_USERS !env_reset -# User privilege specification -root ALL=(ALL) ALL - -# Uncomment to allow people in group wheel to run all commands -# %wheel ALL=(ALL) ALL +# Preserve DISPLAY and XAUTHORITY environment variables +# for "xgrp" group members. +Defaults:XGRP_USERS env_keep += "DISPLAY XAUTHORITY" -# Same thing without a password -# %wheel ALL=(ALL) NOPASSWD: ALL +# User privilege specification +#root ALL=(ALL) ALL -# Samples -# %users ALL=/sbin/mount /cdrom,/sbin/umount /cdrom -# %users localhost=/sbin/shutdown -h now diff --git a/sudoers.control b/sudoers.control new file mode 100755 index 0000000..72dabcf --- /dev/null +++ b/sudoers.control @@ -0,0 +1,19 @@ +#!/bin/sh + +. /etc/control.d/functions + +CONFIG=/etc/sudoers + +new_subst strict \ + '^#Defaults:WHEEL_USERS[[:space:]]+!env_reset$' \ + 's,^\(Defaults:WHEEL_USERS[[:space:]]\+!env_reset\)$,#\1,' +new_subst relaxed \ + '^Defaults:WHEEL_USERS[[:space:]]+!env_reset$' \ + 's,^#\(Defaults:WHEEL_USERS[[:space:]]\+!env_reset\)$,\1,' + +new_help strict 'Enable strict environment rules' +new_help relaxed 'Disable strict environment rules' + +new_summary 'sudoers environment rules' + +control_subst "$CONFIG" "$*" diff --git a/sudoers.man.in b/sudoers.man.in index 101a3d0..ba7b279 100644 --- a/sudoers.man.in +++ b/sudoers.man.in @@ -810,7 +810,7 @@ The user must always enter a password to use the \fB\-l\fR flag. .Sp If no value is specified, a value of \fIany\fR is implied. Negating the option results in a value of \fInever\fR being used. -The default value is \fIany\fR. +The default value is \fIall\fR. .RE .PP \&\fBLists that can be used in a boolean context\fR: diff --git a/sudoers.pod b/sudoers.pod index 3ad6b2a..df22dcb 100644 --- a/sudoers.pod +++ b/sudoers.pod @@ -325,7 +325,7 @@ set, falling back on the shell listed in the invoking user's If set and B is invoked with the B<-s> flag the C environment variable will be set to the home directory of the target user (which is root unless the B<-u> option is used). This effectively -makes the B<-s> flag imply B<-H>. This flag is I by default. +makes the B<-s> flag imply B<-H>. This flag is I by default. =item always_set_home @@ -380,7 +380,7 @@ tty. This will disallow things like C<"rsh somehost sudo ls"> since L does not allocate a tty. Because it is not possible to turn off echo when there is no tty present, some sites may with to set this flag to prevent a user from entering a visible password. This -flag is I by default. +flag is I by default. =item env_editor @@ -732,7 +732,7 @@ The user must always enter a password to use the B<-l> flag. If no value is specified, a value of I is implied. Negating the option results in a value of I being used. -The default value is I. +The default value is I. =back @@ -990,7 +990,8 @@ used as part of a word (e.g. a username or hostname): =head1 FILES - @sysconfdir@/sudoers List of who can run what + @sysconfdir@/sudoers, + @sysconfdir@/sudo.d/* List of who can run what /etc/group Local groups file /etc/netgroup List of network groups diff --git a/tgetpass.c b/tgetpass.c index 0cc2872..56a5b2a 100644 --- a/tgetpass.c +++ b/tgetpass.c @@ -136,10 +136,13 @@ tgetpass(prompt, timeout, flags) (void) fflush(stdout); restart: /* Open /dev/tty for reading/writing if possible else use stdin/stderr. */ - if (ISSET(flags, TGP_STDIN) || - (input = output = open(_PATH_TTY, O_RDWR|O_NOCTTY)) == -1) { + if (ISSET(flags, TGP_STDIN)) { input = STDIN_FILENO; output = STDERR_FILENO; + } else if ((input = output = open(_PATH_TTY, O_RDWR|O_NOCTTY)) == -1) { + log_error(NO_EXIT|NO_MAIL, "must be run from a terminal"); + buf[0] = '\0'; + return buf; } /* diff --git a/visudo.man.in b/visudo.man.in index ad8e8c4..67dafa5 100644 --- a/visudo.man.in +++ b/visudo.man.in @@ -268,7 +268,7 @@ or User specifications. In \fB\-s\fR (strict) mode this is an error, not a warning. .SH "SEE ALSO" .IX Header "SEE ALSO" -\&\fIvi\fR\|(1), sudoers(@mansectform@), sudo(@mansectsu@), vipw(@mansectsu@) +\&\fIvitmp\fR\|(1), sudoers(@mansectform@), sudo(@mansectsu@), vipw(@mansectsu@) .SH "AUTHOR" .IX Header "AUTHOR" Many people have worked on \fIsudo\fR over the years; this version of diff --git a/visudo.pod b/visudo.pod index 735ce8f..541e3e3 100644 --- a/visudo.pod +++ b/visudo.pod @@ -116,7 +116,8 @@ was configured with the I<--with-env-editor> option: =head1 FILES - @sysconfdir@/sudoers List of who can run what + @sysconfdir@/sudoers, + @sysconfdir@/sudo.d/* List of who can run what @sysconfdir@/sudoers.tmp Lock file for visudo =head1 DIAGNOSTICS @@ -160,7 +161,7 @@ not a warning. =head1 SEE ALSO -L, L, L, L +L, L, L, L =head1 AUTHOR