]> granicus.if.org Git - linux-pam/blobdiff - modules/pam_cracklib/pam_cracklib.c
Fix gratuitous use of strdup and x_strdup
[linux-pam] / modules / pam_cracklib / pam_cracklib.c
index 9b496202578d57982fce34abd8d99eb4cae77a2b..5eefd0babbdccad287a5495330dd68e07c60b6ed 100644 (file)
@@ -37,7 +37,9 @@
 #include "config.h"
 
 #include <stdio.h>
-#ifdef HAVE_CRYPT_H
+#ifdef HAVE_LIBXCRYPT
+# include <xcrypt.h>
+#elif defined(HAVE_CRYPT_H)
 # include <crypt.h>
 #endif
 #include <unistd.h>
@@ -49,6 +51,8 @@
 #include <sys/stat.h>
 #include <ctype.h>
 #include <limits.h>
+#include <pwd.h>
+#include <security/pam_modutil.h>
 
 #ifdef HAVE_CRACK_H
 #include <crack.h>
 extern char *FascistCheck(char *pw, const char *dictpath);
 #endif
 
+#ifndef CRACKLIB_DICTS
+#define CRACKLIB_DICTS NULL
+#endif
+
 /* For Translators: "%s%s" could be replaced with "<service> " or "". */
 #define PROMPT1 _("New %s%spassword: ")
 /* For Translators: "%s%s" could be replaced with "<service> " or "". */
@@ -86,27 +94,30 @@ extern char *FascistCheck(char *pw, const char *dictpath);
 struct cracklib_options {
        int retry_times;
        int diff_ok;
-       int diff_ignore;
        int min_length;
        int dig_credit;
        int up_credit;
        int low_credit;
        int oth_credit;
-       int use_authtok;
-       char prompt_type[BUFSIZ];
-        char cracklib_dictpath[PATH_MAX];
+        int min_class;
+       int max_repeat;
+       int max_sequence;
+        int max_class_repeat;
+       int reject_user;
+        int gecos_check;
+        int enforce_for_root;
+        const char *cracklib_dictpath;
 };
 
 #define CO_RETRY_TIMES  1
 #define CO_DIFF_OK      5
-#define CO_DIFF_IGNORE  23
 #define CO_MIN_LENGTH   9
 # define CO_MIN_LENGTH_BASE 5
 #define CO_DIG_CREDIT   1
 #define CO_UP_CREDIT    1
 #define CO_LOW_CREDIT   1
 #define CO_OTH_CREDIT   1
-#define CO_USE_AUTHTOK  0
+#define CO_MIN_WORD_LENGTH 4
 
 static int
 _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
@@ -123,7 +134,7 @@ _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
         if (!strcmp(*argv,"debug"))
             ctrl |= PAM_DEBUG_ARG;
         else if (!strncmp(*argv,"type=",5))
-            strncpy(opt->prompt_type, *argv+5, sizeof(opt->prompt_type) - 1);
+            pam_set_item (pamh, PAM_AUTHTOK_TYPE, *argv+5);
         else if (!strncmp(*argv,"retry=",6)) {
             opt->retry_times = strtol(*argv+6,&ep,10);
             if (!ep || (opt->retry_times < 1))
@@ -133,9 +144,7 @@ _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
             if (!ep || (opt->diff_ok < 0))
                 opt->diff_ok = CO_DIFF_OK;
         } else if (!strncmp(*argv,"difignore=",10)) {
-            opt->diff_ignore = strtol(*argv+10,&ep,10);
-            if (!ep || (opt->diff_ignore < 0))
-                opt->diff_ignore = CO_DIFF_IGNORE;
+               /* just ignore */
         } else if (!strncmp(*argv,"minlen=",7)) {
             opt->min_length = strtol(*argv+7,&ep,10);
             if (!ep || (opt->min_length < CO_MIN_LENGTH_BASE))
@@ -156,31 +165,53 @@ _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
             opt->oth_credit = strtol(*argv+8,&ep,10);
             if (!ep)
                 opt->oth_credit = 0;
+         } else if (!strncmp(*argv,"minclass=",9)) {
+             opt->min_class = strtol(*argv+9,&ep,10);
+             if (!ep)
+                 opt->min_class = 0;
+             if (opt->min_class > 4)
+                 opt->min_class = 4;
+         } else if (!strncmp(*argv,"maxrepeat=",10)) {
+             opt->max_repeat = strtol(*argv+10,&ep,10);
+             if (!ep)
+                 opt->max_repeat = 0;
+         } else if (!strncmp(*argv,"maxsequence=",12)) {
+             opt->max_sequence = strtol(*argv+12,&ep,10);
+             if (!ep)
+                 opt->max_sequence = 0;
+         } else if (!strncmp(*argv,"maxclassrepeat=",15)) {
+             opt->max_class_repeat = strtol(*argv+15,&ep,10);
+             if (!ep)
+                 opt->max_class_repeat = 0;
+        } else if (!strncmp(*argv,"reject_username",15)) {
+                opt->reject_user = 1;
+        } else if (!strncmp(*argv,"gecoscheck",10)) {
+                opt->gecos_check = 1;
+        } else if (!strncmp(*argv,"enforce_for_root",16)) {
+                 opt->enforce_for_root = 1;
+        } else if (!strncmp(*argv,"authtok_type",12)) {
+          /* for pam_get_authtok, ignore */;
         } else if (!strncmp(*argv,"use_authtok",11)) {
-                opt->use_authtok = 1;
+           /* for pam_get_authtok, ignore */;
+        } else if (!strncmp(*argv,"use_first_pass",14)) {
+          /* for pam_get_authtok, ignore */;
+        } else if (!strncmp(*argv,"try_first_pass",14)) {
+          /* for pam_get_authtok, ignore */;
         } else if (!strncmp(*argv,"dictpath=",9)) {
-            strncpy(opt->cracklib_dictpath, *argv+9,
-                    sizeof(opt->cracklib_dictpath) - 1);
+            opt->cracklib_dictpath = *argv+9;
+            if (!*(opt->cracklib_dictpath)) {
+                opt->cracklib_dictpath = CRACKLIB_DICTS;
+            }
         } else {
             pam_syslog(pamh,LOG_ERR,"pam_parse: unknown option; %s",*argv);
         }
      }
-     opt->prompt_type[sizeof(opt->prompt_type) - 1] = '\0';
-     opt->cracklib_dictpath[sizeof(opt->cracklib_dictpath) - 1] = '\0';
 
      return ctrl;
 }
 
 /* Helper functions */
 
-/* use this to free strings. ESPECIALLY password strings */
-static char *_pam_delete(register char *xx)
-{
-    _pam_overwrite(xx);
-    free(xx);
-    return NULL;
-}
-
 /*
  * can't be a palindrome - like `R A D A R' or `M A D A M'
  */
@@ -289,6 +320,47 @@ static int similar(struct cracklib_options *opt,
     return 1;
 }
 
+/*
+ * enough classes of charecters
+ */
+
+static int minclass (struct cracklib_options *opt,
+                    const char *new)
+{
+    int digits = 0;
+    int uppers = 0;
+    int lowers = 0;
+    int others = 0;
+    int total_class;
+    int i;
+    int retval;
+
+    D(( "called" ));
+    for (i = 0; new[i]; i++)
+       {
+        if (isdigit (new[i]))
+             digits = 1;
+        else if (isupper (new[i]))
+             uppers = 1;
+        else if (islower (new[i]))
+             lowers = 1;
+        else
+             others = 1;
+       }
+
+    total_class = digits + uppers + lowers + others;
+
+    D (("total class: %d\tmin_class: %d", total_class, opt->min_class));
+
+    if (total_class >= opt->min_class)
+        retval = 0;
+    else
+      retval = 1;
+
+    return retval;
+}
+
+
 /*
  * a nice mix of characters.
  */
@@ -300,16 +372,45 @@ static int simple(struct cracklib_options *opt, const char *new)
     int        others = 0;
     int        size;
     int        i;
+    enum { NONE, DIGIT, UCASE, LCASE, OTHER } prevclass = NONE;
+    int sameclass = 0;
 
     for (i = 0;new[i];i++) {
-       if (isdigit (new[i]))
+       if (isdigit (new[i])) {
            digits++;
-       else if (isupper (new[i]))
+            if (prevclass != DIGIT) {
+                prevclass = DIGIT;
+                sameclass = 1;
+            } else
+                sameclass++;
+        }
+       else if (isupper (new[i])) {
            uppers++;
-       else if (islower (new[i]))
+            if (prevclass != UCASE) {
+                prevclass = UCASE;
+                sameclass = 1;
+            } else
+                sameclass++;
+        }
+       else if (islower (new[i])) {
            lowers++;
-       else
+            if (prevclass != LCASE) {
+                prevclass = LCASE;
+                sameclass = 1;
+            } else
+                sameclass++;
+        }
+       else {
            others++;
+            if (prevclass != OTHER) {
+                prevclass = OTHER;
+                sameclass = 1;
+            } else
+                sameclass++;
+        }
+        if (opt->max_class_repeat > 1 && sameclass > opt->max_class_repeat) {
+                return 1;
+        }
     }
 
     /*
@@ -360,88 +461,224 @@ static int simple(struct cracklib_options *opt, const char *new)
     return 1;
 }
 
+static int consecutive(struct cracklib_options *opt, const char *new)
+{
+    char c;
+    int i;
+    int same;
+
+    if (opt->max_repeat == 0)
+       return 0;
+
+    for (i = 0; new[i]; i++) {
+       if (i > 0 && new[i] == c) {
+           ++same;
+           if (same > opt->max_repeat)
+               return 1;
+       } else {
+           c = new[i];
+           same = 1;
+       }
+    }
+    return 0;
+}
+
+static int sequence(struct cracklib_options *opt, const char *new)
+{
+    char c;
+    int i;
+    int sequp = 1;
+    int seqdown = 1;
+
+    if (opt->max_sequence == 0)
+       return 0;
+
+    if (new[0] == '\0')
+        return 0;
+
+    for (i = 1; new[i]; i++) {
+        c = new[i-1];
+       if (new[i] == c+1) {
+           ++sequp;
+           if (sequp > opt->max_sequence)
+               return 1;
+           seqdown = 1;
+       } else if (new[i] == c-1) {
+           ++seqdown;
+           if (seqdown > opt->max_sequence)
+               return 1;
+           sequp = 1;
+       } else {
+           sequp = 1;
+            seqdown = 1;
+        }
+    }
+    return 0;
+}
+
+static int wordcheck(const char *new, char *word)
+{
+    char *f, *b;
+
+    if (strstr(new, word) != NULL)
+       return 1;
+
+    /* now reverse the word, we can do that in place
+       as it is strdup-ed */
+    f = word;
+    b = word+strlen(word)-1;
+    while (f < b) {
+       char c;
+
+       c = *f;
+       *f = *b;
+       *b = c;
+       --b;
+       ++f;
+    }
+
+    if (strstr(new, word) != NULL)
+       return 1;
+    return 0;
+}
+
+static int usercheck(struct cracklib_options *opt, const char *new,
+                    char *user)
+{
+    if (!opt->reject_user)
+        return 0;
+
+    return wordcheck(new, user);
+}
+
 static char * str_lower(char *string)
 {
        char *cp;
 
+       if (!string)
+               return NULL;
+
        for (cp = string; *cp; cp++)
                *cp = tolower(*cp);
        return string;
 }
 
-static const char * password_check(struct cracklib_options *opt, const char *old, const char *new)
+static int gecoscheck(pam_handle_t *pamh, struct cracklib_options *opt, const char *new,
+                    const char *user)
 {
-       const char *msg = NULL;
-       char *oldmono, *newmono, *wrapped;
+    struct passwd *pwd;
+    char *list;
+    char *p;
+    char *next;
 
-       if (strcmp(new, old) == 0) {
-        msg = _("is the same as the old one");
-        return msg;
+    if (!opt->gecos_check)
+        return 0;
+
+    if ((pwd = pam_modutil_getpwnam(pamh, user)) == NULL) {
+        return 0;
     }
 
-       newmono = str_lower(x_strdup(new));
-       oldmono = str_lower(x_strdup(old));
-       wrapped = malloc(strlen(oldmono) * 2 + 1);
-       strcpy (wrapped, oldmono);
-       strcat (wrapped, oldmono);
+    list = strdup(pwd->pw_gecos);
+
+    if (list == NULL || *list == '\0') {
+        free(list);
+        return 0;
+    }
+
+    for (p = list;;p = next + 1) {
+         next = strchr(p, ' ');
+         if (next)
+             *next = '\0';
+
+         if (strlen(p) >= CO_MIN_WORD_LENGTH) {
+             str_lower(p);
+             if (wordcheck(new, p)) {
+                 free(list);
+                 return 1;
+             }
+         }
+
+         if (!next)
+             break;
+    }
+
+    free(list);
+    return 0;
+}
+
+static const char *password_check(pam_handle_t *pamh, struct cracklib_options *opt,
+                                 const char *old, const char *new,
+                                 const char *user)
+{
+       const char *msg = NULL;
+       char *oldmono = NULL, *newmono, *wrapped = NULL;
+       char *usermono = NULL;
+
+       if (old && strcmp(new, old) == 0) {
+           msg = _("is the same as the old one");
+           return msg;
+       }
+
+       newmono = str_lower(strdup(new));
+       if (!newmono)
+               msg = _("memory allocation error");
+
+       usermono = str_lower(strdup(user));
+       if (!usermono)
+               msg = _("memory allocation error");
+
+       if (!msg && old) {
+               oldmono = str_lower(strdup(old));
+               if (oldmono)
+                       wrapped = malloc(strlen(oldmono) * 2 + 1);
+               if (wrapped) {
+                       strcpy (wrapped, oldmono);
+                       strcat (wrapped, oldmono);
+               } else {
+                       msg = _("memory allocation error");
+               }
+       }
 
-       if (palindrome(newmono))
+       if (!msg && palindrome(newmono))
                msg = _("is a palindrome");
 
-       if (!msg && strcmp(oldmono, newmono) == 0)
+       if (!msg && oldmono && strcmp(oldmono, newmono) == 0)
                msg = _("case changes only");
 
-       if (!msg && similar(opt, oldmono, newmono))
+       if (!msg && oldmono && similar(opt, oldmono, newmono))
                msg = _("is too similar to the old one");
 
        if (!msg && simple(opt, new))
                msg = _("is too simple");
 
-       if (!msg && strstr(wrapped, newmono))
+       if (!msg && wrapped && strstr(wrapped, newmono))
                msg = _("is rotated");
 
-       memset(newmono, 0, strlen(newmono));
-       memset(oldmono, 0, strlen(oldmono));
-       memset(wrapped, 0, strlen(wrapped));
-       free(newmono);
-       free(oldmono);
-       free(wrapped);
+       if (!msg && minclass (opt, new))
+               msg = _("not enough character classes");
 
-       return msg;
-}
+       if (!msg && consecutive(opt, new))
+               msg = _("contains too many same characters consecutively");
 
+       if (!msg && sequence(opt, new))
+               msg = _("contains too long of a monotonic character sequence");
 
-#define OLD_PASSWORDS_FILE     "/etc/security/opasswd"
+       if (!msg && (usercheck(opt, newmono, usermono) || gecoscheck(pamh, opt, newmono, user)))
+               msg = _("contains the user name in some form");
 
-static const char * check_old_password(const char *forwho, const char *newpass)
-{
-       static char buf[16384];
-       char *s_luser, *s_uid, *s_npas, *s_pas;
-       const char *msg = NULL;
-       FILE *opwfile;
-
-       opwfile = fopen(OLD_PASSWORDS_FILE, "r");
-       if (opwfile == NULL)
-               return NULL;
-
-       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, ":,");
-                       while (s_pas != NULL) {
-                               if (!strcmp(crypt(newpass, s_pas), s_pas)) {
-                                       msg = _("has been already used");
-                                       break;
-                               }
-                               s_pas = strtok(NULL, ":,");
-                       }
-                       break;
-               }
+       free(usermono);
+       if (newmono) {
+               memset(newmono, 0, strlen(newmono));
+               free(newmono);
+       }
+       if (oldmono) {
+         memset(oldmono, 0, strlen(oldmono));
+         free(oldmono);
+       }
+       if (wrapped) {
+         memset(wrapped, 0, strlen(wrapped));
+         free(wrapped);
        }
-       fclose(opwfile);
 
        return msg;
 }
@@ -454,7 +691,7 @@ static int _pam_unix_approve_pass(pam_handle_t *pamh,
                                   const char *pass_new)
 {
     const char *msg = NULL;
-    const void *user;
+    const char *user;
     int retval;
 
     if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
@@ -465,20 +702,17 @@ static int _pam_unix_approve_pass(pam_handle_t *pamh,
         return PAM_AUTHTOK_ERR;
     }
 
+    retval = pam_get_user(pamh, &user, NULL);
+    if (retval != PAM_SUCCESS || user == NULL) {
+       if (ctrl & PAM_DEBUG_ARG)
+               pam_syslog(pamh,LOG_ERR,"Can not get username");
+       return PAM_AUTHTOK_ERR;
+    }
     /*
      * if one wanted to hardwire authentication token strength
      * checking this would be the place
      */
-    msg = password_check(opt, pass_old,pass_new);
-    if (!msg) {
-       retval = pam_get_item(pamh, PAM_USER, &user);
-       if (retval != PAM_SUCCESS || user == NULL) {
-           if (ctrl & PAM_DEBUG_ARG)
-               pam_syslog(pamh,LOG_ERR,"Can not get username");
-           return PAM_AUTHTOK_ERR;
-       }
-       msg = check_old_password(user, pass_new);
-    }
+    msg = password_check(pamh, opt, pass_old, pass_new, user);
 
     if (msg) {
         if (ctrl & PAM_DEBUG_ARG)
@@ -505,17 +739,12 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
     memset(&options, 0, sizeof(options));
     options.retry_times = CO_RETRY_TIMES;
     options.diff_ok = CO_DIFF_OK;
-    options.diff_ignore = CO_DIFF_IGNORE;
     options.min_length = CO_MIN_LENGTH;
     options.dig_credit = CO_DIG_CREDIT;
     options.up_credit = CO_UP_CREDIT;
     options.low_credit = CO_LOW_CREDIT;
     options.oth_credit = CO_OTH_CREDIT;
-    options.use_authtok = CO_USE_AUTHTOK;
-    memset(options.prompt_type, 0, BUFSIZ);
-    strcpy(options.prompt_type,"UNIX");
-    memset(options.cracklib_dictpath, 0,
-          sizeof (options.cracklib_dictpath));
+    options.cracklib_dictpath = CRACKLIB_DICTS;
 
     ctrl = _pam_parse(pamh, &options, argc, argv);
 
@@ -526,189 +755,96 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
         return PAM_SUCCESS;
     } else if (flags & PAM_UPDATE_AUTHTOK) {
         int retval;
-        char *token1, *token2, *resp;
        const void *oldtoken;
+       int tries;
 
        D(("do update"));
-        retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &oldtoken);
+
+
+       retval = pam_get_item (pamh, PAM_OLDAUTHTOK, &oldtoken);
         if (retval != PAM_SUCCESS) {
             if (ctrl & PAM_DEBUG_ARG)
                 pam_syslog(pamh,LOG_ERR,"Can not get old passwd");
-            oldtoken=NULL;
-            retval = PAM_SUCCESS;
+            oldtoken = NULL;
         }
 
-        do {
-        /*
-         * make sure nothing inappropriate gets returned
-         */
-        token1 = token2 = NULL;
+       tries = 0;
+       while (tries < options.retry_times) {
+         const char *crack_msg;
+         const char *newtoken = NULL;
 
-        if (!options.retry_times) {
-           D(("returning %s because maxtries reached",
-              pam_strerror(pamh, retval)));
-            return retval;
-       }
 
-        /* Planned modus operandi:
-         * Get a passwd.
-         * Verify it against cracklib.
-         * If okay get it a second time.
-         * Check to be the same with the first one.
-         * set PAM_AUTHTOK and return
-         */
-
-       if (options.use_authtok == 1) {
-           const void *item = NULL;
-
-           retval = pam_get_item(pamh, PAM_AUTHTOK, &item);
-           if (retval != PAM_SUCCESS) {
-               /* very strange. */
-               pam_syslog(pamh, LOG_ALERT,
-                          "pam_get_item returned error to pam_cracklib");
-           } else if (item != NULL) {      /* we have a password! */
-               token1 = x_strdup(item);
-               item = NULL;
-           } else {
-               retval = PAM_AUTHTOK_RECOVERY_ERR;         /* didn't work */
-           }
+         tries++;
 
-       } else {
-            /* Prepare to ask the user for the first time */
-            resp = NULL;
-           retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF, &resp,
-                                 PROMPT1, options.prompt_type,
-                                options.prompt_type[0]?" ":"");
-
-           if (retval == PAM_SUCCESS) {     /* a good conversation */
-               token1 = x_strdup(resp);
-                if (token1 == NULL) {
-                   pam_syslog(pamh, LOG_NOTICE,
-                               "could not recover authentication token 1");
-                   retval = PAM_AUTHTOK_RECOVERY_ERR;
-               }
-                /*
-                 * tidy up the conversation (resp_retcode) is ignored
-                 */
-                _pam_drop(resp);
-            } else {
-                retval = (retval == PAM_SUCCESS) ?
-                         PAM_AUTHTOK_RECOVERY_ERR:retval ;
-            }
-       }
+         /* Planned modus operandi:
+          * Get a passwd.
+          * Verify it against cracklib.
+          * If okay get it a second time.
+          * Check to be the same with the first one.
+          * set PAM_AUTHTOK and return
+          */
 
-        if (retval != PAM_SUCCESS) {
-            if (ctrl & PAM_DEBUG_ARG)
-                pam_syslog(pamh,LOG_DEBUG,"unable to obtain a password");
-            continue;
-        }
+         retval = pam_get_authtok_noverify (pamh, &newtoken, NULL);
+         if (retval != PAM_SUCCESS) {
+           pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s",
+                      pam_strerror (pamh, retval));
+           continue;
+         } else if (newtoken == NULL) {      /* user aborted password change, quit */
+           return PAM_AUTHTOK_ERR;
+         }
 
-       D(("testing password, retval = %s", pam_strerror(pamh, retval)));
-        /* now test this passwd against cracklib */
-        {
-            const char *crack_msg;
-
-           D(("against cracklib"));
-            if ((crack_msg = FascistCheck(token1,options.cracklib_dictpath[0] == '\0'?NULL:options.cracklib_dictpath))) {
-                if (ctrl & PAM_DEBUG_ARG)
-                    pam_syslog(pamh,LOG_DEBUG,"bad password: %s",crack_msg);
-                pam_error(pamh, _("BAD PASSWORD: %s"), crack_msg);
-                if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
-                    retval = PAM_AUTHTOK_ERR;
-                else
-                    retval = PAM_SUCCESS;
-            } else {
-                /* check it for strength too... */
-               D(("for strength"));
-                if (oldtoken) {
-                    retval = _pam_unix_approve_pass(pamh,ctrl,&options,
-                                               oldtoken,token1);
-                    if (retval != PAM_SUCCESS) {
-                        if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
-                           retval = PAM_AUTHTOK_ERR;
-                       else
-                           retval = PAM_SUCCESS;
-                   }
-                }
-            }
-        }
+         D(("testing password"));
+         /* now test this passwd against cracklib */
 
-       D(("after testing: retval = %s", pam_strerror(pamh, retval)));
-        /* if cracklib/strength check said it is a bad passwd... */
-        if ((retval != PAM_SUCCESS) && (retval != PAM_IGNORE)) {
-           int temp_unused;
+         D(("against cracklib"));
+         if ((crack_msg = FascistCheck (newtoken, options.cracklib_dictpath))) {
+           if (ctrl & PAM_DEBUG_ARG)
+             pam_syslog(pamh,LOG_DEBUG,"bad password: %s",crack_msg);
+           pam_error (pamh, _("BAD PASSWORD: %s"), crack_msg);
+           if (getuid() || options.enforce_for_root || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
+             {
+               pam_set_item (pamh, PAM_AUTHTOK, NULL);
+               retval = PAM_AUTHTOK_ERR;
+               continue;
+             }
+         }
+
+         /* check it for strength too... */
+         D(("for strength"));
+         retval = _pam_unix_approve_pass (pamh, ctrl, &options,
+                                          oldtoken, newtoken);
+         if (retval != PAM_SUCCESS) {
+           if (getuid() || options.enforce_for_root || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
+             {
+               pam_set_item(pamh, PAM_AUTHTOK, NULL);
+               retval = PAM_AUTHTOK_ERR;
+               continue;
+             }
+         }
+
+         retval = pam_get_authtok_verify (pamh, &newtoken, NULL);
+         if (retval != PAM_SUCCESS) {
+           pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s",
+                      pam_strerror (pamh, retval));
+           pam_set_item(pamh, PAM_AUTHTOK, NULL);
+           continue;
+         } else if (newtoken == NULL) {      /* user aborted password change, quit */
+           return PAM_AUTHTOK_ERR;
+         }
 
-           temp_unused = pam_set_item(pamh, PAM_AUTHTOK, NULL);
-            token1 = _pam_delete(token1);
-            continue;
+         return PAM_SUCCESS;
         }
 
-        /* Now we have a good passwd. Ask for it once again */
-
-        if (options.use_authtok == 0) {
-            resp = NULL;
-           retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF, &resp,
-                                PROMPT2, options.prompt_type,
-                                options.prompt_type[0]?" ":"");
-           if (retval == PAM_SUCCESS) {     /* a good conversation */
-               token2 = x_strdup(resp);
-               if (token2 == NULL) {
-                   pam_syslog(pamh,LOG_NOTICE,
-                              "could not recover authentication token 2");
-                   retval = PAM_AUTHTOK_RECOVERY_ERR;
-               }
-                /*
-                 * tidy up the conversation (resp_retcode) is ignored
-                 */
-               _pam_drop(resp);
-            }
-
-           /* No else, the a retval == PAM_SUCCESS path can change retval
-              to a failure code.  */
-           if (retval != PAM_SUCCESS) {
-             if (ctrl & PAM_DEBUG_ARG)
-                pam_syslog(pamh,LOG_DEBUG,"unable to obtain retyped password");
-             continue;
-           }
-
-            /* Hopefully now token1 and token2 the same password ... */
-            if (strcmp(token1,token2) != 0) {
-                /* tell the user */
-               pam_error(pamh, "%s", MISTYPED_PASS);
-                token1 = _pam_delete(token1);
-                token2 = _pam_delete(token2);
-                pam_set_item(pamh, PAM_AUTHTOK, NULL);
-                if (ctrl & PAM_DEBUG_ARG)
-                    pam_syslog(pamh,LOG_NOTICE,"Password mistyped");
-                retval = PAM_AUTHTOK_RECOVERY_ERR;
-                continue;
-            }
-
-            /* Yes, the password was typed correct twice
-             * we store this password as an item
-             */
-
-           {
-               const void *item = NULL;
-
-               retval = pam_set_item(pamh, PAM_AUTHTOK, token1);
-
-               /* clean up */
-               token1 = _pam_delete(token1);
-               token2 = _pam_delete(token2);
-
-               if ( (retval != PAM_SUCCESS) ||
-                    ((retval = pam_get_item(pamh, PAM_AUTHTOK, &item)
-                        ) != PAM_SUCCESS) ) {
-                    pam_syslog(pamh, LOG_CRIT, "error manipulating password");
-                    continue;
-               }
-               item = NULL;                 /* break link to password */
-               return PAM_SUCCESS;
-           }
-        }
+       D(("returning because maxtries reached"));
+
+       pam_set_item (pamh, PAM_AUTHTOK, NULL);
 
-        } while (options.retry_times--);
+       /* if we have only one try, we can use the real reason,
+          else say that there were too many tries. */
+       if (options.retry_times > 1)
+         return PAM_MAXTRIES;
+       else
+         return retval;
 
     } else {
         if (ctrl & PAM_DEBUG_ARG)