From: Sam Hartman Date: Wed, 29 Sep 2010 21:29:14 +0000 (+0000) Subject: kadm5_hook: new plugin interface X-Git-Tag: krb5-1.9-beta1~71 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=36ad7da6dc4dbd1090b2f39b93b30b3ac8eee396;p=krb5.git kadm5_hook: new plugin interface Implement http://k5wiki.kerberos.org/wiki/Projects/Kadmin_hook_interface This provides an interface that allows a plugin to track kadmin operations. This can be used for projects like the krb5-sync project. ticket: 6791 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24375 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/include/k5-int.h b/src/include/k5-int.h index efffa3b5e..750f989c9 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -1517,7 +1517,8 @@ struct plugin_interface { /* A list of plugin interface IDs. Make sure to increment * PLUGIN_NUM_INTERFACES when a new interface is added. */ #define PLUGIN_INTERFACE_PWQUAL 0 -#define PLUGIN_NUM_INTERFACES 1 +#define PLUGIN_INTERFACE_KADM5_HOOK 1 +#define PLUGIN_NUM_INTERFACES 2 /* Retrieve the plugin module of type interface_id and name modname, * storing the result into module. */ diff --git a/src/include/krb5/kadm5_hook_plugin.h b/src/include/krb5/kadm5_hook_plugin.h new file mode 100644 index 000000000..7e23a249d --- /dev/null +++ b/src/include/krb5/kadm5_hook_plugin.h @@ -0,0 +1,126 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * include/krb5/kadm5_hook_plugin.h + */ +/* + * 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. + */ + +#ifndef H_KRB5_KADM5_HOOK_PLUGIN +#define H_KRB5_KADM5_HOOK_PLUGIN + +/** + * @file krb5/krb5_kadm5_hook_plugin.h + * Provide a plugin interface for kadm5 operations. This interface + * permits a plugin to intercept principal modification, creation and + * change password operations. Operations run at two stages: a + * precommit stage that runs before the operation is committed to the + * database and a postcommit operation that runs after the database + * is updated; see #kadm5_hook_stage for details on semantics. + * + * This interface is based on a proposed extension to Heimdal by Russ + * Allbery; it is likely that Heimdal will adopt an approach based on + * stacked kdb modules rather than this interface. For MIT, writing a + * plugin to this interface is significantly easier than stacking kdb + * modules. Also, the kadm5 interface is significantly more stable + * than the kdb interface, so this approach is more desirable than + * stacked kdb modules. + * + * This interface depends on kadm5/admin.h. As such, the interface + * does not provide strong guarantees of ABI stability. + */ + +#include +#include +#include + +/** + * Whether the operation is being run before or after the database + * update + */ +enum kadm5_hook_stage { + /** In this stage, any plugin failure prevents following plugins from + * running and aborts the operation.*/ + KADM5_HOOK_STAGE_PRECOMMIT, + /** In this stage, plugin failures are logged but otherwise ignored.*/ + KADM5_HOOK_STAGE_POSTCOMMIT +}; + +/** Opaque module data pointer*/ +typedef struct kadm5_hook_modinfo_st kadm5_hook_modinfo; + +/** + * Interface for the v1 virtual table for the kadm5_hook plugin + * All entry points are optional. The name field must be provided. + */ +typedef struct kadm5_hook_vtable_1_st { + + /** A text string identifying the plugin for logging messages*/ + char *name; + + /** Initialize a plugin module + * @param modinfo returns newly allocated module info for future + * calls. Cleaned up by the fini() function. + */ + kadm5_ret_t (*init)(krb5_context, kadm5_hook_modinfo **modinfo); + + /** Clean up a module and free @a modinfo*/ + void (*fini)(krb5_context, kadm5_hook_modinfo *modinfo); + + /** Indicates that the password is being changed. + * @param stage is an integer from #kadm5_hook_stage enumeration + * @param keepold is true if existing keys are being kept. + * */ + kadm5_ret_t (*chpass)(krb5_context, + kadm5_hook_modinfo *modinfo, + int stage, + krb5_principal, krb5_boolean keepold, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + const char *newpass); + + /** Indicate a principal is created*/ + kadm5_ret_t (*create)(krb5_context, + kadm5_hook_modinfo *, + int stage, + kadm5_principal_ent_t, long mask, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + const char *password); + /** Modify a principal*/ + kadm5_ret_t (*modify)(krb5_context, + kadm5_hook_modinfo *, + int stage, + kadm5_principal_ent_t, long mask); + + + /** Indicate a principal is deleted*/ + kadm5_ret_t (* remove) (krb5_context, + kadm5_hook_modinfo *modinfo, + int stage, krb5_principal + ); + + /*End of minor version 1*/ +}kadm5_hook_vftable_1; + +#endif /*H_KRB5_KADM5_HOOK_PLUGIN*/ diff --git a/src/lib/kadm5/server_internal.h b/src/lib/kadm5/server_internal.h index fd0f7b4ae..59c7b0512 100644 --- a/src/lib/kadm5/server_internal.h +++ b/src/lib/kadm5/server_internal.h @@ -37,6 +37,8 @@ /* A pwqual_handle represents a password quality plugin module. */ typedef struct pwqual_handle_st *pwqual_handle; +typedef struct kadm5_hook_handle_st *kadm5_hook_handle; + typedef struct _kadm5_server_handle_t { krb5_ui_4 magic_number; krb5_ui_4 struct_version; @@ -47,6 +49,7 @@ typedef struct _kadm5_server_handle_t { struct _kadm5_server_handle_t *lhandle; char **db_args; pwqual_handle *qual_handles; + kadm5_hook_handle *hook_handles; } kadm5_server_handle_rec, *kadm5_server_handle_t; #define OSA_ADB_PRINC_VERSION_1 0x12345C01 @@ -198,4 +201,56 @@ krb5_error_code pwqual_princ_initvt(krb5_context context, int maj_ver, int min_ver, krb5_plugin_vtable vtable); +/** @{ + * @name kadm5_hook plugin support + */ + +/**Load all kadm5_hook plugins*/ +krb5_error_code +k5_kadm5_hook_load(krb5_context context, + kadm5_hook_handle **handles_out); + +/** Free handles allocated by k5_kadm5_hook_load()*/ +void +k5_kadm5_hook_free_handles(krb5_context context, kadm5_hook_handle *handles); + +/**Call the chpass entry point on every kadm5_hook in @a handles*/ +kadm5_ret_t +k5_kadm5_hook_chpass (krb5_context context, + kadm5_hook_handle *handles, + int stage, krb5_principal princ, + krb5_boolean keepold, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + const char *newpass); + +/** Call the create entry point for kadm5_hook_plugins*/ +kadm5_ret_t +k5_kadm5_hook_create (krb5_context context, + kadm5_hook_handle *handles, + int stage, + kadm5_principal_ent_t princ, long mask, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + const char *newpass); + +/** Call modify kadm5_hook entry point*/ +kadm5_ret_t +k5_kadm5_hook_modify (krb5_context context, + kadm5_hook_handle *handles, + int stage, + kadm5_principal_ent_t princ, long mask); + +/** call remove kadm5_hook entry point*/ +kadm5_ret_t +k5_kadm5_hook_remove (krb5_context context, + kadm5_hook_handle *handles, + int stage, + krb5_principal princ); + +/** @}*/ + + + + #endif /* __KADM5_SERVER_INTERNAL_H__ */ diff --git a/src/lib/kadm5/srv/Makefile.in b/src/lib/kadm5/srv/Makefile.in index a513035d5..378e03c76 100644 --- a/src/lib/kadm5/srv/Makefile.in +++ b/src/lib/kadm5/srv/Makefile.in @@ -28,6 +28,7 @@ SHLIB_RDIRS=$(KRB5_LIBDIR) RELDIR=kadm5/srv SRCS = $(srcdir)/pwqual.c \ + $(srcdir)/kadm5_hook.c \ $(srcdir)/pwqual_dict.c \ $(srcdir)/pwqual_empty.c \ $(srcdir)/pwqual_hesiod.c \ @@ -47,6 +48,7 @@ OBJS = pwqual.$(OBJEXT) \ pwqual_empty.$(OBJEXT) \ pwqual_hesiod.$(OBJEXT) \ pwqual_princ.$(OBJEXT) \ + kadm5_hook.$(OBJEXT) \ svr_policy.$(OBJEXT) \ svr_principal.$(OBJEXT) \ server_acl.$(OBJEXT) \ @@ -63,6 +65,7 @@ STLIBOBJS = \ pwqual_empty.o \ pwqual_hesiod.o \ pwqual_princ.o \ + kadm5_hook.o \ svr_policy.o \ svr_principal.o \ server_acl.o \ diff --git a/src/lib/kadm5/srv/kadm5_hook.c b/src/lib/kadm5/srv/kadm5_hook.c new file mode 100644 index 000000000..b86ab4297 --- /dev/null +++ b/src/lib/kadm5/srv/kadm5_hook.c @@ -0,0 +1,189 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * lib/kadm5/srv/kadm5_hook.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 kadm5_hook plugins*/ + + +#include "k5-int.h" +#include "server_internal.h" +#include +#include +#include + +struct kadm5_hook_handle_st { + kadm5_hook_vftable_1 vt; + kadm5_hook_modinfo *data; +}; + +krb5_error_code +k5_kadm5_hook_load(krb5_context context, + kadm5_hook_handle **handles_out) +{ + krb5_error_code ret; + krb5_plugin_initvt_fn *modules = NULL, *mod; + size_t count; + kadm5_hook_handle *list = NULL, handle = NULL; + + *handles_out = NULL; + + ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_KADM5_HOOK, &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, initialize its vtable, and initialize the module*/ + 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) { /* Failed vtable init is non-fatal. */ + free(handle); + handle = NULL; + continue; + } + handle->data = NULL; + if (handle->vt.init != NULL) { + ret = handle->vt.init(context, &handle->data); + if (ret != 0) /* Failed dictionary binding is fatal. */ + goto cleanup; + } + list[count++] = handle; + list[count] = NULL; + handle = NULL; + } + list[count] = NULL; + + ret = 0; + *handles_out = list; + list = NULL; + +cleanup: + free(handle); + k5_plugin_free_modules(context, modules); + k5_kadm5_hook_free_handles(context, list); + return ret; +} + +void +k5_kadm5_hook_free_handles(krb5_context context, kadm5_hook_handle *handles) +{ + kadm5_hook_handle *hp, handle; + + if (handles == NULL) + return; + for (hp = handles; *hp != NULL; hp++) { + handle = *hp; + if (handle->vt.fini != NULL) + handle->vt.fini(context, handle->data); + } + free(handles); +} + +static void +log_failure(krb5_context context, + const char *name, + const char *function, + krb5_error_code ret) +{ + const char *e = krb5_get_error_message(context, ret); + if (e) + krb5_klog_syslog(LOG_ERR, "kadm5_hook %s failed postcommit %s: %s", + name, function, e); + krb5_free_error_message(context, e); +} + +#define ITERATE(operation, params) \ + for (; *handles; handles++) { \ + kadm5_hook_handle h = *handles; \ + krb5_error_code ret = 0; \ + if (h->vt.operation) { \ + ret = h->vt.operation params; \ + } \ + if (ret) { \ + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) \ + return ret; \ + else log_failure(context, h->vt.name, #operation, ret); \ + } \ + } + + +kadm5_ret_t +k5_kadm5_hook_chpass (krb5_context context, + kadm5_hook_handle *handles, + int stage, krb5_principal princ, + krb5_boolean keepold, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + const char *newpass) +{ + ITERATE(chpass, (context, h->data, + stage, princ, keepold, + n_ks_tuple, ks_tuple, newpass)); + return 0; +} + +kadm5_ret_t +k5_kadm5_hook_create (krb5_context context, + kadm5_hook_handle *handles, + int stage, + kadm5_principal_ent_t princ, long mask, + int n_ks_tuple, + krb5_key_salt_tuple *ks_tuple, + const char *newpass) +{ + ITERATE(create, (context, h->data, + stage, princ, mask, n_ks_tuple, ks_tuple, newpass)); + return 0; +} + +kadm5_ret_t +k5_kadm5_hook_modify (krb5_context context, + kadm5_hook_handle *handles, + int stage, + kadm5_principal_ent_t princ, long mask) +{ + ITERATE(modify, (context, h->data, + stage, princ, mask)); + return 0; +} + +kadm5_ret_t +k5_kadm5_hook_remove (krb5_context context, + kadm5_hook_handle *handles, + int stage, + krb5_principal princ) +{ + ITERATE(remove, (context, h->data, + stage, princ)); + return 0; +} diff --git a/src/lib/kadm5/srv/server_init.c b/src/lib/kadm5/srv/server_init.c index 9ebc13eea..21604a32c 100644 --- a/src/lib/kadm5/srv/server_init.c +++ b/src/lib/kadm5/srv/server_init.c @@ -317,8 +317,18 @@ kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass, return ret; } + ret = k5_kadm5_hook_load(context,&handle->hook_handles); + if (ret) { + krb5_db_fini(handle->context); + krb5_free_principal(handle->context, handle->current_caller); + free_db_args(handle); + free(handle); + return ret; + } + ret = init_pwqual(handle); if (ret) { + k5_kadm5_hook_free_handles(context, handle->hook_handles); krb5_db_fini(handle->context); krb5_free_principal(handle->context, handle->current_caller); free_db_args(handle); @@ -339,6 +349,7 @@ kadm5_ret_t kadm5_destroy(void *server_handle) destroy_pwqual(handle); + k5_kadm5_hook_free_handles(handle->context, handle->hook_handles); adb_policy_close(handle); krb5_db_fini(handle->context); krb5_free_principal(handle->context, handle->current_caller); diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c index 132078ce3..6ba2c43dc 100644 --- a/src/lib/kadm5/srv/svr_principal.c +++ b/src/lib/kadm5/srv/svr_principal.c @@ -4,11 +4,7 @@ * * $Header$ */ - -#if !defined(lint) && !defined(__CODECENTER__) -static char *rcsid = "$Header$"; -#endif - +#include #include #include #include @@ -25,6 +21,8 @@ static char *rcsid = "$Header$"; #endif +#include + #ifdef USE_VALGRIND #include #else @@ -393,6 +391,13 @@ kadm5_create_principal_3(void *server_handle, if (ret) goto cleanup; + ret = k5_kadm5_hook_create(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, entry, mask, + n_ks_tuple?n_ks_tuple:handle->params.num_keysalts, + n_ks_tuple?ks_tuple:handle->params.keysalts, password); + if (ret) + goto cleanup; + /* populate the admin-server-specific fields. In the OV server, this used to be in a separate database. Since there's already marshalling code for the admin fields, to keep things simple, @@ -440,6 +445,11 @@ kadm5_create_principal_3(void *server_handle, } } + (void) k5_kadm5_hook_create(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask, + n_ks_tuple?n_ks_tuple:handle->params.num_keysalts, + n_ks_tuple?ks_tuple:handle->params.keysalts, password); + cleanup: krb5_db_free_principal(handle->context, kdb); if (have_polent) @@ -466,6 +476,12 @@ kadm5_delete_principal(void *server_handle, krb5_principal principal) if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) return(ret); + ret = k5_kadm5_hook_remove(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, principal); + if (ret) { + kdb_free_entry(handle, kdb, &adb); + return ret; + } if ((adb.aux_attributes & KADM5_POLICY)) { if ((ret = kadm5_get_policy(handle->lhandle, @@ -490,6 +506,11 @@ kadm5_delete_principal(void *server_handle, krb5_principal principal) kdb_free_entry(handle, kdb, &adb); + if (ret == 0) + (void) k5_kadm5_hook_remove(handle->context, + handle->hook_handles, + KADM5_HOOK_STAGE_POSTCOMMIT, principal); + return ret; } @@ -681,8 +702,15 @@ kadm5_modify_principal(void *server_handle, /* let the mask propagate to the database provider */ kdb->mask = mask; + ret = k5_kadm5_hook_modify(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, entry, mask); + if (ret) + goto done; + ret = kdb_put_entry(handle, kdb, &adb); if (ret) goto done; + (void) k5_kadm5_hook_modify(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask); ret = KADM5_OK; done: @@ -834,7 +862,7 @@ kadm5_get_principal(void *server_handle, krb5_principal principal, if (mask & KADM5_KVNO) for (entry->kvno = 0, i=0; in_key_data; i++) - if (kdb->key_data[i].key_data_kvno > entry->kvno) + if ((krb5_kvno) kdb->key_data[i].key_data_kvno > entry->kvno) entry->kvno = kdb->key_data[i].key_data_kvno; if (mask & KADM5_MKVNO) { @@ -941,13 +969,14 @@ check_pw_reuse(krb5_context context, krb5_keyblock newkey, histkey; krb5_error_code ret; - for (x = 0; x < n_new_key_data; x++) { + assert (n_new_key_data >= 0); + for (x = 0; x < (unsigned) n_new_key_data; x++) { ret = krb5_dbe_decrypt_key_data(context, NULL, &(new_key_data[x]), &newkey, NULL); if (ret) return(ret); for (y = 0; y < n_pw_hist_data; y++) { - for (z = 0; z < pw_hist_data[y].n_key_data; z++) { + for (z = 0; z < (unsigned int) pw_hist_data[y].n_key_data; z++) { ret = krb5_dbe_decrypt_key_data(context, hist_keyblock, &pw_hist_data[y].key_data[z], &histkey, NULL); @@ -1170,7 +1199,7 @@ static kadm5_ret_t add_to_history(krb5_context context, knext = adb->old_key_next = 0; /* free the old pw history entry if it contains data */ histp = &adb->old_keys[knext]; - for (i = 0; i < histp->n_key_data; i++) + for (i = 0; i < (unsigned int) histp->n_key_data; i++) krb5_free_key_data_contents(context, &histp->key_data[i]); free(histp->key_data); @@ -1472,9 +1501,22 @@ kadm5_chpass_principal_3(void *server_handle, KADM5_FAIL_AUTH_COUNT; /* | KADM5_CPW_FUNCTION */ + ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold, + n_ks_tuple?n_ks_tuple:handle->params.num_keysalts, + n_ks_tuple?ks_tuple:handle->params.keysalts, + password); + if (ret) + goto done; + if ((ret = kdb_put_entry(handle, kdb, &adb))) goto done; + (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_POSTCOMMIT, principal, keepold, + n_ks_tuple?n_ks_tuple:handle->params.num_keysalts, + n_ks_tuple?ks_tuple:handle->params.keysalts, + password); ret = KADM5_OK; done: if (!hist_added && hist.key_data) @@ -1608,9 +1650,21 @@ kadm5_randkey_principal_3(void *server_handle, kdb->mask = KADM5_KEY_DATA | KADM5_FAIL_AUTH_COUNT; /* | KADM5_RANDKEY_USED */; + ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold, + n_ks_tuple?n_ks_tuple:handle->params.num_keysalts, + n_ks_tuple?ks_tuple:handle->params.keysalts, + NULL); + if (ret) + goto done; if ((ret = kdb_put_entry(handle, kdb, &adb))) goto done; + (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_POSTCOMMIT, principal, keepold, + n_ks_tuple?n_ks_tuple:handle->params.num_keysalts, + n_ks_tuple?ks_tuple:handle->params.keysalts, + NULL); ret = KADM5_OK; done: kdb_free_entry(handle, kdb, &adb); diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c index 277da2472..0df393ad9 100644 --- a/src/lib/krb5/krb/plugin.c +++ b/src/lib/krb5/krb/plugin.c @@ -31,7 +31,8 @@ #include "k5-int.h" const char *interface_names[PLUGIN_NUM_INTERFACES] = { - "pwqual" + "pwqual", + "kadm5_hook" }; /* Return the context's interface structure for id, or NULL if invalid. */