]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/cdb.c
Revert "Fix edit distance checking and add a test suite"
[kerberos/krb5-strength.git] / plugin / cdb.c
1 /*
2  * Check a CDB database for a password or some simple permutations.
3  *
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.
10  *
11  * Written by Russ Allbery <eagle@eyrie.org>
12  * Copyright 2013, 2014
13  *     The Board of Trustees of the Leland Stanford Junior University
14  *
15  * See LICENSE for licensing terms.
16  */
17
18 #include <config.h>
19 #include <portable/kadmin.h>
20 #include <portable/krb5.h>
21 #include <portable/system.h>
22
23 #ifdef HAVE_CDB_H
24 # include <cdb.h>
25 #endif
26 #include <ctype.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <sys/stat.h>
30
31 #include <plugin/internal.h>
32 #include <util/macros.h>
33
34
35 /*
36  * Stub for strength_init_cdb if not built with CDB support.
37  */
38 #ifndef HAVE_CDB
39 krb5_error_code
40 strength_init_cdb(krb5_context ctx, krb5_pwqual_moddata data UNUSED)
41 {
42     char *path = NULL;
43
44     /* Get CDB dictionary path from krb5.conf. */
45     strength_config_string(ctx, "password_dictionary_cdb", &path);
46
47     /* If it was set, report an error, since we don't have CDB support. */
48     if (path == NULL)
49         return 0;
50     free(path);
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;
54 }
55 #endif
56
57
58 /* Skip the rest of this file if CDB is not available. */
59 #ifdef HAVE_CDB
60
61 /*
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.
65  */
66 # define CHECK_PASSWORD(ctx, data, password)                    \
67     do {                                                        \
68         code = in_cdb_dictionary(ctx, data, password, &found);  \
69         if (code != 0)                                          \
70             goto fail;                                          \
71         if (found)                                              \
72             goto found;                                         \
73     } while (0)
74 # define CHECK_PASSWORD_VARIANT(ctx, data, template, p)                   \
75     do {                                                                  \
76         code = variant_in_cdb_dictionary(ctx, data, template, p, &found); \
77         if (code != 0)                                                    \
78             goto fail;                                                    \
79         if (found)                                                        \
80             goto found;                                                   \
81     } while (0)
82
83
84 /*
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.
88  */
89 static krb5_error_code
90 in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data,
91                   const char *password, bool *found)
92 {
93     int status;
94
95     *found = false;
96     status = cdb_find(&data->cdb, password, strlen(password));
97     if (status < 0)
98         return strength_error_system(ctx, "cannot query CDB database");
99     else {
100         *found = (status == 1);
101         return 0;
102     }
103 }
104
105
106 /*
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.
112  */
113 static krb5_error_code
114 variant_in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data,
115                           char *template, char *permute, bool *found)
116 {
117     int c;
118     krb5_error_code code;
119
120     *found = false;
121     for (c = 0; c <= 127; c++)
122         if (isprint(c)) {
123             *permute = c;
124             code = in_cdb_dictionary(ctx, data, template, found);
125             if (code != 0 || found)
126                 return code;
127         }
128     return 0;
129 }
130
131
132 /*
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
136  * returns an error.
137  */
138 krb5_error_code
139 strength_init_cdb(krb5_context ctx, krb5_pwqual_moddata data)
140 {
141     krb5_error_code code;
142     char *path = NULL;
143
144     /* Get CDB dictionary path from krb5.conf. */
145     strength_config_string(ctx, "password_dictionary_cdb", &path);
146
147     /* If there is no configured dictionary, nothing to do. */
148     if (path == NULL)
149         return 0;
150
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);
157         free(path);
158         close(data->cdb_fd);
159         data->cdb_fd = -1;
160         return code;
161     }
162     free(path);
163     data->have_cdb = true;
164     return 0;
165 }
166
167
168 /*
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
172  * dictionary.
173  */
174 krb5_error_code
175 strength_check_cdb(krb5_context ctx, krb5_pwqual_moddata data,
176                    const char *password)
177 {
178     krb5_error_code code;
179     bool found;
180     size_t length, i;
181     char *p;
182     char *variant = NULL;
183
184     /* If we have no dictionary, there is nothing to do. */
185     if (!data->have_cdb)
186         return 0;
187
188     /* Check the basic password. */
189     CHECK_PASSWORD(ctx, data, password);
190
191     /* Allocate memory for password variations. */
192     length = strlen(password);
193     variant = malloc(length + 2);
194     if (variant == NULL)
195         return strength_error_system(ctx, "cannot allocate memory");
196
197     /* Check all one-character deletions. */
198     for (i = 0; i < length; i++) {
199         if (i > 0)
200             memcpy(variant, password, i);
201         if (i < length - 1)
202             memcpy(variant + i, password + i + 1, length - i - 1);
203         variant[length - 1] = '\0';
204         CHECK_PASSWORD(ctx, data, variant);
205     }
206
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);
211
212     /* Check all one-character additions. */
213     for (i = 0; i <= length; i++) {
214         if (i > 0)
215             memcpy(variant, password, i);
216         if (i < length)
217             memcpy(variant + i + 1, password + i, length - i);
218         variant[length + 1] = '\0';
219         CHECK_PASSWORD_VARIANT(ctx, data, variant, variant + i);
220     }
221
222     /*
223      * Check the password with first and last, two leading, or two trailing
224      * characters removed.
225      */
226     if (length > 2) {
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);
235     }
236
237     /* Password not found. */
238     free(variant);
239     return 0;
240
241 found:
242     /* We found the password or a variant in the dictionary. */
243     free(variant);
244     return strength_error_dict(ctx, ERROR_DICT);
245
246 fail:
247     /* Some sort of failure during CDB lookup. */
248     free(variant);
249     return code;
250 }
251
252
253 /*
254  * Free internal TinyCDB state and close the CDB dictionary.
255  */
256 void
257 strength_close_cdb(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
258 {
259     if (data->have_cdb)
260         cdb_free(&data->cdb);
261     if (data->cdb_fd != -1)
262         close(data->cdb_fd);
263 }
264
265 #endif /* HAVE_CDB */