]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/sqlite.c
Change CrackLib tests for system CrackLib
[kerberos/krb5-strength.git] / plugin / sqlite.c
1 /*
2  * Check a SQLite database for a password within edit distance one.
3  *
4  * This file implements yet another variation on dictionary lookups.
5  * Passwords are checked against a SQLite database (generally created with the
6  * krb5-strength-wordlist utility) that holds words and reversed words, and
7  * all passwords within edit distance one of a word in the database are
8  * rejected.
9  *
10  * To find passwords within edit distance one, this algorithm checks, for each
11  * dictionary word, whether the length of longest common prefix plus the
12  * length of the longest common suffix between that word and the password is
13  * within 1 of the length of the password.  It will be one less if a letter
14  * has been removed or replaced, and equal if the password is an exact match.
15  *
16  * To do this, the SQLite database contains one row for each dictionary word,
17  * containing both the word and the reversed version of the word.  The
18  * password is divided into two components, a prefix and a suffix.  It is
19  * checked against all dictionary words that fall lexicographically between
20  * the prefix and the prefix with its last character incremented, and then
21  * against all words where the word reversed falls lexicographically between
22  * the suffix reversed and the suffix reversed with its last character
23  * incremented.
24  *
25  * If the password matches a dictionary word, the edit must either be in the
26  * first half of the password or the last half of the password.  If in the
27  * first half, the word it will match will fall in the prefix range.  If in
28  * the last half, the word it will match will fall in the suffix range.
29  *
30  * Written by Russ Allbery <eagle@eyrie.org>
31  * Based on work by David Mazières
32  * Copyright 2016 Russ Allbery <eagle@eyrie.org>
33  * Copyright 2014
34  *     The Board of Trustees of the Leland Stanford Junior University
35  *
36  * See LICENSE for licensing terms.
37  */
38
39 #include <config.h>
40 #include <portable/kadmin.h>
41 #include <portable/krb5.h>
42 #include <portable/system.h>
43
44 #ifdef HAVE_SQLITE3_H
45 # include <sqlite3.h>
46 #endif
47
48 #include <plugin/internal.h>
49 #include <util/macros.h>
50
51 /*
52  * The prefix and suffix SQLite query.  Finds all candidate words in range of
53  * the prefix or suffix.  The prefix query should get bind variables for the
54  * prefix and the prefix with the last character incremented; the suffix query
55  * gets the same, but the suffix should be reversed.
56  */
57 #define PREFIX_QUERY \
58     "SELECT password, drowssap FROM passwords WHERE password BETWEEN ? AND ?;"
59 #define SUFFIX_QUERY \
60     "SELECT password, drowssap FROM passwords WHERE drowssap BETWEEN ? AND ?;"
61
62
63 /*
64  * Stub for strength_init_sqlite if not built with SQLite support.
65  */
66 #ifndef HAVE_SQLITE
67 krb5_error_code
68 strength_init_sqlite(krb5_context ctx, krb5_pwqual_moddata data UNUSED)
69 {
70     char *path = NULL;
71
72     /* Get CDB dictionary path from krb5.conf. */
73     strength_config_string(ctx, "password_dictionary_sqlite", &path);
74
75     /* If it was set, report an error, since we don't have CDB support. */
76     if (path == NULL)
77         return 0;
78     free(path);
79     krb5_set_error_message(ctx, KADM5_BAD_SERVER_PARAMS, "SQLite dictionary"
80                            " requested but not built with SQLite support");
81     return KADM5_BAD_SERVER_PARAMS;
82 }
83 #endif
84
85
86 /* Skip the rest of this file if SQLite is not available. */
87 #ifdef HAVE_SQLITE
88
89 /*
90  * Report a SQLite error.  Takes the module data (used to access the SQLite
91  * object) and the Kerberos context, stores the SQLite error in the Kerberos
92  * context, and returns the generic KADM5_FAILURE code, since there doesn't
93  * appear to be anything better.
94  */
95 static krb5_error_code __attribute__((__format__(printf, 3, 4)))
96 error_sqlite(krb5_context ctx, krb5_pwqual_moddata data, const char *format,
97              ...)
98 {
99     va_list args;
100     ssize_t length;
101     char *message;
102     const char *errmsg;
103     
104     errmsg = sqlite3_errmsg(data->sqlite);
105     va_start(args, format);
106     length = vasprintf(&message, format, args);
107     va_end(args);
108     if (length < 0)
109         return strength_error_system(ctx, "cannot allocate memory");
110     krb5_set_error_message(ctx, KADM5_FAILURE, "%s: %s", message, errmsg);
111     free(message);
112     return KADM5_FAILURE;
113 }
114
115
116 /*
117  * Given a string, returns a reversed version of that string in newly
118  * allocated memory.  The caller is responsible for freeing.  Returns NULL on
119  * memory allocation failure.
120  */
121 static char *
122 reverse_string(const char *string)
123 {
124     size_t length, i;
125     char *reversed;
126
127     length = strlen(string);
128     reversed = malloc(length + 1);
129     if (reversed == NULL)
130         return NULL;
131     reversed[length] = '\0';
132     for (i = 0; i < length; i++)
133         reversed[length - i - 1] = string[i];
134     return reversed;
135 }
136
137
138 /*
139  * Given two strings, return the length of their common prefix, not counting
140  * the nul character that terminates either string.
141  */
142 static size_t
143 common_prefix_length(const char *a, const char *b)
144 {
145     size_t i;
146
147     for (i = 0; a[i] == b[i] && a[i] != '\0' && b[i] != '\0'; i++)
148         ;
149     return i;
150 }
151
152
153 /*
154  * Given the length of the password, the password, the reversed password, and
155  * an executed SQLite statement that contains the word and reversed word as
156  * the first two column texts, determine whether this password is a match
157  * within edit distance one.
158  *
159  * It will be a match if the length of the common prefix of the password and
160  * word plus the length of the common prefix of the reversed password and the
161  * reversed word (which is the length of the common suffix) is greater than or
162  * equal to the length of the password minus one.
163  *
164  * To see why the sum of the prefix and suffix length can be longer than the
165  * length of the password when the password doesn't match the word, consider
166  * the password "aaaa" and the word "aaaaaaaaa"
167  * (The prefix length plus the
168  * suffix length may be greater than the length of the password if the
169  * password is an exact match for the word or 
170  */
171 static bool
172 match(size_t length, const char *password, const char *drowssap,
173       sqlite3_stmt *query)
174 {
175     const char *word, *drow;
176     size_t prefix_length, suffix_length, match_length, word_length;
177
178     /* Discard all words whose length is too different. */
179     word = (const char *) sqlite3_column_text(query, 0);
180     word_length = strlen(word);
181     if (length > word_length + 1 || length + 1 < word_length)
182         return false;
183
184     /*
185      * Get the common prefix length and check if the password is an exact
186      * match.
187      */
188     prefix_length = common_prefix_length(password, word);
189     if (prefix_length == length)
190         return true;
191
192     /*
193      * Ensure there aren't too many different characters for this to be a
194      * match.  If the common prefix and the common suffix together have a
195      * length that's more than one character shorter than the password length,
196      * this is different by at least edit distance two.  The sum of the
197      * lengths of the common prefix and suffix can be greater than length in
198      * cases of an edit in the middle of repeated passwords, such as the
199      * password "baaab" and the word "baab", but those are all matches.
200      */
201     drow = (const char *) sqlite3_column_text(query, 1);
202     suffix_length = common_prefix_length(drowssap, drow);
203     match_length = prefix_length + suffix_length;
204     return (match_length > length || length - match_length <= 1);
205 }
206
207
208 /*
209  * Initialize the SQLite dictionary.  Opens the database and compiles the two
210  * queries that we'll use.  Returns 0 on success, non-zero on failure (and
211  * sets the error in the Kerberos context).
212  */
213 krb5_error_code
214 strength_init_sqlite(krb5_context ctx, krb5_pwqual_moddata data)
215 {
216     char *path = NULL;
217     int status;
218
219     /* Get SQLite dictionary path from krb5.conf. */
220     strength_config_string(ctx, "password_dictionary_sqlite", &path);
221
222     /* If there is no configured dictionary, nothing to do. */
223     if (path == NULL)
224         return 0;
225
226     /* Open the database. */
227     status = sqlite3_open_v2(path, &data->sqlite, SQLITE_OPEN_READONLY, NULL);
228     if (status != 0)
229         return error_sqlite(ctx, data, "cannot open dictionary %s", path);
230
231     /* Precompile the queries we'll use. */
232     status = sqlite3_prepare_v2(data->sqlite, PREFIX_QUERY, -1,
233                                 &data->prefix_query, NULL);
234     if (status != 0)
235         return error_sqlite(ctx, data, "cannot prepare prefix query");
236     status = sqlite3_prepare_v2(data->sqlite, SUFFIX_QUERY, -1,
237                                 &data->suffix_query, NULL);
238     if (status != 0)
239         return error_sqlite(ctx, data, "cannot prepare suffix query");
240
241     /* Finished.  Return success. */
242     free(path);
243     return 0;
244 }
245
246
247 /*
248  * Given a password, look for a word in the database within edit distance one.
249  * The full algorithm used here is described in the comment at the start of
250  * this file.  Returns a Kerberos status code, which will be KADM5_PASS_Q_DICT
251  * if the password was found in the dictionary.
252  */
253 krb5_error_code
254 strength_check_sqlite(krb5_context ctx, krb5_pwqual_moddata data,
255                       const char *password)
256 {
257     krb5_error_code code;
258     size_t length, prefix_length, suffix_length;
259     char *prefix = NULL;
260     char *drowssap = NULL;
261     bool found = false;
262     int status;
263
264     /* If we have no dictionary, there is nothing to do. */
265     if (data->sqlite == NULL)
266         return 0;
267
268     /*
269      * Determine the length of the prefix and suffix into which we'll divide
270      * the string.  Passwords shorter than two characters cannot be
271      * meaningfully checked using this method and cause boundary condition
272      * problems.
273      */
274     length = strlen(password);
275     if (length < 2)
276         return 0;
277     prefix_length = length / 2;
278     suffix_length = length - prefix_length;
279
280     /* Obtain the reversed password, used for suffix checks. */
281     drowssap = reverse_string(password);
282     if (drowssap == NULL)
283         return strength_error_system(ctx, "cannot allocate memory");
284
285     /* Set up the query for prefix matching. */
286     prefix = strdup(password);
287     if (prefix == NULL) {
288         code = strength_error_system(ctx, "cannot allocate memory");
289         goto fail;
290     }
291     status = sqlite3_bind_text(data->prefix_query, 1, password, prefix_length,
292                                NULL);
293     if (status != SQLITE_OK) {
294         code = error_sqlite(ctx, data, "cannot bind prefix start");
295         goto fail;
296     }
297     prefix[prefix_length - 1]++;
298     status = sqlite3_bind_text(data->prefix_query, 2, prefix, prefix_length,
299                                NULL);
300     if (status != SQLITE_OK) {
301         code = error_sqlite(ctx, data, "cannot bind prefix end");
302         goto fail;
303     }
304
305     /*
306      * Do prefix matching.  Get the set of all database entries starting with
307      * the same prefix and, for each, check whether our password matches that
308      * entry within edit distance one.
309      */
310     while ((status = sqlite3_step(data->prefix_query)) == SQLITE_ROW)
311         if (match(length, password, drowssap, data->prefix_query)) {
312             found = true;
313             break;
314         }
315     if (status != SQLITE_DONE && status != SQLITE_ROW) {
316         code = error_sqlite(ctx, data, "error searching by password prefix");
317         goto fail;
318     }
319     status = sqlite3_reset(data->prefix_query);
320     if (status != SQLITE_OK) {
321         code = error_sqlite(ctx, data, "error resetting prefix query");
322         goto fail;
323     }
324     if (found)
325         goto found;
326
327     /* Set up the query for suffix matching. */
328     status = sqlite3_bind_text(data->suffix_query, 1, drowssap, suffix_length,
329                                SQLITE_TRANSIENT);
330     if (status != SQLITE_OK) {
331         code = error_sqlite(ctx, data, "cannot bind suffix start");
332         goto fail;
333     }
334     drowssap[prefix_length - 1]++;
335     status = sqlite3_bind_text(data->suffix_query, 2, drowssap, suffix_length,
336                                SQLITE_TRANSIENT);
337     drowssap[prefix_length - 1]--;
338     if (status != SQLITE_OK) {
339         code = error_sqlite(ctx, data, "cannot bind suffix end");
340         goto fail;
341     }
342
343     /*
344      * Do suffix matching.  Get the set of all database entries starting with
345      * the same prefix and, for each, check whether our password matches that
346      * entry within edit distance one.
347      */
348     while ((status = sqlite3_step(data->suffix_query)) == SQLITE_ROW)
349         if (match(length, password, drowssap, data->suffix_query)) {
350             found = true;
351             break;
352         }
353     if (status != SQLITE_DONE && status != SQLITE_ROW) {
354         code = error_sqlite(ctx, data, "error searching by password suffix");
355         goto fail;
356     }
357     status = sqlite3_reset(data->suffix_query);
358     if (status != SQLITE_OK) {
359         code = error_sqlite(ctx, data, "error resetting suffix query");
360         goto fail;
361     }
362     if (found)
363         goto found;
364
365     /* No match.  Clean up and return success. */
366     memset(prefix, 0, length);
367     memset(drowssap, 0, length);
368     free(prefix);
369     free(drowssap);
370     return 0;
371
372 found:
373     /* We found the password in the dictionary. */
374     code = strength_error_dict(ctx, ERROR_DICT);
375
376 fail:
377     memset(prefix, 0, length);
378     memset(drowssap, 0, length);
379     free(prefix);
380     free(drowssap);
381     return code;
382 }
383
384
385 /*
386  * Free internal SQLite state and close the SQLite database.
387  */
388 void
389 strength_close_sqlite(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
390 {
391     if (data->prefix_query != NULL)
392         sqlite3_finalize(data->prefix_query);
393     if (data->suffix_query != NULL)
394         sqlite3_finalize(data->suffix_query);
395     if (data->sqlite != NULL)
396         sqlite3_close(data->sqlite);
397 }
398
399 #endif /* HAVE_SQLITE */