# Rules for building the password strength plugin.
module_LTLIBRARIES = plugin/strength.la
-plugin_strength_la_SOURCES = plugin/cdb.c plugin/config.c plugin/cracklib.c \
- plugin/error.c plugin/general.c plugin/heimdal.c plugin/internal.h \
- plugin/mit.c plugin/principal.c
+plugin_strength_la_SOURCES = plugin/cdb.c plugin/classes.c plugin/config.c \
+ plugin/cracklib.c plugin/error.c plugin/general.c plugin/heimdal.c \
+ plugin/internal.h plugin/mit.c plugin/principal.c plugin/vector.c
plugin_strength_la_LDFLAGS = -module -avoid-version
if EMBEDDED_CRACKLIB
plugin_strength_la_LIBADD = cracklib/libcracklib.la
# The Heimdal external check program.
bin_PROGRAMS = tools/heimdal-strength
tools_heimdal_strength_CFLAGS = $(AM_CFLAGS)
-tools_heimdal_strength_SOURCES = plugin/cdb.c plugin/config.c \
- plugin/cracklib.c plugin/error.c plugin/general.c plugin/internal.h \
- plugin/principal.c tools/heimdal-strength.c
+tools_heimdal_strength_SOURCES = plugin/cdb.c plugin/classes.c \
+ plugin/config.c plugin/cracklib.c plugin/error.c plugin/general.c \
+ plugin/internal.h plugin/principal.c plugin/vector.c \
+ tools/heimdal-strength.c
if EMBEDDED_CRACKLIB
tools_heimdal_strength_LDADD = cracklib/libcracklib.la
else
--- /dev/null
+/*
+ * Password strength checks for character classes.
+ *
+ * Checks whether the password satisfies a set of character class rules.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2013
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <ctype.h>
+
+#include <plugin/internal.h>
+
+/* Stores the characteristics of a particular password as boolean flags. */
+struct password_classes {
+ bool lower;
+ bool upper;
+ bool digit;
+ bool symbol;
+};
+
+/* Abbreviate the most common error reporting syntax. */
+#define MUST_HAVE(ctx, err) \
+ strength_error_class((ctx), "password must contain" err)
+
+
+/*
+ * Analyze a password and fill out a struct with flags indicating which
+ * character classes are present in the password.
+ */
+static void
+analyze_password(const char *password, struct password_classes *classes)
+{
+ const char *p;
+
+ for (p = password; p != '\0'; p++) {
+ if (islower((unsigned char) *p))
+ classes->lower = true;
+ else if (isupper((unsigned char) *p))
+ classes->upper = true;
+ else if (isdigit((unsigned char) *p))
+ classes->digit = true;
+ else
+ classes->symbol = true;
+ }
+}
+
+
+/*
+ * Check whether a password satisfies a required character class rule, given
+ * the length of the password and the classes. Returns 0 if it does and a
+ * Kerberos error code if it does not.
+ */
+static krb5_error_code
+check_rule(krb5_context ctx, struct class_rule *rule, size_t length,
+ struct password_classes *classes)
+{
+ if (length < rule->min || (rule->max > 0 && length > rule->max))
+ return 0;
+ if (rule->lower && !classes->lower)
+ return MUST_HAVE(ctx, "a lowercase letter");
+ if (rule->upper && !classes->upper)
+ return MUST_HAVE(ctx, "an uppercase letter");
+ if (rule->digit && !classes->digit)
+ return MUST_HAVE(ctx, "a digit");
+ if (rule->symbol && !classes->symbol)
+ return MUST_HAVE(ctx, "a symbol");
+ return 0;
+}
+
+
+/*
+ * Check whether a password satisfies the configured character class
+ * restrictions.
+ */
+krb5_error_code
+strength_check_classes(krb5_context ctx, krb5_pwqual_moddata data,
+ const char *password)
+{
+ struct password_classes classes;
+ size_t length;
+ struct class_rule *rule;
+ krb5_error_code code;
+
+ if (data->rules == NULL)
+ return 0;
+ analyze_password(password, &classes);
+ length = strlen(password);
+ for (rule = data->rules; rule != NULL; rule = rule->next) {
+ code = check_rule(ctx, rule, length, &classes);
+ if (code != 0)
+ return code;
+ }
+ return 0;
+}
}
+/*
+ * 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;
+ size_t i;
+ krb5_error_code code;
+
+ /* Parse the required classes into a vector. */
+ classes = strength_vector_split_multi(spec, ",", NULL);
+ if (classes == NULL)
+ return strength_error_system(ctx, "cannot allocate memory");
+
+ /* Create the basic rule structure. */
+ *rule = calloc(1, sizeof(struct class_rule));
+ (*rule)->min = 0;
+ (*rule)->max = 0;
+
+ /*
+ * Walk the list of required classes and set our flags, diagnosing an
+ * unknown character class.
+ */
+ for (i = 0; i < classes->count; i++) {
+ if (strcmp(classes->strings[i], "upper") == 0)
+ (*rule)->upper = true;
+ else if (strcmp(classes->strings[i], "lower") == 0)
+ (*rule)->lower = true;
+ else if (strcmp(classes->strings[i], "digit") == 0)
+ (*rule)->digit = true;
+ else if (strcmp(classes->strings[i], "symbol") == 0)
+ (*rule)->symbol = true;
+ else {
+ code = strength_error_config(ctx, "unknown character class %s",
+ classes->strings[i]);
+ strength_vector_free(classes);
+ free(*rule);
+ *rule = NULL;
+ return code;
+ }
+ }
+ strength_vector_free(classes);
+ return 0;
+}
+
+
+/*
+ * 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;
+ 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)
+ goto fail;
+ last = rules;
+ for (i = 1; i < config->count; i++) {
+ code = parse_class(ctx, config->strings[i], &last->next);
+ if (code != 0)
+ 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-sync", 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
return code; \
}
ERROR_FUNC(class, KADM5_PASS_Q_CLASS)
+ERROR_FUNC(config, KADM5_MISSING_KRB5_CONF_PARAMS)
ERROR_FUNC(dict, KADM5_PASS_Q_DICT)
ERROR_FUNC(generic, KADM5_PASS_Q_GENERIC)
ERROR_FUNC(tooshort, KADM5_PASS_Q_TOOSHORT)
if (data->nonletter && only_alpha_space(password))
return strength_error_class(ctx, ERROR_LETTER);
+ /*
+ * If desired, check that the password satisfies character class
+ * restrictions.
+ */
+ code = strength_check_classes(ctx, data, password);
+ if (code != 0)
+ return code;
+
/* Check if the password is based on the principal in some way. */
code = strength_check_principal(ctx, data, principal, password);
if (code != 0)
#ifdef HAVE_CDB_H
# include <cdb.h>
#endif
+#include <stddef.h>
#ifdef HAVE_KRB5_PWQUAL_PLUGIN_H
# include <krb5/pwqual_plugin.h>
#define ERROR_SHORT "password is too short"
#define ERROR_USERNAME "password based on username or principal"
+/*
+ * A character class rule, which consists of a minimum length to which the
+ * rule is applied, a maximum length to which the rule is applied, and a set
+ * of flags for which character classes are required. The symbol class
+ * includes everything that isn't in one of the other classes, including
+ * space.
+ */
+struct class_rule {
+ size_t min;
+ size_t max;
+ bool lower;
+ bool upper;
+ bool digit;
+ bool symbol;
+ struct class_rule *next;
+};
+
+/* Used to store a list of strings, managed by the sync_vector_* functions. */
+struct vector {
+ size_t count;
+ size_t allocated;
+ char **strings;
+};
+
/*
* MIT Kerberos uses this type as an abstract data type for any data that a
* password quality check needs to carry. Reuse it since then we get type
long minimum_length; /* Minimum password length */
bool ascii; /* Whether to require printable ASCII */
bool nonletter; /* Whether to require a non-letter */
+ struct class_rule *rules; /* Linked list of character class rules */
char *dictionary; /* Base path to CrackLib dictionary */
bool have_cdb; /* Whether we have a CDB dictionary */
int cdb_fd; /* File descriptor of CDB dictionary */
krb5_error_code strength_check_cracklib(krb5_context, krb5_pwqual_moddata,
const char *password);
+/* Check whether the password statisfies character class requirements. */
+krb5_error_code strength_check_classes(krb5_context, krb5_pwqual_moddata,
+ const char *password);
+
/* Check whether the password is based on the principal in some way. */
krb5_error_code strength_check_principal(krb5_context, krb5_pwqual_moddata,
const char *principal,
const char *password);
+/*
+ * Manage vectors, which are counted lists of strings. The functions that
+ * return a boolean return false if memory allocation fails.
+ */
+struct vector *strength_vector_new(void)
+ __attribute__((__malloc__));
+bool strength_vector_add(struct vector *, const char *string)
+ __attribute__((__nonnull__));
+void strength_vector_free(struct vector *);
+
+/*
+ * vector_split_multi splits on a set of characters. If the vector argument
+ * is NULL, a new vector is allocated; otherwise, the provided one is reused.
+ * Returns NULL on memory allocation failure, after which the provided vector
+ * may have been modified to only have partial results.
+ *
+ * Empty strings will yield zero-length vectors. Adjacent delimiters are
+ * treated as a single delimiter by vector_split_multi. Any leading or
+ * trailing delimiters are ignored, so this function will never create
+ * zero-length strings (similar to the behavior of strtok).
+ */
+struct vector *strength_vector_split_multi(const char *string,
+ const char *seps, struct vector *)
+ __attribute__((__nonnull__(1, 2)));
+
/*
* Obtain configuration settings from krb5.conf. These are wrappers around
* the krb5_appdefault_* APIs that handle setting the section name, obtaining
*/
void strength_config_boolean(krb5_context, const char *, bool *)
__attribute__((__nonnull__));
+krb5_error_code strength_config_list(krb5_context, const char *,
+ struct vector **)
+ __attribute__((__nonnull__));
void strength_config_number(krb5_context, const char *, long *)
__attribute__((__nonnull__));
void strength_config_string(krb5_context, const char *, char **)
__attribute__((__nonnull__));
+/* Parse the more complex configuration of required character classes. */
+krb5_error_code strength_config_classes(krb5_context, const char *,
+ struct class_rule **)
+ __attribute__((__nonnull__));
+
/*
* Store a particular password quality error in the Kerberos context. The
* _system variant uses errno for the error code and appends the strerror
*/
krb5_error_code strength_error_class(krb5_context, const char *format, ...)
__attribute__((__nonnull__, __format__(printf, 2, 3)));
+krb5_error_code strength_error_config(krb5_context, const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 2, 3)));
krb5_error_code strength_error_dict(krb5_context, const char *format, ...)
__attribute__((__nonnull__, __format__(printf, 2, 3)));
krb5_error_code strength_error_generic(krb5_context, const char *format, ...)
--- /dev/null
+/*
+ * Vector handling (counted lists of char *'s).
+ *
+ * A vector is a table for handling a list of strings with less overhead than
+ * linked list. The intention is for vectors, once allocated, to be reused;
+ * this saves on memory allocations once the array of char *'s reaches a
+ * stable size.
+ *
+ * This is based on the from rra-c-util util/vector.c library, but that
+ * library uses xmalloc routines to exit the program if memory allocation
+ * fails. This is a modified version of the vector library that instead
+ * returns false on failure to allocate memory, allowing the caller to do
+ * appropriate recovery.
+ *
+ * Vectors require list of strings, not arbitrary binary data, and cannot
+ * handle data elements containing nul characters.
+ *
+ * Only the portions of the vector library needed by this module is
+ * implemented.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2013
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <plugin/internal.h>
+
+
+/*
+ * Allocate a new, empty vector. Returns NULL if memory allocation fails.
+ */
+struct vector *
+strength_vector_new(void)
+{
+ return calloc(1, sizeof(struct vector));
+}
+
+
+/*
+ * Resize a vector (using realloc to resize the table). Return false if
+ * memory allocation fails.
+ */
+static bool
+strength_vector_resize(struct vector *vector, size_t size)
+{
+ size_t i;
+ char **strings;
+
+ /* If we're shrinking the vector, free the excess strings. */
+ if (vector->count > size) {
+ for (i = size; i < vector->count; i++)
+ free(vector->strings[i]);
+ vector->count = size;
+ }
+
+ /* If resizing to zero, free all storage. Otherwise, realloc. */
+ if (size == 0) {
+ free(vector->strings);
+ vector->strings = NULL;
+ } else {
+ strings = realloc(vector->strings, size * sizeof(char *));
+ if (strings == NULL)
+ return false;
+ vector->strings = strings;
+ }
+ vector->allocated = size;
+ return true;
+}
+
+
+/*
+ * Add a new string to the vector, resizing the vector as necessary. The
+ * vector is resized an element at a time; if a lot of resizes are expected,
+ * vector_resize should be called explicitly with a more suitable size.
+ * Return false if memory allocation fails.
+ */
+bool
+strength_vector_add(struct vector *vector, const char *string)
+{
+ size_t next = vector->count;
+
+ if (vector->count == vector->allocated)
+ if (!strength_vector_resize(vector, vector->allocated + 1))
+ return false;
+ vector->strings[next] = strdup(string);
+ if (vector->strings[next] == NULL)
+ return false;
+ vector->count++;
+ return true;
+}
+
+
+/*
+ * Empty a vector but keep the allocated memory for the pointer table.
+ */
+static void
+strength_vector_clear(struct vector *vector)
+{
+ size_t i;
+
+ for (i = 0; i < vector->count; i++)
+ if (vector->strings[i] != NULL)
+ free(vector->strings[i]);
+ vector->count = 0;
+}
+
+
+/*
+ * Free a vector completely.
+ */
+void
+strength_vector_free(struct vector *vector)
+{
+ if (vector == NULL)
+ return;
+ strength_vector_clear(vector);
+ free(vector->strings);
+ free(vector);
+}
+
+
+/*
+ * Given a vector that we may be reusing, clear it out. If the first argument
+ * is NULL, allocate a new vector. Used by vector_split*. Returns NULL if
+ * memory allocation fails.
+ */
+static struct vector *
+strength_vector_reuse(struct vector *vector)
+{
+ if (vector == NULL)
+ return strength_vector_new();
+ else {
+ strength_vector_clear(vector);
+ return vector;
+ }
+}
+
+
+/*
+ * Given a string and a set of separators expressed as a string, count the
+ * number of strings that it will split into when splitting on those
+ * separators.
+ */
+static size_t
+split_multi_count(const char *string, const char *seps)
+{
+ const char *p;
+ size_t count;
+
+ /* If the string is empty, the count of components is zero. */
+ if (*string == '\0')
+ return 0;
+
+ /* Otherwise, walk the string looking for non-consecutive separators. */
+ for (count = 1, p = string + 1; *p != '\0'; p++)
+ if (strchr(seps, *p) != NULL && strchr(seps, p[-1]) == NULL)
+ count++;
+
+ /*
+ * If the string ends in separators, we've overestimated the number of
+ * strings by one.
+ */
+ if (strchr(seps, p[-1]) != NULL)
+ count--;
+ return count;
+}
+
+
+/*
+ * Given a string, split it at any of the provided separators to form a
+ * vector, copying each string segment. If the third argument isn't NULL,
+ * reuse that vector; otherwise, allocate a new one. Any number of
+ * consecutive separators are considered a single separator. Returns NULL on
+ * memory allocation failure, after which the provided vector may only have
+ * partial results.
+ */
+struct vector *
+strength_vector_split_multi(const char *string, const char *seps,
+ struct vector *vector)
+{
+ const char *p, *start;
+ size_t i, count;
+ bool created = false;
+
+ /* Set up the vector we'll use to store the results. */
+ if (vector == NULL)
+ created = true;
+ vector = strength_vector_reuse(vector);
+ if (vector == NULL)
+ return NULL;
+
+ /* Count how big a vector we need and resize accordingly. */
+ count = split_multi_count(string, seps);
+ if (count == 0)
+ return vector;
+ if (vector->allocated < count && !strength_vector_resize(vector, count))
+ goto fail;
+
+ /* Now, walk the string and build the components. */
+ vector->count = 0;
+ for (start = string, p = string, i = 0; *p != '\0'; p++)
+ if (strchr(seps, *p) != NULL) {
+ if (start != p) {
+ vector->strings[i] = strndup(start, (size_t) (p - start));
+ if (vector->strings[i] == NULL)
+ goto fail;
+ i++;
+ vector->count++;
+ }
+ start = p + 1;
+ }
+
+ /* If there is anything left in the string, we have one more component. */
+ if (start != p) {
+ vector->strings[i] = strndup(start, (size_t) (p - start));
+ if (vector->strings[i] == NULL)
+ goto fail;
+ vector->count++;
+ }
+ return vector;
+
+fail:
+ if (created)
+ strength_vector_free(vector);
+ return NULL;
+}
# define KADM5_PASS_Q_GENERIC KADM5_PASS_Q_DICT
#endif
+/* Heimdal doesn't define KADM5_MISSING_KRB5_CONF_PARAMS. */
+#ifndef KADM5_MISSING_KRB5_CONF_PARAMS
+# define KADM5_MISSING_KRB5_CONF_PARAMS KADM5_MISSING_CONF_PARAMS
+#endif
+
/*
* Heimdal provides _ctx functions that take an existing context. MIT always
* requires the context be passed in. Code should use the _ctx variant, and