diff --git a/kdc/windc.c b/kdc/windc.c index 4048f6ca8..84b651ad2 100644 --- a/kdc/windc.c +++ b/kdc/windc.c @@ -39,6 +39,21 @@ static int have_plugin = 0; * Pick the first WINDC module that we find. */ +static const char *windc_plugin_deps[] = { + "kdc", + "krb5", + "hdb", + NULL +}; + +static struct krb5_plugin_data windc_plugin_data = { + "krb5", + "windc", + KRB5_WINDC_PLUGIN_MINOR, + windc_plugin_deps, + kdc_get_instance +}; + static krb5_error_code KRB5_LIB_CALL load(krb5_context context, const void *plug, void *plugctx, void *userctx) { @@ -49,8 +64,8 @@ load(krb5_context context, const void *plug, void *plugctx, void *userctx) krb5_error_code krb5_kdc_windc_init(krb5_context context) { - (void)_krb5_plugin_run_f(context, "krb5", "windc", - KRB5_WINDC_PLUGIN_MINOR, 0, NULL, load); + (void)_krb5_plugin_run_f(context, &windc_plugin_data, 0, NULL, load); + return 0; } @@ -84,8 +99,8 @@ _kdc_pac_generate(krb5_context context, uc.client = client; uc.pac = pac; - (void)_krb5_plugin_run_f(context, "krb5", "windc", - KRB5_WINDC_PLUGIN_MINOR, 0, &uc, generate); + (void)_krb5_plugin_run_f(context, &windc_plugin_data, + 0, &uc, generate); return 0; } @@ -141,8 +156,8 @@ _kdc_pac_verify(krb5_context context, uc.pac = pac; uc.verified = verified; - (void)_krb5_plugin_run_f(context, "krb5", "windc", - KRB5_WINDC_PLUGIN_MINOR, 0, &uc, verify); + (void)_krb5_plugin_run_f(context, &windc_plugin_data, + 0, &uc, verify); return 0; } @@ -191,8 +206,8 @@ _kdc_check_access(krb5_context context, uc.req = req; uc.method_data = method_data; - ret = _krb5_plugin_run_f(context, "krb5", "windc", - KRB5_WINDC_PLUGIN_MINOR, 0, &uc, check); + ret = _krb5_plugin_run_f(context, &windc_plugin_data, + 0, &uc, check); } if (ret == KRB5_PLUGIN_NO_HANDLE) @@ -202,3 +217,18 @@ _kdc_check_access(krb5_context context, req->msg_type == krb_as_req); return ret; } + +uintptr_t +kdc_get_instance(const char *libname) +{ + static const char *instance = "libkdc"; + + if (strcmp(libname, "kdc") == 0) + return (uintptr_t)instance; + else if (strcmp(libname, "hdb") == 0) + return hdb_get_instance(libname); + else if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + + return 0; +} diff --git a/lib/hdb/hdb.c b/lib/hdb/hdb.c index 1cb33df08..ac2a1fe7c 100644 --- a/lib/hdb/hdb.c +++ b/lib/hdb/hdb.c @@ -390,6 +390,8 @@ make_sym(const char *prefix) return sym; } +static const char *hdb_plugin_deps[] = { "hdb", "krb5", NULL }; + krb5_error_code hdb_list_builtin(krb5_context context, char **list) { @@ -414,12 +416,17 @@ hdb_list_builtin(krb5_context context, char **list) if (h->create == NULL) { struct cb_s cb_ctx; char *f; - char *sym; + struct krb5_plugin_data hdb_plugin_data; + + hdb_plugin_data.module = "krb5"; + hdb_plugin_data.min_version = HDB_INTERFACE_VERSION; + hdb_plugin_data.deps = hdb_plugin_deps; + hdb_plugin_data.get_instance = hdb_get_instance; /* Try loading the plugin */ if (asprintf(&f, "%sfoo", h->prefix) == -1) f = NULL; - if ((sym = make_sym(h->prefix)) == NULL) { + if ((hdb_plugin_data.name = make_sym(h->prefix)) == NULL) { free(buf); free(f); return krb5_enomem(context); @@ -427,11 +434,10 @@ hdb_list_builtin(krb5_context context, char **list) cb_ctx.filename = f; cb_ctx.residual = NULL; cb_ctx.h = NULL; - (void)_krb5_plugin_run_f(context, "krb5", sym, - HDB_INTERFACE_VERSION, 0, &cb_ctx, - callback); + (void)_krb5_plugin_run_f(context, &hdb_plugin_data, 0, + &cb_ctx, callback); free(f); - free(sym); + free(rk_UNCONST(hdb_plugin_data.name)); if (cb_ctx.h == NULL || cb_ctx.h->create == NULL) continue; } @@ -483,17 +489,35 @@ hdb_create(krb5_context context, HDB **db, const char *filename) cb_ctx.filename = filename; if (cb_ctx.h == NULL || cb_ctx.h->create == NULL) { - char *sym; + struct krb5_plugin_data hdb_plugin_data; - if ((sym = make_sym(filename)) == NULL) + hdb_plugin_data.module = "krb5"; + hdb_plugin_data.min_version = HDB_INTERFACE_VERSION; + hdb_plugin_data.deps = hdb_plugin_deps; + hdb_plugin_data.get_instance = hdb_get_instance; + + if ((hdb_plugin_data.name = make_sym(filename)) == NULL) return krb5_enomem(context); - (void)_krb5_plugin_run_f(context, "krb5", sym, HDB_INTERFACE_VERSION, + (void)_krb5_plugin_run_f(context, &hdb_plugin_data, 0, &cb_ctx, callback); - free(sym); + free(rk_UNCONST(hdb_plugin_data.name)); } if (cb_ctx.h == NULL) krb5_errx(context, 1, "No database support for %s", cb_ctx.filename); return (*cb_ctx.h->create)(context, db, cb_ctx.residual); } + +uintptr_t +hdb_get_instance(const char *libname) +{ + static const char *instance = "libhdb"; + + if (strcmp(libname, "hdb") == 0) + return (uintptr_t)instance; + else if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + + return 0; +} diff --git a/lib/hdb/keys.c b/lib/hdb/keys.c index c9dc0b9c1..fb6435aa7 100644 --- a/lib/hdb/keys.c +++ b/lib/hdb/keys.c @@ -31,6 +31,7 @@ * SUCH DAMAGE. */ +#include "krb5_locl.h" #include "hdb_locl.h" struct hx509_certs_data; @@ -45,7 +46,6 @@ struct _krb5_key_data; struct _krb5_encryption_type; struct _krb5_key_type; #include -#include #include /* diff --git a/lib/hdb/libhdb-exports.def b/lib/hdb/libhdb-exports.def index df0a1cf9a..bb1d7ad16 100644 --- a/lib/hdb/libhdb-exports.def +++ b/lib/hdb/libhdb-exports.def @@ -43,6 +43,7 @@ EXPORTS hdb_generate_key_set_password hdb_generate_key_set_password_with_ks_tuple hdb_get_dbinfo + hdb_get_instance hdb_init_db hdb_interface_version DATA hdb_key2principal diff --git a/lib/hdb/version-script.map b/lib/hdb/version-script.map index eb409bec4..c421010c9 100644 --- a/lib/hdb/version-script.map +++ b/lib/hdb/version-script.map @@ -46,6 +46,7 @@ HEIMDAL_HDB_1.0 { hdb_generate_key_set_password; hdb_generate_key_set_password_with_ks_tuple; hdb_get_dbinfo; + hdb_get_instance; hdb_init_db; hdb_key2principal; hdb_kvno2keys; diff --git a/lib/kadm5/Makefile.am b/lib/kadm5/Makefile.am index 95dc28d9c..a6151d51b 100644 --- a/lib/kadm5/Makefile.am +++ b/lib/kadm5/Makefile.am @@ -3,6 +3,7 @@ include $(top_srcdir)/Makefile.am.common libkadm5srv_la_CPPFLAGS = -I$(srcdir)/../krb5 +libkadm5clnt_la_CPPFLAGS = -I$(srcdir)/../krb5 lib_LTLIBRARIES = libkadm5srv.la libkadm5clnt.la libkadm5srv_la_LDFLAGS = -version-info 8:1:0 @@ -34,6 +35,7 @@ libkadm5clnt_la_LIBADD = \ libexec_PROGRAMS = ipropd-master ipropd-slave default_keys_SOURCES = default_keys.c +default_keys_CPPFLAGS = -I$(srcdir)/../krb5 kadm5includedir = $(includedir)/kadm5 buildkadm5include = $(buildinclude)/kadm5 @@ -130,8 +132,10 @@ dist_iprop_log_SOURCES = iprop-log.c nodist_iprop_log_SOURCES = iprop-commands.c ipropd_master_SOURCES = ipropd_master.c ipropd_common.c iprop.h kadm5_locl.h +ipropd_master_CPPFLAGS = -I$(srcdir)/../krb5 ipropd_slave_SOURCES = ipropd_slave.c ipropd_common.c iprop.h kadm5_locl.h +ipropd_slave_CPPFLAGS = -I$(srcdir)/../krb5 man_MANS = kadm5_pwcheck.3 iprop.8 iprop-log.8 @@ -146,6 +150,7 @@ LDADD = \ $(LIB_dlopen) \ $(LIB_pidfile) + iprop_log_LDADD = \ libkadm5srv.la \ $(top_builddir)/lib/hdb/libhdb.la \ @@ -159,6 +164,7 @@ iprop_log_LDADD = \ $(LIB_dlopen) \ $(LIB_pidfile) +iprop_log_CPPFLAGS = -I$(srcdir)/../krb5 iprop-commands.c iprop-commands.h: iprop-commands.in $(SLC) $(srcdir)/iprop-commands.in @@ -182,6 +188,7 @@ ALL_OBJECTS += $(ipropd_slave_OBJECTS) ALL_OBJECTS += $(iprop_log_OBJECTS) ALL_OBJECTS += $(test_pw_quality_OBJECTS) ALL_OBJECTS += $(sample_passwd_check_la_OBJECTS) +ALL_OBJECTS += $(sample_hook_la_OBJECTS) ALL_OBJECTS += $(default_keys_OBJECTS) $(ALL_OBJECTS): $(srcdir)/kadm5-protos.h $(srcdir)/kadm5-private.h diff --git a/lib/kadm5/NTMakefile b/lib/kadm5/NTMakefile index b9fd8400d..9357a7fa1 100644 --- a/lib/kadm5/NTMakefile +++ b/lib/kadm5/NTMakefile @@ -268,10 +268,10 @@ EXPORTS << $(DLLPREP_NODIST) -$(OBJ)\sample_hook.dll: $(OBJ)\sample_hook.obj $(LIBHEIMDAL) +$(OBJ)\sample_hook.dll: $(OBJ)\sample_hook.obj $(LIBKADM5SRV) $(LIBHEIMDAL) $(DLLGUILINK) /DEF:<< EXPORTS - kadm5_hook_init + kadm5_hook_plugin_load << $(DLLPREP_NODIST) diff --git a/lib/kadm5/chpass_s.c b/lib/kadm5/chpass_s.c index 2e4999290..d2bf5794e 100644 --- a/lib/kadm5/chpass_s.c +++ b/lib/kadm5/chpass_s.c @@ -35,6 +35,42 @@ RCSID("$Id$"); +struct chpass_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + krb5_const_principal princ; + uint32_t flags; + size_t n_ks_tuple; + krb5_key_salt_tuple *ks_tuple; + const char *password; +}; + +static krb5_error_code +chpass_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct chpass_principal_hook_ctx *ctx = userctx; + + ret = ftable->chpass(context, hookctx, + ctx->stage, ctx->code, ctx->princ, + ctx->flags, ctx->n_ks_tuple, ctx->ks_tuple, + ctx->password); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "chpass", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t chpass_principal_hook(kadm5_server_context *context, enum kadm5_hook_stage stage, @@ -45,24 +81,22 @@ chpass_principal_hook(kadm5_server_context *context, krb5_key_salt_tuple *ks_tuple, const char *password) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct chpass_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; + ctx.flags = flags; + ctx.n_ks_tuple = n_ks_tuple; + ctx.ks_tuple = ks_tuple; + ctx.password = password; - if (hook->hook->chpass != NULL) { - ret = hook->hook->chpass(context->context, hook->data, - stage, code, princ, flags, - n_ks_tuple, ks_tuple, password); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "chpass", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, chpass_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/create_s.c b/lib/kadm5/create_s.c index d6c30444d..b8332db65 100644 --- a/lib/kadm5/create_s.c +++ b/lib/kadm5/create_s.c @@ -102,6 +102,39 @@ create_principal(kadm5_server_context *context, &ent->entry.created_by.principal); } +struct create_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + kadm5_principal_ent_t princ; + uint32_t mask; + const char *password; +}; + +static krb5_error_code +create_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct create_principal_hook_ctx *ctx = userctx; + + ret = ftable->create(context, hookctx, + ctx->stage, ctx->code, ctx->princ, + ctx->mask, ctx->password); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "create", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t create_principal_hook(kadm5_server_context *context, enum kadm5_hook_stage stage, @@ -110,23 +143,20 @@ create_principal_hook(kadm5_server_context *context, uint32_t mask, const char *password) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct create_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; + ctx.mask = mask; + ctx.password = password; - if (hook->hook->create != NULL) { - ret = hook->hook->create(context->context, hook->data, - stage, code, princ, mask, password); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "create", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, create_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/delete_s.c b/lib/kadm5/delete_s.c index d315e792c..465081bd2 100644 --- a/lib/kadm5/delete_s.c +++ b/lib/kadm5/delete_s.c @@ -35,29 +35,54 @@ RCSID("$Id$"); +struct delete_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + krb5_const_principal princ; +}; + +static krb5_error_code +delete_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct delete_principal_hook_ctx *ctx = userctx; + + ret = ftable->delete(context, hookctx, + ctx->stage, ctx->code, ctx->princ); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "delete", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t delete_principal_hook(kadm5_server_context *context, enum kadm5_hook_stage stage, krb5_error_code code, krb5_const_principal princ) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct delete_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; - if (hook->hook->delete != NULL) { - ret = hook->hook->delete(context->context, hook->data, - stage, code, princ); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "delete", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, delete_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/kadm5-hook.h b/lib/kadm5/kadm5-hook.h index 55755b85d..2c767f109 100644 --- a/lib/kadm5/kadm5-hook.h +++ b/lib/kadm5/kadm5-hook.h @@ -37,9 +37,12 @@ /* * Each hook is called before the operation using KADM5_STAGE_PRECOMMIT and - * then after the operation using KADM5_STAGE_POSTCOMMIT. If the hook returns + * then after the operation using KADM5_STAGE_POSTCOMMIT. If the hook returns * failure during precommit, the operation is aborted without changes to the - * database. + * database. All post-commit hook are invoked if the operation was attempted. + * + * Note that unlike libkrb5 plugins, returning success does not prevent other + * plugins being called (i.e. it is equivalent to KRB5_PLUGIN_NO_HANDLE). */ enum kadm5_hook_stage { KADM5_HOOK_STAGE_PRECOMMIT, @@ -49,25 +52,14 @@ enum kadm5_hook_stage { #define KADM5_HOOK_FLAG_KEEPOLD 0x1 /* keep old password */ #define KADM5_HOOK_FLAG_CONDITIONAL 0x2 /* only change password if different */ -/* - * libkadm5srv expects a symbol named kadm5_hook_init that must be a function - * of type kadm5_hook_init_t. The function will be called with the maximum - * version of the hook API supported by libkadm5; the plugin may return an - * earlier version. - */ -typedef struct kadm5_hook { +typedef struct kadm5_hook_ftable { + int version; + krb5_error_code (KRB5_CALLCONV *init)(krb5_context, void **data); + void (KRB5_CALLCONV *fini)(void *data); + const char *name; - uint32_t version; const char *vendor; - /* - * Set this to krb5_init_context(): kadmin will use this to verify - * that we are linked against the same libkrb5. - */ - krb5_error_code (KRB5_CALLCONV *init_context)(krb5_context *); - - void (KRB5_CALLCONV *fini)(krb5_context, void *data); - /* * Hook functions; NULL functions are ignored. code is only valid on * post-commit hooks and represents the result of the commit. Post- @@ -128,12 +120,23 @@ typedef struct kadm5_hook { size_t n_keys, krb5_keyblock *keyblocks); -} kadm5_hook; + krb5_error_code (KRB5_CALLCONV *prune)(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ, + int kvno); +} kadm5_hook_ftable; + +/* + * libkadm5srv expects a symbol named kadm5_hook_plugin_load that must be a + * function of type kadm5_hook_plugin_load_t. + */ typedef krb5_error_code -(KRB5_CALLCONV *kadm5_hook_init_t)(krb5_context context, - uint32_t kadm5_version_max, - const kadm5_hook **hook, - void **data); +(KRB5_CALLCONV *kadm5_hook_plugin_load_t)(krb5_context context, + krb5_get_instance_func_t *func, + size_t *n_hooks, + const kadm5_hook_ftable *const **hooks); #endif /* !KADM5_HOOK_H */ diff --git a/lib/kadm5/kadm5_locl.h b/lib/kadm5/kadm5_locl.h index 11c3ed1bd..ef5b83ee4 100644 --- a/lib/kadm5/kadm5_locl.h +++ b/lib/kadm5/kadm5_locl.h @@ -74,6 +74,7 @@ #include #endif #include +#include #include "admin.h" #include "kadm5_err.h" #include diff --git a/lib/kadm5/libkadm5srv-exports.def b/lib/kadm5/libkadm5srv-exports.def index 9ca7b1511..bf4e6e43b 100644 --- a/lib/kadm5/libkadm5srv-exports.def +++ b/lib/kadm5/libkadm5srv-exports.def @@ -20,6 +20,7 @@ EXPORTS kadm5_free_name_list kadm5_free_policy_ent kadm5_free_principal_ent + kadm5_get_instance kadm5_get_policies kadm5_get_policy kadm5_get_principal diff --git a/lib/kadm5/modify_s.c b/lib/kadm5/modify_s.c index 2e13fd9a0..91b5905f5 100644 --- a/lib/kadm5/modify_s.c +++ b/lib/kadm5/modify_s.c @@ -35,6 +35,37 @@ RCSID("$Id$"); +struct modify_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + kadm5_principal_ent_t princ; + uint32_t mask; +}; + +static krb5_error_code +modify_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct modify_principal_hook_ctx *ctx = userctx; + + ret = ftable->modify(context, hookctx, ctx->stage, + ctx->code, ctx->princ, ctx->mask); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "modify", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t modify_principal_hook(kadm5_server_context *context, enum kadm5_hook_stage stage, @@ -42,23 +73,19 @@ modify_principal_hook(kadm5_server_context *context, kadm5_principal_ent_t princ, uint32_t mask) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct modify_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; + ctx.mask = mask; - if (hook->hook->modify != NULL) { - ret = hook->hook->modify(context->context, hook->data, - stage, code, princ, mask); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "modify", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, modify_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/private.h b/lib/kadm5/private.h index ac3bb1d0f..43b7cd2d1 100644 --- a/lib/kadm5/private.h +++ b/lib/kadm5/private.h @@ -71,8 +71,8 @@ struct kadm_func { }; typedef struct kadm5_hook_context { - void *handle; - const kadm5_hook *hook; + void *dsohandle; + const kadm5_hook_ftable *hook; void *data; } kadm5_hook_context; @@ -197,6 +197,8 @@ enum kadm_recover_mode { #define KADMIN_APPL_VERSION "KADM0.1" #define KADMIN_OLD_APPL_VERSION "KADM0.0" +extern struct krb5_plugin_data kadm5_hook_plugin_data; + #include "kadm5-private.h" #endif /* __kadm5_privatex_h__ */ diff --git a/lib/kadm5/prune_s.c b/lib/kadm5/prune_s.c index fd95af7cf..f786e9249 100644 --- a/lib/kadm5/prune_s.c +++ b/lib/kadm5/prune_s.c @@ -34,6 +34,61 @@ RCSID("$Id$"); +struct prune_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + krb5_const_principal princ; + int kvno; +}; + +static krb5_error_code +prune_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct prune_principal_hook_ctx *ctx = userctx; + + ret = ftable->prune(context, hookctx, + ctx->stage, ctx->code, ctx->princ, ctx->kvno); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "prune", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + +static kadm5_ret_t +prune_principal_hook(kadm5_server_context *context, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ, + int kvno) +{ + krb5_error_code ret; + struct prune_principal_hook_ctx ctx; + + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; + ctx.kvno = kvno; + + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, prune_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; + + return ret; +} + kadm5_ret_t kadm5_s_prune_principal(void *server_handle, krb5_principal princ, @@ -59,6 +114,11 @@ kadm5_s_prune_principal(void *server_handle, if (ret) goto out2; + ret = prune_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, + 0, princ, kvno); + if (ret) + goto out3; + ret = hdb_prune_keys_kvno(context->context, &ent.entry, kvno); if (ret) goto out3; @@ -69,6 +129,9 @@ kadm5_s_prune_principal(void *server_handle, ret = kadm5_log_modify(context, &ent.entry, KADM5_KEY_DATA); + (void) prune_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, + ret, princ, kvno); + out3: hdb_free_entry(context->context, &ent); out2: diff --git a/lib/kadm5/randkey_s.c b/lib/kadm5/randkey_s.c index cb96f638d..5792c61fd 100644 --- a/lib/kadm5/randkey_s.c +++ b/lib/kadm5/randkey_s.c @@ -35,29 +35,54 @@ RCSID("$Id$"); +struct randkey_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + krb5_const_principal princ; +}; + +static krb5_error_code +randkey_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct randkey_principal_hook_ctx *ctx = userctx; + + ret = ftable->randkey(context, hookctx, + ctx->stage, ctx->code, ctx->princ); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "randkey", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t randkey_principal_hook(kadm5_server_context *context, - enum kadm5_hook_stage stage, - krb5_error_code code, - krb5_const_principal princ) + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct randkey_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; - if (hook->hook->randkey != NULL) { - ret = hook->hook->randkey(context->context, hook->data, - stage, code, princ); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "randkey", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, randkey_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/rename_s.c b/lib/kadm5/rename_s.c index 2e10e0a4b..d34d34025 100644 --- a/lib/kadm5/rename_s.c +++ b/lib/kadm5/rename_s.c @@ -35,6 +35,37 @@ RCSID("$Id$"); +struct rename_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + krb5_const_principal source, target; +}; + +static krb5_error_code +rename_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct rename_principal_hook_ctx *ctx = userctx; + + ret = ftable->rename(context, hookctx, + ctx->stage, ctx->code, + ctx->source, ctx->target); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "rename", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t rename_principal_hook(kadm5_server_context *context, enum kadm5_hook_stage stage, @@ -42,23 +73,19 @@ rename_principal_hook(kadm5_server_context *context, krb5_const_principal source, krb5_const_principal target) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct rename_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.source = source; + ctx.target = target; - if (hook->hook->rename != NULL) { - ret = hook->hook->rename(context->context, hook->data, - stage, code, source, target); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "rename", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, rename_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/sample_hook.c b/lib/kadm5/sample_hook.c index e2f9e4fd0..20b1874a7 100644 --- a/lib/kadm5/sample_hook.c +++ b/lib/kadm5/sample_hook.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, AuriStor Inc. + * Copyright (c) 2018, AuriStor, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,9 +29,26 @@ * */ -#include "kadm5_locl.h" +#include -static char sample_data[1]; +#include +#include +#include + +#include "admin.h" +#include "kadm5-hook.h" + +/* + * Sample kadm5 hook plugin that just logs when it is called. Install it + * somewhere and configure the path in the [kadmin] section of krb5.conf. + * e.g. + * + * [kadmin] + * plugin_dir = /usr/local/heimdal/lib/plugin/kadm5 + * + */ + +static char sample_data_1, sample_data_2; static krb5_error_code sample_log(krb5_context context, @@ -43,28 +60,52 @@ sample_log(krb5_context context, { char *p = NULL; krb5_error_code ret; + int which = 0; - if (data != sample_data) - return EINVAL; - if (code != 0 && stage == KADM5_HOOK_STAGE_PRECOMMIT) - return EINVAL; + /* verify we get called with the right contex tpointer */ + if (data == &sample_data_1) + which = 1; + else if (data == &sample_data_2) + which = 2; + + assert(which != 0); + + /* code should always be zero on pre-commit */ + assert(code == 0 || stage == KADM5_HOOK_STAGE_POSTCOMMIT); if (princ) ret = krb5_unparse_name(context, princ, &p); - krb5_warn(context, code, "sample_hook: %s %s hook princ '%s'", tag, + krb5_warn(context, code, "sample_hook_%d: %s %s hook princ '%s'", which, tag, stage == KADM5_HOOK_STAGE_PRECOMMIT ? "pre-commit" : "post-commit", p != NULL ? p : ""); krb5_xfree(p); + /* returning zero and KRB5_PLUGIN_NO_HANDLE are the same for hook plugins */ + return 0; +} + +static krb5_error_code KRB5_CALLCONV +sample_init_1(krb5_context context, void **data) +{ + *data = &sample_data_1; + krb5_warn(context, 0, "sample_hook_1: initializing"); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +sample_init_2(krb5_context context, void **data) +{ + *data = &sample_data_2; + krb5_warn(context, 0, "sample_hook_2: initializing"); return 0; } static void KRB5_CALLCONV -sample_fini(krb5_context context, void *data) +sample_fini(void *data) { - krb5_warn(context, 0, "sample_hook: shutting down\n"); + krb5_warn(NULL, 0, "sample_fini: finalizing"); } static krb5_error_code KRB5_CALLCONV @@ -150,36 +191,82 @@ sample_set_keys_hook(krb5_context context, return sample_log(context, data, stage, "set_keys", code, princ); } -static struct kadm5_hook sample_hook = { - "sample-hook", +static krb5_error_code KRB5_CALLCONV +sample_prune_hook(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ, + int kvno) +{ + return sample_log(context, data, stage, "prune", code, princ); +} + + +static const kadm5_hook_ftable sample_hook_1 = { KADM5_HOOK_VERSION_V1, - "Heimdal", - krb5_init_context, + sample_init_1, sample_fini, + "sample_hook_1", + "Heimdal", sample_chpass_hook, sample_create_hook, sample_modify_hook, sample_delete_hook, sample_randkey_hook, sample_rename_hook, - sample_set_keys_hook + sample_set_keys_hook, + sample_prune_hook, +}; + +static const kadm5_hook_ftable sample_hook_2 = { + KADM5_HOOK_VERSION_V1, + sample_init_2, + sample_fini, + "sample_hook_2", + "Heimdal", + sample_chpass_hook, + sample_create_hook, + sample_modify_hook, + sample_delete_hook, + sample_randkey_hook, + sample_rename_hook, + sample_set_keys_hook, + sample_prune_hook, +}; + +/* Arrays of pointers, because hooks may be different versions/sizes */ +static const kadm5_hook_ftable *const sample_hooks[] = { + &sample_hook_1, + &sample_hook_2, }; krb5_error_code -kadm5_hook_init(krb5_context context, uint32_t vers_max, - const kadm5_hook **hook, void **data); +kadm5_hook_plugin_load(krb5_context context, + krb5_get_instance_func_t *get_instance, + size_t *num_hooks, + const kadm5_hook_ftable *const **hooks); -krb5_error_code -kadm5_hook_init(krb5_context context, uint32_t vers_max, - const kadm5_hook **hook, void **data) +static uintptr_t +sample_hook_get_instance(const char *libname) { - if (vers_max < KADM5_HOOK_VERSION_V1) - return EINVAL; - - krb5_warn(context, 0, "sample_hook: init version %u\n", vers_max); - - *hook = &sample_hook; - *data = sample_data; + if (strcmp(libname, "kadm5") == 0) + return kadm5_get_instance(libname); + else if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + + return 0; +} + +krb5_error_code +kadm5_hook_plugin_load(krb5_context context, + krb5_get_instance_func_t *get_instance, + size_t *num_hooks, + const kadm5_hook_ftable *const **hooks) +{ + *get_instance = sample_hook_get_instance; + *num_hooks = sizeof(sample_hooks) / sizeof(sample_hooks[0]); + *hooks = sample_hooks; return 0; } diff --git a/lib/kadm5/server_hooks.c b/lib/kadm5/server_hooks.c index afb67bb23..2d4f78f12 100644 --- a/lib/kadm5/server_hooks.c +++ b/lib/kadm5/server_hooks.c @@ -1,58 +1,55 @@ /* - * Copyright 2010 - * The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018, AuriStor, Inc. + * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. * - * 3. Neither the name of the Institute nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. * - * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. */ #include "kadm5_locl.h" -#ifdef HAVE_DLFCN_H -#include - -#ifndef RTLD_NOW -# define RTLD_NOW 0 -#endif - -#ifndef RTLD_LOCAL - #define RTLD_LOCAL 0 -#endif - -#ifndef RTLD_GROUP - #define RTLD_GROUP 0 -#endif -#endif /* HAVE_DLFCN_H */ +static const char *kadm5_hook_plugin_deps[] = { + "kadm5", + "krb5", + NULL +}; +struct krb5_plugin_data kadm5_hook_plugin_data = { + "kadm5", + "kadm5_hook", + KADM5_HOOK_VERSION_V1, + kadm5_hook_plugin_deps, + kadm5_get_instance +}; + void _kadm5_s_set_hook_error_message(kadm5_server_context *context, krb5_error_code ret, const char *op, - const struct kadm5_hook *hook, + const struct kadm5_hook_ftable *hook, enum kadm5_hook_stage stage) { assert(ret != 0); @@ -63,137 +60,38 @@ _kadm5_s_set_hook_error_message(kadm5_server_context *context, stage == KADM5_HOOK_STAGE_PRECOMMIT ? "pre" : "post"); } -/* - * Load kadmin server hooks. - */ kadm5_ret_t _kadm5_s_init_hooks(kadm5_server_context *ctx) { krb5_context context = ctx->context; - char **hooks; - void *handle = NULL; - struct kadm5_hook_context *hook_context = NULL; -#ifdef HAVE_DLOPEN - struct kadm5_hook_context **tmp; - size_t i; -#endif - kadm5_ret_t ret = KADM5_BAD_SERVER_HOOK; + char **dirs; - hooks = krb5_config_get_strings(context, NULL, - "kadmin", "hooks", NULL); - if (hooks == NULL) + dirs = krb5_config_get_strings(context, NULL, "kadmin", + "plugin_dir", NULL); + if (dirs == NULL) return 0; -#ifdef HAVE_DLOPEN - for (i = 0; hooks[i] != NULL; i++) { - const char *hookpath = hooks[i]; - kadm5_hook_init_t hook_init; - const struct kadm5_hook *hook = NULL; - void *data = NULL; + _krb5_load_plugins(context, "kadm5", (const char **)dirs); + krb5_config_free_strings(dirs); - handle = dlopen(hookpath, RTLD_NOW | RTLD_LOCAL | RTLD_GROUP); - if (handle == NULL) { - krb5_warnx(context, "failed to open `%s': %s", hookpath, dlerror()); - ret = KADM5_SERVER_HOOK_NOT_FOUND; - goto fail; - } - - hook_init = dlsym(handle, "kadm5_hook_init"); - if (hook_init == NULL) { - krb5_warnx(context, "didn't find kadm5_hook_init symbol in `%s': %s", - hookpath, dlerror()); - ret = KADM5_BAD_SERVER_HOOK; - goto fail; - } - - ret = hook_init(context, KADM5_HOOK_VERSION_V1, &hook, &data); - if (ret == 0 && hook == NULL) - ret = KADM5_BAD_SERVER_HOOK; - if (ret) { - krb5_warn(context, ret, "initialization of hook `%s' failed", hookpath); - goto fail; - } - - if (hook->version < KADM5_HOOK_VERSION_V1) - ret = KADM5_OLD_SERVER_HOOK_VERSION; - else if (hook->version > KADM5_HOOK_VERSION_V1) - ret = KADM5_NEW_SERVER_HOOK_VERSION; - if (ret) { - krb5_warnx(context, "%s: version of loaded hook `%s' by vendor `%s' is %u" - " (supported versions are %u to %u)", - hookpath, hook->name, hook->vendor, hook->version, - KADM5_HOOK_VERSION_V1, KADM5_HOOK_VERSION_V1); - hook->fini(context, data); - goto fail; - } - - if (hook->init_context != krb5_init_context) { - krb5_warnx(context, "%s: loaded hook `%s' by vendor `%s' (API version %u)" - "is not linked against this version of Heimdal", - hookpath, hook->name, hook->vendor, hook->version); - hook->fini(context, data); - goto fail; - } - - hook_context = calloc(1, sizeof(*hook_context)); - if (hook_context == NULL) { - ret = krb5_enomem(context); - hook->fini(context, data); - goto fail; - } - - hook_context->handle = handle; - hook_context->hook = hook; - hook_context->data = data; - - tmp = realloc(ctx->hooks, (ctx->num_hooks + 1) * sizeof(*tmp)); - if (tmp == NULL) { - ret = krb5_enomem(context); - hook->fini(context, data); - goto fail; - } - ctx->hooks = tmp; - ctx->hooks[ctx->num_hooks] = hook_context; - hook_context = NULL; - ctx->num_hooks++; - - krb5_warnx(context, "Loaded kadm5 hook `%s' by vendor `%s' (API version %u)", - hook->name, hook->vendor, hook->version); - } - krb5_config_free_strings(hooks); return 0; -#else - krb5_warnx(context, "kadm5 hooks configured, but platform " - "does not support dynamic loading"); - ret = KADM5_BAD_SERVER_HOOK; - goto fail; -#endif /* HAVE_DLOPEN */ - -fail: - _kadm5_s_free_hooks(ctx); - if (hook_context != NULL) - free(hook_context); - if (handle != NULL) - dlclose(handle); - krb5_config_free_strings(hooks); - - return ret; } void _kadm5_s_free_hooks(kadm5_server_context *ctx) { -#ifdef HAVE_DLOPEN - size_t i; - - for (i = 0; i < ctx->num_hooks; i++) { - if (ctx->hooks[i]->hook->fini != NULL) - ctx->hooks[i]->hook->fini(ctx->context, ctx->hooks[i]->data); - dlclose(ctx->hooks[i]->handle); - free(ctx->hooks[i]); - } - free(ctx->hooks); - ctx->hooks = NULL; - ctx->num_hooks = 0; -#endif /* HAVE_DLOPEN */ + _krb5_unload_plugins(ctx->context, "kadm5"); +} + +uintptr_t +kadm5_get_instance(const char *libname) +{ + static const char *instance = "libkadm5"; + + if (strcmp(libname, "kadm5") == 0) + return (uintptr_t)instance; + else if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + + return 0; } diff --git a/lib/kadm5/setkey3_s.c b/lib/kadm5/setkey3_s.c index 09814a028..7f371d680 100644 --- a/lib/kadm5/setkey3_s.c +++ b/lib/kadm5/setkey3_s.c @@ -33,6 +33,44 @@ #include "kadm5_locl.h" +struct setkey_principal_hook_ctx { + kadm5_server_context *context; + enum kadm5_hook_stage stage; + krb5_error_code code; + krb5_const_principal princ; + uint32_t flags; + size_t n_ks_tuple; + krb5_key_salt_tuple *ks_tuple; + size_t n_keys; + krb5_keyblock *keys; +}; + +static krb5_error_code +setkey_principal_hook_cb(krb5_context context, + const void *hook, + void *hookctx, + void *userctx) +{ + krb5_error_code ret; + const struct kadm5_hook_ftable *ftable = hook; + struct setkey_principal_hook_ctx *ctx = userctx; + + ret = ftable->set_keys(context, hookctx, + ctx->stage, ctx->code, + ctx->princ, ctx->flags, + ctx->n_ks_tuple, ctx->ks_tuple, + ctx->n_keys, ctx->keys); + if (ret != 0 && ret != KRB5_PLUGIN_NO_HANDLE) + _kadm5_s_set_hook_error_message(ctx->context, ret, "setkey", + hook, ctx->stage); + + /* only pre-commit plugins can abort */ + if (ret == 0 || ctx->stage == KADM5_HOOK_STAGE_POSTCOMMIT) + ret = KRB5_PLUGIN_NO_HANDLE; + + return ret; +} + static kadm5_ret_t setkey_principal_hook(kadm5_server_context *context, enum kadm5_hook_stage stage, @@ -44,25 +82,23 @@ setkey_principal_hook(kadm5_server_context *context, size_t n_keys, krb5_keyblock *keyblocks) { - krb5_error_code ret = 0; - size_t i; + krb5_error_code ret; + struct setkey_principal_hook_ctx ctx; - for (i = 0; i < context->num_hooks; i++) { - kadm5_hook_context *hook = context->hooks[i]; + ctx.context = context; + ctx.stage = stage; + ctx.code = code; + ctx.princ = princ; + ctx.flags = flags; + ctx.n_ks_tuple = n_ks_tuple; + ctx.ks_tuple = ks_tuple; + ctx.n_keys = n_keys; + ctx.keys = keyblocks; - if (hook->hook->set_keys != NULL) { - ret = hook->hook->set_keys(context->context, hook->data, - stage, code, princ, - flags, n_ks_tuple, ks_tuple, - n_keys, keyblocks); - if (ret != 0) { - _kadm5_s_set_hook_error_message(context, ret, "setkey", - hook->hook, stage); - if (stage == KADM5_HOOK_STAGE_PRECOMMIT) - break; - } - } - } + ret = _krb5_plugin_run_f(context->context, &kadm5_hook_plugin_data, + 0, &ctx, setkey_principal_hook_cb); + if (ret == KRB5_PLUGIN_NO_HANDLE) + ret = 0; return ret; } diff --git a/lib/kadm5/test_pw_quality.c b/lib/kadm5/test_pw_quality.c index ebef429c4..6544afd95 100644 --- a/lib/kadm5/test_pw_quality.c +++ b/lib/kadm5/test_pw_quality.c @@ -31,10 +31,14 @@ * SUCH DAMAGE. */ -#include "kadm5_locl.h" +#include +#include #include -RCSID("$Id$"); +#include +#include + +#include "admin.h" static int version_flag; static int help_flag; diff --git a/lib/kadm5/version-script.map b/lib/kadm5/version-script.map index 0331b182d..e84703563 100644 --- a/lib/kadm5/version-script.map +++ b/lib/kadm5/version-script.map @@ -23,6 +23,7 @@ HEIMDAL_KAMD5_SERVER_1.0 { kadm5_free_name_list; kadm5_free_policy_ent; kadm5_free_principal_ent; + kadm5_get_instance; kadm5_get_policy; kadm5_get_policies; kadm5_get_principal; diff --git a/lib/krb5/aname_to_localname.c b/lib/krb5/aname_to_localname.c index e4818c360..b002b3982 100644 --- a/lib/krb5/aname_to_localname.c +++ b/lib/krb5/aname_to_localname.c @@ -80,6 +80,17 @@ plcallback(krb5_context context, return locate->an2ln(plugctx, context, plctx->rule, plctx->aname, set_res, plctx); } +static const char *an2ln_plugin_deps[] = { "krb5", NULL }; + +static struct krb5_plugin_data +an2ln_plugin_data = { + "krb5", + KRB5_PLUGIN_AN2LN, + KRB5_PLUGIN_AN2LN_VERSION_0, + an2ln_plugin_deps, + krb5_get_instance +}; + static krb5_error_code an2ln_plugin(krb5_context context, const char *rule, krb5_const_principal aname, size_t lnsize, char *lname) @@ -96,8 +107,8 @@ an2ln_plugin(krb5_context context, const char *rule, krb5_const_principal aname, * really be no more than one plugin that can handle any given kind * rule, so the effect should be deterministic anyways. */ - ret = _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_AN2LN, - KRB5_PLUGIN_AN2LN_VERSION_0, 0, &ctx, plcallback); + ret = _krb5_plugin_run_f(context, &an2ln_plugin_data, + 0, &ctx, plcallback); if (ret != 0) { heim_release(ctx.luser); return ret; diff --git a/lib/krb5/context.c b/lib/krb5/context.c index bd8b2bfc5..e82260b35 100644 --- a/lib/krb5/context.c +++ b/lib/krb5/context.c @@ -1589,3 +1589,4 @@ krb5_set_home_dir_access(krb5_context context, krb5_boolean allow) return old; } + diff --git a/lib/krb5/db_plugin.c b/lib/krb5/db_plugin.c index a46bbc1f0..45f34ff06 100644 --- a/lib/krb5/db_plugin.c +++ b/lib/krb5/db_plugin.c @@ -14,12 +14,22 @@ db_plugins_plcallback(krb5_context context, const void *plug, void *plugctx, return 0; } +static const char *db_plugin_deps[] = { "krb5", NULL }; + +static struct krb5_plugin_data +db_plugin_data = { + "krb5", + KRB5_PLUGIN_DB, + KRB5_PLUGIN_DB_VERSION_0, + db_plugin_deps, + krb5_get_instance +}; + static void db_plugins_init(void *arg) { krb5_context context = arg; - (void)_krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_DB, - KRB5_PLUGIN_DB_VERSION_0, 0, NULL, + (void)_krb5_plugin_run_f(context, &db_plugin_data, 0, NULL, db_plugins_plcallback); } diff --git a/lib/krb5/krb5-plugin.7 b/lib/krb5/krb5-plugin.7 index 49204d2f6..88ace73f0 100644 --- a/lib/krb5/krb5-plugin.7 +++ b/lib/krb5/krb5-plugin.7 @@ -57,11 +57,26 @@ associated header file, such as, for example, .Va krb5plugin_kuserok_ftable and a pointer to which is either registered via .Xr krb5_plugin_register 3 -or found in a shared object via a symbol lookup for the symbol name -defined in the associated header file (e.g., "kuserok" for the +or via a plugin load function exported by a shared object. Plugin load +functions should be named by concatenating the name defined in the associated +header file with the string "plugin_load" (e.g. "kuserok_plugin_load" for the plugin for .Xr krb5_kuserok 3 ). +The plugin load function has the following signature: +.Bd -literal -offset indent +krb5_error_code +kuserok_plugin_load(krb5_context context, + krb5_get_instance_func_t *get_instance, + size_t *n_plugins, + const common_plugin_ftable *const **plugins); +.Ed +.Pp +The plugin should set the get_instance output parameter to the a function +that will return the instances of its library dependencies. The output +parameters n_plugins and plugins specify the number of plugins, and an array of +pointers to function tables, respectively. +.Ed .Pp The plugin structs for all plugin types always begin with the same three common fields: @@ -200,6 +215,31 @@ krb5plugin_an2ln_ftable an2ln = { nouser_plug_fini, nouser_plug_an2ln, }; + +static const krb5plugin_an2ln_ftable *const plugins[] = { + &an2ln +}; + +static uintptr_t +an2ln_get_instance(const char *libname) +{ + if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + + return 0; +} + +krb5_error_code +an2ln_plugin_load(krb5_context context, + krb5_get_instance_func_t *get_instance, + size_t *num_plugins, + const krb5plugin_an2ln_ftable * const **pplugins) +{ + *get_instance = an2ln_get_instance; + *num_plugins = sizeof(plugins) / sizeof(plugins[0]); + *pplugins = plugins; + return 0; +} .Ed .Pp An example kuserok plugin that rejects all requests follows. (Note that @@ -232,12 +272,38 @@ reject_plug_kuserok(void *plug_ctx, krb5_context context, const char *rule, return 0; } -krb5plugin_kuserok_ftable kuserok = { +static krb5plugin_kuserok_ftable kuserok = { KRB5_PLUGIN_KUSEROK_VERSION_0, reject_plug_init, reject_plug_fini, reject_plug_kuserok, }; + +static const krb5plugin_kuserok_ftable *const plugins[] = { + &kuserok +}; + +static uintptr_t +kuserok_get_instance(const char *libname) +{ + if (strcmp(libname, "krb5") == 0) + return krb5_get_instance(libname); + + return 0; +} + +krb5_error_code +kuserok_plugin_load(krb5_context context, + krb5_get_instance_func_t *get_instance, + size_t *num_plugins, + const krb5plugin_kuserok_ftable * const **pplugins) +{ + *krb5_instance = kuserok_get_instance; + *num_plugins = sizeof(plugins) / sizeof(plugins[0]); + *pplugins = plugins; + return 0; +} + .Ed .Sh SEE ALSO .Xr krb5_plugin_register 3 diff --git a/lib/krb5/krb5.h b/lib/krb5/krb5.h index 2e580c036..8697a1f45 100644 --- a/lib/krb5/krb5.h +++ b/lib/krb5/krb5.h @@ -873,14 +873,16 @@ typedef krb5_error_code (KRB5_CALLCONV * krb5_sendto_ctx_func)(krb5_context, krb5_sendto_ctx, void *, const krb5_data *, int *); -struct krb5_plugin; enum krb5_plugin_type { PLUGIN_TYPE_DATA = 1, - PLUGIN_TYPE_FUNC + PLUGIN_TYPE_FUNC /* no longer supported */ }; #define KRB5_PLUGIN_INVOKE_ALL 1 +typedef uintptr_t +(KRB5_CALLCONV *krb5_get_instance_func_t)(const char *); + struct credentials; /* this is to keep the compiler happy */ struct getargs; struct sockaddr; diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index 17d21edef..2f6bc5503 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -134,6 +134,7 @@ struct ContentInfo; struct AlgorithmIdentifier; typedef struct krb5_pk_init_ctx_data *krb5_pk_init_ctx; struct krb5_dh_moduli; +struct krb5_plugin_data; /* v4 glue */ struct _krb5_krb_auth_data; @@ -393,4 +394,12 @@ struct krb5_pk_init_ctx_data { # define ISPATHSEP(x) (x == '/') #endif +struct krb5_plugin_data { + const char *module; + const char *name; + int min_version; + const char **deps; + krb5_get_instance_func_t get_instance; +}; + #endif /* __KRB5_LOCL_H__ */ diff --git a/lib/krb5/krbhst.c b/lib/krb5/krbhst.c index 1c93b75cb..ea2cafac8 100644 --- a/lib/krb5/krbhst.c +++ b/lib/krb5/krbhst.c @@ -674,6 +674,17 @@ plcallback(krb5_context context, return KRB5_PLUGIN_NO_HANDLE; } +static const char *locate_plugin_deps[] = { "krb5", NULL }; + +static struct krb5_plugin_data +locate_plugin_data = { + "krb5", + KRB5_PLUGIN_LOCATE, + KRB5_PLUGIN_LOCATE_VERSION_0, + locate_plugin_deps, + krb5_get_instance +}; + static void plugin_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, @@ -684,8 +695,7 @@ plugin_get_hosts(krb5_context context, if (_krb5_homedir_access(context)) ctx.flags |= KRB5_PLF_ALLOW_HOMEDIR; - _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_LOCATE, - KRB5_PLUGIN_LOCATE_VERSION_0, + _krb5_plugin_run_f(context, &locate_plugin_data, 0, &ctx, plcallback); } diff --git a/lib/krb5/kuserok.c b/lib/krb5/kuserok.c index 807bb3c68..03d146436 100644 --- a/lib/krb5/kuserok.c +++ b/lib/krb5/kuserok.c @@ -455,6 +455,17 @@ krb5_kuserok(krb5_context context, } +static const char *kuserok_plugin_deps[] = { "krb5", NULL }; + +static struct krb5_plugin_data +kuserok_plugin_data = { + "krb5", + KRB5_PLUGIN_KUSEROK, + KRB5_PLUGIN_KUSEROK_VERSION_0, + kuserok_plugin_deps, + krb5_get_instance +}; + KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL _krb5_kuserok(krb5_context context, krb5_principal principal, @@ -515,9 +526,8 @@ _krb5_kuserok(krb5_context context, for (n = 0; rules[n]; n++) { ctx.rule = rules[n]; - ret = _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_KUSEROK, - KRB5_PLUGIN_KUSEROK_VERSION_0, 0, - &ctx, plcallback); + ret = _krb5_plugin_run_f(context, &kuserok_plugin_data, + 0, &ctx, plcallback); if (ret != KRB5_PLUGIN_NO_HANDLE) goto out; } diff --git a/lib/krb5/libkrb5-exports.def.in b/lib/krb5/libkrb5-exports.def.in index fdfb06189..79821fa35 100644 --- a/lib/krb5/libkrb5-exports.def.in +++ b/lib/krb5/libkrb5-exports.def.in @@ -366,6 +366,7 @@ EXPORTS krb5_get_init_creds_opt_set_tkt_life krb5_get_init_creds_opt_set_win2k krb5_get_init_creds_password + krb5_get_instance krb5_get_kdc_cred krb5_get_kdc_sec_offset krb5_get_krb524hst @@ -756,6 +757,10 @@ EXPORTS _krb5_build_authenticator _krb5_kt_client_default_name + ; Shared with libkadm5 + _krb5_load_plugins + _krb5_unload_plugins + ; Shared with libkdc _krb5_AES_SHA1_string_to_default_iterator _krb5_AES_SHA2_string_to_default_iterator @@ -806,8 +811,6 @@ EXPORTS ; Recent additions krb5_cc_type_dcc; krb5_dcc_ops; - _krb5_plugin_find; - _krb5_plugin_free; _krb5_expand_path_tokensv; _krb5_find_capath; _krb5_free_capath; diff --git a/lib/krb5/pcache.c b/lib/krb5/pcache.c index 3a9949dc2..4c22abb7b 100644 --- a/lib/krb5/pcache.c +++ b/lib/krb5/pcache.c @@ -58,13 +58,24 @@ cc_plugin_register_to_context(krb5_context context, const void *plug, void *plug return KRB5_PLUGIN_NO_HANDLE; } +static const char *ccache_plugin_deps[] = { "krb5", NULL }; + +static struct krb5_plugin_data +ccache_plugin_data = { + "krb5", + KRB5_PLUGIN_CCACHE, + 0, + ccache_plugin_deps, + krb5_get_instance +}; + KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL _krb5_load_ccache_plugins(krb5_context context) { krb5_error_code userctx = 0; - (void)_krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_CCACHE, - 0, 0, &userctx, cc_plugin_register_to_context); + (void)_krb5_plugin_run_f(context, &ccache_plugin_data, 0, + &userctx, cc_plugin_register_to_context); return userctx; } diff --git a/lib/krb5/plugin.c b/lib/krb5/plugin.c index f97c513af..0661c7591 100644 --- a/lib/krb5/plugin.c +++ b/lib/krb5/plugin.c @@ -3,6 +3,8 @@ * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * + * Portions Copyright (c) 2018 AuriStor, Inc. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -38,29 +40,159 @@ #endif #include +/* + * Definitions: + * + * module - a category of plugin module, identified by subsystem + * (typically "krb5") + * dso - a library for a module containing a map of plugin + * types to plugins (e.g. "service_locator") + * plugin - a set of callbacks and state that follows the + * common plugin module definition (version, init, fini) + * + * Obviously it would have been clearer to use the term "module" rather than + * "DSO" given there is an internal "DSO", but "module" was already taken... + * + * modules := { module: dsos } + * dsos := { path, dsohandle, plugins-by-name } + * plugins-by-name := { plugin-name: [plug] } + * plug := { ftable, ctx } + * + * Some existing plugin consumers outside libkrb5 use the "krb5" module + * namespace, but going forward the module should match the consumer library + * name (e.g. libhdb should use the "hdb" module rather than "krb5"). + */ + +/* global module use, use copy_modules() accessor to access */ +static heim_dict_t __modules; + +static HEIMDAL_MUTEX modules_mutex = HEIMDAL_MUTEX_INITIALIZER; + +static void +copy_modules_once(void *context) +{ + heim_dict_t *modules = (heim_dict_t *)context; + + *modules = heim_dict_create(11); + heim_assert(*modules, "plugin modules array allocation failure"); +} + +/* returns global modules list, refcount +1 */ +static heim_dict_t +copy_modules(void) +{ + static heim_base_once_t modules_once = HEIM_BASE_ONCE_INIT; + + heim_base_once_f(&modules_once, &__modules, copy_modules_once); + + return heim_retain(__modules); +} + +/* returns named module, refcount +1 */ +static heim_dict_t +copy_module(const char *name) +{ + heim_string_t module_name = heim_string_create(name); + heim_dict_t modules = copy_modules(); + heim_dict_t module; + + module = heim_dict_copy_value(modules, module_name); + if (module == NULL) { + module = heim_dict_create(11); + heim_dict_set_value(modules, module_name, module); + } + + heim_release(modules); + heim_release(module_name); + + return module; +} + +/* DSO helpers */ +struct krb5_dso { + heim_string_t path; + heim_dict_t plugins_by_name; + void *dsohandle; +}; + +static void +dso_dealloc(void *ptr) +{ + struct krb5_dso *p = ptr; + + heim_release(p->path); + heim_release(p->plugins_by_name); +#ifdef HAVE_DLOPEN + if (p->dsohandle) + dlclose(p->dsohandle); +#endif +} + +/* returns internal "DSO" for name, refcount +1 */ +static struct krb5_dso * +copy_internal_dso(const char *name) +{ + heim_string_t dso_name = HSTR("__HEIMDAL_INTERNAL_DSO__"); + heim_dict_t module = copy_module(name); + struct krb5_dso *dso; + + if (module == NULL) + return NULL; + + dso = heim_dict_copy_value(module, dso_name); + if (dso == NULL) { + dso = heim_alloc(sizeof(*dso), "krb5-dso", dso_dealloc); + + dso->path = dso_name; + dso->plugins_by_name = heim_dict_create(11); + + heim_dict_set_value(module, dso_name, dso); + } + + heim_release(module); + + return dso; +} + +/* + * All plugin function tables extend the following structure. + */ +typedef struct common_plugin_ftable_desc { + int version; + krb5_error_code (*init)(krb5_context, void **); + void (*fini)(void *); +} common_plugin_ftable; + struct krb5_plugin { + common_plugin_ftable *ftable; + void *ctx; +}; + +static void +plugin_free(void *ptr) +{ + struct krb5_plugin *pl = ptr; + + if (pl->ftable && pl->ftable->fini) + pl->ftable->fini(pl->ctx); +} + +struct krb5_plugin_register_ctx { void *symbol; - struct krb5_plugin *next; + int is_dup; }; -struct plugin { - enum { DSO, SYMBOL } type; - union { - struct { - char *path; - void *dsohandle; - } dso; - struct { - enum krb5_plugin_type type; - char *name; - char *symbol; - } symbol; - } u; - struct plugin *next; -}; +static void +plugin_register_check_dup(heim_object_t value, void *ctx, int *stop) +{ + struct krb5_plugin_register_ctx *pc = ctx; + struct krb5_plugin *pl = value; -static HEIMDAL_MUTEX plugin_mutex = HEIMDAL_MUTEX_INITIALIZER; -static struct plugin *registered = NULL; + if (pl->ftable == pc->symbol) { + pc->is_dup = 1; + *stop = 1; + } +} /** * Register a plugin symbol name of specific type. @@ -80,147 +212,67 @@ krb5_plugin_register(krb5_context context, const char *name, void *symbol) { - struct plugin *e; - - HEIMDAL_MUTEX_lock(&plugin_mutex); - - /* check for duplicates */ - for (e = registered; e != NULL; e = e->next) { - if (e->type == SYMBOL && - strcmp(e->u.symbol.name, name) == 0 && - e->u.symbol.type == type && e->u.symbol.symbol == symbol) { - HEIMDAL_MUTEX_unlock(&plugin_mutex); - return 0; - } - } - - e = calloc(1, sizeof(*e)); - if (e == NULL) { - HEIMDAL_MUTEX_unlock(&plugin_mutex); - krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); - return ENOMEM; - } - e->type = SYMBOL; - e->u.symbol.type = type; - e->u.symbol.name = strdup(name); - if (e->u.symbol.name == NULL) { - HEIMDAL_MUTEX_unlock(&plugin_mutex); - free(e); - krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); - return ENOMEM; - } - e->u.symbol.symbol = symbol; - - e->next = registered; - registered = e; - HEIMDAL_MUTEX_unlock(&plugin_mutex); - - return 0; -} - -static krb5_error_code -add_symbol(krb5_context context, struct krb5_plugin **list, void *symbol) -{ - struct krb5_plugin *e; - - e = calloc(1, sizeof(*e)); - if (e == NULL) { - krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); - return ENOMEM; - } - e->symbol = symbol; - e->next = *list; - *list = e; - return 0; -} - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -_krb5_plugin_find(krb5_context context, - enum krb5_plugin_type type, - const char *name, - struct krb5_plugin **list) -{ - struct plugin *e; krb5_error_code ret; + heim_array_t plugins; + heim_string_t hname; + struct krb5_dso *dso; + struct krb5_plugin_register_ctx ctx; - *list = NULL; + ctx.symbol = symbol; + ctx.is_dup = 0; - HEIMDAL_MUTEX_lock(&plugin_mutex); - - for (ret = 0, e = registered; e != NULL; e = e->next) { - switch(e->type) { - case DSO: { - void *sym; - if (e->u.dso.dsohandle == NULL) - continue; - sym = dlsym(e->u.dso.dsohandle, name); - if (sym) - ret = add_symbol(context, list, sym); - break; - } - case SYMBOL: - if (strcmp(e->u.symbol.name, name) == 0 && e->u.symbol.type == type) - ret = add_symbol(context, list, e->u.symbol.symbol); - break; - } - if (ret) { - _krb5_plugin_free(*list); - *list = NULL; - } + /* + * It's not clear that PLUGIN_TYPE_FUNC was ever used or supported. It likely + * would have caused _krb5_plugin_run_f() to crash as the previous implementation + * assumed PLUGIN_TYPE_DATA. + */ + if (type != PLUGIN_TYPE_DATA) { + krb5_warnx(context, "krb5_plugin_register: PLUGIN_TYPE_DATA no longer supported"); + return EINVAL; } - HEIMDAL_MUTEX_unlock(&plugin_mutex); - if (ret) - return ret; + HEIMDAL_MUTEX_lock(&modules_mutex); - if (*list == NULL) { - krb5_set_error_message(context, ENOENT, "Did not find a plugin for %s", name); - return ENOENT; + dso = copy_internal_dso("krb5"); + hname = heim_string_create(name); + plugins = heim_dict_copy_value(dso->plugins_by_name, hname); + if (plugins != NULL) + heim_array_iterate_f(plugins, &ctx, plugin_register_check_dup); + else { + plugins = heim_array_create(); + heim_dict_set_value(dso->plugins_by_name, hname, plugins); } - return 0; + if (!ctx.is_dup) { + /* Note: refactored plugin API only supports common plugin layout */ + struct krb5_plugin *pl; + + pl = heim_alloc(sizeof(*pl), "krb5-plugin", plugin_free); + if (pl == NULL) { + ret = krb5_enomem(context); + } else { + pl->ftable = symbol; + ret = pl->ftable->init(context, &pl->ctx); + if (ret == 0) { + heim_array_append_value(plugins, pl); + _krb5_debug(context, 5, "Registered %s plugin", name); + } + heim_release(pl); + } + } else + ret = 0; /* ignore duplicates to match previous behavior */ + + HEIMDAL_MUTEX_unlock(&modules_mutex); + + heim_release(dso); + heim_release(hname); + heim_release(plugins); + + return ret; } -KRB5_LIB_FUNCTION void KRB5_LIB_CALL -_krb5_plugin_free(struct krb5_plugin *list) -{ - struct krb5_plugin *next; - while (list) { - next = list->next; - free(list); - list = next; - } -} -/* - * module - dict of { - * ModuleName = [ - * plugin = object{ - * array = { ptr, ctx } - * } - * ] - * } - */ - -static heim_dict_t modules; - -struct plugin2 { - heim_string_t path; - void *dsohandle; - heim_dict_t names; -}; - #ifdef HAVE_DLOPEN -static void -plug_dealloc(void *ptr) -{ - struct plugin2 *p = ptr; - heim_release(p->path); - heim_release(p->names); - if (p->dsohandle) - dlclose(p->dsohandle); -} - static char * resolve_origin(const char *di) { @@ -277,7 +329,7 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) { #ifdef HAVE_DLOPEN heim_string_t s = heim_string_create(name); - heim_dict_t module; + heim_dict_t module, modules; struct dirent *entry; krb5_error_code ret; const char **di; @@ -292,27 +344,23 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) plugin_prefix_len = (plugin_prefix ? strlen(plugin_prefix) : 0); #endif - HEIMDAL_MUTEX_lock(&plugin_mutex); + HEIMDAL_MUTEX_lock(&modules_mutex); - if (modules == NULL) { - modules = heim_dict_create(11); - if (modules == NULL) { - HEIMDAL_MUTEX_unlock(&plugin_mutex); - return; - } - } + modules = copy_modules(); module = heim_dict_copy_value(modules, s); if (module == NULL) { module = heim_dict_create(11); if (module == NULL) { - HEIMDAL_MUTEX_unlock(&plugin_mutex); + HEIMDAL_MUTEX_unlock(&modules_mutex); heim_release(s); + heim_release(modules); return; } heim_dict_set_value(modules, s, module); } heim_release(s); + heim_release(modules); for (di = paths; *di != NULL; di++) { free(dirname); @@ -328,7 +376,7 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) char *n = entry->d_name; char *path = NULL; heim_string_t spath; - struct plugin2 *p; + struct krb5_dso *p; /* skip . and .. */ if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) @@ -377,13 +425,12 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) /* check if already cached */ p = heim_dict_copy_value(module, spath); if (p == NULL) { - p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc); + p = heim_alloc(sizeof(*p), "krb5-dso", dso_dealloc); if (p) - p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY); - + p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY|RTLD_GROUP); if (p && p->dsohandle) { p->path = heim_retain(spath); - p->names = heim_dict_create(11); + p->plugins_by_name = heim_dict_create(11); heim_dict_set_value(module, spath, p); } } @@ -394,7 +441,7 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) closedir(d); } free(dirname); - HEIMDAL_MUTEX_unlock(&plugin_mutex); + HEIMDAL_MUTEX_unlock(&modules_mutex); heim_release(module); #ifdef _WIN32 if (plugin_prefix) @@ -409,42 +456,24 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) KRB5_LIB_FUNCTION void KRB5_LIB_CALL _krb5_unload_plugins(krb5_context context, const char *name) { - HEIMDAL_MUTEX_lock(&plugin_mutex); + heim_string_t sname = heim_string_create(name); + heim_dict_t modules; + + HEIMDAL_MUTEX_lock(&modules_mutex); + + modules = copy_modules(); + heim_dict_delete_key(modules, sname); + + HEIMDAL_MUTEX_unlock(&modules_mutex); + heim_release(modules); - modules = NULL; - HEIMDAL_MUTEX_unlock(&plugin_mutex); -} - -/* - * - */ - -struct common_plugin_method { - int version; - krb5_error_code (*init)(krb5_context, void **); - void (*fini)(void *); -}; - -struct plug { - void *dataptr; - void *ctx; -}; - -static void -plug_free(void *ptr) -{ - struct plug *pl = ptr; - if (pl->dataptr) { - struct common_plugin_method *cpm = pl->dataptr; - cpm->fini(pl->ctx); - } + heim_release(sname); } struct iter_ctx { krb5_context context; heim_string_t n; - const char *name; - int min_version; + struct krb5_plugin_data *caller; int flags; heim_array_t result; krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *); @@ -452,48 +481,213 @@ struct iter_ctx { krb5_error_code ret; }; +#ifdef HAVE_DLOPEN +/* + * Add plugin from a DSO that exports the plugin structure directly. This is + * provided for backwards compatibility with prior versions of Heimdal, but it + * does not allow a module to export multiple plugins, nor does it allow + * instance validation. + */ +static heim_array_t +add_dso_plugin_struct(krb5_context context, + const char *dsopath, + void *dsohandle, + const char *name) +{ + krb5_error_code ret; + common_plugin_ftable *cpm; + struct krb5_plugin *pl; + heim_array_t plugins; + + if (dsohandle == NULL) + return NULL; + + /* suppress error here because we may be looking for a different plugin type */ + cpm = dlsym(dsohandle, name); + if (cpm == NULL) + return NULL; + + krb5_warnx(context, "plugin %s uses deprecated loading mechanism", dsopath); + + pl = heim_alloc(sizeof(*pl), "krb5-plugin", plugin_free); + + ret = cpm->init(context, &pl->ctx); + if (ret) { + krb5_warn(context, ret, "plugin %s failed to initialize", dsopath); + heim_release(pl); + return NULL; + } + + pl->ftable = cpm; + + plugins = heim_array_create(); + heim_array_append_value(plugins, pl); + heim_release(pl); + + return plugins; +} + +typedef krb5_error_code +(KRB5_CALLCONV *krb5_plugin_load_t)(krb5_context context, + krb5_get_instance_func_t *func, + size_t *n_ftables, + const common_plugin_ftable *const **ftables); + +static krb5_boolean +validate_plugin_deps(krb5_context context, + struct krb5_plugin_data *caller, + const char *dsopath, + krb5_get_instance_func_t get_instance) +{ + size_t i; + + if (get_instance == NULL) { + krb5_warnx(context, "plugin %s omitted instance callback", + dsopath); + return FALSE; + } + + for (i = 0; caller->deps[i] != NULL; i++) { + uintptr_t heim_instance, plugin_instance; + + heim_instance = caller->get_instance(caller->deps[i]); + plugin_instance = get_instance(caller->deps[i]); + + if (heim_instance == 0 || plugin_instance == 0) + continue; + + if (heim_instance != plugin_instance) { + krb5_warnx(context, "plugin %s library %s linked against different " + "version of Heimdal (got %zu, us %zu)", + dsopath, caller->deps[i], + plugin_instance, heim_instance); + return FALSE; + } + _krb5_debug(context, 10, "Validated plugin library dependency %s for %s", + caller->deps[i], dsopath); + } + + return TRUE; +} + +/* + * New interface from Heimdal 8 where a DSO can export a load function + * that can return both a libkrb5 instance identifier along with an array + * of plugins. + */ +static heim_array_t +add_dso_plugins_load_fn(krb5_context context, + struct krb5_plugin_data *caller, + const char *dsopath, + void *dsohandle) +{ + krb5_error_code ret; + heim_array_t plugins; + krb5_plugin_load_t load_fn; + char *sym; + size_t i; + krb5_get_instance_func_t get_instance; + size_t n_ftables; + const common_plugin_ftable *const *ftables; + + if (asprintf(&sym, "%s_plugin_load", caller->name) == -1) + return NULL; + + /* suppress error here because we may be looking for a different plugin type */ + load_fn = dlsym(dsohandle, sym); + free(sym); + if (load_fn == NULL) + return NULL; + + ret = load_fn(context, &get_instance, &n_ftables, &ftables); + if (ret) { + krb5_warn(context, ret, "plugin %s failed to load", dsopath); + return NULL; + } + + if (!validate_plugin_deps(context, caller, dsopath, get_instance)) + return NULL; + + plugins = heim_array_create(); + + for (i = 0; i < n_ftables; i++) { + const common_plugin_ftable *const cpm = ftables[i]; + struct krb5_plugin *pl; + + pl = heim_alloc(sizeof(*pl), "krb5-plugin", plugin_free); + + ret = cpm->init(context, &pl->ctx); + if (ret) { + krb5_warn(context, ret, "plugin %s[%zu] failed to initialize", + dsopath, i); + } else { + pl->ftable = rk_UNCONST(cpm); + heim_array_append_value(plugins, pl); + } + heim_release(pl); + } + + return plugins; +} +#endif /* HAVE_DLOPEN */ + +static void +reduce_by_version(heim_object_t value, void *ctx, int *stop) +{ + struct iter_ctx *s = ctx; + struct krb5_plugin *pl = value; + + if (pl->ftable && pl->ftable->version >= s->caller->min_version) + heim_array_append_value(s->result, pl); +} + static void search_modules(heim_object_t key, heim_object_t value, void *ctx) { struct iter_ctx *s = ctx; - struct plugin2 *p = value; - struct plug *pl = heim_dict_copy_value(p->names, s->n); - struct common_plugin_method *cpm; + struct krb5_dso *p = value; + heim_array_t plugins = heim_dict_copy_value(p->plugins_by_name, s->n); - if (pl == NULL) { - if (p->dsohandle == NULL) - return; +#ifdef HAVE_DLOPEN + if (plugins == NULL && p->dsohandle) { + const char *path = heim_string_get_utf8(p->path); - pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free); - - cpm = pl->dataptr = dlsym(p->dsohandle, s->name); - if (cpm) { - int ret; - - ret = cpm->init(s->context, &pl->ctx); - if (ret) - cpm = pl->dataptr = NULL; + plugins = add_dso_plugins_load_fn(s->context, + s->caller, + path, + p->dsohandle); + if (plugins == NULL) { + /* fallback to loading structure directly */ + plugins = add_dso_plugin_struct(s->context, path, + p->dsohandle, s->caller->name); + } + if (plugins) { + heim_dict_set_value(p->plugins_by_name, s->n, plugins); + _krb5_debug(s->context, 5, "Loaded %zu %s %s plugin%s from %s", + heim_array_get_length(plugins), + s->caller->module, s->caller->name, + heim_array_get_length(plugins) > 1 ? "s" : "", + path); } - heim_dict_set_value(p->names, s->n, pl); - } else { - cpm = pl->dataptr; } +#endif /* HAVE_DLOPEN */ - if (cpm && cpm->version >= s->min_version) - heim_array_append_value(s->result, pl); - heim_release(pl); + if (plugins) { + heim_array_iterate_f(plugins, s, reduce_by_version); + heim_release(plugins); + } } static void eval_results(heim_object_t value, void *ctx, int *stop) { - struct plug *pl = value; + struct krb5_plugin *pl = value; struct iter_ctx *s = ctx; if (s->ret != KRB5_PLUGIN_NO_HANDLE) return; - s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx); + s->ret = s->func(s->context, pl->ftable, pl->ctx, s->userctx); if (s->ret != KRB5_PLUGIN_NO_HANDLE && !(s->flags & KRB5_PLUGIN_INVOKE_ALL)) *stop = 1; @@ -503,7 +697,7 @@ eval_results(heim_object_t value, void *ctx, int *stop) * Run plugins for the given @module (e.g., "krb5") and @name (e.g., * "kuserok"). Specifically, the @func is invoked once per-plugin with * four arguments: the @context, the plugin symbol value (a pointer to a - * struct whose first three fields are the same as struct common_plugin_method), + * struct whose first three fields are the same as common_plugin_ftable), * a context value produced by the plugin's init method, and @userctx. * * @func should unpack arguments for a plugin function and invoke it @@ -530,83 +724,69 @@ eval_results(heim_object_t value, void *ctx, int *stop) */ KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL _krb5_plugin_run_f(krb5_context context, - const char *module, - const char *name, - int min_version, + struct krb5_plugin_data *caller, int flags, void *userctx, krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *)) { - heim_string_t m = heim_string_create(module); - heim_dict_t dict = NULL; - void *plug_ctx; - struct common_plugin_method *cpm; + heim_string_t m = heim_string_create(caller->module); + heim_dict_t modules, dict = NULL; struct iter_ctx s; - struct krb5_plugin *registered_plugins = NULL; - struct krb5_plugin *p; - - /* Get registered plugins */ - (void) _krb5_plugin_find(context, SYMBOL, name, ®istered_plugins); - - HEIMDAL_MUTEX_lock(&plugin_mutex); s.context = context; - s.name = name; - s.n = heim_string_create(name); + s.caller = caller; + s.n = heim_string_create(caller->name); s.flags = flags; - s.min_version = min_version; s.result = heim_array_create(); s.func = func; s.userctx = userctx; s.ret = KRB5_PLUGIN_NO_HANDLE; - /* Get loaded plugins */ - if (modules) - dict = heim_dict_copy_value(modules, m); + HEIMDAL_MUTEX_lock(&modules_mutex); - heim_release(m); + /* Get loaded plugins */ + modules = copy_modules(); + dict = heim_dict_copy_value(modules, m); /* Add loaded plugins to s.result array */ if (dict) heim_dict_iterate_f(dict, &s, search_modules); - /* We don't need to hold plugin_mutex during plugin invocation */ - HEIMDAL_MUTEX_unlock(&plugin_mutex); + /* We don't need to hold modules_mutex during plugin invocation */ + HEIMDAL_MUTEX_unlock(&modules_mutex); - /* Invoke registered plugins (old system) */ - for (p = registered_plugins; p; p = p->next) { - /* - * XXX This is the wrong way to handle registered plugins, as we - * call init/fini on each invocation! We do this because we - * have nowhere in the struct plugin registered list to store - * the context allocated by the plugin's init function. (But at - * least we do call init/fini!) - * - * What we should do is adapt the old plugin system to the new - * one and change how we register plugins so that we use the new - * struct plug to keep track of their context structures, that - * way we can init once, invoke many times, then fini. - */ - cpm = (struct common_plugin_method *)p->symbol; - s.ret = cpm->init(context, &plug_ctx); - if (s.ret) - continue; - s.ret = s.func(s.context, p->symbol, plug_ctx, s.userctx); - cpm->fini(plug_ctx); - if (s.ret != KRB5_PLUGIN_NO_HANDLE && - !(flags & KRB5_PLUGIN_INVOKE_ALL)) - break; - } - _krb5_plugin_free(registered_plugins); - - /* Invoke loaded plugins (new system) */ - if (s.ret == KRB5_PLUGIN_NO_HANDLE) - heim_array_iterate_f(s.result, &s, eval_results); + /* Invoke loaded plugins */ + heim_array_iterate_f(s.result, &s, eval_results); heim_release(s.result); heim_release(s.n); - if (dict) - heim_release(dict); + heim_release(dict); + heim_release(m); + heim_release(modules); return s.ret; } + +/** + * Return a cookie identifying this instance of a library. + * + * Inputs: + * + * @context A krb5_context + * @module Our library name or a library we depend on + * + * Outputs: The instance cookie + * + * @ingroup krb5_support + */ + +KRB5_LIB_FUNCTION uintptr_t KRB5_LIB_CALL +krb5_get_instance(const char *libname) +{ + static const char *instance = "libkrb5"; + + if (strcmp(libname, "krb5") == 0) + return (uintptr_t)instance; + + return 0; +} diff --git a/lib/krb5/send_to_kdc.c b/lib/krb5/send_to_kdc.c index cc77f46cf..ff5fc4438 100644 --- a/lib/krb5/send_to_kdc.c +++ b/lib/krb5/send_to_kdc.c @@ -96,6 +96,17 @@ realmcallback(krb5_context context, const void *plug, void *plugctx, void *userc ctx->send_data, ctx->receive); } +static const char *send_to_kdc_plugin_deps[] = { "krb5", NULL }; + +static struct krb5_plugin_data +send_to_kdc_plugin_data = { + "krb5", + KRB5_PLUGIN_SEND_TO_KDC, + KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, + send_to_kdc_plugin_deps, + krb5_get_instance +}; + static krb5_error_code kdc_via_plugin(krb5_context context, krb5_krbhst_info *hi, @@ -111,8 +122,7 @@ kdc_via_plugin(krb5_context context, userctx.send_data = send_data; userctx.receive = receive; - return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC, - KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0, + return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0, &userctx, kdccallback); } @@ -131,8 +141,7 @@ realm_via_plugin(krb5_context context, userctx.send_data = send_data; userctx.receive = receive; - return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC, - KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0, + return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0, &userctx, realmcallback); } diff --git a/lib/krb5/test_plugin.c b/lib/krb5/test_plugin.c index ed6a9e7f1..61487cdc8 100644 --- a/lib/krb5/test_plugin.c +++ b/lib/krb5/test_plugin.c @@ -66,10 +66,12 @@ resolve_lookup(void *ctx, s.sin_port = htons(88); s.sin_addr.s_addr = htonl(0x7f000002); - if (strcmp(realm, "NOTHERE.H5L.SE") == 0) + if (strcmp(realm, "NOTHERE.H5L.SE") == 0) { (*add)(addctx, type, (struct sockaddr *)&s); + return 0; + } - return 0; + return KRB5_PLUGIN_NO_HANDLE; } diff --git a/lib/krb5/version-script.map b/lib/krb5/version-script.map index 94df630a0..a9710a087 100644 --- a/lib/krb5/version-script.map +++ b/lib/krb5/version-script.map @@ -362,6 +362,7 @@ HEIMDAL_KRB5_2.0 { krb5_get_init_creds_opt_set_tkt_life; krb5_get_init_creds_opt_set_win2k; krb5_get_init_creds_password; + krb5_get_instance; krb5_get_kdc_cred; krb5_get_kdc_sec_offset; krb5_get_krb524hst; @@ -748,6 +749,10 @@ HEIMDAL_KRB5_2.0 { _krb5_build_authenticator; _krb5_kt_client_default_name; + # Shared with libkadm5 + _krb5_load_plugins; + _krb5_unload_plugins; + # Shared with libkdc _krb5_AES_SHA1_string_to_default_iterator; _krb5_AES_SHA2_string_to_default_iterator; @@ -761,8 +766,6 @@ HEIMDAL_KRB5_2.0 { _krb5_pk_load_id; _krb5_pk_mk_ContentInfo; _krb5_pk_octetstring2key; - _krb5_plugin_find; - _krb5_plugin_free; _krb5_plugin_run_f; _krb5_principal2principalname; _krb5_principalname2krb5_principal;