]> eyrie.org Git - kerberos/krb5-strength.git/blobdiff - plugin/principal.c
Merge pull request #4 from dariaphoebe/main
[kerberos/krb5-strength.git] / plugin / principal.c
index 5d396a40c78f8b9f06d6cfa2b5f6a075954117fa..cd3b8b812b5bbafa33e248b2d4a902669ad9add9 100644 (file)
@@ -5,13 +5,13 @@
  * password is being changed, trying to detect and reject passwords based on
  * components of the principal.
  *
- * 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
+ * Developed by Daria Phoebe Brashear and Ken Hornstein of Sine Nomine
+ * Associates, on behalf of Stanford University Extensive modifications by Russ
+ * Allbery <eagle@eyrie.org> Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2006-2007, 2009, 2012-2014
+ *     The Board of Trustees of the Leland Stanford Junior University
  *
- * See LICENSE for licensing terms.
+ * SPDX-License-Identifier: MIT
  */
 
 #include <config.h>
 
 
 /*
- * Check whether the password is based in some way on the principal.  Returns
- * 0 if it is not and some non-zero error code if it appears to be.
+ * Given a string taken from the principal, check if the password matches that
+ * string or is that string with leading or trailing digits added.  If so,
+ * sets the Kerberos error and returns a non-zero error code.  Otherwise,
+ * returns 0.
  */
-krb5_error_code
-strength_check_principal(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
-                         const char *principal, const char *password)
+static krb5_error_code
+check_component(krb5_context ctx, const char *component, const char *password)
 {
-    char *user, *p;
-    const char *q;
-    size_t i, j;
+    char *copy;
+    size_t i, j, complength, passlength;
     char c;
 
-    /*
-     * 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)
+    /* Check if the password is a simple match for the component. */
+    if (strcasecmp(component, password) == 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 the length of the password matches the length of the component,
+     * check for a reversed match.
+     */
+    complength = strlen(component);
+    passlength = strlen(password);
+    if (complength == passlength) {
+        copy = strdup(component);
+        if (copy == NULL)
+            return strength_error_system(ctx, "cannot allocate memory");
+        for (i = 0, j = complength - 1; i < j; i++, j--) {
+            c = copy[i];
+            copy[i] = copy[j];
+            copy[j] = c;
         }
-        if (p[0] == '@') {
-            p[0] = '\0';
-            break;
+        if (strcasecmp(copy, password) == 0) {
+            explicit_bzero(copy, strlen(copy));
+            free(copy);
+            return strength_error_generic(ctx, ERROR_USERNAME);
         }
+        free(copy);
     }
 
     /*
-     * If the length of the password matches the length of the local portion
-     * of the principal, check for exact matches or reversed matches.
+     * We've checked everything we care about unless the password is longer
+     * than the component.
      */
-    if (strlen(password) == strlen(user)) {
-        if (strcasecmp(password, user) == 0) {
-            free(user);
-            return strength_error_generic(ctx, ERROR_USERNAME);
-        }
+    if (passlength <= complength)
+        return 0;
 
-        /* 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);
-        }
+    /*
+     * Check whether the user just added leading or trailing digits to the
+     * component of the principal to form the password.
+     */
+    for (i = 0; i <= passlength - complength; i++) {
+        if (strncasecmp(password + i, component, complength) != 0)
+            continue;
+
+        /*
+         * For this to be a match, all characters from 0 to i - 1 must be
+         * digits, and all characters from strlen(component) + i to
+         * strlen(password) - 1 must be digits.
+         */
+        for (j = 0; j < i; j++)
+            if (!isdigit((unsigned char) password[j]))
+                return 0;
+        for (j = complength + i; j < passlength; j++)
+            if (!isdigit((unsigned char) password[j]))
+                return 0;
+
+        /* The password was formed by adding digits to this component. */
+        return strength_error_generic(ctx, ERROR_USERNAME);
     }
 
+    /* No similarity to component detected. */
+    return 0;
+}
+
+
+/*
+ * Returns true if a given character is a separator character for forming
+ * components, and false otherwise.
+ */
+static bool
+is_separator(unsigned char c)
+{
+    if (c == '-' || c == '_')
+        return false;
+    if (isalnum(c))
+        return false;
+    return true;
+}
+
+
+/*
+ * Check whether the password is based in some way on the principal.  We do
+ * this by scanning the principal (in string form) and checking both each
+ * component of that password (defined as the alphanumeric, hyphen, and
+ * underscore bits between other characters) and the remaining principal from
+ * that point forward (to catch, for example, the entire realm).  Returns 0 if
+ * it is not and some non-zero error code if it appears to be.
+ */
+krb5_error_code
+strength_check_principal(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
+                         const char *principal, const char *password)
+{
+    krb5_error_code code;
+    char *copy, *start;
+    size_t i, length;
+
+    /* Sanity check. */
+    if (principal == NULL)
+        return 0;
+
+    /* Start with checking the entire principal. */
+    code = check_component(ctx, principal, password);
+    if (code != 0)
+        return code;
+
+    /*
+     * Make a copy of the principal and scan forward past any leading
+     * separators.
+     */
+    length = strlen(principal);
+    copy = strdup(principal);
+    if (copy == NULL)
+        return strength_error_system(ctx, "cannot allocate memory");
+    i = 0;
+    while (copy[i] != '\0' && is_separator(copy[i]))
+        i++;
+
     /*
-     * If the length is greater, check whether the user just added trailing
-     * digits to the local portion of the principal to form the password.
+     * Now loop for each component.  At the start of each loop, check against
+     * the component formed by the rest of the principal string.
      */
-    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);
+    do {
+        if (i != 0) {
+            code = check_component(ctx, copy + i, password);
+            if (code != 0) {
+                explicit_bzero(copy, strlen(copy));
+                free(copy);
+                return code;
             }
         }
 
+        /* Set the component start and then scan for a separator. */
+        start = copy + i;
+        while (i < length && !is_separator(copy[i]))
+            i++;
+
+        /* At end of string or a separator.  Truncate the component. */
+        copy[i] = '\0';
+
+        /* Check the current component. */
+        code = check_component(ctx, start, password);
+        if (code != 0) {
+            explicit_bzero(copy, strlen(copy));
+            free(copy);
+            return code;
+        }
+
+        /* Scan forward past any more separators. */
+        while (i < length && is_separator(copy[i]))
+            i++;
+    } while (i < length);
+
     /* Password does not appear to be based on the principal. */
-    free(user);
+    explicit_bzero(copy, strlen(copy));
+    free(copy);
     return 0;
 }