]> granicus.if.org Git - linux-pam/blobdiff - modules/pam_unix/pam_unix_passwd.c
Relevant BUGIDs: Debian bugs #95220, #175900
[linux-pam] / modules / pam_unix / pam_unix_passwd.c
index 7870f320e6d2d4f864654c4d447d2627e7fb5a49..c8ee54924fbbaa8aa278a9297eaf7240ed9bfcf8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software. 
+ * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
  * Copyright (C) 1996.
  * Copyright (c) Jan Rêkorajski, 1999.
  *
@@ -35,7 +35,7 @@
  * OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <security/_pam_aconf.h>
+#include "config.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <rpcsvc/yp_prot.h>
 #include <rpcsvc/ypclnt.h>
 
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+#ifdef WITH_SELINUX
+static int selinux_enabled=-1;
+#include <selinux/selinux.h>
+static security_context_t prev_context=NULL;
+#define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
+#endif
+
 #ifdef USE_CRACKLIB
 #include <crack.h>
 #endif
 #define PAM_SM_PASSWORD
 
 #include <security/pam_modules.h>
-
-#ifndef LINUX_PAM
-#include <security/pam_appl.h>
-#endif                         /* LINUX_PAM */
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
 
 #include "yppasswd.h"
 #include "md5.h"
 #include "support.h"
+#include "bigcrypt.h"
 
 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
 extern int getrpcport(const char *host, unsigned long prognum,
@@ -87,12 +96,10 @@ extern int getrpcport(const char *host, unsigned long prognum,
  * password changing module.
  */
 
-#ifdef NEED_LCKPWDF
-#include "./lckpwdf.-c"
+#if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
+# include "./lckpwdf.-c"
 #endif
 
-extern char *bigcrypt(const char *key, const char *salt);
-
 /*
    How it works:
    Gets in username (has to be done) from the calling program
@@ -114,7 +121,9 @@ extern char *bigcrypt(const char *key, const char *salt);
 #define MAX_PASSWD_TRIES       3
 #define PW_TMPFILE             "/etc/npasswd"
 #define SH_TMPFILE             "/etc/nshadow"
-#define CRACKLIB_DICTS         "/usr/share/dict/cracklib_dict"
+#ifndef CRACKLIB_DICTS
+#define CRACKLIB_DICTS         NULL
+#endif
 #define OPW_TMPFILE            "/etc/security/nopasswd"
 #define OLD_PASSWORDS_FILE     "/etc/security/opasswd"
 
@@ -183,29 +192,131 @@ static char *getNISserver(pam_handle_t *pamh)
        int port, err;
 
        if ((err = yp_get_default_domain(&domainname)) != 0) {
-               _log_err(LOG_WARNING, pamh, "can't get local yp domain: %s\n",
+               pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
                         yperr_string(err));
                return NULL;
        }
        if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
-               _log_err(LOG_WARNING, pamh, "can't find the master ypserver: %s\n",
+               pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
                         yperr_string(err));
                return NULL;
        }
        port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
        if (port == 0) {
-               _log_err(LOG_WARNING, pamh,
-                        "yppasswdd not running on NIS master host\n");
+               pam_syslog(pamh, LOG_WARNING,
+                        "yppasswdd not running on NIS master host");
                return NULL;
        }
        if (port >= IPPORT_RESERVED) {
-               _log_err(LOG_WARNING, pamh,
-                        "yppasswd daemon running on illegal port.\n");
+               pam_syslog(pamh, LOG_WARNING,
+                        "yppasswd daemon running on illegal port");
                return NULL;
        }
        return master;
 }
 
+#ifdef WITH_SELINUX
+
+static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
+{
+    int retval, child, fds[2];
+    void (*sighandler)(int) = NULL;
+
+    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.
+        */
+       sighandler = signal(SIGCHLD, SIG_DFL);
+    }
+
+    /* fork */
+    child = fork();
+    if (child == 0) {
+        size_t i=0;
+        struct rlimit rlim;
+       static char *envp[] = { NULL };
+       char *args[] = { NULL, NULL, NULL, NULL };
+
+       /* XXX - should really tidy up PAM here too */
+
+       close(0); close(1);
+       /* reopen stdin as pipe */
+       close(fds[1]);
+       dup2(fds[0], STDIN_FILENO);
+
+       if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
+         for (i=2; i < rlim.rlim_max; i++) {
+           if ((unsigned int)fds[0] != i)
+                  close(i);
+         }
+       }
+
+        if (SELINUX_ENABLED && geteuid() == 0) {
+          /* must set the real uid to 0 so the helper will not error
+             out if pam is called from setuid binary (su, sudo...) */
+          setuid(0);
+        }
+
+       /* exec binary helper */
+       args[0] = x_strdup(CHKPWD_HELPER);
+       args[1] = x_strdup(user);
+       args[2] = x_strdup("shadow");
+
+       execve(CHKPWD_HELPER, args, envp);
+
+       /* 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)
+         pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
+       else
+         pam_modutil_write(fds[1], "", 1);
+       if (towhat) {
+         pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
+       }
+       else
+         pam_modutil_write(fds[1], "", 1);
+
+       close(fds[0]);       /* close here to avoid possible SIGPIPE above */
+       close(fds[1]);
+       rc=waitpid(child, &retval, 0);  /* wait for helper to complete */
+       if (rc<0) {
+         pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
+         retval = PAM_AUTH_ERR;
+       } else {
+         retval = WEXITSTATUS(retval);
+       }
+    } else {
+       D(("fork failed"));
+       close(fds[0]);
+       close(fds[1]);
+       retval = PAM_AUTH_ERR;
+    }
+
+    if (sighandler != SIG_ERR) {
+        (void) signal(SIGCHLD, sighandler);   /* restore old signal handler */
+    }
+
+    return retval;
+}
+#endif
+
 static int check_old_password(const char *forwho, const char *newpass)
 {
        static char buf[16384];
@@ -215,15 +326,16 @@ static int check_old_password(const char *forwho, const char *newpass)
 
        opwfile = fopen(OLD_PASSWORDS_FILE, "r");
        if (opwfile == NULL)
-               return PAM_AUTHTOK_ERR;
+               return PAM_ABORT;
 
        while (fgets(buf, 16380, opwfile)) {
                if (!strncmp(buf, forwho, strlen(forwho))) {
+                       char *sptr;
                        buf[strlen(buf) - 1] = '\0';
-                       s_luser = strtok(buf, ":,");
-                       s_uid = strtok(NULL, ":,");
-                       s_npas = strtok(NULL, ":,");
-                       s_pas = strtok(NULL, ":,");
+                       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 (!strcmp(md5pass, s_pas)) {
@@ -231,7 +343,7 @@ static int check_old_password(const char *forwho, const char *newpass)
                                        retval = PAM_AUTHTOK_ERR;
                                        break;
                                }
-                               s_pas = strtok(NULL, ":,");
+                               s_pas = strtok_r(NULL, ":,", &sptr);
                                _pam_delete(md5pass);
                        }
                        break;
@@ -242,229 +354,424 @@ static int check_old_password(const char *forwho, const char *newpass)
        return retval;
 }
 
-static int save_old_password(const char *forwho, const char *oldpass, int howmany)
+static int save_old_password(pam_handle_t *pamh,
+                            const char *forwho, const char *oldpass,
+                            int howmany)
 {
-       static char buf[16384];
-       static char nbuf[16384];
-       char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
-       int retval = 0, npas;
-       FILE *pwfile, *opwfile;
-       int err = 0;
-       int oldmask;
-       int found = 0;
-       struct passwd *pwd = NULL;
-
-       if (howmany < 0)
-               return retval;
-
-       if (oldpass == NULL)
-               return retval;
-
-       oldmask = umask(077);
-       pwfile = fopen(OPW_TMPFILE, "w");
-       umask(oldmask);
-       opwfile = fopen(OLD_PASSWORDS_FILE, "r");
-       if (pwfile == NULL || opwfile == NULL)
-               return PAM_AUTHTOK_ERR;
-       chown(OPW_TMPFILE, 0, 0);
-       chmod(OPW_TMPFILE, 0600);
+    static char buf[16384];
+    static char nbuf[16384];
+    char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
+    int npas;
+    FILE *pwfile, *opwfile;
+    int err = 0;
+    int oldmask;
+    int found = 0;
+    struct passwd *pwd = NULL;
+    struct stat st;
+
+    if (howmany < 0) {
+       return PAM_SUCCESS;
+    }
+
+    if (oldpass == NULL) {
+       return PAM_SUCCESS;
+    }
+
+    oldmask = umask(077);
+
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      security_context_t passwd_context=NULL;
+      if (getfilecon("/etc/passwd",&passwd_context)<0) {
+        return PAM_AUTHTOK_ERR;
+      };
+      if (getfscreatecon(&prev_context)<0) {
+        freecon(passwd_context);
+        return PAM_AUTHTOK_ERR;
+      }
+      if (setfscreatecon(passwd_context)) {
+        freecon(passwd_context);
+        freecon(prev_context);
+        return PAM_AUTHTOK_ERR;
+      }
+      freecon(passwd_context);
+    }
+#endif
+    pwfile = fopen(OPW_TMPFILE, "w");
+    umask(oldmask);
+    if (pwfile == NULL) {
+      err = 1;
+      goto done;
+    }
+
+    opwfile = fopen(OLD_PASSWORDS_FILE, "r");
+    if (opwfile == NULL) {
+       fclose(pwfile);
+      err = 1;
+      goto done;
+    }
+
+    if (fstat(fileno(opwfile), &st) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
 
-       while (fgets(buf, 16380, opwfile)) {
-               if (!strncmp(buf, forwho, strlen(forwho))) {
-                       buf[strlen(buf) - 1] = '\0';
-                       s_luser = strtok(buf, ":");
-                       s_uid = strtok(NULL, ":");
-                       s_npas = strtok(NULL, ":");
-                       s_pas = strtok(NULL, ":");
-                       npas = strtol(s_npas, NULL, 10) + 1;
-                       while (npas > howmany) {
-                               s_pas = strpbrk(s_pas, ",");
-                               if (s_pas != NULL)
-                                       s_pas++;
-                               npas--;
-                       }
-                       pass = crypt_md5_wrapper(oldpass);
-                       if (s_pas == NULL)
-                               snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
-                                        s_luser, s_uid, npas, pass);
-                       else
-                               snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
-                                        s_luser, s_uid, npas, s_pas, pass);
-                       _pam_delete(pass);
-                       if (fputs(nbuf, pwfile) < 0) {
-                               retval = PAM_AUTHTOK_ERR;
-                               err = 1;
-                               break;
-                       }
-                       found = 1;
-               } else if (fputs(buf, pwfile) < 0) {
-                       retval = PAM_AUTHTOK_ERR;
-                       err = 1;
-                       break;
-               }
-       }
+    if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
        fclose(opwfile);
-       if (!found) {
-               pwd = getpwnam(forwho);
-               if (pwd == NULL) {
-                       retval = PAM_AUTHTOK_ERR;
-                       err = 1;
-               } else {
-                       pass = crypt_md5_wrapper(oldpass);
-                       snprintf(nbuf, sizeof(nbuf), "%s:%d:1:%s\n",
-                                forwho, pwd->pw_uid, pass);
-                       _pam_delete(pass);
-                       if (fputs(nbuf, pwfile) < 0) {
-                               retval = PAM_AUTHTOK_ERR;
-                               err = 1;
-                       }
-               }
-       }
-       if (fclose(pwfile)) {
-               fprintf(stderr, "error writing entries to old passwords file: %s\n",
-                       strerror(errno));
-               retval = PAM_AUTHTOK_ERR;
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+    if (fchmod(fileno(pwfile), st.st_mode) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    while (fgets(buf, 16380, opwfile)) {
+       if (!strncmp(buf, forwho, strlen(forwho))) {
+           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);
+           npas = strtol(s_npas, NULL, 10) + 1;
+           while (npas > howmany) {
+               s_pas = strpbrk(s_pas, ",");
+               if (s_pas != NULL)
+                   s_pas++;
+               npas--;
+           }
+           pass = crypt_md5_wrapper(oldpass);
+           if (s_pas == NULL)
+               snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
+                        s_luser, s_uid, npas, pass);
+           else
+               snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
+                        s_luser, s_uid, npas, s_pas, pass);
+           _pam_delete(pass);
+           if (fputs(nbuf, pwfile) < 0) {
                err = 1;
+               break;
+           }
+           found = 1;
+       } else if (fputs(buf, pwfile) < 0) {
+           err = 1;
+           break;
        }
-       if (!err)
-               rename(OPW_TMPFILE, OLD_PASSWORDS_FILE);
-       else
-               unlink(OPW_TMPFILE);
+    }
+    fclose(opwfile);
 
-       return retval;
+    if (!found) {
+       pwd = pam_modutil_getpwnam(pamh, forwho);
+       if (pwd == NULL) {
+           err = 1;
+       } else {
+           pass = crypt_md5_wrapper(oldpass);
+           snprintf(nbuf, sizeof(nbuf), "%s:%lu:1:%s\n",
+                    forwho, (unsigned long)pwd->pw_uid, pass);
+           _pam_delete(pass);
+           if (fputs(nbuf, pwfile) < 0) {
+               err = 1;
+           }
+       }
+    }
+
+    if (fclose(pwfile)) {
+       D(("error writing entries to old passwords file: %m"));
+       err = 1;
+    }
+
+done:
+    if (!err) {
+       if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
+           err = 1;
+    }
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      if (setfscreatecon(prev_context)) {
+        err = 1;
+      }
+      if (prev_context)
+        freecon(prev_context);
+      prev_context=NULL;
+    }
+#endif
+    if (!err) {
+       return PAM_SUCCESS;
+    } else {
+       unlink(OPW_TMPFILE);
+       return PAM_AUTHTOK_ERR;
+    }
 }
 
-static int _update_passwd(const char *forwho, const char *towhat)
+static int _update_passwd(pam_handle_t *pamh,
+                         const char *forwho, const char *towhat)
 {
-       struct passwd *tmpent = NULL;
-       FILE *pwfile, *opwfile;
-       int retval = 0;
-       int err = 0;
-       int oldmask;
-
-       oldmask = umask(077);
-       pwfile = fopen(PW_TMPFILE, "w");
-       umask(oldmask);
-       opwfile = fopen("/etc/passwd", "r");
-       if (pwfile == NULL || opwfile == NULL)
-               return PAM_AUTHTOK_ERR;
-       chown(PW_TMPFILE, 0, 0);
-       chmod(PW_TMPFILE, 0644);
-       tmpent = fgetpwent(opwfile);
-       while (tmpent) {
-               if (!strcmp(tmpent->pw_name, forwho)) {
-                       /* To shut gcc up */
-                       union {
-                               const char *const_charp;
-                               char *charp;
-                       } assigned_passwd;
-                       assigned_passwd.const_charp = towhat;
-                       
-                       tmpent->pw_passwd = assigned_passwd.charp;
-               }
-               if (putpwent(tmpent, pwfile)) {
-                       fprintf(stderr, "error writing entry to password file: %s\n",
-                               strerror(errno));
-                       err = 1;
-                       retval = PAM_AUTHTOK_ERR;
-                       break;
-               }
-               tmpent = fgetpwent(opwfile);
-       }
+    struct passwd *tmpent = NULL;
+    struct stat st;
+    FILE *pwfile, *opwfile;
+    int err = 1;
+    int oldmask;
+
+    oldmask = umask(077);
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      security_context_t passwd_context=NULL;
+      if (getfilecon("/etc/passwd",&passwd_context)<0) {
+       return PAM_AUTHTOK_ERR;
+      };
+      if (getfscreatecon(&prev_context)<0) {
+       freecon(passwd_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      if (setfscreatecon(passwd_context)) {
+       freecon(passwd_context);
+       freecon(prev_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      freecon(passwd_context);
+    }
+#endif
+    pwfile = fopen(PW_TMPFILE, "w");
+    umask(oldmask);
+    if (pwfile == NULL) {
+      err = 1;
+      goto done;
+    }
+
+    opwfile = fopen("/etc/passwd", "r");
+    if (opwfile == NULL) {
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    if (fstat(fileno(opwfile), &st) == -1) {
        fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
 
-       if (fclose(pwfile)) {
-               fprintf(stderr, "error writing entries to password file: %s\n",
-                       strerror(errno));
-               retval = PAM_AUTHTOK_ERR;
-               err = 1;
+    if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+    if (fchmod(fileno(pwfile), st.st_mode) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    tmpent = fgetpwent(opwfile);
+    while (tmpent) {
+       if (!strcmp(tmpent->pw_name, forwho)) {
+           /* To shut gcc up */
+           union {
+               const char *const_charp;
+               char *charp;
+           } assigned_passwd;
+           assigned_passwd.const_charp = towhat;
+
+           tmpent->pw_passwd = assigned_passwd.charp;
+           err = 0;
+       }
+       if (putpwent(tmpent, pwfile)) {
+           D(("error writing entry to password file: %m"));
+           err = 1;
+           break;
        }
-       if (!err)
-               rename(PW_TMPFILE, "/etc/passwd");
+       tmpent = fgetpwent(opwfile);
+    }
+    fclose(opwfile);
+
+    if (fclose(pwfile)) {
+       D(("error writing entries to password file: %m"));
+       err = 1;
+    }
+
+done:
+    if (!err) {
+       if (!rename(PW_TMPFILE, "/etc/passwd"))
+           pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
        else
-               unlink(PW_TMPFILE);
-
-       return retval;
+           err = 1;
+    }
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      if (setfscreatecon(prev_context)) {
+       err = 1;
+      }
+      if (prev_context)
+       freecon(prev_context);
+      prev_context=NULL;
+    }
+#endif
+    if (!err) {
+       return PAM_SUCCESS;
+    } else {
+       unlink(PW_TMPFILE);
+       return PAM_AUTHTOK_ERR;
+    }
 }
 
-static int _update_shadow(const char *forwho, char *towhat)
+static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
 {
-       struct spwd *spwdent = NULL, *stmpent = NULL;
-       FILE *pwfile, *opwfile;
-       int retval = 0;
-       int err = 0;
-       int oldmask;
-
-       spwdent = getspnam(forwho);
-       if (spwdent == NULL)
-               return PAM_USER_UNKNOWN;
-       oldmask = umask(077);
-       pwfile = fopen(SH_TMPFILE, "w");
-       umask(oldmask);
-       opwfile = fopen("/etc/shadow", "r");
-       if (pwfile == NULL || opwfile == NULL)
-               return PAM_AUTHTOK_ERR;
-       chown(SH_TMPFILE, 0, 0);
-       chmod(SH_TMPFILE, 0600);
-       stmpent = fgetspent(opwfile);
-       while (stmpent) {
-               if (!strcmp(stmpent->sp_namp, forwho)) {
-                       stmpent->sp_pwdp = towhat;
-                       stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
+    struct spwd *spwdent = NULL, *stmpent = NULL;
+    struct stat st;
+    FILE *pwfile, *opwfile;
+    int err = 1;
+    int oldmask;
+
+    spwdent = getspnam(forwho);
+    if (spwdent == NULL) {
+       return PAM_USER_UNKNOWN;
+    }
+    oldmask = umask(077);
+
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      security_context_t shadow_context=NULL;
+      if (getfilecon("/etc/shadow",&shadow_context)<0) {
+       return PAM_AUTHTOK_ERR;
+      };
+      if (getfscreatecon(&prev_context)<0) {
+       freecon(shadow_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      if (setfscreatecon(shadow_context)) {
+       freecon(shadow_context);
+       freecon(prev_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      freecon(shadow_context);
+    }
+#endif
+    pwfile = fopen(SH_TMPFILE, "w");
+    umask(oldmask);
+    if (pwfile == NULL) {
+       err = 1;
+       goto done;
+    }
+
+    opwfile = fopen("/etc/shadow", "r");
+    if (opwfile == NULL) {
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    if (fstat(fileno(opwfile), &st) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
 
-                       D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
-               }
-               if (putspent(stmpent, pwfile)) {
-                       fprintf(stderr, "error writing entry to shadow file: %s\n",
-                               strerror(errno));
-                       err = 1;
-                       retval = PAM_AUTHTOK_ERR;
-                       break;
-               }
-               stmpent = fgetspent(opwfile);
-       }
+    if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+    if (fchmod(fileno(pwfile), st.st_mode) == -1) {
        fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    stmpent = fgetspent(opwfile);
+    while (stmpent) {
+
+       if (!strcmp(stmpent->sp_namp, forwho)) {
+           stmpent->sp_pwdp = towhat;
+           stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
+           err = 0;
+           D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
+       }
 
-       if (fclose(pwfile)) {
-               fprintf(stderr, "error writing entries to shadow file: %s\n",
-                       strerror(errno));
-               retval = PAM_AUTHTOK_ERR;
-               err = 1;
+       if (putspent(stmpent, pwfile)) {
+           D(("error writing entry to shadow file: %m"));
+           err = 1;
+           break;
        }
-       if (!err)
-               rename(SH_TMPFILE, "/etc/shadow");
+
+       stmpent = fgetspent(opwfile);
+    }
+    fclose(opwfile);
+
+    if (fclose(pwfile)) {
+       D(("error writing entries to shadow file: %m"));
+       err = 1;
+    }
+
+ done:
+    if (!err) {
+       if (!rename(SH_TMPFILE, "/etc/shadow"))
+           pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
        else
-               unlink(SH_TMPFILE);
+           err = 1;
+    }
+
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      if (setfscreatecon(prev_context)) {
+       err = 1;
+      }
+      if (prev_context)
+       freecon(prev_context);
+      prev_context=NULL;
+    }
+#endif
 
-       return retval;
+    if (!err) {
+       return PAM_SUCCESS;
+    } else {
+       unlink(SH_TMPFILE);
+       return PAM_AUTHTOK_ERR;
+    }
 }
 
-static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
+static int _do_setpass(pam_handle_t* pamh, const char *forwho,
+                      const char *fromwhat,
                       char *towhat, unsigned int ctrl, int remember)
 {
        struct passwd *pwd = NULL;
        int retval = 0;
+       int unlocked = 0;
+       char *master = NULL;
 
        D(("called"));
 
-       setpwent();
        pwd = getpwnam(forwho);
-       endpwent();
 
-       if (pwd == NULL)
-               return PAM_AUTHTOK_ERR;
+       if (pwd == NULL) {
+               retval = PAM_AUTHTOK_ERR;
+               goto done;
+       }
 
-       if (on(UNIX_NIS, ctrl)) {
+       if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
+           if ((master=getNISserver(pamh)) != NULL) {
                struct timeval timeout;
                struct yppasswd yppwd;
                CLIENT *clnt;
-               char *master;
                int status;
-               int err = 0;
+               enum clnt_stat err;
 
-               /* Make RPC call to NIS server */
-               if ((master = getNISserver(pamh)) == NULL)
-                       return PAM_TRY_AGAIN;
+               /* Unlock passwd file to avoid deadlock */
+#ifdef USE_LCKPWDF
+               ulckpwdf();
+#endif
+               unlocked = 1;
 
                /* Initialize password information */
                yppwd.newpw.pw_passwd = pwd->pw_passwd;
@@ -474,7 +781,7 @@ static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
                yppwd.newpw.pw_gecos = pwd->pw_gecos;
                yppwd.newpw.pw_dir = pwd->pw_dir;
                yppwd.newpw.pw_shell = pwd->pw_shell;
-               yppwd.oldpass = fromwhat;
+               yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
                yppwd.newpw.pw_passwd = towhat;
 
                D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
@@ -494,42 +801,82 @@ static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
                                (xdrproc_t) xdr_int, (char *) &status,
                                timeout);
 
+               free (yppwd.oldpass);
+
                if (err) {
-                       clnt_perrno(err);
-                       retval = PAM_TRY_AGAIN;
+                       _make_remark(pamh, ctrl, PAM_TEXT_INFO,
+                               clnt_sperrno(err));
                } else if (status) {
-                       fprintf(stderr, "Error while changing NIS password.\n");
-                       retval = PAM_TRY_AGAIN;
+                       D(("Error while changing NIS password.\n"));
                }
-               printf("\nThe password has%s been changed on %s.\n",
-                      (err || status) ? " not" : "", master);
+               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) != 0) {
+               if (err || status) {
+                       _make_remark(pamh, ctrl, PAM_TEXT_INFO,
+                               _("NIS password could not be changed."));
                        retval = PAM_TRY_AGAIN;
                }
 #ifdef DEBUG
                sleep(5);
 #endif
-               return retval;
-       }
-       /* first, save old password */
-       if (save_old_password(forwho, fromwhat, remember)) {
-               return PAM_AUTHTOK_ERR;
+           } else {
+                   retval = PAM_TRY_AGAIN;
+           }
        }
-       if (on(UNIX_SHADOW, ctrl) || (strcmp(pwd->pw_passwd, "x") == 0)) {
-               retval = _update_shadow(forwho, towhat);
-               if (retval == PAM_SUCCESS)
-                       retval = _update_passwd(forwho, "x");
-       } else {
-               retval = _update_passwd(forwho, towhat);
+
+       if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
+#ifdef USE_LCKPWDF
+               if(unlocked) {
+                       int i = 0;
+                       /* These values for the number of attempts and the sleep time
+                          are, of course, completely arbitrary.
+                          My reading of the PAM docs is that, once pam_chauthtok() has been
+                          called with PAM_UPDATE_AUTHTOK, we are obliged to take any
+                          reasonable steps to make sure the token is updated; so retrying
+                          for 1/10 sec. isn't overdoing it. */
+                       while((retval = lckpwdf()) != 0 && i < 100) {
+                               usleep(1000);
+                               i++;
+                       }
+                       if(retval != 0) {
+                               return PAM_AUTHTOK_LOCK_BUSY;
+                       }
+               }
+#endif
+               /* first, save old password */
+               if (save_old_password(pamh, forwho, fromwhat, remember)) {
+                       retval = PAM_AUTHTOK_ERR;
+                       goto done;
+               }
+               if (on(UNIX_SHADOW, ctrl) || _unix_shadowed(pwd)) {
+                       retval = _update_shadow(pamh, forwho, towhat);
+#ifdef WITH_SELINUX
+                       if (retval != PAM_SUCCESS && SELINUX_ENABLED)
+                         retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
+#endif
+                       if (retval == PAM_SUCCESS)
+                               if (!_unix_shadowed(pwd))
+                                       retval = _update_passwd(pamh, forwho, "x");
+               } else {
+                       retval = _update_passwd(pamh, forwho, towhat);
+               }
        }
 
+
+done:
+#ifdef USE_LCKPWDF
+       ulckpwdf();
+#endif
+
        return retval;
 }
 
-static int _unix_verify_shadow(const char *user, unsigned int ctrl)
+static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
 {
        struct passwd *pwd = NULL;      /* Password and shadow password */
        struct spwd *spwdent = NULL;    /* file entries for the user */
@@ -537,23 +884,25 @@ static int _unix_verify_shadow(const char *user, unsigned int ctrl)
        int retval = PAM_SUCCESS;
 
        /* UNIX passwords area */
-       setpwent();
        pwd = getpwnam(user);   /* Get password file entry... */
-       endpwent();
        if (pwd == NULL)
                return PAM_AUTHINFO_UNAVAIL;    /* We don't need to do the rest... */
 
-       if (strcmp(pwd->pw_passwd, "x") == 0) {
+       if (_unix_shadowed(pwd)) {
                /* ...and shadow password file entry for this user, if shadowing
                   is enabled */
                setspent();
                spwdent = getspnam(user);
                endspent();
 
+#ifdef WITH_SELINUX
+               if (spwdent == NULL && SELINUX_ENABLED )
+                   spwdent = _unix_run_verify_binary(pamh, ctrl, user);
+#endif
                if (spwdent == NULL)
                        return PAM_AUTHINFO_UNAVAIL;
        } else {
-               if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */                 
+               if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */
                        uid_t save_uid;
 
                        save_uid = geteuid();
@@ -574,10 +923,22 @@ static int _unix_verify_shadow(const char *user, unsigned int ctrl)
                if (off(UNIX__IAMROOT, ctrl)) {
                        /* Get the current number of days since 1970 */
                        curdays = time(NULL) / (60 * 60 * 24);
-                       if ((curdays < (spwdent->sp_lstchg + spwdent->sp_min))
-                           && (spwdent->sp_min != -1))
+                       if (curdays < spwdent->sp_lstchg) {
+                               pam_syslog(pamh, LOG_DEBUG,
+                                       "account %s has password changed in future",
+                                       user);
+                               curdays = spwdent->sp_lstchg;
+                       }
+                       if ((curdays - spwdent->sp_lstchg < spwdent->sp_min)
+                                && (spwdent->sp_min != -1))
+                               /*
+                                * The last password change was too recent.
+                                */
                                retval = PAM_AUTHTOK_ERR;
-                       else if ((curdays > (spwdent->sp_lstchg + spwdent->sp_max + spwdent->sp_inact))
+                       else if ((curdays - spwdent->sp_lstchg > spwdent->sp_max)
+                                && (curdays - spwdent->sp_lstchg > spwdent->sp_inact)
+                                && (curdays - spwdent->sp_lstchg >
+                                    spwdent->sp_max + spwdent->sp_inact)
                                 && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1)
                                 && (spwdent->sp_lstchg != 0))
                                /*
@@ -600,7 +961,7 @@ static int _pam_unix_approve_pass(pam_handle_t * pamh
                                  ,const char *pass_old
                                  ,const char *pass_new)
 {
-       const char *user;
+       const void *user;
        const char *remark = NULL;
        int retval = PAM_SUCCESS;
 
@@ -610,10 +971,10 @@ static int _pam_unix_approve_pass(pam_handle_t * pamh
 
        if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
                if (on(UNIX_DEBUG, ctrl)) {
-                       _log_err(LOG_DEBUG, pamh, "bad authentication token");
+                       pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
                }
                _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
-                         "No password supplied" : "Password unchanged");
+                       _("No password supplied") : _("Password unchanged"));
                return PAM_AUTHTOK_ERR;
        }
        /*
@@ -621,25 +982,31 @@ static int _pam_unix_approve_pass(pam_handle_t * pamh
         * checking this would be the place - AGM
         */
 
-       retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
+       retval = pam_get_item(pamh, PAM_USER, &user);
        if (retval != PAM_SUCCESS) {
                if (on(UNIX_DEBUG, ctrl)) {
-                       _log_err(LOG_ERR, pamh, "Can not get username");
+                       pam_syslog(pamh, LOG_ERR, "Can not get username");
                        return PAM_AUTHTOK_ERR;
                }
        }
        if (off(UNIX__IAMROOT, ctrl)) {
 #ifdef USE_CRACKLIB
-               remark = FascistCheck(pass_new, CRACKLIB_DICTS);
+               remark = FascistCheck (pass_new, CRACKLIB_DICTS);
                D(("called cracklib [%s]", remark));
 #else
                if (strlen(pass_new) < 6)
-                       remark = "You must choose a longer password";
-               D(("lenth check [%s]", remark));
+                 remark = _("You must choose a longer password");
+               D(("length check [%s]", remark));
 #endif
-               if (on(UNIX_REMEMBER_PASSWD, ctrl))
-                       if ((retval = check_old_password(user, pass_new)) != PAM_SUCCESS)
-                               remark = "Password has been already used. Choose another.";
+               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);
@@ -658,67 +1025,62 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
 
        /* <DO NOT free() THESE> */
        const char *user;
-       char *pass_old, *pass_new;
+       const void *pass_old, *pass_new;
        /* </DO NOT free() THESE> */
 
        D(("called."));
 
-#ifdef USE_LCKPWDF
-       /* our current locking system requires that we lock the
-          entire password database.  This avoids both livelock
-          and deadlock. */
-       /* These values for the number of attempts and the sleep time
-          are, of course, completely arbitrary.
-          My reading of the PAM docs is that, once pam_chauthtok() has been
-          called with PAM_UPDATE_AUTHTOK, we are obliged to take any
-          reasonable steps to make sure the token is updated; so retrying
-          for 1/10 sec. isn't overdoing it.
-          The other possibility is to call lckpwdf() on the first
-          pam_chauthtok() pass, and hold the lock until released in the
-          second pass--but is this guaranteed to work? -SRL */
-       i=0;
-       while((retval = lckpwdf()) != 0 && i < 100) {
-               usleep(1000);
-       }
-       if(retval != 0) {
-               return PAM_AUTHTOK_LOCK_BUSY;
-       }
-#endif
        ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
 
        /*
         * First get the name of a user
         */
-       retval = pam_get_user(pamh, &user, "Username: ");
+       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 take
-                * any chances here. Require that the username starts with an
-                * alphanumeric character.
+                * '+' or '-' as the first character of a user name. Don't
+                * allow them.
                 */
-               if (user == NULL || !isalnum(*user)) {
-                       _log_err(LOG_ERR, pamh, "bad username [%s]", user);
-#ifdef USE_LCKPWDF
-                       ulckpwdf();
-#endif
+               if (user == NULL || user[0] == '-' || user[0] == '+') {
+                       pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
                        return PAM_USER_UNKNOWN;
                }
                if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
-                       _log_err(LOG_DEBUG, pamh, "username [%s] obtained",
+                       pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
                                 user);
        } else {
                if (on(UNIX_DEBUG, ctrl))
-                       _log_err(LOG_DEBUG, pamh,
+                       pam_syslog(pamh, LOG_DEBUG,
                                 "password - could not identify user");
-#ifdef USE_LCKPWDF
-               ulckpwdf();
-#endif
                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!
         */
@@ -734,44 +1096,30 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
 
                D(("prelim check"));
 
-               if (_unix_blankpasswd(ctrl, user)) {
-#ifdef USE_LCKPWDF
-                       ulckpwdf();
-#endif
+               if (_unix_blankpasswd(pamh, ctrl, user)) {
                        return PAM_SUCCESS;
                } else if (off(UNIX__IAMROOT, ctrl)) {
-
                        /* instruct user what is happening */
-#define greeting "Changing password for "
-                       Announce = (char *) malloc(sizeof(greeting) + strlen(user));
-                       if (Announce == NULL) {
-                               _log_err(LOG_CRIT, pamh,
+                       if (asprintf(&Announce, _("Changing password for %s."),
+                               user) < 0) {
+                               pam_syslog(pamh, LOG_CRIT,
                                         "password - out of memory");
-#ifdef USE_LCKPWDF
-                               ulckpwdf();
-#endif
                                return PAM_BUF_ERR;
                        }
-                       (void) strcpy(Announce, greeting);
-                       (void) strcpy(Announce + sizeof(greeting) - 1, user);
-#undef greeting
 
                        lctrl = ctrl;
                        set(UNIX__OLD_PASSWD, lctrl);
                        retval = _unix_read_password(pamh, lctrl
                                                     ,Announce
-                                            ,"(current) UNIX password: "
+                                            ,_("(current) UNIX password: ")
                                                     ,NULL
                                                     ,_UNIX_OLD_AUTHTOK
-                                            ,(const char **) &pass_old);
+                                            ,&pass_old);
                        free(Announce);
 
                        if (retval != PAM_SUCCESS) {
-                               _log_err(LOG_NOTICE, pamh
-                                ,"password - (old) token not obtained");
-#ifdef USE_LCKPWDF
-                               ulckpwdf();
-#endif
+                               pam_syslog(pamh, LOG_NOTICE,
+                                   "password - (old) token not obtained");
                                return retval;
                        }
                        /* verify that this is the password for this user */
@@ -786,22 +1134,19 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
                if (retval != PAM_SUCCESS) {
                        D(("Authentication failed"));
                        pass_old = NULL;
-#ifdef USE_LCKPWDF
-                       ulckpwdf();
-#endif
                        return retval;
                }
                retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
                pass_old = NULL;
                if (retval != PAM_SUCCESS) {
-                       _log_err(LOG_CRIT, pamh,
+                       pam_syslog(pamh, LOG_CRIT,
                                 "failed to set PAM_OLDAUTHTOK");
                }
-               retval = _unix_verify_shadow(user, ctrl);
+               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");
+                                            _("You must wait longer to change your password"));
                        else
                                retval = PAM_SUCCESS;
                }
@@ -828,10 +1173,10 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
 
                if (off(UNIX_NOT_SET_PASS, ctrl)) {
                        retval = pam_get_item(pamh, PAM_OLDAUTHTOK
-                                             ,(const void **) &pass_old);
+                                             ,&pass_old);
                } else {
                        retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
-                                             ,(const void **) &pass_old);
+                                             ,&pass_old);
                        if (retval == PAM_NO_MODULE_DATA) {
                                retval = PAM_SUCCESS;
                                pass_old = NULL;
@@ -840,20 +1185,10 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
                D(("pass_old [%s]", pass_old));
 
                if (retval != PAM_SUCCESS) {
-                       _log_err(LOG_NOTICE, pamh, "user not authenticated");
-#ifdef USE_LCKPWDF
-                       ulckpwdf();
-#endif
-                       return retval;
-               }
-               retval = _unix_verify_shadow(user, ctrl);
-               if (retval != PAM_SUCCESS) {
-                       _log_err(LOG_NOTICE, pamh, "user not authenticated 2");
-#ifdef USE_LCKPWDF
-                       ulckpwdf();
-#endif
+                       pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
                        return retval;
                }
+
                D(("get new password now"));
 
                lctrl = ctrl;
@@ -871,20 +1206,17 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
 
                        retval = _unix_read_password(pamh, lctrl
                                                     ,NULL
-                                            ,"Enter new UNIX password: "
-                                           ,"Retype new UNIX password: "
+                                            ,_("Enter new UNIX password: ")
+                                           ,_("Retype new UNIX password: ")
                                                     ,_UNIX_NEW_AUTHTOK
-                                            ,(const char **) &pass_new);
+                                            ,&pass_new);
 
                        if (retval != PAM_SUCCESS) {
                                if (on(UNIX_DEBUG, ctrl)) {
-                                       _log_err(LOG_ALERT, pamh
-                                                ,"password - new password not obtained");
+                                       pam_syslog(pamh, LOG_ALERT,
+                                                "password - new password not obtained");
                                }
                                pass_old = NULL;        /* tidy up */
-#ifdef USE_LCKPWDF
-                               ulckpwdf();
-#endif
                                return retval;
                        }
                        D(("returned to _unix_chauthtok"));
@@ -895,21 +1227,66 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
                         * password is acceptable.
                         */
 
-                       if (pass_new[0] == '\0') {      /* "\0" password = NULL */
+                       if (*(const char *)pass_new == '\0') {  /* "\0" password = NULL */
                                pass_new = NULL;
                        }
                        retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
                }
 
                if (retval != PAM_SUCCESS) {
-                       _log_err(LOG_NOTICE, pamh,
+                       pam_syslog(pamh, LOG_NOTICE,
                                 "new password not acceptable");
                        pass_new = pass_old = NULL;     /* tidy up */
+                       return retval;
+               }
+#ifdef USE_LCKPWDF
+               /* These values for the number of attempts and the sleep time
+                  are, of course, completely arbitrary.
+                  My reading of the PAM docs is that, once pam_chauthtok() has been
+                  called with PAM_UPDATE_AUTHTOK, we are obliged to take any
+                  reasonable steps to make sure the token is updated; so retrying
+                  for 1/10 sec. isn't overdoing it. */
+               i=0;
+               while((retval = lckpwdf()) != 0 && i < 100) {
+                       usleep(1000);
+                       i++;
+               }
+               if(retval != 0) {
+                       return PAM_AUTHTOK_LOCK_BUSY;
+               }
+#endif
+
+               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");
+#ifdef USE_LCKPWDF
+                               ulckpwdf();
+#endif
+                               return retval;
+                       }
+               }
+
+               retval = _unix_verify_shadow(pamh, user, ctrl);
+               if (retval != PAM_SUCCESS) {
+                       pam_syslog(pamh, LOG_NOTICE, "user not authenticated 2");
+#ifdef USE_LCKPWDF
+                       ulckpwdf();
+#endif
+                       return retval;
+               }
+
+               retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
+               if (retval != PAM_SUCCESS) {
+                       pam_syslog(pamh, LOG_NOTICE,
+                                "new password not acceptable 2");
+                       pass_new = pass_old = NULL;     /* tidy up */
 #ifdef USE_LCKPWDF
                        ulckpwdf();
 #endif
                        return retval;
                }
+
                /*
                 * By reaching here we have approved the passwords and must now
                 * rebuild the password database file.
@@ -936,14 +1313,16 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
                        salt[2] = '\0';
 
                        if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
-                               /* 
+                               /*
                                 * to avoid using the _extensions_ of the bigcrypt()
                                 * function we truncate the newly entered password
+                                * [Problems that followed from this are fixed as per
+                                *  Bug 521314.]
                                 */
                                char *temp = malloc(9);
 
                                if (temp == NULL) {
-                                       _log_err(LOG_CRIT, pamh,
+                                       pam_syslog(pamh, LOG_CRIT,
                                                 "out of memory for password");
                                        pass_new = pass_old = NULL;     /* tidy up */
 #ifdef USE_LCKPWDF
@@ -970,19 +1349,18 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
 
                retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
                                     remember);
+               /* _do_setpass has called ulckpwdf for us */
+
                _pam_delete(tpass);
                pass_old = pass_new = NULL;
        } else {                /* something has broken with the module */
-               _log_err(LOG_ALERT, pamh,
+               pam_syslog(pamh, LOG_ALERT,
                         "password received unknown request");
                retval = PAM_ABORT;
        }
 
        D(("retval was %d", retval));
 
-#ifdef USE_LCKPWDF
-       ulckpwdf();
-#endif
        return retval;
 }
 
@@ -999,4 +1377,3 @@ struct pam_module _pam_unix_passwd_modstruct = {
     pam_sm_chauthtok,
 };
 #endif
-