module_LTLIBRARIES = plugin/krb5_sync.la
plugin_krb5_sync_la_SOURCES = plugin/ad.c plugin/api.c plugin/config.c \
plugin/error.c plugin/internal.h plugin/heimdal.c plugin/instance.c \
- plugin/mit.c plugin/queue.c
+ plugin/mit.c plugin/queue.c plugin/vector.c
plugin_krb5_sync_la_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
plugin_krb5_sync_la_LDFLAGS = -module -avoid-version $(KADM5SRV_LDFLAGS) \
sync_config_string(ctx, "ad_ldap_base", &config->ad_ldap_base);
/* Get allowed instances from krb5.conf. */
- sync_config_string(ctx, "ad_instances", &config->ad_instances);
+ sync_config_list(ctx, "ad_instances", &config->ad_instances);
/* See if we're propagating an instance to the base account in AD. */
sync_config_string(ctx, "ad_base_instance", &config->ad_base_instance);
void
sync_close(krb5_context ctx UNUSED, kadm5_hook_modinfo *config)
{
+ free(config->ad_admin_server);
+ free(config->ad_base_instance);
+ sync_vector_free(config->ad_instances);
free(config->ad_keytab);
+ free(config->ad_ldap_base);
free(config->ad_principal);
free(config->ad_realm);
- free(config->ad_admin_server);
- free(config->ad_base_instance);
free(config->queue_dir);
free(config);
}
/*
- * Given the list of allowed principals as a space-delimited string and the
- * instance of a principal, returns true if that instance is allowed and false
- * otherwise.
+ * Given the configuration and the instance of a principal, returns true if
+ * that instance is allowed and false otherwise.
*/
static bool
-instance_allowed(const char *allowed, const char *instance)
+instance_allowed(kadm5_hook_modinfo *config, const char *instance)
{
- const char *p, *i, *end;
- bool checking, okay;
+ size_t i;
- if (allowed == NULL || instance == NULL)
- return false;
- i = instance;
- end = i + strlen(instance);
- checking = true;
- okay = false;
- for (p = allowed; *p != '\0'; p++) {
- if (*p == ' ') {
- if (okay && i == end)
- break;
- okay = false;
- checking = true;
- i = instance;
- } else if (checking && (i == end || *p != *i)) {
- okay = false;
- checking = false;
- i = instance;
- } else if (checking && *p == *i) {
- okay = true;
- i++;
- }
- }
- if (okay && (*p == '\0' || *p == ' ') && i == end)
- return true;
- else
+ if (config->ad_instances == NULL || instance == NULL)
return false;
+ for (i = 0; i < config->ad_instances->count; i++)
+ if (strcmp(config->ad_instances->strings[i], instance) == 0)
+ return true;
+ return false;
}
const char *instance;
instance = krb5_principal_get_comp_string(ctx, principal, 1);
- if (!instance_allowed(config->ad_instances, instance)) {
+ if (!instance_allowed(config, instance)) {
code = krb5_unparse_name(ctx, principal, &display);
if (code != 0)
display = NULL;
}
+/*
+ * 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
+sync_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 = sync_vector_split_multi(value, " \t", *result);
+ if (*result == NULL)
+ return sync_error_system(ctx, "cannot allocate memory");
+ }
+ krb5_free_string(ctx, value);
+ }
+ return 0;
+}
+
+
/*
* Load a string option from Kerberos appdefaults. Takes the Kerberos
* context, the option, and the result location.
typedef struct kadm5_hook_modinfo_st kadm5_hook_modinfo;
#endif
+/* Used to store a list of strings, managed by the sync_vector_* functions. */
+struct vector {
+ size_t count;
+ size_t allocated;
+ char **strings;
+};
+
/*
* Local configuration information for the module. This contains all the
* parameters that are read from the krb5-sync sub-section of the appdefaults
struct kadm5_hook_modinfo_st {
char *ad_admin_server;
char *ad_base_instance;
- char *ad_instances;
+ struct vector *ad_instances;
char *ad_keytab;
char *ad_ldap_base;
char *ad_principal;
krb5_principal, const char *operation,
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 *sync_vector_new(void)
+ __attribute__((__malloc__));
+bool sync_vector_add(struct vector *, const char *string)
+ __attribute__((__nonnull__));
+void sync_vector_free(struct vector *)
+ __attribute__((__nonnull__));
+
+/*
+ * 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 *sync_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 sync_config_boolean(krb5_context, const char *, bool *)
__attribute__((__nonnull__));
+krb5_error_code sync_config_list(krb5_context, const char *, struct vector **)
+ __attribute__((__nonnull__));
void sync_config_string(krb5_context, const char *, char **)
__attribute__((__nonnull__));
--- /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 *
+sync_vector_new(void)
+{
+ struct vector *vector;
+
+ vector = calloc(1, sizeof(struct vector));
+ if (vector == NULL)
+ return NULL;
+ return vector;
+}
+
+
+/*
+ * Resize a vector (using realloc to resize the table). Return false if
+ * memory allocation fails.
+ */
+static bool
+sync_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
+sync_vector_add(struct vector *vector, const char *string)
+{
+ size_t next = vector->count;
+
+ if (vector->count == vector->allocated)
+ if (!sync_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
+sync_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
+sync_vector_free(struct vector *vector)
+{
+ if (vector == NULL)
+ return;
+ sync_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 *
+sync_vector_reuse(struct vector *vector)
+{
+ if (vector == NULL)
+ return sync_vector_new();
+ else {
+ sync_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 *
+sync_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 = sync_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 (vector->allocated < count && !sync_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)
+ sync_vector_free(vector);
+ return NULL;
+}