aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorGreg Hudson <ghudson@mit.edu>2010-07-22 03:13:38 +0000
committerGreg Hudson <ghudson@mit.edu>2010-07-22 03:13:38 +0000
commit9e1b4bc8d254fd6671a94d2a8a5c84650754fce3 (patch)
tree81c86ecb849fb18b3ae2cda4f4fe3eb3bcbad871 /src/lib
parentc5e940f6d4d6737bf0ed778a72dbfc6dfb9640dc (diff)
downloadkrb5-9e1b4bc8d254fd6671a94d2a8a5c84650754fce3.zip
krb5-9e1b4bc8d254fd6671a94d2a8a5c84650754fce3.tar.gz
krb5-9e1b4bc8d254fd6671a94d2a8a5c84650754fce3.tar.bz2
Proof of concept code for a candidate plugin framework
git-svn-id: svn://anonsvn.mit.edu/krb5/branches/plugins2@24203 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/kadm5/server_internal.h53
-rw-r--r--src/lib/kadm5/srv/Makefile.in16
-rw-r--r--src/lib/kadm5/srv/libkadm5srv_mit.exports3
-rw-r--r--src/lib/kadm5/srv/pwqual.c114
-rw-r--r--src/lib/kadm5/srv/pwqual_dict.c242
-rw-r--r--src/lib/kadm5/srv/pwqual_policy.c81
-rw-r--r--src/lib/kadm5/srv/server_dict.c208
-rw-r--r--src/lib/kadm5/srv/server_init.c4
-rw-r--r--src/lib/kadm5/srv/server_misc.c187
-rw-r--r--src/lib/kadm5/srv/svr_principal.c6
-rw-r--r--src/lib/krb5/krb/Makefile.in3
-rw-r--r--src/lib/krb5/krb/init_ctx.c2
-rw-r--r--src/lib/krb5/krb/plugin.c382
-rw-r--r--src/lib/krb5/libkrb5.exports4
14 files changed, 944 insertions, 361 deletions
diff --git a/src/lib/kadm5/server_internal.h b/src/lib/kadm5/server_internal.h
index cc589fa..52f71e9 100644
--- a/src/lib/kadm5/server_internal.h
+++ b/src/lib/kadm5/server_internal.h
@@ -22,6 +22,7 @@
#include <errno.h>
#include <kdb.h>
#include <kadm5/admin.h>
+#include <krb5/plugin.h>
#include "admin_internal.h"
/*
@@ -33,6 +34,9 @@
*/
#define INITIAL_HIST_KVNO 2
+/* A pwqual_handle represents a password quality plugin module. */
+typedef struct pwqual_handle_st *pwqual_handle;
+
typedef struct _kadm5_server_handle_t {
krb5_ui_4 magic_number;
krb5_ui_4 struct_version;
@@ -42,6 +46,7 @@ typedef struct _kadm5_server_handle_t {
kadm5_config_params params;
struct _kadm5_server_handle_t *lhandle;
char **db_args;
+ pwqual_handle *qual_handles;
} kadm5_server_handle_rec, *kadm5_server_handle_t;
#define OSA_ADB_PRINC_VERSION_1 0x12345C01
@@ -65,8 +70,7 @@ typedef struct _osa_princ_ent_t {
kadm5_ret_t adb_policy_init(kadm5_server_handle_t handle);
kadm5_ret_t adb_policy_close(kadm5_server_handle_t handle);
kadm5_ret_t passwd_check(kadm5_server_handle_t handle,
- char *pass, int use_policy,
- kadm5_policy_ent_t policy,
+ const char *pass, kadm5_policy_ent_t policy,
krb5_principal principal);
kadm5_ret_t principal_exists(krb5_principal principal);
krb5_error_code kdb_init_master(kadm5_server_handle_t handle,
@@ -90,9 +94,8 @@ krb5_error_code kdb_iter_entry(kadm5_server_handle_t handle,
void (*iter_fct)(void *, krb5_principal),
void *data);
-int init_dict(kadm5_config_params *);
-int find_word(const char *word);
-void destroy_dict(void);
+kadm5_ret_t init_pwqual(kadm5_server_handle_t handle);
+void destroy_pwqual(kadm5_server_handle_t handle);
/* XXX this ought to be in libkrb5.a, but isn't */
kadm5_ret_t krb5_copy_key_data_contents(krb5_context context,
@@ -153,4 +156,44 @@ bool_t xdr_osa_princ_ent_rec(XDR *xdrs, osa_princ_ent_t objp);
void
osa_free_princ_ent(osa_princ_ent_t val);
+/*** Password quality plugin consumer interface ***/
+
+/* Load the available password quality plugins and store the result into
+ * *handles. Free the result with k5_pwqual_free_handles. */
+krb5_error_code
+k5_pwqual_load(krb5_context context, pwqual_handle **handles);
+
+/* Release a handle list allocated by k5_pwqual_load. All modules must have
+ * been closed by the caller. */
+krb5_error_code
+k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles);
+
+/* Initialize a password quality plugin, possibly using the realm's configured
+ * dictionary filename. */
+krb5_error_code
+k5_pwqual_open(krb5_context context, pwqual_handle handle,
+ const char *dict_file);
+
+/* Check a password using a password quality plugin. */
+krb5_error_code
+k5_pwqual_check(krb5_context context, pwqual_handle handle,
+ const char *password, kadm5_policy_ent_t policy,
+ krb5_principal princ);
+
+/* Release the memory used by a password quality plugin. */
+void
+k5_pwqual_close(krb5_context context, pwqual_handle handle);
+
+/*** Init functions for built-in password quality modules ***/
+
+/* The dict module checks passwords against the realm's dictionary. */
+krb5_error_code
+pwqual_dict_init(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+/* The policy module enforces password policy constraints. */
+krb5_error_code
+pwqual_policy_init(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
#endif /* __KADM5_SERVER_INTERNAL_H__ */
diff --git a/src/lib/kadm5/srv/Makefile.in b/src/lib/kadm5/srv/Makefile.in
index c7e0fac..6000d73 100644
--- a/src/lib/kadm5/srv/Makefile.in
+++ b/src/lib/kadm5/srv/Makefile.in
@@ -27,36 +27,42 @@ SHLIB_DIRS=-L$(TOPLIBD)
SHLIB_RDIRS=$(KRB5_LIBDIR)
RELDIR=kadm5/srv
-SRCS = $(srcdir)/svr_policy.c \
+SRCS = $(srcdir)/pwqual.c \
+ $(srcdir)/pwqual_dict.c \
+ $(srcdir)/pwqual_policy.c \
+ $(srcdir)/svr_policy.c \
$(srcdir)/svr_principal.c \
$(srcdir)/server_acl.c \
$(srcdir)/server_kdb.c \
$(srcdir)/server_misc.c \
$(srcdir)/server_init.c \
- $(srcdir)/server_dict.c \
$(srcdir)/svr_iters.c \
$(srcdir)/svr_chpass_util.c \
$(srcdir)/adb_xdr.c
-OBJS = svr_policy.$(OBJEXT) \
+OBJS = pwqual.$(OBJEXT) \
+ pwqual_dict.$(OBJEXT) \
+ pwqual_policy.$(OBJECT) \
+ svr_policy.$(OBJEXT) \
svr_principal.$(OBJEXT) \
server_acl.$(OBJEXT) \
server_kdb.$(OBJEXT) \
server_misc.$(OBJEXT) \
server_init.$(OBJEXT) \
- server_dict.$(OBJEXT) \
svr_iters.$(OBJEXT) \
svr_chpass_util.$(OBJEXT) \
adb_xdr.$(OBJEXT)
STLIBOBJS = \
+ pwqual.o \
+ pwqual_dict.o \
+ pwqual_policy.o \
svr_policy.o \
svr_principal.o \
server_acl.o \
server_kdb.o \
server_misc.o \
server_init.o \
- server_dict.o \
svr_iters.o \
svr_chpass_util.o \
adb_xdr.o
diff --git a/src/lib/kadm5/srv/libkadm5srv_mit.exports b/src/lib/kadm5/srv/libkadm5srv_mit.exports
index 6da95bd..345957a 100644
--- a/src/lib/kadm5/srv/libkadm5srv_mit.exports
+++ b/src/lib/kadm5/srv/libkadm5srv_mit.exports
@@ -7,10 +7,7 @@ kadm5int_acl_impose_restrictions
kadm5int_acl_init
adb_policy_close
adb_policy_init
-destroy_dict
-find_word
hist_princ
-init_dict
kadm5_set_use_password_server
kadm5_chpass_principal
kadm5_chpass_principal_3
diff --git a/src/lib/kadm5/srv/pwqual.c b/src/lib/kadm5/srv/pwqual.c
new file mode 100644
index 0000000..4452376
--- /dev/null
+++ b/src/lib/kadm5/srv/pwqual.c
@@ -0,0 +1,114 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Consumer interface for password quality plugins
+ */
+
+#include "k5-int.h"
+#include "server_internal.h"
+#include <krb5/pwqual_plugin.h>
+
+struct pwqual_handle_st {
+ struct krb5_pwqual_vtable_st vt;
+ krb5_pwqual_moddata data;
+};
+
+krb5_error_code
+k5_pwqual_load(krb5_context context, pwqual_handle **handles)
+{
+ krb5_error_code ret;
+ krb5_plugin_init_fn *modules = NULL, *mod;
+ size_t count;
+ pwqual_handle *list = NULL, handle = NULL;
+
+ ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_PWQUAL, &modules);
+ if (ret != 0)
+ goto cleanup;
+
+ /* Allocate a large enough list of handles. */
+ for (count = 0; modules[count] != NULL; count++);
+ list = k5alloc((count + 1) * sizeof(*list), &ret);
+ if (list == NULL)
+ goto cleanup;
+
+ /* For each module, allocate a handle and initialize its vtable. Skip
+ * modules which don't successfully initialize. */
+ count = 0;
+ for (mod = modules; *mod != NULL; mod++) {
+ handle = k5alloc(sizeof(*handle), &ret);
+ if (handle == NULL)
+ goto cleanup;
+ ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt);
+ if (ret == 0)
+ list[count++] = handle;
+ else
+ free(handle);
+ }
+
+ *handles = list;
+ list = NULL;
+
+cleanup:
+ k5_plugin_free_modules(context, modules);
+ k5_pwqual_free_handles(context, list);
+ return ret;
+}
+
+krb5_error_code
+k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles)
+{
+ /* It's the caller's responsibility to close each handle, so all of the
+ * module data should be freed by now, leaving only the list itself. */
+ free(handles);
+}
+
+krb5_error_code
+k5_pwqual_open(krb5_context context, pwqual_handle handle,
+ const char *dict_file)
+{
+ if (handle->data != NULL)
+ return EINVAL;
+ if (handle->vt.open == NULL)
+ return 0;
+ return handle->vt.open(context, dict_file, &handle->data);
+}
+
+krb5_error_code
+k5_pwqual_check(krb5_context context, pwqual_handle handle,
+ const char *password, kadm5_policy_ent_t policy,
+ krb5_principal princ)
+{
+ return handle->vt.check(context, handle->data, password, policy, princ);
+}
+
+void
+k5_pwqual_close(krb5_context context, pwqual_handle handle)
+{
+ if (handle->vt.close)
+ handle->vt.close(context, handle->data);
+ handle->data = NULL;
+}
diff --git a/src/lib/kadm5/srv/pwqual_dict.c b/src/lib/kadm5/srv/pwqual_dict.c
new file mode 100644
index 0000000..60bc5ff
--- /dev/null
+++ b/src/lib/kadm5/srv/pwqual_dict.c
@@ -0,0 +1,242 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
+ *
+ * $Header$
+ */
+
+#if !defined(lint) && !defined(__CODECENTER__)
+static char *rcsid = "$Header$";
+#endif
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <kadm5/admin.h>
+#include "adm_proto.h"
+#include <syslog.h>
+#include "server_internal.h"
+
+typedef struct dict_moddata_st {
+ char **word_list; /* list of word pointers */
+ char *word_block; /* actual word data */
+ unsigned int word_count; /* number of words */
+} *dict_moddata;
+
+
+/*
+ * Function: word_compare
+ *
+ * Purpose: compare two words in the dictionary.
+ *
+ * Arguments:
+ * w1 (input) pointer to first word
+ * w2 (input) pointer to second word
+ * <return value> result of strcmp
+ *
+ * Requires:
+ * w1 and w2 to point to valid memory
+ *
+ */
+
+static int
+word_compare(const void *s1, const void *s2)
+{
+ return (strcasecmp(*(const char **)s1, *(const char **)s2));
+}
+
+/*
+ * Function: init-dict
+ *
+ * Purpose: Initialize in memory word dictionary
+ *
+ * Arguments:
+ * none
+ * <return value> KADM5_OK on success errno on failure;
+ * (but success on ENOENT)
+ *
+ * Requires:
+ * If WORDFILE exists, it must contain a list of words,
+ * one word per-line.
+ *
+ * Effects:
+ * If WORDFILE exists, it is read into memory sorted for future
+ * use. If it does not exist, it syslogs an error message and returns
+ * success.
+ *
+ * Modifies:
+ * word_list to point to a chunck of allocated memory containing
+ * pointers to words
+ * word_block to contain the dictionary.
+ *
+ */
+
+static int
+init_dict(dict_moddata dict, const char *dict_file)
+{
+ int fd;
+ size_t len, i;
+ char *p, *t;
+ struct stat sb;
+
+ if (dict_file == NULL) {
+ krb5_klog_syslog(LOG_INFO, "No dictionary file specified, continuing "
+ "without one.");
+ return KADM5_OK;
+ }
+ if ((fd = open(dict_file, O_RDONLY)) == -1) {
+ if (errno == ENOENT) {
+ krb5_klog_syslog(LOG_ERR,
+ "WARNING! Cannot find dictionary file %s, "
+ "continuing without one.", dict_file);
+ return KADM5_OK;
+ } else
+ return errno;
+ }
+ set_cloexec_fd(fd);
+ if (fstat(fd, &sb) == -1) {
+ close(fd);
+ return errno;
+ }
+ if ((dict->word_block = malloc(sb.st_size + 1)) == NULL)
+ return ENOMEM;
+ if (read(fd, dict->word_block, sb.st_size) != sb.st_size)
+ return errno;
+ (void) close(fd);
+ dict->word_block[sb.st_size] = '\0';
+
+ p = dict->word_block;
+ len = sb.st_size;
+ while(len > 0 && (t = memchr(p, '\n', len)) != NULL) {
+ *t = '\0';
+ len -= t - p + 1;
+ p = t + 1;
+ dict->word_count++;
+ }
+ if ((dict->word_list = malloc(dict->word_count * sizeof(char *))) == NULL)
+ return ENOMEM;
+ p = dict->word_block;
+ for (i = 0; i < dict->word_count; i++) {
+ dict->word_list[i] = p;
+ p += strlen(p) + 1;
+ }
+ qsort(dict->word_list, dict->word_count, sizeof(char *), word_compare);
+ return KADM5_OK;
+}
+
+/*
+ * Function: destroy_dict
+ *
+ * Purpose: destroy in-core copy of dictionary.
+ *
+ * Arguments:
+ * none
+ * <return value> none
+ * Requires:
+ * nothing
+ * Effects:
+ * frees up memory occupied by word_list and word_block
+ * sets count back to 0, and resets the pointers to NULL
+ *
+ * Modifies:
+ * word_list, word_block, and word_count.
+ *
+ */
+
+static void
+destroy_dict(dict_moddata dict)
+{
+ if (dict == NULL)
+ return;
+ free(dict->word_list);
+ free(dict->word_block);
+ free(dict);
+ return;
+}
+
+/* Implement the password quality open method by reading in dict_file. */
+static krb5_error_code
+dict_open(krb5_context context, const char *dict_file,
+ krb5_pwqual_moddata *data)
+{
+ krb5_error_code ret;
+ dict_moddata dict;
+
+ *data = NULL;
+
+ /* Allocate and initialize a dictionary structure. */
+ dict = malloc(sizeof(*dict));
+ if (dict == NULL)
+ return ENOMEM;
+ dict->word_list = NULL;
+ dict->word_block = NULL;
+ dict->word_count = 0;
+
+ /* Fill in the dictionary structure with data from dict_file. */
+ ret = init_dict(dict, dict_file);
+ if (ret != 0) {
+ destroy_dict(dict);
+ return ret;
+ }
+
+ *data = (krb5_pwqual_moddata)dict;
+ return 0;
+}
+
+/* Implement the password quality check method by checking the password
+ * against the dictionary, as well as against principal components. */
+static krb5_error_code
+dict_check(krb5_context context, krb5_pwqual_moddata data,
+ const char *password, kadm5_policy_ent_t policy,
+ krb5_principal princ)
+{
+ dict_moddata dict = (dict_moddata)data;
+ int i, n;
+ char *cp;
+
+ /* Don't check the dictionary for principals with no password policy. */
+ if (policy == NULL)
+ return 0;
+
+ /* Check against words in the dictionary if we successfully loaded one. */
+ if (dict->word_list != NULL &&
+ bsearch(&password, dict->word_list, dict->word_count, sizeof(char *),
+ word_compare) != NULL)
+ return KADM5_PASS_Q_DICT;
+
+ /* Check against components of the principal. */
+ n = krb5_princ_size(handle->context, princ);
+ cp = krb5_princ_realm(handle->context, princ)->data;
+ if (strcasecmp(cp, password) == 0)
+ return KADM5_PASS_Q_DICT;
+ for (i = 0; i < n; i++) {
+ cp = krb5_princ_component(handle->context, princ, i)->data;
+ if (strcasecmp(cp, password) == 0)
+ return KADM5_PASS_Q_DICT;
+ }
+ return 0;
+}
+
+/* Implement the password quality close method. */
+static void
+dict_close(krb5_context context, krb5_pwqual_moddata data)
+{
+ destroy_dict((dict_moddata)data);
+}
+
+krb5_error_code
+pwqual_dict_init(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_pwqual_vtable vt;
+
+ if (maj_ver != 1)
+ return EINVAL; /* XXX create error code */
+ vt = (krb5_pwqual_vtable)vtable;
+ vt->open = dict_open;
+ vt->check = dict_check;
+ vt->close = dict_close;
+ return 0;
+}
diff --git a/src/lib/kadm5/srv/pwqual_policy.c b/src/lib/kadm5/srv/pwqual_policy.c
new file mode 100644
index 0000000..978744d
--- /dev/null
+++ b/src/lib/kadm5/srv/pwqual_policy.c
@@ -0,0 +1,81 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual_policy.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Password quality module to enforce password policy
+ */
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include <kadm5/admin.h>
+#include <ctype.h>
+#include "server_internal.h"
+
+/* Implement the password quality check module. */
+static krb5_error_code
+policy_check(krb5_context context, krb5_pwqual_moddata data,
+ const char *password, kadm5_policy_ent_t policy,
+ krb5_principal princ)
+{
+ int nupper = 0, nlower = 0, ndigit = 0, npunct = 0, nspec = 0;
+ const char *s;
+ unsigned char c;
+
+ if (policy == NULL)
+ return (*password == '\0') ? KADM5_PASS_Q_TOOSHORT : 0;
+
+ if(strlen(password) < (size_t)policy->pw_min_length)
+ return KADM5_PASS_Q_TOOSHORT;
+ s = password;
+ while ((c = (unsigned char)*s++)) {
+ if (islower(c))
+ nlower = 1;
+ else if (isupper(c))
+ nupper = 1;
+ else if (isdigit(c))
+ ndigit = 1;
+ else if (ispunct(c))
+ npunct = 1;
+ else
+ nspec = 1;
+ }
+ if ((nupper + nlower + ndigit + npunct + nspec) < policy->pw_min_classes)
+ return KADM5_PASS_Q_CLASS;
+ return 0;
+}
+
+krb5_error_code
+pwqual_policy_init(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_pwqual_vtable vt;
+
+ if (maj_ver != 1)
+ return EINVAL; /* XXX create error code */
+ vt = (krb5_pwqual_vtable)vtable;
+ vt->check = policy_check;
+ return 0;
+}
diff --git a/src/lib/kadm5/srv/server_dict.c b/src/lib/kadm5/srv/server_dict.c
deleted file mode 100644
index 81cc5f9..0000000
--- a/src/lib/kadm5/srv/server_dict.c
+++ /dev/null
@@ -1,208 +0,0 @@
-/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
- * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
- *
- * $Header$
- */
-
-#if !defined(lint) && !defined(__CODECENTER__)
-static char *rcsid = "$Header$";
-#endif
-
-#include <sys/types.h>
-#include <sys/file.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <kadm5/admin.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#ifdef HAVE_MEMORY_H
-#include <memory.h>
-#endif
-#include "adm_proto.h"
-#include <syslog.h>
-#include "server_internal.h"
-#include "k5-platform.h"
-
-static char **word_list = NULL; /* list of word pointers */
-static char *word_block = NULL; /* actual word data */
-static unsigned int word_count = 0; /* number of words */
-
-
-/*
- * Function: word_compare
- *
- * Purpose: compare two words in the dictionary.
- *
- * Arguments:
- * w1 (input) pointer to first word
- * w2 (input) pointer to second word
- * <return value> result of strcmp
- *
- * Requires:
- * w1 and w2 to point to valid memory
- *
- */
-
-static int
-word_compare(const void *s1, const void *s2)
-{
- return (strcasecmp(*(const char **)s1, *(const char **)s2));
-}
-
-/*
- * Function: init-dict
- *
- * Purpose: Initialize in memory word dictionary
- *
- * Arguments:
- * none
- * <return value> KADM5_OK on success errno on failure;
- * (but success on ENOENT)
- *
- * Requires:
- * If WORDFILE exists, it must contain a list of words,
- * one word per-line.
- *
- * Effects:
- * If WORDFILE exists, it is read into memory sorted for future
- * use. If it does not exist, it syslogs an error message and returns
- * success.
- *
- * Modifies:
- * word_list to point to a chunck of allocated memory containing
- * pointers to words
- * word_block to contain the dictionary.
- *
- */
-
-int init_dict(kadm5_config_params *params)
-{
- int fd,
- len,
- i;
- char *p,
- *t;
- struct stat sb;
-
- if(word_list != NULL && word_block != NULL)
- return KADM5_OK;
- if (! (params->mask & KADM5_CONFIG_DICT_FILE)) {
- krb5_klog_syslog(LOG_INFO, "No dictionary file specified, continuing "
- "without one.");
- return KADM5_OK;
- }
- if ((fd = open(params->dict_file, O_RDONLY)) == -1) {
- if (errno == ENOENT) {
- krb5_klog_syslog(LOG_ERR,
- "WARNING! Cannot find dictionary file %s, "
- "continuing without one.", params->dict_file);
- return KADM5_OK;
- } else
- return errno;
- }
- set_cloexec_fd(fd);
- if (fstat(fd, &sb) == -1) {
- close(fd);
- return errno;
- }
- if ((word_block = (char *) malloc(sb.st_size + 1)) == NULL)
- return ENOMEM;
- if (read(fd, word_block, sb.st_size) != sb.st_size)
- return errno;
- (void) close(fd);
- word_block[sb.st_size] = '\0';
-
- p = word_block;
- len = sb.st_size;
- while(len > 0 && (t = memchr(p, '\n', len)) != NULL) {
- *t = '\0';
- len -= t - p + 1;
- p = t + 1;
- word_count++;
- }
- if ((word_list = (char **) malloc(word_count * sizeof(char *))) == NULL)
- return ENOMEM;
- p = word_block;
- for (i = 0; i < word_count; i++) {
- word_list[i] = p;
- p += strlen(p) + 1;
- }
- qsort(word_list, word_count, sizeof(char *), word_compare);
- return KADM5_OK;
-}
-
-/*
- * Function: find_word
- *
- * Purpose: See if the specified word exists in the in-core dictionary
- *
- * Arguments:
- * word (input) word to search for.
- * <return value> WORD_NOT_FOUND if not in dictionary,
- * KADM5_OK if if found word
- * errno if init needs to be called and returns an
- * error
- *
- * Requires:
- * word to be a null terminated string.
- * That word_list and word_block besetup
- *
- * Effects:
- * finds word in dictionary.
- * Modifies:
- * nothing.
- *
- */
-
-int
-find_word(const char *word)
-{
- char **value;
-
- if(word_list == NULL || word_block == NULL)
- return WORD_NOT_FOUND;
- if ((value = (char **) bsearch(&word, word_list, word_count, sizeof(char *),
- word_compare)) == NULL)
- return WORD_NOT_FOUND;
- else
- return KADM5_OK;
-}
-
-/*
- * Function: destroy_dict
- *
- * Purpose: destroy in-core copy of dictionary.
- *
- * Arguments:
- * none
- * <return value> none
- * Requires:
- * nothing
- * Effects:
- * frees up memory occupied by word_list and word_block
- * sets count back to 0, and resets the pointers to NULL
- *
- * Modifies:
- * word_list, word_block, and word_count.
- *
- */
-
-void
-destroy_dict(void)
-{
- if(word_list) {
- free(word_list);
- word_list = NULL;
- }
- if(word_block) {
- free(word_block);
- word_block = NULL;
- }
- if(word_count)
- word_count = 0;
- return;
-}
diff --git a/src/lib/kadm5/srv/server_init.c b/src/lib/kadm5/srv/server_init.c
index 557ef0a..9ebc13e 100644
--- a/src/lib/kadm5/srv/server_init.c
+++ b/src/lib/kadm5/srv/server_init.c
@@ -317,7 +317,7 @@ kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass,
return ret;
}
- ret = init_dict(&handle->params);
+ ret = init_pwqual(handle);
if (ret) {
krb5_db_fini(handle->context);
krb5_free_principal(handle->context, handle->current_caller);
@@ -337,7 +337,7 @@ kadm5_ret_t kadm5_destroy(void *server_handle)
CHECK_HANDLE(server_handle);
- destroy_dict();
+ destroy_pwqual(handle);
adb_policy_close(handle);
krb5_db_fini(handle->context);
diff --git a/src/lib/kadm5/srv/server_misc.c b/src/lib/kadm5/srv/server_misc.c
index 1faeb86..38c0607 100644
--- a/src/lib/kadm5/srv/server_misc.c
+++ b/src/lib/kadm5/srv/server_misc.c
@@ -13,10 +13,6 @@ static char *rcsid = "$Header$";
#include <kdb.h>
#include <ctype.h>
#include <pwd.h>
-
-/* for strcasecmp */
-#include <string.h>
-
#include "server_internal.h"
kadm5_ret_t
@@ -37,147 +33,68 @@ adb_policy_close(kadm5_server_handle_t handle)
return KADM5_OK;
}
-#ifdef HESIOD
-/* stolen from v4sever/kadm_funcs.c */
-static char *
-reverse(str)
- char *str;
-{
- static char newstr[80];
- char *p, *q;
- int i;
-
- i = strlen(str);
- if (i >= sizeof(newstr))
- i = sizeof(newstr)-1;
- p = str+i-1;
- q = newstr;
- q[i]='\0';
- for(; i > 0; i--)
- *q++ = *p--;
-
- return(newstr);
-}
-#endif /* HESIOD */
-
-#if 0
-static int
-lower(str)
- char *str;
+kadm5_ret_t
+init_pwqual(kadm5_server_handle_t handle)
{
- register char *cp;
- int effect=0;
-
- for (cp = str; *cp; cp++) {
- if (isupper(*cp)) {
- *cp = tolower(*cp);
- effect++;
+ krb5_error_code ret;
+ pwqual_handle *list, *h;
+ const char *dict_file = NULL;
+
+ ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
+ "dict", pwqual_dict_init);
+ if (ret != 0)
+ return ret;
+
+ ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
+ "policy", pwqual_policy_init);
+ if (ret != 0)
+ return ret;
+
+ ret = k5_pwqual_load(handle->context, &list);
+ if (ret != 0)
+ return ret;
+
+ if (handle->params.mask & KADM5_CONFIG_DICT_FILE)
+ dict_file = handle->params.dict_file;
+
+ for (h = list; *h != NULL; h++) {
+ ret = k5_pwqual_open(handle->context, *h, dict_file);
+ if (ret != 0) {
+ /* Close any previously opened modules and error out. */
+ for (; h > list; h--)
+ k5_pwqual_close(handle->context, *(h - 1));
+ k5_pwqual_free_handles(handle->context, list);
+ return ret;
}
}
- return(effect);
+
+ handle->qual_handles = list;
+ return 0;
}
-#endif
-#ifdef HESIOD
-static int
-str_check_gecos(gecos, pwstr)
- char *gecos;
- char *pwstr;
+/* Check a password against all available password quality plugin modules. */
+kadm5_ret_t
+passwd_check(kadm5_server_handle_t handle, const char *password,
+ kadm5_policy_ent_t policy, krb5_principal princ)
{
- char *cp, *ncp, *tcp;
-
- for (cp = gecos; *cp; ) {
- /* Skip past punctuation */
- for (; *cp; cp++)
- if (isalnum(*cp))
- break;
- /* Skip to the end of the word */
- for (ncp = cp; *ncp; ncp++)
- if (!isalnum(*ncp) && *ncp != '\'')
- break;
- /* Delimit end of word */
- if (*ncp)
- *ncp++ = '\0';
- /* Check word to see if it's the password */
- if (*cp) {
- if (!strcasecmp(pwstr, cp))
- return 1;
- tcp = reverse(cp);
- if (!strcasecmp(pwstr, tcp))
- return 1;
- cp = ncp;
- } else
- break;
+ krb5_error_code ret;
+ pwqual_handle *h;
+
+ for (h = handle->qual_handles; *h != NULL; h++) {
+ ret = k5_pwqual_check(handle->context, *h, password, policy, princ);
+ if (ret != 0)
+ return ret;
}
return 0;
}
-#endif /* HESIOD */
-/* some of this is stolen from gatekeeper ... */
-kadm5_ret_t
-passwd_check(kadm5_server_handle_t handle,
- char *password, int use_policy, kadm5_policy_ent_t pol,
- krb5_principal principal)
+void
+destroy_pwqual(kadm5_server_handle_t handle)
{
- int nupper = 0,
- nlower = 0,
- ndigit = 0,
- npunct = 0,
- nspec = 0;
- char c, *s, *cp;
-#ifdef HESIOD
- extern struct passwd *hes_getpwnam();
- struct passwd *ent;
-#endif
+ pwqual_handle *h;
- if(use_policy) {
- if(strlen(password) < pol->pw_min_length)
- return KADM5_PASS_Q_TOOSHORT;
- s = password;
- while ((c = *s++)) {
- if (islower((unsigned char) c)) {
- nlower = 1;
- continue;
- }
- else if (isupper((unsigned char) c)) {
- nupper = 1;
- continue;
- } else if (isdigit((unsigned char) c)) {
- ndigit = 1;
- continue;
- } else if (ispunct((unsigned char) c)) {
- npunct = 1;
- continue;
- } else {
- nspec = 1;
- continue;
- }
- }
- if ((nupper + nlower + ndigit + npunct + nspec) < pol->pw_min_classes)
- return KADM5_PASS_Q_CLASS;
- if((find_word(password) == KADM5_OK))
- return KADM5_PASS_Q_DICT;
- else {
- int i, n = krb5_princ_size(handle->context, principal);
- cp = krb5_princ_realm(handle->context, principal)->data;
- if (strcasecmp(cp, password) == 0)
- return KADM5_PASS_Q_DICT;
- for (i = 0; i < n ; i++) {
- cp = krb5_princ_component(handle->context, principal, i)->data;
- if (strcasecmp(cp, password) == 0)
- return KADM5_PASS_Q_DICT;
-#ifdef HESIOD
- ent = hes_getpwnam(cp);
- if (ent && ent->pw_gecos)
- if (str_check_gecos(ent->pw_gecos, password))
- return KADM5_PASS_Q_DICT; /* XXX new error code? */
-#endif
- }
- return KADM5_OK;
- }
- } else {
- if (strlen(password) < 1)
- return KADM5_PASS_Q_TOOSHORT;
- }
- return KADM5_OK;
+ for (h = handle->qual_handles; *h != NULL; h++)
+ k5_pwqual_close(handle->context, *h);
+ k5_pwqual_free_handles(handle->context, handle->qual_handles);
+ handle->qual_handles = NULL;
}
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
index 6b14d3b..dc16406 100644
--- a/src/lib/kadm5/srv/svr_principal.c
+++ b/src/lib/kadm5/srv/svr_principal.c
@@ -292,7 +292,7 @@ kadm5_create_principal_3(void *server_handle,
have_polent = TRUE;
}
if (password) {
- ret = passwd_check(handle, password, have_polent, &polent,
+ ret = passwd_check(handle, password, have_polent ? &polent : NULL,
entry->principal);
if (ret)
goto cleanup;
@@ -1341,8 +1341,8 @@ kadm5_chpass_principal_3(void *server_handle,
have_pol = 1;
}
- if ((ret = passwd_check(handle, password, adb.aux_attributes &
- KADM5_POLICY, &pol, principal)))
+ if ((ret = passwd_check(handle, password, have_pol ? &pol : NULL,
+ principal)))
goto done;
ret = krb5_dbe_find_act_mkey(handle->context, master_keylist,
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index e52200d..0c01386 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -74,6 +74,7 @@ STLIBOBJS= \
pac.o \
pac_sign.o \
parse.o \
+ plugin.o \
pr_to_salt.o \
preauth2.o \
gic_opt_set_pa.o \
@@ -173,6 +174,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \
$(OUTPRE)pac.$(OBJEXT) \
$(OUTPRE)pac_sign.$(OBJEXT) \
$(OUTPRE)parse.$(OBJEXT) \
+ $(OUTPRE)plugin.$(OBJEXT) \
$(OUTPRE)pr_to_salt.$(OBJEXT) \
$(OUTPRE)preauth2.$(OBJEXT) \
$(OUTPRE)gic_opt_set_pa.$(OBJEXT) \
@@ -273,6 +275,7 @@ SRCS= $(srcdir)/addr_comp.c \
$(srcdir)/pac.c \
$(srcdir)/pac_sign.c \
$(srcdir)/parse.c \
+ $(srcdir)/plugin.c \
$(srcdir)/pr_to_salt.c \
$(srcdir)/preauth2.c \
$(srcdir)/gic_opt_set_pa.c \
diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c
index e7419f5..c5975f1 100644
--- a/src/lib/krb5/krb/init_ctx.c
+++ b/src/lib/krb5/krb/init_ctx.c
@@ -273,6 +273,8 @@ krb5_free_context(krb5_context ctx)
ctx->trace_callback(ctx, NULL, ctx->trace_callback_data);
#endif
+ k5_plugin_free_context(ctx);
+
ctx->magic = 0;
free(ctx);
}
diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c
new file mode 100644
index 0000000..aa9ac89
--- /dev/null
+++ b/src/lib/krb5/krb/plugin.c
@@ -0,0 +1,382 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/krb5/krb/plugin.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Plugin framework functions
+ */
+
+#include "k5-int.h"
+
+const char *interface_names[PLUGIN_NUM_INTERFACES] = {
+ "pwqual"
+};
+
+/* Return the context's interface structure for id, or NULL if invalid. */
+static inline struct plugin_interface *
+get_interface(krb5_context context, int id)
+{
+ if (context == NULL || id < 0 || id >= PLUGIN_NUM_INTERFACES)
+ return NULL;
+ return &context->plugins[id];
+}
+
+/* Release the memory associated with the linked list entry map. */
+static void
+free_plugin_mapping(struct plugin_mapping *map)
+{
+ if (map == NULL)
+ return;
+ free(map->modname);
+ if (map->dyn_handle != NULL)
+ krb5int_close_plugin(map->dyn_handle);
+ free(map);
+}
+
+/*
+ * Register a mapping from modname to module. On success, dyn_handle is
+ * remembered in the mapping and will be released when the mapping is
+ * overwritten or the context is destroyed.
+ */
+static krb5_error_code
+register_module(krb5_context context, struct plugin_interface *interface,
+ const char *modname, krb5_plugin_init_fn module,
+ struct plugin_file_handle *dyn_handle)
+{
+ struct plugin_mapping *map, **pmap;
+
+ /* If a mapping already exists for modname, remove it. */
+ for (pmap = &interface->modules; *pmap != NULL; pmap = &(*pmap)->next) {
+ map = *pmap;
+ if (strcmp(map->modname, modname) == 0) {
+ *pmap = map->next;
+ free_plugin_mapping(map);
+ break;
+ }
+ }
+
+ /* Create a new mapping structure. */
+ map = malloc(sizeof(*map));
+ if (map == NULL)
+ return ENOMEM;
+ map->modname = strdup(modname);
+ if (map->modname == NULL) {
+ free(map);
+ return ENOMEM;
+ }
+ map->module = module;
+ map->dyn_handle = dyn_handle;
+
+ /* Chain it into the list. */
+ map->next = interface->modules;
+ interface->modules = map;
+ return 0;
+}
+
+/* Parse a profile module string of the form "modname:modpath" into its
+ * component parts. */
+static krb5_error_code
+parse_modstr(krb5_context context, const char *modstr,
+ char **modname, char **modpath)
+{
+ const char *sep;
+ char *name = NULL, *path = NULL;
+
+ *modname = NULL;
+ *modpath = NULL;
+
+ sep = strchr(modstr, ':');
+ if (sep == NULL) {
+ krb5_set_error_message(context, EINVAL, "Invalid module string %s",
+ modstr);
+ return EINVAL; /* XXX create specific error code */
+ }
+
+ /* Copy the module name. */
+ name = malloc(sep - modstr + 1);
+ if (name == NULL)
+ return ENOMEM;
+ memcpy(name, modstr, sep - modstr);
+ name[sep - modstr] = '\0';
+
+ /* Copy the module path. */
+ path = strdup(sep + 1);
+ if (path == NULL) {
+ free(name);
+ return ENOMEM;
+ }
+
+ *modname = name;
+ *modpath = path;
+ return 0;
+}
+
+/* Open a dynamic object at modpath, look up symname within it, and register
+ * the resulting init function as modname. */
+static krb5_error_code
+open_and_register(krb5_context context, struct plugin_interface *interface,
+ const char *modname, const char *modpath,
+ const char *symname)
+{
+ krb5_error_code ret;
+ struct plugin_file_handle *handle;
+ void (*init_fn)();
+
+ ret = krb5int_open_plugin(modpath, &handle, &context->err);
+ if (ret != 0)
+ return ret;
+
+ ret = krb5int_get_plugin_func(handle, symname, &init_fn, &context->err);
+ if (ret != 0) {
+ krb5int_close_plugin(handle);
+ return ret;
+ }
+
+ ret = register_module(context, interface, modname,
+ (krb5_plugin_init_fn)init_fn, handle);
+ if (ret != 0)
+ krb5int_close_plugin(handle);
+ return ret;
+}
+
+/* Register the plugins given by the profile strings in modules. */
+static krb5_error_code
+register_dyn_modules(krb5_context context, struct plugin_interface *interface,
+ const char *iname, char **modules)
+{
+ krb5_error_code ret;
+ char *modname = NULL, *modpath = NULL, *symname = NULL;
+
+ for (; *modules != NULL; modules++) {
+ ret = parse_modstr(context, *modules, &modname, &modpath);
+ if (ret != 0)
+ return ret;
+ if (asprintf(&symname, "%s_%s_init", iname, modname) < 0) {
+ free(modname);
+ free(modpath);
+ return ENOMEM;
+ }
+ /* XXX should errors here be fatal, or just ignore the module? */
+ ret = open_and_register(context, interface, modname, modpath, symname);
+ free(modname);
+ free(modpath);
+ free(symname);
+ if (ret != 0)
+ return ret;
+ }
+ return 0;
+}
+
+/* Return true if value is found in list. */
+static krb5_boolean
+find_in_list(char **list, const char *value)
+{
+ for (; *list != NULL; list++) {
+ if (strcmp(*list, value) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Remove any registered modules whose names are not present in enable. */
+static void
+filter_enable(krb5_context context, struct plugin_interface *interface,
+ char **enable)
+{
+ struct plugin_mapping *map, **pmap;
+
+ pmap = &interface->modules;
+ while (*pmap != NULL) {
+ map = *pmap;
+ if (!find_in_list(enable, map->modname)) {
+ *pmap = map->next;
+ free_plugin_mapping(map);
+ } else
+ pmap = &map->next;
+ }
+}
+
+/* Remove any registered modules whose names are present in disable. */
+static void
+filter_disable(krb5_context context, struct plugin_interface *interface,
+ char **disable)
+{
+ struct plugin_mapping *map, **pmap;
+
+ pmap = &interface->modules;
+ while (*pmap != NULL) {
+ map = *pmap;
+ if (find_in_list(disable, map->modname)) {
+ *pmap = map->next;
+ free_plugin_mapping(map);
+ } else
+ pmap = &map->next;
+ }
+}
+
+/* Ensure that a plugin interface is configured. id is assumed to be valid. */
+static krb5_error_code
+configure_interface(krb5_context context, int id)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = &context->plugins[id];
+ const char *iname = interface_names[id];
+ char **modules = NULL, **enable = NULL, **disable = NULL;
+ static const char *path[4];
+
+ if (interface->configured)
+ return 0;
+
+ /* Read the configuration variables for this interface. */
+ path[0] = KRB5_CONF_PLUGINS;
+ path[1] = iname;
+ path[2] = KRB5_CONF_MODULE;
+ path[3] = NULL;
+ ret = profile_get_values(context->profile, path, &modules);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
+ path[2] = KRB5_CONF_ENABLE_ONLY;
+ ret = profile_get_values(context->profile, path, &enable);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
+ path[2] = KRB5_CONF_DISABLE;
+ ret = profile_get_values(context->profile, path, &disable);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
+
+ if (modules != NULL) {
+ ret = register_dyn_modules(context, interface, iname, modules);
+ if (ret != 0)
+ return ret;
+ }
+ if (enable != NULL)
+ filter_enable(context, interface, enable);
+ if (disable != NULL)
+ filter_disable(context, interface, disable);
+
+ ret = 0;
+cleanup:
+ profile_free_list(modules);
+ profile_free_list(enable);
+ profile_free_list(disable);
+ return ret;
+}
+
+krb5_error_code
+k5_plugin_load(krb5_context context, int interface_id, const char *modname,
+ krb5_plugin_init_fn *module)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = get_interface(context, interface_id);
+ struct plugin_mapping *map;
+
+ if (interface == NULL)
+ return EINVAL;
+ ret = configure_interface(context, interface_id);
+ if (ret != 0)
+ return ret;
+ for (map = interface->modules; map != NULL; map = map->next) {
+ if (strcmp(map->modname, modname) == 0) {
+ *module = map->module;
+ return 0;
+ }
+ }
+ return ENOENT; /* XXX Create error code? */
+}
+
+krb5_error_code
+k5_plugin_load_all(krb5_context context, int interface_id,
+ krb5_plugin_init_fn **modules)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = get_interface(context, interface_id);
+ struct plugin_mapping *map;
+ krb5_plugin_init_fn *list;
+ size_t count;
+
+ if (interface == NULL)
+ return EINVAL;
+ ret = configure_interface(context, interface_id);
+ if (ret != 0)
+ return ret;
+
+ /* Count the modules and allocate a list to hold them. */
+ count = 0;
+ for (map = interface->modules; map != NULL; map = map->next)
+ count++;
+ list = malloc((count + 1) * sizeof(*list));
+ if (list == NULL)
+ return ENOMEM;
+
+ /* Place each module's init function into list. */
+ count = 0;
+ for (map = interface->modules; map != NULL; map = map->next)
+ list[count++] = map->module;
+ list[count] = NULL;
+
+ *modules = list;
+ return 0;
+}
+
+void
+k5_plugin_free_modules(krb5_context context, krb5_plugin_init_fn *modules)
+{
+ free(modules);
+}
+
+krb5_error_code
+k5_plugin_register(krb5_context context, int interface_id, const char *modname,
+ krb5_plugin_init_fn module)
+{
+ struct plugin_interface *interface = get_interface(context, interface_id);
+
+ if (interface == NULL)
+ return EINVAL;
+
+ /* Disallow registering plugins after load. We may need to reconsider
+ * this, but it simplifies the design. */
+ if (interface->configured)
+ return EINVAL;
+
+ return register_module(context, interface, modname, module, NULL);
+}
+
+void
+k5_plugin_free_context(krb5_context context)
+{
+ int i;
+ struct plugin_interface *interface;
+ struct plugin_mapping *map, *next;
+
+ for (i = 0; i < PLUGIN_NUM_INTERFACES; i++) {
+ interface = &context->plugins[i];
+ for (map = interface->modules; map != NULL; map = next) {
+ next = map->next;
+ free_plugin_mapping(map);
+ }
+ interface->modules = NULL;
+ interface->configured = FALSE;
+ }
+}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 2bd5972..b375c1b 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -104,6 +104,10 @@ initialize_kdb5_error_table
initialize_krb5_error_table
initialize_kv5m_error_table
initialize_prof_error_table
+k5_plugin_free_modules
+k5_plugin_load
+k5_plugin_load_all
+k5_plugin_register
krb524_convert_creds_kdc
krb524_init_ets
krb5_425_conv_principal