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
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:
AUTH_SUCCESS Function succeeded. For a ``verify'' function
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
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:
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;
debug_decl(sudo_afs_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* Try to just check the password */
ka_StringToKey(pass, NULL, &afs_key);
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;
debug_decl(sudo_aix_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
do {
pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback);
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;
debug_decl(bsdauth_verify, SUDOERS_DEBUG_AUTH);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* save old signal handler */
sigemptyset(&sa.sa_mask);
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;
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
* 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)
debug_return_int(AUTH_SUCCESS);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
if ((confp = cfg_read("sudo")) == (Cfg *)-1) {
sudo_warnx("%s", U_("unable to read fwtk config"));
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 char *def_prompt = PASSPROMPT;
static bool getpass_error;
static bool noninteractive;
static pam_handle_t *pamh;
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);
}
/* Stash value of noninteractive flag for conversation function. */
noninteractive = IS_NONINTERACTIVE(auth);
/* Initial PAM. */
pam_service = ISSET(sudo_mode, MODE_LOGIN_SHELL) ?
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) {
/* error or ^C from tgetpass() */
debug_return_int(AUTH_INTR);
debug_return_int(noninteractive ? AUTH_NONINTERACTIVE : AUTH_INTR);
}
switch (*pam_status) {
case PAM_SUCCESS:
@@ -707,6 +711,13 @@ converse(int num_msg, PAM_CONST struct pam_message **msg,
if (getpass_error)
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. */
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)
debug_return_int(AUTH_SUCCESS);
if (IS_NONINTERACTIVE(auth))
debug_return_int(AUTH_NONINTERACTIVE);
/* Start communications */
if (AceInitialize() == SD_FALSE) {
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;
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 */
pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback);
if (pass == NULL)

View File

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

View File

@@ -24,6 +24,7 @@
#define AUTH_FAILURE 1
#define AUTH_INTR 2
#define AUTH_FATAL 3
#define AUTH_NONINTERACTIVE 4
typedef struct sudo_auth {
int flags; /* various flags, see below */
@@ -43,11 +44,13 @@ typedef struct sudo_auth {
#define FLAG_DISABLED 0x02 /* method disabled */
#define FLAG_STANDALONE 0x04 /* standalone 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. */
#define IS_DISABLED(x) ((x)->flags & FLAG_DISABLED)
#define IS_STANDALONE(x) ((x)->flags & FLAG_STANDALONE)
#define IS_ONEANDONLY(x) ((x)->flags & FLAG_ONEANDONLY)
#define IS_NONINTERACTIVE(x) ((x)->flags & FLAG_NONINTERACTIVE)
/* Like tgetpass() but uses conversation function */
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;
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. */
lectured = display_lecture(closure->tstat);
@@ -170,7 +163,7 @@ check_user(int validated, int mode)
*/
if ((closure.auth_pw = get_authpw(mode)) == NULL)
goto done;
if (sudo_auth_init(closure.auth_pw) == -1)
if (sudo_auth_init(closure.auth_pw, mode) == -1)
goto done;
/*

View File

@@ -435,7 +435,7 @@ log_auth_failure(int status, unsigned int tries)
audit_failure(NewArgv, "%s", N_("authentication failure"));
/* 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;
/*

View File

@@ -157,7 +157,7 @@ struct sudo_user {
#define FLAG_NO_USER 0x020
#define FLAG_NO_HOST 0x040
#define FLAG_NO_CHECK 0x080
#define FLAG_NON_INTERACTIVE 0x100
#define FLAG_NO_USER_INPUT 0x100
#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 sudo_auth_begin_session(struct passwd *pw, char **user_env[]);
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_cleanup(struct passwd *pw, bool force);