/*
* Stub for strength_init_sqlite if not built with SQLite support.
*/
-#ifndef HAVE_SQLITE3
+#ifndef HAVE_SQLITE
krb5_error_code
strength_init_sqlite(krb5_context ctx, krb5_pwqual_moddata data UNUSED)
{
/* Skip the rest of this file if SQLite is not available. */
-#ifdef HAVE_SQLITE3
+#ifdef HAVE_SQLITE
/*
- * Report a SQLite error. Takes the SQLite error code and the Kerberos
- * context, stores the resulting error in the Kerberos context, and returns
- * the generic KADM5_FAILURE code, since there doesn't appear to be anything
- * better.
+ * Report a SQLite error. Takes the module data (used to access the SQLite
+ * object) and the Kerberos context, stores the SQLite error in the Kerberos
+ * context, and returns the generic KADM5_FAILURE code, since there doesn't
+ * appear to be anything better.
*/
static krb5_error_code
-error_sqlite(krb5_context ctx, int status, const char *format, ...)
+error_sqlite(krb5_context ctx, krb5_pwqual_moddata data, const char *format,
+ ...)
{
va_list args;
ssize_t length;
char *message;
- const char *errstr;
+ const char *errmsg;
- errstr = sqlite3_errstr(status);
+ errmsg = sqlite3_errmsg(data->sqlite);
va_start(args, format);
length = vasprintf(&message, format, args);
va_end(args);
if (length < 0)
return strength_error_system(ctx, "cannot allocate memory");
- krb5_set_error_message(ctx, KADM5_FAILURE, "%s: %s", message, errstr);
+ krb5_set_error_message(ctx, KADM5_FAILURE, "%s: %s", message, errmsg);
free(message);
return KADM5_FAILURE;
}
* the first two column texts, determine whether this password is a match
* within edit distance one.
*
- * It will be a match if the length of the common prefix of the passwod and
+ * It will be a match if the length of the common prefix of the password and
* word plus the length of the common prefix of the reversed password and the
- * reversed word (which is the length of the common suffix) is within 1 of the
- * length of the password.
+ * reversed word (which is the length of the common suffix) is greater than or
+ * equal to the length of the password minus one.
+ *
+ * To see why the sum of the prefix and suffix length can be longer than the
+ * length of the password when the password doesn't match the word, consider
+ * the password "aaaa" and the word "aaaaaaaaa"
+ * (The prefix length plus the
+ * suffix length may be greater than the length of the password if the
+ * password is an exact match for the word or
*/
static bool
match(size_t length, const char *password, const char *drowssap,
sqlite3_stmt *query)
{
const char *word, *drow;
- size_t prefix_length, suffix_length;
+ size_t prefix_length, suffix_length, match_length, word_length;
+ /* Discard all words whose length is too different. */
word = (const char *) sqlite3_column_text(query, 0);
- drow = (const char *) sqlite3_column_text(query, 1);
+ word_length = strlen(word);
+ if (length > word_length + 1 || length + 1 < word_length)
+ return false;
+
+ /*
+ * Get the common prefix length and check if the password is an exact
+ * match.
+ */
prefix_length = common_prefix_length(password, word);
if (prefix_length == length)
return true;
+
+ /*
+ * Ensure there aren't too many different characters for this to be a
+ * match. If the common prefix and the common suffix together have a
+ * length that's more than one character shorter than the password length,
+ * this is different by at least edit distance two. The sum of the
+ * lengths of the common prefix and suffix can be greater than length in
+ * cases of an edit in the middle of repeated passwords, such as the
+ * password "baaab" and the word "baab", but those are all matches.
+ */
+ drow = (const char *) sqlite3_column_text(query, 1);
suffix_length = common_prefix_length(drowssap, drow);
- return (length - prefix_length - suffix_length <= 1);
+ match_length = prefix_length + suffix_length;
+ return (match_length > length || length - match_length <= 1);
}
/* Open the database. */
status = sqlite3_open_v2(path, &data->sqlite, SQLITE_OPEN_READONLY, NULL);
if (status != 0)
- return error_sqlite(ctx, status, "cannot open dictionary %s", path);
+ return error_sqlite(ctx, data, "cannot open dictionary %s", path);
/* Precompile the queries we'll use. */
status = sqlite3_prepare_v2(data->sqlite, PREFIX_QUERY, -1,
&data->prefix_query, NULL);
if (status != 0)
- return error_sqlite(ctx, status, "cannot prepare prefix query");
+ return error_sqlite(ctx, data, "cannot prepare prefix query");
status = sqlite3_prepare_v2(data->sqlite, SUFFIX_QUERY, -1,
&data->suffix_query, NULL);
if (status != 0)
- return error_sqlite(ctx, status, "cannot prepare suffix query");
+ return error_sqlite(ctx, data, "cannot prepare suffix query");
/* Finished. Return success. */
+ free(path);
return 0;
}
status = sqlite3_bind_text(data->prefix_query, 1, password, prefix_length,
NULL);
if (status != SQLITE_OK) {
- code = error_sqlite(ctx, status, "cannot bind prefix start");
+ code = error_sqlite(ctx, data, "cannot bind prefix start");
goto fail;
}
prefix[prefix_length - 1]++;
status = sqlite3_bind_text(data->prefix_query, 2, prefix, prefix_length,
NULL);
if (status != SQLITE_OK) {
- code = error_sqlite(ctx, status, "cannot bind prefix end");
+ code = error_sqlite(ctx, data, "cannot bind prefix end");
goto fail;
}
break;
}
if (status != SQLITE_DONE && status != SQLITE_ROW) {
- code = error_sqlite(ctx, status, "error searching by password prefix");
+ code = error_sqlite(ctx, data, "error searching by password prefix");
goto fail;
}
status = sqlite3_reset(data->prefix_query);
if (status != SQLITE_OK) {
- code = error_sqlite(ctx, status, "error resetting prefix query");
+ code = error_sqlite(ctx, data, "error resetting prefix query");
goto fail;
}
if (found)
status = sqlite3_bind_text(data->suffix_query, 1, drowssap, suffix_length,
SQLITE_TRANSIENT);
if (status != SQLITE_OK) {
- code = error_sqlite(ctx, status, "cannot bind suffix start");
+ code = error_sqlite(ctx, data, "cannot bind suffix start");
goto fail;
}
drowssap[prefix_length - 1]++;
SQLITE_TRANSIENT);
drowssap[prefix_length - 1]--;
if (status != SQLITE_OK) {
- code = error_sqlite(ctx, status, "cannot bind suffix end");
+ code = error_sqlite(ctx, data, "cannot bind suffix end");
goto fail;
}
break;
}
if (status != SQLITE_DONE && status != SQLITE_ROW) {
- code = error_sqlite(ctx, status, "error searching by password suffix");
+ code = error_sqlite(ctx, data, "error searching by password suffix");
goto fail;
}
status = sqlite3_reset(data->suffix_query);
if (status != SQLITE_OK) {
- code = error_sqlite(ctx, status, "error resetting suffix query");
+ code = error_sqlite(ctx, data, "error resetting suffix query");
goto fail;
}
if (found)
if (data->suffix_query != NULL)
sqlite3_finalize(data->suffix_query);
if (data->sqlite != NULL)
- sqlite3_close_v2(data->sqlite);
+ sqlite3_close(data->sqlite);
}
-#endif /* HAVE_CDB */
+#endif /* HAVE_SQLITE */