]> eyrie.org Git - kerberos/krb5-strength.git/blobdiff - plugin/config.c
Merge pull request #5 from rra/dependabot/github_actions/actions/checkout-4
[kerberos/krb5-strength.git] / plugin / config.c
index 9cbd3b44435e105c750afde1fc7e8572958f312c..d9d2a5248bc58e2ec5c73b1b02cbbe8c8fc0dd97 100644 (file)
@@ -5,17 +5,19 @@
  * settings from krb5.conf.  This wraps the somewhat awkward
  * krb5_appdefaults_* functions.
  *
- * Written by Russ Allbery <rra@stanford.edu>
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2016 Russ Allbery <eagle@eyrie.org>
  * Copyright 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
- * See LICENSE for licensing terms.
+ * SPDX-License-Identifier: MIT
  */
 
 #include <config.h>
 #include <portable/krb5.h>
 #include <portable/system.h>
 
+#include <ctype.h>
 #include <errno.h>
 
 #include <plugin/internal.h>
@@ -28,6 +30,9 @@ typedef krb5_realm realm_type;
 typedef krb5_data *realm_type;
 #endif
 
+/* Maximum number of character classes. */
+#define MAX_CLASSES 4
+
 
 /*
  * Obtain the default realm and translate it into the format required by
@@ -63,7 +68,7 @@ default_realm(krb5_context ctx)
         return NULL;
     code = krb5_get_default_realm(ctx, &realm);
     if (code != 0) {
-        free(realm);
+        free(realm_data);
         return NULL;
     }
     realm_data->magic = KV5M_DATA;
@@ -73,7 +78,7 @@ default_realm(krb5_context ctx)
         krb5_free_default_realm(ctx, realm);
         return NULL;
     }
-    realm_data->length = strlen(realm);
+    realm_data->length = (unsigned int) strlen(realm);
     krb5_free_default_realm(ctx, realm);
     return realm_data;
 }
@@ -105,6 +110,26 @@ free_default_realm(krb5_context ctx UNUSED, realm_type realm)
 #endif /* !HAVE_KRB5_REALM */
 
 
+/*
+ * Helper function to parse a number.  Takes the string to parse, the unsigned
+ * int in which to store the number, and the pointer to set to the first
+ * invalid character after the number.  Returns true if a number could be
+ * successfully parsed and false otherwise.
+ */
+static bool
+parse_number(const char *string, unsigned long *result, const char **end)
+{
+    unsigned long value;
+
+    errno = 0;
+    value = strtoul(string, (char **) end, 10);
+    if (errno != 0 || *end == string)
+        return false;
+    *result = value;
+    return true;
+}
+
+
 /*
  * Load a boolean option from Kerberos appdefaults.  Takes the Kerberos
  * context, the option, and the result location.
@@ -127,6 +152,182 @@ strength_config_boolean(krb5_context ctx, const char *opt, bool *result)
 }
 
 
+/*
+ * Parse a single class specification.  Currently, this assumes that the class
+ * specification is a comma-separated list of required classes, and those
+ * classes are required for any length of password.  This will be enhanced
+ * later.
+ */
+static krb5_error_code
+parse_class(krb5_context ctx, const char *spec, struct class_rule **rule)
+{
+    struct vector *classes = NULL;
+    size_t i;
+    krb5_error_code code;
+    const char *class, *end;
+    bool okay;
+
+    /* Create the basic rule structure. */
+    *rule = calloc(1, sizeof(struct class_rule));
+    if (*rule == NULL)
+        return strength_error_system(ctx, "cannot allocate memory");
+
+    /*
+     * If the rule starts with a digit and contains a '-', it starts
+     * with a range of affected password lengths.  Parse that range.
+     */
+    if (isdigit((unsigned char) *spec) && strchr(spec, '-') != NULL) {
+        okay = parse_number(spec, &(*rule)->min, &end);
+        if (okay)
+            okay = (*end == '-');
+        if (okay)
+            okay = parse_number(end + 1, &(*rule)->max, &end);
+        if (okay)
+            okay = (*end == ':');
+        if (okay)
+            spec = end + 1;
+        else {
+            code = strength_error_config(ctx,
+                                         "bad character class requirement in"
+                                         " configuration: %s",
+                                         spec);
+            goto fail;
+        }
+    }
+
+    /* Parse the required classes into a vector. */
+    classes = strength_vector_split_multi(spec, ",", NULL);
+    if (classes == NULL) {
+        code = strength_error_system(ctx, "cannot allocate memory");
+        goto fail;
+    }
+
+    /*
+     * Walk the list of required classes and set our flags, diagnosing an
+     * unknown character class.
+     */
+    for (i = 0; i < classes->count; i++) {
+        class = classes->strings[i];
+        if (strcmp(class, "upper") == 0)
+            (*rule)->upper = true;
+        else if (strcmp(class, "lower") == 0)
+            (*rule)->lower = true;
+        else if (strcmp(class, "digit") == 0)
+            (*rule)->digit = true;
+        else if (strcmp(class, "symbol") == 0)
+            (*rule)->symbol = true;
+        else if (isdigit((unsigned char) *class)) {
+            okay = parse_number(class, &(*rule)->num_classes, &end);
+            if (!okay || *end != '\0' || (*rule)->num_classes > MAX_CLASSES) {
+                code = strength_error_config(ctx,
+                                             "bad character class minimum in"
+                                             " configuration: %s",
+                                             class);
+                goto fail;
+            }
+        } else {
+            code = strength_error_config(ctx, "unknown character class %s",
+                                         class);
+            goto fail;
+        }
+    }
+    strength_vector_free(classes);
+    return 0;
+
+fail:
+    strength_vector_free(classes);
+    free(*rule);
+    *rule = NULL;
+    return code;
+}
+
+
+/*
+ * Parse character class requirements from Kerberos appdefaults.  Takes the
+ * Kerberos context, the option, and the place to store the linked list of
+ * class requirements.
+ */
+krb5_error_code
+strength_config_classes(krb5_context ctx, const char *opt,
+                        struct class_rule **result)
+{
+    struct vector *config = NULL;
+    struct class_rule *rules, *last, *tmp;
+    krb5_error_code code;
+    size_t i;
+
+    /* Get the basic configuration as a list. */
+    code = strength_config_list(ctx, opt, &config);
+    if (code != 0)
+        return code;
+    if (config == NULL || config->count == 0) {
+        *result = NULL;
+        return 0;
+    }
+
+    /* Each word in the list will be a class rule. */
+    code = parse_class(ctx, config->strings[0], &rules);
+    if (code != 0 || rules == NULL)
+        goto fail;
+    last = rules;
+    for (i = 1; i < config->count; i++) {
+        code = parse_class(ctx, config->strings[i], &last->next);
+        if (code != 0 || last->next == NULL)
+            goto fail;
+        last = last->next;
+    }
+
+    /* Success.  Free the vector and return the results. */
+    strength_vector_free(config);
+    *result = rules;
+    return 0;
+
+fail:
+    last = rules;
+    while (last != NULL) {
+        tmp = last;
+        last = last->next;
+        free(tmp);
+    }
+    strength_vector_free(config);
+    return code;
+}
+
+
+/*
+ * Load a list option from Kerberos appdefaults.  Takes the Kerberos context,
+ * the option, and the result location.  The option is read as a string and
+ * the split on spaces and tabs into a list.
+ *
+ * This requires an annoying workaround because one cannot specify a default
+ * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
+ * strdup on the default value.  There's also no way to determine if memory
+ * allocation failed while parsing or while setting the default value.
+ */
+krb5_error_code
+strength_config_list(krb5_context ctx, const char *opt, struct vector **result)
+{
+    realm_type realm;
+    char *value = NULL;
+
+    /* Obtain the string from [appdefaults]. */
+    realm = default_realm(ctx);
+    krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &value);
+    free_default_realm(ctx, realm);
+
+    /* If we got something back, store it in result. */
+    if (value != NULL) {
+        if (value[0] != '\0') {
+            *result = strength_vector_split_multi(value, " \t", *result);
+            if (*result == NULL)
+                return strength_error_system(ctx, "cannot allocate memory");
+        }
+        krb5_free_string(ctx, value);
+    }
+    return 0;
+}
+
+
 /*
  * Load a number option from Kerberos appdefaults.  Takes the Kerberos
  * context, the option, and the result location.  The native interface doesn't