Push non-interactive mode checking down into the auth methods.

For "sudo -n" we only want to reject a command if user input is
actually required.  In the case of PAM at least, we may not need
to interact with the user.  Bug #956, GitHub issue #83
This commit is contained in:
Todd C. Miller
2022-01-04 18:57:36 -07:00
parent 296d876b76
commit 521ef37aea
14 changed files with 72 additions and 21 deletions

View File

@@ -45,6 +45,10 @@ Possible values of sudo_auth.flags:
to determine whether to return a fatal or nonfatal to determine whether to return a fatal or nonfatal
error. error.
FLAG_NONINTERACTIVE If set, this indicates that the user invoked
sudo with the -n option and no user interaction
is allowed.
The member functions can return the following values: The member functions can return the following values:
AUTH_SUCCESS Function succeeded. For a ``verify'' function AUTH_SUCCESS Function succeeded. For a ``verify'' function
this means the user correctly authenticated. this means the user correctly authenticated.
@@ -59,6 +63,14 @@ The member functions can return the following values:
When verify_user() gets AUTH_FATAL from an auth When verify_user() gets AUTH_FATAL from an auth
function it does an exit(1). function it does an exit(1).
AUTH_INTR An attempt to read the password read was interrupted.
Usually this means the user entered ^C at the
password prompt.
AUTH_NONINTERACTIVE Function failed because user interaction was
required but sudo was run in non-interactive
mode.
The functions in the struct are as follows: The functions in the struct are as follows:
int init(struct passwd *pw, sudo_auth *auth) int init(struct passwd *pw, sudo_auth *auth)

View File

@@ -50,6 +50,9 @@ sudo_afs_verify(struct passwd *pw, char *pass, sudo_auth *auth, struct sudo_conv
struct ktc_token afs_token; struct ktc_token afs_token;
debug_decl(sudo_afs_verify, SUDOERS_DEBUG_AUTH); debug_decl(sudo_afs_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* Try to just check the password */ /* Try to just check the password */
ka_StringToKey(pass, NULL, &afs_key); ka_StringToKey(pass, NULL, &afs_key);
if (ka_GetAdminToken(pw->pw_name, /* name */ if (ka_GetAdminToken(pw->pw_name, /* name */

View File

@@ -236,6 +236,9 @@ sudo_aix_verify(struct passwd *pw, char *prompt, sudo_auth *auth, struct sudo_co
int ret = AUTH_SUCCESS; int ret = AUTH_SUCCESS;
debug_decl(sudo_aix_verify, SUDOERS_DEBUG_AUTH); debug_decl(sudo_aix_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
do { do {
pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback); pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback);
if (pass == NULL) if (pass == NULL)

View File

@@ -114,6 +114,9 @@ bsdauth_verify(struct passwd *pw, char *prompt, sudo_auth *auth, struct sudo_con
auth_session_t *as = ((struct bsdauth_state *) auth->data)->as; auth_session_t *as = ((struct bsdauth_state *) auth->data)->as;
debug_decl(bsdauth_verify, SUDOERS_DEBUG_AUTH); debug_decl(bsdauth_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* save old signal handler */ /* save old signal handler */
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;

View File

@@ -68,6 +68,9 @@ sudo_dce_verify(struct passwd *pw, char *plain_pw, sudo_auth *auth, struct sudo_
error_status_t status; error_status_t status;
debug_decl(sudo_dce_verify, SUDOERS_DEBUG_AUTH); debug_decl(sudo_dce_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* /*
* Create the local context of the DCE principal necessary * Create the local context of the DCE principal necessary
* to perform authenticated network operations. The network * to perform authenticated network operations. The network

View File

@@ -54,6 +54,9 @@ sudo_fwtk_init(struct passwd *pw, sudo_auth *auth)
if (auth->data != NULL) if (auth->data != NULL)
debug_return_int(AUTH_SUCCESS); debug_return_int(AUTH_SUCCESS);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
if ((confp = cfg_read("sudo")) == (Cfg *)-1) { if ((confp = cfg_read("sudo")) == (Cfg *)-1) {
sudo_warnx("%s", U_("unable to read fwtk config")); sudo_warnx("%s", U_("unable to read fwtk config"));
debug_return_int(AUTH_FATAL); debug_return_int(AUTH_FATAL);

View File

@@ -92,6 +92,7 @@ static struct sudo_conv_callback *conv_callback;
static struct pam_conv pam_conv = { converse, &conv_callback }; static struct pam_conv pam_conv = { converse, &conv_callback };
static char *def_prompt = PASSPROMPT; static char *def_prompt = PASSPROMPT;
static bool getpass_error; static bool getpass_error;
static bool noninteractive;
static pam_handle_t *pamh; static pam_handle_t *pamh;
static struct conv_filter *conv_filter; static struct conv_filter *conv_filter;
@@ -203,6 +204,9 @@ sudo_pam_init2(struct passwd *pw, sudo_auth *auth, bool quiet)
debug_return_int(AUTH_SUCCESS); debug_return_int(AUTH_SUCCESS);
} }
/* Stash value of noninteractive flag for conversation function. */
noninteractive = IS_NONINTERACTIVE(auth);
/* Initial PAM. */ /* Initial PAM. */
pam_service = ISSET(sudo_mode, MODE_LOGIN_SHELL) ? pam_service = ISSET(sudo_mode, MODE_LOGIN_SHELL) ?
def_pam_login_service : def_pam_service; def_pam_login_service : def_pam_service;
@@ -321,7 +325,7 @@ sudo_pam_verify(struct passwd *pw, char *prompt, sudo_auth *auth, struct sudo_co
if (getpass_error) { if (getpass_error) {
/* error or ^C from tgetpass() */ /* error or ^C from tgetpass() */
debug_return_int(AUTH_INTR); debug_return_int(noninteractive ? AUTH_NONINTERACTIVE : AUTH_INTR);
} }
switch (*pam_status) { switch (*pam_status) {
case PAM_SUCCESS: case PAM_SUCCESS:
@@ -707,6 +711,13 @@ converse(int num_msg, PAM_CONST struct pam_message **msg,
if (getpass_error) if (getpass_error)
goto done; goto done;
/* Treat non-interactive mode as a getpass error. */
if (noninteractive) {
getpass_error = true;
ret = PAM_CONV_ERR;
goto done;
}
/* Choose either the sudo prompt or the PAM one. */ /* Choose either the sudo prompt or the PAM one. */
prompt = use_pam_prompt(pm->msg) ? pm->msg : def_prompt; prompt = use_pam_prompt(pm->msg) ? pm->msg : def_prompt;

View File

@@ -67,6 +67,9 @@ sudo_securid_init(struct passwd *pw, sudo_auth *auth)
if (auth->data != NULL) if (auth->data != NULL)
debug_return_int(AUTH_SUCCESS); debug_return_int(AUTH_SUCCESS);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* Start communications */ /* Start communications */
if (AceInitialize() == SD_FALSE) { if (AceInitialize() == SD_FALSE) {
sudo_warnx("%s", U_("failed to initialise the ACE API library")); sudo_warnx("%s", U_("failed to initialise the ACE API library"));

View File

@@ -83,6 +83,9 @@ sudo_sia_verify(struct passwd *pw, char *prompt, sudo_auth *auth,
int rc; int rc;
debug_decl(sudo_sia_verify, SUDOERS_DEBUG_AUTH); debug_decl(sudo_sia_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* Get password, return AUTH_INTR if we got ^C */ /* Get password, return AUTH_INTR if we got ^C */
pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback); pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback);
if (pass == NULL) if (pass == NULL)

View File

@@ -98,7 +98,7 @@ static bool standalone;
* Returns 0 on success and -1 on error. * Returns 0 on success and -1 on error.
*/ */
int int
sudo_auth_init(struct passwd *pw) sudo_auth_init(struct passwd *pw, int mode)
{ {
sudo_auth *auth; sudo_auth *auth;
int status = AUTH_SUCCESS; int status = AUTH_SUCCESS;
@@ -109,6 +109,8 @@ sudo_auth_init(struct passwd *pw)
/* Initialize auth methods and unconfigure the method if necessary. */ /* Initialize auth methods and unconfigure the method if necessary. */
for (auth = auth_switch; auth->name; auth++) { for (auth = auth_switch; auth->name; auth++) {
if (ISSET(mode, MODE_NONINTERACTIVE))
SET(auth->flags, FLAG_NONINTERACTIVE);
if (auth->init && !IS_DISABLED(auth)) { if (auth->init && !IS_DISABLED(auth)) {
/* Disable if it failed to init unless there was a fatal error. */ /* Disable if it failed to init unless there was a fatal error. */
status = (auth->init)(pw, auth); status = (auth->init)(pw, auth);
@@ -297,6 +299,8 @@ verify_user(struct passwd *pw, char *prompt, int validated,
status = (auth->setup)(pw, &prompt, auth); status = (auth->setup)(pw, &prompt, auth);
if (status == AUTH_FAILURE) if (status == AUTH_FAILURE)
SET(auth->flags, FLAG_DISABLED); SET(auth->flags, FLAG_DISABLED);
else if (status == AUTH_NONINTERACTIVE)
goto done;
else if (status == AUTH_FATAL || user_interrupted()) else if (status == AUTH_FATAL || user_interrupted())
goto done; /* assume error msg already printed */ goto done; /* assume error msg already printed */
} }
@@ -310,6 +314,10 @@ verify_user(struct passwd *pw, char *prompt, int validated,
/* Get the password unless the auth function will do it for us */ /* Get the password unless the auth function will do it for us */
if (!standalone) { if (!standalone) {
if (IS_NONINTERACTIVE(auth_switch)) {
status = AUTH_NONINTERACTIVE;
goto done;
}
pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback); pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback);
if (pass == NULL) if (pass == NULL)
break; break;
@@ -344,10 +352,13 @@ done:
case AUTH_INTR: case AUTH_INTR:
case AUTH_FAILURE: case AUTH_FAILURE:
if (ntries != 0) if (ntries != 0)
validated |= FLAG_BAD_PASSWORD; SET(validated, FLAG_BAD_PASSWORD);
log_auth_failure(validated, ntries); log_auth_failure(validated, ntries);
ret = false; ret = false;
break; break;
case AUTH_NONINTERACTIVE:
SET(validated, FLAG_NO_USER_INPUT);
FALLTHROUGH;
case AUTH_FATAL: case AUTH_FATAL:
default: default:
log_auth_failure(validated, 0); log_auth_failure(validated, 0);

View File

@@ -20,10 +20,11 @@
#define SUDO_AUTH_H #define SUDO_AUTH_H
/* Auth function return values. */ /* Auth function return values. */
#define AUTH_SUCCESS 0 #define AUTH_SUCCESS 0
#define AUTH_FAILURE 1 #define AUTH_FAILURE 1
#define AUTH_INTR 2 #define AUTH_INTR 2
#define AUTH_FATAL 3 #define AUTH_FATAL 3
#define AUTH_NONINTERACTIVE 4
typedef struct sudo_auth { typedef struct sudo_auth {
int flags; /* various flags, see below */ int flags; /* various flags, see below */
@@ -40,14 +41,16 @@ typedef struct sudo_auth {
} sudo_auth; } sudo_auth;
/* Values for sudo_auth.flags. */ /* Values for sudo_auth.flags. */
#define FLAG_DISABLED 0x02 /* method disabled */ #define FLAG_DISABLED 0x02 /* method disabled */
#define FLAG_STANDALONE 0x04 /* standalone auth method */ #define FLAG_STANDALONE 0x04 /* standalone auth method */
#define FLAG_ONEANDONLY 0x08 /* one and only auth method */ #define FLAG_ONEANDONLY 0x08 /* one and only auth method */
#define FLAG_NONINTERACTIVE 0x10 /* no user input allowed */
/* Shortcuts for using the flags above. */ /* Shortcuts for using the flags above. */
#define IS_DISABLED(x) ((x)->flags & FLAG_DISABLED) #define IS_DISABLED(x) ((x)->flags & FLAG_DISABLED)
#define IS_STANDALONE(x) ((x)->flags & FLAG_STANDALONE) #define IS_STANDALONE(x) ((x)->flags & FLAG_STANDALONE)
#define IS_ONEANDONLY(x) ((x)->flags & FLAG_ONEANDONLY) #define IS_ONEANDONLY(x) ((x)->flags & FLAG_ONEANDONLY)
#define IS_NONINTERACTIVE(x) ((x)->flags & FLAG_NONINTERACTIVE)
/* Like tgetpass() but uses conversation function */ /* Like tgetpass() but uses conversation function */
char *auth_getpass(const char *prompt, int type, struct sudo_conv_callback *callback); char *auth_getpass(const char *prompt, int type, struct sudo_conv_callback *callback);

View File

@@ -125,13 +125,6 @@ check_user_interactive(int validated, int mode, struct getpass_closure *closure)
FALLTHROUGH; FALLTHROUGH;
default: default:
/* Bail out if we are non-interactive and a password is required */
if (ISSET(mode, MODE_NONINTERACTIVE)) {
validated |= FLAG_NON_INTERACTIVE;
log_auth_failure(validated, 0);
goto done;
}
/* XXX - should not lecture if askpass helper is being used. */ /* XXX - should not lecture if askpass helper is being used. */
lectured = display_lecture(closure->tstat); lectured = display_lecture(closure->tstat);
@@ -170,7 +163,7 @@ check_user(int validated, int mode)
*/ */
if ((closure.auth_pw = get_authpw(mode)) == NULL) if ((closure.auth_pw = get_authpw(mode)) == NULL)
goto done; goto done;
if (sudo_auth_init(closure.auth_pw) == -1) if (sudo_auth_init(closure.auth_pw, mode) == -1)
goto done; goto done;
/* /*

View File

@@ -435,7 +435,7 @@ log_auth_failure(int status, unsigned int tries)
audit_failure(NewArgv, "%s", N_("authentication failure")); audit_failure(NewArgv, "%s", N_("authentication failure"));
/* If sudoers denied the command we'll log that separately. */ /* If sudoers denied the command we'll log that separately. */
if (!ISSET(status, FLAG_BAD_PASSWORD|FLAG_NON_INTERACTIVE)) if (!ISSET(status, FLAG_BAD_PASSWORD|FLAG_NO_USER_INPUT))
logit = false; logit = false;
/* /*

View File

@@ -157,7 +157,7 @@ struct sudo_user {
#define FLAG_NO_USER 0x020 #define FLAG_NO_USER 0x020
#define FLAG_NO_HOST 0x040 #define FLAG_NO_HOST 0x040
#define FLAG_NO_CHECK 0x080 #define FLAG_NO_CHECK 0x080
#define FLAG_NON_INTERACTIVE 0x100 #define FLAG_NO_USER_INPUT 0x100
#define FLAG_BAD_PASSWORD 0x200 #define FLAG_BAD_PASSWORD 0x200
/* /*
@@ -297,7 +297,7 @@ bool sudo_auth_needs_end_session(void);
int verify_user(struct passwd *pw, char *prompt, int validated, struct sudo_conv_callback *callback); int verify_user(struct passwd *pw, char *prompt, int validated, struct sudo_conv_callback *callback);
int sudo_auth_begin_session(struct passwd *pw, char **user_env[]); int sudo_auth_begin_session(struct passwd *pw, char **user_env[]);
int sudo_auth_end_session(struct passwd *pw); int sudo_auth_end_session(struct passwd *pw);
int sudo_auth_init(struct passwd *pw); int sudo_auth_init(struct passwd *pw, int mode);
int sudo_auth_approval(struct passwd *pw, int validated, bool exempt); int sudo_auth_approval(struct passwd *pw, int validated, bool exempt);
int sudo_auth_cleanup(struct passwd *pw, bool force); int sudo_auth_cleanup(struct passwd *pw, bool force);