]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/cdb.c
3207fc996d66c6ac0f836163d7ec36bfeee6796d
[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_EDIT(ctx, data, template, p)                    \
75     do {                                                                \
76         code = edit_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 edit_in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data,
115                        char *template, char *permute, bool *found)
116 {
117     int c, save;
118     krb5_error_code code;
119
120     *found = false;
121     save = *permute;
122     for (c = 0; c <= 127; c++)
123         if (isprint(c)) {
124             *permute = c;
125             code = in_cdb_dictionary(ctx, data, template, found);
126             if (code != 0 || *found)
127                 return code;
128         }
129     *permute = save;
130     return 0;
131 }
132
133
134 /*
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
138  * returns an error.
139  */
140 krb5_error_code
141 strength_init_cdb(krb5_context ctx, krb5_pwqual_moddata data)
142 {
143     krb5_error_code code;
144     char *path = NULL;
145
146     /* Get CDB dictionary path from krb5.conf. */
147     strength_config_string(ctx, "password_dictionary_cdb", &path);
148
149     /* If there is no configured dictionary, nothing to do. */
150     if (path == NULL)
151         return 0;
152
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);
159         free(path);
160         close(data->cdb_fd);
161         data->cdb_fd = -1;
162         return code;
163     }
164     free(path);
165     data->have_cdb = true;
166     return 0;
167 }
168
169
170 /*
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
174  * dictionary.
175  */
176 krb5_error_code
177 strength_check_cdb(krb5_context ctx, krb5_pwqual_moddata data,
178                    const char *password)
179 {
180     krb5_error_code code;
181     bool found;
182     size_t length, i;
183     char *p;
184     char *variant = NULL;
185
186     /* If we have no dictionary, there is nothing to do. */
187     if (!data->have_cdb)
188         return 0;
189
190     /* Check the basic password. */
191     CHECK_PASSWORD(ctx, data, password);
192
193     /* Allocate memory for password variations. */
194     length = strlen(password);
195     variant = malloc(length + 2);
196     if (variant == NULL)
197         return strength_error_system(ctx, "cannot allocate memory");
198
199     /* Check all one-character deletions. */
200     for (i = 0; i < length; i++) {
201         if (i > 0)
202             memcpy(variant, password, i);
203         if (i < length - 1)
204             memcpy(variant + i, password + i + 1, length - i - 1);
205         variant[length - 1] = '\0';
206         CHECK_PASSWORD(ctx, data, variant);
207     }
208
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);
213
214     /* Check all one-character additions. */
215     for (i = 0; i <= length; i++) {
216         if (i > 0)
217             memcpy(variant, password, i);
218         if (i < length)
219             memcpy(variant + i + 1, password + i, length - i);
220         variant[length + 1] = '\0';
221         CHECK_PASSWORD_EDIT(ctx, data, variant, variant + i);
222     }
223
224     /*
225      * Check the password with first and last, two leading, or two trailing
226      * characters removed.
227      */
228     if (length > 2) {
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);
237     }
238
239     /* Password not found. */
240     free(variant);
241     return 0;
242
243 found:
244     /* We found the password or a variant in the dictionary. */
245     free(variant);
246     return strength_error_dict(ctx, ERROR_DICT);
247
248 fail:
249     /* Some sort of failure during CDB lookup. */
250     free(variant);
251     return code;
252 }
253
254
255 /*
256  * Free internal TinyCDB state and close the CDB dictionary.
257  */
258 void
259 strength_close_cdb(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
260 {
261     if (data->have_cdb)
262         cdb_free(&data->cdb);
263     if (data->cdb_fd != -1)
264         close(data->cdb_fd);
265 }
266
267 #endif /* HAVE_CDB */