]> eyrie.org Git - kerberos/krb5-strength.git/blobdiff - plugin/general.c
Merge branch 'debian' into squeeze
[kerberos/krb5-strength.git] / plugin / general.c
index 49a5f4ce20f3a9dad40cee5573163ae239bf12d6..c491ea01b9522742e2fd2afc0ff4f5cb587a4ccd 100644 (file)
@@ -7,24 +7,19 @@
  * 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>
@@ -52,22 +47,31 @@ strength_init(krb5_context ctx, const char *dictionary,
         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;
 
@@ -113,6 +117,39 @@ only_alpha_space(const char *password)
 }
 
 
+/*
+ * 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
@@ -120,12 +157,8 @@ only_alpha_space(const char *password)
  */
 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. */
@@ -148,73 +181,32 @@ strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
     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;
 
@@ -230,9 +222,18 @@ strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
 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);
 }