passwdqc-1.3.0/0000700000000000000000000000000012135636516012034 5ustar rootrootpasswdqc-1.3.0/pwqgen.10000600000000000000000000000503612135513457013423 0ustar rootroot.\" Copyright (c) 2009 Dmitry V. Levin .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. .\" .\" $Owl: Owl/packages/passwdqc/passwdqc/pwqgen.1,v 1.11 2013/04/23 14:14:07 solar Exp $ .\" .Dd March 13, 2010 .Dt PWQGEN 1 .Os "Openwall Project" .Sh NAME .Nm pwqgen .Nd Generate quality controllable random passphrase .Sh SYNOPSIS .Nm Op Ar options .Sh DESCRIPTION The .Nm program generates a random passphrase using the libpasswdqc library. Strength of the generated passphrase depends on the amount of randomness read from .Pa /dev/urandom . .Sh OPTIONS .Bl -tag -width indent .It Cm random Ns = Ns Ar N .Pq default: Cm random Ns = Ns 47 The size of randomly-generated passphrase in bits (24 to 85). .It Cm config Ns = Ns Ar FILE Load config .Ar FILE in the .Cm passwdqc.conf format. This file may define any options described in .Xr passwdqc.conf 5 , but only the .Cm random and .Cm config options are honored by .Nm . .It Cm --version Output .Nm program version and exit. .It Cm -h , --help Output .Nm help text and exit. .El .Sh EXIT STATUS .Nm exits with non-zero status when it encounters invalid config file, invalid option, invalid parameter value, when it fails to obtain enough randomness, and in any case when it fails to generate a passphrase. .Sh FILES .Pa /etc/passwdqc.conf . .Sh SEE ALSO .Xr pwqcheck 1 , .Xr urandom 4 , .Xr passwdqc.conf 5 , .Xr pam_passwdqc 8 . .Pp http://www.openwall.com/passwdqc/ .Sh AUTHORS The pam_passwdqc module was written for Openwall GNU/*/Linux by Solar Designer. The .Nm program was originally written for ALT GNU/*/Linux by Dmitry V. Levin, indirectly reusing code from pam_passwdqc (via libpasswdqc). This manual page was written for Openwall GNU/*/Linux by Dmitry V. Levin. passwdqc-1.3.0/pam_passwdqc.c0000600000000000000000000003313212014013011014635 0ustar rootroot/* * Copyright (c) 2000-2003,2005,2012 by Solar Designer. See LICENSE. */ #ifdef __FreeBSD__ /* For vsnprintf(3) */ #define _XOPEN_SOURCE 600 #else #define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE_EXTENDED #define _XOPEN_VERSION 500 #endif #include #include #include #include #include #include #include #ifdef HAVE_SHADOW #include #endif #define PAM_SM_PASSWORD #ifndef LINUX_PAM #include #endif #include #include "pam_macros.h" #if !defined(PAM_EXTERN) && !defined(PAM_STATIC) #define PAM_EXTERN extern #endif #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR) #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR #endif #if (defined(__sun) || defined(__hpux)) && \ !defined(LINUX_PAM) && !defined(_OPENPAM) /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */ #define lo_const #else #define lo_const const #endif #ifdef _OPENPAM /* OpenPAM doesn't use const here, while Linux-PAM does */ #define l_const #else #define l_const lo_const #endif typedef lo_const void *pam_item_t; #include "passwdqc.h" #define PROMPT_OLDPASS \ "Enter current password: " #define PROMPT_NEWPASS1 \ "Enter new password: " #define PROMPT_NEWPASS2 \ "Re-type new password: " #define MESSAGE_MISCONFIGURED \ "System configuration error. Please contact your administrator." #define MESSAGE_INVALID_OPTION \ "pam_passwdqc: %s." #define MESSAGE_INTRO_PASSWORD \ "\nYou can now choose the new password.\n" #define MESSAGE_INTRO_BOTH \ "\nYou can now choose the new password or passphrase.\n" #define MESSAGE_EXPLAIN_PASSWORD_1CLASS \ "A good password should be a mix of upper and lower case letters,\n" \ "digits, and other characters. You can use a%s %d character long\n" \ "password.\n" #define MESSAGE_EXPLAIN_PASSWORD_CLASSES \ "A valid password should be a mix of upper and lower case letters,\n" \ "digits, and other characters. You can use a%s %d character long\n" \ "password with characters from at least %d of these 4 classes.\n" \ "An upper case letter that begins the password and a digit that\n" \ "ends it do not count towards the number of character classes used.\n" #define MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES \ "A valid password should be a mix of upper and lower case letters,\n" \ "digits, and other characters. You can use a%s %d character long\n" \ "password with characters from all of these classes. An upper\n" \ "case letter that begins the password and a digit that ends it do\n" \ "not count towards the number of character classes used.\n" #define MESSAGE_EXPLAIN_PASSWORD_ALT \ "A valid password should be a mix of upper and lower case letters,\n" \ "digits, and other characters. You can use a%s %d character long\n" \ "password with characters from at least 3 of these 4 classes, or\n" \ "a%s %d character long password containing characters from all the\n" \ "classes. An upper case letter that begins the password and a\n" \ "digit that ends it do not count towards the number of character\n" \ "classes used.\n" #define MESSAGE_EXPLAIN_PASSPHRASE \ "A passphrase should be of at least %d words, %d to %d characters\n" \ "long, and contain enough different characters.\n" #define MESSAGE_RANDOM \ "Alternatively, if no one else can see your terminal now, you can\n" \ "pick this as your password: \"%s\".\n" #define MESSAGE_RANDOMONLY \ "This system is configured to permit randomly generated passwords\n" \ "only. If no one else can see your terminal now, you can pick this\n" \ "as your password: \"%s\". Otherwise come back later.\n" #define MESSAGE_RANDOMFAILED \ "This system is configured to use randomly generated passwords\n" \ "only, but the attempt to generate a password has failed. This\n" \ "could happen for a number of reasons: you could have requested\n" \ "an impossible password length, or the access to kernel random\n" \ "number pool could have failed." #define MESSAGE_TOOLONG \ "This password may be too long for some services. Choose another." #define MESSAGE_TRUNCATED \ "Warning: your longer password will be truncated to 8 characters." #define MESSAGE_WEAKPASS \ "Weak password: %s." #define MESSAGE_NOTRANDOM \ "Sorry, you've mistyped the password that was generated for you." #define MESSAGE_MISTYPED \ "Sorry, passwords do not match." #define MESSAGE_RETRY \ "Try again." static int converse(pam_handle_t *pamh, int style, l_const char *text, struct pam_response **resp) { pam_item_t item; const struct pam_conv *conv; struct pam_message msg, *pmsg; int status; *resp = NULL; status = pam_get_item(pamh, PAM_CONV, &item); if (status != PAM_SUCCESS) return status; conv = item; pmsg = &msg; msg.msg_style = style; msg.msg = text; return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp, conv->appdata_ptr); } #ifdef __GNUC__ __attribute__ ((format (printf, 3, 4))) #endif static int say(pam_handle_t *pamh, int style, const char *format, ...) { va_list args; char buffer[0x800]; int needed; struct pam_response *resp; int status; va_start(args, format); needed = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); if ((unsigned int)needed < sizeof(buffer)) { status = converse(pamh, style, buffer, &resp); pwqc_drop_pam_reply(resp, 1); } else { status = PAM_ABORT; } memset(buffer, 0, sizeof(buffer)); return status; } static int check_max(passwdqc_params_qc_t *qc, pam_handle_t *pamh, const char *newpass) { if ((int)strlen(newpass) > qc->max) { if (qc->max != 8) { say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG); return -1; } say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED); } return 0; } static int check_pass(struct passwd *pw, const char *pass) { const char *hash; int retval; #ifdef HAVE_SHADOW #ifdef __hpux if (iscomsec()) { #else if (!strcmp(pw->pw_passwd, "x")) { #endif struct spwd *spw = getspnam(pw->pw_name); endspent(); if (!spw) return -1; hash = NULL; if (strlen(spw->sp_pwdp) >= 13) { #ifdef __hpux hash = bigcrypt(pass, spw->sp_pwdp); #else hash = crypt(pass, spw->sp_pwdp); #endif } retval = (hash && !strcmp(hash, spw->sp_pwdp)) ? 0 : -1; memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp)); return retval; } #endif hash = NULL; if (strlen(pw->pw_passwd) >= 13) hash = crypt(pass, pw->pw_passwd); retval = (hash && !strcmp(hash, pw->pw_passwd)) ? 0 : -1; memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); return retval; } static int am_root(pam_handle_t *pamh) { pam_item_t item; const char *service; if (getuid() != 0) return 0; if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS) return 0; service = item; return !strcmp(service, "passwd"); } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { passwdqc_params_t params; struct pam_response *resp; struct passwd *pw, fake_pw; pam_item_t item; const char *user, *oldpass, *newpass; char *trypass, *randompass; char *parse_reason; const char *check_reason; int ask_oldauthtok; int randomonly, enforce, retries_left, retry_wanted; int status; passwdqc_params_reset(¶ms); if (passwdqc_params_parse(¶ms, &parse_reason, argc, argv)) { say(pamh, PAM_ERROR_MSG, am_root(pamh) ? MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, parse_reason); free(parse_reason); return PAM_ABORT; } status = PAM_SUCCESS; ask_oldauthtok = 0; if (flags & PAM_PRELIM_CHECK) { if (params.pam.flags & F_ASK_OLDAUTHTOK_PRELIM) ask_oldauthtok = 1; } else if (flags & PAM_UPDATE_AUTHTOK) { if (params.pam.flags & F_ASK_OLDAUTHTOK_UPDATE) ask_oldauthtok = 1; } else return PAM_SERVICE_ERR; if (ask_oldauthtok && !am_root(pamh)) { status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_OLDPASS, &resp); if (status == PAM_SUCCESS) { if (resp && resp->resp) { status = pam_set_item(pamh, PAM_OLDAUTHTOK, resp->resp); pwqc_drop_pam_reply(resp, 1); } else status = PAM_AUTHTOK_RECOVERY_ERR; } if (status != PAM_SUCCESS) return status; } if (flags & PAM_PRELIM_CHECK) return status; status = pam_get_item(pamh, PAM_USER, &item); if (status != PAM_SUCCESS) return status; user = item; status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item); if (status != PAM_SUCCESS) return status; oldpass = item; if (params.pam.flags & F_NON_UNIX) { pw = &fake_pw; pw->pw_name = (char *)user; pw->pw_gecos = ""; } else { /* As currently implemented, we don't avoid timing leaks for valid vs. not * usernames and hashes. Normally, the username would have already been * checked and determined valid, and the check_oldauthtok option is only needed * on systems that happen to have similar timing leaks all over the place. */ pw = getpwnam(user); endpwent(); if (!pw) return PAM_USER_UNKNOWN; if ((params.pam.flags & F_CHECK_OLDAUTHTOK) && !am_root(pamh) && (!oldpass || check_pass(pw, oldpass))) status = PAM_AUTH_ERR; memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); if (status != PAM_SUCCESS) return status; } randomonly = params.qc.min[4] > params.qc.max; if (am_root(pamh)) enforce = params.pam.flags & F_ENFORCE_ROOT; else enforce = params.pam.flags & F_ENFORCE_USERS; if (params.pam.flags & F_USE_AUTHTOK) { status = pam_get_item(pamh, PAM_AUTHTOK, &item); if (status != PAM_SUCCESS) return status; newpass = item; if (!newpass || (check_max(¶ms.qc, pamh, newpass) && enforce)) return PAM_AUTHTOK_ERR; check_reason = passwdqc_check(¶ms.qc, newpass, oldpass, pw); if (check_reason) { say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, check_reason); if (enforce) status = PAM_AUTHTOK_ERR; } return status; } retries_left = params.pam.retry; retry: retry_wanted = 0; if (!randomonly && params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH); else status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD); if (status != PAM_SUCCESS) return status; if (!randomonly && params.qc.min[0] == params.qc.min[4]) status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1CLASS, params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", params.qc.min[4]); else if (!randomonly && params.qc.min[3] == params.qc.min[4]) status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_CLASSES, params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", params.qc.min[4], params.qc.min[1] != params.qc.min[3] ? 3 : 2); else if (!randomonly && params.qc.min[3] == INT_MAX) status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES, params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", params.qc.min[4]); else if (!randomonly) status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_ALT, params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "", params.qc.min[3], params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", params.qc.min[4]); if (status != PAM_SUCCESS) return status; if (!randomonly && params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) { status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE, params.qc.passphrase_words, params.qc.min[2], params.qc.max); if (status != PAM_SUCCESS) return status; } randompass = passwdqc_random(¶ms.qc); if (randompass) { status = say(pamh, PAM_TEXT_INFO, randomonly ? MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass); if (status != PAM_SUCCESS) { pwqc_overwrite_string(randompass); pwqc_drop_mem(randompass); } } else if (randomonly) { say(pamh, PAM_ERROR_MSG, am_root(pamh) ? MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED); return PAM_AUTHTOK_ERR; } status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp); if (status == PAM_SUCCESS && (!resp || !resp->resp)) status = PAM_AUTHTOK_ERR; if (status != PAM_SUCCESS) { pwqc_overwrite_string(randompass); pwqc_drop_mem(randompass); return status; } trypass = strdup(resp->resp); pwqc_drop_pam_reply(resp, 1); if (!trypass) { pwqc_overwrite_string(randompass); pwqc_drop_mem(randompass); return PAM_AUTHTOK_ERR; } if (check_max(¶ms.qc, pamh, trypass) && enforce) { status = PAM_AUTHTOK_ERR; retry_wanted = 1; } check_reason = NULL; /* unused */ if (status == PAM_SUCCESS && (!randompass || !strstr(trypass, randompass)) && (randomonly || (check_reason = passwdqc_check(¶ms.qc, trypass, oldpass, pw)))) { if (randomonly) say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM); else say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, check_reason); if (enforce) { status = PAM_AUTHTOK_ERR; retry_wanted = 1; } } if (status == PAM_SUCCESS) status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS2, &resp); if (status == PAM_SUCCESS) { if (resp && resp->resp) { if (strcmp(trypass, resp->resp)) { status = say(pamh, PAM_ERROR_MSG, MESSAGE_MISTYPED); if (status == PAM_SUCCESS) { status = PAM_AUTHTOK_ERR; retry_wanted = 1; } } pwqc_drop_pam_reply(resp, 1); } else status = PAM_AUTHTOK_ERR; } if (status == PAM_SUCCESS) status = pam_set_item(pamh, PAM_AUTHTOK, trypass); pwqc_overwrite_string(randompass); pwqc_drop_mem(randompass); pwqc_overwrite_string(trypass); pwqc_drop_mem(trypass); if (retry_wanted && --retries_left > 0) { status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY); if (status == PAM_SUCCESS) goto retry; } return status; } #ifdef PAM_MODULE_ENTRY PAM_MODULE_ENTRY("pam_passwdqc"); #elif defined(PAM_STATIC) const struct pam_module _pam_passwdqc_modstruct = { "pam_passwdqc", NULL, NULL, NULL, NULL, NULL, pam_sm_chauthtok }; #endif passwdqc-1.3.0/passwdqc_check.c0000600000000000000000000003275312135631123015163 0ustar rootroot/* * Copyright (c) 2000-2002,2010,2013 by Solar Designer. See LICENSE. */ #include #include #include #include #include #include "passwdqc.h" #include "wordset_4k.h" #define REASON_ERROR \ "check failed" #define REASON_SAME \ "is the same as the old one" #define REASON_SIMILAR \ "is based on the old one" #define REASON_SHORT \ "too short" #define REASON_LONG \ "too long" #define REASON_SIMPLESHORT \ "not enough different characters or classes for this length" #define REASON_SIMPLE \ "not enough different characters or classes" #define REASON_PERSONAL \ "based on personal login information" #define REASON_WORD \ "based on a dictionary word and not a passphrase" #define REASON_SEQ \ "based on a common sequence of characters and not a passphrase" #define FIXED_BITS 15 typedef unsigned long fixed; /* * Calculates the expected number of different characters for a random * password of a given length. The result is rounded down. We use this * with the _requested_ minimum length (so longer passwords don't have * to meet this strict requirement for their length). */ static int expected_different(int charset, int length) { fixed x, y, z; x = ((fixed)(charset - 1) << FIXED_BITS) / charset; y = x; while (--length > 0) y = (y * x) >> FIXED_BITS; z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y); return (int)(z >> FIXED_BITS); } /* * A password is too simple if it is too short for its class, or doesn't * contain enough different characters for its class, or doesn't contain * enough words for a passphrase. * * The biases are added to the length, and they may be positive or negative. * The passphrase length check uses passphrase_bias instead of bias so that * zero may be passed for this parameter when the (other) bias is non-zero * because of a dictionary word, which is perfectly normal for a passphrase. * The biases do not affect the number of different characters, character * classes, and word count. */ static int is_simple(const passwdqc_params_qc_t *params, const char *newpass, int bias, int passphrase_bias) { int length, classes, words, chars; int digits, lowers, uppers, others, unknowns; int c, p; length = classes = words = chars = 0; digits = lowers = uppers = others = unknowns = 0; p = ' '; while ((c = (unsigned char)newpass[length])) { length++; if (!isascii(c)) unknowns++; else if (isdigit(c)) digits++; else if (islower(c)) lowers++; else if (isupper(c)) uppers++; else others++; /* A word starts when a letter follows a non-letter or when a non-ASCII * character follows a space character. We treat all non-ASCII characters * as non-spaces, which is not entirely correct (there's the non-breaking * space character at 0xa0, 0x9a, or 0xff), but it should not hurt. */ if (isascii(p)) { if (isascii(c)) { if (isalpha(c) && !isalpha(p)) words++; } else if (isspace(p)) words++; } p = c; /* Count this character just once: when we're not going to see it anymore */ if (!strchr(&newpass[length], c)) chars++; } if (!length) return 1; /* Upper case characters and digits used in common ways don't increase the * strength of a password */ c = (unsigned char)newpass[0]; if (uppers && isascii(c) && isupper(c)) uppers--; c = (unsigned char)newpass[length - 1]; if (digits && isascii(c) && isdigit(c)) digits--; /* Count the number of different character classes we've seen. We assume * that there are no non-ASCII characters for digits. */ classes = 0; if (digits) classes++; if (lowers) classes++; if (uppers) classes++; if (others) classes++; if (unknowns && classes <= 1 && (!classes || digits || words >= 2)) classes++; for (; classes > 0; classes--) switch (classes) { case 1: if (length + bias >= params->min[0] && chars >= expected_different(10, params->min[0]) - 1) return 0; return 1; case 2: if (length + bias >= params->min[1] && chars >= expected_different(36, params->min[1]) - 1) return 0; if (!params->passphrase_words || words < params->passphrase_words) continue; if (length + passphrase_bias >= params->min[2] && chars >= expected_different(27, params->min[2]) - 1) return 0; continue; case 3: if (length + bias >= params->min[3] && chars >= expected_different(62, params->min[3]) - 1) return 0; continue; case 4: if (length + bias >= params->min[4] && chars >= expected_different(95, params->min[4]) - 1) return 0; continue; } return 1; } static char *unify(char *dst, const char *src) { const char *sptr; char *dptr; int c; if (!dst && !(dst = malloc(strlen(src) + 1))) return NULL; sptr = src; dptr = dst; do { c = (unsigned char)*sptr; if (isascii(c) && isupper(c)) c = tolower(c); switch (c) { case 'a': case '@': c = '4'; break; case 'e': c = '3'; break; /* Unfortunately, if we translate both 'i' and 'l' to '1', this would * associate these two letters with each other - e.g., "mile" would * match "MLLE", which is undesired. To solve this, we'd need to test * different translations separately, which is not implemented yet. */ case 'i': case '|': c = '!'; break; case 'l': c = '1'; break; case 'o': c = '0'; break; case 's': case '$': c = '5'; break; case 't': case '+': c = '7'; break; } *dptr++ = c; } while (*sptr++); return dst; } static char *reverse(const char *src) { const char *sptr; char *dst, *dptr; if (!(dst = malloc(strlen(src) + 1))) return NULL; sptr = &src[strlen(src)]; dptr = dst; while (sptr > src) *dptr++ = *--sptr; *dptr = '\0'; return dst; } static void clean(char *dst) { if (dst) { memset(dst, 0, strlen(dst)); free(dst); } } /* * Needle is based on haystack if both contain a long enough common * substring and needle would be too simple for a password with the * substring either removed with partial length credit for it added * or partially discounted for the purpose of the length check. */ static int is_based(const passwdqc_params_qc_t *params, const char *haystack, const char *needle, const char *original, int mode) { char *scratch; int length; int i, j; const char *p; int worst_bias; if (!params->match_length) /* disabled */ return 0; if (params->match_length < 0) /* misconfigured */ return 1; scratch = NULL; worst_bias = 0; length = strlen(needle); for (i = 0; i <= length - params->match_length; i++) for (j = params->match_length; i + j <= length; j++) { int bias = 0, j1 = j - 1; const char q0 = needle[i], *q1 = &needle[i + 1]; for (p = haystack; *p; p++) if (*p == q0 && !strncmp(p + 1, q1, j1)) { /* or memcmp() */ if ((mode & 0xff) == 0) { /* remove & credit */ if (!scratch) { if (!(scratch = malloc(length + 1))) return 1; } /* remove j chars */ { int pos = length - (i + j); if (!(mode & 0x100)) /* not reversed */ pos = i; memcpy(scratch, original, pos); memcpy(&scratch[pos], &original[pos + j], length + 1 - (pos + j)); } /* add credit for match_length - 1 chars */ bias = params->match_length - 1; if (is_simple(params, scratch, bias, bias)) { clean(scratch); return 1; } } else { /* discount */ /* Require a 1 character longer match for substrings containing leetspeak * when matching against dictionary words */ bias = -1; if ((mode & 0xff) == 1) { /* words */ int pos = i, end = i + j; if (mode & 0x100) { /* reversed */ pos = length - end; end = length - i; } for (; pos < end; pos++) if (!isalpha((int)(unsigned char) original[pos])) { if (j == params->match_length) goto next_match_length; bias = 0; break; } } /* discount j - (match_length + bias) chars */ bias += (int)params->match_length - j; /* bias <= -1 */ if (bias < worst_bias) { if (is_simple(params, original, bias, (mode & 0xff) == 1 ? 0 : bias)) return 1; worst_bias = bias; } } } /* Zero bias implies that there were no matches for this length. If so, * there's no reason to try the next substring length (it would result in * no matches as well). We break out of the substring length loop and * proceed with all substring lengths for the next position in needle. */ if (!bias) break; next_match_length: ; } clean(scratch); return 0; } /* * Common sequences of characters. * We don't need to list any of the entire strings in reverse order because the * code checks the new password in both "unified" and "unified and reversed" * form against these strings (unifying them first indeed). We also don't have * to include common repeats of characters (e.g., "777", "!!!", "1000") because * these are often taken care of by the requirement on the number of different * characters. */ const char * const seq[] = { "0123456789", "`1234567890-=", "~!@#$%^&*()_+", "abcdefghijklmnopqrstuvwxyz", "a1b2c3d4e5f6g7h8i9j0", "1a2b3c4d5e6f7g8h9i0j", "abc123", "qwertyuiop[]\\asdfghjkl;'zxcvbnm,./", "qwertyuiop{}|asdfghjkl:\"zxcvbnm<>?", "qwertyuiopasdfghjklzxcvbnm", "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\", "!qaz@wsx#edc$rfv%tgb^yhn&ujm*ik<(ol>)p:?_{\"+}|", "qazwsxedcrfvtgbyhnujmikolp", "1q2w3e4r5t6y7u8i9o0p-[=]", "q1w2e3r4t5y6u7i8o9p0[-]=\\", "1qaz1qaz", "1qaz!qaz", /* can't unify '1' and '!' - see comment in unify() */ "1qazzaq1", "zaq!1qaz", "zaq!2wsx" }; /* * This wordlist check is now the least important given the checks above * and the support for passphrases (which are based on dictionary words, * and checked by other means). It is still useful to trap simple short * passwords (if short passwords are allowed) that are word-based, but * passed the other checks due to uncommon capitalization, digits, and * special characters. We (mis)use the same set of words that are used * to generate random passwords. This list is much smaller than those * used for password crackers, and it doesn't contain common passwords * that aren't short English words. Perhaps support for large wordlists * should still be added, even though this is now of little importance. */ static const char *is_word_based(const passwdqc_params_qc_t *params, const char *needle, const char *original, int is_reversed) { char word[WORDSET_4K_LENGTH_MAX + 1]; char *unified; unsigned int i; int length; int mode; if (!params->match_length) /* disabled */ return NULL; mode = is_reversed | 1; word[WORDSET_4K_LENGTH_MAX] = '\0'; for (i = 0; i < 0x1000; i++) { memcpy(word, _passwdqc_wordset_4k[i], WORDSET_4K_LENGTH_MAX); length = strlen(word); if (length < params->match_length) continue; if (i < 0xfff && !memcmp(word, _passwdqc_wordset_4k[i + 1], length)) continue; unify(word, word); if (is_based(params, word, needle, original, mode)) return REASON_WORD; } mode = is_reversed | 2; for (i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) { unified = unify(NULL, seq[i]); if (!unified) return REASON_ERROR; if (is_based(params, unified, needle, original, mode)) { free(unified); return REASON_SEQ; } free(unified); } if (params->match_length <= 4) for (i = 1900; i <= 2039; i++) { sprintf(word, "%u", i); if (is_based(params, word, needle, original, mode)) return REASON_SEQ; } return NULL; } const char *passwdqc_check(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw) { char truncated[9]; char *u_newpass, *u_reversed; char *u_oldpass; char *u_name, *u_gecos, *u_dir; const char *reason; int length; u_newpass = u_reversed = NULL; u_oldpass = NULL; u_name = u_gecos = u_dir = NULL; reason = REASON_ERROR; if (oldpass && !strcmp(oldpass, newpass)) { reason = REASON_SAME; goto out; } length = strlen(newpass); if (length < params->min[4]) { reason = REASON_SHORT; goto out; } if (length > params->max) { if (params->max == 8) { truncated[0] = '\0'; strncat(truncated, newpass, 8); newpass = truncated; if (oldpass && !strncmp(oldpass, newpass, 8)) { reason = REASON_SAME; goto out; } } else { reason = REASON_LONG; goto out; } } if (is_simple(params, newpass, 0, 0)) { reason = REASON_SIMPLE; if (length < params->min[1] && params->min[1] <= params->max) reason = REASON_SIMPLESHORT; goto out; } if (!(u_newpass = unify(NULL, newpass))) goto out; /* REASON_ERROR */ if (!(u_reversed = reverse(u_newpass))) goto out; if (oldpass && !(u_oldpass = unify(NULL, oldpass))) goto out; if (pw) { if (!(u_name = unify(NULL, pw->pw_name)) || !(u_gecos = unify(NULL, pw->pw_gecos)) || !(u_dir = unify(NULL, pw->pw_dir))) goto out; } if (oldpass && params->similar_deny && (is_based(params, u_oldpass, u_newpass, newpass, 0) || is_based(params, u_oldpass, u_reversed, newpass, 0x100))) { reason = REASON_SIMILAR; goto out; } if (pw && (is_based(params, u_name, u_newpass, newpass, 0) || is_based(params, u_name, u_reversed, newpass, 0x100) || is_based(params, u_gecos, u_newpass, newpass, 0) || is_based(params, u_gecos, u_reversed, newpass, 0x100) || is_based(params, u_dir, u_newpass, newpass, 0) || is_based(params, u_dir, u_reversed, newpass, 0x100))) { reason = REASON_PERSONAL; goto out; } reason = is_word_based(params, u_newpass, newpass, 0); if (!reason) reason = is_word_based(params, u_reversed, newpass, 0x100); out: memset(truncated, 0, sizeof(truncated)); clean(u_newpass); clean(u_reversed); clean(u_oldpass); clean(u_name); clean(u_gecos); clean(u_dir); return reason; } passwdqc-1.3.0/concat.h0000600000000000000000000000025011260240003013427 0ustar rootroot/* * Copyright (c) 2009 by Dmitry V. Levin. See LICENSE. */ #ifndef CONCAT_H__ #define CONCAT_H__ extern char *concat(const char *, ...); #endif /* CONCAT_H__ */ passwdqc-1.3.0/passwdqc.spec0000600000000000000000000003237312135636516014547 0ustar rootroot# $Owl: Owl/packages/passwdqc/passwdqc/passwdqc.spec,v 1.63 2013/04/24 02:02:54 solar Exp $ Summary: A password/passphrase strength checking and policy enforcement toolset. Name: passwdqc Version: 1.3.0 Release: owl1 License: BSD-compatible Group: System Environment/Base URL: http://www.openwall.com/passwdqc/ Source: http://www.openwall.com/passwdqc/%name-%version.tar.gz Provides: pam_passwdqc = %version-%release Obsoletes: pam_passwdqc < %version-%release BuildRequires: pam-devel BuildRoot: /override/%name-%version %description passwdqc is a password/passphrase strength checking and policy enforcement toolset, including a PAM module (pam_passwdqc), command-line programs (pwqcheck and pwqgen), and a library (libpasswdqc). pam_passwdqc is normally invoked on password changes by programs such as passwd(1). It is capable of checking password or passphrase strength, enforcing a policy, and offering randomly-generated passphrases, with all of these features being optional and easily (re-)configurable. pwqcheck and pwqgen are standalone password/passphrase strength checking and random passphrase generator programs, respectively, which are usable from scripts. libpasswdqc is the underlying library, which may also be used from third-party programs. %package devel Summary: Libraries and header files for building passwdqc-aware applications. Group: Development/Libraries Requires: %name = %version-%release %description devel This package contains development libraries and header files needed for building passwdqc-aware applications. %prep %setup -q %{expand:%%define optflags_lib %{?optflags_lib:%optflags_lib}%{!?optflags_lib:%optflags}} %build %__make \ CFLAGS_lib="-Wall -W -fPIC -DLINUX_PAM %optflags_lib" \ CFLAGS_bin="-Wall -W %optflags" %install rm -rf %buildroot %__make install DESTDIR=%buildroot MANDIR=%_mandir \ SHARED_LIBDIR=/%_lib DEVEL_LIBDIR=%_libdir \ SECUREDIR=/%_lib/security %post -p /sbin/ldconfig %postun -p /sbin/ldconfig %files %defattr(-,root,root) %doc LICENSE README pwqcheck.php %config(noreplace) /etc/passwdqc.conf /%_lib/lib*.so* %_bindir/* /%_lib/security/pam_passwdqc.so %_mandir/man*/* %files devel %defattr(-,root,root) %_includedir/*.h %_libdir/lib*.so %changelog * Wed Apr 24 2013 Solar Designer 1.3.0-owl1 - When checking is_simple() after discounting a common character sequence, apply the (negative) bias even for the passphrase length check. Previously, we were not doing this because passphrases are normally built from words, and the same code was being used for the check for dictionary words. - Expanded the list of common character sequences. Along with the change above, this reduces the number of passing passwords for RockYou top 100k from 35 to 18, and for RockYou top 1M from 2333 to 2273 (all of these are with passwdqc's default policy). - Moved the common character sequences check to be made after the dictionary words check, to avoid introducing more cases of misreporting. - Added pwqcheck.php, a PHP wrapper function around the pwqcheck program. * Tue Apr 23 2013 Solar Designer 1.2.4-owl1 - In randomly generated passphrases: toggle case of the first character of each word only if we wouldn't achieve sufficient entropy otherwise, use a trailing separator if we achieve sufficient entropy even with the final word omitted (in fact, we now enable the use of different separators in more cases for this reason), use dashes rather than spaces to separate words when different separator characters are not in use. - Expanded the allowed size of randomly-generated passphrases in bits (now it's 24 to 85 in the tools, and 24 to 136 in the passwdqc_random() interface). * Wed Aug 15 2012 Solar Designer 1.2.3-owl1 - Handle possible NULL returns from crypt(). - Declared all pre-initialized arrays and structs as const. - Added Darwin (Mac OS X) support to the Makefile, loosely based on a patch by Ronald Ip (thanks!) * Tue Jun 22 2010 Solar Designer 1.2.2-owl1 - Introduced the GNU'ish "uninstall" make target name (a synonym for "remove"). - Makefile updates to make the "install" and "uninstall" targets with their default settings friendlier to Solaris systems. - Added a link to a wiki page with detailed Solaris-specific instructions to the PLATFORMS file. * Sat Mar 27 2010 Solar Designer 1.2.1-owl1 - When matching against the reversed new password, always pass the original non-reversed new password (possibly with a substring removed) into is_simple(), but remove or check the correct substring in is_based() considering that the matching is possibly being done against the reversed password. * Tue Mar 16 2010 Solar Designer 1.2.0-owl1 - New command-line options for pwqcheck: -1 and -2 for reading just 1 and just 2 lines from stdin, respectively (instead of reading 3 lines, which is the default), --multi for checking multiple passphrases at once (until EOF). - With randomly-generated passphrases, encode more entropy per separator character (by increasing the number of different separators from 8 to 16) and per word (by altering the case of the first letter of each word), which increases the default generated passphrase size from 42 to 47 bits. - Substring matching has been enhanced to partially discount rather than fully remove weak substrings, support leetspeak, and detect some common sequences of characters (sequential digits, letters in alphabetical order, adjacent keys on a QWERTY keyboard). - Detect and allow passphrases with non-ASCII characters in the words. - A number of optimizations have been made resulting in significant speedup of passwdqc_check() on real-world passwords. - Don't require %%optflags_lib such that the package can be built with "rpmbuild -tb" on the tarball on non-Owl. * Fri Oct 30 2009 Dmitry V. Levin 1.1.4-owl1 - Added const qualifier to all arguments of passwdqc_check() and passwdqc_random(). - Implemented pwqcheck's stdin check for too long lines. - Applied markup corrections to passwdqc.conf(5) and pwqcheck(1) for better portability (by Kevin Steves and Jason McIntyre, with minor changes made by Solar Designer). - Changed use of mdoc's .Os macro to be consistent with other Openwall Project's software (by Solar Designer). * Wed Oct 21 2009 Dmitry V. Levin 1.1.3-owl1 - Eliminated insufficiently portable EXIT_FAILURE and EXIT_SUCCESS macros. - In passwdqc_load.c, replaced redundant snprintf(3) with plain sprintf(3). - Added pw_dir checks to passwdqc_check(), similar to already existing pw_gecos checks. - Dropped undocumented support for multiple options per config file line. - Switched to a heavily cut-down BSD license. - Added ldconfig calls to %%post and %%postun scripts. * Sat Oct 17 2009 Solar Designer 1.1.2-owl1 - In pwqcheck.c, replaced the uses of strsep(), which were insufficiently portable, with code based on strchr(). - Corrected the linker invocations for Solaris (tested on Solaris 10) and likely for HP-UX (untested). We broke this between 1.0.5 and 1.1.0. - Split the CFLAGS into two, separate for libraries (libpasswdqc, pam_passwdqc) and binaries (the pwq* programs). - In the Makefile, set umask 022 on mkdir's invoked by "make install". * Thu Oct 15 2009 Dmitry V. Levin 1.1.1-owl1 - Relaxed license of pwqgen and pwqcheck manual pages. - Ensure that pwqgen's exit status is zero only if generated passphrase has been printed successfully. - Changed pwqcheck to print "OK" line on success. - Changed pwqcheck to print "Weak passphrase" diagnostics to stdout instead of stderr. * Sat Oct 10 2009 Solar Designer 1.1.0-owl1 - Export passwdqc_params_load in libpasswdqc. - Minor English grammar corrections to messages produced by pam_passwdqc. - Minor documentation edits. - Added/adjusted copyright statements and attributions to reflect Dmitry's recent changes. * Mon Sep 28 2009 Dmitry V. Levin unreleased - Introduced libpasswdqc shared library. - Implemented pwqgen and pwqcheck utilities. - Implemented config= parameter support in libpasswdqc. - Packaged /etc/passwdqc.conf file with default configuration. - Added passwdqc.conf(5) manual page. * Tue Feb 12 2008 Solar Designer 1.0.5-owl1 - Replaced the separator characters with some of those defined by RFC 3986 as being safe within "userinfo" part of URLs without encoding. - Reduced the default value for the N2 parameter to min=... (the minimum length for passphrases) from 12 to 11. - Corrected the potentially misleading description of N2 (Debian bug #310595). - Applied minor grammar and style corrections to the documentation, a pam_passwdqc message, and source code comments. * Tue Apr 04 2006 Dmitry V. Levin 1.0.4-owl1 - Changed Makefile to pass list of libraries to linker after regular object files, to fix build with -Wl,--as-needed. - Corrected specfile to make it build on x86_64. * Wed Aug 17 2005 Dmitry V. Levin 1.0.3-owl1 - Fixed potential memory leak in conversation wrapper. - Restricted list of global symbols exported by the PAM module to standard set of six pam_sm_* functions. * Wed May 18 2005 Solar Designer 1.0.2-owl1 - Fixed compiler warnings seen on FreeBSD 5.3. - Updated the Makefile to not require editing on FreeBSD. - Updated the FreeBSD-specific notes in PLATFORMS. * Sun Mar 27 2005 Solar Designer 1.0.1-owl1 - Further compiler warning fixes on LP64 platforms. * Fri Mar 25 2005 Solar Designer 1.0-owl1 - Corrected the source code to not break C strict aliasing rules. * Wed Jan 26 2005 Solar Designer 0.7.6-owl1 - Disallow unreasonable random= settings. - Clarified the allowable bit sizes for randomly-generated passphrases and the lack of relationship between passphrase= and random= options. * Fri Oct 31 2003 Solar Designer 0.7.5-owl1 - Assume invocation by root only if both the UID is 0 and the PAM service name is "passwd"; this should solve changing expired passwords on Solaris and HP-UX and make "enforce=users" safe. - Produce proper English explanations for a wider variety of settings. - Moved the "-c" out of CFLAGS, renamed FAKEROOT to DESTDIR. * Sat Jun 21 2003 Solar Designer 0.7.4-owl1 - Documented that "enforce=users" may not always work for services other than the passwd command. - Applied a patch to PLATFORMS from Mike Gerdts of GE Medical Systems to reflect how Solaris 8 patch 108993-18 (or 108994-18 on x86) changes Solaris 8's PAM implementation to look like Solaris 9. * Mon Jun 02 2003 Solar Designer 0.7.3.1-owl1 - Added URL. * Thu Oct 31 2002 Solar Designer 0.7.3-owl1 - When compiling with gcc, also link with gcc. - Use $(MAKE) to invoke sub-makes. * Fri Oct 04 2002 Solar Designer - Solaris 9 notes in PLATFORMS. * Wed Sep 18 2002 Solar Designer - Build with Sun's C compiler cleanly, from Kevin Steves. - Use install -c as that actually makes a difference on at least HP-UX (otherwise install would possibly move files and not change the owner). * Fri Sep 13 2002 Solar Designer - Have the same pam_passwdqc binary work for both trusted and non-trusted HP-UX, from Kevin Steves. * Fri Sep 06 2002 Solar Designer - Use bigcrypt() on HP-UX whenever necessary, from Kevin Steves of Atomic Gears LLC. - Moved the old password checking into a separate function. * Wed Jul 31 2002 Solar Designer - Call it 0.6. * Sat Jul 27 2002 Solar Designer - Documented that the man page is under the 3-clause BSD-style license. - HP-UX 11 support. * Tue Jul 23 2002 Solar Designer - Applied minor corrections to the man page and at the same time eliminated unneeded/unimportant differences between it and the README. * Sun Jul 21 2002 Solar Designer - 0.5.1: imported the pam_passwdqc(8) manual page back from FreeBSD. * Tue Apr 16 2002 Solar Designer - 0.5: preliminary OpenPAM (FreeBSD-current) support in the code and related code cleanups (thanks to Dag-Erling Smorgrav). * Thu Feb 07 2002 Michail Litvak - Enforce our new spec file conventions. * Sun Nov 04 2001 Solar Designer - Updated to 0.4: - Added "ask_oldauthtok" and "check_oldauthtok" as needed for stacking with the Solaris pam_unix; - Permit for stacking of more than one instance of this module (no statics). * Tue Feb 13 2001 Solar Designer - Install the module as mode 755. * Tue Dec 19 2000 Solar Designer - Added "-Wall -fPIC" to the CFLAGS. * Mon Oct 30 2000 Solar Designer - 0.3: portability fixes (this might build on non-Linux-PAM now). * Fri Sep 22 2000 Solar Designer - 0.2: added "use_authtok", added README. * Fri Aug 18 2000 Solar Designer - 0.1, "retry_wanted" bugfix. * Sun Jul 02 2000 Solar Designer - Initial version (non-public). passwdqc-1.3.0/passwdqc.conf0000600000000000000000000000014311347000766014524 0ustar rootrootmin=disabled,24,11,8,7 max=40 passphrase=3 match=4 similar=deny random=47 enforce=everyone retry=3 passwdqc-1.3.0/pam_macros.h0000600000000000000000000000152311263741115014322 0ustar rootroot/* * These macros are partially based on Linux-PAM's , * which were organized by Cristian Gafton and I believe are in the public * domain. * * - Solar Designer */ #ifndef PAM_PASSWDQC_MACROS_H__ #define PAM_PASSWDQC_MACROS_H__ #include #include #define pwqc_overwrite_string(x) \ do { \ if (x) \ memset((x), 0, strlen(x)); \ } while (0) #define pwqc_drop_mem(x) \ do { \ if (x) { \ free(x); \ (x) = NULL; \ } \ } while (0) #define pwqc_drop_pam_reply(/* struct pam_response* */ reply, /* int */ replies) \ do { \ if (reply) { \ int reply_i; \ \ for (reply_i = 0; reply_i < (replies); ++reply_i) { \ pwqc_overwrite_string((reply)[reply_i].resp); \ pwqc_drop_mem((reply)[reply_i].resp); \ } \ pwqc_drop_mem(reply); \ } \ } while (0) #endif /* PAM_PASSWDQC_MACROS_H__ */ passwdqc-1.3.0/PLATFORMS0000600000000000000000000000426511410241054013317 0ustar rootrootPlease see the README for instructions common to all platforms and descriptions of the options mentioned here. Linux. Most modern Linux distributions use Linux-PAM with a password changing module which understands "use_authtok". Thus, you may choose which module prompts for the old password, things should work either way. FreeBSD 5+, DragonFly BSD 2.2+. FreeBSD 5 and newer, as well as DragonFly BSD 2.2 and newer, include pam_passwdqc in the base system. You should be able to use either the included or the distributed separately version of pam_passwdqc with these systems. There's a commented out usage example in the default /etc/pam.d/passwd. FreeBSD 4 and older used a cut down version of Linux-PAM (not OpenPAM) and didn't use PAM for password changing. OpenBSD. OpenBSD does not use PAM, however it is able to use passwdqc's pwqcheck program. Insert the line ":passwordcheck=/usr/bin/pwqcheck -1:\" into the "default" section in /etc/login.conf. Solaris, HP-UX 11. On Solaris 2.6, 7, and 8 (without patch 108993-18/108994-18 or later) and on HP-UX 11, pam_passwdqc has to ask for the old password during the update phase. Use "ask_oldauthtok=update check_oldauthtok" with pam_passwdqc and "use_first_pass" with pam_unix. On Solaris 8 (with patch 108993-18/108994-18 or later), 9, and 10, use pam_passwdqc instead of both pam_authtok_get and pam_authtok_check, and set "retry=1" with pam_passwdqc as the passwd command has its own handling for that. You will likely also need to set "max=8" in order to actually enforce not-so-weak passwords with the obsolete traditional DES-based hashes that most Solaris systems use and the flawed approach HP-UX uses to process characters past 8. Of course this way you only get about one third of the functionality of pam_passwdqc. As a better alternative, on modern Solaris systems you may edit the "CRYPT_DEFAULT=__unix__" line in /etc/security/policy.conf to read "CRYPT_DEFAULT=2a" to enable the OpenBSD-style bcrypt (Blowfish-based) password hashing. There's a wiki page with detailed instructions specific to Solaris: http://openwall.info/wiki/passwdqc/solaris $Owl: Owl/packages/passwdqc/passwdqc/PLATFORMS,v 1.15 2010/06/22 23:07:24 solar Exp $ passwdqc-1.3.0/pam_passwdqc.80000600000000000000000000000731211346633002014602 0ustar rootroot.\" Copyright (c) 2001 Networks Associates Technology, Inc. .\" All rights reserved. .\" Copyright (c) 2009 Dmitry V. Levin .\" All rights reserved. .\" Copyright (c) 2009 Solar Designer .\" All rights reserved. .\" .\" Portions of this software were developed for the FreeBSD Project by .\" ThinkSec AS and NAI Labs, the Security Research Division of Network .\" Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 .\" ("CBOSS"), as part of the DARPA CHATS research program. .\" .\" 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, this list of conditions and the following disclaimer. .\" 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. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. .\" .\" $FreeBSD: src/lib/libpam/modules/pam_passwdqc/pam_passwdqc.8,v 1.4 2002/05/30 14:49:57 ru Exp $ .\" $Owl: Owl/packages/passwdqc/passwdqc/pam_passwdqc.8,v 1.15 2010/03/13 06:51:46 solar Exp $ .\" .Dd March 13, 2010 .Dt PAM_PASSWDQC 8 .Os "Openwall Project" .Sh NAME .Nm pam_passwdqc .Nd Password quality-control PAM module .Sh SYNOPSIS .Op Ar service-name .Ar module-type .Ar control-flag .Pa pam_passwdqc .Op Ar options .Sh DESCRIPTION The .Nm module is a simple password strength checking module for PAM. In addition to checking regular passwords, it offers support for passphrases and can provide randomly generated ones. .Pp The .Nm module provides functionality for only one PAM management group: password changing. In terms of the .Ar module-type parameter, this is the .Dq Li password feature. .Pp The .Fn pam_chauthtok service function may ask the user for a new password, and verify that it meets certain minimum standards. If the chosen password is unsatisfactory, the service function returns .Dv PAM_AUTHTOK_ERR . .Pp The set of options that may be passed to the module is exactly the same as the set of options that may be specified in the .Pa /etc/passwdqc.conf file. These options are described in .Xr passwdqc.conf 5 . .Sh SEE ALSO .Xr pam.conf 5 , .Xr passwdqc.conf 5 , .Xr pam 8 . .Pp http://www.openwall.com/passwdqc/ .Sh AUTHORS The .Nm module was written for Openwall GNU/*/Linux by .An Solar Designer Aq solar at openwall.com . This manual page was written for the .Fx Project by ThinkSec AS and NAI Labs, the Security Research Division of Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 .Pq Dq CBOSS , as part of the DARPA CHATS research program. It has since been revised, most importantly to refer to .Xr passwdqc.conf 5 instead of describing the options right on this page. passwdqc-1.3.0/pwqcheck.php0000600000000000000000000000475412135636006014360 0ustar rootroot array('pipe', 'r'), 1 => array('pipe', 'w')); // Leave stderr (fd 2) pointing to where it is, likely to error_log // Replace characters that would violate the protocol $newpass = strtr($newpass, "\n", '.'); $oldpass = strtr($oldpass, "\n", '.'); $user = strtr($user, "\n:", '..'); // Trigger a "too short" rather than "is the same" message in this special case if (!$newpass && !$oldpass) $oldpass = '.'; if ($args) $args = ' ' . $args; if (!$user) $args = ' -2' . $args; // passwdqc 1.2.0+ $command = 'exec '; // No need to keep the shell process around on Unix $command .= 'pwqcheck' . $args; if (!($process = @proc_open($command, $descriptorspec, $pipes))) return $retval; $err = 0; fwrite($pipes[0], "$newpass\n$oldpass\n") || $err = 1; if ($user) fwrite($pipes[0], "$user::::$aux:/:\n") || $err = 1; fclose($pipes[0]) || $err = 1; ($output = stream_get_contents($pipes[1])) || $err = 1; fclose($pipes[1]); $status = proc_close($process); // There must be a linefeed character at the end. Remove it. if (substr($output, -1) === "\n") $output = substr($output, 0, -1); else $err = 1; if ($err === 0 && ($status === 0 || $output !== 'OK')) $retval = $output; return $retval; } ?> passwdqc-1.3.0/pwqgen.c0000600000000000000000000000303011271450316013467 0ustar rootroot/* * Copyright (c) 2008,2009 by Dmitry V. Levin. See LICENSE. */ #include #include #include #include "passwdqc.h" static void print_help(void) { puts("Generate quality controllable passphrase.\n" "\nUsage: pwqgen [options]\n" "\nValid options are:\n" " random=N\n" " set size of randomly-generated passphrase in bits;\n" " config=FILE\n" " load config FILE in passwdqc.conf format;\n" " --version\n" " print program version and exit;\n" " -h or --help\n" " print this help text and exit."); } int main(int argc, const char **argv) { passwdqc_params_t params; char *reason, *pass; if (argc > 1 && argv[1][0] == '-') { if (!strcmp("-h", argv[1]) || !strcmp("--help", argv[1])) { print_help(); return 0; } if (!strcmp("--version", argv[1])) { printf("pwqgen version %s\n", PASSWDQC_VERSION); return 0; } } passwdqc_params_reset(¶ms); if (argc > 1 && passwdqc_params_parse(¶ms, &reason, argc - 1, argv + 1)) { fprintf(stderr, "pwqgen: %s\n", (reason ? reason : "Out of memory")); free(reason); return 1; } pass = passwdqc_random(¶ms.qc); if (!pass) { fprintf(stderr, "pwqgen: Failed to generate a passphrase.\n" "This could happen for a number of reasons: you could have requested\n" "an impossible passphrase length, or the access to kernel random number\n" "pool could have failed.\n"); return 1; } return (puts(pass) >= 0 && fflush(stdout) >= 0) ? 0 : 1; } passwdqc-1.3.0/passwdqc_random.c0000600000000000000000000001403012135512006015350 0ustar rootroot/* * Copyright (c) 2000-2002,2005,2008,2010,2013 by Solar Designer. See LICENSE. */ #include #include #include #include #include #include "passwdqc.h" #include "wordset_4k.h" /* * We separate words in the generated "passphrases" with random special * characters out of a set of 16 (so we encode 4 bits per separator * character). To enable the use of our "passphrases" within FTP URLs * (and similar), we pick characters that are defined by RFC 3986 as * being safe within "userinfo" part of URLs without encoding and * without having a special meaning. Out of those, we avoid characters * that are visually ambiguous or difficult over the phone. This * happens to leave us with exactly 8 symbols, and we add 8 digits that * are not visually ambiguous. Unfortunately, the exclamation mark * might be confused for the digit 1 (which we don't use), though. */ #define SEPARATORS "-_!$&*+=23456789" /* * Number of bits encoded per separator character. */ #define SEPARATOR_BITS 4 /* * Number of bits encoded per word. We use 4096 words, which gives 12 bits, * and we toggle the case of the first character, which gives one bit more. */ #define WORD_BITS 13 /* * Number of bits encoded per separator and word. */ #define SWORD_BITS \ (SEPARATOR_BITS + WORD_BITS) /* * Maximum number of words to use. */ #define WORDS_MAX 8 /* * Minimum and maximum number of bits to encode. With the settings above, * these are 24 and 136, respectively. */ #define BITS_MIN \ (2 * (WORD_BITS - 1)) #define BITS_MAX \ (WORDS_MAX * SWORD_BITS) static int read_loop(int fd, unsigned char *buffer, int count) { int offset, block; offset = 0; while (count > 0) { block = read(fd, &buffer[offset], count); if (block < 0) { if (errno == EINTR) continue; return block; } if (!block) return offset; offset += block; count -= block; } return offset; } char *passwdqc_random(const passwdqc_params_qc_t *params) { char output[0x100], *retval; int bits; int word_count, trailing_separator, use_separators, toggle_case; int i; unsigned int max_length, length, extra; const char *start, *end; int fd; unsigned char bytes[3]; bits = params->random_bits; if (bits < BITS_MIN || bits > BITS_MAX) return NULL; /* * Calculate the number of words to use. The first word is always present * (hence the "1 +" and the "- WORD_BITS"). Each one of the following words, * if any, is prefixed by a separator character, so we use SWORD_BITS when * calculating how many additional words to use. We divide "bits - WORD_BITS" * by SWORD_BITS with rounding up (hence the addition of "SWORD_BITS - 1"). */ word_count = 1 + (bits + (SWORD_BITS - 1 - WORD_BITS)) / SWORD_BITS; /* * Special case: would we still encode enough bits if we omit the final word, * but keep the would-be-trailing separator? */ trailing_separator = (SWORD_BITS * (word_count - 1) >= bits); word_count -= trailing_separator; /* * To determine whether we need to use different separator characters or maybe * not, calculate the number of words we'd need to use if we don't use * different separators. We calculate it by dividing "bits" by WORD_BITS with * rounding up (hence the addition of "WORD_BITS - 1"). The resulting number * is either the same as or greater than word_count. Use different separators * only if their use, in the word_count calculation above, has helped reduce * word_count. */ use_separators = ((bits + (WORD_BITS - 1)) / WORD_BITS != word_count); trailing_separator &= use_separators; /* * Toggle case of the first character of each word only if we wouldn't achieve * sufficient entropy otherwise. */ toggle_case = (bits > ((WORD_BITS - 1) * word_count) + (use_separators ? (SEPARATOR_BITS * (word_count - !trailing_separator)) : 0)); /* * Calculate and check the maximum possible length of a "passphrase" we may * generate for a given word_count. We add 1 to WORDSET_4K_LENGTH_MAX to * account for separators (whether different or not). When there's no * trailing separator, we subtract 1. The check against sizeof(output) uses * ">=" to account for NUL termination. */ max_length = word_count * (WORDSET_4K_LENGTH_MAX + 1) - !trailing_separator; if (max_length >= sizeof(output) || (int)max_length > params->max) return NULL; if ((fd = open("/dev/urandom", O_RDONLY)) < 0) return NULL; retval = NULL; length = 0; do { if (read_loop(fd, bytes, sizeof(bytes)) != sizeof(bytes)) goto out; /* * Append a word. Treating bytes as little-endian, we use bits 0 to 11 for the * word index, and bit 13 for toggling the case of the first character. Bits * 12, 14, and 15 are left unused. Bits 16 to 23 are left for the separator. */ i = (((int)bytes[1] & 0x0f) << 8) | (int)bytes[0]; start = _passwdqc_wordset_4k[i]; end = memchr(start, '\0', WORDSET_4K_LENGTH_MAX); if (!end) end = start + WORDSET_4K_LENGTH_MAX; extra = end - start; /* The ">=" leaves room for either one more separator or NUL */ if (length + extra >= sizeof(output)) goto out; memcpy(&output[length], start, extra); if (toggle_case) { /* Toggle case if bit set (we assume ASCII) */ output[length] ^= bytes[1] & 0x20; bits--; } length += extra; bits -= WORD_BITS - 1; if (bits <= 0) break; /* * Append a separator character. We use bits 16 to 19. Bits 20 to 23 are left * unused. * * Special case: we may happen to leave a trailing separator if it provides * enough bits on its own. With WORD_BITS 13 and SEPARATOR_BITS 4, this * happens e.g. for bits values from 31 to 34, 48 to 51, 65 to 68. */ if (use_separators) { i = bytes[2] & 0x0f; output[length++] = SEPARATORS[i]; bits -= SEPARATOR_BITS; } else output[length++] = SEPARATORS[0]; } while (bits > 0); /* * Since we may have added a separator after the check in the loop above, we * must check again now. */ if (length < sizeof(output)) { output[length] = '\0'; retval = strdup(output); } out: memset(bytes, 0, sizeof(bytes)); memset(output, 0, length); close(fd); return retval; } passwdqc-1.3.0/passwdqc.h0000600000000000000000000000265112135634446014040 0ustar rootroot/* * Copyright (c) 2000-2002 by Solar Designer * Copyright (c) 2008,2009 by Dmitry V. Levin * See LICENSE */ #ifndef PASSWDQC_H__ #define PASSWDQC_H__ #include typedef struct { int min[5], max; int passphrase_words; int match_length; int similar_deny; int random_bits; } passwdqc_params_qc_t; typedef struct { int flags; int retry; } passwdqc_params_pam_t; typedef struct { passwdqc_params_qc_t qc; passwdqc_params_pam_t pam; } passwdqc_params_t; extern const char *passwdqc_check(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw); extern char *passwdqc_random(const passwdqc_params_qc_t *params); extern int passwdqc_params_parse(passwdqc_params_t *params, char **reason, int argc, const char *const *argv); extern int passwdqc_params_load(passwdqc_params_t *params, char **reason, const char *pathname); extern void passwdqc_params_reset(passwdqc_params_t *params); #define F_ENFORCE_MASK 0x00000003 #define F_ENFORCE_USERS 0x00000001 #define F_ENFORCE_ROOT 0x00000002 #define F_ENFORCE_EVERYONE F_ENFORCE_MASK #define F_NON_UNIX 0x00000004 #define F_ASK_OLDAUTHTOK_MASK 0x00000030 #define F_ASK_OLDAUTHTOK_PRELIM 0x00000010 #define F_ASK_OLDAUTHTOK_UPDATE 0x00000020 #define F_CHECK_OLDAUTHTOK 0x00000040 #define F_USE_FIRST_PASS 0x00000100 #define F_USE_AUTHTOK 0x00000200 #define PASSWDQC_VERSION "1.3.0" #endif /* PASSWDQC_H__ */ passwdqc-1.3.0/INTERNALS0000600000000000000000000000017111263732064013313 0ustar rootrootThe functions defined in passwdqc.h may be used without PAM at all, and all of them are in fact exported by libpasswdqc. passwdqc-1.3.0/README0000600000000000000000000001445212135513457012722 0ustar rootrootpam_passwdqc is a simple password strength checking module for PAM-aware password changing programs, such as passwd(1). In addition to checking regular passwords, it offers support for passphrases and can provide randomly generated ones. All features are optional and can be (re-)configured without rebuilding. This module should be stacked before your usual password changing module (such as pam_unix or pam_pwdb) in the password management group (the "password" lines in /etc/pam.d/passwd or /etc/pam.conf). The password changing module should then be told to use the provided new authentication token (new password) rather than request it from the user. There's usually the "use_authtok" option to do that. If your password changing module lacks the "use_authtok" option or its prompts are inconsistent with pam_passwdqc's, you may tell pam_passwdqc to ask for the old password as well, with "ask_oldauthtok". In that case the option to use with the password changing module is "use_first_pass". There are a number of supported options, which can be used to modify the behavior of pam_passwdqc (defaults are given in square brackets): config=FILE [] Load the specified configuration FILE, which must be in the passwdqc.conf format (described in the passwdqc.conf(5) manual page). This file may define any options described in here, including load of yet another configuration file, but loops are not allowed. min=N0,N1,N2,N3,N4 [min=disabled,24,11,8,7] The minimum allowed password lengths for different kinds of passwords and passphrases. The keyword "disabled" can be used to disallow passwords of a given kind regardless of their length. Each subsequent number is required to be no larger than the preceding one. N0 is used for passwords consisting of characters from one character class only. The character classes are: digits, lower-case letters, upper-case letters, and other characters. There is also a special class for non-ASCII characters, which could not be classified, but are assumed to be non-digits. N1 is used for passwords consisting of characters from two character classes that do not meet the requirements for a passphrase. N2 is used for passphrases. Note that besides meeting this length requirement, a passphrase must also consist of a sufficient number of words (see the "passphrase" option below). N3 and N4 are used for passwords consisting of characters from three and four character classes, respectively. When calculating the number of character classes, upper-case letters used as the first character and digits used as the last character of a password are not counted. In addition to being sufficiently long, passwords are required to contain enough different characters for the character classes and the minimum length they have been checked against. max=N [max=40] The maximum allowed password length. This can be used to prevent users from setting passwords that may be too long for some system services. The value 8 is treated specially: with max=8, passwords longer than 8 characters will not be rejected, but will be truncated to 8 characters for the strength checks and the user will be warned. This is to be used with the traditional DES-based password hashes, which truncate the password at 8 characters. It is important that you do set max=8 if you are using the traditional hashes, or some weak passwords will pass the checks. passphrase=N [passphrase=3] The number of words required for a passphrase, or 0 to disable the support for user-chosen passphrases. match=N [match=4] The length of common substring required to conclude that a password is at least partially based on information found in a character string, or 0 to disable the substring search. Note that the password will not be rejected once a weak substring is found; it will instead be subjected to the usual strength requirements with the weak substring partially discounted. The substring search is case-insensitive and is able to detect and remove a common substring spelled backwards. similar=permit|deny [similar=deny] Whether a new password is allowed to be similar to the old one. The passwords are considered to be similar when there is a sufficiently long common substring and the new password with the substring partially discounted would be weak. random=N[,only] [random=47] The size of randomly-generated passphrases in bits (24 to 85), or 0 to disable this feature. Any passphrase that contains the offered randomly-generated string will be allowed regardless of other possible restrictions. The "only" modifier can be used to disallow user-chosen passwords. enforce=none|users|everyone [enforce=everyone] The module can be configured to warn of weak passwords only, but not actually enforce strong passwords. The "users" setting will enforce strong passwords for invocations by non-root users only. non-unix [] Normally, the module uses getpwnam(3) to obtain the user's personal login information and use that during the password strength checks. This behavior can be disabled with the "non-unix" option. retry=N [retry=3] The number of times the module will ask for a new password if the user fails to provide a sufficiently strong password and enter it twice the first time. ask_oldauthtok[=update] [] Ask for the old password as well. Normally, pam_passwdqc leaves this task for subsequent modules. With no argument, the "ask_oldauthtok" option will cause pam_passwdqc to ask for the old password during the preliminary check phase. With "ask_oldauthtok=update", pam_passwdqc will do that during the update phase. check_oldauthtok [] This tells pam_passwdqc to validate the old password before giving a new password prompt. Normally, this task is left for subsequent modules. The primary use for this option is when "ask_oldauthtok=update" is also specified, in which case no other module gets a chance to ask for and validate the password. Of course, this will only work with Unix passwords. use_first_pass [] use_authtok [] Use the new password obtained by modules stacked before pam_passwdqc. This disables user interaction within pam_passwdqc. With this module, the only difference between "use_first_pass" and "use_authtok" is that the former is incompatible with "ask_oldauthtok". -- Solar Designer $Owl: Owl/packages/passwdqc/passwdqc/README,v 1.16 2013/04/23 14:14:07 solar Exp $ passwdqc-1.3.0/INSTALL0000600000000000000000000000412012135636516013064 0ustar rootrootMany Linux distributions, FreeBSD 5.0+, and DragonFly BSD 2.2+ include pam_passwdqc or the full-blown passwdqc package in the distribution or provide it as a "native" package to be installed - so you may just use that. The instructions below apply if your distribution lacks passwdqc or if you prefer to build and install passwdqc on your own (such as to get a newer version of it than one available in/for the distribution). On a system with the PAM (Pluggable Authentication Modules) framework, you may build all components of passwdqc (the library, the PAM module, and two command-line programs) by simply running "make". To install, run "make install". To uninstall, run "make uninstall". On a system without PAM, you may build everything but the PAM module with "make utils". To install, run "make install_lib install_utils". To uninstall, run "make remove_lib remove_utils". Please note that currently passwdqc's default is to install right into system directories such as /etc, /lib, /usr/lib, /usr/include, /usr/share/man, /usr/bin. If desired, these pathnames may be overridden on make's command-line (please see Makefile for the available macro names and passwdqc.spec for some examples). Since passwdqc installs a new shared library, you may need to run the ldconfig(8) program to update the dynamic linker cache. Alternatively, on a Red Hat'ish Linux system and under an account configured to build RPM packages (perhaps with ~/.rpmmacros specifying the proper pathnames for %_topdir, %_tmppath, and %buildroot), you may build RPM packages by running "rpmbuild -tb passwdqc-1.3.0.tar.gz", then install the two binary subpackages with "rpm -Uvh passwdqc*-1.3.0*.rpm". This works due to the RPM spec file included in the tarball. Please refer to README and PLATFORMS for information on configuring your system to use the PAM module. You may also refer to the pam_passwdqc(8) and passwdqc.conf(5) manual pages. Please refer to the pwqcheck(1) and pwqgen(1) manual pages for information on using the command-line programs. $Owl: Owl/packages/passwdqc/passwdqc/INSTALL,v 1.8 2013/04/24 02:02:54 solar Exp $ passwdqc-1.3.0/Makefile0000600000000000000000000001502312014013011013446 0ustar rootroot# # Copyright (c) 2000-2003,2005,2009,2010 by Solar Designer # Copyright (c) 2008,2009 by Dmitry V. Levin # See LICENSE # TITLE = pam_passwdqc SHARED_LIB = libpasswdqc.so.0 DEVEL_LIB = libpasswdqc.so SHARED_LIB_DARWIN = libpasswdqc.0.dylib DEVEL_LIB_DARWIN = libpasswdqc.dylib MAP_LIB = libpasswdqc.map PAM_SO_SUFFIX = SHARED_PAM = $(TITLE).so$(PAM_SO_SUFFIX) MAP_PAM = pam_passwdqc.map SHLIBMODE = 755 HEADER = passwdqc.h INCMODE = 644 MAN1 = pwqgen.1 pwqcheck.1 MAN5 = passwdqc.conf.5 MAN8 = $(TITLE).8 MANMODE = 644 BINDIR = /usr/bin BINMODE = 755 CONFDIR = /etc CONFMODE = 644 SHARED_LIBDIR = /lib SHARED_LIBDIR_SUN = /usr/lib SHARED_LIBDIR_REL = ../..$(SHARED_LIBDIR) DEVEL_LIBDIR = /usr/lib SECUREDIR = /lib/security SECUREDIR_SUN = /usr/lib/security SECUREDIR_DARWIN = /usr/lib/pam INCLUDEDIR = /usr/include MANDIR = /usr/share/man DESTDIR = CC = gcc LD = $(CC) LD_lib = $(LD) RM = rm -f LN_s = ln -s -f MKDIR = umask 022 && mkdir -p INSTALL = install -c # We support Sun's older /usr/ucb/install, but not the newer /usr/sbin/install. INSTALL_SUN = /usr/ucb/install -c CFLAGS = -Wall -W -O2 CFLAGS_lib = $(CFLAGS) -fPIC CFLAGS_bin = $(CFLAGS) -fomit-frame-pointer LDFLAGS = LDFLAGS_shared = --shared LDFLAGS_shared_LINUX = --shared LDFLAGS_shared_SUN = -G LDFLAGS_shared_HP = -b LDFLAGS_lib = $(LDFLAGS_shared) LDFLAGS_lib_LINUX = $(LDFLAGS_shared_LINUX) \ -Wl,--soname,$(SHARED_LIB),--version-script,$(MAP_LIB) LDFLAGS_lib_SUN = $(LDFLAGS_shared_SUN) LDFLAGS_lib_HP = $(LDFLAGS_shared_HP) LDFLAGS_pam = $(LDFLAGS_shared) LDFLAGS_pam_LINUX = $(LDFLAGS_shared_LINUX) \ -Wl,--version-script,$(MAP_PAM) LDFLAGS_pam_SUN = $(LDFLAGS_shared_SUN) LDFLAGS_pam_HP = $(LDFLAGS_shared_HP) LDLIBS_lib = LDLIBS_pam = -lpam -lcrypt LDLIBS_pam_LINUX = -lpam -lcrypt LDLIBS_pam_SUN = -lpam -lcrypt LDLIBS_pam_HP = -lpam -lsec LDLIBS_pam_DARWIN = -lpam -lSystem # Uncomment this to use cc instead of gcc #CC = cc # Uncomment this to use Sun's C compiler flags #CFLAGS = -xO2 #CFLAGS_lib = $(CFLAGS) -KPIC #CFLAGS_bin = $(CFLAGS) # Uncomment this to use HP's ANSI C compiler flags #CFLAGS = -Ae +w1 +W 474,486,542 +O2 #CFLAGS_lib = $(CFLAGS) +z #CFLAGS_bin = $(CFLAGS) CONFIGS = passwdqc.conf BINS = pwqgen pwqcheck PROJ = $(SHARED_LIB) $(DEVEL_LIB) $(SHARED_PAM) $(BINS) OBJS_LIB = concat.o passwdqc_check.o passwdqc_load.o passwdqc_parse.o passwdqc_random.o wordset_4k.o OBJS_PAM = pam_passwdqc.o OBJS_GEN = pwqgen.o OBJS_CHECK = pwqcheck.o default: all all pam utils install install_lib install_pam install_utils uninstall remove remove_lib remove_pam remove_utils: case "`uname -s`" in \ Linux) $(MAKE) CFLAGS_lib="$(CFLAGS_lib) -DHAVE_SHADOW" \ LDFLAGS_lib="$(LDFLAGS_lib_LINUX)" \ LDFLAGS_pam="$(LDFLAGS_pam_LINUX)" \ LDLIBS_pam="$(LDLIBS_pam_LINUX)" \ $@_wrapped;; \ SunOS) $(MAKE) -e CFLAGS_lib="$(CFLAGS_lib) -DHAVE_SHADOW" \ LD_lib=ld \ LDFLAGS_lib="$(LDFLAGS_lib_SUN)" \ LDFLAGS_pam="$(LDFLAGS_pam_SUN)" \ LDLIBS_pam="$(LDLIBS_pam_SUN)" \ INSTALL="$(INSTALL_SUN)" \ SHARED_LIBDIR="$(SHARED_LIBDIR_SUN)" \ SECUREDIR="$(SECUREDIR_SUN)" \ $@_wrapped;; \ HP-UX) $(MAKE) CFLAGS_lib="$(CFLAGS_lib) -DHAVE_SHADOW" \ LD_lib=ld \ LDFLAGS_lib="$(LDFLAGS_lib_HP)" \ LDFLAGS_pam="$(LDFLAGS_pam_HP)" \ LDLIBS_pam="$(LDLIBS_pam_HP)" \ $@_wrapped;; \ Darwin) $(MAKE) \ SHARED_LIB="$(SHARED_LIB_DARWIN)" \ DEVEL_LIB="$(DEVEL_LIB_DARWIN)" \ SECUREDIR="$(SECUREDIR_DARWIN)" \ LDLIBS_pam="$(LDLIBS_pam_DARWIN)" \ $@_wrapped;; \ *) $(MAKE) $@_wrapped;; \ esac all_wrapped: pam_wrapped utils_wrapped pam_wrapped: $(SHARED_PAM) utils_wrapped: $(BINS) $(SHARED_LIB): $(OBJS_LIB) $(MAP_LIB) $(LD_lib) $(LDFLAGS_lib) $(OBJS_LIB) $(LDLIBS_lib) -o $(SHARED_LIB) $(DEVEL_LIB): $(SHARED_LIB) $(LN_s) $(SHARED_LIB) $(DEVEL_LIB) $(SHARED_PAM): $(OBJS_PAM) $(MAP_PAM) $(DEVEL_LIB) $(LD_lib) $(LDFLAGS_pam) $(OBJS_PAM) $(LDLIBS_pam) -L. -lpasswdqc -o $(SHARED_PAM) pwqgen: $(OBJS_GEN) $(DEVEL_LIB) $(LD) $(LDFLAGS) $(OBJS_GEN) -L. -lpasswdqc -o $@ pwqcheck: $(OBJS_CHECK) $(DEVEL_LIB) $(LD) $(LDFLAGS) $(OBJS_CHECK) -L. -lpasswdqc -o $@ pwqgen.o: pwqgen.c passwdqc.h $(CC) $(CFLAGS_bin) -c $*.c pwqcheck.o: pwqcheck.c passwdqc.h $(CC) $(CFLAGS_bin) -c $*.c .c.o: $(CC) $(CFLAGS_lib) -c $*.c concat.o: concat.h pam_passwdqc.o: passwdqc.h pam_macros.h passwdqc_check.o: passwdqc.h wordset_4k.h passwdqc_load.o: passwdqc.h concat.h passwdqc_parse.o: passwdqc.h concat.h passwdqc_random.o: passwdqc.h wordset_4k.h wordset_4k.o: wordset_4k.h install_wrapped: install_lib_wrapped install_utils_wrapped install_pam_wrapped @echo 'Consider running ldconfig(8) to update the dynamic linker cache.' install_lib_wrapped: $(MKDIR) $(DESTDIR)$(CONFDIR) $(INSTALL) -m $(CONFMODE) $(CONFIGS) $(DESTDIR)$(CONFDIR)/ $(MKDIR) $(DESTDIR)$(SHARED_LIBDIR) $(INSTALL) -m $(SHLIBMODE) $(SHARED_LIB) $(DESTDIR)$(SHARED_LIBDIR)/ $(MKDIR) $(DESTDIR)$(DEVEL_LIBDIR) $(LN_s) $(SHARED_LIBDIR_REL)/$(SHARED_LIB) \ $(DESTDIR)$(DEVEL_LIBDIR)/$(DEVEL_LIB) $(MKDIR) $(DESTDIR)$(INCLUDEDIR) $(INSTALL) -m $(INCMODE) $(HEADER) $(DESTDIR)$(INCLUDEDIR)/ $(MKDIR) $(DESTDIR)$(MANDIR)/man5 $(INSTALL) -m $(MANMODE) $(MAN5) $(DESTDIR)$(MANDIR)/man5/ install_utils_wrapped: $(MKDIR) $(DESTDIR)$(BINDIR) $(INSTALL) -m $(BINMODE) $(BINS) $(DESTDIR)$(BINDIR)/ $(MKDIR) $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m $(MANMODE) $(MAN1) $(DESTDIR)$(MANDIR)/man1/ install_pam_wrapped: $(MKDIR) $(DESTDIR)$(SECUREDIR) $(INSTALL) -m $(SHLIBMODE) $(SHARED_PAM) $(DESTDIR)$(SECUREDIR)/ $(MKDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m $(MANMODE) $(MAN8) $(DESTDIR)$(MANDIR)/man8/ uninstall_wrapped remove_wrapped: remove_pam_wrapped remove_utils_wrapped remove_lib_wrapped remove_pam_wrapped: $(RM) $(DESTDIR)$(MANDIR)/man8/$(MAN8) $(RM) $(DESTDIR)$(SECUREDIR)/$(SHARED_PAM) remove_utils_wrapped: for f in $(MAN1); do $(RM) $(DESTDIR)$(MANDIR)/man1/$$f; done for f in $(BINS); do $(RM) $(DESTDIR)$(BINDIR)/$$f; done remove_lib_wrapped: for f in $(MAN5); do $(RM) $(DESTDIR)$(MANDIR)/man5/$$f; done for f in $(HEADER); do $(RM) $(DESTDIR)$(INCLUDEDIR)/$$f; done for f in $(DEVEL_LIB); do $(RM) $(DESTDIR)$(DEVEL_LIBDIR)/$$f; done for f in $(SHARED_LIB); do $(RM) $(DESTDIR)$(SHARED_LIBDIR)/$$f; done for f in $(CONFIGS); do $(RM) $(DESTDIR)$(CONFDIR)/$$f; done clean: $(RM) $(PROJ) *.o .PHONY: all all_wrapped clean install install_lib install_pam install_utils \ pam pam_wrapped uninstall remove remove_lib remove_pam remove_utils \ utils utils_wrapped \ install_wrapped install_lib_wrapped install_pam_wrapped \ install_utils_wrapped \ remove_wrapped remove_lib_wrapped remove_pam_wrapped \ remove_utils_wrapped passwdqc-1.3.0/pwqcheck.c0000600000000000000000000001162411347326575014020 0ustar rootroot/* * Copyright (c) 2008,2009 by Dmitry V. Levin * Copyright (c) 2010 by Solar Designer * See LICENSE */ #include #include #include #include "passwdqc.h" static void clean(char *dst, int size) { if (!dst) return; memset(dst, 0, size); free(dst); } static char *read_line(unsigned int size, int eof_ok) { char *p, *buf = malloc(size + 1); if (!buf) { fprintf(stderr, "pwqcheck: Memory allocation failed.\n"); return NULL; } if (!fgets(buf, size + 1, stdin)) { clean(buf, size + 1); if (!eof_ok || !feof(stdin) || ferror(stdin)) fprintf(stderr, "pwqcheck: Error reading standard input.\n"); return NULL; } if (strlen(buf) >= size) { clean(buf, size + 1); fprintf(stderr, "pwqcheck: Line too long.\n"); return NULL; } if ((p = strpbrk(buf, "\r\n"))) *p = '\0'; return buf; } static char *extract_string(char **stringp) { char *token = *stringp, *colon; if (!token) return ""; colon = strchr(token, ':'); if (colon) { *colon = '\0'; *stringp = colon + 1; } else *stringp = NULL; return token; } static struct passwd *parse_pwline(char *line, struct passwd *pw) { if (!strchr(line, ':')) { struct passwd *p = getpwnam(line); endpwent(); if (!p) { fprintf(stderr, "pwqcheck: User not found.\n"); return NULL; } if (p->pw_passwd) memset(p->pw_passwd, 0, strlen(p->pw_passwd)); memcpy(pw, p, sizeof(*pw)); } else { memset(pw, 0, sizeof(*pw)); pw->pw_name = extract_string(&line); pw->pw_passwd = extract_string(&line); extract_string(&line); /* uid */ extract_string(&line); /* gid */ pw->pw_gecos = extract_string(&line); pw->pw_dir = extract_string(&line); pw->pw_shell = line ? line : ""; if (!*pw->pw_name || !*pw->pw_dir) { fprintf(stderr, "pwqcheck: Invalid passwd entry.\n"); return NULL; } } return pw; } static void print_help(void) { puts("Check passphrase quality.\n" "\nFor each passphrase to check, pwqcheck reads up to 3 lines from standard input:\n" " first line is a new passphrase,\n" " second line is an old passphrase, and\n" " third line is either an existing account name or a passwd entry.\n" "\nUsage: pwqcheck [options]\n" "\nValid options are:\n" " min=N0,N1,N2,N3,N4\n" " set minimum allowed lengths for different kinds of passphrases;\n" " max=N\n" " set maximum allowed passphrase length;\n" " passphrase=N\n" " set number of words required for a passphrase;\n" " match=N\n" " set length of common substring in substring check;\n" " config=FILE\n" " load config FILE in passwdqc.conf format;\n" " -1\n" " read just 1 line (new passphrase);\n" " -2\n" " read just 2 lines (new and old passphrases);\n" " --multi\n" " check multiple passphrases (until EOF);\n" " --version\n" " print program version and exit;\n" " -h or --help\n" " print this help text and exit."); } int main(int argc, const char **argv) { passwdqc_params_t params; const char *check_reason; char *parse_reason, *newpass, *oldpass, *pwline; struct passwd pwbuf, *pw; int lines_to_read = 3, multi = 0; int size = 8192; int rc = 1; while (argc > 1 && argv[1][0] == '-') { const char *arg = argv[1]; if (!strcmp("-h", arg) || !strcmp("--help", arg)) { print_help(); return 0; } if (!strcmp("--version", arg)) { printf("pwqcheck version %s\n", PASSWDQC_VERSION); return 0; } if ((arg[1] == '1' || arg[1] == '2') && !arg[2]) { lines_to_read = arg[1] - '0'; goto next_arg; } if (!strcmp("--multi", arg)) { multi = 1; goto next_arg; } break; next_arg: argc--; argv++; } passwdqc_params_reset(¶ms); if (argc > 1 && passwdqc_params_parse(¶ms, &parse_reason, argc - 1, argv + 1)) { fprintf(stderr, "pwqcheck: %s\n", (parse_reason ? parse_reason : "Out of memory")); free(parse_reason); return rc; } if (params.qc.max + 1 > size) size = params.qc.max + 1; next_pass: oldpass = pwline = NULL; pw = NULL; if (!(newpass = read_line(size, multi))) { if (multi && feof(stdin) && !ferror(stdin) && fflush(stdout) >= 0) rc = 0; goto done; } if (lines_to_read >= 2 && !(oldpass = read_line(size, 0))) goto done; if (lines_to_read >= 3 && (!(pwline = read_line(size, 0)) || !parse_pwline(pwline, pw = &pwbuf))) goto done; check_reason = passwdqc_check(¶ms.qc, newpass, oldpass, pw); if (!check_reason) { if (multi) printf("OK: %s\n", newpass); else if (puts("OK") >= 0 && fflush(stdout) >= 0) rc = 0; goto cleanup; } if (multi) printf("Bad passphrase (%s): %s\n", check_reason, newpass); else printf("Bad passphrase (%s)\n", check_reason); cleanup: memset(&pwbuf, 0, sizeof(pwbuf)); clean(pwline, size); clean(oldpass, size); clean(newpass, size); if (multi) goto next_pass; return rc; done: multi = 0; goto cleanup; } passwdqc-1.3.0/LICENSE0000600000000000000000000000246612135636407013052 0ustar rootrootTwo manual pages (pam_passwdqc.8 and passwdqc.conf.5) are under the 3-clause BSD-style license as specified within the files themselves. concat.c, wordset_4k.c, wordset_4k.h, pam_macros.h, and pwqcheck.php are in the public domain, but at your option they may also be used under this package's license below. The rest of the files in this package fall under the following terms (heavily cut-down "BSD license"): Redistribution and use in source and binary forms, with or without modification, are permitted. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. $Owl: Owl/packages/passwdqc/passwdqc/LICENSE,v 1.8 2013/04/24 02:01:43 solar Exp $ passwdqc-1.3.0/pam_passwdqc.map0000600000000000000000000000041310336632032015203 0ustar rootroot# $Owl: Owl/packages/passwdqc/passwdqc/pam_passwdqc.map,v 1.2 2005/11/16 13:28:58 solar Exp $ { global: pam_sm_acct_mgmt; pam_sm_authenticate; pam_sm_chauthtok; pam_sm_close_session; pam_sm_open_session; pam_sm_setcred; local: *; }; passwdqc-1.3.0/libpasswdqc.map0000600000000000000000000000036611263732235015052 0ustar rootroot# $Owl: Owl/packages/passwdqc/passwdqc/libpasswdqc.map,v 1.2 2009/10/09 22:09:33 solar Exp $ { global: passwdqc_check; passwdqc_params_load; passwdqc_params_parse; passwdqc_params_reset; passwdqc_random; local: *; }; passwdqc-1.3.0/passwdqc.conf.50000600000000000000000000002203312135513457014673 0ustar rootroot.\" Copyright (c) 2000-2003,2005,2008 Solar Designer .\" All rights reserved. .\" Copyright (c) 2001 Networks Associates Technology, Inc. .\" All rights reserved. .\" Copyright (c) 2009 Dmitry V. Levin .\" All rights reserved. .\" .\" Portions of this software were developed for the FreeBSD Project by .\" ThinkSec AS and NAI Labs, the Security Research Division of Network .\" Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 .\" ("CBOSS"), as part of the DARPA CHATS research program. .\" .\" 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, this list of conditions and the following disclaimer. .\" 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. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. .\" .\" $FreeBSD: src/lib/libpam/modules/pam_passwdqc/pam_passwdqc.8,v 1.4 2002/05/30 14:49:57 ru Exp $ .\" $Owl: Owl/packages/passwdqc/passwdqc/passwdqc.conf.5,v 1.11 2013/04/23 14:14:07 solar Exp $ .\" .Dd March 13, 2010 .Dt PASSWDQC.CONF 5 .Os "Openwall Project" .Sh NAME .Nm passwdqc.conf .Nd libpasswdqc configuration file .Sh DESCRIPTION libpasswdqc is a simple password strength checking library. In addition to checking regular passwords, it offers support for passphrases and can provide randomly generated ones. A .Nm configuration file may be used to override default libpasswdqc settings. .Sh FORMAT A .Nm file consists of 0 or more lines of the following format: .Dl Ar option Ns = Ns Ar value .Pp Empty lines and lines beginning with .Dq Li # are ignored. Whitespace characters between the .Ar option , .Dq Li = , and .Ar value are not allowed. .Sh DIRECTIVE OPTIONS .Bl -tag -width indent .It Cm config Ns = Ns Ar FILE Load the specified configuration .Ar FILE in the .Cm passwdqc.conf format. This file may define any options described in this manual, including load of yet another configuration file, but loops are not allowed. .El .Sh PASSWORD QUALITY CONTROL OPTIONS .Bl -tag -width Ds .Sm off .It Xo .Cm min No = .Ar N0 , N1 , N2 , N3 , N4 .Xc .Sm on .Pq default: min=disabled,24,11,8,7 The minimum allowed password lengths for different kinds of passwords/passphrases. The keyword .Cm disabled can be used to disallow passwords of a given kind regardless of their length. Each subsequent number is required to be no larger than the preceding one. .Pp .Ar N0 is used for passwords consisting of characters from one character class only. The character classes are: digits, lower-case letters, upper-case letters, and other characters. There is also a special class for .No non- Ns Tn ASCII characters, which could not be classified, but are assumed to be non-digits. .Pp .Ar N1 is used for passwords consisting of characters from two character classes that do not meet the requirements for a passphrase. .Pp .Ar N2 is used for passphrases. Note that besides meeting this length requirement, a passphrase must also consist of a sufficient number of words (see the .Cm passphrase option below). .Pp .Ar N3 and .Ar N4 are used for passwords consisting of characters from three and four character classes, respectively. .Pp When calculating the number of character classes, upper-case letters used as the first character and digits used as the last character of a password are not counted. .Pp In addition to being sufficiently long, passwords are required to contain enough different characters for the character classes and the minimum length they have been checked against. .Pp .It Cm max Ns = Ns Ar N .Pq default: Cm max Ns = Ns 40 The maximum allowed password length. This can be used to prevent users from setting passwords that may be too long for some system services. The value 8 is treated specially: if .Cm max is set to 8, passwords longer than 8 characters will not be rejected, but will be truncated to 8 characters for the strength checks and the user will be warned. This is to be used with the traditional DES-based password hashes, which truncate the password at 8 characters. .Pp It is important that you do set .Cm max Ns = Ns 8 if you are using the traditional hashes, or some weak passwords will pass the checks. .It Cm passphrase Ns = Ns Ar N .Pq default: Cm passphrase Ns = Ns 3 The number of words required for a passphrase, or 0 to disable the support for user-chosen passphrases. .It Cm match Ns = Ns Ar N .Pq default: Cm match Ns = Ns 4 The length of common substring required to conclude that a password is at least partially based on information found in a character string, or 0 to disable the substring search. Note that the password will not be rejected once a weak substring is found; it will instead be subjected to the usual strength requirements with the weak substring partially discounted. .Pp The substring search is case-insensitive and is able to detect and remove a common substring spelled backwards. .It Xo .Sm off .Cm similar No = Cm permit | deny .Sm on .Xc .Pq default: Cm similar Ns = Ns Cm deny Whether a new password is allowed to be similar to the old one. The passwords are considered to be similar when there is a sufficiently long common substring and the new password with the substring partially discounted would be weak. .It Xo .Sm off .Cm random No = Ar N .Op , Cm only .Sm on .Xc .Pq default: Cm random Ns = Ns 47 The size of randomly-generated passphrases in bits (24 to 85), or 0 to disable this feature. Any passphrase that contains the offered randomly-generated string will be allowed regardless of other possible restrictions. .Pp The .Cm only modifier can be used to disallow user-chosen passwords. .El .Sh PAM MODULE OPTIONS .Bl -tag -width indent .It Xo .Sm off .Cm enforce No = Cm none | users | everyone .Sm on .Xc .Pq default: Cm enforce Ns = Ns Cm everyone The PAM module can be configured to warn of weak passwords only, but not actually enforce strong passwords. The .Cm users setting will enforce strong passwords for invocations by non-root users only. .It Cm non-unix Normally, the PAM module uses .Xr getpwnam 3 to obtain the user's personal login information and use that during the password strength checks. This behavior can be disabled with the .Cm non-unix option. .It Cm retry Ns = Ns Ar N .Pq default: Cm retry Ns = Ns 3 The number of times the PAM module will ask for a new password if the user fails to provide a sufficiently strong password and enter it twice the first time. .It Cm ask_oldauthtok Ns Op = Ns Cm update Ask for the old password as well. Normally, the PAM module leaves this task for subsequent modules. With no argument, the .Cm ask_oldauthtok option will cause the PAM module to ask for the old password during the preliminary check phase. If the .Cm ask_oldauthtok option is specified with the .Cm update argument, the PAM module will do that during the update phase. .It Cm check_oldauthtok This tells the PAM module to validate the old password before giving a new password prompt. Normally, this task is left for subsequent modules. .Pp The primary use for this option is when .Cm ask_oldauthtok Ns = Ns Cm update is also specified, in which case no other module gets a chance to ask for and validate the password. Of course, this will only work with .Ux passwords. .It Cm use_first_pass , use_authtok Use the new password obtained by other modules stacked before the PAM module. This disables user interaction within the PAM module. The only difference between .Cm use_first_pass and .Cm use_authtok is that the former is incompatible with .Cm ask_oldauthtok . .El .Sh FILES .Pa /etc/passwdqc.conf . .Sh SEE ALSO .Xr getpwnam 3 , .Xr pam_passwdqc 8 . .Pp http://www.openwall.com/passwdqc/ .Sh AUTHORS The pam_passwdqc module was written for Openwall GNU/*/Linux by .An Solar Designer Aq solar at openwall.com . This manual page was derived from .Xr pam_passwdqc 8 . The latter, derived from the author's documentation, was written for the .Fx Project by ThinkSec AS and NAI Labs, the Security Research Division of Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 .Pq Dq CBOSS , as part of the DARPA CHATS research program. passwdqc-1.3.0/wordset_4k.h0000600000000000000000000000042612135476375014303 0ustar rootroot/* * Written by Solar Designer and placed in the * public domain. */ #ifndef WORDSET_4K_H__ #define WORDSET_4K_H__ #define WORDSET_4K_LENGTH_MAX 6 extern const char _passwdqc_wordset_4k[0x1000][WORDSET_4K_LENGTH_MAX]; #endif /* WORDSET_4K_H__ */ passwdqc-1.3.0/concat.c0000600000000000000000000000301411260240003013423 0ustar rootroot/* * concat() - allocate memory and safely concatenate strings in portable C * (and C++ if you like). * * This code deals gracefully with potential integer overflows (perhaps when * input strings are maliciously long), as well as with input strings changing * from under it (perhaps because of misbehavior of another thread). It does * not depend on non-portable functions such as snprintf() and asprintf(). * * Written by Solar Designer and placed in the * public domain. * * Originally written for and currently maintained as a part of popa3d, * a POP3 server: * * http://www.openwall.com/popa3d/ */ #include #include #include #include #include "concat.h" char *concat(const char *s1, ...) { va_list args; const char *s; char *p, *result; unsigned long l, m, n; m = n = strlen(s1); va_start(args, s1); while ((s = va_arg(args, char *))) { l = strlen(s); if ((m += l) < l) break; } va_end(args); if (s || m >= INT_MAX) return NULL; result = (char *)malloc(m + 1); if (!result) return NULL; memcpy(p = result, s1, n); p += n; va_start(args, s1); while ((s = va_arg(args, char *))) { l = strlen(s); if ((n += l) < l || n > m) break; memcpy(p, s, l); p += l; } va_end(args); if (s || m != n || p != result + n) { free(result); return NULL; } *p = 0; return result; } #ifdef TEST #include int main(int argc, char **argv) { puts(concat(argv[0], argv[1], argv[2], argv[3], NULL)); return 0; } #endif passwdqc-1.3.0/pwqcheck.10000600000000000000000000001534611347332317013732 0ustar rootroot.\" Copyright (c) 2009 Dmitry V. Levin .\" All rights reserved. .\" Copyright (c) 2000-2003,2005,2008,2010 Solar Designer .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. .\" .\" $Owl: Owl/packages/passwdqc/passwdqc/pwqcheck.1,v 1.15 2010/03/15 04:17:19 solar Exp $ .\" .Dd March 15, 2010 .Dt PWQCHECK 1 .Os "Openwall Project" .Sh NAME .Nm pwqcheck .Nd Check passphrase quality .Sh SYNOPSIS .Nm Op Ar options .Sh DESCRIPTION The .Nm program checks passphrase quality using the libpasswdqc library. By default, it expects to read 3 lines from standard input: .Pp .Bl -item -compact -offset indent .It first line is a new password, .It second line is an old password, and .It third line is either an existing account name or a .Xr passwd 5 entry. .El .Pp There are a number of supported options, which can be used to control the .Nm behavior. .Pp .Nm prints .Ar OK on success. Scripts invoking .Nm are suggested to check for both a zero exit status and the .Ar OK line. .Sh OPTIONS .Bl -tag -width Ds .Sm off .It Xo .Cm min No = .Ar N0 , N1 , N2 , N3 , N4 .Xc .Sm on .Pq default: min=disabled,24,11,8,7 The minimum allowed password lengths for different kinds of passwords/passphrases. The keyword .Cm disabled can be used to disallow passwords of a given kind regardless of their length. Each subsequent number is required to be no larger than the preceding one. .Pp .Ar N0 is used for passwords consisting of characters from one character class only. The character classes are: digits, lower-case letters, upper-case letters, and other characters. There is also a special class for .No non- Ns Tn ASCII characters, which could not be classified, but are assumed to be non-digits. .Pp .Ar N1 is used for passwords consisting of characters from two character classes that do not meet the requirements for a passphrase. .Pp .Ar N2 is used for passphrases. Note that besides meeting this length requirement, a passphrase must also consist of a sufficient number of words (see the .Cm passphrase option below). .Pp .Ar N3 and .Ar N4 are used for passwords consisting of characters from three and four character classes, respectively. .Pp When calculating the number of character classes, upper-case letters used as the first character and digits used as the last character of a password are not counted. .Pp In addition to being sufficiently long, passwords are required to contain enough different characters for the character classes and the minimum length they have been checked against. .Pp .It Cm max Ns = Ns Ar N .Pq default: Cm max Ns = Ns 40 The maximum allowed password length. This can be used to prevent users from setting passwords that may be too long for some system services. The value 8 is treated specially: if .Cm max is set to 8, passwords longer than 8 characters will not be rejected, but will be truncated to 8 characters for the strength checks and the user will be warned. This is to be used with the traditional DES-based password hashes, which truncate the password at 8 characters. .Pp It is important that you do set .Cm max Ns = Ns 8 if you are using the traditional hashes, or some weak passwords will pass the checks. .It Cm passphrase Ns = Ns Ar N .Pq default: Cm passphrase Ns = Ns 3 The number of words required for a passphrase. .It Cm match Ns = Ns Ar N .Pq default: Cm match Ns = Ns 4 The length of common substring required to conclude that a password is at least partially based on information found in a character string, or 0 to disable the substring search. Note that the password will not be rejected once a weak substring is found; it will instead be subjected to the usual strength requirements with the weak substring partially discounted. .Pp The substring search is case-insensitive and is able to detect and remove a common substring spelled backwards. .It Cm config Ns = Ns Ar FILE Load config .Ar FILE in the .Cm passwdqc.conf format. This file may define any options described in .Xr passwdqc.conf 5 , but only the .Cm min , .Cm max , .Cm passphrase , .Cm match Ns , and .Cm config options are honored by .Nm . .It Cm -1 Read just 1 line (new passphrase). This is needed to use .Nm as the passwordcheck program on OpenBSD - e.g., with ":passwordcheck=/usr/bin/pwqcheck -1:\\" in the "default" section in .Cm /etc/login.conf . .It Cm -2 Read just 2 lines (new and old passphrases). .It Cm --multi Check multiple passphrases (until EOF). This option may be used on its own or along with the .Cm -1 or .Cm -2 options. .Nm will read 1, 2, or 3 lines and will output one line per passphrase to check. The lines will start with either .Ar OK or a message explaining why the passphrase did not pass the checks, followed by a colon and a space, and finally followed by the passphrase. The explanatory message is guaranteed to not include a colon. With this option, the exit status of .Nm depends solely on whether there were any errors preventing the strength of passphrases from being fully checked or not. A primary use for this option is to test different policies and/or different versions of passwdqc on large passphrase lists. .It Cm --version Output .Nm program version and exit. .It Cm -h , --help Output .Nm help text and exit. .El .Sh EXIT STATUS .Nm exits with non-zero status when it encounters invalid config file, invalid option, invalid parameter value, invalid data in standard input, and in any case when it fails to check passphrase strength. Without the .Cm --multi option, .Nm also exits with non-zero status when it detects a weak passphrase. .Sh FILES .Pa /etc/passwdqc.conf . .Sh SEE ALSO .Xr pwqgen 1 , .Xr passwd 5 , .Xr passwdqc.conf 5 , .Xr pam_passwdqc 8 . .Pp http://www.openwall.com/passwdqc/ .Sh AUTHORS The pam_passwdqc module was written for Openwall GNU/*/Linux by Solar Designer. The .Nm program was originally written for ALT GNU/*/Linux by Dmitry V. Levin, indirectly reusing code from pam_passwdqc (via libpasswdqc). This manual page (derived from the pam_passwdqc documentation) was written for Openwall GNU/*/Linux by Dmitry V. Levin. passwdqc-1.3.0/wordset_4k.c0000600000000000000000000012521312135476417014275 0ustar rootroot/* * 4096 English words for generation of easy to memorize random passphrases. * This list comes from the MakePass passphrase generator developed by * Dianelos Georgoudis , which was announced on * sci.crypt on 1997/10/24. Here's a relevant excerpt from that posting: * * > The 4096 words in the word list were chosen according to the following * > criteria: * > - each word must contain between 3 and 6 characters * > - each word must be a common English word * > - each word should be clearly different from each other * > word, orthographically or semantically * > * > The MakePass word list has been placed in the public domain * * At least two other sci.crypt postings by Dianelos Georgoudis also state * that the word list is in the public domain, and so did the web page at: * * http://web.archive.org/web/%2a/http://www.tecapro.com/makepass.html * * which existed until 2006 and is available from the Wayback Machine as of * this writing (March 2010). Specifically, the web page said: * * > The MakePass word list has been placed in the public domain. To download * > a copy click here. You can use the MakePass word list for many other * > purposes. * * "To download a copy click here" was a link to free/makepass.lst, which is * currently available via the Wayback Machine: * * http://web.archive.org/web/%2a/http://www.tecapro.com/free/makepass.lst * * Even though the original description of the list stated that "each word * must contain between 3 and 6 characters", there were two 7-character words: * "England" and "Germany". For use in passwdqc, these have been replaced * with "erase" and "gag". * * The code in passwdqc_check.c and passwdqc_random.c makes the following * assumptions about this list: * * - there are exactly 4096 words; * - the words are of up to 6 characters long; * - although some words may contain capital letters, no two words differ by * the case of characters alone (e.g., converting the list to all-lowercase * would yield a list of 4096 unique words); * - the words contain alphabetical characters only; * - if an entire word on this list matches the initial substring of other * word(s) on the list, it is placed immediately before those words (e.g., * "bake", "baker", "bakery"). * * Additionally, the default minimum passphrase length of 11 characters * specified in passwdqc_parse.c has been chosen such that a passphrase * consisting of any three words from this list with two separator * characters will pass the minimum length check. In other words, this * default assumes that no word is shorter than 3 characters. */ #include "wordset_4k.h" const char _passwdqc_wordset_4k[0x1000][WORDSET_4K_LENGTH_MAX] = { "Adam", "Afghan", "Alaska", "Alice", "Allah", "Amazon", "Andrew", "Anglo", "Angola", "Antony", "April", "Arab", "Arctic", "Athens", "Austin", "Bach", "Baltic", "Basque", "Berlin", "Bible", "Bombay", "Bonn", "Boston", "Brazil", "Briton", "Buddha", "Burma", "Caesar", "Cairo", "Canada", "Carl", "Carol", "Celtic", "Chile", "China", "Christ", "Congo", "Cuba", "Cyprus", "Czech", "Dallas", "Danish", "Darwin", "David", "Delhi", "Derby", "Diana", "Dublin", "Dutch", "East", "Eden", "Edward", "Eric", "Essex", "Europe", "Eve", "Exodus", "France", "French", "Friday", "Gandhi", "Gaul", "Gemini", "Geneva", "George", "German", "Gloria", "God", "Gothic", "Greece", "Greek", "Hague", "Haiti", "Hanoi", "Harry", "Havana", "Hawaii", "Hebrew", "Henry", "Hermes", "Hindu", "Hitler", "Idaho", "Inca", "India", "Indian", "Iowa", "Iran", "Iraq", "Irish", "Isaac", "Isabel", "Islam", "Israel", "Italy", "Ivan", "Jack", "Jacob", "James", "Japan", "Java", "Jersey", "Jesus", "Jewish", "Jim", "John", "Jordan", "Joseph", "Judas", "Judy", "July", "June", "Kansas", "Karl", "Kenya", "Koran", "Korea", "Kuwait", "Laos", "Latin", "Leo", "Libya", "Lima", "Lisbon", "Liz", "London", "Louvre", "Lucy", "Luther", "Madame", "Madrid", "Malta", "Maria", "Mars", "Mary", "Maya", "Mecca", "Mexico", "Miami", "Mickey", "Milan", "Monaco", "Monday", "Moscow", "Moses", "Moslem", "Mrs", "Munich", "Muslim", "Naples", "Nazi", "Nepal", "Newark", "Nile", "Nobel", "North", "Norway", "Ohio", "Oscar", "Oslo", "Oxford", "Panama", "Paris", "Pascal", "Paul", "Peking", "Peru", "Peter", "Philip", "Poland", "Polish", "Prague", "Quebec", "Rex", "Rhine", "Ritz", "Robert", "Roman", "Rome", "Rosa", "Russia", "Sahara", "Sam", "Saturn", "Saudi", "Saxon", "Scot", "Seoul", "Somali", "Sony", "Soviet", "Spain", "Stalin", "Sudan", "Suez", "Sunday", "Sweden", "Swiss", "Sydney", "Syria", "Taiwan", "Tarzan", "Taurus", "Tehran", "Teresa", "Texas", "Thomas", "Tibet", "Tokyo", "Tom", "Turk", "Turkey", "Uganda", "Venice", "Venus", "Vienna", "Viking", "Virgo", "Warsaw", "West", "Yale", "Yemen", "York", "Zaire", "Zurich", "aback", "abbey", "abbot", "abide", "ablaze", "able", "aboard", "abode", "abort", "abound", "about", "above", "abroad", "abrupt", "absent", "absorb", "absurd", "abuse", "accent", "accept", "access", "accord", "accuse", "ace", "ache", "aching", "acid", "acidic", "acorn", "acre", "across", "act", "action", "active", "actor", "actual", "acute", "adapt", "add", "added", "addict", "adept", "adhere", "adjust", "admire", "admit", "adobe", "adopt", "adrift", "adult", "adverb", "advert", "aerial", "afar", "affair", "affect", "afford", "afield", "afloat", "afraid", "afresh", "after", "again", "age", "agency", "agenda", "agent", "aghast", "agile", "ago", "agony", "agree", "agreed", "ahead", "aid", "aide", "aim", "air", "airman", "airy", "akin", "alarm", "albeit", "album", "alert", "alibi", "alien", "alight", "align", "alike", "alive", "alkali", "all", "alley", "allied", "allow", "alloy", "ally", "almond", "almost", "aloft", "alone", "along", "aloof", "aloud", "alpha", "alpine", "also", "altar", "alter", "always", "amaze", "amber", "ambush", "amen", "amend", "amid", "amidst", "amiss", "among", "amount", "ample", "amuse", "anchor", "and", "anew", "angel", "anger", "angle", "angry", "animal", "ankle", "annoy", "annual", "answer", "anthem", "anti", "any", "anyhow", "anyway", "apart", "apathy", "apex", "apiece", "appeal", "appear", "apple", "apply", "apron", "arcade", "arcane", "arch", "ardent", "are", "area", "argue", "arid", "arise", "arm", "armful", "armpit", "army", "aroma", "around", "arouse", "array", "arrest", "arrive", "arrow", "arson", "art", "artery", "artful", "artist", "ascent", "ashen", "ashore", "aside", "ask", "asleep", "aspect", "assay", "assent", "assert", "assess", "asset", "assign", "assist", "assume", "assure", "asthma", "astute", "asylum", "ate", "atlas", "atom", "atomic", "attach", "attack", "attain", "attend", "attic", "auburn", "audio", "audit", "august", "aunt", "auntie", "aura", "author", "auto", "autumn", "avail", "avenge", "avenue", "avert", "avid", "avoid", "await", "awake", "awaken", "award", "aware", "awash", "away", "awful", "awhile", "axes", "axiom", "axis", "axle", "aye", "babe", "baby", "back", "backup", "bacon", "bad", "badge", "badly", "bag", "baggy", "bail", "bait", "bake", "baker", "bakery", "bald", "ball", "ballad", "ballet", "ballot", "bamboo", "ban", "banal", "banana", "band", "bang", "bank", "bar", "barber", "bare", "barely", "barge", "bark", "barley", "barn", "baron", "barrel", "barren", "basalt", "base", "basic", "basil", "basin", "basis", "basket", "bass", "bat", "batch", "bath", "baton", "battle", "bay", "beach", "beacon", "beak", "beam", "bean", "bear", "beard", "beast", "beat", "beauty", "become", "bed", "beech", "beef", "beefy", "beep", "beer", "beet", "beetle", "before", "beggar", "begin", "behalf", "behave", "behind", "beige", "being", "belief", "bell", "belly", "belong", "below", "belt", "bench", "bend", "benign", "bent", "berry", "berth", "beset", "beside", "best", "bestow", "bet", "beta", "betray", "better", "beware", "beyond", "bias", "biceps", "bicker", "bid", "big", "bigger", "bike", "bile", "bill", "binary", "bind", "biopsy", "birch", "bird", "birdie", "birth", "bishop", "bit", "bitch", "bite", "bitter", "black", "blade", "blame", "bland", "blast", "blaze", "bleak", "blend", "bless", "blew", "blind", "blink", "blip", "bliss", "blitz", "block", "blond", "blood", "bloody", "bloom", "blot", "blouse", "blow", "blue", "bluff", "blunt", "blur", "blush", "boar", "board", "boast", "boat", "bodily", "body", "bogus", "boil", "bold", "bolt", "bomb", "bond", "bone", "bonnet", "bonus", "bony", "book", "boom", "boost", "boot", "booth", "booze", "border", "bore", "borrow", "bosom", "boss", "both", "bother", "bottle", "bottom", "bought", "bounce", "bound", "bounty", "bout", "bovine", "bow", "bowel", "bowl", "box", "boy", "boyish", "brace", "brain", "brainy", "brake", "bran", "branch", "brand", "brandy", "brass", "brave", "bravo", "breach", "bread", "break", "breast", "breath", "bred", "breed", "breeze", "brew", "brick", "bride", "bridge", "brief", "bright", "brim", "brine", "bring", "brink", "brisk", "broad", "broke", "broken", "bronze", "brook", "broom", "brown", "bruise", "brush", "brutal", "brute", "bubble", "buck", "bucket", "buckle", "budget", "buffet", "buggy", "build", "bulb", "bulge", "bulk", "bulky", "bull", "bullet", "bully", "bump", "bumpy", "bunch", "bundle", "bunk", "bunny", "burden", "bureau", "burial", "buried", "burly", "burn", "burnt", "burrow", "burst", "bury", "bus", "bush", "bust", "bustle", "busy", "but", "butler", "butt", "butter", "button", "buy", "buyer", "buzz", "bye", "byte", "cab", "cabin", "cable", "cache", "cactus", "cage", "cake", "calf", "call", "caller", "calm", "calmly", "came", "camel", "camera", "camp", "campus", "can", "canal", "canary", "cancel", "cancer", "candid", "candle", "candy", "cane", "canine", "canoe", "canopy", "canvas", "canyon", "cap", "cape", "car", "carbon", "card", "care", "career", "caress", "cargo", "carnal", "carp", "carpet", "carrot", "carry", "cart", "cartel", "case", "cash", "cask", "cast", "castle", "casual", "cat", "catch", "cater", "cattle", "caught", "causal", "cause", "cave", "cease", "celery", "cell", "cellar", "cement", "censor", "census", "cereal", "cervix", "chain", "chair", "chalk", "chalky", "champ", "chance", "change", "chant", "chaos", "chap", "chapel", "charge", "charm", "chart", "chase", "chat", "cheap", "cheat", "check", "cheek", "cheeky", "cheer", "cheery", "cheese", "chef", "cherry", "chess", "chest", "chew", "chic", "chick", "chief", "child", "chill", "chilly", "chin", "chip", "choice", "choir", "choose", "chop", "choppy", "chord", "chorus", "chose", "chosen", "chrome", "chunk", "chunky", "church", "cider", "cigar", "cinema", "circa", "circle", "circus", "cite", "city", "civic", "civil", "clad", "claim", "clammy", "clan", "clap", "clash", "clasp", "class", "clause", "claw", "clay", "clean", "clear", "clergy", "clerk", "clever", "click", "client", "cliff", "climax", "climb", "clinch", "cling", "clinic", "clip", "cloak", "clock", "clone", "close", "closer", "closet", "cloth", "cloud", "cloudy", "clout", "clown", "club", "clue", "clumsy", "clung", "clutch", "coach", "coal", "coarse", "coast", "coat", "coax", "cobalt", "cobra", "coca", "cock", "cocoa", "code", "coffee", "coffin", "cohort", "coil", "coin", "coke", "cold", "collar", "colon", "colony", "colt", "column", "comb", "combat", "come", "comedy", "comic", "commit", "common", "compel", "comply", "concur", "cone", "confer", "consul", "convex", "convey", "convoy", "cook", "cool", "cope", "copper", "copy", "coral", "cord", "core", "cork", "corn", "corner", "corps", "corpse", "corpus", "cortex", "cosmic", "cosmos", "cost", "costly", "cosy", "cotton", "couch", "cough", "could", "count", "county", "coup", "couple", "coupon", "course", "court", "cousin", "cove", "cover", "covert", "cow", "coward", "cowboy", "crab", "crack", "cradle", "craft", "crafty", "crag", "crane", "crap", "crash", "crate", "crater", "crawl", "crazy", "creak", "cream", "creamy", "create", "credit", "creed", "creek", "creep", "creepy", "crept", "crest", "crew", "cried", "crime", "crisis", "crisp", "critic", "croft", "crook", "crop", "cross", "crow", "crowd", "crown", "crude", "cruel", "cruise", "crunch", "crush", "crust", "crux", "cry", "crypt", "cube", "cubic", "cuckoo", "cuff", "cult", "cup", "curb", "cure", "curfew", "curl", "curry", "curse", "cursor", "curve", "custom", "cut", "cute", "cycle", "cyclic", "cynic", "dad", "daddy", "dagger", "daily", "dairy", "daisy", "dale", "damage", "damn", "damp", "dampen", "dance", "danger", "dare", "dark", "darken", "dash", "data", "date", "dawn", "day", "dead", "deadly", "deaf", "deal", "dealer", "dean", "dear", "death", "debate", "debit", "debris", "debt", "debtor", "decade", "decay", "decent", "decide", "deck", "decor", "decree", "deduce", "deed", "deep", "deeply", "deer", "defeat", "defect", "defend", "defer", "define", "defy", "degree", "deity", "delay", "delete", "delta", "demand", "demise", "demo", "demon", "demure", "denial", "denote", "dense", "dental", "deny", "depart", "depend", "depict", "deploy", "depot", "depth", "deputy", "derive", "desert", "design", "desire", "desist", "desk", "detail", "detect", "deter", "detest", "detour", "device", "devil", "devise", "devoid", "devote", "devour", "dial", "diary", "dice", "dictum", "did", "die", "diesel", "diet", "differ", "digest", "digit", "dine", "dinghy", "dinner", "diode", "dire", "direct", "dirt", "dirty", "disc", "disco", "dish", "disk", "dismal", "dispel", "ditch", "dive", "divert", "divide", "divine", "dizzy", "docile", "dock", "doctor", "dog", "dogma", "dole", "doll", "dollar", "dolly", "domain", "dome", "domino", "donate", "done", "donkey", "donor", "doom", "door", "dorsal", "dose", "double", "doubt", "dough", "dour", "dove", "down", "dozen", "draft", "drag", "dragon", "drain", "drama", "drank", "draw", "drawer", "dread", "dream", "dreary", "dress", "drew", "dried", "drift", "drill", "drink", "drip", "drive", "driver", "drop", "drove", "drown", "drug", "drum", "drunk", "dry", "dual", "duck", "duct", "due", "duel", "duet", "duke", "dull", "duly", "dumb", "dummy", "dump", "dune", "dung", "duress", "during", "dusk", "dust", "dusty", "duty", "dwarf", "dwell", "dyer", "dying", "dynamo", "each", "eager", "eagle", "ear", "earl", "early", "earn", "earth", "ease", "easel", "easily", "easter", "easy", "eat", "eaten", "eater", "echo", "eddy", "edge", "edible", "edict", "edit", "editor", "eerie", "eerily", "effect", "effort", "egg", "ego", "eight", "eighth", "eighty", "either", "elbow", "elder", "eldest", "elect", "eleven", "elicit", "elite", "else", "elude", "elves", "embark", "emblem", "embryo", "emerge", "emit", "empire", "employ", "empty", "enable", "enamel", "end", "endure", "enemy", "energy", "engage", "engine", "enjoy", "enlist", "enough", "ensure", "entail", "enter", "entire", "entry", "envoy", "envy", "enzyme", "epic", "epoch", "equal", "equate", "equip", "equity", "era", "erase", "erect", "erode", "erotic", "errant", "error", "escape", "escort", "essay", "estate", "esteem", "ethic", "ethnic", "evade", "even", "event", "ever", "every", "evict", "evil", "evoke", "evolve", "exact", "exam", "exceed", "excel", "except", "excess", "excise", "excite", "excuse", "exempt", "exert", "exile", "exist", "exit", "exotic", "expand", "expect", "expert", "expire", "export", "expose", "extend", "extra", "eye", "eyed", "fabric", "face", "facial", "fact", "factor", "fade", "fail", "faint", "fair", "fairly", "fairy", "faith", "fake", "falcon", "fall", "false", "falter", "fame", "family", "famine", "famous", "fan", "fancy", "far", "farce", "fare", "farm", "farmer", "fast", "fasten", "faster", "fat", "fatal", "fate", "father", "fatty", "fault", "faulty", "fauna", "fear", "feast", "feat", "fed", "fee", "feeble", "feed", "feel", "feet", "fell", "fellow", "felt", "female", "fence", "fend", "ferry", "fetal", "fetch", "feudal", "fever", "few", "fewer", "fiance", "fiasco", "fiddle", "field", "fiend", "fierce", "fiery", "fifth", "fifty", "fig", "fight", "figure", "file", "fill", "filled", "filler", "film", "filter", "filth", "filthy", "final", "finale", "find", "fine", "finger", "finish", "finite", "fire", "firm", "firmly", "first", "fiscal", "fish", "fisher", "fist", "fit", "fitful", "five", "fix", "flag", "flair", "flak", "flame", "flank", "flap", "flare", "flash", "flask", "flat", "flaw", "fled", "flee", "fleece", "fleet", "flesh", "fleshy", "flew", "flick", "flight", "flimsy", "flint", "flirt", "float", "flock", "flood", "floor", "floppy", "flora", "floral", "flour", "flow", "flower", "fluent", "fluffy", "fluid", "flung", "flurry", "flush", "flute", "flux", "fly", "flyer", "foal", "foam", "focal", "focus", "fog", "foil", "fold", "folk", "follow", "folly", "fond", "fondly", "font", "food", "fool", "foot", "for", "forbid", "force", "ford", "forest", "forge", "forget", "fork", "form", "formal", "format", "former", "fort", "forth", "forty", "forum", "fossil", "foster", "foul", "found", "four", "fourth", "fox", "foyer", "frail", "frame", "franc", "frank", "fraud", "free", "freed", "freely", "freer", "freeze", "frenzy", "fresh", "friar", "fridge", "fried", "friend", "fright", "fringe", "frock", "frog", "from", "front", "frost", "frosty", "frown", "frozen", "frugal", "fruit", "fudge", "fuel", "fulfil", "full", "fully", "fun", "fund", "funny", "fur", "furry", "fury", "fuse", "fusion", "fuss", "fussy", "futile", "future", "fuzzy", "gadget", "gag", "gain", "gala", "galaxy", "gale", "gall", "galley", "gallon", "gallop", "gamble", "game", "gamma", "gang", "gap", "garage", "garden", "garlic", "gas", "gasp", "gate", "gather", "gauge", "gaunt", "gave", "gay", "gaze", "gear", "geese", "gender", "gene", "genial", "genius", "genre", "gentle", "gently", "gentry", "genus", "get", "ghetto", "ghost", "giant", "gift", "giggle", "gill", "gilt", "ginger", "girl", "give", "given", "glad", "glade", "glance", "gland", "glare", "glass", "glassy", "gleam", "glee", "glide", "global", "globe", "gloom", "gloomy", "glory", "gloss", "glossy", "glove", "glow", "glue", "goal", "goat", "gold", "golden", "golf", "gone", "gong", "good", "goose", "gorge", "gory", "gosh", "gospel", "gossip", "got", "govern", "gown", "grab", "grace", "grade", "grain", "grand", "grant", "grape", "graph", "grasp", "grass", "grassy", "grate", "grave", "gravel", "gravy", "gray", "grease", "greasy", "great", "greed", "greedy", "green", "greet", "grew", "grey", "grid", "grief", "grill", "grim", "grin", "grind", "grip", "grit", "gritty", "groan", "groin", "groom", "groove", "gross", "ground", "group", "grove", "grow", "grown", "growth", "grudge", "grunt", "guard", "guess", "guest", "guide", "guild", "guilt", "guilty", "guise", "guitar", "gulf", "gully", "gun", "gunman", "guru", "gut", "guy", "gypsy", "habit", "hack", "had", "hail", "hair", "hairy", "hale", "half", "hall", "halt", "hamlet", "hammer", "hand", "handle", "handy", "hang", "hangar", "happen", "happy", "harass", "hard", "harder", "hardly", "hare", "harem", "harm", "harp", "harsh", "has", "hash", "hassle", "haste", "hasten", "hasty", "hat", "hatch", "hate", "haul", "haunt", "have", "haven", "havoc", "hawk", "hazard", "haze", "hazel", "hazy", "head", "heal", "health", "heap", "hear", "heard", "heart", "hearth", "hearty", "heat", "heater", "heaven", "heavy", "heck", "hectic", "hedge", "heel", "hefty", "height", "heir", "held", "helium", "helix", "hell", "hello", "helm", "helmet", "help", "hemp", "hence", "her", "herald", "herb", "herd", "here", "hereby", "hernia", "hero", "heroic", "heroin", "hey", "heyday", "hick", "hidden", "hide", "high", "higher", "highly", "hill", "him", "hind", "hint", "hippy", "hire", "his", "hiss", "hit", "hive", "hoard", "hoarse", "hobby", "hockey", "hold", "holder", "hole", "hollow", "holly", "holy", "home", "honest", "honey", "hood", "hook", "hope", "horn", "horny", "horrid", "horror", "horse", "hose", "host", "hot", "hotel", "hound", "hour", "house", "hover", "how", "huge", "hull", "human", "humane", "humble", "humid", "hung", "hunger", "hungry", "hunt", "hurdle", "hurl", "hurry", "hurt", "hush", "hut", "hybrid", "hymn", "hyphen", "ice", "icing", "icon", "idea", "ideal", "idiom", "idiot", "idle", "idly", "idol", "ignite", "ignore", "ill", "image", "immune", "impact", "imply", "import", "impose", "incest", "inch", "income", "incur", "indeed", "index", "indoor", "induce", "inept", "inert", "infant", "infect", "infer", "influx", "inform", "inject", "injure", "injury", "inlaid", "inland", "inlet", "inmate", "inn", "innate", "inner", "input", "insane", "insect", "insert", "inset", "inside", "insist", "insult", "insure", "intact", "intake", "intend", "inter", "into", "invade", "invent", "invest", "invite", "invoke", "inward", "iron", "ironic", "irony", "island", "isle", "issue", "itch", "item", "itself", "ivory", "jacket", "jade", "jaguar", "jail", "jargon", "jaw", "jazz", "jeep", "jelly", "jerky", "jest", "jet", "jewel", "job", "jock", "jockey", "join", "joint", "joke", "jolly", "jolt", "joy", "joyful", "joyous", "judge", "juice", "juicy", "jumble", "jumbo", "jump", "jungle", "junior", "junk", "junta", "jury", "just", "karate", "keel", "keen", "keep", "keeper", "kept", "kernel", "kettle", "key", "khaki", "kick", "kid", "kidnap", "kidney", "kill", "killer", "kin", "kind", "kindly", "king", "kiss", "kite", "kitten", "knack", "knee", "knew", "knife", "knight", "knit", "knob", "knock", "knot", "know", "known", "label", "lace", "lack", "lad", "ladder", "laden", "lady", "lagoon", "laity", "lake", "lamb", "lame", "lamp", "lance", "land", "lane", "lap", "lapse", "large", "larval", "laser", "last", "latch", "late", "lately", "latent", "later", "latest", "latter", "laugh", "launch", "lava", "lavish", "law", "lawful", "lawn", "lawyer", "lay", "layer", "layman", "lazy", "lead", "leader", "leaf", "leafy", "league", "leak", "leaky", "lean", "leap", "learn", "lease", "leash", "least", "leave", "led", "ledge", "left", "leg", "legacy", "legal", "legend", "legion", "lemon", "lend", "length", "lens", "lent", "leper", "lesion", "less", "lessen", "lesser", "lesson", "lest", "let", "lethal", "letter", "level", "lever", "levy", "lewis", "liable", "liar", "libel", "lice", "lick", "lid", "lie", "lied", "life", "lift", "light", "like", "likely", "limb", "lime", "limit", "limp", "line", "linear", "linen", "linger", "link", "lion", "lip", "liquid", "liquor", "list", "listen", "lit", "live", "lively", "liver", "lizard", "load", "loaf", "loan", "lobby", "lobe", "local", "locate", "lock", "locus", "lodge", "loft", "lofty", "log", "logic", "logo", "lone", "lonely", "long", "longer", "look", "loop", "loose", "loosen", "loot", "lord", "lorry", "lose", "loss", "lost", "lot", "lotion", "lotus", "loud", "loudly", "lounge", "lousy", "love", "lovely", "lover", "low", "lower", "lowest", "loyal", "lucid", "luck", "lucky", "lull", "lump", "lumpy", "lunacy", "lunar", "lunch", "lung", "lure", "lurid", "lush", "lust", "lute", "luxury", "lying", "lymph", "lynch", "lyric", "macho", "macro", "mad", "madam", "made", "mafia", "magic", "magma", "magnet", "magnum", "maid", "maiden", "mail", "main", "mainly", "major", "make", "maker", "male", "malice", "mall", "malt", "mammal", "manage", "mane", "mania", "manic", "manner", "manor", "mantle", "manual", "manure", "many", "map", "maple", "marble", "march", "mare", "margin", "marina", "mark", "market", "marry", "marsh", "martin", "martyr", "mask", "mason", "mass", "mast", "master", "match", "mate", "matrix", "matter", "mature", "maxim", "may", "maybe", "mayor", "maze", "mead", "meadow", "meal", "mean", "meant", "meat", "medal", "media", "median", "medic", "medium", "meet", "mellow", "melody", "melon", "melt", "member", "memo", "memory", "menace", "mend", "mental", "mentor", "menu", "mercy", "mere", "merely", "merge", "merger", "merit", "merry", "mesh", "mess", "messy", "met", "metal", "meter", "method", "methyl", "metric", "metro", "mid", "midday", "middle", "midst", "midway", "might", "mighty", "mild", "mildew", "mile", "milk", "milky", "mill", "mimic", "mince", "mind", "mine", "mini", "mink", "minor", "mint", "minus", "minute", "mirror", "mirth", "misery", "miss", "mist", "misty", "mite", "mix", "moan", "moat", "mobile", "mock", "mode", "model", "modem", "modern", "modest", "modify", "module", "moist", "molar", "mole", "molten", "moment", "money", "monies", "monk", "monkey", "month", "mood", "moody", "moon", "moor", "moral", "morale", "morbid", "more", "morgue", "mortal", "mortar", "mosaic", "mosque", "moss", "most", "mostly", "moth", "mother", "motion", "motive", "motor", "mould", "mount", "mourn", "mouse", "mouth", "move", "movie", "much", "muck", "mucus", "mud", "muddle", "muddy", "mule", "mummy", "murder", "murky", "murmur", "muscle", "museum", "music", "mussel", "must", "mutant", "mute", "mutiny", "mutter", "mutton", "mutual", "muzzle", "myopic", "myriad", "myself", "mystic", "myth", "nadir", "nail", "naked", "name", "namely", "nape", "napkin", "narrow", "nasal", "nasty", "nation", "native", "nature", "nausea", "naval", "nave", "navy", "near", "nearer", "nearly", "neat", "neatly", "neck", "need", "needle", "needy", "negate", "neon", "nephew", "nerve", "nest", "neural", "never", "newly", "next", "nice", "nicely", "niche", "nickel", "niece", "night", "nimble", "nine", "ninety", "ninth", "noble", "nobody", "node", "noise", "noisy", "non", "none", "noon", "nor", "norm", "normal", "nose", "nosy", "not", "note", "notice", "notify", "notion", "nought", "noun", "novel", "novice", "now", "nozzle", "nude", "null", "numb", "number", "nurse", "nylon", "nymph", "oak", "oasis", "oath", "obese", "obey", "object", "oblige", "oboe", "obtain", "occult", "occupy", "occur", "ocean", "octave", "odd", "off", "offend", "offer", "office", "offset", "often", "oil", "oily", "okay", "old", "older", "oldest", "olive", "omega", "omen", "omit", "once", "one", "onion", "only", "onset", "onto", "onus", "onward", "opaque", "open", "openly", "opera", "opium", "oppose", "optic", "option", "oracle", "oral", "orange", "orbit", "orchid", "ordeal", "order", "organ", "orgasm", "orient", "origin", "ornate", "orphan", "other", "otter", "ought", "ounce", "our", "out", "outer", "output", "outset", "oval", "oven", "over", "overt", "owe", "owing", "owl", "own", "owner", "oxide", "oxygen", "oyster", "ozone", "pace", "pack", "packet", "pact", "paddle", "paddy", "pagan", "page", "paid", "pain", "paint", "pair", "palace", "pale", "palm", "panel", "panic", "papa", "papal", "paper", "parade", "parcel", "pardon", "parent", "parish", "park", "parody", "parrot", "part", "partly", "party", "pass", "past", "paste", "pastel", "pastor", "pastry", "pat", "patch", "patent", "path", "patio", "patrol", "patron", "pause", "pave", "pawn", "pay", "peace", "peach", "peak", "pear", "pearl", "pedal", "peel", "peer", "pelvic", "pelvis", "pen", "penal", "pence", "pencil", "penis", "penny", "people", "pepper", "per", "perch", "peril", "period", "perish", "permit", "person", "pest", "petite", "petrol", "petty", "phase", "phone", "photo", "phrase", "piano", "pick", "picket", "picnic", "pie", "piece", "pier", "pierce", "piety", "pig", "pigeon", "piggy", "pike", "pile", "pill", "pillar", "pillow", "pilot", "pin", "pinch", "pine", "pink", "pint", "pious", "pipe", "pirate", "piss", "pistol", "piston", "pit", "pitch", "pity", "pivot", "pixel", "pizza", "place", "placid", "plague", "plain", "plan", "plane", "planet", "plank", "plant", "plasma", "plate", "play", "player", "plea", "plead", "please", "pledge", "plenty", "plenum", "plight", "plot", "ploy", "plug", "plum", "plump", "plunge", "plural", "plus", "plush", "pocket", "poem", "poet", "poetic", "poetry", "point", "poison", "polar", "pole", "police", "policy", "polite", "poll", "pollen", "polo", "pond", "ponder", "pony", "pool", "poor", "poorly", "pop", "pope", "poppy", "pore", "pork", "port", "portal", "pose", "posh", "post", "postal", "pot", "potato", "potent", "pouch", "pound", "pour", "powder", "power", "praise", "pray", "prayer", "preach", "prefer", "prefix", "press", "pretty", "price", "pride", "priest", "primal", "prime", "prince", "print", "prior", "prism", "prison", "privy", "prize", "probe", "profit", "prompt", "prone", "proof", "propel", "proper", "prose", "proton", "proud", "prove", "proven", "proxy", "prune", "psalm", "pseudo", "psyche", "pub", "public", "puff", "pull", "pulp", "pulpit", "pulsar", "pulse", "pump", "punch", "punish", "punk", "pupil", "puppet", "puppy", "pure", "purely", "purge", "purify", "purple", "purse", "pursue", "push", "pushy", "pussy", "put", "putt", "puzzle", "quaint", "quake", "quarry", "quartz", "quay", "queen", "queer", "query", "quest", "queue", "quick", "quid", "quiet", "quilt", "quirk", "quit", "quite", "quiver", "quiz", "quota", "quote", "rabbit", "race", "racial", "racism", "rack", "racket", "radar", "radio", "radish", "radius", "raffle", "raft", "rage", "raid", "rail", "rain", "rainy", "raise", "rally", "ramp", "random", "range", "rank", "ransom", "rape", "rapid", "rare", "rarely", "rarity", "rash", "rat", "rate", "rather", "ratify", "ratio", "rattle", "rave", "raven", "raw", "ray", "razor", "reach", "react", "read", "reader", "ready", "real", "really", "realm", "reap", "rear", "reason", "rebel", "recall", "recent", "recess", "recipe", "reckon", "record", "recoup", "rector", "red", "redeem", "reduce", "reed", "reef", "refer", "reform", "refuge", "refuse", "regal", "regard", "regent", "regime", "region", "regret", "reign", "reject", "relate", "relax", "relay", "relic", "relief", "relish", "rely", "remain", "remark", "remedy", "remind", "remit", "remote", "remove", "renal", "render", "rent", "rental", "repair", "repeal", "repeat", "repent", "reply", "report", "rescue", "resent", "reside", "resign", "resin", "resist", "resort", "rest", "result", "resume", "retail", "retain", "retina", "retire", "return", "reveal", "review", "revise", "revive", "revolt", "reward", "rhino", "rhyme", "rhythm", "ribbon", "rice", "rich", "rick", "rid", "ride", "rider", "ridge", "rife", "rifle", "rift", "right", "rigid", "ring", "rinse", "riot", "ripe", "ripen", "ripple", "rise", "risk", "risky", "rite", "ritual", "rival", "river", "road", "roar", "roast", "rob", "robe", "robin", "robot", "robust", "rock", "rocket", "rocky", "rod", "rode", "rodent", "rogue", "role", "roll", "roof", "room", "root", "rope", "rose", "rosy", "rotate", "rotor", "rotten", "rouge", "rough", "round", "route", "rover", "row", "royal", "rubble", "ruby", "rudder", "rude", "rugby", "ruin", "rule", "ruler", "rumble", "rump", "run", "rune", "rung", "runway", "rural", "rush", "rust", "rustic", "rusty", "sack", "sacred", "sad", "saddle", "sadism", "sadly", "safari", "safe", "safely", "safer", "safety", "saga", "sage", "said", "sail", "sailor", "saint", "sake", "salad", "salary", "sale", "saline", "saliva", "salmon", "saloon", "salt", "salty", "salute", "same", "sample", "sand", "sandy", "sane", "sash", "satan", "satin", "satire", "sauce", "sauna", "savage", "save", "say", "scale", "scalp", "scan", "scant", "scar", "scarce", "scare", "scarf", "scary", "scene", "scenic", "scent", "school", "scope", "score", "scorn", "scotch", "scout", "scrap", "scream", "screen", "screw", "script", "scroll", "scrub", "scum", "sea", "seal", "seam", "seaman", "search", "season", "seat", "second", "secret", "sect", "sector", "secure", "see", "seed", "seeing", "seek", "seem", "seize", "seldom", "select", "self", "sell", "seller", "semi", "senate", "send", "senile", "senior", "sense", "sensor", "sent", "sentry", "sequel", "serene", "serial", "series", "sermon", "serum", "serve", "server", "set", "settle", "seven", "severe", "sewage", "sex", "sexual", "sexy", "shabby", "shade", "shadow", "shady", "shaft", "shaggy", "shah", "shake", "shaky", "shall", "sham", "shame", "shape", "share", "shark", "sharp", "shawl", "she", "shear", "sheen", "sheep", "sheer", "sheet", "shelf", "shell", "sherry", "shield", "shift", "shine", "shiny", "ship", "shire", "shirt", "shit", "shiver", "shock", "shoe", "shook", "shoot", "shop", "shore", "short", "shot", "should", "shout", "show", "shower", "shrank", "shrewd", "shrill", "shrimp", "shrine", "shrink", "shrub", "shrug", "shut", "shy", "shyly", "sick", "side", "siege", "sigh", "sight", "sigma", "sign", "signal", "silent", "silk", "silken", "silky", "sill", "silly", "silver", "simple", "simply", "since", "sinful", "sing", "singer", "single", "sink", "sir", "siren", "sister", "sit", "site", "six", "sixth", "sixty", "size", "sketch", "skill", "skin", "skinny", "skip", "skirt", "skull", "sky", "slab", "slack", "slain", "slam", "slang", "slap", "slate", "slater", "slave", "sleek", "sleep", "sleepy", "sleeve", "slice", "slick", "slid", "slide", "slight", "slim", "slimy", "sling", "slip", "slit", "slogan", "slope", "sloppy", "slot", "slow", "slowly", "slug", "slum", "slump", "smack", "small", "smart", "smash", "smear", "smell", "smelly", "smelt", "smile", "smoke", "smoky", "smooth", "smug", "snack", "snail", "snake", "snap", "snatch", "sneak", "snow", "snowy", "snug", "soak", "soap", "sober", "soccer", "social", "sock", "socket", "soda", "sodden", "sodium", "sofa", "soft", "soften", "softly", "soggy", "soil", "solar", "sold", "sole", "solely", "solemn", "solid", "solo", "solve", "some", "son", "sonar", "sonata", "song", "sonic", "soon", "sooner", "soot", "soothe", "sordid", "sore", "sorrow", "sorry", "sort", "soul", "sound", "soup", "sour", "source", "space", "spade", "span", "spare", "spark", "sparse", "spasm", "spat", "spate", "speak", "spear", "speech", "speed", "speedy", "spell", "spend", "sperm", "sphere", "spice", "spicy", "spider", "spiky", "spill", "spin", "spinal", "spine", "spiral", "spirit", "spit", "spite", "splash", "split", "spoil", "spoke", "sponge", "spoon", "sport", "spot", "spouse", "spray", "spread", "spree", "spring", "sprint", "spur", "squad", "square", "squash", "squat", "squid", "stab", "stable", "stack", "staff", "stage", "stain", "stair", "stake", "stale", "stall", "stamp", "stance", "stand", "staple", "star", "starch", "stare", "stark", "start", "starve", "state", "static", "statue", "status", "stay", "stead", "steady", "steak", "steal", "steam", "steel", "steep", "steer", "stem", "stench", "step", "stereo", "stern", "stew", "stick", "sticky", "stiff", "stifle", "stigma", "still", "sting", "stint", "stir", "stitch", "stock", "stocky", "stone", "stony", "stool", "stop", "store", "storm", "stormy", "story", "stout", "stove", "strain", "strait", "strand", "strap", "strata", "straw", "stray", "streak", "stream", "street", "stress", "strict", "stride", "strife", "strike", "string", "strip", "strive", "stroke", "stroll", "strong", "stud", "studio", "study", "stuff", "stuffy", "stunt", "stupid", "sturdy", "style", "submit", "subtle", "subtly", "suburb", "such", "suck", "sudden", "sue", "suffer", "sugar", "suit", "suite", "suitor", "sullen", "sultan", "sum", "summer", "summit", "summon", "sun", "sunny", "sunset", "super", "superb", "supper", "supple", "supply", "sure", "surely", "surf", "surge", "survey", "suture", "swamp", "swan", "swap", "swarm", "sway", "swear", "sweat", "sweaty", "sweep", "sweet", "swell", "swift", "swim", "swine", "swing", "swirl", "switch", "sword", "swore", "symbol", "synod", "syntax", "syrup", "system", "table", "tablet", "taboo", "tacit", "tackle", "tact", "tactic", "tail", "tailor", "take", "tale", "talent", "talk", "tall", "tally", "tame", "tandem", "tangle", "tank", "tap", "tape", "target", "tariff", "tart", "task", "taste", "tasty", "tattoo", "taut", "tavern", "tax", "taxi", "tea", "teach", "teak", "team", "tear", "tease", "tech", "teeth", "tell", "temper", "temple", "tempo", "tempt", "ten", "tenant", "tend", "tender", "tendon", "tennis", "tenor", "tense", "tensor", "tent", "tenth", "tenure", "term", "terror", "test", "text", "than", "thank", "that", "the", "their", "them", "theme", "then", "thence", "theory", "there", "these", "thesis", "they", "thick", "thief", "thigh", "thin", "thing", "think", "third", "thirst", "thirty", "this", "thorn", "those", "though", "thread", "threat", "three", "thrill", "thrive", "throat", "throne", "throng", "throw", "thrust", "thud", "thug", "thumb", "thus", "thyme", "tick", "ticket", "tidal", "tide", "tidy", "tie", "tier", "tiger", "tight", "tile", "till", "tilt", "timber", "time", "timid", "tin", "tiny", "tip", "tissue", "title", "toad", "toast", "today", "toilet", "token", "told", "toll", "tomato", "tomb", "tonal", "tone", "tongue", "tonic", "too", "took", "tool", "tooth", "top", "topaz", "topic", "torch", "torque", "torso", "tort", "toss", "total", "touch", "tough", "tour", "toward", "towel", "tower", "town", "toxic", "toxin", "trace", "track", "tract", "trade", "tragic", "trail", "train", "trait", "tram", "trance", "trap", "trauma", "travel", "tray", "tread", "treat", "treaty", "treble", "tree", "trek", "tremor", "trench", "trend", "trendy", "trial", "tribal", "tribe", "trick", "tricky", "tried", "trifle", "trim", "trio", "trip", "triple", "troop", "trophy", "trot", "trough", "trout", "truce", "truck", "true", "truly", "trunk", "trust", "truth", "try", "tsar", "tube", "tumble", "tuna", "tundra", "tune", "tung", "tunic", "tunnel", "turban", "turf", "turn", "turtle", "tutor", "tweed", "twelve", "twenty", "twice", "twin", "twist", "two", "tycoon", "tying", "type", "tyrant", "ugly", "ulcer", "ultra", "umpire", "unable", "uncle", "under", "uneasy", "unfair", "unify", "union", "unique", "unit", "unite", "unity", "unlike", "unrest", "unruly", "until", "update", "upheld", "uphill", "uphold", "upon", "uproar", "upset", "upshot", "uptake", "upturn", "upward", "urban", "urge", "urgent", "urging", "urine", "usable", "usage", "use", "useful", "user", "usual", "uterus", "utmost", "utter", "vacant", "vacuum", "vagina", "vague", "vain", "valet", "valid", "valley", "value", "valve", "van", "vanish", "vanity", "vary", "vase", "vast", "vat", "vault", "vector", "veil", "vein", "velvet", "vendor", "veneer", "venom", "vent", "venue", "verb", "verbal", "verge", "verify", "verity", "verse", "versus", "very", "vessel", "vest", "veto", "via", "viable", "vicar", "vice", "victim", "victor", "video", "view", "vigil", "vile", "villa", "vine", "vinyl", "viola", "violet", "violin", "viral", "virgin", "virtue", "virus", "visa", "vision", "visit", "visual", "vital", "vivid", "vocal", "vodka", "vogue", "voice", "void", "volley", "volume", "vomit", "vote", "vowel", "voyage", "vulgar", "wade", "wage", "waist", "wait", "waiter", "wake", "walk", "walker", "wall", "wallet", "walnut", "wander", "want", "war", "warden", "warm", "warmth", "warn", "warp", "wary", "was", "wash", "wasp", "waste", "watch", "water", "watery", "wave", "way", "weak", "weaken", "wealth", "weapon", "wear", "weary", "wedge", "wee", "weed", "week", "weekly", "weep", "weight", "weird", "well", "were", "wet", "whale", "wharf", "what", "wheat", "wheel", "when", "whence", "where", "which", "whiff", "whig", "while", "whim", "whip", "whisky", "white", "who", "whole", "wholly", "whom", "whore", "whose", "why", "wide", "widely", "widen", "wider", "widow", "width", "wife", "wild", "wildly", "wilful", "will", "willow", "win", "wind", "window", "windy", "wine", "wing", "wink", "winner", "winter", "wipe", "wire", "wisdom", "wise", "wish", "wit", "witch", "with", "within", "witty", "wizard", "woke", "wolf", "wolves", "woman", "womb", "won", "wonder", "wood", "wooden", "woods", "woody", "wool", "word", "work", "worker", "world", "worm", "worry", "worse", "worst", "worth", "worthy", "would", "wound", "wrap", "wrath", "wreath", "wreck", "wright", "wrist", "writ", "write", "writer", "wrong", "xerox", "yacht", "yard", "yarn", "yeah", "year", "yeast", "yellow", "yet", "yield", "yogurt", "yolk", "you", "young", "your", "youth", "zeal", "zebra", "zenith", "zero", "zigzag", "zinc", "zombie", "zone" }; passwdqc-1.3.0/passwdqc_load.c0000600000000000000000000000551311272222054015017 0ustar rootroot/* * Copyright (c) 2008,2009 by Dmitry V. Levin. See LICENSE. */ #include #include #include #include #include #include #include #include "passwdqc.h" #include "concat.h" static char *mkreason(const char *what, const char *pathname, unsigned int lineno, const char *why) { char buf[sizeof(unsigned int) * 3 + 1]; const char *at_line = (lineno ? " at line " : ""); const char *at_num = (lineno ? buf : ""); if (lineno) sprintf(buf, "%u", lineno); return concat(what, " \"", pathname, "\"", at_line, at_num, ": ", (why ? why : strerror(errno)), NULL); } static char * skip_whitespaces(char *str) { char *p; for (p = str; *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n'; ++p) ; return p; } static char * skip_nonwhitespaces(char *str) { char *p; for (p = str; *p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n'; ++p) ; return p; } static int parse_file(FILE *fp, passwdqc_params_t *params, char **reason, const char *pathname) { unsigned int lineno; char buf[8192]; for (lineno = 1; fgets(buf, sizeof(buf), fp); ++lineno) { char *str, *end, *rt; const char *cstr; int rc; if (strlen(buf) >= sizeof(buf) - 1) { *reason = mkreason("Error reading", pathname, lineno, "Line too long"); return -1; } str = skip_whitespaces(buf); if (!*str || *str == '#') continue; end = skip_nonwhitespaces(str); if (*skip_whitespaces(end)) { *reason = mkreason("Error loading", pathname, lineno, "Unexpected token"); return -1; } *end = '\0'; cstr = str; if ((rc = passwdqc_params_parse(params, &rt, 1, &cstr))) { *reason = mkreason("Error loading", pathname, lineno, (rt ? rt : "Out of memory")); free(rt); return rc; } } if (!feof(fp) || ferror(fp)) { *reason = mkreason("Error reading", pathname, 0, NULL); return -1; } return 0; } struct dev_ino_t; struct dev_ino_t { struct dev_ino_t *next; dev_t dev; ino_t ino; }; static struct dev_ino_t *dev_ino_head; int passwdqc_params_load(passwdqc_params_t *params, char **reason, const char *pathname) { int rc; FILE *fp; struct dev_ino_t di, *di_p; struct stat st; if (!(fp = fopen(pathname, "r"))) { *reason = mkreason("Error opening", pathname, 0, NULL); return -1; } if (fstat(fileno(fp), &st)) { *reason = mkreason("Error stat", pathname, 0, NULL); fclose(fp); return -1; } di.dev = st.st_dev; di.ino = st.st_ino; for (di_p = dev_ino_head; di_p; di_p = di_p->next) if (di_p->dev == di.dev && di_p->ino == di.ino) break; if (di_p) { *reason = mkreason("Error opening", pathname, 0, "Loop detected"); fclose(fp); return -1; } di.next = dev_ino_head; dev_ino_head = &di; rc = parse_file(fp, params, reason, pathname); fclose(fp); dev_ino_head = dev_ino_head->next; return rc; } passwdqc-1.3.0/passwdqc_parse.c0000600000000000000000000001033112135511065015206 0ustar rootroot/* * Copyright (c) 2000-2003,2005 by Solar Designer * Copyright (c) 2008,2009 by Dmitry V. Levin * See LICENSE */ #include #include #include #include #include "passwdqc.h" #include "concat.h" static const char *skip_prefix(const char *sample, const char *prefix) { size_t len = strlen(prefix); if (strncmp(sample, prefix, len)) return NULL; return sample + len; } static int parse_option(passwdqc_params_t *params, char **reason, const char *option) { const char *err = "Invalid parameter value"; const char *p; char *e; int i, rc = 0; unsigned long v; *reason = NULL; if ((p = skip_prefix(option, "min="))) { for (i = 0; i < 5; i++) { if (!strncmp(p, "disabled", 8)) { v = INT_MAX; p += 8; } else { v = strtoul(p, &e, 10); p = e; } if (i < 4 && *p++ != ',') goto parse_error; if (v > INT_MAX) goto parse_error; if (i && (int)v > params->qc.min[i - 1]) goto parse_error; params->qc.min[i] = v; } if (*p) goto parse_error; } else if ((p = skip_prefix(option, "max="))) { v = strtoul(p, &e, 10); if (*e || v < 8 || v > INT_MAX) goto parse_error; params->qc.max = v; } else if ((p = skip_prefix(option, "passphrase="))) { v = strtoul(p, &e, 10); if (*e || v > INT_MAX) goto parse_error; params->qc.passphrase_words = v; } else if ((p = skip_prefix(option, "match="))) { v = strtoul(p, &e, 10); if (*e || v > INT_MAX) goto parse_error; params->qc.match_length = v; } else if ((p = skip_prefix(option, "similar="))) { if (!strcmp(p, "permit")) params->qc.similar_deny = 0; else if (!strcmp(p, "deny")) params->qc.similar_deny = 1; else goto parse_error; } else if ((p = skip_prefix(option, "random="))) { v = strtoul(p, &e, 10); if (!strcmp(e, ",only")) { e += 5; params->qc.min[4] = INT_MAX; } if (*e || (v && v < 24) || v > 85) goto parse_error; params->qc.random_bits = v; } else if ((p = skip_prefix(option, "enforce="))) { params->pam.flags &= ~F_ENFORCE_MASK; if (!strcmp(p, "users")) params->pam.flags |= F_ENFORCE_USERS; else if (!strcmp(p, "everyone")) params->pam.flags |= F_ENFORCE_EVERYONE; else if (strcmp(p, "none")) goto parse_error; } else if (!strcmp(option, "non-unix")) { if (params->pam.flags & F_CHECK_OLDAUTHTOK) goto parse_error; params->pam.flags |= F_NON_UNIX; } else if ((p = skip_prefix(option, "retry="))) { v = strtoul(p, &e, 10); if (*e || v > INT_MAX) goto parse_error; params->pam.retry = v; } else if ((p = skip_prefix(option, "ask_oldauthtok"))) { params->pam.flags &= ~F_ASK_OLDAUTHTOK_MASK; if (params->pam.flags & F_USE_FIRST_PASS) goto parse_error; if (!p[0]) params->pam.flags |= F_ASK_OLDAUTHTOK_PRELIM; else if (!strcmp(p, "=update")) params->pam.flags |= F_ASK_OLDAUTHTOK_UPDATE; else goto parse_error; } else if (!strcmp(option, "check_oldauthtok")) { if (params->pam.flags & F_NON_UNIX) goto parse_error; params->pam.flags |= F_CHECK_OLDAUTHTOK; } else if (!strcmp(option, "use_first_pass")) { if (params->pam.flags & F_ASK_OLDAUTHTOK_MASK) goto parse_error; params->pam.flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK; } else if (!strcmp(option, "use_authtok")) { params->pam.flags |= F_USE_AUTHTOK; } else if ((p = skip_prefix(option, "config="))) { if ((rc = passwdqc_params_load(params, reason, p))) goto parse_error; } else { err = "Invalid parameter"; goto parse_error; } return 0; parse_error: e = concat("Error parsing parameter \"", option, "\": ", (rc ? (*reason ? *reason : "Out of memory") : err), NULL); free(*reason); *reason = e; return rc ? rc : -1; } int passwdqc_params_parse(passwdqc_params_t *params, char **reason, int argc, const char *const *argv) { int i; *reason = NULL; for (i = 0; i < argc; ++i) { int rc; if ((rc = parse_option(params, reason, argv[i]))) return rc; } return 0; } static const passwdqc_params_t defaults = { { {INT_MAX, 24, 11, 8, 7}, /* min */ 40, /* max */ 3, /* passphrase_words */ 4, /* match_length */ 1, /* similar_deny */ 47 /* random_bits */ }, { F_ENFORCE_EVERYONE, /* flags */ 3 /* retry */ } }; void passwdqc_params_reset(passwdqc_params_t *params) { *params = defaults; }