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>
32 /* If not built with CDB support, provide some stubs. */
34 # define strength_check_cdb(c, d, p) 0
35 # define strength_close_cdb(c, d) /* empty */
40 * Load a boolean option from Kerberos appdefaults. Takes the Kerberos
41 * context, the section name, the option, and the result location.
43 * The stupidity of rewriting the realm argument into a krb5_data is required
47 default_boolean(krb5_context ctx, const char *section, const char *opt,
53 #ifdef HAVE_KRB5_REALM
54 krb5_const_realm rdata = realm;
56 krb5_data realm_struct;
57 const krb5_data *rdata;
60 /* Get the default realm. This is annoying for MIT Kerberos. */
61 code = krb5_get_default_realm(ctx, &realm);
64 #ifdef HAVE_KRB5_REALM
70 rdata = &realm_struct;
71 realm_struct.magic = KV5M_DATA;
72 realm_struct.data = (void *) realm;
73 realm_struct.length = strlen(realm);
78 * The MIT version of krb5_appdefault_boolean takes an int * and the
79 * Heimdal version takes a krb5_boolean *, so hope that Heimdal always
80 * defines krb5_boolean to int or this will require more portability work.
82 krb5_appdefault_boolean(ctx, section, rdata, opt, *result, &tmp);
88 * Load a number option from Kerberos appdefaults. Takes the Kerberos
89 * context, the section name, the option, and the result location. The native
90 * interface doesn't support numbers, so we actually read a string and then
94 default_number(krb5_context ctx, const char *section, const char *opt,
101 krb5_error_code code;
102 #ifdef HAVE_KRB5_REALM
103 krb5_const_realm rdata = realm;
105 krb5_data realm_struct;
106 const krb5_data *rdata;
109 /* Get the default realm. This is annoying for MIT Kerberos. */
110 code = krb5_get_default_realm(ctx, &realm);
113 #ifdef HAVE_KRB5_REALM
119 rdata = &realm_struct;
120 realm_struct.magic = KV5M_DATA;
121 realm_struct.data = (void *) realm;
122 realm_struct.length = strlen(realm);
126 /* Obtain the string from [appdefaults]. */
127 krb5_appdefault_string(ctx, section, rdata, opt, "", &tmp);
130 * If we found anything, convert it to a number. Currently, we ignore
133 if (tmp != NULL && tmp[0] != '\0') {
135 value = strtol(tmp, &end, 10);
136 if (errno == 0 && *end == '\0')
140 krb5_free_string(ctx, tmp);
145 * Load a string option from Kerberos appdefaults. Takes the Kerberos
146 * context, the section name, the realm, the option, and the result location.
148 * This requires an annoying workaround because one cannot specify a default
149 * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
150 * strdup on the default value. There's also no way to determine if memory
151 * allocation failed while parsing or while setting the default value, so we
152 * don't return an error code.
155 default_string(krb5_context ctx, const char *section, const char *opt,
160 krb5_error_code code;
161 #ifdef HAVE_KRB5_REALM
162 krb5_const_realm rdata;
164 krb5_data realm_struct;
165 const krb5_data *rdata;
168 /* Get the default realm. This is annoying for MIT Kerberos. */
169 code = krb5_get_default_realm(ctx, &realm);
172 #ifdef HAVE_KRB5_REALM
178 rdata = &realm_struct;
179 realm_struct.magic = KV5M_DATA;
180 realm_struct.data = (void *) realm;
181 realm_struct.length = strlen(realm);
185 /* Obtain the string from [appdefaults]. */
186 krb5_appdefault_string(ctx, section, rdata, opt, "", &value);
188 /* If we got something back, store it in result. */
190 if (value[0] == '\0')
195 *result = strdup(value);
196 krb5_free_string(ctx, value);
200 /* Free the realm if we got one. */
202 krb5_free_default_realm(ctx, realm);
207 * Initialize the module. Ensure that the dictionary file exists and is
208 * readable and store the path in the module context. Returns 0 on success,
209 * non-zero on failure. This function returns failure only if it could not
210 * allocate memory or internal Kerberos calls that shouldn't fail do.
212 * The dictionary file should not include the trailing .pwd extension.
213 * Currently, we don't cope with a NULL dictionary path.
216 strength_init(krb5_context ctx, const char *dictionary,
217 krb5_pwqual_moddata *moddata)
219 krb5_pwqual_moddata data = NULL;
220 char *cdb_path = NULL;
221 krb5_error_code code;
223 /* Allocate our internal data. */
224 data = calloc(1, sizeof(*data));
226 return strength_error_system(ctx, "cannot allocate memory");
229 /* Get minimum length information from krb5.conf. */
230 default_number(ctx, "krb5-strength", "minimum_length", &data->min_length);
232 /* Get character class restrictions from krb5.conf. */
233 default_boolean(ctx, "krb5-strength", "require_ascii_printable",
235 default_boolean(ctx, "krb5-strength", "require_non_letter",
238 /* Use dictionary if given, otherwise get from krb5.conf. */
239 if (dictionary == NULL)
240 default_string(ctx, "krb5-strength", "password_dictionary",
243 data->dictionary = strdup(dictionary);
244 if (data->dictionary == NULL) {
245 code = strength_error_system(ctx, "cannot allocate memory");
250 /* Get CDB dictionary path from krb5.conf. */
251 default_string(ctx, "krb5-strength", "password_dictionary_cdb", &cdb_path);
253 /* If there is no dictionary, abort our setup with an error. */
254 if (data->dictionary == NULL && cdb_path == NULL) {
255 code = KADM5_MISSING_CONF_PARAMS;
256 krb5_set_error_message(ctx, code, "password_dictionary not configured"
261 /* If there is a CrackLib dictionary, initialize CrackLib. */
262 if (data->dictionary != NULL) {
263 code = strength_init_cracklib(ctx, data);
268 /* If there is a CDB dictionary, initialize TinyCDB. */
269 if (cdb_path != NULL) {
270 code = strength_init_cdb(ctx, data, cdb_path);
275 /* Initialized. Set moddata and return. */
281 strength_close(ctx, data);
289 * Check if a password contains only printable ASCII characters.
292 only_printable_ascii(const char *password)
296 for (p = password; *p != '\0'; p++)
297 if (!isascii((unsigned char) *p) || !isprint((unsigned char) *p))
304 * Check if a password contains only letters and spaces.
307 only_alpha_space(const char *password)
311 for (p = password; *p != '\0'; p++)
312 if (!isalpha((unsigned char) *p) && *p != ' ')
319 * Check a given password. Takes a Kerberos context, our module data, the
320 * password, the principal the password is for, and a buffer and buffer length
321 * into which to put any failure message.
324 strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
325 const char *password, const char *principal)
331 krb5_error_code code;
333 /* Check minimum length first, since that's easy. */
334 if ((long) strlen(password) < data->min_length)
335 return strength_error_tooshort(ctx, ERROR_SHORT);
338 * If desired, check whether the password contains non-ASCII or
339 * non-printable ASCII characters.
341 if (data->ascii && !only_printable_ascii(password))
342 return strength_error_generic(ctx, ERROR_ASCII);
345 * If desired, ensure the password has a non-letter (and non-space)
346 * character. This requires that people using phrases at least include a
347 * digit or punctuation to make phrase dictionary attacks or dictionary
348 * attacks via combinations of words harder.
350 if (data->nonletter && only_alpha_space(password))
351 return strength_error_class(ctx, ERROR_LETTER);
354 * We get the principal (in krb5_unparse_name format) and we want to be
355 * sure that the password doesn't match the username, the username
356 * reversed, or the username with trailing digits. We therefore have to
357 * copy the string so that we can manipulate it a bit.
359 if (strcasecmp(password, principal) == 0)
360 return strength_error_generic(ctx, ERROR_USERNAME);
361 user = strdup(principal);
363 return strength_error_system(ctx, "cannot allocate memory");
365 /* Strip the realm off of the principal. */
366 for (p = user; p[0] != '\0'; p++) {
367 if (p[0] == '\\' && p[1] != '\0') {
378 * If the length of the password matches the length of the local portion
379 * of the principal, check for exact matches or reversed matches.
381 if (strlen(password) == strlen(user)) {
382 if (strcasecmp(password, user) == 0) {
384 return strength_error_generic(ctx, ERROR_USERNAME);
387 /* Check against the reversed username. */
388 for (i = 0, j = strlen(user) - 1; i < j; i++, j--) {
393 if (strcasecmp(password, user) == 0) {
395 return strength_error_generic(ctx, ERROR_USERNAME);
400 * If the length is greater, check whether the user just added trailing
401 * digits to the local portion of the principal to form the password.
403 if (strlen(password) > strlen(user))
404 if (strncasecmp(password, user, strlen(user)) == 0) {
405 q = password + strlen(user);
406 while (isdigit((unsigned char) *q))
410 return strength_error_generic(ctx, ERROR_USERNAME);
415 /* Check the password against CrackLib if it is configured. */
416 if (data->dictionary != NULL) {
417 code = strength_check_cracklib(ctx, data, password);
422 /* Check the password against CDB if it is configured. */
423 if (data->have_cdb) {
424 code = strength_check_cdb(ctx, data, password);
433 * Cleanly shut down the password strength plugin. The only thing we have to
434 * do is free the memory allocated for our internal data.
437 strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
440 strength_close_cdb(ctx, data);
441 free(data->dictionary);