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 Daria Phoebe Brashear and Ken Hornstein of Sine Nomine
10 * Associates, on behalf of Stanford University Extensive modifications by Russ
11 * Allbery <eagle@eyrie.org> Copyright 2006-2007, 2009, 2012-2014 The Board of
12 * Trustees of the Leland Stanford Junior University
14 * SPDX-License-Identifier: MIT
18 #include <portable/krb5.h>
19 #include <portable/system.h>
23 #include <plugin/internal.h>
24 #include <util/macros.h>
28 * Initialize the module. Ensure that the dictionary file exists and is
29 * readable and store the path in the module context. Returns 0 on success,
30 * non-zero on failure. This function returns failure only if it could not
31 * allocate memory or internal Kerberos calls that shouldn't fail do.
33 * The dictionary file should not include the trailing .pwd extension.
34 * Currently, we don't cope with a NULL dictionary path.
37 strength_init(krb5_context ctx, const char *dictionary,
38 krb5_pwqual_moddata *moddata)
40 krb5_pwqual_moddata data = NULL;
43 /* Allocate our internal data. */
44 data = calloc(1, sizeof(*data));
46 return strength_error_system(ctx, "cannot allocate memory");
49 /* Get minimum length and character information from krb5.conf. */
50 strength_config_number(ctx, "minimum_different", &data->minimum_different);
51 strength_config_number(ctx, "minimum_length", &data->minimum_length);
53 /* Get simple character class restrictions from krb5.conf. */
54 strength_config_boolean(ctx, "require_ascii_printable", &data->ascii);
55 strength_config_boolean(ctx, "require_non_letter", &data->nonletter);
57 /* Get complex character class restrictions from krb5.conf. */
58 code = strength_config_classes(ctx, "require_classes", &data->rules);
62 /* Get CrackLib maximum length from krb5.conf. */
63 strength_config_number(ctx, "cracklib_maxlen", &data->cracklib_maxlen);
66 * Try to initialize CDB, CrackLib, and SQLite dictionaries. These
67 * functions handle their own configuration parsing and will do nothing if
68 * the corresponding dictionary is not configured.
70 code = strength_init_cracklib(ctx, data, dictionary);
73 code = strength_init_cdb(ctx, data);
76 code = strength_init_sqlite(ctx, data);
80 /* Initialized. Set moddata and return. */
86 strength_close(ctx, data);
93 * Check if a password contains only printable ASCII characters.
96 only_printable_ascii(const char *password)
100 for (p = password; *p != '\0'; p++)
101 if (!isascii((unsigned char) *p) || !isprint((unsigned char) *p))
108 * Check if a password contains only letters and spaces.
111 only_alpha_space(const char *password)
115 for (p = password; *p != '\0'; p++)
116 if (!isalpha((unsigned char) *p) && *p != ' ')
123 * Check if a password has a sufficient number of unique characters. Takes
124 * the password and the required number of characters.
127 has_minimum_different(const char *password, long minimum)
132 /* Special cases for passwords of length 0 and a minimum <= 1. */
133 if (password == NULL || password[0] == '\0')
139 * Count the number of unique characters by incrementing the count if each
140 * subsequent character is not found in the previous password characters.
141 * This algorithm is O(n^2), but passwords are short enough it shouldn't
145 for (p = password + 1; *p != '\0'; p++)
146 if (memchr(password, *p, p - password) == NULL) {
148 if (unique >= (size_t) minimum)
156 * Check a given password. Takes a Kerberos context, our module data, the
157 * password, the principal the password is for, and a buffer and buffer length
158 * into which to put any failure message.
161 strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
162 const char *principal, const char *password)
164 krb5_error_code code;
166 /* Check minimum length first, since that's easy. */
167 if ((long) strlen(password) < data->minimum_length)
168 return strength_error_tooshort(ctx, ERROR_SHORT);
171 * If desired, check whether the password contains non-ASCII or
172 * non-printable ASCII characters.
174 if (data->ascii && !only_printable_ascii(password))
175 return strength_error_generic(ctx, ERROR_ASCII);
178 * If desired, ensure the password has a non-letter (and non-space)
179 * character. This requires that people using phrases at least include a
180 * digit or punctuation to make phrase dictionary attacks or dictionary
181 * attacks via combinations of words harder.
183 if (data->nonletter && only_alpha_space(password))
184 return strength_error_class(ctx, ERROR_LETTER);
186 /* If desired, check for enough unique characters. */
187 if (data->minimum_different > 0)
188 if (!has_minimum_different(password, data->minimum_different))
189 return strength_error_class(ctx, ERROR_MINDIFF);
192 * If desired, check that the password satisfies character class
195 code = strength_check_classes(ctx, data, password);
199 /* Check if the password is based on the principal in some way. */
200 code = strength_check_principal(ctx, data, principal, password);
204 /* Check the password against CDB, CrackLib, and SQLite if configured. */
205 code = strength_check_cracklib(ctx, data, password);
208 code = strength_check_cdb(ctx, data, password);
211 code = strength_check_sqlite(ctx, data, password);
215 /* Success. Password accepted. */
221 * Cleanly shut down the password strength plugin. The only thing we have to
222 * do is free the memory allocated for our internal data.
225 strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
227 struct class_rule *last, *tmp;
231 strength_close_cdb(ctx, data);
232 strength_close_sqlite(ctx, data);
234 while (last != NULL) {
239 free(data->dictionary);