/* * pam_cracklib module */ /* * 0.9. switch to using a distance algorithm in similar() * 0.86. added support for setting minimum numbers of digits, uppers, * lowers, and others * 0.85. added six new options to use this with long passwords. * 0.8. tidied output and improved D(()) usage for debugging. * 0.7. added support for more obscure checks for new passwd. * 0.6. root can reset user passwd to any values (it's only warned) * 0.5. supports retries - 'retry=N' argument * 0.4. added argument 'type=XXX' for 'New XXX password' prompt * 0.3. Added argument 'debug' * 0.2. new password is feeded to cracklib for verify after typed once * 0.1. First release */ /* * Written by Cristian Gafton 1996/09/10 * Long password support by Philip W. Dalrymple 1997/07/18 * See the end of the file for Copyright Information * * Modification for long password systems (>8 chars). The original * module had problems when used in a md5 password system in that it * allowed too short passwords but required that at least half of the * bytes in the new password did not appear in the old one. this * action is still the default and the changes should not break any * current user. This modification adds 6 new options, one to set the * number of bytes in the new password that are not in the old one, * the other five to control the length checking, these are all * documented (or will be before anyone else sees this code) in the PAM * S.A.G. in the section on the cracklib module. */ #include "config.h" #include #ifdef HAVE_CRYPT_H # include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_CRACK_H #include #else extern char *FascistCheck(char *pw, const char *dictpath); #endif /* For Translators: "%s%s" could be replaced with " " or "". */ #define PROMPT1 _("New %s%spassword: ") /* For Translators: "%s%s" could be replaced with " " or "". */ #define PROMPT2 _("Retype new %s%spassword: ") #define MISTYPED_PASS _("Sorry, passwords do not match.") #ifdef MIN #undef MIN #endif #define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b)) /* * here, we make a definition for the externally accessible function * in this file (this definition is required for static a module * but strongly encouraged generally) it is used to instruct the * modules include file to define the function prototypes. */ #define PAM_SM_PASSWORD #include #include #include /* argument parsing */ #define PAM_DEBUG_ARG 0x0001 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]; }; #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 static int _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt, int argc, const char **argv) { int ctrl=0; /* step through arguments */ for (ctrl=0; argc-- > 0; ++argv) { char *ep = NULL; /* generic options */ 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); else if (!strncmp(*argv,"retry=",6)) { opt->retry_times = strtol(*argv+6,&ep,10); if (!ep || (opt->retry_times < 1)) opt->retry_times = CO_RETRY_TIMES; } else if (!strncmp(*argv,"difok=",6)) { opt->diff_ok = strtol(*argv+6,&ep,10); 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; } else if (!strncmp(*argv,"minlen=",7)) { opt->min_length = strtol(*argv+7,&ep,10); if (!ep || (opt->min_length < CO_MIN_LENGTH_BASE)) opt->min_length = CO_MIN_LENGTH_BASE; } else if (!strncmp(*argv,"dcredit=",8)) { opt->dig_credit = strtol(*argv+8,&ep,10); if (!ep) opt->dig_credit = 0; } else if (!strncmp(*argv,"ucredit=",8)) { opt->up_credit = strtol(*argv+8,&ep,10); if (!ep) opt->up_credit = 0; } else if (!strncmp(*argv,"lcredit=",8)) { opt->low_credit = strtol(*argv+8,&ep,10); if (!ep) opt->low_credit = 0; } else if (!strncmp(*argv,"ocredit=",8)) { opt->oth_credit = strtol(*argv+8,&ep,10); if (!ep) opt->oth_credit = 0; } else if (!strncmp(*argv,"use_authtok",11)) { opt->use_authtok = 1; } else if (!strncmp(*argv,"dictpath=",9)) { strncpy(opt->cracklib_dictpath, *argv+9, sizeof(opt->cracklib_dictpath) - 1); } 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' */ static int palindrome(const char *new) { int i, j; i = strlen (new); for (j = 0;j < i;j++) if (new[i - j - 1] != new[j]) return 0; return 1; } /* * Calculate how different two strings are in terms of the number of * character removals, additions, and changes needed to go from one to * the other */ static int distdifferent(const char *old, const char *new, size_t i, size_t j) { char c, d; if ((i == 0) || (strlen(old) < i)) { c = 0; } else { c = old[i - 1]; } if ((j == 0) || (strlen(new) < j)) { d = 0; } else { d = new[j - 1]; } return (c != d); } static int distcalculate(int **distances, const char *old, const char *new, size_t i, size_t j) { int tmp = 0; if (distances[i][j] != -1) { return distances[i][j]; } tmp = distcalculate(distances, old, new, i - 1, j - 1); tmp = MIN(tmp, distcalculate(distances, old, new, i, j - 1)); tmp = MIN(tmp, distcalculate(distances, old, new, i - 1, j)); tmp += distdifferent(old, new, i, j); distances[i][j] = tmp; return tmp; } static int distance(const char *old, const char *new) { int **distances = NULL; size_t m, n, i, j, r; m = strlen(old); n = strlen(new); distances = malloc(sizeof(int*) * (m + 1)); for (i = 0; i <= m; i++) { distances[i] = malloc(sizeof(int) * (n + 1)); for(j = 0; j <= n; j++) { distances[i][j] = -1; } } for (i = 0; i <= m; i++) { distances[i][0] = i; } for (j = 0; j <= n; j++) { distances[0][j] = j; } distances[0][0] = 0; r = distcalculate(distances, old, new, m, n); for (i = 0; i <= m; i++) { memset(distances[i], 0, sizeof(int) * (n + 1)); free(distances[i]); } free(distances); return r; } static int similar(struct cracklib_options *opt, const char *old, const char *new) { if (distance(old, new) >= opt->diff_ok) { return 0; } if (strlen(new) >= (strlen(old) * 2)) { return 0; } /* passwords are too similar */ return 1; } /* * a nice mix of characters. */ static int simple(struct cracklib_options *opt, const char *new) { int digits = 0; int uppers = 0; int lowers = 0; int others = 0; int size; int i; for (i = 0;new[i];i++) { if (isdigit (new[i])) digits++; else if (isupper (new[i])) uppers++; else if (islower (new[i])) lowers++; else others++; } /* * The scam was this - a password of only one character type * must be 8 letters long. Two types, 7, and so on. * This is now changed, the base size and the credits or defaults * see the docs on the module for info on these parameters, the * defaults cause the effect to be the same as before the change */ if ((opt->dig_credit >= 0) && (digits > opt->dig_credit)) digits = opt->dig_credit; if ((opt->up_credit >= 0) && (uppers > opt->up_credit)) uppers = opt->up_credit; if ((opt->low_credit >= 0) && (lowers > opt->low_credit)) lowers = opt->low_credit; if ((opt->oth_credit >= 0) && (others > opt->oth_credit)) others = opt->oth_credit; size = opt->min_length; if (opt->dig_credit >= 0) size -= digits; else if (digits < opt->dig_credit * -1) return 1; if (opt->up_credit >= 0) size -= uppers; else if (uppers < opt->up_credit * -1) return 1; if (opt->low_credit >= 0) size -= lowers; else if (lowers < opt->low_credit * -1) return 1; if (opt->oth_credit >= 0) size -= others; else if (others < opt->oth_credit * -1) return 1; if (size <= i) return 0; return 1; } static char * str_lower(char *string) { char *cp; 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) { const char *msg = NULL; char *oldmono, *newmono, *wrapped; if (strcmp(new, old) == 0) { msg = _("is the same as the old one"); return msg; } newmono = str_lower(x_strdup(new)); oldmono = str_lower(x_strdup(old)); wrapped = malloc(strlen(oldmono) * 2 + 1); strcpy (wrapped, oldmono); strcat (wrapped, oldmono); if (palindrome(newmono)) msg = _("is a palindrome"); if (!msg && strcmp(oldmono, newmono) == 0) msg = _("case changes only"); if (!msg && 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)) msg = _("is rotated"); memset(newmono, 0, strlen(newmono)); memset(oldmono, 0, strlen(oldmono)); memset(wrapped, 0, strlen(wrapped)); free(newmono); free(oldmono); free(wrapped); return msg; } #define OLD_PASSWORDS_FILE "/etc/security/opasswd" 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; } } fclose(opwfile); return msg; } static int _pam_unix_approve_pass(pam_handle_t *pamh, unsigned int ctrl, struct cracklib_options *opt, const char *pass_old, const char *pass_new) { const char *msg = NULL; const void *user; int retval; if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) { if (ctrl && PAM_DEBUG_ARG) pam_syslog(pamh, LOG_DEBUG, "bad authentication token"); pam_error(pamh, "%s", pass_new == NULL ? _("No password supplied"):_("Password unchanged")); 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); } if (msg) { if (ctrl && PAM_DEBUG_ARG) pam_syslog(pamh, LOG_NOTICE, "new passwd fails strength check: %s", msg); pam_error(pamh, _("BAD PASSWORD: %s"), msg); return PAM_AUTHTOK_ERR; }; return PAM_SUCCESS; } /* The Main Thing (by Cristian Gafton, CEO at this module :-) * (stolen from http://home.netscape.com) */ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { unsigned int ctrl; struct cracklib_options options; D(("called.")); 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)); ctrl = _pam_parse(pamh, &options, argc, argv); if (flags & PAM_PRELIM_CHECK) { /* Check for passwd dictionary */ /* We cannot do that, since the original path is compiled into the cracklib library and we don't know it. */ return PAM_SUCCESS; } else if (flags & PAM_UPDATE_AUTHTOK) { int retval; char *token1, *token2, *resp; const void *oldtoken; D(("do update")); 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; } do { /* * make sure nothing inappropriate gets returned */ token1 = token2 = 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 */ } } 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 ; } } if (retval != PAM_SUCCESS) { if (ctrl && PAM_DEBUG_ARG) pam_syslog(pamh,LOG_DEBUG,"unable to obtain a password"); continue; } 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(("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; temp_unused = pam_set_item(pamh, PAM_AUTHTOK, NULL); token1 = _pam_delete(token1); continue; } /* 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; } } } while (options.retry_times--); } else { if (ctrl & PAM_DEBUG_ARG) pam_syslog(pamh, LOG_NOTICE, "UNKNOWN flags setting %02X",flags); return PAM_SERVICE_ERR; } /* Not reached */ return PAM_SERVICE_ERR; } #ifdef PAM_STATIC /* static module data */ struct pam_module _pam_cracklib_modstruct = { "pam_cracklib", NULL, NULL, NULL, NULL, NULL, pam_sm_chauthtok }; #endif /* * Copyright (c) Cristian Gafton , 1996. * All rights reserved * * 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. * * The following copyright was appended for the long password support * added with the libpam 0.58 release: * * Modificaton Copyright (c) Philip W. Dalrymple III * 1997. All rights reserved * * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO * 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. */