diff --git a/lib/kadm5/Makefile.am b/lib/kadm5/Makefile.am index de9a20822..6f16ec6e8 100644 --- a/lib/kadm5/Makefile.am +++ b/lib/kadm5/Makefile.am @@ -17,11 +17,14 @@ sbin_PROGRAMS = iprop-log check_PROGRAMS = default_keys noinst_PROGRAMS = test_pw_quality -noinst_LTLIBRARIES = sample_passwd_check.la +noinst_LTLIBRARIES = sample_passwd_check.la sample_hook.la sample_passwd_check_la_SOURCES = sample_passwd_check.c sample_passwd_check_la_LDFLAGS = -module +sample_hook_la_SOURCES = sample_hook.c +sample_hook_la_LDFLAGS = -module + libkadm5srv_la_LIBADD = \ $(LIB_com_err) ../krb5/libkrb5.la \ ../hdb/libhdb.la $(LIBADD_roken) @@ -35,7 +38,7 @@ default_keys_SOURCES = default_keys.c kadm5includedir = $(includedir)/kadm5 buildkadm5include = $(buildinclude)/kadm5 -dist_kadm5include_HEADERS = admin.h private.h kadm5-pwcheck.h +dist_kadm5include_HEADERS = admin.h private.h kadm5-hook.h kadm5-pwcheck.h dist_kadm5include_HEADERS += $(srcdir)/kadm5-protos.h $(srcdir)/kadm5-private.h nodist_kadm5include_HEADERS = kadm5_err.h @@ -108,6 +111,7 @@ dist_libkadm5srv_la_SOURCES = \ randkey_s.c \ rename_s.c \ server_glue.c \ + server_hooks.c \ setkey3_s.c \ set_keys.c \ set_modifier.c \ @@ -212,5 +216,6 @@ EXTRA_DIST = \ check-cracklib.pl \ flush.c \ sample_passwd_check.c \ + sample_hook.c \ version-script.map \ version-script-client.map diff --git a/lib/kadm5/NTMakefile b/lib/kadm5/NTMakefile index 392a88438..94d5d99fd 100644 --- a/lib/kadm5/NTMakefile +++ b/lib/kadm5/NTMakefile @@ -55,7 +55,8 @@ dist_libkadm5clnt_la_SOURCES = \ rename_c.c \ send_recv.c \ kadm5-pwcheck.h \ - admin.h + admin.h \ + kadm5-hook.h dist_libkadm5srv_la_SOURCES = \ acl.c \ @@ -85,11 +86,13 @@ dist_libkadm5srv_la_SOURCES = \ randkey_s.c \ rename_s.c \ server_glue.c \ + server_hooks.c \ set_keys.c \ setkey3_s.c \ set_modifier.c \ kadm5-pwcheck.h \ - admin.h + admin.h \ + kadm5-hook.h LIBKADM5CLNT_OBJS= \ $(OBJ)\ad.obj \ @@ -137,6 +140,7 @@ LIBKADM5SRV_OBJS= \ $(OBJ)\randkey_s.obj \ $(OBJ)\rename_s.obj \ $(OBJ)\server_glue.obj \ + $(OBJ)\server_hooks.obj \ $(OBJ)\set_keys.obj \ $(OBJ)\setkey3_s.obj \ $(OBJ)\set_modifier.obj \ @@ -178,6 +182,7 @@ INCFILES=\ $(KADM5INCDIR)\kadm5_err.h \ $(KADM5INCDIR)\admin.h \ $(KADM5INCDIR)\private.h \ + $(KADM5INCDIR)\kadm5-hook.h \ $(KADM5INCDIR)\kadm5-protos.h \ $(KADM5INCDIR)\kadm5-private.h \ $(OBJ)\iprop-commands.h @@ -239,7 +244,8 @@ test:: test-binaries test-run test-binaries: \ $(OBJ)\default_keys.exe \ $(OBJ)\test_pw_quality.exe \ - $(OBJ)\sample_passwd_check.dll + $(OBJ)\sample_passwd_check.dll \ + $(OBJ)\sample_hook.dll $(OBJ)\default_keys.exe: $(OBJ)\default_keys.obj $(LIBHEIMDAL) $(LIBROKEN) $(LIBHDB) $(EXECONLINK) @@ -258,6 +264,13 @@ EXPORTS << $(DLLPREP_NODIST) +$(OBJ)\sample_hook.dll: $(OBJ)\sample_hook.obj $(LIBHEIMDAL) + $(DLLGUILINK) /DEF:<< +EXPORTS + kadm5_hook_init +<< + $(DLLPREP_NODIST) + test-run: cd $(OBJ) -default_keys.exe diff --git a/lib/kadm5/chpass_s.c b/lib/kadm5/chpass_s.c index d1ed73207..726ab5f0a 100644 --- a/lib/kadm5/chpass_s.c +++ b/lib/kadm5/chpass_s.c @@ -35,6 +35,41 @@ RCSID("$Id$"); +static kadm5_ret_t +chpass_principal_hook(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) +{ + krb5_error_code ret = 0; + size_t i; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + 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) { + krb5_prepend_error_message(context->context, ret, + "chpass hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + static kadm5_ret_t change(void *server_handle, krb5_principal princ, @@ -50,6 +85,7 @@ change(void *server_handle, Key *keys; size_t num_keys; int existsp = 0; + uint32_t hook_flags = 0; memset(&ent, 0, sizeof(ent)); if (!context->keep_open) { @@ -67,6 +103,16 @@ change(void *server_handle, if (ret) goto out2; + if (keepold) + hook_flags |= KADM5_HOOK_FLAG_KEEPOLD; + if (cond) + hook_flags |= KADM5_HOOK_FLAG_CONDITIONAL; + ret = chpass_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, + 0, princ, hook_flags, + n_ks_tuple, ks_tuple, password); + if (ret) + goto out3; + if (keepold || cond) { /* * We save these for now so we can handle password history checking; @@ -149,6 +195,10 @@ change(void *server_handle, KADM5_KEY_DATA | KADM5_KVNO | KADM5_PW_EXPIRATION | KADM5_TL_DATA); + (void) chpass_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, + ret, princ, hook_flags, + n_ks_tuple, ks_tuple, password); + out3: hdb_free_entry(context->context, &ent); out2: diff --git a/lib/kadm5/context_s.c b/lib/kadm5/context_s.c index 7e8b6153c..55421b127 100644 --- a/lib/kadm5/context_s.c +++ b/lib/kadm5/context_s.c @@ -268,6 +268,13 @@ _kadm5_s_init_context(kadm5_server_context **ctx, find_db_spec(*ctx); + ret = _kadm5_s_init_hooks(*ctx); + if (ret != 0) { + kadm5_s_destroy(*ctx); + *ctx = NULL; + return ret; + } + /* PROFILE can't be specified for now */ /* KADMIND_PORT is supposed to be used on the server also, but this doesn't make sense */ diff --git a/lib/kadm5/create_s.c b/lib/kadm5/create_s.c index 7d8f89837..b1fe35d9c 100644 --- a/lib/kadm5/create_s.c +++ b/lib/kadm5/create_s.c @@ -102,6 +102,38 @@ create_principal(kadm5_server_context *context, &ent->entry.created_by.principal); } +static kadm5_ret_t +create_principal_hook(kadm5_server_context *context, + enum kadm5_hook_stage stage, + krb5_error_code code, + kadm5_principal_ent_t princ, + uint32_t mask, + const char *password) +{ + krb5_error_code ret = 0; + size_t i; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + if (hook->hook->create != NULL) { + ret = hook->hook->create(context->context, hook->data, + stage, code, princ, mask, password); + if (ret != 0) { + krb5_prepend_error_message(context->context, ret, + "create hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + kadm5_ret_t kadm5_s_create_principal_with_key(void *server_handle, kadm5_principal_ent_t princ, @@ -117,6 +149,11 @@ kadm5_s_create_principal_with_key(void *server_handle, mask |= KADM5_KVNO; } + ret = create_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, + 0, princ, mask, NULL); + if (ret) + return ret; + ret = create_principal(context, princ, mask, &ent, KADM5_PRINCIPAL | KADM5_KEY_DATA, KADM5_LAST_PWD_CHANGE | KADM5_MOD_TIME @@ -146,6 +183,9 @@ kadm5_s_create_principal_with_key(void *server_handle, /* This logs the change for iprop and writes to the HDB */ ret = kadm5_log_create(context, &ent.entry); + (void) create_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, + ret, princ, mask, NULL); + out2: (void) kadm5_log_end(context); out: @@ -178,6 +218,11 @@ kadm5_s_create_principal(void *server_handle, mask |= KADM5_KVNO; } + ret = create_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, + 0, princ, mask, password); + if (ret) + return ret; + ret = create_principal(context, princ, mask, &ent, KADM5_PRINCIPAL, KADM5_LAST_PWD_CHANGE | KADM5_MOD_TIME @@ -214,6 +259,9 @@ kadm5_s_create_principal(void *server_handle, /* This logs the change for iprop and writes to the HDB */ ret = kadm5_log_create(context, &ent.entry); + (void) create_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, + ret, princ, mask, password); + out2: (void) kadm5_log_end(context); out: diff --git a/lib/kadm5/delete_s.c b/lib/kadm5/delete_s.c index 2e4aeff53..55236fb5b 100644 --- a/lib/kadm5/delete_s.c +++ b/lib/kadm5/delete_s.c @@ -35,6 +35,36 @@ RCSID("$Id$"); +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; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + if (hook->hook->delete != NULL) { + ret = hook->hook->delete(context->context, hook->data, + stage, code, princ); + if (ret != 0) { + krb5_prepend_error_message(context->context, ret, + "delete hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + kadm5_ret_t kadm5_s_delete_principal(void *server_handle, krb5_principal princ) { @@ -64,6 +94,10 @@ kadm5_s_delete_principal(void *server_handle, krb5_principal princ) goto out3; } + ret = delete_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, 0, princ); + if (ret) + goto out3; + ret = hdb_seal_keys(context->context, context->db, &ent.entry); if (ret) goto out3; @@ -71,6 +105,8 @@ kadm5_s_delete_principal(void *server_handle, krb5_principal princ) /* This logs the change for iprop and writes to the HDB */ ret = kadm5_log_delete(context, princ); + (void) delete_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, ret, princ); + out3: hdb_free_entry(context->context, &ent); out2: diff --git a/lib/kadm5/destroy_s.c b/lib/kadm5/destroy_s.c index 242436637..fc86e5960 100644 --- a/lib/kadm5/destroy_s.c +++ b/lib/kadm5/destroy_s.c @@ -77,6 +77,7 @@ kadm5_s_destroy(void *server_handle) kadm5_server_context *context = server_handle; krb5_context kcontext = context->context; + _kadm5_s_free_hooks(context); if (context->db != NULL) ret = context->db->hdb_destroy(kcontext, context->db); destroy_kadm5_log_context(&context->log_context); diff --git a/lib/kadm5/kadm5-hook.h b/lib/kadm5/kadm5-hook.h new file mode 100644 index 000000000..55755b85d --- /dev/null +++ b/lib/kadm5/kadm5-hook.h @@ -0,0 +1,139 @@ +/* + * Copyright 2010 + * The Board of Trustees of the Leland Stanford Junior University + * + * 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. + * + * 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. + * + * 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 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. + */ + +#ifndef KADM5_HOOK_H +#define KADM5_HOOK_H 1 + +#define KADM5_HOOK_VERSION_V1 1 + +/* + * Each hook is called before the operation using KADM5_STAGE_PRECOMMIT and + * then after the operation using KADM5_STAGE_POSTCOMMIT. If the hook returns + * failure during precommit, the operation is aborted without changes to the + * database. + */ +enum kadm5_hook_stage { + KADM5_HOOK_STAGE_PRECOMMIT, + KADM5_HOOK_STAGE_POSTCOMMIT +}; + +#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 { + 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- + * commit hooks are not called if a pre-commit hook aborted the call. + */ + krb5_error_code (KRB5_CALLCONV *chpass)(krb5_context context, + void *data, + 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); + + krb5_error_code (KRB5_CALLCONV *create)(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + kadm5_principal_ent_t ent, + uint32_t mask, + const char *password); + + krb5_error_code (KRB5_CALLCONV *modify)(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + kadm5_principal_ent_t ent, + uint32_t mask); + + krb5_error_code (KRB5_CALLCONV *delete)(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ); + + krb5_error_code (KRB5_CALLCONV *randkey)(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ); + + krb5_error_code (KRB5_CALLCONV *rename)(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal source, + krb5_const_principal target); + + krb5_error_code (KRB5_CALLCONV *set_keys)(krb5_context context, + void *data, + 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 *keyblocks); + +} kadm5_hook; + +typedef krb5_error_code +(KRB5_CALLCONV *kadm5_hook_init_t)(krb5_context context, + uint32_t kadm5_version_max, + const kadm5_hook **hook, + void **data); + +#endif /* !KADM5_HOOK_H */ diff --git a/lib/kadm5/kadm5_err.et b/lib/kadm5/kadm5_err.et index bcbaea846..42f4069e7 100644 --- a/lib/kadm5/kadm5_err.et +++ b/lib/kadm5/kadm5_err.et @@ -67,3 +67,7 @@ error_code ALREADY_LOCKED, "Database already locked" error_code NOT_LOCKED, "Database not locked" error_code LOG_CORRUPT, "Incremental propagation log got corrupted" error_code LOG_NEEDS_UPGRADE, "Incremental propagation log must be upgraded" +error_code BAD_SERVER_HOOK, "Bad KADM5 server hook" +error_code SERVER_HOOK_NOT_FOUND, "Cannot find KADM5 server hook" +error_code OLD_SERVER_HOOK_VERSION, "KADM5 server hook is too old for this version of Heimdal" +error_code NEW_SERVER_HOOK_VERSION, "KADM5 server hook is too new for this version of Heimdal" diff --git a/lib/kadm5/modify_s.c b/lib/kadm5/modify_s.c index e76dbdbbc..27d9f5dae 100644 --- a/lib/kadm5/modify_s.c +++ b/lib/kadm5/modify_s.c @@ -35,6 +35,37 @@ RCSID("$Id$"); +static kadm5_ret_t +modify_principal_hook(kadm5_server_context *context, + enum kadm5_hook_stage stage, + krb5_error_code code, + kadm5_principal_ent_t princ, + uint32_t mask) +{ + krb5_error_code ret = 0; + size_t i; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + if (hook->hook->modify != NULL) { + ret = hook->hook->modify(context->context, hook->data, + stage, code, princ, mask); + if (ret != 0) { + krb5_prepend_error_message(context->context, ret, + "modify hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + static kadm5_ret_t modify_principal(void *server_handle, kadm5_principal_ent_t princ, @@ -66,6 +97,11 @@ modify_principal(void *server_handle, princ->principal, HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent); if (ret) goto out2; + + ret = modify_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, + 0, princ, mask); + if (ret) + goto out3; ret = _kadm5_setup_entry(context, &ent, mask, princ, mask, NULL, 0); if (ret) goto out3; @@ -114,6 +150,9 @@ modify_principal(void *server_handle, ret = kadm5_log_modify(context, &ent.entry, mask | KADM5_MOD_NAME | KADM5_MOD_TIME); + (void) modify_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, + ret, princ, mask); + out3: hdb_free_entry(context->context, &ent); out2: diff --git a/lib/kadm5/private.h b/lib/kadm5/private.h index 0b14ebd68..69d1bdc8b 100644 --- a/lib/kadm5/private.h +++ b/lib/kadm5/private.h @@ -36,6 +36,8 @@ #ifndef __kadm5_privatex_h__ #define __kadm5_privatex_h__ +#include "kadm5-hook.h" + #ifdef HAVE_SYS_UN_H #include #endif @@ -67,6 +69,12 @@ struct kadm_func { krb5_keyblock *, int); }; +typedef struct kadm5_hook_context { + void *handle; + const kadm5_hook *hook; + void *data; +} kadm5_hook_context; + /* XXX should be integrated */ typedef struct kadm5_common_context { krb5_context context; @@ -108,6 +116,8 @@ typedef struct kadm5_server_context { krb5_principal caller; unsigned acl_flags; kadm5_log_context log_context; + size_t num_hooks; + kadm5_hook_context **hooks; } kadm5_server_context; typedef struct kadm5_client_context { diff --git a/lib/kadm5/randkey_s.c b/lib/kadm5/randkey_s.c index 64578ab49..61b390a58 100644 --- a/lib/kadm5/randkey_s.c +++ b/lib/kadm5/randkey_s.c @@ -35,6 +35,36 @@ RCSID("$Id$"); +static kadm5_ret_t +randkey_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; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + if (hook->hook->randkey != NULL) { + ret = hook->hook->randkey(context->context, hook->data, + stage, code, princ); + if (ret != 0) { + krb5_prepend_error_message(context->context, ret, + "randkey hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + /* * Set the keys of `princ' to random values, returning the random keys * in `new_keys', `n_keys'. @@ -52,6 +82,7 @@ kadm5_s_randkey_principal(void *server_handle, kadm5_server_context *context = server_handle; hdb_entry_ex ent; kadm5_ret_t ret; + size_t i; memset(&ent, 0, sizeof(ent)); if (!context->keep_open) { @@ -69,6 +100,10 @@ kadm5_s_randkey_principal(void *server_handle, if(ret) goto out2; + ret = randkey_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, 0, princ); + if (ret) + goto out3; + if (keepold) { ret = hdb_add_current_keys_to_history(context->context, &ent.entry); if (ret) @@ -112,10 +147,10 @@ kadm5_s_randkey_principal(void *server_handle, KADM5_KEY_DATA | KADM5_KVNO | KADM5_PW_EXPIRATION | KADM5_TL_DATA); + (void) randkey_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, ret, princ); + out4: if (ret) { - int i; - for (i = 0; i < *n_keys; ++i) krb5_free_keyblock_contents(context->context, &(*new_keys)[i]); free (*new_keys); diff --git a/lib/kadm5/rename_s.c b/lib/kadm5/rename_s.c index c37aca768..78f28a3f1 100644 --- a/lib/kadm5/rename_s.c +++ b/lib/kadm5/rename_s.c @@ -35,6 +35,37 @@ RCSID("$Id$"); +static kadm5_ret_t +rename_principal_hook(kadm5_server_context *context, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal source, + krb5_const_principal target) +{ + krb5_error_code ret = 0; + size_t i; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + if (hook->hook->rename != NULL) { + ret = hook->hook->rename(context->context, hook->data, + stage, code, source, target); + if (ret != 0) { + krb5_prepend_error_message(context->context, ret, + "rename hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + kadm5_ret_t kadm5_s_rename_principal(void *server_handle, krb5_principal source, @@ -44,6 +75,7 @@ kadm5_s_rename_principal(void *server_handle, kadm5_ret_t ret; hdb_entry_ex ent; krb5_principal oldname; + size_t i; memset(&ent, 0, sizeof(ent)); if (krb5_principal_compare(context->context, source, target)) @@ -63,12 +95,17 @@ kadm5_s_rename_principal(void *server_handle, if (ret) goto out2; oldname = ent.entry.principal; + + ret = rename_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, + 0, source, target); + if (ret) + goto out3; + ret = _kadm5_set_modifier(context, &ent.entry); if (ret) goto out3; { /* fix salt */ - size_t i; Salt salt; krb5_salt salt2; memset(&salt, 0, sizeof(salt)); @@ -101,6 +138,9 @@ kadm5_s_rename_principal(void *server_handle, /* This logs the change for iprop and writes to the HDB */ ret = kadm5_log_rename(context, source, &ent.entry); + (void) rename_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, + ret, source, target); + out3: ent.entry.principal = oldname; /* Unborrow target */ hdb_free_entry(context->context, &ent); diff --git a/lib/kadm5/sample_hook.c b/lib/kadm5/sample_hook.c new file mode 100644 index 000000000..e2f9e4fd0 --- /dev/null +++ b/lib/kadm5/sample_hook.c @@ -0,0 +1,185 @@ +/* + * 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: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * 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. + * + */ + +#include "kadm5_locl.h" + +static char sample_data[1]; + +static krb5_error_code +sample_log(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + const char *tag, + krb5_error_code code, + krb5_const_principal princ) +{ + char *p = NULL; + krb5_error_code ret; + + if (data != sample_data) + return EINVAL; + if (code != 0 && stage == KADM5_HOOK_STAGE_PRECOMMIT) + return EINVAL; + + if (princ) + ret = krb5_unparse_name(context, princ, &p); + + krb5_warn(context, code, "sample_hook: %s %s hook princ '%s'", tag, + stage == KADM5_HOOK_STAGE_PRECOMMIT ? "pre-commit" : "post-commit", + p != NULL ? p : ""); + + krb5_xfree(p); + + return 0; +} + +static void KRB5_CALLCONV +sample_fini(krb5_context context, void *data) +{ + krb5_warn(context, 0, "sample_hook: shutting down\n"); +} + +static krb5_error_code KRB5_CALLCONV +sample_chpass_hook(krb5_context context, + void *data, + 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) +{ + return sample_log(context, data, stage, "chpass", code, princ); +} + +static krb5_error_code KRB5_CALLCONV +sample_create_hook(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + kadm5_principal_ent_t ent, + uint32_t mask, + const char *password) +{ + return sample_log(context, data, stage, "create", code, ent->principal); +} + +static krb5_error_code KRB5_CALLCONV +sample_modify_hook(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + kadm5_principal_ent_t ent, + uint32_t mask) +{ + return sample_log(context, data, stage, "modify", code, ent->principal); +} + +static krb5_error_code KRB5_CALLCONV +sample_delete_hook(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ) +{ + return sample_log(context, data, stage, "delete", code, princ); +} + +static krb5_error_code KRB5_CALLCONV +sample_randkey_hook(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal princ) +{ + return sample_log(context, data, stage, "randkey", code, princ); +} + +static krb5_error_code KRB5_CALLCONV +sample_rename_hook(krb5_context context, + void *data, + enum kadm5_hook_stage stage, + krb5_error_code code, + krb5_const_principal source, + krb5_const_principal target) +{ + return sample_log(context, data, stage, "rename", code, source); +} + +static krb5_error_code KRB5_CALLCONV +sample_set_keys_hook(krb5_context context, + void *data, + 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 *keyblocks) +{ + return sample_log(context, data, stage, "set_keys", code, princ); +} + +static struct kadm5_hook sample_hook = { + "sample-hook", + KADM5_HOOK_VERSION_V1, + "Heimdal", + krb5_init_context, + sample_fini, + sample_chpass_hook, + sample_create_hook, + sample_modify_hook, + sample_delete_hook, + sample_randkey_hook, + sample_rename_hook, + sample_set_keys_hook +}; + +krb5_error_code +kadm5_hook_init(krb5_context context, uint32_t vers_max, + const kadm5_hook **hook, void **data); + +krb5_error_code +kadm5_hook_init(krb5_context context, uint32_t vers_max, + const kadm5_hook **hook, void **data) +{ + 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; + + return 0; +} diff --git a/lib/kadm5/server_hooks.c b/lib/kadm5/server_hooks.c new file mode 100644 index 000000000..5a3cc0c5c --- /dev/null +++ b/lib/kadm5/server_hooks.c @@ -0,0 +1,183 @@ +/* + * Copyright 2010 + * The Board of Trustees of the Leland Stanford Junior University + * + * 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. + * + * 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. + * + * 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 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" +#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 + +/* + * Load kadmin server hooks. + */ +#ifdef HAVE_DLOPEN + +kadm5_ret_t +_kadm5_s_init_hooks(kadm5_server_context *ctx) +{ + krb5_context context = ctx->context; + char **hooks; + size_t i; + void *handle = NULL; + struct kadm5_hook_context *hook_context = NULL; + struct kadm5_hook_context **tmp; + kadm5_ret_t ret = KADM5_BAD_SERVER_HOOK; + + hooks = krb5_config_get_strings(context, NULL, + "kadmin", "hooks", NULL); + if (hooks == NULL) + return 0; + + 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; + + 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, "initialize 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, "version of loaded hook `%s' is %u" + " (supported versions %u to %u)", hookpath, 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, "loaded hook `%s' is not linked against " + "this version of Heimdal", hookpath); + 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++; + } + + return 0; + +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) +{ + int 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; +} + +# else /* !HAVE_DLOPEN */ + +kadm5_ret_t +_kadm5_s_init_hooks(kadm5_server_context *ctx) +{ + return 0; +} + +void +_kadm5_s_free_hooks(kadm5_server_context *ctx) +{ + return 0; +} + +#endif /* !HAVE_DLOPEN */ diff --git a/lib/kadm5/setkey3_s.c b/lib/kadm5/setkey3_s.c index 03b5d8101..91df57f2d 100644 --- a/lib/kadm5/setkey3_s.c +++ b/lib/kadm5/setkey3_s.c @@ -33,6 +33,43 @@ #include "kadm5_locl.h" +static kadm5_ret_t +setkey_principal_hook(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 *keyblocks) +{ + krb5_error_code ret = 0; + size_t i; + + for (i = 0; i < context->num_hooks; i++) { + kadm5_hook_context *hook = context->hooks[i]; + + 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) { + krb5_prepend_error_message(context->context, ret, + "setkey hook `%s' failed %scommit", + hook->hook->name, + stage == KADM5_HOOK_STAGE_PRECOMMIT + ? "pre" : "post"); + if (stage == KADM5_HOOK_STAGE_PRECOMMIT) + break; + } + } + } + + return ret; +} + /** * Server-side function to set new keys for a principal. */ @@ -47,6 +84,7 @@ kadm5_s_setkey_principal_3(void *server_handle, kadm5_server_context *context = server_handle; hdb_entry_ex ent; kadm5_ret_t ret = 0; + size_t i; memset(&ent, 0, sizeof(ent)); if (!context->keep_open) @@ -70,6 +108,16 @@ kadm5_s_setkey_principal_3(void *server_handle, return ret; } + ret = setkey_principal_hook(context, KADM5_HOOK_STAGE_PRECOMMIT, 0, + princ, keepold ? KADM5_HOOK_FLAG_KEEPOLD : 0, + n_ks_tuple, ks_tuple, n_keys, keyblocks); + if (ret) { + (void) kadm5_log_end(context); + if (!context->keep_open) + context->db->hdb_close(context->context, context->db); + return ret; + } + if (keepold) { ret = hdb_add_current_keys_to_history(context->context, &ent.entry); } else @@ -84,8 +132,6 @@ kadm5_s_setkey_principal_3(void *server_handle, * each ks_tuple's enctype matches the corresponding key enctype. */ if (ret == 0) { - int i; - free_Keys(&ent.entry.keys); for (i = 0; i < n_keys; ++i) { Key k; @@ -127,6 +173,10 @@ kadm5_s_setkey_principal_3(void *server_handle, KADM5_PW_EXPIRATION | KADM5_TL_DATA); } + (void) setkey_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT, ret, + princ, keepold, n_ks_tuple, ks_tuple, + n_keys, keyblocks); + hdb_free_entry(context->context, &ent); (void) kadm5_log_end(context); if (!context->keep_open)