aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2002-04-04 15:50:47 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2002-04-04 15:50:47 +0000
commit0b0ecb56f2b297a48f1f7ba332e1c70be86c7c30 (patch)
tree1dcc5978e4a3b724654b82d0e61f6dc3e94705ee
downloadsrc-0b0ecb56f2b297a48f1f7ba332e1c70be86c7c30.tar.gz
src-0b0ecb56f2b297a48f1f7ba332e1c70be86c7c30.zip
Vendor import of Solar Designer's pam_passwdqc module.vendor/pam_modules/0.4
Notes
Notes: svn path=/vendor/pam_modules/dist/; revision=93787 svn path=/vendor/pam_modules/0.4/; revision=93789; tag=vendor/pam_modules/0.4
-rw-r--r--contrib/pam_modules/pam_passwdqc/INTERNALS2
-rw-r--r--contrib/pam_modules/pam_passwdqc/LICENSE9
-rw-r--r--contrib/pam_modules/pam_passwdqc/Makefile48
-rw-r--r--contrib/pam_modules/pam_passwdqc/PLATFORMS27
-rw-r--r--contrib/pam_modules/pam_passwdqc/README143
-rw-r--r--contrib/pam_modules/pam_passwdqc/pam_macros.h28
-rw-r--r--contrib/pam_modules/pam_passwdqc/pam_passwdqc.c553
-rw-r--r--contrib/pam_modules/pam_passwdqc/pam_passwdqc.spec58
-rw-r--r--contrib/pam_modules/pam_passwdqc/passwdqc.h24
-rw-r--r--contrib/pam_modules/pam_passwdqc/passwdqc_check.c361
-rw-r--r--contrib/pam_modules/pam_passwdqc/passwdqc_random.c90
-rw-r--r--contrib/pam_modules/pam_passwdqc/wordset_4k.c4108
12 files changed, 5451 insertions, 0 deletions
diff --git a/contrib/pam_modules/pam_passwdqc/INTERNALS b/contrib/pam_modules/pam_passwdqc/INTERNALS
new file mode 100644
index 000000000000..bce8f85ad54d
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/INTERNALS
@@ -0,0 +1,2 @@
+The functions defined in passwdqc.h may be used without PAM at all.
+They will eventually be moved into a libpasswdqc.
diff --git a/contrib/pam_modules/pam_passwdqc/LICENSE b/contrib/pam_modules/pam_passwdqc/LICENSE
new file mode 100644
index 000000000000..dc50971f05da
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/LICENSE
@@ -0,0 +1,9 @@
+You're allowed to do whatever you like with this software (including
+re-distribution in source and/or binary form, with or without
+modification), provided that credit is given where it is due and any
+modified versions are marked as such. There's absolutely no warranty.
+
+Note that you don't have to re-distribute this software under these
+same relaxed terms. In particular, you're free to place modified
+versions under (L)GPL, thus disallowing further re-distribution in
+binary-only form.
diff --git a/contrib/pam_modules/pam_passwdqc/Makefile b/contrib/pam_modules/pam_passwdqc/Makefile
new file mode 100644
index 000000000000..49678481456b
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/Makefile
@@ -0,0 +1,48 @@
+#
+# Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
+#
+
+CC = gcc
+LD = ld
+RM = rm -f
+MKDIR = mkdir -p
+INSTALL = install
+CFLAGS = -c -Wall -fPIC -DHAVE_SHADOW -O2
+LDFLAGS = -s -lpam -lcrypt --shared
+LDFLAGS_SUN = -s -lpam -lcrypt -G
+
+TITLE = pam_passwdqc
+LIBSHARED = $(TITLE).so
+SHLIBMODE = 755
+SECUREDIR = /lib/security
+FAKEROOT =
+
+PROJ = $(LIBSHARED)
+OBJS = pam_passwdqc.o passwdqc_check.o passwdqc_random.o wordset_4k.o
+
+all:
+ if [ "`uname -s`" = "SunOS" ]; then \
+ make LDFLAGS="$(LDFLAGS_SUN)" $(PROJ); \
+ else \
+ make $(PROJ); \
+ fi
+
+$(LIBSHARED): $(OBJS)
+ $(LD) $(LDFLAGS) $(OBJS) -o $(LIBSHARED)
+
+.c.o:
+ $(CC) $(CFLAGS) $*.c
+
+pam_passwdqc.o: passwdqc.h pam_macros.h
+passwdqc_check.o: passwdqc.h
+passwdqc_random.o: passwdqc.h
+
+install:
+ $(MKDIR) $(FAKEROOT)$(SECUREDIR)
+ $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)
+
+remove:
+ $(RM) $(FAKEROOT)$(SECUREDIR)/$(TITLE).so
+
+clean:
+ $(RM) $(PROJ) *.o
diff --git a/contrib/pam_modules/pam_passwdqc/PLATFORMS b/contrib/pam_modules/pam_passwdqc/PLATFORMS
new file mode 100644
index 000000000000..c38ecfc28f31
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/PLATFORMS
@@ -0,0 +1,27 @@
+Please 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.
+
+Currently, FreeBSD doesn't use PAM for password changing. This means
+you won't be able to use pam_passwdqc with FreeBSD.
+
+
+ Solaris.
+
+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.
+
+You will likely also need to set "max=8" in order to actually enforce
+not-so-weak passwords with the obsolete "traditional" crypt(3) hashes
+that most Solaris systems use. Of course this way you only get about
+one third of the functionality of pam_passwdqc.
diff --git a/contrib/pam_modules/pam_passwdqc/README b/contrib/pam_modules/pam_passwdqc/README
new file mode 100644
index 000000000000..ca2af89955da
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/README
@@ -0,0 +1,143 @@
+pam_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 passwords. 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's a number of supported options which can be used to modify the
+behavior of pam_passwdqc (defaults are given in square brackets):
+
+ min=N0,N1,N2,N3,N4 [min=disabled,24,12,8,7]
+
+The minimum allowed password lengths, separately for different kinds
+of passwords/passphrases. The special word "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's also the special
+class for non-ASCII characters which couldn't be classified, but are
+assumed to be non-digits.)
+
+N1 is used for passwords consisting of characters from two character
+classes, which don't meet the requirements for a passphrase.
+
+N2 is used for passphrases. A passphrase must consist of sufficient
+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've been checked against.
+
+ max=N [max=40]
+
+The maximum allowed password length. This can be used to prevent
+users from setting passwords which may be too long for some system
+services.
+
+The value 8 is treated specially. 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 crypt(3) password hashes.
+
+It is important that you do set max=8 if you're 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 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. Instead, the password
+will be subjected to the usual strength requirements with the weak
+substring removed.
+
+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's a sufficiently
+long common substring and the new password with the substring removed
+would be weak.
+
+ random=N[,only] [random=42]
+
+The size of randomly-generated passwords in bits, or 0 to disable this
+feature. Passwords that contain 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 non-root users only.
+
+ non-unix []
+
+By default, 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 "non-unix".
+
+ 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 the password changing module. A simple "ask_oldauthtok" 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 the password
+changing module.
+
+The primary use for this option is with "ask_oldauthtok=update" in
+which case no other modules have a chance to run and validate the
+password between the prompts. 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 <solar@openwall.com>
diff --git a/contrib/pam_modules/pam_passwdqc/pam_macros.h b/contrib/pam_modules/pam_passwdqc/pam_macros.h
new file mode 100644
index 000000000000..adc04bcb1f77
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/pam_macros.h
@@ -0,0 +1,28 @@
+/*
+ * These macros are partially based on Linux-PAM's <security/_pam_macros.h>,
+ * which were organized by Cristian Gafton and I believe are in the public
+ * domain.
+ */
+
+#if !defined(_PAM_MACROS_H) && !defined(_pam_overwrite)
+#define _PAM_MACROS_H
+
+#include <string.h>
+#include <stdlib.h>
+
+#define _pam_overwrite(x) \
+ memset((x), 0, strlen((x)))
+
+#define _pam_drop_reply(/* struct pam_response * */ reply, /* int */ replies) \
+do { \
+ int i; \
+\
+ for (i = 0; i < (replies); i++) \
+ if ((reply)[i].resp) { \
+ _pam_overwrite((reply)[i].resp); \
+ free((reply)[i].resp); \
+ } \
+ if ((reply)) free((reply)); \
+} while (0)
+
+#endif
diff --git a/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c b/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c
new file mode 100644
index 000000000000..7cc0c812e03b
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c
@@ -0,0 +1,553 @@
+/*
+ * Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
+ */
+
+#define _XOPEN_SOURCE 500
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 500
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+#include <pwd.h>
+#ifdef HAVE_SHADOW
+#include <shadow.h>
+#endif
+
+#define PAM_SM_PASSWORD
+#ifndef LINUX_PAM
+#include <security/pam_appl.h>
+#endif
+#include <security/pam_modules.h>
+
+#include "pam_macros.h"
+
+#if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
+#define PAM_EXTERN extern
+#endif
+
+#if !defined(PAM_AUTHTOK_RECOVER_ERR) && defined(PAM_AUTHTOK_RECOVERY_ERR)
+#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR
+#endif
+
+#if defined(__sun__) && !defined(LINUX_PAM)
+#define linux_const /* Sun's PAM doesn't use const here */
+#else
+#define linux_const const
+#endif
+typedef linux_const void *pam_item_t;
+
+#include "passwdqc.h"
+
+#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
+
+typedef struct {
+ passwdqc_params_t qc;
+ int flags;
+ int retry;
+} params_t;
+
+static params_t defaults = {
+ {
+ {INT_MAX, 24, 12, 8, 7}, /* min */
+ 40, /* max */
+ 3, /* passphrase_words */
+ 4, /* match_length */
+ 1, /* similar_deny */
+ 42 /* random_bits */
+ },
+ F_ENFORCE_EVERYONE, /* flags */
+ 3 /* retry */
+};
+
+#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: Invalid option: \"%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_1 \
+ "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.\n" \
+ "Characters that form a common pattern are discarded by the check.\n"
+#define MESSAGE_EXPLAIN_PASSWORD_2 \
+ "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. Characters that form a common pattern are discarded by\n" \
+ "the check.\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 noone 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 noone 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, char *text,
+ struct pam_response **resp)
+{
+ struct pam_conv *conv;
+ struct pam_message msg, *pmsg;
+ int status;
+
+ status = pam_get_item(pamh, PAM_CONV, (pam_item_t *)&conv);
+ if (status != PAM_SUCCESS)
+ return status;
+
+ pmsg = &msg;
+ msg.msg_style = style;
+ msg.msg = text;
+
+ *resp = NULL;
+ return conv->conv(1, (linux_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 (needed > 0 && needed < sizeof(buffer)) {
+ status = converse(pamh, style, buffer, &resp);
+ _pam_overwrite(buffer);
+ } else {
+ status = PAM_ABORT;
+ memset(buffer, 0, sizeof(buffer));
+ }
+
+ return status;
+}
+
+static int check_max(params_t *params, pam_handle_t *pamh, char *newpass)
+{
+ if (strlen(newpass) > params->qc.max) {
+ if (params->qc.max != 8) {
+ say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
+ return -1;
+ }
+ say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
+ }
+
+ return 0;
+}
+
+static int parse(params_t *params, pam_handle_t *pamh,
+ int argc, const char **argv)
+{
+ const char *p;
+ int i;
+ unsigned long v;
+
+ while (argc) {
+ if (!strncmp(*argv, "min=", 4)) {
+ p = *argv + 4;
+ for (i = 0; i < 5; i++) {
+ if (!strncmp(p, "disabled", 8)) {
+ v = INT_MAX;
+ p += 8;
+ } else
+ v = strtoul(p, (char **)&p, 10);
+ if (i < 4 && *p++ != ',') break;
+ if (v > INT_MAX) break;
+ if (i && v > params->qc.min[i - 1]) break;
+ params->qc.min[i] = v;
+ }
+ if (*p) break;
+ } else
+ if (!strncmp(*argv, "max=", 4)) {
+ v = strtoul(*argv + 4, (char **)&p, 10);
+ if (*p || v < 8 || v > INT_MAX) break;
+ params->qc.max = v;
+ } else
+ if (!strncmp(*argv, "passphrase=", 11)) {
+ v = strtoul(*argv + 11, (char **)&p, 10);
+ if (*p || v > INT_MAX) break;
+ params->qc.passphrase_words = v;
+ } else
+ if (!strncmp(*argv, "match=", 6)) {
+ v = strtoul(*argv + 6, (char **)&p, 10);
+ if (*p || v > INT_MAX) break;
+ params->qc.match_length = v;
+ } else
+ if (!strncmp(*argv, "similar=", 8)) {
+ if (!strcmp(*argv + 8, "permit"))
+ params->qc.similar_deny = 0;
+ else
+ if (!strcmp(*argv + 8, "deny"))
+ params->qc.similar_deny = 1;
+ else
+ break;
+ } else
+ if (!strncmp(*argv, "random=", 7)) {
+ v = strtoul(*argv + 7, (char **)&p, 10);
+ if (!strcmp(p, ",only")) {
+ p += 5;
+ params->qc.min[4] = INT_MAX;
+ }
+ if (*p || v > INT_MAX) break;
+ params->qc.random_bits = v;
+ } else
+ if (!strncmp(*argv, "enforce=", 8)) {
+ params->flags &= ~F_ENFORCE_MASK;
+ if (!strcmp(*argv + 8, "users"))
+ params->flags |= F_ENFORCE_USERS;
+ else
+ if (!strcmp(*argv + 8, "everyone"))
+ params->flags |= F_ENFORCE_EVERYONE;
+ else
+ if (strcmp(*argv + 8, "none"))
+ break;
+ } else
+ if (!strcmp(*argv, "non-unix")) {
+ if (params->flags & F_CHECK_OLDAUTHTOK) break;
+ params->flags |= F_NON_UNIX;
+ } else
+ if (!strncmp(*argv, "retry=", 6)) {
+ v = strtoul(*argv + 6, (char **)&p, 10);
+ if (*p || v > INT_MAX) break;
+ params->retry = v;
+ } else
+ if (!strncmp(*argv, "ask_oldauthtok", 14)) {
+ params->flags &= ~F_ASK_OLDAUTHTOK_MASK;
+ if (params->flags & F_USE_FIRST_PASS) break;
+ if (!strcmp(*argv + 14, "=update"))
+ params->flags |= F_ASK_OLDAUTHTOK_UPDATE;
+ else
+ if (!(*argv)[14])
+ params->flags |= F_ASK_OLDAUTHTOK_PRELIM;
+ else
+ break;
+ } else
+ if (!strcmp(*argv, "check_oldauthtok")) {
+ if (params->flags & F_NON_UNIX) break;
+ params->flags |= F_CHECK_OLDAUTHTOK;
+ } else
+ if (!strcmp(*argv, "use_first_pass")) {
+ if (params->flags & F_ASK_OLDAUTHTOK_MASK) break;
+ params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
+ } else
+ if (!strcmp(*argv, "use_authtok")) {
+ params->flags |= F_USE_AUTHTOK;
+ } else
+ break;
+ argc--; argv++;
+ }
+
+ if (argc) {
+ say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
+ MESSAGE_MISCONFIGURED : MESSAGE_INVALID_OPTION, *argv);
+ return PAM_ABORT;
+ }
+
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ params_t params;
+ struct pam_response *resp;
+ struct passwd *pw, fake_pw;
+#ifdef HAVE_SHADOW
+ struct spwd *spw;
+#endif
+ char *user, *oldpass, *newpass, *randompass;
+ char *reason;
+ int ask_oldauthtok;
+ int randomonly, enforce, retries_left, retry_wanted;
+ int status;
+
+ params = defaults;
+ status = parse(&params, pamh, argc, argv);
+ if (status != PAM_SUCCESS)
+ return status;
+
+ ask_oldauthtok = 0;
+ if (flags & PAM_PRELIM_CHECK) {
+ if (params.flags & F_ASK_OLDAUTHTOK_PRELIM)
+ ask_oldauthtok = 1;
+ } else
+ if (flags & PAM_UPDATE_AUTHTOK) {
+ if (params.flags & F_ASK_OLDAUTHTOK_UPDATE)
+ ask_oldauthtok = 1;
+ } else
+ return PAM_SERVICE_ERR;
+
+ if (ask_oldauthtok && getuid() != 0) {
+ 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);
+ _pam_drop_reply(resp, 1);
+ } else
+ status = PAM_AUTHTOK_RECOVER_ERR;
+ }
+
+ if (status != PAM_SUCCESS)
+ return status;
+ }
+
+ if (flags & PAM_PRELIM_CHECK)
+ return status;
+
+ status = pam_get_item(pamh, PAM_USER, (pam_item_t *)&user);
+ if (status != PAM_SUCCESS)
+ return status;
+
+ status = pam_get_item(pamh, PAM_OLDAUTHTOK, (pam_item_t *)&oldpass);
+ if (status != PAM_SUCCESS)
+ return status;
+
+ if (params.flags & F_NON_UNIX) {
+ pw = &fake_pw;
+ pw->pw_name = user;
+ pw->pw_gecos = "";
+ } else {
+ pw = getpwnam(user);
+ endpwent();
+ if (!pw)
+ return PAM_USER_UNKNOWN;
+ if ((params.flags & F_CHECK_OLDAUTHTOK) && getuid() != 0) {
+ if (!oldpass)
+ status = PAM_AUTH_ERR;
+ else
+#ifdef HAVE_SHADOW
+ if (!strcmp(pw->pw_passwd, "x")) {
+ spw = getspnam(user);
+ endspent();
+ if (spw) {
+ if (strcmp(crypt(oldpass, spw->sp_pwdp),
+ spw->sp_pwdp))
+ status = PAM_AUTH_ERR;
+ memset(spw->sp_pwdp, 0,
+ strlen(spw->sp_pwdp));
+ } else
+ status = PAM_AUTH_ERR;
+ } else
+#endif
+ if (strcmp(crypt(oldpass, pw->pw_passwd),
+ pw->pw_passwd))
+ 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 (getuid() != 0)
+ enforce = params.flags & F_ENFORCE_USERS;
+ else
+ enforce = params.flags & F_ENFORCE_ROOT;
+
+ if (params.flags & F_USE_AUTHTOK) {
+ status = pam_get_item(pamh, PAM_AUTHTOK,
+ (pam_item_t *)&newpass);
+ if (status != PAM_SUCCESS)
+ return status;
+ if (!newpass || (check_max(&params, pamh, newpass) && enforce))
+ return PAM_AUTHTOK_RECOVER_ERR;
+ reason = _passwdqc_check(&params.qc, newpass, oldpass, pw);
+ if (reason) {
+ say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
+ if (enforce)
+ status = PAM_AUTHTOK_RECOVER_ERR;
+ }
+ return status;
+ }
+
+ retries_left = params.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[3] <= params.qc.min[4])
+ status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1,
+ params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
+ params.qc.min[3]);
+ else
+ if (!randomonly)
+ status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_2,
+ 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(&params.qc);
+ if (randompass) {
+ status = say(pamh, PAM_TEXT_INFO, randomonly ?
+ MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
+ if (status != PAM_SUCCESS) {
+ _pam_overwrite(randompass);
+ randompass = NULL;
+ }
+ } else
+ if (randomonly) {
+ say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
+ MESSAGE_MISCONFIGURED : MESSAGE_RANDOMFAILED);
+ return PAM_AUTHTOK_RECOVER_ERR;
+ }
+
+ status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
+ if (status == PAM_SUCCESS && (!resp || !resp->resp))
+ status = PAM_AUTHTOK_RECOVER_ERR;
+
+ if (status != PAM_SUCCESS) {
+ if (randompass) _pam_overwrite(randompass);
+ return status;
+ }
+
+ newpass = strdup(resp->resp);
+
+ _pam_drop_reply(resp, 1);
+
+ if (!newpass) {
+ if (randompass) _pam_overwrite(randompass);
+ return PAM_AUTHTOK_RECOVER_ERR;
+ }
+
+ if (check_max(&params, pamh, newpass) && enforce) {
+ status = PAM_AUTHTOK_RECOVER_ERR;
+ retry_wanted = 1;
+ }
+
+ reason = NULL;
+ if (status == PAM_SUCCESS &&
+ (!randompass || !strstr(newpass, randompass)) &&
+ (randomonly ||
+ (reason = _passwdqc_check(&params.qc, newpass, oldpass, pw)))) {
+ if (randomonly)
+ say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
+ else
+ say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
+ if (enforce) {
+ status = PAM_AUTHTOK_RECOVER_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(newpass, resp->resp)) {
+ status = say(pamh,
+ PAM_ERROR_MSG, MESSAGE_MISTYPED);
+ if (status == PAM_SUCCESS) {
+ status = PAM_AUTHTOK_RECOVER_ERR;
+ retry_wanted = 1;
+ }
+ }
+ _pam_drop_reply(resp, 1);
+ } else
+ status = PAM_AUTHTOK_RECOVER_ERR;
+ }
+
+ if (status == PAM_SUCCESS)
+ status = pam_set_item(pamh, PAM_AUTHTOK, newpass);
+
+ if (randompass) _pam_overwrite(randompass);
+ _pam_overwrite(newpass);
+ free(newpass);
+
+ if (retry_wanted && --retries_left > 0) {
+ status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
+ if (status == PAM_SUCCESS)
+ goto retry;
+ }
+
+ return status;
+}
+
+#ifdef PAM_STATIC
+struct pam_module _pam_passwdqc_modstruct = {
+ "pam_passwdqc",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ pam_sm_chauthtok
+};
+#endif
diff --git a/contrib/pam_modules/pam_passwdqc/pam_passwdqc.spec b/contrib/pam_modules/pam_passwdqc/pam_passwdqc.spec
new file mode 100644
index 000000000000..1a53494334b8
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/pam_passwdqc.spec
@@ -0,0 +1,58 @@
+Summary: Pluggable password "quality check"
+Name: pam_passwdqc
+Version: 0.4
+Release: 1owl
+License: relaxed BSD and (L)GPL-compatible
+Group: System Environment/Base
+Source: pam_passwdqc-%version.tar.gz
+BuildRoot: /override/%name-%version
+
+%description
+pam_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 passwords. All features are optional
+and can be (re-)configured without rebuilding.
+
+%prep
+%setup -q
+
+%build
+make CFLAGS="-c -Wall -fPIC -DHAVE_SHADOW -DLINUX_PAM $RPM_OPT_FLAGS"
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make install FAKEROOT=$RPM_BUILD_ROOT
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%doc LICENSE README
+/lib/security/pam_passwdqc.so
+
+%changelog
+* Sun Nov 04 2001 Solar Designer <solar@owl.openwall.com>
+- 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 <solar@owl.openwall.com>
+- Install the module as mode 755.
+
+* Tue Dec 19 2000 Solar Designer <solar@owl.openwall.com>
+- Added "-Wall -fPIC" to the CFLAGS.
+
+* Mon Oct 30 2000 Solar Designer <solar@owl.openwall.com>
+- 0.3: portability fixes (this might build on non-Linux-PAM now).
+
+* Fri Sep 22 2000 Solar Designer <solar@owl.openwall.com>
+- 0.2: added "use_authtok", added README.
+
+* Fri Aug 18 2000 Solar Designer <solar@owl.openwall.com>
+- 0.1, "retry_wanted" bugfix.
+
+* Sun Jul 02 2000 Solar Designer <solar@owl.openwall.com>
+- Initial version (non-public).
diff --git a/contrib/pam_modules/pam_passwdqc/passwdqc.h b/contrib/pam_modules/pam_passwdqc/passwdqc.h
new file mode 100644
index 000000000000..f0516717dafe
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/passwdqc.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
+ */
+
+#ifndef _PASSWDQC_H
+#define _PASSWDQC_H
+
+#include <pwd.h>
+
+typedef struct {
+ int min[5], max;
+ int passphrase_words;
+ int match_length;
+ int similar_deny;
+ int random_bits;
+} passwdqc_params_t;
+
+extern char _passwdqc_wordset_4k[0x1000][6];
+
+extern char *_passwdqc_check(passwdqc_params_t *params,
+ char *newpass, char *oldpass, struct passwd *pw);
+extern char *_passwdqc_random(passwdqc_params_t *params);
+
+#endif
diff --git a/contrib/pam_modules/pam_passwdqc/passwdqc_check.c b/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
new file mode 100644
index 000000000000..08e2b4945de9
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <pwd.h>
+
+#include "passwdqc.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 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.
+ */
+static int is_simple(passwdqc_params_t *params, char *newpass)
+{
+ 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++;
+
+ if (isascii(c) && isalpha(c) && isascii(p) && !isalpha(p))
+ words++;
+ p = c;
+
+ 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're no non-ASCII characters for digits. */
+ classes = 0;
+ if (digits) classes++;
+ if (lowers) classes++;
+ if (uppers) classes++;
+ if (others) classes++;
+ if (unknowns && (!classes || (digits && classes == 1))) classes++;
+
+ for (; classes > 0; classes--)
+ switch (classes) {
+ case 1:
+ if (length >= params->min[0] &&
+ chars >= expected_different(10, params->min[0]) - 1)
+ return 0;
+ return 1;
+
+ case 2:
+ if (length >= params->min[1] &&
+ chars >= expected_different(36, params->min[1]) - 1)
+ return 0;
+ if (!params->passphrase_words ||
+ words < params->passphrase_words)
+ continue;
+ if (length >= params->min[2] &&
+ chars >= expected_different(27, params->min[2]) - 1)
+ return 0;
+ continue;
+
+ case 3:
+ if (length >= params->min[3] &&
+ chars >= expected_different(62, params->min[3]) - 1)
+ return 0;
+ continue;
+
+ case 4:
+ if (length >= params->min[4] &&
+ chars >= expected_different(95, params->min[4]) - 1)
+ return 0;
+ continue;
+ }
+
+ return 1;
+}
+
+static char *unify(char *src)
+{
+ char *dst;
+ char *sptr, *dptr;
+ int c;
+
+ if (!(dst = malloc(strlen(src) + 1)))
+ return NULL;
+
+ sptr = src;
+ dptr = dst;
+ do {
+ c = (unsigned char)*sptr;
+ if (isascii(c) && isupper(c))
+ *dptr++ = tolower(c);
+ else
+ *dptr++ = *sptr;
+ } while (*sptr++);
+
+ return dst;
+}
+
+static char *reverse(char *src)
+{
+ char *dst;
+ char *sptr, *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 removed.
+ */
+static int is_based(passwdqc_params_t *params,
+ char *haystack, char *needle, char *original)
+{
+ char *scratch;
+ int length;
+ int i, j;
+ char *p;
+ int match;
+
+ if (!params->match_length) /* disabled */
+ return 0;
+
+ if (params->match_length < 0) /* misconfigured */
+ return 1;
+
+ if (strstr(haystack, needle)) /* based on haystack entirely */
+ return 1;
+
+ scratch = NULL;
+
+ length = strlen(needle);
+ for (i = 0; i <= length - params->match_length; i++)
+ for (j = params->match_length; i + j <= length; j++) {
+ match = 0;
+ for (p = haystack; *p; p++)
+ if (*p == needle[i] && !strncmp(p, &needle[i], j)) {
+ match = 1;
+ if (!scratch) {
+ if (!(scratch = malloc(length + 1)))
+ return 1;
+ }
+ memcpy(scratch, original, i);
+ memcpy(&scratch[i], &original[i + j],
+ length + 1 - (i + j));
+ if (is_simple(params, scratch)) {
+ clean(scratch);
+ return 1;
+ }
+ }
+ if (!match) break;
+ }
+
+ clean(scratch);
+
+ return 0;
+}
+
+/*
+ * 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 int is_word_based(passwdqc_params_t *params,
+ char *needle, char *original)
+{
+ char word[7];
+ char *unified;
+ int index;
+
+ word[6] = '\0';
+ for (index = 0; index < 0x1000; index++) {
+ memcpy(word, _passwdqc_wordset_4k[index], 6);
+ if (strlen(word) < params->match_length) continue;
+ unified = unify(word);
+ if (is_based(params, unified, needle, original)) {
+ clean(unified);
+ return 1;
+ }
+ clean(unified);
+ }
+
+ return 0;
+}
+
+char *_passwdqc_check(passwdqc_params_t *params,
+ char *newpass, char *oldpass, struct passwd *pw)
+{
+ char truncated[9], *reversed;
+ char *u_newpass, *u_reversed;
+ char *u_oldpass;
+ char *u_name, *u_gecos;
+ char *reason;
+ int length;
+
+ reversed = NULL;
+ u_newpass = u_reversed = NULL;
+ u_oldpass = NULL;
+ u_name = u_gecos = NULL;
+
+ reason = NULL;
+
+ if (oldpass && !strcmp(oldpass, newpass))
+ reason = REASON_SAME;
+
+ length = strlen(newpass);
+
+ if (!reason && length < params->min[4])
+ reason = REASON_SHORT;
+
+ if (!reason && 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;
+ } else
+ reason = REASON_LONG;
+ }
+
+ if (!reason && is_simple(params, newpass)) {
+ if (length < params->min[1] && params->min[1] <= params->max)
+ reason = REASON_SIMPLESHORT;
+ else
+ reason = REASON_SIMPLE;
+ }
+
+ if (!reason) {
+ if ((reversed = reverse(newpass))) {
+ u_newpass = unify(newpass);
+ u_reversed = unify(reversed);
+ if (oldpass)
+ u_oldpass = unify(oldpass);
+ if (pw) {
+ u_name = unify(pw->pw_name);
+ u_gecos = unify(pw->pw_gecos);
+ }
+ }
+ if (!reversed ||
+ !u_newpass || !u_reversed ||
+ (oldpass && !u_oldpass) ||
+ (pw && (!u_name || !u_gecos)))
+ reason = REASON_ERROR;
+ }
+
+ if (!reason && oldpass && params->similar_deny &&
+ (is_based(params, u_oldpass, u_newpass, newpass) ||
+ is_based(params, u_oldpass, u_reversed, reversed)))
+ reason = REASON_SIMILAR;
+
+ if (!reason && pw &&
+ (is_based(params, u_name, u_newpass, newpass) ||
+ is_based(params, u_name, u_reversed, reversed) ||
+ is_based(params, u_gecos, u_newpass, newpass) ||
+ is_based(params, u_gecos, u_reversed, reversed)))
+ reason = REASON_PERSONAL;
+
+ if (!reason && strlen(newpass) < params->min[2] &&
+ (is_word_based(params, u_newpass, newpass) ||
+ is_word_based(params, u_reversed, reversed)))
+ reason = REASON_WORD;
+
+ memset(truncated, 0, sizeof(truncated));
+ clean(reversed);
+ clean(u_newpass); clean(u_reversed);
+ clean(u_oldpass);
+ clean(u_name); clean(u_gecos);
+
+ return reason;
+}
diff --git a/contrib/pam_modules/pam_passwdqc/passwdqc_random.c b/contrib/pam_modules/pam_passwdqc/passwdqc_random.c
new file mode 100644
index 000000000000..cffd9f3e748c
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/passwdqc_random.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "passwdqc.h"
+
+#define SEPARATORS "_,.;:-!&"
+
+static int read_loop(int fd, 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(passwdqc_params_t *params)
+{
+ static char output[0x100];
+ int bits;
+ int use_separators, count, length, index;
+ char *start, *end;
+ int fd;
+ unsigned char bytes[2];
+
+ if (!(bits = params->random_bits))
+ return NULL;
+
+ count = 1 + ((bits - 12) + 14) / 15;
+ use_separators = ((bits + 11) / 12 != count);
+
+ length = count * 7 - 1;
+ if (length >= sizeof(output) || length > params->max) return NULL;
+
+ if ((fd = open("/dev/urandom", O_RDONLY)) < 0) return NULL;
+
+ length = 0;
+ do {
+ if (read_loop(fd, bytes, sizeof(bytes)) != sizeof(bytes)) {
+ close(fd);
+ return NULL;
+ }
+
+ index = (((int)bytes[1] & 0x0f) << 8) | (int)bytes[0];
+ start = _passwdqc_wordset_4k[index];
+ end = memchr(start, '\0', 6);
+ if (!end) end = start + 6;
+ if (length + (end - start) >= sizeof(output) - 1) {
+ close(fd);
+ return NULL;
+ }
+ memcpy(&output[length], start, end - start);
+ length += end - start;
+ bits -= 12;
+
+ if (use_separators && bits > 3) {
+ index = ((int)bytes[1] & 0x70) >> 4;
+ output[length++] = SEPARATORS[index];
+ bits -= 3;
+ } else
+ if (bits > 0)
+ output[length++] = ' ';
+ } while (bits > 0);
+
+ memset(bytes, 0, sizeof(bytes));
+ output[length] = '\0';
+
+ close(fd);
+
+ return output;
+}
diff --git a/contrib/pam_modules/pam_passwdqc/wordset_4k.c b/contrib/pam_modules/pam_passwdqc/wordset_4k.c
new file mode 100644
index 000000000000..1a604d49b00b
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/wordset_4k.c
@@ -0,0 +1,4108 @@
+/*
+ * 4096 English words for generation of easy to memorize random passphrases.
+ * This list comes from a passphrase generator mentioned on sci.crypt, and I
+ * believe is in the public domain.
+ *
+ * I've replaced two 7-character words to save space.
+ */
+
+#include "passwdqc.h"
+
+char _passwdqc_wordset_4k[0x1000][6] = {
+ "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"
+};