Add a way to run a command without updating the cached credentials.

This can also be used to test for whether or not the user's
credentials are currently cached.
This commit is contained in:
Todd C. Miller
2022-08-02 14:28:28 -06:00
parent 2d94d329cf
commit 556dacf1ff
9 changed files with 115 additions and 49 deletions

View File

@@ -25,7 +25,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .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 .nh
.if n .ad l .if n .ad l
.SH "NAME" .SH "NAME"
@@ -41,7 +41,7 @@
.HP 5n .HP 5n
\fBsudo\fR \fBsudo\fR
\fB\-v\fR \fB\-v\fR
[\fB\-ABknS\fR] [\fB\-ABkNnS\fR]
.if \n(BA [\fB\-a\fR\ \fItype\fR] .if \n(BA [\fB\-a\fR\ \fItype\fR]
[\fB\-g\fR\ \fIgroup\fR] [\fB\-g\fR\ \fIgroup\fR]
[\fB\-h\fR\ \fIhost\fR] [\fB\-h\fR\ \fIhost\fR]
@@ -51,7 +51,7 @@
.HP 5n .HP 5n
\fBsudo\fR \fBsudo\fR
\fB\-l\fR \fB\-l\fR
[\fB\-ABknS\fR] [\fB\-ABkNnS\fR]
.if \n(BA [\fB\-a\fR\ \fItype\fR] .if \n(BA [\fB\-a\fR\ \fItype\fR]
[\fB\-g\fR\ \fIgroup\fR] [\fB\-g\fR\ \fIgroup\fR]
[\fB\-h\fR\ \fIhost\fR] [\fB\-h\fR\ \fIhost\fR]
@@ -81,7 +81,7 @@
.br .br
.HP 9n .HP 9n
\fBsudoedit\fR \fBsudoedit\fR
[\fB\-ABknS\fR] [\fB\-ABkNnS\fR]
.if \n(BA [\fB\-a\fR\ \fItype\fR] .if \n(BA [\fB\-a\fR\ \fItype\fR]
[\fB\-C\fR\ \fInum\fR] [\fB\-C\fR\ \fInum\fR]
.if \n(LC [\fB\-c\fR\ \fIclass\fR] .if \n(LC [\fB\-c\fR\ \fIclass\fR]
@@ -532,6 +532,22 @@ is specified but not allowed by the policy,
\fBsudo\fR \fBsudo\fR
will exit with a status value of 1. will exit with a status value of 1.
.TP 12n .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 \fB\-n\fR, \fB\--non-interactive\fR
Avoid prompting the user for input of any kind. Avoid prompting the user for input of any kind.
If a password is required for the command to run, If a password is required for the command to run,

View File

@@ -24,7 +24,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .nr PS @PSMAN@
.Dd February 16, 2022 .Dd August 2, 2022
.Dt SUDO @mansectsu@ .Dt SUDO @mansectsu@
.Os Sudo @PACKAGE_VERSION@ .Os Sudo @PACKAGE_VERSION@
.Sh NAME .Sh NAME
@@ -36,7 +36,7 @@
.Fl h | K | k | V .Fl h | K | k | V
.Nm sudo .Nm sudo
.Fl v .Fl v
.Op Fl ABknS .Op Fl ABkNnS
.if \n(BA \{\ .if \n(BA \{\
.Op Fl a Ar type .Op Fl a Ar type
.\} .\}
@@ -46,7 +46,7 @@
.Op Fl u Ar user .Op Fl u Ar user
.Nm sudo .Nm sudo
.Fl l .Fl l
.Op Fl ABknS .Op Fl ABkNnS
.if \n(BA \{\ .if \n(BA \{\
.Op Fl a Ar type .Op Fl a Ar type
.\} .\}
@@ -80,7 +80,7 @@
.Op Fl i | s .Op Fl i | s
.Op Ar command .Op Ar command
.Nm sudoedit .Nm sudoedit
.Op Fl ABknS .Op Fl ABkNnS
.if \n(BA \{\ .if \n(BA \{\
.Op Fl a Ar type .Op Fl a Ar type
.\} .\}
@@ -505,6 +505,17 @@ If a
is specified but not allowed by the policy, is specified but not allowed by the policy,
.Nm .Nm
will exit with a status value of 1. 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 .It Fl n , -non-interactive
Avoid prompting the user for input of any kind. Avoid prompting the user for input of any kind.
If a password is required for the command to run, If a password is required for the command to run,

View File

@@ -215,8 +215,8 @@ done:
* Only update time stamp if user validated and was approved. * Only update time stamp if user validated and was approved.
* Failure to update the time stamp is not a fatal error. * Failure to update the time stamp is not a fatal error.
*/ */
if (ret == true && closure.tstat != TS_ERROR) { if (ret == true && ISSET(validated, VALIDATE_SUCCESS)) {
if (ISSET(validated, VALIDATE_SUCCESS)) if (ISSET(mode, MODE_UPDATE_TICKET) && closure.tstat != TS_ERROR)
(void)timestamp_update(closure.cookie, closure.auth_pw); (void)timestamp_update(closure.cookie, closure.auth_pw);
} }
} }

View File

@@ -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 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_EDIT) #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_LIST|MODE_CHECK) #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_VALIDATE) #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_INVALIDATE) #define INVALIDATE_VALID_FLAGS (MODE_ASKPASS|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_UPDATE_TICKET|MODE_INVALIDATE)
/* /*
* Deserialize args, settings and user_info arrays. * 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; const char *remhost = NULL;
unsigned char uuid[16]; unsigned char uuid[16];
char * const *cur; char * const *cur;
int flags = 0; int flags = MODE_UPDATE_TICKET;
debug_decl(sudoers_policy_deserialize_info, SUDOERS_DEBUG_PLUGIN); debug_decl(sudoers_policy_deserialize_info, SUDOERS_DEBUG_PLUGIN);
#define MATCHES(s, v) \ #define MATCHES(s, v) \
@@ -280,6 +280,12 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults)
goto bad; goto bad;
continue; 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 (MATCHES(*cur, "noninteractive=")) {
if (parse_bool(*cur, sizeof("noninteractive") - 1, &flags, if (parse_bool(*cur, sizeof("noninteractive") - 1, &flags,
MODE_NONINTERACTIVE) == -1) MODE_NONINTERACTIVE) == -1)
@@ -395,6 +401,9 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults)
} }
#endif #endif
} }
/* Ignore ticket trumps update. */
if (ISSET(flags, MODE_IGNORE_TICKET))
CLR(flags, MODE_UPDATE_TICKET);
user_gid = (gid_t)-1; user_gid = (gid_t)-1;
user_sid = (pid_t)-1; user_sid = (pid_t)-1;

View File

@@ -201,7 +201,8 @@ struct sudo_user {
#define MODE_PRESERVE_ENV 0x00400000 #define MODE_PRESERVE_ENV 0x00400000
#define MODE_NONINTERACTIVE 0x00800000 #define MODE_NONINTERACTIVE 0x00800000
#define MODE_IGNORE_TICKET 0x01000000 #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. */ /* Mode bits allowed for intercepted commands. */
#define MODE_INTERCEPT_MASK (MODE_RUN|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_POLICY_INTERCEPTED) #define MODE_INTERCEPT_MASK (MODE_RUN|MODE_NONINTERACTIVE|MODE_IGNORE_TICKET|MODE_POLICY_INTERCEPTED)

View File

@@ -49,6 +49,7 @@ int tgetpass_flags;
*/ */
static void help(void) __attribute__((__noreturn__)); static void help(void) __attribute__((__noreturn__));
static void usage_excl(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. * Mapping of command line flags to name/value settings.
@@ -63,6 +64,7 @@ static struct sudo_settings sudo_settings[] = {
{ "run_shell" }, { "run_shell" },
{ "login_shell" }, { "login_shell" },
{ "ignore_ticket" }, { "ignore_ticket" },
{ "update_ticket" },
{ "prompt" }, { "prompt" },
{ "selinux_role" }, { "selinux_role" },
{ "selinux_type" }, { "selinux_type" },
@@ -111,8 +113,8 @@ struct environment {
* There is a more limited set of options for sudoedit (the sudo-specific * There is a more limited set of options for sudoedit (the sudo-specific
* long options are listed first). * 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 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::knp:R:r:ST:t:u:V"; 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[] = { static struct option sudo_long_opts[] = {
/* sudo-specific long options */ /* sudo-specific long options */
{ "background", no_argument, NULL, 'b' }, { "background", no_argument, NULL, 'b' },
@@ -137,6 +139,7 @@ static struct option sudo_long_opts[] = {
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "host", required_argument, NULL, OPT_HOSTNAME }, { "host", required_argument, NULL, OPT_HOSTNAME },
{ "reset-timestamp", no_argument, NULL, 'k' }, { "reset-timestamp", no_argument, NULL, 'k' },
{ "no-update", no_argument, NULL, 'N' },
{ "non-interactive", no_argument, NULL, 'n' }, { "non-interactive", no_argument, NULL, 'n' },
{ "prompt", required_argument, NULL, 'p' }, { "prompt", required_argument, NULL, 'p' },
{ "chroot", required_argument, NULL, 'R' }, { "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"; sudo_settings[ARG_LOGIN_SHELL].value = "true";
SET(flags, MODE_LOGIN_SHELL); SET(flags, MODE_LOGIN_SHELL);
break; break;
case 'k':
sudo_settings[ARG_IGNORE_TICKET].value = "true";
break;
case 'K': case 'K':
sudo_settings[ARG_IGNORE_TICKET].value = "true";
if (mode && mode != MODE_KILL) if (mode && mode != MODE_KILL)
usage_excl(); usage_excl();
mode = MODE_KILL; mode = MODE_KILL;
valid_flags = 0; 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; break;
case 'l': case 'l':
if (mode) { if (mode) {
@@ -423,6 +427,11 @@ parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv,
mode = MODE_LIST; mode = MODE_LIST;
valid_flags = LIST_VALID_FLAGS; valid_flags = LIST_VALID_FLAGS;
break; break;
case 'N':
if (sudo_settings[ARG_IGNORE_TICKET].value != NULL)
usage_excl_ticket();
sudo_settings[ARG_UPDATE_TICKET].value = "false";
break;
case 'n': case 'n':
SET(flags, MODE_NONINTERACTIVE); SET(flags, MODE_NONINTERACTIVE);
sudo_settings[ARG_NONINTERACTIVE].value = "true"; sudo_settings[ARG_NONINTERACTIVE].value = "true";
@@ -758,6 +767,19 @@ usage_excl(void)
usage(); 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 static void
help(void) help(void)
{ {

View File

@@ -1065,6 +1065,12 @@ format_plugin_settings(struct plugin_container *plugin)
unsigned int i = 0; unsigned int i = 0;
debug_decl(format_plugin_settings, SUDO_DEBUG_PCOMM); 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) */ /* Determine sudo_settings array size (including plugin_path and NULL) */
plugin_settings_size = 2; plugin_settings_size = 2;
for (setting = sudo_settings; setting->name != NULL; setting++) for (setting = sudo_settings; setting->name != NULL; setting++)

View File

@@ -84,27 +84,28 @@
#define ARG_USER_SHELL 5 #define ARG_USER_SHELL 5
#define ARG_LOGIN_SHELL 6 #define ARG_LOGIN_SHELL 6
#define ARG_IGNORE_TICKET 7 #define ARG_IGNORE_TICKET 7
#define ARG_PROMPT 8 #define ARG_UPDATE_TICKET 8
#define ARG_SELINUX_ROLE 9 #define ARG_PROMPT 9
#define ARG_SELINUX_TYPE 10 #define ARG_SELINUX_ROLE 10
#define ARG_RUNAS_USER 11 #define ARG_SELINUX_TYPE 11
#define ARG_PROGNAME 12 #define ARG_RUNAS_USER 12
#define ARG_IMPLIED_SHELL 13 #define ARG_PROGNAME 13
#define ARG_PRESERVE_GROUPS 14 #define ARG_IMPLIED_SHELL 14
#define ARG_NONINTERACTIVE 15 #define ARG_PRESERVE_GROUPS 15
#define ARG_SUDOEDIT 16 #define ARG_NONINTERACTIVE 16
#define ARG_CLOSEFROM 17 #define ARG_SUDOEDIT 17
#define ARG_NET_ADDRS 18 #define ARG_CLOSEFROM 18
#define ARG_MAX_GROUPS 19 #define ARG_NET_ADDRS 19
#define ARG_PLUGIN_DIR 20 #define ARG_MAX_GROUPS 20
#define ARG_REMOTE_HOST 21 #define ARG_PLUGIN_DIR 21
#define ARG_TIMEOUT 22 #define ARG_REMOTE_HOST 22
#define ARG_CHROOT 23 #define ARG_TIMEOUT 23
#define ARG_CWD 24 #define ARG_CHROOT 24
#define ARG_ASKPASS 25 #define ARG_CWD 25
#define ARG_INTERCEPT_SETID 26 #define ARG_ASKPASS 26
#define ARG_INTERCEPT_PTRACE 27 #define ARG_INTERCEPT_SETID 27
#define ARG_APPARMOR_PROFILE 28 #define ARG_INTERCEPT_PTRACE 28
#define ARG_APPARMOR_PROFILE 29
/* /*
* Flags for tgetpass() * Flags for tgetpass()

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 2007-2010, 2013, 2015, 2017, 2020 * Copyright (c) 2007-2010, 2013, 2015, 2017, 2020-2022
* Todd C. Miller <Todd.Miller@sudo.ws> * Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
@@ -26,10 +26,10 @@
*/ */
#define SUDO_USAGE0 " -h | -V" #define SUDO_USAGE0 " -h | -V"
#define SUDO_USAGE1 " -h | -K | -k | -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_USAGE2 " -v [-ABkNnS] @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_USAGE3 " -l [-ABkNnS] @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] [<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] [<command>]"
#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_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. * Configure script arguments used to build sudo.