From 21330cb3db69fc5a004844a1e4dec8998eb50068 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 3 Mar 2016 18:53:31 +0100 Subject: [PATCH 14/19] Add KDC pre-send and post-receive KDC hooks Add two new APIs, krb5_set_kdc_send_hook() and krb5_set_kdc_recv_hook(), which can be used to inspect and override messages sent to KDCs. [ghudson@mit.edu: style and documentation changes] ticket: 8386 (new) --- doc/appdev/refs/api/index.rst | 2 + doc/appdev/refs/types/index.rst | 2 + src/include/k5-int.h | 6 +++ src/include/krb5/krb5.hin | 104 ++++++++++++++++++++++++++++++++++++++++ src/lib/krb5/libkrb5.exports | 2 + src/lib/krb5/os/sendto_kdc.c | 56 +++++++++++++++++++++- src/lib/krb5_32.def | 4 ++ 7 files changed, 174 insertions(+), 2 deletions(-) diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst index 8df351d..e97cbca 100644 --- a/doc/appdev/refs/api/index.rst +++ b/doc/appdev/refs/api/index.rst @@ -268,6 +268,8 @@ Rarely used public interfaces krb5_server_decrypt_ticket_keytab.rst krb5_set_default_tgs_enctypes.rst krb5_set_error_message.rst + krb5_set_kdc_recv_hook.rst + krb5_set_kdc_send_hook.rst krb5_set_real_time.rst krb5_string_to_cksumtype.rst krb5_string_to_deltat.rst diff --git a/doc/appdev/refs/types/index.rst b/doc/appdev/refs/types/index.rst index 51c4093..dc414cf 100644 --- a/doc/appdev/refs/types/index.rst +++ b/doc/appdev/refs/types/index.rst @@ -57,6 +57,8 @@ Public krb5_pa_svr_referral_data.rst krb5_pa_data.rst krb5_pointer.rst + krb5_post_recv_fn.rst + krb5_pre_send_fn.rst krb5_preauthtype.rst krb5_principal.rst krb5_principal_data.rst diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 6b7b2e3..045abfc 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -1238,6 +1238,12 @@ struct _krb5_context { krb5_trace_callback trace_callback; void *trace_callback_data; + krb5_pre_send_fn kdc_send_hook; + void *kdc_send_hook_data; + + krb5_post_recv_fn kdc_recv_hook; + void *kdc_recv_hook_data; + struct plugin_interface plugins[PLUGIN_NUM_INTERFACES]; char *plugin_base_dir; }; diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index c93a0f2..2b0d59e 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -8300,6 +8300,110 @@ krb5_set_trace_callback(krb5_context context, krb5_trace_callback fn, krb5_error_code KRB5_CALLCONV krb5_set_trace_filename(krb5_context context, const char *filename); + +/** + * Hook function for inspecting or modifying messages sent to KDCs. + * + * If the hook function returns an error code, the KDC communication will be + * aborted and the error code will be returned to the library operation which + * initiated the communication. + * + * If the hook function sets @a reply_out, @a message will not be sent to the + * KDC, and the given reply will used instead. + * + * If the hook function sets @a new_message_out, the given message will be sent + * to the KDC in place of @a message. + * + * If the hook function returns successfully without setting either output, + * @a message will be sent to the KDC normally. + * + * The hook function should use krb5_copy_data() to construct the value for + * @a new_message_out or @a reply_out, to ensure that it can be freed correctly + * by the library. + * + * @param [in] context Library context + * @param [in] data Callback data + * @param [in] realm The realm the message will be sent to + * @param [in] message The original message to be sent to the KDC + * @param [out] new_message_out Optional replacement message to be sent + * @param [out] reply_out Optional synthetic reply + * + * @retval 0 Success + * @return A Kerberos error code + */ +typedef krb5_error_code +(KRB5_CALLCONV *krb5_pre_send_fn)(krb5_context context, void *data, + const krb5_data *realm, + const krb5_data *message, + krb5_data **new_message_out, + krb5_data **new_reply_out); + +/** + * Hook function for inspecting or overriding KDC replies. + * + * If @a code is zero, @a reply contains the reply received from the KDC. The + * hook function may return an error code to simulate an error, may synthesize + * a different reply by setting @a new_reply_out, or may simply return + * successfully to do nothing. + * + * If @a code is non-zero, KDC communication failed and @a reply should be + * ignored. The hook function may return @a code or a different error code, or + * may synthesize a reply by setting @a new_reply_out and return successfully. + * + * The hook function should use krb5_copy_data() to construct the value for + * @a new_reply_out, to ensure that it can be freed correctly by the library. + * + * @param [in] context Library context + * @param [in] data Callback data + * @param [in] code Status of KDC communication + * @param [in] realm The realm the reply was received from + * @param [in] message The message sent to the realm's KDC + * @param [in] reply The reply received from the KDC + * @param [out] new_reply_out Optional replacement reply + * + * @retval 0 Success + * @return A Kerberos error code + */ +typedef krb5_error_code +(KRB5_CALLCONV *krb5_post_recv_fn)(krb5_context context, void *data, + krb5_error_code code, + const krb5_data *realm, + const krb5_data *message, + const krb5_data *reply, + krb5_data **new_reply_out); + +/** + * Set a KDC pre-send hook function. + * + * @a send_hook will be called before messages are sent to KDCs by library + * functions such as krb5_get_credentials(). The hook function may inspect, + * override, or synthesize its own reply to the message. + * + * @param [in] context Library context + * @param [in] send_hook Hook function (or NULL to disable the hook) + * @param [in] data Callback data to be passed to @a send_hook + */ +void KRB5_CALLCONV +krb5_set_kdc_send_hook(krb5_context context, krb5_pre_send_fn send_hook, + void *data); + +/** + * Set a KDC post-receive hook function. + * + * @a recv_hook will be called after a reply is received from a KDC during a + * call to a library function such as krb5_get_credentials(). The hook + * function may inspect or override the reply. This hook will not be executed + * if the pre-send hook returns a synthetic reply. + * + * @param [in] context The library context. + * @param [in] recv_hook Hook function (or NULL to disable the hook) + * @param [in] data Callback data to be passed to @a recv_hook + */ +void KRB5_CALLCONV +krb5_set_kdc_recv_hook(krb5_context context, krb5_post_recv_fn recv_hook, + void *data); + + #if TARGET_OS_MAC # pragma pack(pop) #endif diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index c623409..ea6982d 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -581,6 +581,8 @@ krb5_set_password krb5_set_password_using_ccache krb5_set_principal_realm krb5_set_real_time +krb5_set_kdc_send_hook +krb5_set_kdc_recv_hook krb5_set_time_offsets krb5_set_trace_callback krb5_set_trace_filename diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c index 6231de2..be00b8f 100644 --- a/src/lib/krb5/os/sendto_kdc.c +++ b/src/lib/krb5/os/sendto_kdc.c @@ -403,6 +403,22 @@ check_for_svc_unavailable (krb5_context context, return 1; } +void +krb5_set_kdc_send_hook(krb5_context context, krb5_pre_send_fn send_hook, + void *data) +{ + context->kdc_send_hook = send_hook; + context->kdc_send_hook_data = data; +} + +void +krb5_set_kdc_recv_hook(krb5_context context, krb5_post_recv_fn recv_hook, + void *data) +{ + context->kdc_recv_hook = recv_hook; + context->kdc_recv_hook_data = data; +} + /* * send the formatted request 'message' to a KDC for realm 'realm' and * return the response (if any) in 'reply'. @@ -416,13 +432,16 @@ check_for_svc_unavailable (krb5_context context, krb5_error_code krb5_sendto_kdc(krb5_context context, const krb5_data *message, - const krb5_data *realm, krb5_data *reply, int *use_master, + const krb5_data *realm, krb5_data *reply_out, int *use_master, int no_udp) { krb5_error_code retval, err; struct serverlist servers; int server_used; k5_transport_strategy strategy; + krb5_data reply = empty_data(), *hook_message = NULL, *hook_reply = NULL; + + *reply_out = empty_data(); /* * find KDC location(s) for realm @@ -467,9 +486,26 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, if (retval) return retval; + if (context->kdc_send_hook != NULL) { + retval = context->kdc_send_hook(context, context->kdc_send_hook_data, + realm, message, &hook_message, + &hook_reply); + if (retval) + goto cleanup; + + if (hook_reply != NULL) { + *reply_out = *hook_reply; + free(hook_reply); + goto cleanup; + } + + if (hook_message != NULL) + message = hook_message; + } + err = 0; retval = k5_sendto(context, message, realm, &servers, strategy, NULL, - reply, NULL, NULL, &server_used, + &reply, NULL, NULL, &server_used, check_for_svc_unavailable, &err); if (retval == KRB5_KDC_UNREACH) { if (err == KDC_ERR_SVC_UNAVAILABLE) { @@ -480,9 +516,23 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, realm->length, realm->data); } } + + if (context->kdc_recv_hook != NULL) { + retval = context->kdc_recv_hook(context, context->kdc_recv_hook_data, + retval, realm, message, &reply, + &hook_reply); + } if (retval) goto cleanup; + if (hook_reply != NULL) { + *reply_out = *hook_reply; + free(hook_reply); + } else { + *reply_out = reply; + reply = empty_data(); + } + /* Set use_master to 1 if we ended up talking to a master when we didn't * explicitly request to. */ if (*use_master == 0) { @@ -492,6 +542,8 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, } cleanup: + krb5_free_data(context, hook_message); + krb5_free_data_contents(context, &reply); k5_free_serverlist(&servers); return retval; } diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index 3734e9b..8d58ea1 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -463,3 +463,7 @@ EXPORTS krb5_vwrap_error_message @430 krb5_c_prfplus @431 krb5_c_derive_prfplus @432 + +; new in 1.15 + krb5_set_kdc_send_hook @433 + krb5_set_kdc_recv_hook @434 -- 2.9.3