2 * The general entry points for password strength checking.
4 * Provides the strength_init, strength_check, and strength_close entry points
5 * for doing password strength checking. These are the only interfaces that
6 * are called by the implementation-specific code, and all other checks are
7 * wrapped up in those interfaces.
9 * Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates,
10 * on behalf of Stanford University.
11 * Extensive modifications by Russ Allbery <rra@stanford.edu>
12 * Copyright 2006, 2007, 2009, 2012, 2013
13 * The Board of Trustees of the Leland Stanford Junior Unversity
15 * See LICENSE for licensing terms.
19 #include <portable/kadmin.h>
20 #include <portable/krb5.h>
21 #include <portable/system.h>
29 #include <plugin/internal.h>
30 #include <util/macros.h>
34 * Initialize the module. Ensure that the dictionary file exists and is
35 * readable and store the path in the module context. Returns 0 on success,
36 * non-zero on failure. This function returns failure only if it could not
37 * allocate memory or internal Kerberos calls that shouldn't fail do.
39 * The dictionary file should not include the trailing .pwd extension.
40 * Currently, we don't cope with a NULL dictionary path.
43 strength_init(krb5_context ctx, const char *dictionary,
44 krb5_pwqual_moddata *moddata)
46 krb5_pwqual_moddata data = NULL;
49 /* Allocate our internal data. */
50 data = calloc(1, sizeof(*data));
52 return strength_error_system(ctx, "cannot allocate memory");
55 /* Get minimum length information from krb5.conf. */
56 strength_config_number(ctx, "minimum_length", &data->minimum_length);
58 /* Get character class restrictions from krb5.conf. */
59 strength_config_boolean(ctx, "require_ascii_printable", &data->ascii);
60 strength_config_boolean(ctx, "require_non_letter", &data->nonletter);
63 * Try to initialize CDB and CrackLib dictionaries. Both functions handle
64 * their own configuration parsing and will do nothing if the
65 * corresponding dictionary is not configured.
67 code = strength_init_cracklib(ctx, data, dictionary);
70 code = strength_init_cdb(ctx, data);
74 /* Initialized. Set moddata and return. */
80 strength_close(ctx, data);
87 * Check if a password contains only printable ASCII characters.
90 only_printable_ascii(const char *password)
94 for (p = password; *p != '\0'; p++)
95 if (!isascii((unsigned char) *p) || !isprint((unsigned char) *p))
102 * Check if a password contains only letters and spaces.
105 only_alpha_space(const char *password)
109 for (p = password; *p != '\0'; p++)
110 if (!isalpha((unsigned char) *p) && *p != ' ')
117 * Check a given password. Takes a Kerberos context, our module data, the
118 * password, the principal the password is for, and a buffer and buffer length
119 * into which to put any failure message.
122 strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
123 const char *password, const char *principal)
129 krb5_error_code code;
131 /* Check minimum length first, since that's easy. */
132 if ((long) strlen(password) < data->minimum_length)
133 return strength_error_tooshort(ctx, ERROR_SHORT);
136 * If desired, check whether the password contains non-ASCII or
137 * non-printable ASCII characters.
139 if (data->ascii && !only_printable_ascii(password))
140 return strength_error_generic(ctx, ERROR_ASCII);
143 * If desired, ensure the password has a non-letter (and non-space)
144 * character. This requires that people using phrases at least include a
145 * digit or punctuation to make phrase dictionary attacks or dictionary
146 * attacks via combinations of words harder.
148 if (data->nonletter && only_alpha_space(password))
149 return strength_error_class(ctx, ERROR_LETTER);
152 * We get the principal (in krb5_unparse_name format) and we want to be
153 * sure that the password doesn't match the username, the username
154 * reversed, or the username with trailing digits. We therefore have to
155 * copy the string so that we can manipulate it a bit.
157 if (strcasecmp(password, principal) == 0)
158 return strength_error_generic(ctx, ERROR_USERNAME);
159 user = strdup(principal);
161 return strength_error_system(ctx, "cannot allocate memory");
163 /* Strip the realm off of the principal. */
164 for (p = user; p[0] != '\0'; p++) {
165 if (p[0] == '\\' && p[1] != '\0') {
176 * If the length of the password matches the length of the local portion
177 * of the principal, check for exact matches or reversed matches.
179 if (strlen(password) == strlen(user)) {
180 if (strcasecmp(password, user) == 0) {
182 return strength_error_generic(ctx, ERROR_USERNAME);
185 /* Check against the reversed username. */
186 for (i = 0, j = strlen(user) - 1; i < j; i++, j--) {
191 if (strcasecmp(password, user) == 0) {
193 return strength_error_generic(ctx, ERROR_USERNAME);
198 * If the length is greater, check whether the user just added trailing
199 * digits to the local portion of the principal to form the password.
201 if (strlen(password) > strlen(user))
202 if (strncasecmp(password, user, strlen(user)) == 0) {
203 q = password + strlen(user);
204 while (isdigit((unsigned char) *q))
208 return strength_error_generic(ctx, ERROR_USERNAME);
213 /* Check the password against CDB and CrackLib if configured. */
214 code = strength_check_cracklib(ctx, data, password);
217 code = strength_check_cdb(ctx, data, password);
221 /* Success. Password accepted. */
227 * Cleanly shut down the password strength plugin. The only thing we have to
228 * do is free the memory allocated for our internal data.
231 strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
234 strength_close_cdb(ctx, data);
235 free(data->dictionary);