diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/sudoers/auth/API | 148 | ||||
-rw-r--r-- | plugins/sudoers/auth/afs.c | 88 | ||||
-rw-r--r-- | plugins/sudoers/auth/aix_auth.c | 314 | ||||
-rw-r--r-- | plugins/sudoers/auth/bsdauth.c | 224 | ||||
-rw-r--r-- | plugins/sudoers/auth/dce.c | 202 | ||||
-rw-r--r-- | plugins/sudoers/auth/fwtk.c | 161 | ||||
-rw-r--r-- | plugins/sudoers/auth/kerb5.c | 345 | ||||
-rw-r--r-- | plugins/sudoers/auth/pam.c | 776 | ||||
-rw-r--r-- | plugins/sudoers/auth/passwd.c | 141 | ||||
-rw-r--r-- | plugins/sudoers/auth/rfc1938.c | 141 | ||||
-rw-r--r-- | plugins/sudoers/auth/secureware.c | 116 | ||||
-rw-r--r-- | plugins/sudoers/auth/securid5.c | 237 | ||||
-rw-r--r-- | plugins/sudoers/auth/sia.c | 160 | ||||
-rw-r--r-- | plugins/sudoers/auth/sudo_auth.c | 557 | ||||
-rw-r--r-- | plugins/sudoers/auth/sudo_auth.h | 106 |
15 files changed, 3716 insertions, 0 deletions
diff --git a/plugins/sudoers/auth/API b/plugins/sudoers/auth/API new file mode 100644 index 0000000..b38a1c4 --- /dev/null +++ b/plugins/sudoers/auth/API @@ -0,0 +1,148 @@ +NOTE: the Sudo auth API is subject to change + +Purpose: to provide a simple API for authentication methods that + encapsulates things nicely without turning into a maze + of #ifdef's + +The sudo_auth struct looks like this: + +typedef struct sudo_auth { + unsigned int flags; /* various flags, see below */ + int status; /* status from verify routine */ + char *name; /* name of the method in string form */ + void *data; /* method-specific data pointer */ + + int (*init)(struct passwd *pw, sudo_auth *auth); + int (*setup)(struct passwd *pw, char **prompt, sudo_auth *auth); + int (*verify)(struct passwd *pw, const char *p, sudo_auth *auth, struct sudo_conv_callback *callback); + int (*approval)(struct passwd *pw, sudo_auth *auth); + int (*cleanup)(struct passwd *pw, sudo_auth *auth, bool force); + int (*begin_session)(struct passwd *pw, char **user_env[], struct sudo_auth *auth); + int (*end_session)(struct passwd *pw, struct sudo_auth *auth); +} sudo_auth; + +The variables in the struct are as follows: + flags Bitwise binary flags, see below. + + status Contains the return value from the last run of + the "verify" function. Starts out as AUTH_FAILURE. + + name The name of the authentication method as a C string. + + data A pointer to method-specific data. This is passed to + all the functions of an auth method and is usually + initialized in the "init" or "setup" routines. + +Possible values of sudo_auth.flags: + FLAG_DISABLED Set if an "init" or "setup" function fails. + + FLAG_STANDALONE If set, this indicates that the method must + be the only auth method configured, and that + it will prompt for the password itself. + + FLAG_ONEANDONLY If set, this indicates that the method is the + only one in use. Can be used by auth functions + 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. + + AUTH_FAILURE Function failed. If this is an ``init'' or + ``setup'' routine, the auth method will be + marked as !configured. + + AUTH_ERROR A fatal error occurred. The routine should have + written an error message to stderr and optionally + sent mail to the administrator. When verify_user() + receives AUTH_ERROR from an auth function it stops + authenticating and returns an error. + + 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) + Function to do any one-time initialization for the auth + method. All of the "init" functions are run before anything + else. + + int setup(struct passwd *pw, char **prompt, sudo_auth *auth) + Function to do method-specific setup. All the "setup" + routines are run before any of the "verify" routines. A + pointer to the prompt string may be used to add method-specific + info to the prompt. + + int verify(struct passwd *pw, char *p, sudo_auth *auth, struct sudo_conv_callback *callback) + Function to do user verification for this auth method. For + standalone auth methods ``p'' is the prompt string. For + normal auth methods, ``p'' is the password the user entered. + The callback should be passed to auth_getpass() to allow sudoers + to unlock the ticket file when sudo is suspended. + Note that standalone auth methods are responsible for + rerading the password themselves. + + int approval(struct passwd *pw, struct sudo_auth *auth) + Function to perform account management and approval *after* + the user has authenticated successfully. This function may + check for expired accounts, perform time of day restrictions, etc. + For PAM, this calls pam_acct_mgmt(). For BSD auth, it calls + auth_approval(). + + int cleanup(struct passwd *pw, sudo_auth *auth, bool force) + Function to do per-auth method cleanup. This is only run + at the end of the authentication process, after the user + has completely failed or succeeded to authenticate. + The ``auth->status'' variable contains the result of the + last authentication attempt which may be interesting. + If the force flag is set, cleanup should happen immediately. + + int begin_session(struct passwd *pw, char **user_env[], struct sudo_auth *auth) + Function to begin a user session. This is used for session handling + in PAM and SIA. + + int end_session(struct passwd *pw, struct sudo_auth *auth) + Function to end a user session. This is used for session handling + in PAM and SIA. + +A note about standalone methods. Some authentication methods can't +coexist with any others. This may be because they encapsulate other +methods (pam, sia) or because they have a special way of interacting +with the user (securid). + +Adding a new authentication method: + +Each method should live in its own file. Add prototypes for the functions +in sudo_auth.h. + +Add the method to the ``auth_switch'' in sudo_auth.c. Note that +standalone methods must go first. If ``fooauth'' is a normal auth +method, its entry would look like: + +#ifdef HAVE_FOOAUTH +AUTH_ENTRY("foo", 0, foo_init, foo_setup, foo_verify, + foo_cleanup, foo_begin_session, foo_end_session) +#endif + +If this is a standalone method, it would be: + +#ifdef HAVE_FOOAUTH +AUTH_ENTRY("foo", FLAG_STANDALONE, foo_init, foo_setup, foo_verify, + foo_cleanup, foo_begin_session, foo_end_session) +#endif + +If the method needs to run as the user, not root, add FLAG_USER to +the second argument in the AUTH_ENTRY line. If you don't have an +init/setup/cleanup/begin/end routine, just use a NULL for that +field. diff --git a/plugins/sudoers/auth/afs.c b/plugins/sudoers/auth/afs.c new file mode 100644 index 0000000..7fae207 --- /dev/null +++ b/plugins/sudoers/auth/afs.c @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999, 2001-2005, 2007, 2010-2012, 2014-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_AFS + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include <afs/stds.h> +#include <afs/kautils.h> + +#include <sudoers.h> +#include "sudo_auth.h" +#include <timestamp.h> + +int +sudo_afs_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + struct ktc_encryptionKey afs_key; + struct ktc_token afs_token; + debug_decl(sudo_afs_verify, SUDOERS_DEBUG_AUTH); + + if (IS_NONINTERACTIVE(auth)) + debug_return_int(AUTH_NONINTERACTIVE); + + /* Display lecture if needed and we haven't already done so. */ + display_lecture(callback); + + /* Try to just check the password */ + ka_StringToKey(pass, NULL, &afs_key); + if (ka_GetAdminToken(pw->pw_name, /* name */ + NULL, /* instance */ + NULL, /* realm */ + &afs_key, /* key (contains password) */ + 0, /* lifetime */ + &afs_token, /* token */ + 0) == 0) /* new */ + debug_return_int(AUTH_SUCCESS); + + /* Fall back on old method XXX - needed? */ + setpag(); + if (ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION+KA_USERAUTH_DOSETPAG, + pw->pw_name, /* name */ + NULL, /* instance */ + NULL, /* realm */ + pass, /* password */ + 0, /* lifetime */ + NULL, /* expiration ptr (unused) */ + 0, /* spare */ + NULL) == 0) /* reason */ + debug_return_int(AUTH_SUCCESS); + + debug_return_int(AUTH_FAILURE); +} + +#endif HAVE_AFS diff --git a/plugins/sudoers/auth/aix_auth.c b/plugins/sudoers/auth/aix_auth.c new file mode 100644 index 0000000..74a3bc9 --- /dev/null +++ b/plugins/sudoers/auth/aix_auth.c @@ -0,0 +1,314 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007-2018 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_AIXAUTH + +#include <sys/types.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <pwd.h> +#include <signal.h> +#include <usersec.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +/* + * For a description of the AIX authentication API, see + * http://publib16.boulder.ibm.com/doc_link/en_US/a_doc_lib/libs/basetrf1/authenticate.htm + */ + +#ifdef HAVE_PAM +# define AIX_AUTH_UNKNOWN 0 +# define AIX_AUTH_STD 1 +# define AIX_AUTH_PAM 2 + +static int +sudo_aix_authtype(void) +{ + size_t linesize = 0; + ssize_t len; + char *cp, *line = NULL; + bool in_stanza = false; + int authtype = AIX_AUTH_UNKNOWN; + FILE *fp; + debug_decl(sudo_aix_authtype, SUDOERS_DEBUG_AUTH); + + if ((fp = fopen("/etc/security/login.cfg", "r")) == NULL) + debug_return_int(AIX_AUTH_UNKNOWN); + + while ((len = getdelim(&line, &linesize, '\n', fp)) != -1) { + /* First remove comments. */ + if ((cp = strchr(line, '#')) != NULL) { + *cp = '\0'; + len = (ssize_t)(cp - line); + } + + /* Next remove trailing newlines and whitespace. */ + while (len > 0 && isspace((unsigned char)line[len - 1])) + line[--len] = '\0'; + + /* Skip blank lines. */ + if (len == 0) + continue; + + /* Match start of the usw stanza. */ + if (!in_stanza) { + if (strncmp(line, "usw:", 4) == 0) + in_stanza = true; + continue; + } + + /* Check for end of the usw stanza. */ + if (!isblank((unsigned char)line[0])) { + in_stanza = false; + break; + } + + /* Skip leading blanks. */ + cp = line; + do { + cp++; + } while (isblank((unsigned char)*cp)); + + /* Match "auth_type = (PAM_AUTH|STD_AUTH)". */ + if (strncmp(cp, "auth_type", 9) != 0) + continue; + cp += 9; + while (isblank((unsigned char)*cp)) + cp++; + if (*cp++ != '=') + continue; + while (isblank((unsigned char)*cp)) + cp++; + if (strcmp(cp, "PAM_AUTH") == 0) { + authtype = AIX_AUTH_PAM; + break; + } + if (strcmp(cp, "STD_AUTH") == 0) { + authtype = AIX_AUTH_STD; + break; + } + } + free(line); + fclose(fp); + + debug_return_int(authtype); +} +#endif /* HAVE_PAM */ + +int +sudo_aix_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + debug_decl(sudo_aix_init, SUDOERS_DEBUG_AUTH); + +#ifdef HAVE_PAM + /* Check auth_type in /etc/security/login.cfg. */ + if (sudo_aix_authtype() == AIX_AUTH_PAM) { + if (sudo_pam_init_quiet(ctx, pw, auth) == AUTH_SUCCESS) { + /* Fail AIX authentication so we can use PAM instead. */ + debug_return_int(AUTH_FAILURE); + } + } +#endif + debug_return_int(AUTH_SUCCESS); +} + +/* Ignore AIX password incorrect message */ +static bool +sudo_aix_valid_message(const char *message) +{ + const char *cp; + const char badpass_msgid[] = "3004-300"; + debug_decl(sudo_aix_valid_message, SUDOERS_DEBUG_AUTH); + + if (message == NULL || message[0] == '\0') + debug_return_bool(false); + + /* Match "3004-300: You entered an invalid login name or password" */ + for (cp = message; *cp != '\0'; cp++) { + if (isdigit((unsigned char)*cp)) { + if (strncmp(cp, badpass_msgid, strlen(badpass_msgid)) == 0) + debug_return_bool(false); + break; + } + } + debug_return_bool(true); +} + +/* + * Change the user's password. If root changes the user's password + * the ADMCHG flag is set on the account (and the user must change + * it again) so we run passwd(1) as the user. This does mean that + * the user will need to re-enter their original password again, + * unlike with su(1). We may consider using pwdadm(1) as root to + * change the password and then clear the flag in the future. + */ +static bool +sudo_aix_change_password(const struct sudoers_context *ctx, const char *user) +{ + struct sigaction sa, savechld; + pid_t child, pid; + bool ret = false; + sigset_t mask; + int status; + debug_decl(sudo_aix_change_password, SUDOERS_DEBUG_AUTH); + + /* Set SIGCHLD handler to default since we call waitpid() below. */ + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + (void) sigaction(SIGCHLD, &sa, &savechld); + + switch (child = sudo_debug_fork()) { + case -1: + /* error */ + sudo_warn("%s", U_("unable to fork")); + break; + case 0: + /* child, run passwd(1) */ + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGQUIT); + (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); + set_perms(ctx, PERM_USER); + execl("/usr/bin/passwd", "passwd", user, (char *)NULL); + sudo_warn("passwd"); + _exit(127); + /* NOTREACHED */ + default: + /* parent */ + break; + } + + /* Wait for passwd(1) to complete. */ + do { + pid = waitpid(child, &status, 0); + } while (pid == -1 && errno == EINTR); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "child (%d) exit value %d", (int)child, status); + if (pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0) + ret = true; + + /* Restore saved SIGCHLD handler. */ + (void) sigaction(SIGCHLD, &savechld, NULL); + + debug_return_bool(ret); +} + +int +sudo_aix_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + char *pass, *message = NULL; + int result = 1, reenter = 0; + 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) + break; + free(message); + message = NULL; + result = authenticate(pw->pw_name, pass, &reenter, &message); + freezero(pass, strlen(pass)); + prompt = message; + } while (reenter); + + if (result != 0) { + /* Display error message, if any. */ + if (sudo_aix_valid_message(message)) + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "%s", message); + ret = pass ? AUTH_FAILURE : AUTH_INTR; + } + free(message); + message = NULL; + + /* Check if password expired and allow user to change it if possible. */ + if (ret == AUTH_SUCCESS) { + result = passwdexpired(pw->pw_name, &message); + if (message != NULL && message[0] != '\0') { + int msg_type = SUDO_CONV_PREFER_TTY; + msg_type |= result ? SUDO_CONV_ERROR_MSG : SUDO_CONV_INFO_MSG, + sudo_printf(msg_type, "%s", message); + free(message); + message = NULL; + } + switch (result) { + case 0: + /* password not expired. */ + break; + case 1: + /* password expired, user must change it */ + if (!sudo_aix_change_password(ctx, pw->pw_name)) { + sudo_warnx(U_("unable to change password for %s"), pw->pw_name); + ret = AUTH_ERROR; + } + break; + case 2: + /* password expired, only admin can change it */ + ret = AUTH_ERROR; + break; + default: + /* error (-1) */ + sudo_warn("passwdexpired"); + ret = AUTH_ERROR; + break; + } + } + + debug_return_int(ret); +} + +int +sudo_aix_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + debug_decl(sudo_aix_cleanup, SUDOERS_DEBUG_AUTH); + + /* Unset AUTHSTATE as it may not be correct for the runas user. */ + if (sudo_unsetenv("AUTHSTATE") == -1) + debug_return_int(AUTH_FAILURE); + + debug_return_int(AUTH_SUCCESS); +} + +#endif /* HAVE_AIXAUTH */ diff --git a/plugins/sudoers/auth/bsdauth.c b/plugins/sudoers/auth/bsdauth.c new file mode 100644 index 0000000..d48148c --- /dev/null +++ b/plugins/sudoers/auth/bsdauth.c @@ -0,0 +1,224 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2000-2005, 2007-2008, 2010-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_BSD_AUTH_H + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <pwd.h> +#include <signal.h> + +#include <login_cap.h> +#include <bsd_auth.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +# ifndef LOGIN_DEFROOTCLASS +# define LOGIN_DEFROOTCLASS "daemon" +# endif + +struct bsdauth_state { + auth_session_t *as; + login_cap_t *lc; +}; + +static char *login_style; /* user may set style via -a option */ + +int +bsdauth_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + static struct bsdauth_state state; + debug_decl(bsdauth_init, SUDOERS_DEBUG_AUTH); + + /* Only initialize once. */ + if (auth->data != NULL) + debug_return_int(AUTH_SUCCESS); + + /* Get login class based on auth user, which may not be invoking user. */ + if (pw->pw_class && *pw->pw_class) { + state.lc = login_getclass(pw->pw_class); + } else { + state.lc = login_getclass( + pw->pw_uid ? (char *)LOGIN_DEFCLASS : (char *)LOGIN_DEFROOTCLASS); + } + if (state.lc == NULL) { + log_warning(ctx, 0, N_("unable to get login class for user %s"), + pw->pw_name); + goto bad; + } + + login_style = login_getstyle(state.lc, login_style, (char *)"auth-sudo"); + if (login_style == NULL) { + log_warningx(ctx, 0, N_("invalid authentication type")); + goto bad; + } + + if ((state.as = auth_open()) == NULL) { + log_warning(ctx, 0, N_("unable to begin BSD authentication")); + goto bad; + } + + if (auth_setitem(state.as, AUTHV_STYLE, login_style) < 0 || + auth_setitem(state.as, AUTHV_NAME, pw->pw_name) < 0 || + auth_setitem(state.as, AUTHV_CLASS, ctx->runas.class) < 0) { + log_warningx(ctx, 0, N_("unable to initialize BSD authentication")); + goto bad; + } + + auth->data = (void *) &state; + debug_return_int(AUTH_SUCCESS); +bad: + auth_close(state.as); + login_close(state.lc); + debug_return_int(AUTH_ERROR); +} + +int +bsdauth_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + char *pass; + char *s; + size_t len; + int authok = 0; + struct sigaction sa, osa; + 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; + sa.sa_handler = SIG_DFL; + (void) sigaction(SIGCHLD, &sa, &osa); + + /* + * If there is a challenge then print that instead of the normal + * prompt. If the user just hits return we prompt again with echo + * turned on, which is useful for challenge/response things like + * S/Key. + */ + if ((s = auth_challenge(as)) == NULL) { + pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback); + } else { + pass = auth_getpass(s, SUDO_CONV_PROMPT_ECHO_OFF, callback); + if (pass != NULL && *pass == '\0') { + if ((prompt = strrchr(s, '\n'))) + prompt++; + else + prompt = s; + + /* + * Append '[echo on]' to the last line of the challenge and + * re-prompt with echo turned on. + */ + len = strlen(prompt); + while (len > 0 && (isspace((unsigned char)prompt[len - 1]) || prompt[len - 1] == ':')) + len--; + if (asprintf(&s, "%.*s [echo on]: ", (int)len, prompt) == -1) { + log_warningx(ctx, 0, N_("unable to allocate memory")); + debug_return_int(AUTH_ERROR); + } + free(pass); + pass = auth_getpass(s, SUDO_CONV_PROMPT_ECHO_ON, callback); + free(s); + } + } + + if (pass != NULL) { + authok = auth_userresponse(as, pass, 1); + freezero(pass, strlen(pass)); + } + + /* restore old signal handler */ + (void) sigaction(SIGCHLD, &osa, NULL); + + if (authok) + debug_return_int(AUTH_SUCCESS); + + if (pass == NULL) + debug_return_int(AUTH_INTR); + + if ((s = auth_getvalue(as, (char *)"errormsg")) != NULL) + log_warningx(ctx, 0, "%s", s); + debug_return_int(AUTH_FAILURE); +} + +int +bsdauth_approval(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool exempt) +{ + struct bsdauth_state *state = auth->data; + debug_decl(bsdauth_approval, SUDOERS_DEBUG_AUTH); + + if (auth_approval(state->as, state->lc, pw->pw_name, (char *)"auth-sudo") == 0) { + if (auth_getstate(state->as) & AUTH_EXPIRED) + log_warningx(ctx, 0, "%s", N_("your account has expired")); + else + log_warningx(ctx, 0, "%s", N_("approval failed")); + debug_return_int(AUTH_FAILURE); + } + debug_return_int(AUTH_SUCCESS); +} + +int +bsdauth_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + struct bsdauth_state *state = auth->data; + debug_decl(bsdauth_cleanup, SUDOERS_DEBUG_AUTH); + + if (state != NULL) { + auth_close(state->as); + state->as = NULL; + login_close(state->lc); + state->lc = NULL; + auth->data = NULL; + } + login_style = NULL; + + debug_return_int(AUTH_SUCCESS); +} + +void +bsdauth_set_style(const char *style) +{ + login_style = (char *)style; +} + +#endif /* HAVE_BSD_AUTH_H */ diff --git a/plugins/sudoers/auth/dce.c b/plugins/sudoers/auth/dce.c new file mode 100644 index 0000000..aa7c47b --- /dev/null +++ b/plugins/sudoers/auth/dce.c @@ -0,0 +1,202 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1996, 1998-2005, 2010-2012, 2014-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ +/* + * The code below basically comes from the examples supplied on + * the OSF DCE 1.0.3 manpages for the sec_login routines, with + * enough additional polishing to make the routine work with the + * rest of sudo. + * + * This code is known to work on HP 700 and 800 series systems + * running HP-UX 9.X and 10.X, with either HP's version 1.2.1 of DCE. + * (aka, OSF DCE 1.0.3) or with HP's version 1.4 of DCE (aka, OSF + * DCE 1.1). + */ + +#include <config.h> + +#ifdef HAVE_DCE + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include <dce/rpc.h> +#include <dce/sec_login.h> +#include <dce/dce_error.h> /* required to call dce_error_inq_text routine */ + +#include <sudoers.h> +#include "sudo_auth.h" +#include <timestamp.h> + +static int check_dce_status(error_status_t, char *); + +int +sudo_dce_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *plain_pw, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + struct passwd temp_pw; + sec_passwd_rec_t password_rec; + sec_login_handle_t login_context; + boolean32 reset_passwd; + sec_login_auth_src_t auth_src; + error_status_t status; + debug_decl(sudo_dce_verify, SUDOERS_DEBUG_AUTH); + + if (IS_NONINTERACTIVE(auth)) + debug_return_int(AUTH_NONINTERACTIVE); + + /* Display lecture if needed and we haven't already done so. */ + display_lecture(callback); + + /* + * Create the local context of the DCE principal necessary + * to perform authenticated network operations. The network + * identity set up by this operation cannot be used until it + * is validated via sec_login_validate_identity(). + */ + if (sec_login_setup_identity((unsigned_char_p_t) pw->pw_name, + sec_login_no_flags, &login_context, &status)) { + + if (check_dce_status(status, "sec_login_setup_identity(1):")) + debug_return_int(AUTH_FAILURE); + + password_rec.key.key_type = sec_passwd_plain; + password_rec.key.tagged_union.plain = (idl_char *) plain_pw; + password_rec.pepper = NULL; + password_rec.version_number = sec_passwd_c_version_none; + + /* Validate the login context with the password */ + if (sec_login_validate_identity(login_context, &password_rec, + &reset_passwd, &auth_src, &status)) { + + if (check_dce_status(status, "sec_login_validate_identity(1):")) + debug_return_int(AUTH_FAILURE); + + /* + * Certify that the DCE Security Server used to set + * up and validate a login context is legitimate. Makes + * sure that we didn't get spoofed by another DCE server. + */ + if (!sec_login_certify_identity(login_context, &status)) { + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "Whoa! Bogus authentication server!\n"); + (void) check_dce_status(status,"sec_login_certify_identity(1):"); + debug_return_int(AUTH_FAILURE); + } + if (check_dce_status(status, "sec_login_certify_identity(2):")) + debug_return_int(AUTH_FAILURE); + + /* + * Sets the network credentials to those specified + * by the now validated login context. + */ + sec_login_set_context(login_context, &status); + if (check_dce_status(status, "sec_login_set_context:")) + debug_return_int(AUTH_FAILURE); + + /* + * Oops, your credentials were no good. Possibly + * caused by clock times out of adjustment between + * DCE client and DCE security server... + */ + if (auth_src != sec_login_auth_src_network) { + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "You have no network credentials.\n"); + debug_return_int(AUTH_FAILURE); + } + /* Check if the password has aged and is thus no good */ + if (reset_passwd) { + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "Your DCE password needs resetting.\n"); + debug_return_int(AUTH_FAILURE); + } + + /* + * We should be a valid user by this point. Pull the + * user's password structure from the DCE security + * server just to make sure. If we get it with no + * problems, then we really are legitimate... + */ + sec_login_get_pwent(login_context, (sec_login_passwd_t) &temp_pw, + &status); + if (check_dce_status(status, "sec_login_get_pwent:")) + debug_return_int(AUTH_FAILURE); + + /* + * If we get to here, then the pwent above properly fetched + * the password structure from the DCE registry, so the user + * must be valid. We don't really care what the user's + * registry password is, just that the user could be + * validated. In fact, if we tried to compare the local + * password to the DCE entry at this point, the operation + * would fail if the hidden password feature is turned on, + * because the password field would contain an asterisk. + * Also go ahead and destroy the user's DCE login context + * before we leave here (and don't bother checking the + * status), in order to clean up credentials files in + * /opt/dcelocal/var/security/creds. By doing this, we are + * assuming that the user will not need DCE authentication + * later in the program, only local authentication. If this + * is not true, then the login_context will have to be + * returned to the calling program, and the context purged + * somewhere later in the program. + */ + sec_login_purge_context(&login_context, &status); + debug_return_int(AUTH_SUCCESS); + } else { + if(check_dce_status(status, "sec_login_validate_identity(2):")) + debug_return_int(AUTH_FAILURE); + sec_login_purge_context(&login_context, &status); + if(check_dce_status(status, "sec_login_purge_context:")) + debug_return_int(AUTH_FAILURE); + } + } + (void) check_dce_status(status, "sec_login_setup_identity(2):"); + debug_return_int(AUTH_FAILURE); +} + +/* Returns 0 for DCE "ok" status, 1 otherwise */ +static int +check_dce_status(error_status_t input_status, char *comment) +{ + int error_stat; + unsigned char error_string[dce_c_error_string_len]; + debug_decl(check_dce_status, SUDOERS_DEBUG_AUTH); + + if (input_status == rpc_s_ok) + debug_return_int(0); + dce_error_inq_text(input_status, error_string, &error_stat); + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "%s %s\n", comment, error_string); + debug_return_int(1); +} + +#endif /* HAVE_DCE */ diff --git a/plugins/sudoers/auth/fwtk.c b/plugins/sudoers/auth/fwtk.c new file mode 100644 index 0000000..ae62c97 --- /dev/null +++ b/plugins/sudoers/auth/fwtk.c @@ -0,0 +1,161 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2008, 2010-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_FWTK + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include <auth.h> +#include <firewall.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +int +sudo_fwtk_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + static Cfg *confp; /* Configuration entry struct */ + char resp[128]; /* Response from the server */ + debug_decl(sudo_fwtk_init, SUDOERS_DEBUG_AUTH); + + /* Only initialize once. */ + 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_ERROR); + } + + if (auth_open(confp)) { + sudo_warnx("%s", U_("unable to connect to authentication server")); + debug_return_int(AUTH_ERROR); + } + + /* Get welcome message from auth server */ + if (auth_recv(resp, sizeof(resp))) { + sudo_warnx("%s", U_("lost connection to authentication server")); + debug_return_int(AUTH_ERROR); + } + if (strncmp(resp, "Authsrv ready", 13) != 0) { + sudo_warnx(U_("authentication server error:\n%s"), resp); + debug_return_int(AUTH_ERROR); + } + auth->data = (void *) confp; + + debug_return_int(AUTH_SUCCESS); +} + +int +sudo_fwtk_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + char *pass; /* Password from the user */ + char buf[SUDO_CONV_REPL_MAX + 12]; /* General prupose buffer */ + char resp[128]; /* Response from the server */ + int error; + debug_decl(sudo_fwtk_verify, SUDOERS_DEBUG_AUTH); + + /* Send username to authentication server. */ + (void) snprintf(buf, sizeof(buf), "authorize %s 'sudo'", pw->pw_name); +restart: + if (auth_send(buf) || auth_recv(resp, sizeof(resp))) { + sudo_warnx("%s", U_("lost connection to authentication server")); + debug_return_int(AUTH_ERROR); + } + + /* Get the password/response from the user. */ + if (strncmp(resp, "challenge ", 10) == 0) { + (void) snprintf(buf, sizeof(buf), "%s\nResponse: ", &resp[10]); + pass = auth_getpass(buf, SUDO_CONV_PROMPT_ECHO_OFF, callback); + if (pass && *pass == '\0') { + free(pass); + pass = auth_getpass("Response [echo on]: ", + SUDO_CONV_PROMPT_ECHO_ON, callback); + } + } else if (strncmp(resp, "chalnecho ", 10) == 0) { + pass = auth_getpass(&resp[10], SUDO_CONV_PROMPT_ECHO_OFF, callback); + } else if (strncmp(resp, "password", 8) == 0) { + pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback); + } else if (strncmp(resp, "display ", 8) == 0) { + sudo_printf(SUDO_CONV_INFO_MSG|SUDO_CONV_PREFER_TTY, "%s\n", &resp[8]); + strlcpy(buf, "response noop", sizeof(buf)); + goto restart; + } else { + sudo_warnx("%s", resp); + debug_return_int(AUTH_ERROR); + } + if (pass == NULL) { /* ^C or error */ + debug_return_int(AUTH_INTR); + } + + /* Send the user's response to the server */ + (void) snprintf(buf, sizeof(buf), "response '%s'", pass); + if (auth_send(buf) || auth_recv(resp, sizeof(resp))) { + sudo_warnx("%s", U_("lost connection to authentication server")); + error = AUTH_ERROR; + goto done; + } + + if (strncmp(resp, "ok", 2) == 0) { + error = AUTH_SUCCESS; + goto done; + } + + /* Main loop prints "Permission Denied" or insult. */ + if (strcmp(resp, "Permission Denied.") != 0) + sudo_warnx("%s", resp); + error = AUTH_FAILURE; +done: + explicit_bzero(buf, sizeof(buf)); + freezero(pass, strlen(pass)); + debug_return_int(error); +} + +int +sudo_fwtk_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + debug_decl(sudo_fwtk_cleanup, SUDOERS_DEBUG_AUTH); + + auth_close(); + debug_return_int(AUTH_SUCCESS); +} + +#endif /* HAVE_FWTK */ diff --git a/plugins/sudoers/auth/kerb5.c b/plugins/sudoers/auth/kerb5.c new file mode 100644 index 0000000..f7bab93 --- /dev/null +++ b/plugins/sudoers/auth/kerb5.c @@ -0,0 +1,345 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007-2008, 2010-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_KERB5 + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#include <krb5.h> +#ifdef HAVE_HEIMDAL +#include <com_err.h> +#endif + +#include <sudoers.h> +#include "sudo_auth.h" + +#ifdef HAVE_HEIMDAL +# define extract_name(c, p) krb5_principal_get_comp_string(c, p, 1) +# define krb5_free_data_contents(c, d) krb5_data_free(d) +#else +# define extract_name(c, p) (krb5_princ_component(c, p, 1)->data) +#endif + +#ifndef HAVE_KRB5_VERIFY_USER +static int verify_krb_v5_tgt(const struct sudoers_context *, krb5_context, + krb5_creds *, const char *); +#endif +static struct _sudo_krb5_data { + krb5_context sudo_context; + krb5_principal princ; + krb5_ccache ccache; +} sudo_krb5_data = { NULL, NULL, NULL }; +typedef struct _sudo_krb5_data *sudo_krb5_datap; + +#ifdef SUDO_KRB5_INSTANCE +static const char *sudo_krb5_instance = SUDO_KRB5_INSTANCE; +#else +static const char *sudo_krb5_instance = NULL; +#endif + +#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC +static krb5_error_code +krb5_get_init_creds_opt_alloc(krb5_context context, + krb5_get_init_creds_opt **opts) +{ + *opts = malloc(sizeof(krb5_get_init_creds_opt)); + if (*opts == NULL) + return KRB5_CC_NOMEM; + krb5_get_init_creds_opt_init(*opts); + return 0; +} + +static void +krb5_get_init_creds_opt_free(krb5_get_init_creds_opt *opts) +{ + free(opts); +} +#endif + +int +sudo_krb5_setup(const struct sudoers_context *ctx, struct passwd *pw, + char **promptp, sudo_auth *auth) +{ + static char *krb5_prompt; + debug_decl(sudo_krb5_init, SUDOERS_DEBUG_AUTH); + + /* Don't override the prompt if the user specified their own. */ + if (strcmp(*promptp, PASSPROMPT) != 0) { + debug_return_int(AUTH_SUCCESS); + } + + if (krb5_prompt == NULL) { + krb5_context sudo_context; + krb5_principal princ; + char *pname; + krb5_error_code error; + + sudo_context = ((sudo_krb5_datap) auth->data)->sudo_context; + princ = ((sudo_krb5_datap) auth->data)->princ; + + /* + * Really, we need to tell the caller not to prompt for password. The + * API does not currently provide this unless the auth is standalone. + */ + if ((error = krb5_unparse_name(sudo_context, princ, &pname))) { + log_warningx(ctx, 0, + N_("%s: unable to convert principal to string ('%s'): %s"), + auth->name, pw->pw_name, error_message(error)); + debug_return_int(AUTH_FAILURE); + } + + if (asprintf(&krb5_prompt, "Password for %s: ", pname) == -1) { + log_warningx(ctx, 0, N_("unable to allocate memory")); + free(pname); + debug_return_int(AUTH_ERROR); + } + free(pname); + } + *promptp = krb5_prompt; + + debug_return_int(AUTH_SUCCESS); +} + +int +sudo_krb5_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + krb5_context sudo_context; + krb5_error_code error; + char cache_name[64], *pname = pw->pw_name; + debug_decl(sudo_krb5_init, SUDOERS_DEBUG_AUTH); + + /* Only initialize once. */ + if (auth->data != NULL) + debug_return_int(AUTH_SUCCESS); + + if (sudo_krb5_instance != NULL) { + int len = asprintf(&pname, "%s%s%s", pw->pw_name, + sudo_krb5_instance[0] != '/' ? "/" : "", sudo_krb5_instance); + if (len == -1) { + log_warningx(ctx, 0, N_("unable to allocate memory")); + debug_return_int(AUTH_ERROR); + } + } + +#ifdef HAVE_KRB5_INIT_SECURE_CONTEXT + error = krb5_init_secure_context(&(sudo_krb5_data.sudo_context)); +#else + error = krb5_init_context(&(sudo_krb5_data.sudo_context)); +#endif + if (error) + goto done; + sudo_context = sudo_krb5_data.sudo_context; + + error = krb5_parse_name(sudo_context, pname, &(sudo_krb5_data.princ)); + if (error) { + log_warningx(ctx, 0, N_("%s: unable to parse '%s': %s"), auth->name, + pname, error_message(error)); + goto done; + } + + (void) snprintf(cache_name, sizeof(cache_name), "MEMORY:sudocc_%ld", + (long) getpid()); + if ((error = krb5_cc_resolve(sudo_context, cache_name, + &(sudo_krb5_data.ccache)))) { + log_warningx(ctx, 0, N_("%s: unable to resolve credential cache: %s"), + auth->name, error_message(error)); + goto done; + } + + auth->data = (void *) &sudo_krb5_data; /* Stash all our data here */ + +done: + if (sudo_krb5_instance != NULL) + free(pname); + debug_return_int(error ? AUTH_FAILURE : AUTH_SUCCESS); +} + +#ifdef HAVE_KRB5_VERIFY_USER +int +sudo_krb5_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + krb5_context sudo_context; + krb5_principal princ; + krb5_ccache ccache; + krb5_error_code error; + debug_decl(sudo_krb5_verify, SUDOERS_DEBUG_AUTH); + + sudo_context = ((sudo_krb5_datap) auth->data)->sudo_context; + princ = ((sudo_krb5_datap) auth->data)->princ; + ccache = ((sudo_krb5_datap) auth->data)->ccache; + + error = krb5_verify_user(sudo_context, princ, ccache, pass, 1, NULL); + debug_return_int(error ? AUTH_FAILURE : AUTH_SUCCESS); +} +#else +int +sudo_krb5_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + krb5_context sudo_context; + krb5_principal princ; + krb5_creds credbuf, *creds = NULL; + krb5_ccache ccache; + krb5_error_code error; + krb5_get_init_creds_opt *opts = NULL; + debug_decl(sudo_krb5_verify, SUDOERS_DEBUG_AUTH); + + sudo_context = ((sudo_krb5_datap) auth->data)->sudo_context; + princ = ((sudo_krb5_datap) auth->data)->princ; + ccache = ((sudo_krb5_datap) auth->data)->ccache; + + /* Set default flags based on the local config file. */ + error = krb5_get_init_creds_opt_alloc(sudo_context, &opts); + if (error) { + log_warningx(ctx, 0, N_("%s: unable to allocate options: %s"), + auth->name, error_message(error)); + goto done; + } +#ifdef HAVE_HEIMDAL + krb5_get_init_creds_opt_set_default_flags(sudo_context, NULL, + krb5_principal_get_realm(sudo_context, princ), opts); +#endif + + /* Note that we always obtain a new TGT to verify the user */ + if ((error = krb5_get_init_creds_password(sudo_context, &credbuf, princ, + pass, krb5_prompter_posix, + NULL, 0, NULL, opts))) { + /* Don't print error if just a bad password */ + if (error != KRB5KRB_AP_ERR_BAD_INTEGRITY) { + log_warningx(ctx, 0, N_("%s: unable to get credentials: %s"), + auth->name, error_message(error)); + } + goto done; + } + creds = &credbuf; + + /* Verify the TGT to prevent spoof attacks. */ + if ((error = verify_krb_v5_tgt(ctx, sudo_context, creds, auth->name))) + goto done; + + /* Store credential in cache. */ + if ((error = krb5_cc_initialize(sudo_context, ccache, princ))) { + log_warningx(ctx, 0, N_("%s: unable to initialize credential cache: %s"), + auth->name, error_message(error)); + } else if ((error = krb5_cc_store_cred(sudo_context, ccache, creds))) { + log_warningx(ctx, 0, N_("%s: unable to store credential in cache: %s"), + auth->name, error_message(error)); + } + +done: + if (opts) { +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_FREE_TWO_ARGS + krb5_get_init_creds_opt_free(sudo_context, opts); +#else + krb5_get_init_creds_opt_free(opts); +#endif + } + if (creds) + krb5_free_cred_contents(sudo_context, creds); + debug_return_int(error ? AUTH_FAILURE : AUTH_SUCCESS); +} +#endif + +int +sudo_krb5_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + krb5_context sudo_context; + krb5_principal princ; + krb5_ccache ccache; + debug_decl(sudo_krb5_cleanup, SUDOERS_DEBUG_AUTH); + + sudo_context = ((sudo_krb5_datap) auth->data)->sudo_context; + princ = ((sudo_krb5_datap) auth->data)->princ; + ccache = ((sudo_krb5_datap) auth->data)->ccache; + + if (sudo_context) { + if (ccache) + krb5_cc_destroy(sudo_context, ccache); + if (princ) + krb5_free_principal(sudo_context, princ); + krb5_free_context(sudo_context); + } + + debug_return_int(AUTH_SUCCESS); +} + +#ifndef HAVE_KRB5_VERIFY_USER +/* + * Verify the Kerberos ticket-granting ticket just retrieved for the + * user. If the Kerberos server doesn't respond, assume the user is + * trying to fake us out (since we DID just get a TGT from what is + * supposedly our KDC). + * + * Returns 0 for successful authentication, non-zero for failure. + */ +static int +verify_krb_v5_tgt(const struct sudoers_context *ctx, krb5_context sudo_context, + krb5_creds *cred, const char *auth_name) +{ + krb5_error_code error; + krb5_principal server; + krb5_verify_init_creds_opt vopt; + debug_decl(verify_krb_v5_tgt, SUDOERS_DEBUG_AUTH); + + /* + * Get the server principal for the local host. + * (Use defaults of "host" and canonicalized local name.) + */ + if ((error = krb5_sname_to_principal(sudo_context, NULL, NULL, + KRB5_NT_SRV_HST, &server))) { + log_warningx(ctx, 0, N_("%s: unable to get host principal: %s"), + auth_name, error_message(error)); + debug_return_int(-1); + } + + /* Initialize verify opts and set secure mode */ + krb5_verify_init_creds_opt_init(&vopt); + krb5_verify_init_creds_opt_set_ap_req_nofail(&vopt, 1); + + /* verify the Kerberos ticket-granting ticket we just retrieved */ + error = krb5_verify_init_creds(sudo_context, cred, server, NULL, + NULL, &vopt); + krb5_free_principal(sudo_context, server); + if (error) { + log_warningx(ctx, 0, N_("%s: Cannot verify TGT! Possible attack!: %s"), + auth_name, error_message(error)); + } + debug_return_int(error); +} +#endif + +#endif /* HAVE_KERB5 */ diff --git a/plugins/sudoers/auth/pam.c b/plugins/sudoers/auth/pam.c new file mode 100644 index 0000000..e8d1785 --- /dev/null +++ b/plugins/sudoers/auth/pam.c @@ -0,0 +1,776 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_PAM + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#include <errno.h> + +#ifdef HAVE_PAM_PAM_APPL_H +# include <pam/pam_appl.h> +#else +# include <security/pam_appl.h> +#endif + +#ifdef __hpux +# include <nl_types.h> +#endif + +#ifdef HAVE_LIBINTL_H +# if defined(__LINUX_PAM__) +# define PAM_TEXT_DOMAIN "Linux-PAM" +# elif defined(__sun__) +# define PAM_TEXT_DOMAIN "SUNW_OST_SYSOSPAM" +# endif +#endif + +/* We don't want to translate the strings in the calls to dgt(). */ +#ifdef PAM_TEXT_DOMAIN +# define dgt(d, t) dgettext(d, t) +#endif + +#include <sudoers.h> +#include "sudo_auth.h" + +/* Only OpenPAM and Linux PAM use const qualifiers. */ +#ifdef PAM_SUN_CODEBASE +# define PAM_CONST +#else +# define PAM_CONST const +#endif + +/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */ +#ifdef PAM_SUN_CODEBASE +# define PAM_MSG_GET(msg, n) (*(msg) + (n)) +#else +# define PAM_MSG_GET(msg, n) ((msg)[(n)]) +#endif + +#ifndef PAM_DATA_SILENT +#define PAM_DATA_SILENT 0 +#endif + +struct sudo_pam_closure { + const struct sudoers_context *ctx; + struct sudo_conv_callback *callback; +}; + +struct conv_filter { + char *msg; + size_t msglen; +}; + +static int converse(int, PAM_CONST struct pam_message **, + struct pam_response **, void *); +static struct sudo_pam_closure pam_closure; +static struct pam_conv pam_conv = { converse, &pam_closure }; +static const char *def_prompt = PASSPROMPT; +static bool getpass_error; +static bool noninteractive; +static pam_handle_t *pamh; +static struct conv_filter *conv_filter; + +static void +conv_filter_init(const struct sudoers_context *ctx) +{ + debug_decl(conv_filter_init, SUDOERS_DEBUG_AUTH); + +#ifdef __hpux + /* + * HP-UX displays last login information as part of either account + * management (in trusted mode) or session management (regular mode). + * Filter those out in the conversation function unless running a shell. + */ + if (!ISSET(ctx->mode, MODE_SHELL|MODE_LOGIN_SHELL)) { + int i, nfilt = 0, maxfilters = 0; + struct conv_filter *newfilt; + nl_catd catd; + char *msg; + + /* + * Messages from PAM account management when trusted mode is enabled: + * 1 Last successful login for %s: %s + * 2 Last successful login for %s: %s on %s + * 3 Last unsuccessful login for %s: %s + * 4 Last unsuccessful login for %s: %s on %s + */ + if ((catd = catopen("pam_comsec", NL_CAT_LOCALE)) != -1) { + maxfilters += 4; + newfilt = reallocarray(conv_filter, maxfilters + 1, + sizeof(*conv_filter)); + if (newfilt != NULL) { + conv_filter = newfilt; + for (i = 1; i < 5; i++) { + if ((msg = catgets(catd, 1, i, NULL)) == NULL) + break; + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "adding \"%s\" to conversation filter", msg); + if ((conv_filter[nfilt].msg = strdup(msg)) == NULL) + break; + conv_filter[nfilt].msglen = strcspn(msg, "%"); + nfilt++; + } + } + } + /* + * Messages from PAM session management when trusted mode is disabled: + * 3 Last successful login: %s %s %s %s + * 4 Last authentication failure: %s %s %s %s + */ + if ((catd = catopen("pam_hpsec", NL_CAT_LOCALE)) != -1) { + maxfilters += 2; + newfilt = reallocarray(conv_filter, maxfilters + 1, + sizeof(*conv_filter)); + if (newfilt != NULL) { + conv_filter = newfilt; + for (i = 3; i < 5; i++) { + if ((msg = catgets(catd, 1, i, NULL)) == NULL) + break; + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "adding \"%s\" to conversation filter", msg); + if ((conv_filter[nfilt].msg = strdup(msg)) == NULL) + break; + conv_filter[nfilt].msglen = strcspn(msg, "%"); + nfilt++; + } + } + } + if (conv_filter != NULL) { + conv_filter[nfilt].msg = NULL; + conv_filter[nfilt].msglen = 0; + } + } +#endif /* __hpux */ + debug_return; +} + +/* + * Like pam_strerror() but never returns NULL and uses strerror(errno) + * for PAM_SYSTEM_ERR. + */ +static const char * +sudo_pam_strerror(pam_handle_t *handle, int errnum) +{ + const char *errstr; + static char errbuf[32]; + + if (errnum == PAM_SYSTEM_ERR) + return strerror(errno); + if ((errstr = pam_strerror(handle, errnum)) == NULL) + (void)snprintf(errbuf, sizeof(errbuf), "PAM error %d", errnum); + return errstr; +} + +static int +sudo_pam_init2(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool quiet) +{ + static int pam_status = PAM_SUCCESS; + const char *ttypath = ctx->user.ttypath; + const char *errstr, *pam_service; + int rc; + debug_decl(sudo_pam_init, SUDOERS_DEBUG_AUTH); + + /* Stash pointer to last pam status. */ + auth->data = &pam_status; + + if (pamh != NULL) { + /* Already initialized (may happen with AIX or with sub-commands). */ + debug_return_int(AUTH_SUCCESS); + } + + /* Stash value of noninteractive flag for conversation function. */ + noninteractive = IS_NONINTERACTIVE(auth); + + /* Store context in closure so converse() has access to it. */ + pam_closure.ctx = ctx; + + /* Initialize PAM. */ + if (ISSET(ctx->mode, MODE_ASKPASS) && def_pam_askpass_service != NULL) { + pam_service = def_pam_askpass_service; + } else { + pam_service = ISSET(ctx->mode, MODE_LOGIN_SHELL) ? + def_pam_login_service : def_pam_service; + } + pam_status = pam_start(pam_service, pw->pw_name, &pam_conv, &pamh); + if (pam_status != PAM_SUCCESS) { + errstr = sudo_pam_strerror(NULL, pam_status); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_start(%s, %s, %p, %p): %s", pam_service, pw->pw_name, + &pam_conv, &pamh, errstr); + if (!quiet) + log_warningx(ctx, 0, N_("unable to initialize PAM: %s"), errstr); + debug_return_int(AUTH_ERROR); + } + + /* Initialize conversation function message filter. */ + conv_filter_init(ctx); + + /* + * Set PAM_RUSER to the invoking user (the "from" user). + * Solaris 7 and below require PAM_RHOST to be set if PAM_RUSER is. + * Note: PAM_RHOST may cause a DNS lookup on Linux in libaudit. + */ + if (def_pam_ruser) { + rc = pam_set_item(pamh, PAM_RUSER, ctx->user.name); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_set_item(pamh, PAM_RUSER, %s): %s", ctx->user.name, errstr); + } + } + if (def_pam_rhost) { + rc = pam_set_item(pamh, PAM_RHOST, ctx->user.host); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_set_item(pamh, PAM_RHOST, %s): %s", ctx->user.host, errstr); + } + } + if (ttypath != NULL) { + rc = pam_set_item(pamh, PAM_TTY, ttypath); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_set_item(pamh, PAM_TTY, %s): %s", ttypath, errstr); + } + } + + /* + * If PAM session and setcred support is disabled we don't + * need to keep a sudo process around to close the session. + */ + if (!def_pam_session && !def_pam_setcred) + auth->end_session = NULL; + + debug_return_int(AUTH_SUCCESS); +} + +int +sudo_pam_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + return sudo_pam_init2(ctx, pw, auth, false); +} + +#ifdef _AIX +int +sudo_pam_init_quiet(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + return sudo_pam_init2(ctx, pw, auth, true); +} +#endif /* _AIX */ + +int +sudo_pam_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + const char *envccname; + const char *s; + int *pam_status = (int *)auth->data; + debug_decl(sudo_pam_verify, SUDOERS_DEBUG_AUTH); + + def_prompt = prompt; /* for converse */ + getpass_error = false; /* set by converse if user presses ^C */ + pam_closure.callback = callback; /* passed to conversation function */ + + /* Set KRB5CCNAME from the user environment if not set to propagate this + * information to PAM modules that may use it to authentication. */ + envccname = sudo_getenv("KRB5CCNAME"); + if (envccname == NULL && ctx->user.ccname != NULL) { + if (sudo_setenv("KRB5CCNAME", ctx->user.ccname, true) != 0) { + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "unable to set KRB5CCNAME"); + debug_return_int(AUTH_FAILURE); + } + } + + /* PAM_SILENT prevents the authentication service from generating output. */ + *pam_status = pam_authenticate(pamh, PAM_SILENT); + + /* Restore def_prompt, the passed-in prompt may be freed later. */ + def_prompt = PASSPROMPT; + + /* Restore KRB5CCNAME to its original value. */ + if (envccname == NULL && sudo_unsetenv("KRB5CCNAME") != 0) { + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "unable to restore KRB5CCNAME"); + debug_return_int(AUTH_FAILURE); + } + + if (getpass_error) { + /* error or ^C from tgetpass() or running non-interactive */ + debug_return_int(noninteractive ? AUTH_NONINTERACTIVE : AUTH_INTR); + } + switch (*pam_status) { + case PAM_SUCCESS: + debug_return_int(AUTH_SUCCESS); + case PAM_AUTH_ERR: + case PAM_AUTHINFO_UNAVAIL: + case PAM_MAXTRIES: + case PAM_PERM_DENIED: + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "pam_authenticate: %d", *pam_status); + debug_return_int(AUTH_FAILURE); + default: + s = sudo_pam_strerror(pamh, *pam_status); + log_warningx(ctx, 0, N_("PAM authentication error: %s"), s); + debug_return_int(AUTH_ERROR); + } +} + +int +sudo_pam_approval(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool exempt) +{ + const char *s; + int rc, status = AUTH_SUCCESS; + int *pam_status = (int *) auth->data; + debug_decl(sudo_pam_approval, SUDOERS_DEBUG_AUTH); + + if (def_pam_acct_mgmt) { + rc = pam_acct_mgmt(pamh, PAM_SILENT); + switch (rc) { + case PAM_SUCCESS: + break; + case PAM_AUTH_ERR: + log_warningx(ctx, 0, N_("account validation failure, " + "is your account locked?")); + status = AUTH_ERROR; + break; + case PAM_NEW_AUTHTOK_REQD: + /* Ignore if user is exempt from password restrictions. */ + if (exempt) { + rc = *pam_status; + break; + } + /* New password required, try to change it. */ + log_warningx(ctx, 0, N_("Account or password is " + "expired, reset your password and try again")); + rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (rc == PAM_SUCCESS) + break; + s = pam_strerror(pamh, rc); + log_warningx(ctx, 0, + N_("unable to change expired password: %s"), s); + status = AUTH_FAILURE; + break; + case PAM_AUTHTOK_EXPIRED: + /* Ignore if user is exempt from password restrictions. */ + if (exempt) { + rc = *pam_status; + break; + } + /* Password expired, cannot be updated by user. */ + log_warningx(ctx, 0, + N_("Password expired, contact your system administrator")); + status = AUTH_ERROR; + break; + case PAM_ACCT_EXPIRED: + log_warningx(ctx, 0, + N_("Account expired or PAM config lacks an \"account\" " + "section for sudo, contact your system administrator")); + status = AUTH_ERROR; + break; + case PAM_AUTHINFO_UNAVAIL: + case PAM_MAXTRIES: + case PAM_PERM_DENIED: + s = sudo_pam_strerror(pamh, rc); + log_warningx(ctx, 0, N_("PAM account management error: %s"), s); + status = AUTH_FAILURE; + break; + default: + s = sudo_pam_strerror(pamh, rc); + log_warningx(ctx, 0, N_("PAM account management error: %s"), s); + status = AUTH_ERROR; + break; + } + *pam_status = rc; + } + debug_return_int(status); +} + +int +sudo_pam_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + int *pam_status = (int *) auth->data; + debug_decl(sudo_pam_cleanup, SUDOERS_DEBUG_AUTH); + + /* If successful, we can't close the session until sudo_pam_end_session() */ + if (force || *pam_status != PAM_SUCCESS || auth->end_session == NULL) { + *pam_status = pam_end(pamh, *pam_status | PAM_DATA_SILENT); + pamh = NULL; + } + debug_return_int(*pam_status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE); +} + +int +sudo_pam_begin_session(const struct sudoers_context *ctx, struct passwd *pw, + char **user_envp[], sudo_auth *auth) +{ + int rc, status = AUTH_SUCCESS; + int *pam_status = (int *) auth->data; + const char *errstr; + debug_decl(sudo_pam_begin_session, SUDOERS_DEBUG_AUTH); + + /* + * If there is no valid user we cannot open a PAM session. + * This is not an error as sudo can run commands with arbitrary + * uids, it just means we are done from a session management standpoint. + */ + if (pw == NULL) { + if (pamh != NULL) { + rc = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_end: %s", errstr); + } + pamh = NULL; + } + goto done; + } + + /* + * Update PAM_USER to reference the user we are running the command + * as, as opposed to the user we authenticated as. + */ + rc = pam_set_item(pamh, PAM_USER, pw->pw_name); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_set_item(pamh, PAM_USER, %s): %s", pw->pw_name, errstr); + } + + /* + * Reinitialize credentials when changing the user. + * We don't worry about a failure from pam_setcred() since with + * stacked PAM auth modules a failure from one module may override + * PAM_SUCCESS from another. For example, given a non-local user, + * pam_unix will fail but pam_ldap or pam_sss may succeed, but if + * pam_unix is first in the stack, pam_setcred() will fail. + */ + if (def_pam_setcred) { + rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_setcred: %s", errstr); + def_pam_setcred = false; + } + } + + if (def_pam_session) { + /* + * We use PAM_SILENT to prevent pam_lastlog from printing last login + * information except when explicitly running a shell. + */ + const bool silent = !ISSET(ctx->mode, MODE_SHELL|MODE_LOGIN_SHELL); + rc = pam_open_session(pamh, silent ? PAM_SILENT : 0); + switch (rc) { + case PAM_SUCCESS: + break; + case PAM_SESSION_ERR: + /* Treat PAM_SESSION_ERR as a non-fatal error. */ + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_open_session: %s", errstr); + /* Avoid closing session that was not opened. */ + def_pam_session = false; + break; + default: + /* Unexpected session failure, treat as fatal error. */ + *pam_status = rc; + errstr = sudo_pam_strerror(pamh, rc); + log_warningx(ctx, 0, N_("%s: %s"), "pam_open_session", errstr); + rc = pam_end(pamh, *pam_status | PAM_DATA_SILENT); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_end: %s", errstr); + } + pamh = NULL; + status = AUTH_ERROR; + goto done; + } + } + +#ifdef HAVE_PAM_GETENVLIST + /* + * Update environment based on what is stored in pamh. + * If no authentication is done we will only have environment + * variables if pam_env is called via session. + */ + if (user_envp != NULL) { + char **pam_envp = pam_getenvlist(pamh); + if (pam_envp != NULL) { + /* Merge pam env with user env. */ + if (!env_init(*user_envp) || !env_merge(ctx, pam_envp)) + status = AUTH_ERROR; + *user_envp = env_get(); + free(pam_envp); + /* XXX - we leak any duplicates that were in pam_envp */ + } + } +#endif /* HAVE_PAM_GETENVLIST */ + +done: + debug_return_int(status); +} + +int +sudo_pam_end_session(sudo_auth *auth) +{ + int rc, status = AUTH_SUCCESS; + const char *errstr; + debug_decl(sudo_pam_end_session, SUDOERS_DEBUG_AUTH); + + if (pamh != NULL) { + if (def_pam_session) { + rc = pam_close_session(pamh, PAM_SILENT); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_close_session: %s", errstr); + } + } + if (def_pam_setcred) { + rc = pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_setcred: %s", errstr); + } + } + rc = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT); + if (rc != PAM_SUCCESS) { + errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_end: %s", errstr); + status = AUTH_ERROR; + } + pamh = NULL; + } + + debug_return_int(status); +} + +#define PROMPT_IS_PASSWORD(_p) \ + (strncmp((_p), "Password:", 9) == 0 && \ + ((_p)[9] == '\0' || ((_p)[9] == ' ' && (_p)[10] == '\0'))) + +#ifdef PAM_TEXT_DOMAIN +# define PAM_PROMPT_IS_PASSWORD(_p) \ + (strcmp((_p), dgt(PAM_TEXT_DOMAIN, "Password:")) == 0 || \ + strcmp((_p), dgt(PAM_TEXT_DOMAIN, "Password: ")) == 0 || \ + PROMPT_IS_PASSWORD(_p)) +#else +# define PAM_PROMPT_IS_PASSWORD(_p) PROMPT_IS_PASSWORD(_p) +#endif /* PAM_TEXT_DOMAIN */ + +/* + * We use the PAM prompt in preference to sudo's as long + * as passprompt_override is not set and: + * a) the (translated) sudo prompt matches /^Password: ?/ + * or: + * b) the PAM prompt itself *doesn't* match /^Password: ?/ + * or /^username's Password: ?/ + * + * The intent is to use the PAM prompt for things like + * challenge-response, otherwise use sudo's prompt. + * There may also be cases where a localized translation + * of "Password: " exists for PAM but not for sudo. + */ +static bool +use_pam_prompt(const char *pam_prompt) +{ + size_t user_len; + debug_decl(use_pam_prompt, SUDOERS_DEBUG_AUTH); + + /* Always use sudo prompt if passprompt_override is set. */ + if (def_passprompt_override) + debug_return_bool(false); + + /* If sudo prompt matches "^Password: ?$", use PAM prompt. */ + if (PROMPT_IS_PASSWORD(def_prompt)) + debug_return_bool(true); + + /* If PAM prompt matches "^Password: ?$", use sudo prompt. */ + if (PAM_PROMPT_IS_PASSWORD(pam_prompt)) + debug_return_bool(false); + + /* + * Some PAM modules use "^username's Password: ?$" instead of + * "^Password: ?" so check for that too. + */ + if (pam_closure.ctx != NULL) { + const char *user_name = pam_closure.ctx->user.name; + user_len = strlen(user_name); + if (strncmp(pam_prompt, user_name, user_len) == 0) { + const char *cp = pam_prompt + user_len; + if (strncmp(cp, "'s Password:", 12) == 0 && + (cp[12] == '\0' || (cp[12] == ' ' && cp[13] == '\0'))) + debug_return_bool(false); + } + } + + /* Otherwise, use the PAM prompt. */ + debug_return_bool(true); +} + +static bool +is_filtered(const char *msg) +{ + bool filtered = false; + + if (conv_filter != NULL) { + struct conv_filter *filt = conv_filter; + while (filt->msg != NULL) { + if (strncmp(msg, filt->msg, filt->msglen) == 0) { + filtered = true; + break; + } + filt++; + } + } + return filtered; +} + +/* + * ``Conversation function'' for PAM <-> human interaction. + */ +static int +converse(int num_msg, PAM_CONST struct pam_message **msg, + struct pam_response **reply_out, void *appdata_ptr) +{ + struct sudo_conv_callback *callback = NULL; + struct sudo_pam_closure *closure = appdata_ptr; + struct pam_response *reply; + const char *prompt; + char *pass; + int n, type; + debug_decl(converse, SUDOERS_DEBUG_AUTH); + + if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "invalid number of PAM messages: %d", num_msg); + debug_return_int(PAM_CONV_ERR); + } + sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, + "number of PAM messages: %d", num_msg); + + reply = calloc((size_t)num_msg, sizeof(struct pam_response)); + if (reply == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(PAM_BUF_ERR); + } + + if (closure != NULL) + callback = closure->callback; + + for (n = 0; n < num_msg; n++) { + PAM_CONST struct pam_message *pm = PAM_MSG_GET(msg, n); + + type = SUDO_CONV_PROMPT_ECHO_OFF; + switch (pm->msg_style) { + case PAM_PROMPT_ECHO_ON: + type = SUDO_CONV_PROMPT_ECHO_ON; + FALLTHROUGH; + case PAM_PROMPT_ECHO_OFF: + /* Error out if the last password read was interrupted. */ + if (getpass_error) + goto bad; + + /* Treat non-interactive mode as a getpass error. */ + if (noninteractive) { + getpass_error = true; + goto bad; + } + + /* Choose either the sudo prompt or the PAM one. */ + prompt = use_pam_prompt(pm->msg) ? pm->msg : def_prompt; + + /* Read the password unless interrupted. */ + pass = auth_getpass(prompt, type, callback); + if (pass == NULL) { + /* Error (or ^C) reading password, don't try again. */ + getpass_error = true; + goto bad; + } + if (strlen(pass) >= PAM_MAX_RESP_SIZE) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "password longer than %d", PAM_MAX_RESP_SIZE); + freezero(pass, strlen(pass)); + pass = NULL; + goto bad; + } + reply[n].resp = pass; /* auth_getpass() malloc's a copy */ + break; + case PAM_TEXT_INFO: + if (pm->msg != NULL && !is_filtered(pm->msg)) + sudo_printf(SUDO_CONV_INFO_MSG|SUDO_CONV_PREFER_TTY, + "%s\n", pm->msg); + break; + case PAM_ERROR_MSG: + if (pm->msg != NULL) + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "%s\n", pm->msg); + break; + default: + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unsupported message style: %d", pm->msg_style); + goto bad; + } + } + + *reply_out = reply; + debug_return_int(PAM_SUCCESS); + +bad: + /* Zero and free allocated memory and return an error. */ + for (n = 0; n < num_msg; n++) { + struct pam_response *pr = &reply[n]; + + if (pr->resp != NULL) { + freezero(pr->resp, strlen(pr->resp)); + pr->resp = NULL; + } + } + free(reply); + debug_return_int(PAM_CONV_ERR); +} + +#endif /* HAVE_PAM */ diff --git a/plugins/sudoers/auth/passwd.c b/plugins/sudoers/auth/passwd.c new file mode 100644 index 0000000..0b1fcde --- /dev/null +++ b/plugins/sudoers/auth/passwd.c @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2010-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +#define DESLEN 13 +#define HAS_AGEINFO(p, l) (l == 18 && p[DESLEN] == ',') + +int +sudo_passwd_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + debug_decl(sudo_passwd_init, SUDOERS_DEBUG_AUTH); + + /* Only initialize once. */ + if (auth->data != NULL) + debug_return_int(AUTH_SUCCESS); + +#ifdef HAVE_SKEYACCESS + if (skeyaccess(pw, ctx->user.tty, NULL, NULL) == 0) + debug_return_int(AUTH_FAILURE); +#endif + sudo_setspent(); + auth->data = sudo_getepw(pw); + sudo_endspent(); + debug_return_int(auth->data ? AUTH_SUCCESS : AUTH_ERROR); +} + +#ifdef HAVE_CRYPT +int +sudo_passwd_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + char des_pass[9], *epass; + char *pw_epasswd = auth->data; + size_t pw_len; + int ret; + debug_decl(sudo_passwd_verify, SUDOERS_DEBUG_AUTH); + + /* An empty plain-text password must match an empty encrypted password. */ + if (pass[0] == '\0') + debug_return_int(pw_epasswd[0] ? AUTH_FAILURE : AUTH_SUCCESS); + + /* + * Truncate to 8 chars if standard DES since not all crypt()'s do this. + */ + pw_len = strlen(pw_epasswd); + if (pw_len == DESLEN || HAS_AGEINFO(pw_epasswd, pw_len)) { + (void)strlcpy(des_pass, pass, sizeof(des_pass)); + pass = des_pass; + } + + /* + * Normal UN*X password check. + * HP-UX may add aging info (separated by a ',') at the end so + * only compare the first DESLEN characters in that case. + */ + epass = (char *) crypt(pass, pw_epasswd); + ret = AUTH_FAILURE; + if (epass != NULL) { + if (HAS_AGEINFO(pw_epasswd, pw_len) && strlen(epass) == DESLEN) { + if (strncmp(pw_epasswd, epass, DESLEN) == 0) + ret = AUTH_SUCCESS; + } else { + if (strcmp(pw_epasswd, epass) == 0) + ret = AUTH_SUCCESS; + } + } + + explicit_bzero(des_pass, sizeof(des_pass)); + + debug_return_int(ret); +} +#else +int +sudo_passwd_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + char *pw_passwd = auth->data; + int ret; + debug_decl(sudo_passwd_verify, SUDOERS_DEBUG_AUTH); + + /* Simple string compare for systems without crypt(). */ + if (strcmp(pass, pw_passwd) == 0) + ret = AUTH_SUCCESS; + else + ret = AUTH_FAILURE; + + debug_return_int(ret); +} +#endif + +int +sudo_passwd_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + debug_decl(sudo_passwd_cleanup, SUDOERS_DEBUG_AUTH); + + if (auth->data != NULL) { + /* Zero out encrypted password before freeing. */ + size_t len = strlen((char *)auth->data); + freezero(auth->data, len); + auth->data = NULL; + } + + debug_return_int(AUTH_SUCCESS); +} diff --git a/plugins/sudoers/auth/rfc1938.c b/plugins/sudoers/auth/rfc1938.c new file mode 100644 index 0000000..bde9e33 --- /dev/null +++ b/plugins/sudoers/auth/rfc1938.c @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1994-1996, 1998-2005, 2010-2012, 2014-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#if defined(HAVE_SKEY) || defined(HAVE_OPIE) + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#if defined(HAVE_SKEY) +# include <skey.h> +# define RFC1938 skey +# ifdef HAVE_RFC1938_SKEYCHALLENGE +# define rfc1938challenge(a,b,c,d) skeychallenge((a),(b),(c),(d)) +# else +# define rfc1938challenge(a,b,c,d) skeychallenge((a),(b),(c)) +# endif +# define rfc1938verify(a,b) skeyverify((a),(b)) +#elif defined(HAVE_OPIE) +# include <opie.h> +# define RFC1938 opie +# define rfc1938challenge(a,b,c,d) opiechallenge((a),(b),(c)) +# define rfc1938verify(a,b) opieverify((a),(b)) +#endif + +#include <sudoers.h> +#include "sudo_auth.h" + +int +sudo_rfc1938_setup(const struct sudoers_context *ctx, struct passwd *pw, + char **promptp, sudo_auth *auth) +{ + char challenge[256]; + size_t challenge_len; + static char *orig_prompt = NULL, *new_prompt = NULL; + static size_t op_len, np_size; + static struct RFC1938 rfc1938; + debug_decl(sudo_rfc1938_setup, SUDOERS_DEBUG_AUTH); + + /* Stash a pointer to the rfc1938 struct if we have not initialized */ + if (!auth->data) + auth->data = &rfc1938; + + /* Save the original prompt */ + if (orig_prompt == NULL) { + orig_prompt = *promptp; + op_len = strlen(orig_prompt); + + /* Ignore trailing colon (we will add our own) */ + if (orig_prompt[op_len - 1] == ':') + op_len--; + else if (op_len >= 2 && orig_prompt[op_len - 1] == ' ' + && orig_prompt[op_len - 2] == ':') + op_len -= 2; + } + +#ifdef HAVE_SKEY + /* Close old stream */ + if (rfc1938.keyfile) + (void) fclose(rfc1938.keyfile); +#endif + + /* + * Look up the user and get the rfc1938 challenge. + * If the user is not in the OTP db, only post a fatal error if + * we are running alone (since they may just use a normal passwd). + */ + if (rfc1938challenge(&rfc1938, pw->pw_name, challenge, sizeof(challenge))) { + if (IS_ONEANDONLY(auth)) { + sudo_warnx(U_("you do not exist in the %s database"), auth->name); + debug_return_int(AUTH_ERROR); + } else { + debug_return_int(AUTH_FAILURE); + } + } + + /* Get space for new prompt with embedded challenge */ + challenge_len = strlen(challenge); + if (np_size < op_len + challenge_len + 7) { + char *p = realloc(new_prompt, op_len + challenge_len + 7); + if (p == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(AUTH_ERROR); + } + np_size = op_len + challenge_len + 7; + new_prompt = p; + } + + if (def_long_otp_prompt) + (void) snprintf(new_prompt, np_size, "%s\n%s", challenge, orig_prompt); + else + (void) snprintf(new_prompt, np_size, "%.*s [ %s ]:", (int)op_len, + orig_prompt, challenge); + + *promptp = new_prompt; + debug_return_int(AUTH_SUCCESS); +} + +int +sudo_rfc1938_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + debug_decl(sudo_rfc1938_verify, SUDOERS_DEBUG_AUTH); + + if (rfc1938verify((struct RFC1938 *) auth->data, (char *)pass) == 0) + debug_return_int(AUTH_SUCCESS); + else + debug_return_int(AUTH_FAILURE); +} + +#endif /* HAVE_SKEY || HAVE_OPIE */ diff --git a/plugins/sudoers/auth/secureware.c b/plugins/sudoers/auth/secureware.c new file mode 100644 index 0000000..a193e55 --- /dev/null +++ b/plugins/sudoers/auth/secureware.c @@ -0,0 +1,116 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1998-2005, 2010-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_GETPRPWNAM + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#ifdef __hpux +# undef MAXINT +# include <hpsecurity.h> +#else +# include <sys/security.h> +#endif /* __hpux */ +#include <prot.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +#ifdef __alpha +extern int crypt_type; +#endif + +int +sudo_secureware_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + debug_decl(sudo_secureware_init, SUDOERS_DEBUG_AUTH); + + /* Only initialize once. */ + if (auth->data != NULL) + debug_return_int(AUTH_SUCCESS); + +#ifdef __alpha + if (crypt_type == INT_MAX) + debug_return_int(AUTH_FAILURE); /* no shadow */ +#endif + + sudo_setspent(); + auth->data = sudo_getepw(pw); + sudo_endspent(); + debug_return_int(auth->data ? AUTH_SUCCESS : AUTH_ERROR); +} + +int +sudo_secureware_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + char *pw_epasswd = auth->data; + char *epass = NULL; + debug_decl(sudo_secureware_verify, SUDOERS_DEBUG_AUTH); + + /* An empty plain-text password must match an empty encrypted password. */ + if (pass[0] == '\0') + debug_return_int(pw_epasswd[0] ? AUTH_FAILURE : AUTH_SUCCESS); + +#if defined(__alpha) +# ifdef HAVE_DISPCRYPT + epass = dispcrypt(pass, pw_epasswd, crypt_type); +# else + if (crypt_type == AUTH_CRYPT_BIGCRYPT) + epass = bigcrypt(pass, pw_epasswd); + else if (crypt_type == AUTH_CRYPT_CRYPT16) + epass = crypt(pass, pw_epasswd); +# endif /* HAVE_DISPCRYPT */ +#elif defined(HAVE_BIGCRYPT) + epass = bigcrypt(pass, pw_epasswd); +#endif /* __alpha */ + + if (epass != NULL && strcmp(pw_epasswd, epass) == 0) + debug_return_int(AUTH_SUCCESS); + debug_return_int(AUTH_FAILURE); +} + +int +sudo_secureware_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + char *pw_epasswd = auth->data; + debug_decl(sudo_secureware_cleanup, SUDOERS_DEBUG_AUTH); + + if (pw_epasswd != NULL) + freezero(pw_epasswd, strlen(pw_epasswd)); + debug_return_int(AUTH_SUCCESS); +} + +#endif /* HAVE_GETPRPWNAM */ diff --git a/plugins/sudoers/auth/securid5.c b/plugins/sudoers/auth/securid5.c new file mode 100644 index 0000000..6d3c636 --- /dev/null +++ b/plugins/sudoers/auth/securid5.c @@ -0,0 +1,237 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007, 2010-2012, 2014-2016 + * Todd C. Miller <Todd.Miller@sudo.ws> + * Copyright (c) 2002 Michael Stroucken <michael@stroucken.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_SECURID + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +/* Needed for SecurID v5.0 Authentication on UNIX */ +#define UNIX 1 +#include <acexport.h> +#include <sdacmvls.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +/* + * securid_init - Initialises communications with ACE server + * Arguments in: + * pw - UNUSED + * auth - sudo authentication structure + * + * Results out: + * auth - auth->data contains pointer to new SecurID handle + * return code - Fatal if initialization unsuccessful, otherwise + * success. + */ +int +sudo_securid_init(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth) +{ + static SDI_HANDLE sd_dat; /* SecurID handle */ + debug_decl(sudo_securid_init, SUDOERS_DEBUG_AUTH); + + /* Only initialize once. */ + 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")); + debug_return_int(AUTH_ERROR); + } + + auth->data = (void *) &sd_dat; /* For method-specific data */ + + debug_return_int(AUTH_SUCCESS); +} + +/* + * securid_setup - Initialises a SecurID transaction and locks out other + * ACE servers + * + * Arguments in: + * pw - struct passwd for username + * promptp - UNUSED + * auth - sudo authentication structure for SecurID handle + * + * Results out: + * return code - Success if transaction started correctly, fatal + * otherwise + */ +int +sudo_securid_setup(const struct sudoers_context *ctx, struct passwd *pw, + char **promptp, sudo_auth *auth) +{ + SDI_HANDLE *sd = (SDI_HANDLE *) auth->data; + int retval; + debug_decl(sudo_securid_setup, SUDOERS_DEBUG_AUTH); + + /* Re-initialize SecurID every time. */ + if (SD_Init(sd) != ACM_OK) { + sudo_warnx("%s", U_("unable to contact the SecurID server")); + debug_return_int(AUTH_ERROR); + } + + /* Lock new PIN code */ + retval = SD_Lock(*sd, pw->pw_name); + + switch (retval) { + case ACM_OK: + sudo_warnx("%s", U_("User ID locked for SecurID Authentication")); + debug_return_int(AUTH_SUCCESS); + + case ACE_UNDEFINED_USERNAME: + sudo_warnx("%s", U_("invalid username length for SecurID")); + debug_return_int(AUTH_ERROR); + + case ACE_ERR_INVALID_HANDLE: + sudo_warnx("%s", U_("invalid Authentication Handle for SecurID")); + debug_return_int(AUTH_ERROR); + + case ACM_ACCESS_DENIED: + sudo_warnx("%s", U_("SecurID communication failed")); + debug_return_int(AUTH_ERROR); + + default: + sudo_warnx("%s", U_("unknown SecurID error")); + debug_return_int(AUTH_ERROR); + } +} + +/* + * securid_verify - Authenticates user and handles ACE responses + * + * Arguments in: + * pw - struct passwd for username + * prompt - UNUSED + * auth - sudo authentication structure for SecurID handle + * + * Results out: + * return code - Success on successful authentication, failure on + * incorrect authentication, fatal on errors + */ +int +sudo_securid_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *promp, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + SDI_HANDLE *sd = (SDI_HANDLE *) auth->data; + char *pass; + int ret; + debug_decl(sudo_securid_verify, SUDOERS_DEBUG_AUTH); + + pass = auth_getpass("Enter your PASSCODE: ", SUDO_CONV_PROMPT_ECHO_OFF, + callback); + + /* Have ACE verify password */ + switch (SD_Check(*sd, pass, pw->pw_name)) { + case ACM_OK: + ret = AUTH_SUCESS; + break; + + case ACE_UNDEFINED_PASSCODE: + sudo_warnx("%s", U_("invalid passcode length for SecurID")); + ret = AUTH_ERROR; + break; + + case ACE_UNDEFINED_USERNAME: + sudo_warnx("%s", U_("invalid username length for SecurID")); + ret = AUTH_ERROR; + break; + + case ACE_ERR_INVALID_HANDLE: + sudo_warnx("%s", U_("invalid Authentication Handle for SecurID")); + ret = AUTH_ERROR; + break; + + case ACM_ACCESS_DENIED: + ret = AUTH_FAILURE; + break; + + case ACM_NEXT_CODE_REQUIRED: + /* Sometimes (when current token close to expire?) + ACE challenges for the next token displayed + (entered without the PIN) */ + if (pass != NULL) + freezero(pass, strlen(pass)); + pass = auth_getpass("\ +!!! ATTENTION !!!\n\ +Wait for the token code to change, \n\ +then enter the new token code.\n", \ + SUDO_CONV_PROMPT_ECHO_OFF, callback); + + if (SD_Next(*sd, pass) == ACM_OK) { + ret = AUTH_SUCCESS; + break; + } + + ret = AUTH_FAILURE; + break; + + case ACM_NEW_PIN_REQUIRED: + /* + * This user's SecurID has not been activated yet, + * or the pin has been reset + */ + /* XXX - Is setting up a new PIN within sudo's scope? */ + SD_Pin(*sd, ""); + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "Your SecurID access has not yet been set up.\n"); + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, + "Please set up a PIN before you try to authenticate.\n"); + ret = AUTH_ERROR; + break; + + default: + sudo_warnx("%s", U_("unknown SecurID error")); + ret = AUTH_ERROR; + break; + } + + /* Free resources */ + SD_Close(*sd); + + if (pass != NULL) + freezero(pass, strlen(pass)); + + /* Return stored state to calling process */ + debug_return_int(ret); +} + +#endif /* HAVE_SECURID */ diff --git a/plugins/sudoers/auth/sia.c b/plugins/sudoers/auth/sia.c new file mode 100644 index 0000000..9c55071 --- /dev/null +++ b/plugins/sudoers/auth/sia.c @@ -0,0 +1,160 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007, 2010-2015 + * Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifdef HAVE_SIA_SES_INIT + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#include <signal.h> +#include <siad.h> + +#include <sudoers.h> +#include "sudo_auth.h" + +static int sudo_argc; +static char **sudo_argv; +static char *sudo_tty; + +int +sudo_sia_setup(const struct sudoers_context *ctx, struct passwd *pw, + char **promptp, sudo_auth *auth) +{ + SIAENTITY *siah; + int i; + debug_decl(sudo_sia_setup, SUDOERS_DEBUG_AUTH); + + /* Rebuild argv for sia_ses_init() */ + sudo_argc = ctx->runas.argc + 1; + sudo_argv = reallocarray(NULL, sudo_argc + 1, sizeof(char *)); + if (sudo_argv == NULL) { + log_warningx(ctx, 0, N_("unable to allocate memory")); + debug_return_int(AUTH_ERROR); + } + sudo_argv[0] = "sudo"; + for (i = 0; i < ctx->runas.argc; i++) + sudo_argv[i + 1] = ctx->runas.argv[i]; + sudo_argv[sudo_argc] = NULL; + + /* We don't let SIA prompt the user for input. */ + sudo_tty = ctx->user.ttypath; + if (sia_ses_init(&siah, sudo_argc, sudo_argv, NULL, pw->pw_name, sudo_tty, 0, NULL) != SIASUCCESS) { + log_warning(ctx, 0, N_("unable to initialize SIA session")); + debug_return_int(AUTH_ERROR); + } + + auth->data = siah; + debug_return_int(AUTH_SUCCESS); +} + +int +sudo_sia_verify(const struct sudoers_context *ctx, struct passwd *pw, + const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback) +{ + SIAENTITY *siah = auth->data; + char *pass; + 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) + debug_return_int(AUTH_INTR); + + /* Check password and zero out plaintext copy. */ + rc = sia_ses_authent(NULL, pass, siah); + freezero(pass, strlen(pass)); + + if (rc == SIASUCCESS) + debug_return_int(AUTH_SUCCESS); + if (ISSET(rc, SIASTOP)) + debug_return_int(AUTH_ERROR); + debug_return_int(AUTH_FAILURE); +} + +int +sudo_sia_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + sudo_auth *auth, bool force) +{ + SIAENTITY *siah = auth->data; + debug_decl(sudo_sia_cleanup, SUDOERS_DEBUG_AUTH); + + (void) sia_ses_release(&siah); + auth->data = NULL; + free(sudo_argv); + debug_return_int(AUTH_SUCCESS); +} + +int +sudo_sia_begin_session(const struct sudoers_context *ctx, struct passwd *pw, + char **user_envp[], sudo_auth *auth) +{ + SIAENTITY *siah; + int status = AUTH_ERROR; + debug_decl(sudo_sia_begin_session, SUDOERS_DEBUG_AUTH); + + /* Re-init sia for the target user's session. */ + if (sia_ses_init(&siah, sudo_argc - 1, sudo_argv + 1, NULL, pw->pw_name, sudo_tty, 0, NULL) != SIASUCCESS) { + log_warning(ctx, 0, N_("unable to initialize SIA session")); + goto done; + } + + if (sia_make_entity_pwd(pw, siah) != SIASUCCESS) { + sudo_warn("sia_make_entity_pwd"); + goto done; + } + + status = AUTH_FAILURE; /* no more fatal errors. */ + + siah->authtype = SIA_A_NONE; + if (sia_ses_estab(sia_collect_trm, siah) != SIASUCCESS) { + sudo_warn("sia_ses_estab"); + goto done; + } + + if (sia_ses_launch(sia_collect_trm, siah) != SIASUCCESS) { + sudo_warn("sia_ses_launch"); + goto done; + } + + status = AUTH_SUCCESS; + +done: + (void) sia_ses_release(&siah); + debug_return_int(status); +} + +#endif /* HAVE_SIA_SES_INIT */ diff --git a/plugins/sudoers/auth/sudo_auth.c b/plugins/sudoers/auth/sudo_auth.c new file mode 100644 index 0000000..b74ab34 --- /dev/null +++ b/plugins/sudoers/auth/sudo_auth.c @@ -0,0 +1,557 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2008-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#include <time.h> +#include <signal.h> + +#include <sudoers.h> +#include "sudo_auth.h" +#include <insults.h> +#include <timestamp.h> + +static sudo_auth auth_switch[] = { +/* Standalone entries first */ +#ifdef HAVE_AIXAUTH + AUTH_ENTRY("aixauth", FLAG_STANDALONE, sudo_aix_init, NULL, sudo_aix_verify, NULL, sudo_aix_cleanup, NULL, NULL) +#endif +#ifdef HAVE_PAM + AUTH_ENTRY("pam", FLAG_STANDALONE, sudo_pam_init, NULL, sudo_pam_verify, sudo_pam_approval, sudo_pam_cleanup, sudo_pam_begin_session, sudo_pam_end_session) +#endif +#ifdef HAVE_SECURID + AUTH_ENTRY("SecurId", FLAG_STANDALONE, sudo_securid_init, sudo_securid_setup, sudo_securid_verify, NULL, NULL, NULL, NULL) +#endif +#ifdef HAVE_SIA_SES_INIT + AUTH_ENTRY("sia", FLAG_STANDALONE, NULL, sudo_sia_setup, sudo_sia_verify, NULL, sudo_sia_cleanup, sudo_sia_begin_session, NULL) +#endif +#ifdef HAVE_FWTK + AUTH_ENTRY("fwtk", FLAG_STANDALONE, sudo_fwtk_init, NULL, sudo_fwtk_verify, NULL, sudo_fwtk_cleanup, NULL, NULL) +#endif +#ifdef HAVE_BSD_AUTH_H + AUTH_ENTRY("bsdauth", FLAG_STANDALONE, bsdauth_init, NULL, bsdauth_verify, bsdauth_approval, bsdauth_cleanup, NULL, NULL) +#endif + +/* Non-standalone entries */ +#ifndef WITHOUT_PASSWD + AUTH_ENTRY("passwd", 0, sudo_passwd_init, NULL, sudo_passwd_verify, NULL, sudo_passwd_cleanup, NULL, NULL) +#endif +#if defined(HAVE_GETPRPWNAM) && !defined(WITHOUT_PASSWD) + AUTH_ENTRY("secureware", 0, sudo_secureware_init, NULL, sudo_secureware_verify, NULL, sudo_secureware_cleanup, NULL, NULL) +#endif +#ifdef HAVE_AFS + AUTH_ENTRY("afs", 0, NULL, NULL, sudo_afs_verify, NULL, NULL, NULL, NULL) +#endif +#ifdef HAVE_DCE + AUTH_ENTRY("dce", 0, NULL, NULL, sudo_dce_verify, NULL, NULL, NULL, NULL) +#endif +#ifdef HAVE_KERB5 + AUTH_ENTRY("kerb5", 0, sudo_krb5_init, sudo_krb5_setup, sudo_krb5_verify, NULL, sudo_krb5_cleanup, NULL, NULL) +#endif +#ifdef HAVE_SKEY + AUTH_ENTRY("S/Key", 0, NULL, sudo_rfc1938_setup, sudo_rfc1938_verify, NULL, NULL, NULL, NULL) +#endif +#ifdef HAVE_OPIE + AUTH_ENTRY("OPIE", 0, NULL, sudo_rfc1938_setup, sudo_rfc1938_verify, NULL, NULL, NULL, NULL) +#endif + AUTH_ENTRY(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL) +}; + +static bool standalone; + +/* + * Initialize sudoers authentication method(s). + * Returns AUTH_SUCCESS on success and AUTH_ERROR on error. + */ +int +sudo_auth_init(const struct sudoers_context *ctx, struct passwd *pw, + unsigned int mode) +{ + sudo_auth *auth; + debug_decl(sudo_auth_init, SUDOERS_DEBUG_AUTH); + + if (auth_switch[0].name == NULL) + debug_return_int(AUTH_SUCCESS); + + /* 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. */ + switch ((auth->init)(ctx, pw, auth)) { + case AUTH_SUCCESS: + break; + case AUTH_FAILURE: + SET(auth->flags, FLAG_DISABLED); + break; + default: + /* Assume error msg already printed. */ + debug_return_int(AUTH_ERROR); + } + } + } + + /* + * Make sure we haven't mixed standalone and shared auth methods. + * If there are multiple standalone methods, only use the first one. + */ + if ((standalone = IS_STANDALONE(&auth_switch[0]))) { + bool found = false; + for (auth = auth_switch; auth->name; auth++) { + if (IS_DISABLED(auth)) + continue; + if (!IS_STANDALONE(auth)) { + audit_failure(ctx, ctx->runas.argv, + N_("invalid authentication methods")); + log_warningx(ctx, SLOG_SEND_MAIL, + N_("Invalid authentication methods compiled into sudo! " + "You may not mix standalone and non-standalone authentication.")); + debug_return_int(AUTH_ERROR); + } + if (!found) { + /* Found first standalone method. */ + found = true; + continue; + } + /* Disable other standalone methods. */ + SET(auth->flags, FLAG_DISABLED); + } + } + + /* Set FLAG_ONEANDONLY if there is only one auth method. */ + for (auth = auth_switch; auth->name; auth++) { + /* Find first enabled auth method. */ + if (!IS_DISABLED(auth)) { + sudo_auth *first = auth; + /* Check for others. */ + for (; auth->name; auth++) { + if (!IS_DISABLED(auth)) + break; + } + if (auth->name == NULL) + SET(first->flags, FLAG_ONEANDONLY); + break; + } + } + + debug_return_int(AUTH_SUCCESS); +} + +/* + * Call all authentication approval methods, if any. + * Returns AUTH_SUCCESS, AUTH_FAILURE or AUTH_ERROR. + */ +int +sudo_auth_approval(const struct sudoers_context *ctx, struct passwd *pw, + unsigned int validated, bool exempt) +{ + int ret = AUTH_SUCCESS; + sudo_auth *auth; + debug_decl(sudo_auth_approval, SUDOERS_DEBUG_AUTH); + + /* Call approval routines. */ + for (auth = auth_switch; auth->name; auth++) { + if (auth->approval && !IS_DISABLED(auth)) { + ret = (auth->approval)(ctx, pw, auth, exempt); + if (ret != AUTH_SUCCESS) { + /* Assume error msg already printed. */ + log_auth_failure(ctx, validated, 0); + break; + } + } + } + debug_return_int(ret); +} + +/* + * Cleanup all authentication methods. + * Returns AUTH_SUCCESS on success and AUTH_ERROR on error. + */ +int +sudo_auth_cleanup(const struct sudoers_context *ctx, struct passwd *pw, + bool force) +{ + sudo_auth *auth; + debug_decl(sudo_auth_cleanup, SUDOERS_DEBUG_AUTH); + + /* Call cleanup routines. */ + for (auth = auth_switch; auth->name; auth++) { + if (auth->cleanup && !IS_DISABLED(auth)) { + int status = (auth->cleanup)(ctx, pw, auth, force); + if (status != AUTH_SUCCESS) { + /* Assume error msg already printed. */ + debug_return_int(AUTH_ERROR); + } + } + } + debug_return_int(AUTH_SUCCESS); +} + +static void +pass_warn(void) +{ + const char *warning = def_badpass_message; + debug_decl(pass_warn, SUDOERS_DEBUG_AUTH); + +#ifdef INSULT + if (def_insults) + warning = INSULT; +#endif + sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY, "%s\n", warning); + + debug_return; +} + +static bool +user_interrupted(void) +{ + sigset_t mask; + + return (sigpending(&mask) == 0 && + (sigismember(&mask, SIGINT) || sigismember(&mask, SIGQUIT))); +} + +/* + * Called when getpass is suspended so we can drop the lock. + */ +static int +getpass_suspend(int signo, void *vclosure) +{ + struct getpass_closure *closure = vclosure; + + timestamp_close(closure->cookie); + closure->cookie = NULL; + return 0; +} + +/* + * Called when getpass is resumed so we can reacquire the lock. + */ +static int +getpass_resume(int signo, void *vclosure) +{ + struct getpass_closure *closure = vclosure; + + closure->cookie = timestamp_open(closure->ctx); + if (closure->cookie == NULL) + return -1; + if (!timestamp_lock(closure->cookie, closure->auth_pw)) + return -1; + return 0; +} + +/* + * Verify the specified user. + * Returns AUTH_SUCCESS, AUTH_FAILURE or AUTH_ERROR. + */ +int +verify_user(const struct sudoers_context *ctx, struct passwd *pw, char *prompt, + unsigned int validated, struct sudo_conv_callback *callback) +{ + struct sigaction sa, saved_sigtstp; + int ret = AUTH_FAILURE; + unsigned int ntries; + sigset_t mask, omask; + sudo_auth *auth; + debug_decl(verify_user, SUDOERS_DEBUG_AUTH); + + /* Make sure we have at least one auth method. */ + if (auth_switch[0].name == NULL) { + audit_failure(ctx, ctx->runas.argv, N_("no authentication methods")); + log_warningx(ctx, SLOG_SEND_MAIL, + N_("There are no authentication methods compiled into sudo! " + "If you want to turn off authentication, use the " + "--disable-authentication configure option.")); + debug_return_int(AUTH_ERROR); + } + + /* Enable suspend during password entry. */ + callback->on_suspend = getpass_suspend; + callback->on_resume = getpass_resume; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + (void) sigaction(SIGTSTP, &sa, &saved_sigtstp); + + /* + * We treat authentication as a critical section and block + * keyboard-generated signals such as SIGINT and SIGQUIT + * which might otherwise interrupt a sleep(3). + * They are temporarily unblocked by auth_getpass(). + */ + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGQUIT); + (void) sigprocmask(SIG_BLOCK, &mask, &omask); + + for (ntries = 0; ntries < def_passwd_tries; ntries++) { + int num_methods = 0; + char *pass = NULL; + + /* If user attempted to interrupt password verify, quit now. */ + if (user_interrupted()) + goto done; + + if (ntries != 0) + pass_warn(); + + /* Do any per-method setup and unconfigure the method if needed */ + for (auth = auth_switch; auth->name; auth++) { + if (IS_DISABLED(auth)) + continue; + num_methods++; + if (auth->setup != NULL) { + switch ((auth->setup)(ctx, pw, &prompt, auth)) { + case AUTH_SUCCESS: + if (user_interrupted()) + goto done; /* assume error msg already printed */ + break; + case AUTH_FAILURE: + SET(auth->flags, FLAG_DISABLED); + break; + case AUTH_NONINTERACTIVE: + /* Non-interactive mode, cannot prompt user. */ + goto done; + default: + ret = AUTH_ERROR; + goto done; + } + } + } + if (num_methods == 0) { + audit_failure(ctx, ctx->runas.argv, + N_("no authentication methods")); + log_warningx(ctx, SLOG_SEND_MAIL, + N_("Unable to initialize authentication methods.")); + debug_return_int(AUTH_ERROR); + } + + /* Get the password unless the auth function will do it for us */ + if (!standalone) { + if (IS_NONINTERACTIVE(&auth_switch[0])) { + ret = AUTH_NONINTERACTIVE; + goto done; + } + pass = auth_getpass(prompt, SUDO_CONV_PROMPT_ECHO_OFF, callback); + if (pass == NULL) + break; + } + + /* Call authentication functions. */ + for (auth = auth_switch; auth->name; auth++) { + if (IS_DISABLED(auth)) + continue; + + ret = auth->status = (auth->verify)(ctx, pw, + standalone ? prompt : pass, auth, callback); + if (ret != AUTH_FAILURE) + break; + } + if (pass != NULL) + freezero(pass, strlen(pass)); + + if (ret != AUTH_FAILURE) + goto done; + } + +done: + /* Restore signal handlers and signal mask. */ + (void) sigaction(SIGTSTP, &saved_sigtstp, NULL); + (void) sigprocmask(SIG_SETMASK, &omask, NULL); + + switch (ret) { + case AUTH_SUCCESS: + break; + case AUTH_INTR: + ret = AUTH_FAILURE; + FALLTHROUGH; + case AUTH_FAILURE: + if (ntries != 0) + SET(validated, FLAG_BAD_PASSWORD); + log_auth_failure(ctx, validated, ntries); + break; + case AUTH_NONINTERACTIVE: + SET(validated, FLAG_NO_USER_INPUT); + FALLTHROUGH; + default: + log_auth_failure(ctx, validated, 0); + ret = AUTH_ERROR; + break; + } + + debug_return_int(ret); +} + +/* + * Call authentication method begin session hooks. + * Returns true on success, false on failure and -1 on error. + */ +int +sudo_auth_begin_session(const struct sudoers_context *ctx, struct passwd *pw, + char **user_env[]) +{ + sudo_auth *auth; + int ret = true; + debug_decl(sudo_auth_begin_session, SUDOERS_DEBUG_AUTH); + + for (auth = auth_switch; auth->name; auth++) { + if (auth->begin_session && !IS_DISABLED(auth)) { + int status = (auth->begin_session)(ctx, pw, user_env, auth); + switch (status) { + case AUTH_SUCCESS: + break; + case AUTH_FAILURE: + ret = false; + break; + default: + /* Assume error msg already printed. */ + ret = -1; + break; + } + } + } + debug_return_int(ret); +} + +bool +sudo_auth_needs_end_session(void) +{ + sudo_auth *auth; + bool needed = false; + debug_decl(sudo_auth_needs_end_session, SUDOERS_DEBUG_AUTH); + + for (auth = auth_switch; auth->name; auth++) { + if (auth->end_session && !IS_DISABLED(auth)) { + needed = true; + break; + } + } + debug_return_bool(needed); +} + +/* + * Call authentication method end session hooks. + * Returns true on success, false on failure and -1 on error. + */ +int +sudo_auth_end_session(void) +{ + sudo_auth *auth; + int ret = true; + int status; + debug_decl(sudo_auth_end_session, SUDOERS_DEBUG_AUTH); + + for (auth = auth_switch; auth->name; auth++) { + if (auth->end_session && !IS_DISABLED(auth)) { + status = (auth->end_session)(auth); + switch (status) { + case AUTH_SUCCESS: + break; + case AUTH_FAILURE: + ret = false; + break; + default: + /* Assume error msg already printed. */ + ret = -1; + break; + } + } + } + debug_return_int(ret); +} + +/* + * Prompts the user for a password using the conversation function. + * Returns the plaintext password or NULL. + * The user is responsible for freeing the returned value. + */ +char * +auth_getpass(const char *prompt, int type, struct sudo_conv_callback *callback) +{ + struct sudo_conv_message msg; + struct sudo_conv_reply repl; + sigset_t mask, omask; + debug_decl(auth_getpass, SUDOERS_DEBUG_AUTH); + + /* Display lecture if needed and we haven't already done so. */ + display_lecture(callback); + + /* Mask user input if pwfeedback set and echo is off. */ + if (type == SUDO_CONV_PROMPT_ECHO_OFF && def_pwfeedback) + type = SUDO_CONV_PROMPT_MASK; + + /* If visiblepw set, do not error out if there is no tty. */ + if (def_visiblepw) + type |= SUDO_CONV_PROMPT_ECHO_OK; + + /* Unblock SIGINT and SIGQUIT during password entry. */ + /* XXX - do in tgetpass() itself instead? */ + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGQUIT); + (void) sigprocmask(SIG_UNBLOCK, &mask, &omask); + + /* Call conversation function. */ + memset(&msg, 0, sizeof(msg)); + msg.msg_type = type; + msg.timeout = (int)def_passwd_timeout.tv_sec; + msg.msg = prompt; + memset(&repl, 0, sizeof(repl)); + sudo_conv(1, &msg, &repl, callback); + /* XXX - check for ENOTTY? */ + + /* Restore previous signal mask. */ + (void) sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return_str_masked(repl.reply); +} + +void +dump_auth_methods(void) +{ + sudo_auth *auth; + debug_decl(dump_auth_methods, SUDOERS_DEBUG_AUTH); + + sudo_printf(SUDO_CONV_INFO_MSG, _("Authentication methods:")); + for (auth = auth_switch; auth->name; auth++) + sudo_printf(SUDO_CONV_INFO_MSG, " '%s'", auth->name); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + + debug_return; +} diff --git a/plugins/sudoers/auth/sudo_auth.h b/plugins/sudoers/auth/sudo_auth.h new file mode 100644 index 0000000..bcb248a --- /dev/null +++ b/plugins/sudoers/auth/sudo_auth.h @@ -0,0 +1,106 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007-2016, 2018 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SUDO_AUTH_H +#define SUDO_AUTH_H + +/* Private auth function return values (rowhammer resistant). */ +#define AUTH_INTR 0x69d61fc8 /* 1101001110101100001111111001000 */ +#define AUTH_NONINTERACTIVE 0x1629e037 /* 0010110001010011110000000110111 */ + +struct sudoers_context; +typedef struct sudo_auth { + unsigned int flags; /* various flags, see below */ + int status; /* status from verify routine */ + const char *name; /* name of the method as a string */ + void *data; /* method-specific data pointer */ + int (*init)(const struct sudoers_context *ctx, struct passwd *pw, struct sudo_auth *auth); + int (*setup)(const struct sudoers_context *ctx, struct passwd *pw, char **prompt, struct sudo_auth *auth); + int (*verify)(const struct sudoers_context *ctx, struct passwd *pw, const char *p, struct sudo_auth *auth, struct sudo_conv_callback *callback); + int (*approval)(const struct sudoers_context *ctx, struct passwd *pw, struct sudo_auth *auth, bool exempt); + int (*cleanup)(const struct sudoers_context *ctx, struct passwd *pw, struct sudo_auth *auth, bool force); + int (*begin_session)(const struct sudoers_context *ctx, struct passwd *pw, char **user_env[], struct sudo_auth *auth); + int (*end_session)(struct sudo_auth *auth); +} sudo_auth; + +/* Values for sudo_auth.flags. */ +#define FLAG_DISABLED 0x02U /* method disabled */ +#define FLAG_STANDALONE 0x04U /* standalone auth method */ +#define FLAG_ONEANDONLY 0x08U /* one and only auth method */ +#define FLAG_NONINTERACTIVE 0x10U /* 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); + +/* Pointer to conversation function to use with auth_getpass(). */ +extern sudo_conv_t sudo_conv; + +/* Prototypes for standalone methods */ +int bsdauth_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int bsdauth_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback); +int bsdauth_approval(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool exempt); +int bsdauth_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +void bsdauth_set_style(const char *style); +int sudo_aix_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_aix_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_aix_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +int sudo_fwtk_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_fwtk_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_fwtk_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +int sudo_pam_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_pam_init_quiet(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_pam_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_pam_approval(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool exempt); +int sudo_pam_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +int sudo_pam_begin_session(const struct sudoers_context *ctx, struct passwd *pw, char **user_env[], sudo_auth *auth); +int sudo_pam_end_session(sudo_auth *auth); +int sudo_securid_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_securid_setup(const struct sudoers_context *ctx, struct passwd *pw, char **prompt, sudo_auth *auth); +int sudo_securid_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_sia_setup(const struct sudoers_context *ctx, struct passwd *pw, char **prompt, sudo_auth *auth); +int sudo_sia_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_sia_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +int sudo_sia_begin_session(const struct sudoers_context *ctx, struct passwd *pw, char **user_env[], sudo_auth *auth); + +/* Prototypes for normal methods */ +int sudo_afs_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_dce_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_krb5_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_krb5_setup(const struct sudoers_context *ctx, struct passwd *pw, char **prompt, sudo_auth *auth); +int sudo_krb5_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_krb5_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +int sudo_passwd_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_passwd_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_passwd_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); +int sudo_rfc1938_setup(const struct sudoers_context *ctx, struct passwd *pw, char **prompt, sudo_auth *auth); +int sudo_rfc1938_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_secureware_init(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth); +int sudo_secureware_verify(const struct sudoers_context *ctx, struct passwd *pw, const char *pass, sudo_auth *auth, struct sudo_conv_callback *callback); +int sudo_secureware_cleanup(const struct sudoers_context *ctx, struct passwd *pw, sudo_auth *auth, bool force); + +/* Fields: name, flags, init, setup, verify, approval, cleanup, begin_sess, end_sess */ +#define AUTH_ENTRY(n, f, i, s, v, a, c, b, e) \ + { (f), AUTH_FAILURE, (n), NULL, (i), (s), (v), (a), (c) , (b), (e) }, + +#endif /* SUDO_AUTH_H */ |