2 * Check a CDB database for a password or some simple permutations.
4 * This file implements a much simpler variation on CrackLib checks intended
5 * for use with longer passwords where some of the CrackLib permutations don't
6 * make as much sense. A CDB database with passwords as keys is checked for
7 * the password and for variations with one character removed from the start
8 * or end, two characters removed from the start, two from the end, or one
9 * character from both start and end.
11 * Written by Russ Allbery <eagle@eyrie.org>
12 * Copyright 2013, 2014
13 * The Board of Trustees of the Leland Stanford Junior University
15 * See LICENSE for licensing terms.
19 #include <portable/kadmin.h>
20 #include <portable/krb5.h>
21 #include <portable/system.h>
31 #include <plugin/internal.h>
32 #include <util/macros.h>
36 * Stub for strength_init_cdb if not built with CDB support.
40 strength_init_cdb(krb5_context ctx, krb5_pwqual_moddata data UNUSED)
44 /* Get CDB dictionary path from krb5.conf. */
45 strength_config_string(ctx, "password_dictionary_cdb", &path);
47 /* If it was set, report an error, since we don't have CDB support. */
51 krb5_set_error_message(ctx, KADM5_BAD_SERVER_PARAMS, "CDB dictionary"
52 " requested but not built with CDB support");
53 return KADM5_BAD_SERVER_PARAMS;
58 /* Skip the rest of this file if CDB is not available. */
62 * Macros used to make password checks more readable. Assumes that the found
63 * and fail labels are available for the abort cases of finding a password or
64 * failing to look it up.
66 # define CHECK_PASSWORD(ctx, data, password) \
68 code = in_cdb_dictionary(ctx, data, password, &found); \
74 # define CHECK_PASSWORD_VARIANT(ctx, data, template, p) \
76 code = variant_in_cdb_dictionary(ctx, data, template, p, &found); \
85 * Look up a password in CDB and set the found parameter to true if it is
86 * found, false otherwise. Returns a Kerberos status code, which will be 0 on
87 * success and something else on failure.
89 static krb5_error_code
90 in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data,
91 const char *password, bool *found)
96 status = cdb_find(&data->cdb, password, strlen(password));
98 return strength_error_system(ctx, "cannot query CDB database");
100 *found = (status == 1);
107 * Given a password template and a pointer to the character to change, check
108 * all versions of that template with that character replaced by all possible
109 * printable ASCII characters. The template will be modified in place to try
110 * the various characters. Sets the found parameter to true if some variation
111 * of the template is found, false otherwise. Returns a Kerberos status code.
113 static krb5_error_code
114 variant_in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data,
115 char *template, char *permute, bool *found)
118 krb5_error_code code;
121 for (c = 0; c <= 127; c++)
124 code = in_cdb_dictionary(ctx, data, template, found);
125 if (code != 0 || found)
133 * Initialize the CDB dictionary. Opens the dictionary and sets up the
134 * TinyCDB state. Returns 0 on success, non-zero on failure (and sets the
135 * error in the Kerberos context). If not built with CDB support, always
139 strength_init_cdb(krb5_context ctx, krb5_pwqual_moddata data)
141 krb5_error_code code;
144 /* Get CDB dictionary path from krb5.conf. */
145 strength_config_string(ctx, "password_dictionary_cdb", &path);
147 /* If there is no configured dictionary, nothing to do. */
151 /* Open the dictionary and initialize the CDB data. */
152 data->cdb_fd = open(path, O_RDONLY);
153 if (data->cdb_fd < 0)
154 return strength_error_system(ctx, "cannot open dictionary %s", path);
155 if (cdb_init(&data->cdb, data->cdb_fd) < 0) {
156 code = strength_error_system(ctx, "cannot init dictionary %s", path);
163 data->have_cdb = true;
169 * Given a password, try the various transformations that we want to apply and
170 * check for each of them in the dictionary. Returns a Kerberos status code,
171 * which will be KADM5_PASS_Q_DICT if the password was found in the
175 strength_check_cdb(krb5_context ctx, krb5_pwqual_moddata data,
176 const char *password)
178 krb5_error_code code;
182 char *variant = NULL;
184 /* If we have no dictionary, there is nothing to do. */
188 /* Check the basic password. */
189 CHECK_PASSWORD(ctx, data, password);
191 /* Allocate memory for password variations. */
192 length = strlen(password);
193 variant = malloc(length + 2);
195 return strength_error_system(ctx, "cannot allocate memory");
197 /* Check all one-character deletions. */
198 for (i = 0; i < length; i++) {
200 memcpy(variant, password, i);
202 memcpy(variant + i, password + i + 1, length - i - 1);
203 variant[length - 1] = '\0';
204 CHECK_PASSWORD(ctx, data, variant);
207 /* Check all one-character permutations. */
208 memcpy(variant, password, length + 1);
209 for (p = variant; *p != '\0'; p++)
210 CHECK_PASSWORD_VARIANT(ctx, data, variant, p);
212 /* Check all one-character additions. */
213 for (i = 0; i <= length; i++) {
215 memcpy(variant, password, i);
217 memcpy(variant + i + 1, password + i, length - i);
218 variant[length + 1] = '\0';
219 CHECK_PASSWORD_VARIANT(ctx, data, variant, variant + i);
223 * Check the password with first and last, two leading, or two trailing
224 * characters removed.
227 memcpy(variant, password + 2, length - 1);
228 CHECK_PASSWORD(ctx, data, variant);
229 memcpy(variant, password + 1, length - 2);
230 variant[length - 2] = '\0';
231 CHECK_PASSWORD(ctx, data, variant);
232 memcpy(variant, password, length - 2);
233 variant[length - 2] = '\0';
234 CHECK_PASSWORD(ctx, data, variant);
237 /* Password not found. */
242 /* We found the password or a variant in the dictionary. */
244 return strength_error_dict(ctx, ERROR_DICT);
247 /* Some sort of failure during CDB lookup. */
254 * Free internal TinyCDB state and close the CDB dictionary.
257 strength_close_cdb(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
260 cdb_free(&data->cdb);
261 if (data->cdb_fd != -1)
265 #endif /* HAVE_CDB */