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_EDIT(ctx, data, template, p) \
76 code = edit_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 edit_in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data,
115 char *template, char *permute, bool *found)
118 krb5_error_code code;
122 for (c = 0; c <= 127; c++)
125 code = in_cdb_dictionary(ctx, data, template, found);
126 if (code != 0 || *found)
135 * Initialize the CDB dictionary. Opens the dictionary and sets up the
136 * TinyCDB state. Returns 0 on success, non-zero on failure (and sets the
137 * error in the Kerberos context). If not built with CDB support, always
141 strength_init_cdb(krb5_context ctx, krb5_pwqual_moddata data)
143 krb5_error_code code;
146 /* Get CDB dictionary path from krb5.conf. */
147 strength_config_string(ctx, "password_dictionary_cdb", &path);
149 /* If there is no configured dictionary, nothing to do. */
153 /* Open the dictionary and initialize the CDB data. */
154 data->cdb_fd = open(path, O_RDONLY);
155 if (data->cdb_fd < 0)
156 return strength_error_system(ctx, "cannot open dictionary %s", path);
157 if (cdb_init(&data->cdb, data->cdb_fd) < 0) {
158 code = strength_error_system(ctx, "cannot init dictionary %s", path);
165 data->have_cdb = true;
171 * Given a password, try the various transformations that we want to apply and
172 * check for each of them in the dictionary. Returns a Kerberos status code,
173 * which will be KADM5_PASS_Q_DICT if the password was found in the
177 strength_check_cdb(krb5_context ctx, krb5_pwqual_moddata data,
178 const char *password)
180 krb5_error_code code;
184 char *variant = NULL;
186 /* If we have no dictionary, there is nothing to do. */
190 /* Check the basic password. */
191 CHECK_PASSWORD(ctx, data, password);
193 /* Allocate memory for password variations. */
194 length = strlen(password);
195 variant = malloc(length + 2);
197 return strength_error_system(ctx, "cannot allocate memory");
199 /* Check all one-character deletions. */
200 for (i = 0; i < length; i++) {
202 memcpy(variant, password, i);
204 memcpy(variant + i, password + i + 1, length - i - 1);
205 variant[length - 1] = '\0';
206 CHECK_PASSWORD(ctx, data, variant);
209 /* Check all one-character permutations. */
210 memcpy(variant, password, length + 1);
211 for (p = variant; *p != '\0'; p++)
212 CHECK_PASSWORD_EDIT(ctx, data, variant, p);
214 /* Check all one-character additions. */
215 for (i = 0; i <= length; i++) {
217 memcpy(variant, password, i);
219 memcpy(variant + i + 1, password + i, length - i);
220 variant[length + 1] = '\0';
221 CHECK_PASSWORD_EDIT(ctx, data, variant, variant + i);
225 * Check the password with first and last, two leading, or two trailing
226 * characters removed.
229 memcpy(variant, password + 2, length - 1);
230 CHECK_PASSWORD(ctx, data, variant);
231 memcpy(variant, password + 1, length - 2);
232 variant[length - 2] = '\0';
233 CHECK_PASSWORD(ctx, data, variant);
234 memcpy(variant, password, length - 2);
235 variant[length - 2] = '\0';
236 CHECK_PASSWORD(ctx, data, variant);
239 /* Password not found. */
244 /* We found the password or a variant in the dictionary. */
246 return strength_error_dict(ctx, ERROR_DICT);
249 /* Some sort of failure during CDB lookup. */
256 * Free internal TinyCDB state and close the CDB dictionary.
259 strength_close_cdb(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
262 cdb_free(&data->cdb);
263 if (data->cdb_fd != -1)
267 #endif /* HAVE_CDB */