* wrapped up in those interfaces.
*
* Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates,
- * on behalf of Stanford University.
- * Extensive modifications by Russ Allbery <rra@stanford.edu>
- * Copyright 2006, 2007, 2009, 2012, 2013
- * The Board of Trustees of the Leland Stanford Junior Unversity
+ * on behalf of Stanford University
+ * Extensive modifications by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2006, 2007, 2009, 2012, 2013, 2014
+ * The Board of Trustees of the Leland Stanford Junior University
*
* See LICENSE for licensing terms.
*/
#include <config.h>
-#include <portable/kadmin.h>
#include <portable/krb5.h>
#include <portable/system.h>
-#ifdef HAVE_CDB_H
-# include <cdb.h>
-#endif
#include <ctype.h>
-#include <errno.h>
#include <plugin/internal.h>
#include <util/macros.h>
return strength_error_system(ctx, "cannot allocate memory");
data->cdb_fd = -1;
- /* Get minimum length information from krb5.conf. */
+ /* Get minimum length and character information from krb5.conf. */
+ strength_config_number(ctx, "minimum_different", &data->minimum_different);
strength_config_number(ctx, "minimum_length", &data->minimum_length);
- /* Get character class restrictions from krb5.conf. */
+ /* Get simple character class restrictions from krb5.conf. */
strength_config_boolean(ctx, "require_ascii_printable", &data->ascii);
strength_config_boolean(ctx, "require_non_letter", &data->nonletter);
+ /* Get complex character class restrictions from krb5.conf. */
+ code = strength_config_classes(ctx, "require_classes", &data->rules);
+ if (code != 0)
+ goto fail;
+
/*
- * Try to initialize CDB and CrackLib dictionaries. Both functions handle
- * their own configuration parsing and will do nothing if the
- * corresponding dictionary is not configured.
+ * Try to initialize CDB, CrackLib, and SQLite dictionaries. These
+ * functions handle their own configuration parsing and will do nothing if
+ * the corresponding dictionary is not configured.
*/
code = strength_init_cracklib(ctx, data, dictionary);
if (code != 0)
goto fail;
code = strength_init_cdb(ctx, data);
+ if (code != 0)
+ goto fail;
+ code = strength_init_sqlite(ctx, data);
if (code != 0)
goto fail;
}
+/*
+ * Check if a password has a sufficient number of unique characters. Takes
+ * the password and the required number of characters.
+ */
+static bool
+has_minimum_different(const char *password, long minimum)
+{
+ size_t unique;
+ const char *p;
+
+ /* Special cases for passwords of length 0 and a minimum <= 1. */
+ if (password == NULL || password[0] == '\0')
+ return minimum <= 0;
+ if (minimum <= 1)
+ return true;
+
+ /*
+ * Count the number of unique characters by incrementing the count if each
+ * subsequent character is not found in the previous password characters.
+ * This algorithm is O(n^2), but passwords are short enough it shouldn't
+ * matter.
+ */
+ unique = 1;
+ for (p = password + 1; *p != '\0'; p++)
+ if (memchr(password, *p, p - password) == NULL) {
+ unique++;
+ if (unique >= (size_t) minimum)
+ return true;
+ }
+ return false;
+}
+
+
/*
* Check a given password. Takes a Kerberos context, our module data, the
* password, the principal the password is for, and a buffer and buffer length
*/
krb5_error_code
strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
- const char *password, const char *principal)
+ const char *principal, const char *password)
{
- char *user, *p;
- const char *q;
- size_t i, j;
- char c;
krb5_error_code code;
/* Check minimum length first, since that's easy. */
if (data->nonletter && only_alpha_space(password))
return strength_error_class(ctx, ERROR_LETTER);
- /*
- * We get the principal (in krb5_unparse_name format) and we want to be
- * sure that the password doesn't match the username, the username
- * reversed, or the username with trailing digits. We therefore have to
- * copy the string so that we can manipulate it a bit.
- */
- if (strcasecmp(password, principal) == 0)
- return strength_error_generic(ctx, ERROR_USERNAME);
- user = strdup(principal);
- if (user == NULL)
- return strength_error_system(ctx, "cannot allocate memory");
-
- /* Strip the realm off of the principal. */
- for (p = user; p[0] != '\0'; p++) {
- if (p[0] == '\\' && p[1] != '\0') {
- p++;
- continue;
- }
- if (p[0] == '@') {
- p[0] = '\0';
- break;
- }
- }
+ /* If desired, check for enough unique characters. */
+ if (data->minimum_different > 0)
+ if (!has_minimum_different(password, data->minimum_different))
+ return strength_error_class(ctx, ERROR_MINDIFF);
/*
- * If the length of the password matches the length of the local portion
- * of the principal, check for exact matches or reversed matches.
+ * If desired, check that the password satisfies character class
+ * restrictions.
*/
- if (strlen(password) == strlen(user)) {
- if (strcasecmp(password, user) == 0) {
- free(user);
- return strength_error_generic(ctx, ERROR_USERNAME);
- }
-
- /* Check against the reversed username. */
- for (i = 0, j = strlen(user) - 1; i < j; i++, j--) {
- c = user[i];
- user[i] = user[j];
- user[j] = c;
- }
- if (strcasecmp(password, user) == 0) {
- free(user);
- return strength_error_generic(ctx, ERROR_USERNAME);
- }
- }
+ code = strength_check_classes(ctx, data, password);
+ if (code != 0)
+ return code;
- /*
- * If the length is greater, check whether the user just added trailing
- * digits to the local portion of the principal to form the password.
- */
- if (strlen(password) > strlen(user))
- if (strncasecmp(password, user, strlen(user)) == 0) {
- q = password + strlen(user);
- while (isdigit((unsigned char) *q))
- q++;
- if (*q == '\0') {
- free(user);
- return strength_error_generic(ctx, ERROR_USERNAME);
- }
- }
- free(user);
+ /* Check if the password is based on the principal in some way. */
+ code = strength_check_principal(ctx, data, principal, password);
+ if (code != 0)
+ return code;
- /* Check the password against CDB and CrackLib if configured. */
+ /* Check the password against CDB, CrackLib, and SQLite if configured. */
code = strength_check_cracklib(ctx, data, password);
if (code != 0)
return code;
code = strength_check_cdb(ctx, data, password);
+ if (code != 0)
+ return code;
+ code = strength_check_sqlite(ctx, data, password);
if (code != 0)
return code;
void
strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
{
- if (data != NULL) {
- strength_close_cdb(ctx, data);
- free(data->dictionary);
- free(data);
+ struct class_rule *last, *tmp;
+
+ if (data == NULL)
+ return;
+ strength_close_cdb(ctx, data);
+ strength_close_sqlite(ctx, data);
+ last = data->rules;
+ while (last != NULL) {
+ tmp = last;
+ last = last->next;
+ free(tmp);
}
+ free(data->dictionary);
+ free(data);
}