Import pam_passwdqc 1.0.5 from the Openwall project.
authorPeter Avalos <pavalos@theshell.com>
Sun, 4 Jan 2009 16:25:06 +0000 (11:25 -0500)
committerPeter Avalos <pavalos@theshell.com>
Sun, 4 Jan 2009 16:52:17 +0000 (11:52 -0500)
contrib/pam_passwdqc/INTERNALS [new file with mode: 0644]
contrib/pam_passwdqc/LICENSE [new file with mode: 0644]
contrib/pam_passwdqc/README [new file with mode: 0644]
contrib/pam_passwdqc/pam_macros.h [new file with mode: 0644]
contrib/pam_passwdqc/pam_passwdqc.8 [new file with mode: 0644]
contrib/pam_passwdqc/pam_passwdqc.c [new file with mode: 0644]
contrib/pam_passwdqc/pam_passwdqc.spec [new file with mode: 0644]
contrib/pam_passwdqc/passwdqc.h [new file with mode: 0644]
contrib/pam_passwdqc/passwdqc_check.c [new file with mode: 0644]
contrib/pam_passwdqc/passwdqc_random.c [new file with mode: 0644]
contrib/pam_passwdqc/wordset_4k.c [new file with mode: 0644]

diff --git a/contrib/pam_passwdqc/INTERNALS b/contrib/pam_passwdqc/INTERNALS
new file mode 100644 (file)
index 0000000..1026c05
--- /dev/null
@@ -0,0 +1,3 @@
+The functions defined in passwdqc.h may be used without PAM at all.
+They will eventually be moved into a libpasswdqc (which has already been
+done in ALT Linux distributions).
diff --git a/contrib/pam_passwdqc/LICENSE b/contrib/pam_passwdqc/LICENSE
new file mode 100644 (file)
index 0000000..58f1196
--- /dev/null
@@ -0,0 +1,18 @@
+pam_passwdqc.8 is under the 3-clause BSD-style license as specified
+within the file itself.
+
+wordset_4k.c is in the public domain.
+
+The rest of the files in this package fall under the following terms:
+
+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.
+
+$Owl: Owl/packages/pam_passwdqc/pam_passwdqc/LICENSE,v 1.3 2005/11/16 13:28:57 solar Exp $
diff --git a/contrib/pam_passwdqc/README b/contrib/pam_passwdqc/README
new file mode 100644 (file)
index 0000000..7085a57
--- /dev/null
@@ -0,0 +1,148 @@
+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 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'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,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
+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 is 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 passphrases in bits (24 to 72), 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 <solar at openwall.com>
+
+$Owl: Owl/packages/pam_passwdqc/pam_passwdqc/README,v 1.11 2008/02/12 19:43:33 solar Exp $
diff --git a/contrib/pam_passwdqc/pam_macros.h b/contrib/pam_passwdqc/pam_macros.h
new file mode 100644 (file)
index 0000000..3006af7
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef PAM_PASSWDQC_MACROS_H__
+#define PAM_PASSWDQC_MACROS_H__
+
+#include <string.h>
+#include <stdlib.h>
+
+#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__ */
diff --git a/contrib/pam_passwdqc/pam_passwdqc.8 b/contrib/pam_passwdqc/pam_passwdqc.8
new file mode 100644 (file)
index 0000000..8247652
--- /dev/null
@@ -0,0 +1,269 @@
+.\" Copyright (c) 2000-2003,2005,2008 Solar Designer.
+.\" All rights reserved.
+.\" Copyright (c) 2001 Networks Associates Technology, Inc.
+.\" 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/pam_passwdqc/pam_passwdqc/pam_passwdqc.8,v 1.11 2008/02/12 20:33:09 solar Exp $
+.\"
+.Dd February 12, 2008
+.Dt PAM_PASSWDQC 8
+.Os
+.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 following options may be passed to the module:
+.Bl -tag -width indent
+.It Xo
+.Sm off
+.Cm min No = Ar N0 , N1 , N2 , N3 , N4
+.Sm on
+.Xc
+.Sm off
+.Pq Cm min No = Cm disabled , No 24 , 11 , 8 , 7
+.Sm on
+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 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 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 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 removed.
+.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 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 removed
+would be weak.
+.It Xo
+.Sm off
+.Cm random No = Ar N Op , Cm only
+.Sm on
+.Xc
+.Pq Cm random Ns = Ns 42
+The size of randomly-generated passphrases in bits (24 to 72),
+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.
+.It Xo
+.Sm off
+.Cm enforce No = Cm none | users | everyone
+.Sm on
+.Xc
+.Pq Cm enforce Ns = Ns Cm everyone
+The 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,
+.Nm
+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 Cm retry Ns = Ns 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.
+.It Cm ask_oldauthtok Ns Op = Ns Cm update
+Ask for the old password as well.
+Normally,
+.Nm
+leaves this task for subsequent modules.
+With no argument, the
+.Cm ask_oldauthtok
+option will cause
+.Nm
+to ask for the old password during the preliminary check phase.
+If the
+.Cm ask_oldauthtok
+option is specified with the
+.Cm update
+argument,
+.Nm
+will do that during the update phase.
+.It Cm check_oldauthtok
+This tells
+.Nm
+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 modules stacked before
+.Nm .
+This disables user interaction within
+.Nm .
+The only difference between
+.Cm use_first_pass
+and
+.Cm use_authtok
+is that the former is incompatible with
+.Cm ask_oldauthtok .
+.El
+.Sh SEE ALSO
+.Xr getpwnam 3 ,
+.Xr pam.conf 5 ,
+.Xr pam 8
+.Sh AUTHORS
+The
+.Nm
+module was written for Openwall GNU/*/Linux by
+.An Solar Designer Aq solar at openwall.com .
+This manual page, 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.
diff --git a/contrib/pam_passwdqc/pam_passwdqc.c b/contrib/pam_passwdqc/pam_passwdqc.c
new file mode 100644 (file)
index 0000000..e7e3390
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2000-2003,2005 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 <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_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 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, 11, 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_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 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, 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_overwrite_string(buffer);
+               pwqc_drop_pam_reply(resp, 1);
+       } else {
+               status = PAM_ABORT;
+               memset(buffer, 0, sizeof(buffer));
+       }
+
+       return status;
+}
+
+static int check_max(params_t *params, pam_handle_t *pamh, const char *newpass)
+{
+       if ((int)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 check_pass(struct passwd *pw, const char *pass)
+{
+#ifdef HAVE_SHADOW
+       struct spwd *spw;
+       const char *hash;
+       int retval;
+
+#ifdef __hpux
+       if (iscomsec()) {
+#else
+       if (!strcmp(pw->pw_passwd, "x")) {
+#endif
+               spw = getspnam(pw->pw_name);
+               endspent();
+               if (!spw)
+                       return -1;
+#ifdef __hpux
+               hash = bigcrypt(pass, spw->sp_pwdp);
+#else
+               hash = crypt(pass, spw->sp_pwdp);
+#endif
+               retval = strcmp(hash, spw->sp_pwdp) ? -1 : 0;
+               memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp));
+               return retval;
+       }
+#endif
+
+       return strcmp(crypt(pass, pw->pw_passwd), pw->pw_passwd) ? -1 : 0;
+}
+
+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");
+}
+
+static int parse(params_t *params, pam_handle_t *pamh,
+    int argc, const char **argv)
+{
+       const char *p;
+       char *e;
+       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, &e, 10);
+                                       p = e;
+                               }
+                               if (i < 4 && *p++ != ',') break;
+                               if (v > INT_MAX) break;
+                               if (i && (int)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, &e, 10);
+                       if (*e || v < 8 || v > INT_MAX) break;
+                       params->qc.max = v;
+               } else
+               if (!strncmp(*argv, "passphrase=", 11)) {
+                       v = strtoul(*argv + 11, &e, 10);
+                       if (*e || v > INT_MAX) break;
+                       params->qc.passphrase_words = v;
+               } else
+               if (!strncmp(*argv, "match=", 6)) {
+                       v = strtoul(*argv + 6, &e, 10);
+                       if (*e || 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, &e, 10);
+                       if (!strcmp(e, ",only")) {
+                               e += 5;
+                               params->qc.min[4] = INT_MAX;
+                       }
+                       if (*e || (v && v < 24) || v > 72) 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, &e, 10);
+                       if (*e || 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, am_root(pamh) ?
+                   MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, *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;
+       pam_item_t item;
+       const char *user, *oldpass, *newpass;
+       char *trypass, *randompass;
+       const 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 && !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.flags & F_NON_UNIX) {
+               pw = &fake_pw;
+               pw->pw_name = (char *)user;
+               pw->pw_gecos = "";
+       } else {
+               pw = getpwnam(user);
+               endpwent();
+               if (!pw)
+                       return PAM_USER_UNKNOWN;
+               if ((params.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.flags & F_ENFORCE_ROOT;
+       else
+               enforce = params.flags & F_ENFORCE_USERS;
+
+       if (params.flags & F_USE_AUTHTOK) {
+               status = pam_get_item(pamh, PAM_AUTHTOK, &item);
+               if (status != PAM_SUCCESS)
+                       return status;
+               newpass = item;
+               if (!newpass || (check_max(&params, pamh, newpass) && enforce))
+                       return PAM_AUTHTOK_ERR;
+               reason = _passwdqc_check(&params.qc, newpass, oldpass, pw);
+               if (reason) {
+                       say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
+                       if (enforce)
+                               status = PAM_AUTHTOK_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[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(&params.qc);
+       if (randompass) {
+               status = say(pamh, PAM_TEXT_INFO, randomonly ?
+                   MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
+               if (status != PAM_SUCCESS) {
+                       pwqc_overwrite_string(randompass);
+                       randompass = NULL;
+               }
+       } 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);
+               return status;
+       }
+
+       trypass = strdup(resp->resp);
+
+       pwqc_drop_pam_reply(resp, 1);
+
+       if (!trypass) {
+               pwqc_overwrite_string(randompass);
+               return PAM_AUTHTOK_ERR;
+       }
+
+       if (check_max(&params, pamh, trypass) && enforce) {
+               status = PAM_AUTHTOK_ERR;
+               retry_wanted = 1;
+       }
+
+       reason = NULL;
+       if (status == PAM_SUCCESS &&
+           (!randompass || !strstr(trypass, randompass)) &&
+           (randomonly ||
+           (reason = _passwdqc_check(&params.qc, trypass, 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_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_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)
+struct pam_module _pam_passwdqc_modstruct = {
+       "pam_passwdqc",
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       pam_sm_chauthtok
+};
+#endif
diff --git a/contrib/pam_passwdqc/pam_passwdqc.spec b/contrib/pam_passwdqc/pam_passwdqc.spec
new file mode 100644 (file)
index 0000000..2273f0d
--- /dev/null
@@ -0,0 +1,154 @@
+# $Owl: Owl/packages/pam_passwdqc/pam_passwdqc/pam_passwdqc.spec,v 1.38 2008/02/12 20:28:48 solar Exp $
+
+Summary: Pluggable password quality-control module.
+Name: pam_passwdqc
+Version: 1.0.5
+Release: owl1
+License: BSD-compatible
+Group: System Environment/Base
+URL: http://www.openwall.com/passwdqc/
+Source: ftp://ftp.openwall.com/pub/projects/pam/modules/%name/%name-%version.tar.gz
+BuildRequires: pam-devel
+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 ones.  All features are optional and
+can be (re-)configured without rebuilding.
+
+%prep
+%setup -q
+
+%build
+%__make CFLAGS="-Wall -fPIC -DLINUX_PAM %optflags"
+
+%install
+rm -rf %buildroot
+%__make install DESTDIR=%buildroot MANDIR=%_mandir SECUREDIR=/%_lib/security
+
+%files
+%defattr(-,root,root)
+%doc LICENSE README
+/%_lib/security/pam_passwdqc.so
+%_mandir/man*/*
+
+%changelog
+* Tue Feb 12 2008 Solar Designer <solar-at-owl.openwall.com> 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 <ldv-at-owl.openwall.com> 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 <ldv-at-owl.openwall.com> 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 <solar-at-owl.openwall.com> 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 <solar-at-owl.openwall.com> 1.0.1-owl1
+- Further compiler warning fixes on LP64 platforms.
+
+* Fri Mar 25 2005 Solar Designer <solar-at-owl.openwall.com> 1.0-owl1
+- Corrected the source code to not break C strict aliasing rules.
+
+* Wed Jan 26 2005 Solar Designer <solar-at-owl.openwall.com> 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 <solar-at-owl.openwall.com> 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 <solar-at-owl.openwall.com> 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 <solar-at-owl.openwall.com> 0.7.3.1-owl1
+- Added URL.
+
+* Thu Oct 31 2002 Solar Designer <solar-at-owl.openwall.com> 0.7.3-owl1
+- When compiling with gcc, also link with gcc.
+- Use $(MAKE) to invoke sub-makes.
+
+* Fri Oct 04 2002 Solar Designer <solar-at-owl.openwall.com>
+- Solaris 9 notes in PLATFORMS.
+
+* Wed Sep 18 2002 Solar Designer <solar-at-owl.openwall.com>
+- 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 <solar-at-owl.openwall.com>
+- Have the same pam_passwdqc binary work for both trusted and non-trusted
+HP-UX, from Kevin Steves.
+
+* Fri Sep 06 2002 Solar Designer <solar-at-owl.openwall.com>
+- 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 <solar-at-owl.openwall.com>
+- Call it 0.6.
+
+* Sat Jul 27 2002 Solar Designer <solar-at-owl.openwall.com>
+- Documented that the man page is under the 3-clause BSD-style license.
+- HP-UX 11 support.
+
+* Tue Jul 23 2002 Solar Designer <solar-at-owl.openwall.com>
+- 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 <solar-at-owl.openwall.com>
+- 0.5.1: imported the pam_passwdqc(8) manual page back from FreeBSD.
+
+* Tue Apr 16 2002 Solar Designer <solar-at-owl.openwall.com>
+- 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 <mci-at-owl.openwall.com>
+- Enforce our new spec file conventions.
+
+* Sun Nov 04 2001 Solar Designer <solar-at-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-at-owl.openwall.com>
+- Install the module as mode 755.
+
+* Tue Dec 19 2000 Solar Designer <solar-at-owl.openwall.com>
+- Added "-Wall -fPIC" to the CFLAGS.
+
+* Mon Oct 30 2000 Solar Designer <solar-at-owl.openwall.com>
+- 0.3: portability fixes (this might build on non-Linux-PAM now).
+
+* Fri Sep 22 2000 Solar Designer <solar-at-owl.openwall.com>
+- 0.2: added "use_authtok", added README.
+
+* Fri Aug 18 2000 Solar Designer <solar-at-owl.openwall.com>
+- 0.1, "retry_wanted" bugfix.
+
+* Sun Jul 02 2000 Solar Designer <solar-at-owl.openwall.com>
+- Initial version (non-public).
diff --git a/contrib/pam_passwdqc/passwdqc.h b/contrib/pam_passwdqc/passwdqc.h
new file mode 100644 (file)
index 0000000..75f1dff
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000-2002 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 const char *_passwdqc_check(passwdqc_params_t *params,
+    const char *newpass, const char *oldpass, struct passwd *pw);
+extern char *_passwdqc_random(passwdqc_params_t *params);
+
+#endif
diff --git a/contrib/pam_passwdqc/passwdqc_check.c b/contrib/pam_passwdqc/passwdqc_check.c
new file mode 100644 (file)
index 0000000..2d5f714
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2000-2002 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, const 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 are 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(const char *src)
+{
+       const char *sptr;
+       char *dst, *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(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 removed.
+ */
+static int is_based(passwdqc_params_t *params,
+    const char *haystack, const char *needle, const char *original)
+{
+       char *scratch;
+       int length;
+       int i, j;
+       const 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,
+    const char *needle, const char *original)
+{
+       char word[7];
+       char *unified;
+       int i;
+
+       word[6] = '\0';
+       for (i = 0; i < 0x1000; i++) {
+               memcpy(word, _passwdqc_wordset_4k[i], 6);
+               if ((int)strlen(word) < params->match_length) continue;
+               unified = unify(word);
+               if (is_based(params, unified, needle, original)) {
+                       clean(unified);
+                       return 1;
+               }
+               clean(unified);
+       }
+
+       return 0;
+}
+
+const char *_passwdqc_check(passwdqc_params_t *params,
+    const char *newpass, const char *oldpass, struct passwd *pw)
+{
+       char truncated[9], *reversed;
+       char *u_newpass, *u_reversed;
+       char *u_oldpass;
+       char *u_name, *u_gecos;
+       const 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 && (int)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_passwdqc/passwdqc_random.c b/contrib/pam_passwdqc/passwdqc_random.c
new file mode 100644 (file)
index 0000000..4183cf6
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2000-2002,2005,2008 by Solar Designer.  See LICENSE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "passwdqc.h"
+
+/*
+ * We separate words in the generated "passphrases" with random special
+ * characters out of a set of 8 (so we encode 3 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 characters.
+ */
+#define SEPARATORS                     "-_!$&*+="
+
+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(passwdqc_params_t *params)
+{
+       static char output[0x100];
+       int bits;
+       int use_separators, count, i;
+       unsigned int length, extra;
+       char *start, *end;
+       int fd;
+       unsigned char bytes[2];
+
+       bits = params->random_bits;
+       if (bits < 24 || bits > 128)
+               return NULL;
+
+       count = 1 + (bits + (14 - 12)) / 15;
+       use_separators = ((bits + 11) / 12 != count);
+
+       length = count * 7 - 1;
+       if (length >= sizeof(output) || (int)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;
+               }
+
+               i = (((int)bytes[1] & 0x0f) << 8) | (int)bytes[0];
+               start = _passwdqc_wordset_4k[i];
+               end = memchr(start, '\0', 6);
+               if (!end) end = start + 6;
+               extra = end - start;
+               if (length + extra >= sizeof(output) - 1) {
+                       close(fd);
+                       return NULL;
+               }
+               memcpy(&output[length], start, extra);
+               length += extra;
+               bits -= 12;
+
+               if (use_separators && bits > 3) {
+                       i = ((int)bytes[1] & 0x70) >> 4;
+                       output[length++] = SEPARATORS[i];
+                       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_passwdqc/wordset_4k.c b/contrib/pam_passwdqc/wordset_4k.c
new file mode 100644 (file)
index 0000000..1a604d4
--- /dev/null
@@ -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"
+};