diff --git a/docs/sudo.man.in b/docs/sudo.man.in index 15a07c0ac..8d4f6e30b 100644 --- a/docs/sudo.man.in +++ b/docs/sudo.man.in @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDO" "@mansectsu@" "February 16, 2022" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" +.TH "SUDO" "@mansectsu@" "August 2, 2022" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" .nh .if n .ad l .SH "NAME" @@ -41,7 +41,7 @@ .HP 5n \fBsudo\fR \fB\-v\fR -[\fB\-ABknS\fR] +[\fB\-ABkNnS\fR] .if \n(BA [\fB\-a\fR\ \fItype\fR] [\fB\-g\fR\ \fIgroup\fR] [\fB\-h\fR\ \fIhost\fR] @@ -51,7 +51,7 @@ .HP 5n \fBsudo\fR \fB\-l\fR -[\fB\-ABknS\fR] +[\fB\-ABkNnS\fR] .if \n(BA [\fB\-a\fR\ \fItype\fR] [\fB\-g\fR\ \fIgroup\fR] [\fB\-h\fR\ \fIhost\fR] @@ -81,7 +81,7 @@ .br .HP 9n \fBsudoedit\fR -[\fB\-ABknS\fR] +[\fB\-ABkNnS\fR] .if \n(BA [\fB\-a\fR\ \fItype\fR] [\fB\-C\fR\ \fInum\fR] .if \n(LC [\fB\-c\fR\ \fIclass\fR] @@ -532,6 +532,22 @@ is specified but not allowed by the policy, \fBsudo\fR will exit with a status value of 1. .TP 12n +\fB\-N\fR, \fB\--no-update\fR +Do not update the user's cached credentials, even if the user successfully +authenticates. +Unlike the +\fB\-k\fR +flag, existing cached credentials are used if they are valid. +To detect when the user's cached credentials are valid (or when no +authentication is required), the following command can be used: +.RS 18n +sudo -Nnv +.RE +.RS 12n +.sp +Not all security policies support credential caching. +.RE +.TP 12n \fB\-n\fR, \fB\--non-interactive\fR Avoid prompting the user for input of any kind. If a password is required for the command to run, diff --git a/docs/sudo.mdoc.in b/docs/sudo.mdoc.in index 3b1914aa2..f5a687e74 100644 --- a/docs/sudo.mdoc.in +++ b/docs/sudo.mdoc.in @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd February 16, 2022 +.Dd August 2, 2022 .Dt SUDO @mansectsu@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -36,7 +36,7 @@ .Fl h | K | k | V .Nm sudo .Fl v -.Op Fl ABknS +.Op Fl ABkNnS .if \n(BA \{\ .Op Fl a Ar type .\} @@ -46,7 +46,7 @@ .Op Fl u Ar user .Nm sudo .Fl l -.Op Fl ABknS +.Op Fl ABkNnS .if \n(BA \{\ .Op Fl a Ar type .\} @@ -80,7 +80,7 @@ .Op Fl i | s .Op Ar command .Nm sudoedit -.Op Fl ABknS +.Op Fl ABkNnS .if \n(BA \{\ .Op Fl a Ar type .\} @@ -505,6 +505,17 @@ If a is specified but not allowed by the policy, .Nm will exit with a status value of 1. +.It Fl N , -no-update +Do not update the user's cached credentials, even if the user successfully +authenticates. +Unlike the +.Fl k +flag, existing cached credentials are used if they are valid. +To detect when the user's cached credentials are valid (or when no +authentication is required), the following command can be used: +.Dl sudo -Nnv +.Pp +Not all security policies support credential caching. .It Fl n , -non-interactive Avoid prompting the user for input of any kind. If a password is required for the command to run, diff --git a/plugins/sudoers/check.c b/plugins/sudoers/check.c index 6f5d9cc0a..d6ef6be2a 100644 --- a/plugins/sudoers/check.c +++ b/plugins/sudoers/check.c @@ -215,8 +215,8 @@ done: * Only update time stamp if user validated and was approved. * Failure to update the time stamp is not a fatal error. */ - if (ret == true && closure.tstat != TS_ERROR) { - if (ISSET(validated, VALIDATE_SUCCESS)) + if (ret == true && ISSET(validated, VALIDATE_SUCCESS)) { + if (ISSET(mode, MODE_UPDATE_TICKET) && closure.tstat != TS_ERROR) (void)timestamp_update(closure.cookie, closure.auth_pw); } } diff --git a/plugins/sudoers/policy.c b/plugins/sudoers/policy.c index fe2c20d73..a7d79a6da 100644 --- a/plugins/sudoers/policy.c +++ b/plugins/sudoers/policy.c @@ -84,11 +84,11 @@ parse_bool(const char *line, int varlen, int *flags, int fval) } } -#define RUN_VALID_FLAGS (MODE_ASKPASS|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_IMPLIED_SHELL|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_PRESERVE_GROUPS|MODE_SHELL|MODE_RUN|MODE_POLICY_INTERCEPTED) -#define EDIT_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_EDIT) -#define LIST_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_LIST|MODE_CHECK) -#define VALIDATE_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_VALIDATE) -#define INVALIDATE_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_INVALIDATE) +#define RUN_VALID_FLAGS (MODE_ASKPASS|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_IMPLIED_SHELL|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_UPDATE_TICKET|MODE_PRESERVE_GROUPS|MODE_SHELL|MODE_RUN|MODE_POLICY_INTERCEPTED) +#define EDIT_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_UPDATE_TICKET|MODE_EDIT) +#define LIST_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_UPDATE_TICKET|MODE_LIST|MODE_CHECK) +#define VALIDATE_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_UPDATE_TICKET|MODE_VALIDATE) +#define INVALIDATE_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_UPDATE_TICKET|MODE_INVALIDATE) /* * Deserialize args, settings and user_info arrays. @@ -102,7 +102,7 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults) const char *remhost = NULL; unsigned char uuid[16]; char * const *cur; - int flags = 0; + int flags = MODE_UPDATE_TICKET; debug_decl(sudoers_policy_deserialize_info, SUDOERS_DEBUG_PLUGIN); #define MATCHES(s, v) \ @@ -280,6 +280,12 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults) goto bad; continue; } + if (MATCHES(*cur, "update_ticket=")) { + if (parse_bool(*cur, sizeof("update_ticket") -1, &flags, + MODE_UPDATE_TICKET) == -1) + goto bad; + continue; + } if (MATCHES(*cur, "noninteractive=")) { if (parse_bool(*cur, sizeof("noninteractive") - 1, &flags, MODE_NONINTERACTIVE) == -1) @@ -395,6 +401,9 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults) } #endif } + /* Ignore ticket trumps update. */ + if (ISSET(flags, MODE_IGNORE_TICKET)) + CLR(flags, MODE_UPDATE_TICKET); user_gid = (gid_t)-1; user_sid = (pid_t)-1; diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index da6d8cde0..e27067480 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -201,7 +201,8 @@ struct sudo_user { #define MODE_PRESERVE_ENV 0x00400000 #define MODE_NONINTERACTIVE 0x00800000 #define MODE_IGNORE_TICKET 0x01000000 -#define MODE_POLICY_INTERCEPTED 0x02000000 +#define MODE_UPDATE_TICKET 0x02000000 +#define MODE_POLICY_INTERCEPTED 0x04000000 /* Mode bits allowed for intercepted commands. */ #define MODE_INTERCEPT_MASK (MODE_RUN|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_POLICY_INTERCEPTED) diff --git a/src/parse_args.c b/src/parse_args.c index ae8af5dd2..fefd9edd6 100644 --- a/src/parse_args.c +++ b/src/parse_args.c @@ -49,6 +49,7 @@ int tgetpass_flags; */ static void help(void) __attribute__((__noreturn__)); static void usage_excl(void) __attribute__((__noreturn__)); +static void usage_excl_ticket(void) __attribute__((__noreturn__)); /* * Mapping of command line flags to name/value settings. @@ -63,6 +64,7 @@ static struct sudo_settings sudo_settings[] = { { "run_shell" }, { "login_shell" }, { "ignore_ticket" }, + { "update_ticket" }, { "prompt" }, { "selinux_role" }, { "selinux_type" }, @@ -111,8 +113,8 @@ struct environment { * There is a more limited set of options for sudoedit (the sudo-specific * long options are listed first). */ -static const char sudo_short_opts[] = "+Aa:BbC:c:D:Eeg:Hh::iKklnPp:R:r:SsT:t:U:u:Vv"; -static const char edit_short_opts[] = "+Aa:BC:c:D:g:h::knp:R:r:ST:t:u:V"; +static const char sudo_short_opts[] = "+Aa:BbC:c:D:Eeg:Hh::iKklNnPp:R:r:SsT:t:U:u:Vv"; +static const char edit_short_opts[] = "+Aa:BC:c:D:g:h::KkNnp:R:r:ST:t:u:V"; static struct option sudo_long_opts[] = { /* sudo-specific long options */ { "background", no_argument, NULL, 'b' }, @@ -137,6 +139,7 @@ static struct option sudo_long_opts[] = { { "help", no_argument, NULL, 'h' }, { "host", required_argument, NULL, OPT_HOSTNAME }, { "reset-timestamp", no_argument, NULL, 'k' }, + { "no-update", no_argument, NULL, 'N' }, { "non-interactive", no_argument, NULL, 'n' }, { "prompt", required_argument, NULL, 'p' }, { "chroot", required_argument, NULL, 'R' }, @@ -403,15 +406,16 @@ parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv, sudo_settings[ARG_LOGIN_SHELL].value = "true"; SET(flags, MODE_LOGIN_SHELL); break; - case 'k': - sudo_settings[ARG_IGNORE_TICKET].value = "true"; - break; case 'K': - sudo_settings[ARG_IGNORE_TICKET].value = "true"; if (mode && mode != MODE_KILL) usage_excl(); mode = MODE_KILL; valid_flags = 0; + FALLTHROUGH; + case 'k': + if (sudo_settings[ARG_UPDATE_TICKET].value != NULL) + usage_excl_ticket(); + sudo_settings[ARG_IGNORE_TICKET].value = "true"; break; case 'l': if (mode) { @@ -423,6 +427,11 @@ parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv, mode = MODE_LIST; valid_flags = LIST_VALID_FLAGS; break; + case 'N': + if (sudo_settings[ARG_IGNORE_TICKET].value != NULL) + usage_excl_ticket(); + sudo_settings[ARG_UPDATE_TICKET].value = "false"; + break; case 'n': SET(flags, MODE_NONINTERACTIVE); sudo_settings[ARG_NONINTERACTIVE].value = "true"; @@ -758,6 +767,19 @@ usage_excl(void) usage(); } +/* + * Tell which options are mutually exclusive and exit. + */ +static void +usage_excl_ticket(void) +{ + debug_decl(usage_excl_ticket, SUDO_DEBUG_ARGS); + + sudo_warnx("%s", + U_("Only one of the -K, -k or -N options may be specified")); + usage(); +} + static void help(void) { diff --git a/src/sudo.c b/src/sudo.c index 909700443..18beb1bd8 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -1065,6 +1065,12 @@ format_plugin_settings(struct plugin_container *plugin) unsigned int i = 0; debug_decl(format_plugin_settings, SUDO_DEBUG_PCOMM); + /* We update the ticket entry by default. */ + if (sudo_settings[ARG_IGNORE_TICKET].value == NULL && + sudo_settings[ARG_UPDATE_TICKET].value == NULL) { + sudo_settings[ARG_UPDATE_TICKET].value = "true"; + } + /* Determine sudo_settings array size (including plugin_path and NULL) */ plugin_settings_size = 2; for (setting = sudo_settings; setting->name != NULL; setting++) diff --git a/src/sudo.h b/src/sudo.h index f6b7a9130..c0d2dcfbb 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -84,27 +84,28 @@ #define ARG_USER_SHELL 5 #define ARG_LOGIN_SHELL 6 #define ARG_IGNORE_TICKET 7 -#define ARG_PROMPT 8 -#define ARG_SELINUX_ROLE 9 -#define ARG_SELINUX_TYPE 10 -#define ARG_RUNAS_USER 11 -#define ARG_PROGNAME 12 -#define ARG_IMPLIED_SHELL 13 -#define ARG_PRESERVE_GROUPS 14 -#define ARG_NONINTERACTIVE 15 -#define ARG_SUDOEDIT 16 -#define ARG_CLOSEFROM 17 -#define ARG_NET_ADDRS 18 -#define ARG_MAX_GROUPS 19 -#define ARG_PLUGIN_DIR 20 -#define ARG_REMOTE_HOST 21 -#define ARG_TIMEOUT 22 -#define ARG_CHROOT 23 -#define ARG_CWD 24 -#define ARG_ASKPASS 25 -#define ARG_INTERCEPT_SETID 26 -#define ARG_INTERCEPT_PTRACE 27 -#define ARG_APPARMOR_PROFILE 28 +#define ARG_UPDATE_TICKET 8 +#define ARG_PROMPT 9 +#define ARG_SELINUX_ROLE 10 +#define ARG_SELINUX_TYPE 11 +#define ARG_RUNAS_USER 12 +#define ARG_PROGNAME 13 +#define ARG_IMPLIED_SHELL 14 +#define ARG_PRESERVE_GROUPS 15 +#define ARG_NONINTERACTIVE 16 +#define ARG_SUDOEDIT 17 +#define ARG_CLOSEFROM 18 +#define ARG_NET_ADDRS 19 +#define ARG_MAX_GROUPS 20 +#define ARG_PLUGIN_DIR 21 +#define ARG_REMOTE_HOST 22 +#define ARG_TIMEOUT 23 +#define ARG_CHROOT 24 +#define ARG_CWD 25 +#define ARG_ASKPASS 26 +#define ARG_INTERCEPT_SETID 27 +#define ARG_INTERCEPT_PTRACE 28 +#define ARG_APPARMOR_PROFILE 29 /* * Flags for tgetpass() diff --git a/src/sudo_usage.h.in b/src/sudo_usage.h.in index fce40d30c..9b85a8580 100644 --- a/src/sudo_usage.h.in +++ b/src/sudo_usage.h.in @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2007-2010, 2013, 2015, 2017, 2020 + * Copyright (c) 2007-2010, 2013, 2015, 2017, 2020-2022 * Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any @@ -26,10 +26,10 @@ */ #define SUDO_USAGE0 " -h | -V" #define SUDO_USAGE1 " -h | -K | -k | -V" -#define SUDO_USAGE2 " -v [-ABknS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-u user]" -#define SUDO_USAGE3 " -l [-ABknS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-U user] [-u user] [command]" -#define SUDO_USAGE4 " [-ABbEHknPS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] [-D directory] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] [VAR=value] [-i|-s] []" -#define SUDO_USAGE5 " -e [-ABknS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-D directory] [-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] file ..." +#define SUDO_USAGE2 " -v [-ABkNnS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-u user]" +#define SUDO_USAGE3 " -l [-ABkNnS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-U user] [-u user] [command]" +#define SUDO_USAGE4 " [-ABbEHkNnPS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] [-D directory] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] [VAR=value] [-i|-s] []" +#define SUDO_USAGE5 " -e [-ABkNnS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-D directory] [-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] file ..." /* * Configure script arguments used to build sudo.