diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:22:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:22:51 +0000 |
commit | 9ada0093e92388590c7368600ca4e9e3e376f0d0 (patch) | |
tree | a56fe41110023676d7082028cbaa47ca4b6e6164 /modules/pam_unix/pam_unix_passwd.c | |
parent | Initial commit. (diff) | |
download | pam-9ada0093e92388590c7368600ca4e9e3e376f0d0.tar.xz pam-9ada0093e92388590c7368600ca4e9e3e376f0d0.zip |
Adding upstream version 1.5.2.upstream/1.5.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/pam_unix/pam_unix_passwd.c')
-rw-r--r-- | modules/pam_unix/pam_unix_passwd.c | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c new file mode 100644 index 0000000..a20e919 --- /dev/null +++ b/modules/pam_unix/pam_unix_passwd.c @@ -0,0 +1,875 @@ +/* + * pam_unix password management + * + * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software. + * Copyright (C) 1996. + * Copyright (c) Jan Rękorajski, 1999. + * Copyright (c) Red Hat, Inc., 2007, 2008. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <malloc.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <pwd.h> +#include <syslog.h> +#include <shadow.h> +#include <time.h> /* for time() */ +#include <fcntl.h> +#include <ctype.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <signal.h> +#include <sys/wait.h> +#include <sys/resource.h> + +#include <security/_pam_macros.h> +#include <security/pam_modules.h> +#include <security/pam_ext.h> +#include <security/pam_modutil.h> + +#include "pam_cc_compat.h" +#include "md5.h" +#include "support.h" +#include "passverify.h" +#include "bigcrypt.h" + +#if (HAVE_YP_GET_DEFAULT_DOMAIN || HAVE_GETDOMAINNAME) && HAVE_YP_MASTER +# define HAVE_NIS +#endif + +#ifdef HAVE_NIS +# include <rpc/rpc.h> + +# if HAVE_RPCSVC_YP_PROT_H +# include <rpcsvc/yp_prot.h> +# endif + +# if HAVE_RPCSVC_YPCLNT_H +# include <rpcsvc/ypclnt.h> +# endif + +# include "yppasswd.h" + +# if !HAVE_DECL_GETRPCPORT &&!HAVE_RPCB_GETADDR +extern int getrpcport(const char *host, unsigned long prognum, + unsigned long versnum, unsigned int proto); +# endif /* GNU libc 2.1 */ +#endif + +/* + How it works: + Gets in username (has to be done) from the calling program + Does authentication of user (only if we are not running as root) + Gets new password/checks for sanity + Sets it. + */ + +#define MAX_PASSWD_TRIES 3 + +#ifdef HAVE_NIS +#ifdef HAVE_RPCB_GETADDR +static unsigned short +__taddr2port (const struct netconfig *nconf, const struct netbuf *nbuf) +{ + unsigned short port = 0; + struct __rpc_sockinfo si; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + if (!__rpc_nconf2sockinfo(nconf, &si)) + return 0; + + switch (si.si_af) + { + case AF_INET: + sin = nbuf->buf; + port = sin->sin_port; + break; + case AF_INET6: + sin6 = nbuf->buf; + port = sin6->sin6_port; + break; + default: + break; + } + + return htons (port); +} +#endif + +static char *getNISserver(pam_handle_t *pamh, unsigned long long ctrl) +{ + char *master; + char *domainname; + int port, err; +#if defined(HAVE_RPCB_GETADDR) + struct netconfig *nconf; + struct netbuf svcaddr; + char addrbuf[INET6_ADDRSTRLEN]; + void *handle; + int found; +#endif + + +#ifdef HAVE_YP_GET_DEFAULT_DOMAIN + if ((err = yp_get_default_domain(&domainname)) != 0) { + pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s", + yperr_string(err)); + return NULL; + } +#elif defined(HAVE_GETDOMAINNAME) + char domainname_res[256]; + + if (getdomainname (domainname_res, sizeof (domainname_res)) == 0) + { + if (strcmp (domainname_res, "(none)") == 0) + { + /* If domainname is not set, some systems will return "(none)" */ + domainname_res[0] = '\0'; + } + domainname = domainname_res; + } + else domainname = NULL; +#endif + + if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) { + pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s", + yperr_string(err)); + return NULL; + } +#ifdef HAVE_RPCB_GETADDR + svcaddr.len = 0; + svcaddr.maxlen = sizeof (addrbuf); + svcaddr.buf = addrbuf; + port = 0; + found = 0; + + handle = setnetconfig(); + while ((nconf = getnetconfig(handle)) != NULL) { + if (!strcmp(nconf->nc_proto, "udp")) { + if (rpcb_getaddr(YPPASSWDPROG, YPPASSWDPROC_UPDATE, + nconf, &svcaddr, master)) { + port = __taddr2port (nconf, &svcaddr); + endnetconfig (handle); + found=1; + break; + } + + if (rpc_createerr.cf_stat != RPC_UNKNOWNHOST) { + clnt_pcreateerror (master); + pam_syslog (pamh, LOG_ERR, + "rpcb_getaddr (%s) failed!", master); + return NULL; + } + } + } + + if (!found) { + pam_syslog (pamh, LOG_ERR, + "Cannot find suitable transport for protocol 'udp'"); + return NULL; + } +#else + port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP); +#endif + if (port == 0) { + pam_syslog(pamh, LOG_WARNING, + "yppasswdd not running on NIS master host"); + return NULL; + } + if (port >= IPPORT_RESERVED) { + pam_syslog(pamh, LOG_WARNING, + "yppasswd daemon running on illegal port"); + return NULL; + } + if (on(UNIX_DEBUG, ctrl)) { + pam_syslog(pamh, LOG_DEBUG, "Use NIS server on %s with port %d", + master, port); + } + return master; +} +#endif + +#ifdef WITH_SELINUX + +static int _unix_run_update_binary(pam_handle_t *pamh, unsigned long long ctrl, const char *user, + const char *fromwhat, const char *towhat, int remember) +{ + int retval, child, fds[2]; + struct sigaction newsa, oldsa; + + D(("called.")); + /* create a pipe for the password */ + if (pipe(fds) != 0) { + D(("could not make pipe")); + return PAM_AUTH_ERR; + } + + if (off(UNIX_NOREAP, ctrl)) { + /* + * This code arranges that the demise of the child does not cause + * the application to receive a signal it is not expecting - which + * may kill the application or worse. + * + * The "noreap" module argument is provided so that the admin can + * override this behavior. + */ + memset(&newsa, '\0', sizeof(newsa)); + newsa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &newsa, &oldsa); + } + + /* fork */ + child = fork(); + if (child == 0) { + static char *envp[] = { NULL }; + const char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL }; + char buffer[16]; + + /* XXX - should really tidy up PAM here too */ + + /* reopen stdin as pipe */ + if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO) { + pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin"); + _exit(PAM_AUTHINFO_UNAVAIL); + } + + if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD, + PAM_MODUTIL_PIPE_FD, + PAM_MODUTIL_PIPE_FD) < 0) { + _exit(PAM_AUTHINFO_UNAVAIL); + } + + /* exec binary helper */ + args[0] = UPDATE_HELPER; + args[1] = user; + args[2] = "update"; + if (on(UNIX_SHADOW, ctrl)) + args[3] = "1"; + else + args[3] = "0"; + + snprintf(buffer, sizeof(buffer), "%d", remember); + args[4] = buffer; + + DIAG_PUSH_IGNORE_CAST_QUAL; + execve(UPDATE_HELPER, (char *const *) args, envp); + DIAG_POP_IGNORE_CAST_QUAL; + + /* should not get here: exit with error */ + D(("helper binary is not available")); + _exit(PAM_AUTHINFO_UNAVAIL); + } else if (child > 0) { + /* wait for child */ + /* if the stored password is NULL */ + int rc=0; + if (fromwhat) { + int len = strlen(fromwhat); + + if (len > PAM_MAX_RESP_SIZE) + len = PAM_MAX_RESP_SIZE; + pam_modutil_write(fds[1], fromwhat, len); + } + pam_modutil_write(fds[1], "", 1); + if (towhat) { + int len = strlen(towhat); + + if (len > PAM_MAX_RESP_SIZE) + len = PAM_MAX_RESP_SIZE; + pam_modutil_write(fds[1], towhat, len); + } + pam_modutil_write(fds[1], "", 1); + + close(fds[0]); /* close here to avoid possible SIGPIPE above */ + close(fds[1]); + /* wait for helper to complete: */ + while ((rc=waitpid(child, &retval, 0)) < 0 && errno == EINTR); + if (rc<0) { + pam_syslog(pamh, LOG_ERR, "unix_update waitpid failed: %m"); + retval = PAM_AUTHTOK_ERR; + } else if (!WIFEXITED(retval)) { + pam_syslog(pamh, LOG_ERR, "unix_update abnormal exit: %d", retval); + retval = PAM_AUTHTOK_ERR; + } else { + retval = WEXITSTATUS(retval); + } + } else { + D(("fork failed")); + close(fds[0]); + close(fds[1]); + retval = PAM_AUTH_ERR; + } + + if (off(UNIX_NOREAP, ctrl)) { + sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */ + } + + return retval; +} +#endif + +static int check_old_password(const char *forwho, const char *newpass) +{ + static char buf[16384]; + char *s_pas; + int retval = PAM_SUCCESS; + FILE *opwfile; + size_t len = strlen(forwho); + + opwfile = fopen(OLD_PASSWORDS_FILE, "r"); + if (opwfile == NULL) + return PAM_ABORT; + + while (fgets(buf, 16380, opwfile)) { + if (!strncmp(buf, forwho, len) && (buf[len] == ':' || + buf[len] == ',')) { + char *sptr; + buf[strlen(buf) - 1] = '\0'; + /* s_luser = */ strtok_r(buf, ":,", &sptr); + /* s_uid = */ strtok_r(NULL, ":,", &sptr); + /* s_npas = */ strtok_r(NULL, ":,", &sptr); + s_pas = strtok_r(NULL, ":,", &sptr); + while (s_pas != NULL) { + char *md5pass = Goodcrypt_md5(newpass, s_pas); + if (md5pass == NULL || !strcmp(md5pass, s_pas)) { + _pam_delete(md5pass); + retval = PAM_AUTHTOK_ERR; + break; + } + s_pas = strtok_r(NULL, ":,", &sptr); + _pam_delete(md5pass); + } + break; + } + } + fclose(opwfile); + + return retval; +} + +static int _do_setpass(pam_handle_t* pamh, const char *forwho, + const char *fromwhat, + char *towhat, unsigned long long ctrl, int remember) +{ + struct passwd *pwd = NULL; + int retval = 0; + int unlocked = 0; + + D(("called")); + + pwd = getpwnam(forwho); + + if (pwd == NULL) { + retval = PAM_AUTHTOK_ERR; + goto done; + } + + if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) { +#ifdef HAVE_NIS + char *master; + + if ((master=getNISserver(pamh, ctrl)) != NULL) { + struct timeval timeout; + struct yppasswd yppwd; + CLIENT *clnt; + int status; + enum clnt_stat err; + + /* Unlock passwd file to avoid deadlock */ + unlock_pwdf(); + unlocked = 1; + + /* Initialize password information */ + yppwd.newpw.pw_passwd = pwd->pw_passwd; + yppwd.newpw.pw_name = pwd->pw_name; + yppwd.newpw.pw_uid = pwd->pw_uid; + yppwd.newpw.pw_gid = pwd->pw_gid; + yppwd.newpw.pw_gecos = pwd->pw_gecos; + yppwd.newpw.pw_dir = pwd->pw_dir; + yppwd.newpw.pw_shell = pwd->pw_shell; + yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup (""); + yppwd.newpw.pw_passwd = towhat; + + D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho)); + + /* The yppasswd.x file said `unix authentication required', + * so I added it. This is the only reason it is in here. + * My yppasswdd doesn't use it, but maybe some others out there + * do. --okir + */ + clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp"); + clnt->cl_auth = authunix_create_default(); + memset((char *) &status, '\0', sizeof(status)); + timeout.tv_sec = 25; + timeout.tv_usec = 0; + err = clnt_call(clnt, YPPASSWDPROC_UPDATE, + (xdrproc_t) xdr_yppasswd, (char *) &yppwd, + (xdrproc_t) xdr_int, (char *) &status, + timeout); + + free (yppwd.oldpass); + + if (err) { + _make_remark(pamh, ctrl, PAM_TEXT_INFO, + clnt_sperrno(err)); + } else if (status) { + D(("Error while changing NIS password.\n")); + } + D(("The password has%s been changed on %s.", + (err || status) ? " not" : "", master)); + pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s", + (err || status) ? " not" : "", pwd->pw_name, master); + + auth_destroy(clnt->cl_auth); + clnt_destroy(clnt); + if (err || status) { + _make_remark(pamh, ctrl, PAM_TEXT_INFO, + _("NIS password could not be changed.")); + retval = PAM_TRY_AGAIN; + } +#ifdef PAM_DEBUG + sleep(5); +#endif + } else { + retval = PAM_TRY_AGAIN; + } +#else + if (on(UNIX_DEBUG, ctrl)) { + pam_syslog(pamh, LOG_DEBUG, "No NIS support available"); + } + + retval = PAM_TRY_AGAIN; +#endif + } + + if (_unix_comesfromsource(pamh, forwho, 1, 0)) { + if(unlocked) { + if (lock_pwdf() != PAM_SUCCESS) { + return PAM_AUTHTOK_LOCK_BUSY; + } + } +#ifdef WITH_SELINUX + if (unix_selinux_confined()) + return _unix_run_update_binary(pamh, ctrl, forwho, fromwhat, towhat, remember); +#endif + /* first, save old password */ + if (save_old_password(pamh, forwho, fromwhat, remember)) { + retval = PAM_AUTHTOK_ERR; + goto done; + } + if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) { + retval = unix_update_shadow(pamh, forwho, towhat); + if (retval == PAM_SUCCESS) + if (!is_pwd_shadowed(pwd)) + retval = unix_update_passwd(pamh, forwho, "x"); + } else { + retval = unix_update_passwd(pamh, forwho, towhat); + } + } + + +done: + unlock_pwdf(); + + return retval; +} + +static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned long long ctrl) +{ + struct passwd *pwent = NULL; /* Password and shadow password */ + struct spwd *spent = NULL; /* file entries for the user */ + int daysleft; + int retval; + + retval = get_account_info(pamh, user, &pwent, &spent); + if (retval == PAM_USER_UNKNOWN) { + return retval; + } + + if (retval == PAM_SUCCESS && spent == NULL) + return PAM_SUCCESS; + + if (retval == PAM_UNIX_RUN_HELPER) { + retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft); + if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN) + return retval; + } + else if (retval == PAM_SUCCESS) + retval = check_shadow_expiry(pamh, spent, &daysleft); + + if (on(UNIX__IAMROOT, ctrl) || retval == PAM_NEW_AUTHTOK_REQD) + return PAM_SUCCESS; + + return retval; +} + +static int _pam_unix_approve_pass(pam_handle_t * pamh + ,unsigned long long ctrl + ,const char *pass_old + ,const char *pass_new, + int pass_min_len) +{ + const void *user; + const char *remark = NULL; + int retval = PAM_SUCCESS; + + D(("&new=%p, &old=%p", pass_old, pass_new)); + D(("new=[%s]", pass_new)); + D(("old=[%s]", pass_old)); + + if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) { + if (on(UNIX_DEBUG, ctrl)) { + pam_syslog(pamh, LOG_DEBUG, "bad authentication token"); + } + _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ? + _("No password has been supplied.") : + _("The password has not been changed.")); + return PAM_AUTHTOK_ERR; + } + /* + * if one wanted to hardwire authentication token strength + * checking this would be the place - AGM + */ + + retval = pam_get_item(pamh, PAM_USER, &user); + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) { + pam_syslog(pamh, LOG_ERR, "Can not get username"); + return PAM_AUTHTOK_ERR; + } + } + + if (strlen(pass_new) > PAM_MAX_RESP_SIZE) { + remark = _("You must choose a shorter password."); + D(("length exceeded [%s]", remark)); + } else if (off(UNIX__IAMROOT, ctrl)) { + if ((int)strlen(pass_new) < pass_min_len) + remark = _("You must choose a longer password."); + D(("length check [%s]", remark)); + if (on(UNIX_REMEMBER_PASSWD, ctrl)) { + if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR) + remark = _("Password has been already used. Choose another."); + if (retval == PAM_ABORT) { + pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords", + OLD_PASSWORDS_FILE); + return retval; + } + } + } + if (remark) { + _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark); + retval = PAM_AUTHTOK_ERR; + } + return retval; +} + +int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + unsigned long long ctrl, lctrl; + int retval; + int remember = -1; + int rounds = 0; + int pass_min_len = 0; + + /* <DO NOT free() THESE> */ + const char *user; + const void *item; + const char *pass_old, *pass_new; + /* </DO NOT free() THESE> */ + + D(("called.")); + + ctrl = _set_ctrl(pamh, flags, &remember, &rounds, &pass_min_len, + argc, argv); + + /* + * First get the name of a user + */ + retval = pam_get_user(pamh, &user, NULL); + if (retval == PAM_SUCCESS) { + /* + * Various libraries at various times have had bugs related to + * '+' or '-' as the first character of a user name. Don't + * allow them. + */ + if (user[0] == '-' || user[0] == '+') { + pam_syslog(pamh, LOG_NOTICE, "bad username [%s]", user); + return PAM_USER_UNKNOWN; + } + if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) + pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained", + user); + } else { + if (on(UNIX_DEBUG, ctrl)) + pam_syslog(pamh, LOG_DEBUG, + "password - could not identify user"); + return retval; + } + + D(("Got username of %s", user)); + + /* + * Before we do anything else, check to make sure that the user's + * info is in one of the databases we can modify from this module, + * which currently is 'files' and 'nis'. We have to do this because + * getpwnam() doesn't tell you *where* the information it gives you + * came from, nor should it. That's our job. + */ + if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) { + pam_syslog(pamh, LOG_DEBUG, + "user \"%s\" does not exist in /etc/passwd%s", + user, on(UNIX_NIS, ctrl) ? " or NIS" : ""); + return PAM_USER_UNKNOWN; + } else { + struct passwd *pwd; + _unix_getpwnam(pamh, user, 1, 1, &pwd); + if (pwd == NULL) { + pam_syslog(pamh, LOG_DEBUG, + "user \"%s\" has corrupted passwd entry", + user); + return PAM_USER_UNKNOWN; + } + } + + /* + * This is not an AUTH module! + */ + if (on(UNIX__NONULL, ctrl)) + set(UNIX__NULLOK, ctrl); + + if (on(UNIX__PRELIM, ctrl)) { + /* + * obtain and verify the current password (OLDAUTHTOK) for + * the user. + */ + D(("prelim check")); + + if (_unix_blankpasswd(pamh, ctrl, user)) { + return PAM_SUCCESS; + } else if (off(UNIX__IAMROOT, ctrl) || + (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, user, 0, 1))) { + /* instruct user what is happening */ + if (off(UNIX__QUIET, ctrl)) { + retval = pam_info(pamh, _("Changing password for %s."), user); + if (retval != PAM_SUCCESS) + return retval; + } + retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass_old, NULL); + + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, + "password - (old) token not obtained"); + return retval; + } + /* verify that this is the password for this user */ + + retval = _unix_verify_password(pamh, user, pass_old, ctrl); + } else { + D(("process run by root so do nothing this time around")); + pass_old = NULL; + retval = PAM_SUCCESS; /* root doesn't have too */ + } + + if (retval != PAM_SUCCESS) { + D(("Authentication failed")); + pass_old = NULL; + return retval; + } + pass_old = NULL; + retval = _unix_verify_shadow(pamh,user, ctrl); + if (retval == PAM_AUTHTOK_ERR) { + if (off(UNIX__IAMROOT, ctrl)) + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + _("You must wait longer to change your password.")); + else + retval = PAM_SUCCESS; + } + } else if (on(UNIX__UPDATE, ctrl)) { + /* + * tpass is used below to store the _pam_md() return; it + * should be _pam_delete()'d. + */ + + char *tpass = NULL; + int retry = 0; + + /* + * obtain the proposed password + */ + + D(("do update")); + + /* + * get the old token back. NULL was ok only if root [at this + * point we assume that this has already been enforced on a + * previous call to this function]. + */ + + retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &item); + + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, "user not authenticated"); + return retval; + } + pass_old = item; + D(("pass_old [%s]", pass_old)); + + D(("get new password now")); + + lctrl = ctrl; + + if (on(UNIX_USE_AUTHTOK, lctrl)) { + set(UNIX_USE_FIRST_PASS, lctrl); + } + if (on(UNIX_USE_FIRST_PASS, lctrl)) { + retry = MAX_PASSWD_TRIES-1; + } + retval = PAM_AUTHTOK_ERR; + while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { + /* + * use_authtok is to force the use of a previously entered + * password -- needed for pluggable password strength checking + */ + + retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass_new, NULL); + + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) { + pam_syslog(pamh, LOG_ERR, + "password - new password not obtained"); + } + pass_old = NULL; /* tidy up */ + return retval; + } + D(("returned to _unix_chauthtok")); + + /* + * At this point we know who the user is and what they + * propose as their new password. Verify that the new + * password is acceptable. + */ + + if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */ + pass_new = NULL; + } + retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, + pass_new, pass_min_len); + + if (retval != PAM_SUCCESS) { + pam_set_item(pamh, PAM_AUTHTOK, NULL); + } + } + + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, + "new password not acceptable"); + pass_new = pass_old = NULL; /* tidy up */ + return retval; + } + if (lock_pwdf() != PAM_SUCCESS) { + return PAM_AUTHTOK_LOCK_BUSY; + } + + if (pass_old) { + retval = _unix_verify_password(pamh, user, pass_old, ctrl); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, "user password changed by another process"); + unlock_pwdf(); + return retval; + } + } + + retval = _unix_verify_shadow(pamh, user, ctrl); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, "user shadow entry expired"); + unlock_pwdf(); + return retval; + } + + retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new, + pass_min_len); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_NOTICE, + "new password not acceptable 2"); + pass_new = pass_old = NULL; /* tidy up */ + unlock_pwdf(); + return retval; + } + + /* + * By reaching here we have approved the passwords and must now + * rebuild the password database file. + */ + + /* + * First we encrypt the new password. + */ + + tpass = create_password_hash(pamh, pass_new, ctrl, rounds); + if (tpass == NULL) { + pam_syslog(pamh, LOG_CRIT, + "crypt() failure or out of memory for password"); + pass_new = pass_old = NULL; /* tidy up */ + unlock_pwdf(); + return PAM_BUF_ERR; + } + + D(("password processed")); + + /* update the password database(s) -- race conditions..? */ + + retval = _do_setpass(pamh, user, pass_old, tpass, ctrl, + remember); + /* _do_setpass has called unlock_pwdf for us */ + + _pam_delete(tpass); + pass_old = pass_new = NULL; + } else { /* something has broken with the module */ + pam_syslog(pamh, LOG_CRIT, + "password received unknown request"); + retval = PAM_ABORT; + } + + D(("retval was %d", retval)); + + return retval; +} |