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