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