Blame SOURCES/Add-KDC-policy-pluggable-interface.patch

d738b9
From 7ed63b6bdeff7b94775432415682051eca479071 Mon Sep 17 00:00:00 2001
d738b9
From: Robbie Harwood <rharwood@redhat.com>
d738b9
Date: Tue, 27 Jun 2017 17:15:39 -0400
d738b9
Subject: [PATCH] Add KDC policy pluggable interface
d738b9
d738b9
Add the header include/krb5/kdcpolicy_plugin.h, defining a pluggable
d738b9
interface for modules to deny AS and TGS requests and set maximum
d738b9
ticket lifetimes.  This interface replaces the policy.c stub functions.
d738b9
d738b9
Add check_kdcpolicy_as() and check_kdcpolicy_tgs() as entry functions.
d738b9
Call them after auth indicators and ticket lifetimes have been
d738b9
determined.
d738b9
d738b9
Add a test module and a test script with basic kdcpolicy tests.  Add
d738b9
plugin interface documentation in doc/plugindev/policy.rst.
d738b9
d738b9
Also authored by Matt Rogers <mrogers@redhat.com>.
d738b9
d738b9
ticket: 8606 (new)
d738b9
(cherry picked from commit d0969f6a8170344031ef58fd2a161190f1edfb96)
d738b9
[rharwood@redhat.com: mention but do not use kadm_auth]
d738b9
---
d738b9
 doc/plugindev/index.rst                       |   1 +
d738b9
 doc/plugindev/kdcpolicy.rst                   |  24 ++
d738b9
 src/Makefile.in                               |   1 +
d738b9
 src/configure.in                              |   1 +
d738b9
 src/include/Makefile.in                       |   1 +
d738b9
 src/include/k5-int.h                          |   4 +-
d738b9
 src/include/k5-trace.h                        |   5 +
d738b9
 src/include/krb5/kdcpolicy_plugin.h           | 128 +++++++++
d738b9
 src/kdc/do_as_req.c                           |   7 +
d738b9
 src/kdc/do_tgs_req.c                          |   6 +
d738b9
 src/kdc/kdc_util.c                            |   7 -
d738b9
 src/kdc/kdc_util.h                            |  11 -
d738b9
 src/kdc/main.c                                |   8 +
d738b9
 src/kdc/policy.c                              | 267 +++++++++++++++---
d738b9
 src/kdc/policy.h                              |  19 +-
d738b9
 src/kdc/tgs_policy.c                          |   6 -
d738b9
 src/lib/krb5/krb/plugin.c                     |   4 +-
d738b9
 src/plugins/kdcpolicy/test/Makefile.in        |  20 ++
d738b9
 src/plugins/kdcpolicy/test/deps               |   0
d738b9
 src/plugins/kdcpolicy/test/main.c             | 111 ++++++++
d738b9
 .../kdcpolicy/test/policy_test.exports        |   1 +
d738b9
 src/tests/Makefile.in                         |   1 +
d738b9
 src/tests/t_kdcpolicy.py                      |  57 ++++
d738b9
 23 files changed, 616 insertions(+), 74 deletions(-)
d738b9
 create mode 100644 doc/plugindev/kdcpolicy.rst
d738b9
 create mode 100644 src/include/krb5/kdcpolicy_plugin.h
d738b9
 create mode 100644 src/plugins/kdcpolicy/test/Makefile.in
d738b9
 create mode 100644 src/plugins/kdcpolicy/test/deps
d738b9
 create mode 100644 src/plugins/kdcpolicy/test/main.c
d738b9
 create mode 100644 src/plugins/kdcpolicy/test/policy_test.exports
d738b9
 create mode 100644 src/tests/t_kdcpolicy.py
d738b9
d738b9
diff --git a/doc/plugindev/index.rst b/doc/plugindev/index.rst
d738b9
index 67dbc2790..0a012b82b 100644
d738b9
--- a/doc/plugindev/index.rst
d738b9
+++ b/doc/plugindev/index.rst
d738b9
@@ -32,5 +32,6 @@ Contents
d738b9
    gssapi.rst
d738b9
    internal.rst
d738b9
    certauth.rst
d738b9
+   kdcpolicy.rst
d738b9
 
d738b9
 .. TODO: GSSAPI mechanism plugins
d738b9
diff --git a/doc/plugindev/kdcpolicy.rst b/doc/plugindev/kdcpolicy.rst
d738b9
new file mode 100644
d738b9
index 000000000..74f21f08f
d738b9
--- /dev/null
d738b9
+++ b/doc/plugindev/kdcpolicy.rst
d738b9
@@ -0,0 +1,24 @@
d738b9
+.. _kdcpolicy_plugin:
d738b9
+
d738b9
+KDC policy interface (kdcpolicy)
d738b9
+================================
d738b9
+
d738b9
+The kdcpolicy interface was first introduced in release 1.16.  It
d738b9
+allows modules to veto otherwise valid AS and TGS requests or restrict
d738b9
+the lifetime and renew time of the resulting ticket.  For a detailed
d738b9
+description of the kdcpolicy interface, see the header file
d738b9
+``<krb5/kdcpolicy_plugin.h>``.
d738b9
+
d738b9
+The optional **check_as** and **check_tgs** functions allow the module
d738b9
+to perform access control.  Additionally, a module can create and
d738b9
+destroy module data with the **init** and **fini** methods.  Module
d738b9
+data objects last for the lifetime of the KDC process, and are
d738b9
+provided to all other methods.  The data has the type
d738b9
+krb5_kdcpolicy_moddata, which should be cast to the appropriate
d738b9
+internal type.
d738b9
+
d738b9
+kdcpolicy modules can optionally inspect principal entries.  To do
d738b9
+this, the module must also include ``<kdb.h>`` to gain access to the
d738b9
+principal entry structure definition.  As the KDB interface is
d738b9
+explicitly not as stable as other public interfaces, modules which do
d738b9
+this may not retain compatibility across releases.
d738b9
diff --git a/src/Makefile.in b/src/Makefile.in
d738b9
index ad8565056..e47bddcb1 100644
d738b9
--- a/src/Makefile.in
d738b9
+++ b/src/Makefile.in
d738b9
@@ -21,6 +21,7 @@ SUBDIRS=util include lib \
d738b9
 	plugins/kdb/db2 \
d738b9
 	@ldap_plugin_dir@ \
d738b9
 	plugins/kdb/test \
d738b9
+	plugins/kdcpolicy/test \
d738b9
 	plugins/preauth/otp \
d738b9
 	plugins/preauth/pkinit \
d738b9
 	plugins/preauth/test \
d738b9
diff --git a/src/configure.in b/src/configure.in
d738b9
index 4ae2c07d5..ee1983043 100644
d738b9
--- a/src/configure.in
d738b9
+++ b/src/configure.in
d738b9
@@ -1470,6 +1470,7 @@ dnl	ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test
d738b9
 	plugins/kdb/db2/libdb2/recno
d738b9
 	plugins/kdb/db2/libdb2/test
d738b9
 	plugins/kdb/test
d738b9
+	plugins/kdcpolicy/test
d738b9
 	plugins/preauth/otp
d738b9
 	plugins/preauth/test
d738b9
 	plugins/authdata/greet_client
d738b9
diff --git a/src/include/Makefile.in b/src/include/Makefile.in
d738b9
index 0239338a1..6a3fa8242 100644
d738b9
--- a/src/include/Makefile.in
d738b9
+++ b/src/include/Makefile.in
d738b9
@@ -144,6 +144,7 @@ install-headers-unix install: krb5/krb5.h profile.h
d738b9
 	$(INSTALL_DATA) $(srcdir)/krb5/ccselect_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)ccselect_plugin.h
d738b9
 	$(INSTALL_DATA) $(srcdir)/krb5/clpreauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)clpreauth_plugin.h
d738b9
 	$(INSTALL_DATA) $(srcdir)/krb5/hostrealm_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)hostrealm_plugin.h
d738b9
+	$(INSTALL_DATA) $(srcdir)/krb5/kdcpolicy_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)kdcpolicy_plugin.h
d738b9
 	$(INSTALL_DATA) $(srcdir)/krb5/kdcpreauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)kdcpreauth_plugin.h
d738b9
 	$(INSTALL_DATA) $(srcdir)/krb5/localauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)localauth_plugin.h
d738b9
 	$(INSTALL_DATA) $(srcdir)/krb5/locate_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)locate_plugin.h
d738b9
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
d738b9
index ed9c7bf75..39ffb9568 100644
d738b9
--- a/src/include/k5-int.h
d738b9
+++ b/src/include/k5-int.h
d738b9
@@ -1157,7 +1157,9 @@ struct plugin_interface {
d738b9
 #define PLUGIN_INTERFACE_TLS         8
d738b9
 #define PLUGIN_INTERFACE_KDCAUTHDATA 9
d738b9
 #define PLUGIN_INTERFACE_CERTAUTH    10
d738b9
-#define PLUGIN_NUM_INTERFACES        11
d738b9
+#define PLUGIN_INTERFACE_KADM5_AUTH  11
d738b9
+#define PLUGIN_INTERFACE_KDCPOLICY   12
d738b9
+#define PLUGIN_NUM_INTERFACES        13
d738b9
 
d738b9
 /* Retrieve the plugin module of type interface_id and name modname,
d738b9
  * storing the result into module. */
d738b9
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
d738b9
index c75e264e0..2885408a2 100644
d738b9
--- a/src/include/k5-trace.h
d738b9
+++ b/src/include/k5-trace.h
d738b9
@@ -454,4 +454,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
d738b9
 #define TRACE_GET_CRED_VIA_TKT_EXT_RETURN(c, ret) \
d738b9
     TRACE(c, "Got cred; {kerr}", ret)
d738b9
 
d738b9
+#define TRACE_KDCPOLICY_VTINIT_FAIL(c, ret)                             \
d738b9
+    TRACE(c, "KDC policy module failed to init vtable: {kerr}", ret)
d738b9
+#define TRACE_KDCPOLICY_INIT_SKIP(c, name)                              \
d738b9
+    TRACE(c, "kadm5_auth module {str} declined to initialize", name)
d738b9
+
d738b9
 #endif /* K5_TRACE_H */
d738b9
diff --git a/src/include/krb5/kdcpolicy_plugin.h b/src/include/krb5/kdcpolicy_plugin.h
d738b9
new file mode 100644
d738b9
index 000000000..c7592c5db
d738b9
--- /dev/null
d738b9
+++ b/src/include/krb5/kdcpolicy_plugin.h
d738b9
@@ -0,0 +1,128 @@
d738b9
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
d738b9
+/* include/krb5/kdcpolicy_plugin.h - KDC policy plugin interface */
d738b9
+/*
d738b9
+ * Copyright (C) 2017 by Red Hat, Inc.
d738b9
+ * All rights reserved.
d738b9
+ *
d738b9
+ * Redistribution and use in source and binary forms, with or without
d738b9
+ * modification, are permitted provided that the following conditions
d738b9
+ * are met:
d738b9
+ *
d738b9
+ * * Redistributions of source code must retain the above copyright
d738b9
+ *   notice, this list of conditions and the following disclaimer.
d738b9
+ *
d738b9
+ * * Redistributions in binary form must reproduce the above copyright
d738b9
+ *   notice, this list of conditions and the following disclaimer in
d738b9
+ *   the documentation and/or other materials provided with the
d738b9
+ *   distribution.
d738b9
+ *
d738b9
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
d738b9
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
d738b9
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
d738b9
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
d738b9
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
d738b9
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
d738b9
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
d738b9
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
d738b9
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
d738b9
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
d738b9
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
d738b9
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
d738b9
+ */
d738b9
+
d738b9
+/*
d738b9
+ * Declarations for kdcpolicy plugin module implementors.
d738b9
+ *
d738b9
+ * The kdcpolicy pluggable interface currently has only one supported major
d738b9
+ * version, which is 1.  Major version 1 has a current minor version number of
d738b9
+ * 1.
d738b9
+ *
d738b9
+ * kdcpolicy plugin modules should define a function named
d738b9
+ * kdcpolicy_<modulename>_initvt, matching the signature:
d738b9
+ *
d738b9
+ *   krb5_error_code
d738b9
+ *   kdcpolicy_modname_initvt(krb5_context context, int maj_ver, int min_ver,
d738b9
+ *                            krb5_plugin_vtable vtable);
d738b9
+ *
d738b9
+ * The initvt function should:
d738b9
+ *
d738b9
+ * - Check that the supplied maj_ver number is supported by the module, or
d738b9
+ *   return KRB5_PLUGIN_VER_NOTSUPP if it is not.
d738b9
+ *
d738b9
+ * - Cast the vtable pointer as appropriate for maj_ver:
d738b9
+ *   maj_ver == 1: Cast to krb5_kdcpolicy_vtable
d738b9
+ *
d738b9
+ * - Initialize the methods of the vtable, stopping as appropriate for the
d738b9
+ *   supplied min_ver.  Optional methods may be left uninitialized.
d738b9
+ *
d738b9
+ * Memory for the vtable is allocated by the caller, not by the module.
d738b9
+ */
d738b9
+
d738b9
+#ifndef KRB5_POLICY_PLUGIN_H
d738b9
+#define KRB5_POLICY_PLUGIN_H
d738b9
+
d738b9
+#include <krb5/krb5.h>
d738b9
+
d738b9
+/* Abstract module datatype. */
d738b9
+typedef struct krb5_kdcpolicy_moddata_st *krb5_kdcpolicy_moddata;
d738b9
+
d738b9
+/* A module can optionally include kdb.h to inspect principal entries when
d738b9
+ * authorizing requests. */
d738b9
+struct _krb5_db_entry_new;
d738b9
+
d738b9
+/*
d738b9
+ * Optional: Initialize module data.  Return 0 on success,
d738b9
+ * KRB5_PLUGIN_NO_HANDLE if the module is inoperable (due to configuration, for
d738b9
+ * example), and any other error code to abort KDC startup.  Optionally set
d738b9
+ * *data_out to a module data object to be passed to future calls.
d738b9
+ */
d738b9
+typedef krb5_error_code
d738b9
+(*krb5_kdcpolicy_init_fn)(krb5_context context,
d738b9
+                          krb5_kdcpolicy_moddata *data_out);
d738b9
+
d738b9
+/* Optional: Clean up module data. */
d738b9
+typedef krb5_error_code
d738b9
+(*krb5_kdcpolicy_fini_fn)(krb5_context context,
d738b9
+                          krb5_kdcpolicy_moddata moddata);
d738b9
+
d738b9
+/*
d738b9
+ * Optional: return an error code and set status to an appropriate string
d738b9
+ * literal to deny an AS request; otherwise return 0.  lifetime_out, if set,
d738b9
+ * restricts the ticket lifetime.  renew_lifetime_out, if set, restricts the
d738b9
+ * ticket renewable lifetime.
d738b9
+ */
d738b9
+typedef krb5_error_code
d738b9
+(*krb5_kdcpolicy_check_as_fn)(krb5_context context,
d738b9
+                              krb5_kdcpolicy_moddata moddata,
d738b9
+                              const krb5_kdc_req *request,
d738b9
+                              const struct _krb5_db_entry_new *client,
d738b9
+                              const struct _krb5_db_entry_new *server,
d738b9
+                              const char *const *auth_indicators,
d738b9
+                              const char **status, krb5_deltat *lifetime_out,
d738b9
+                              krb5_deltat *renew_lifetime_out);
d738b9
+
d738b9
+/*
d738b9
+ * Optional: return an error code and set status to an appropriate string
d738b9
+ * literal to deny a TGS request; otherwise return 0.  lifetime_out, if set,
d738b9
+ * restricts the ticket lifetime.  renew_lifetime_out, if set, restricts the
d738b9
+ * ticket renewable lifetime.
d738b9
+ */
d738b9
+typedef krb5_error_code
d738b9
+(*krb5_kdcpolicy_check_tgs_fn)(krb5_context context,
d738b9
+                               krb5_kdcpolicy_moddata moddata,
d738b9
+                               const krb5_kdc_req *request,
d738b9
+                               const struct _krb5_db_entry_new *server,
d738b9
+                               const krb5_ticket *ticket,
d738b9
+                               const char *const *auth_indicators,
d738b9
+                               const char **status, krb5_deltat *lifetime_out,
d738b9
+                               krb5_deltat *renew_lifetime_out);
d738b9
+
d738b9
+typedef struct krb5_kdcpolicy_vtable_st {
d738b9
+    const char *name;
d738b9
+    krb5_kdcpolicy_init_fn init;
d738b9
+    krb5_kdcpolicy_fini_fn fini;
d738b9
+    krb5_kdcpolicy_check_as_fn check_as;
d738b9
+    krb5_kdcpolicy_check_tgs_fn check_tgs;
d738b9
+} *krb5_kdcpolicy_vtable;
d738b9
+
d738b9
+#endif /* KRB5_POLICY_PLUGIN_H */
d738b9
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
d738b9
index 59a39cd30..241b05b40 100644
d738b9
--- a/src/kdc/do_as_req.c
d738b9
+++ b/src/kdc/do_as_req.c
d738b9
@@ -207,6 +207,13 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
d738b9
 
d738b9
     state->ticket_reply.enc_part2 = &state->enc_tkt_reply;
d738b9
 
d738b9
+    errcode = check_kdcpolicy_as(kdc_context, state->request, state->client,
d738b9
+                                 state->server, state->auth_indicators,
d738b9
+                                 state->kdc_time, &state->enc_tkt_reply.times,
d738b9
+                                 &state->status);
d738b9
+    if (errcode)
d738b9
+        goto egress;
d738b9
+
d738b9
     /*
d738b9
      * Find the server key
d738b9
      */
d738b9
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
d738b9
index aacd2f20d..4c722a4a3 100644
d738b9
--- a/src/kdc/do_tgs_req.c
d738b9
+++ b/src/kdc/do_tgs_req.c
d738b9
@@ -518,6 +518,12 @@ process_tgs_req(struct server_handle *handle, krb5_data *pkt,
d738b9
     kdc_get_ticket_renewtime(kdc_active_realm, request, header_enc_tkt, client,
d738b9
                              server, &enc_tkt_reply);
d738b9
 
d738b9
+    errcode = check_kdcpolicy_tgs(kdc_context, request, server, header_ticket,
d738b9
+                                  auth_indicators, kdc_time,
d738b9
+                                  &enc_tkt_reply.times, &status);
d738b9
+    if (errcode)
d738b9
+        goto cleanup;
d738b9
+
d738b9
     /*
d738b9
      * Set authtime to be the same as header or evidence ticket's
d738b9
      */
d738b9
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
d738b9
index 778a629e5..8cbdf2c5b 100644
d738b9
--- a/src/kdc/kdc_util.c
d738b9
+++ b/src/kdc/kdc_util.c
d738b9
@@ -642,7 +642,6 @@ validate_as_request(kdc_realm_t *kdc_active_realm,
d738b9
                     krb5_db_entry server, krb5_timestamp kdc_time,
d738b9
                     const char **status, krb5_pa_data ***e_data)
d738b9
 {
d738b9
-    int errcode;
d738b9
     krb5_error_code ret;
d738b9
 
d738b9
     /*
d738b9
@@ -750,12 +749,6 @@ validate_as_request(kdc_realm_t *kdc_active_realm,
d738b9
     if (ret && ret != KRB5_PLUGIN_OP_NOTSUPP)
d738b9
         return errcode_to_protocol(ret);
d738b9
 
d738b9
-    /* Check against local policy. */
d738b9
-    errcode = against_local_policy_as(request, client, server,
d738b9
-                                      kdc_time, status, e_data);
d738b9
-    if (errcode)
d738b9
-        return errcode;
d738b9
-
d738b9
     return 0;
d738b9
 }
d738b9
 
d738b9
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
d738b9
index 672f94380..dcedfd538 100644
d738b9
--- a/src/kdc/kdc_util.h
d738b9
+++ b/src/kdc/kdc_util.h
d738b9
@@ -166,17 +166,6 @@ kdc_err(krb5_context call_context, errcode_t code, const char *fmt, ...)
d738b9
 #endif
d738b9
     ;
d738b9
 
d738b9
-/* policy.c */
d738b9
-int
d738b9
-against_local_policy_as (krb5_kdc_req *, krb5_db_entry,
d738b9
-                         krb5_db_entry, krb5_timestamp,
d738b9
-                         const char **, krb5_pa_data ***);
d738b9
-
d738b9
-int
d738b9
-against_local_policy_tgs (krb5_kdc_req *, krb5_db_entry,
d738b9
-                          krb5_ticket *, const char **,
d738b9
-                          krb5_pa_data ***);
d738b9
-
d738b9
 /* kdc_preauth.c */
d738b9
 krb5_boolean
d738b9
 enctype_requires_etype_info_2(krb5_enctype enctype);
d738b9
diff --git a/src/kdc/main.c b/src/kdc/main.c
d738b9
index a4dffb29a..ccac3a759 100644
d738b9
--- a/src/kdc/main.c
d738b9
+++ b/src/kdc/main.c
d738b9
@@ -31,6 +31,7 @@
d738b9
 #include "kdc_util.h"
d738b9
 #include "kdc_audit.h"
d738b9
 #include "extern.h"
d738b9
+#include "policy.h"
d738b9
 #include "kdc5_err.h"
d738b9
 #include "kdb_kt.h"
d738b9
 #include "net-server.h"
d738b9
@@ -986,6 +987,12 @@ int main(int argc, char **argv)
d738b9
 
d738b9
     load_preauth_plugins(&shandle, kcontext, ctx);
d738b9
     load_authdata_plugins(kcontext);
d738b9
+    retval = load_kdcpolicy_plugins(kcontext);
d738b9
+    if (retval) {
d738b9
+        kdc_err(kcontext, retval, _("while loading KDC policy plugin"));
d738b9
+        finish_realms();
d738b9
+        return 1;
d738b9
+    }
d738b9
 
d738b9
     retval = setup_sam();
d738b9
     if (retval) {
d738b9
@@ -1068,6 +1075,7 @@ int main(int argc, char **argv)
d738b9
     krb5_klog_syslog(LOG_INFO, _("shutting down"));
d738b9
     unload_preauth_plugins(kcontext);
d738b9
     unload_authdata_plugins(kcontext);
d738b9
+    unload_kdcpolicy_plugins(kcontext);
d738b9
     unload_audit_modules(kcontext);
d738b9
     krb5_klog_close(kcontext);
d738b9
     finish_realms();
d738b9
diff --git a/src/kdc/policy.c b/src/kdc/policy.c
d738b9
index 6cba4303f..e49644e06 100644
d738b9
--- a/src/kdc/policy.c
d738b9
+++ b/src/kdc/policy.c
d738b9
@@ -1,67 +1,246 @@
d738b9
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
d738b9
 /* kdc/policy.c - Policy decision routines for KDC */
d738b9
 /*
d738b9
- * Copyright 1990 by the Massachusetts Institute of Technology.
d738b9
+ * Copyright (C) 2017 by Red Hat, Inc.
d738b9
+ * All rights reserved.
d738b9
  *
d738b9
- * Export of this software from the United States of America may
d738b9
- *   require a specific license from the United States Government.
d738b9
- *   It is the responsibility of any person or organization contemplating
d738b9
- *   export to obtain such a license before exporting.
d738b9
+ * Redistribution and use in source and binary forms, with or without
d738b9
+ * modification, are permitted provided that the following conditions
d738b9
+ * are met:
d738b9
  *
d738b9
- * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
d738b9
- * distribute this software and its documentation for any purpose and
d738b9
- * without fee is hereby granted, provided that the above copyright
d738b9
- * notice appear in all copies and that both that copyright notice and
d738b9
- * this permission notice appear in supporting documentation, and that
d738b9
- * the name of M.I.T. not be used in advertising or publicity pertaining
d738b9
- * to distribution of the software without specific, written prior
d738b9
- * permission.  Furthermore if you modify this software you must label
d738b9
- * your software as modified software and not distribute it in such a
d738b9
- * fashion that it might be confused with the original M.I.T. software.
d738b9
- * M.I.T. makes no representations about the suitability of
d738b9
- * this software for any purpose.  It is provided "as is" without express
d738b9
- * or implied warranty.
d738b9
+ * * Redistributions of source code must retain the above copyright
d738b9
+ *   notice, this list of conditions and the following disclaimer.
d738b9
+ *
d738b9
+ * * Redistributions in binary form must reproduce the above copyright
d738b9
+ *   notice, this list of conditions and the following disclaimer in
d738b9
+ *   the documentation and/or other materials provided with the
d738b9
+ *   distribution.
d738b9
+ *
d738b9
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
d738b9
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
d738b9
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
d738b9
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
d738b9
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
d738b9
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
d738b9
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
d738b9
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
d738b9
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
d738b9
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
d738b9
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
d738b9
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
d738b9
  */
d738b9
 
d738b9
 #include "k5-int.h"
d738b9
 #include "kdc_util.h"
d738b9
 #include "extern.h"
d738b9
+#include "policy.h"
d738b9
+#include "adm_proto.h"
d738b9
+#include <krb5/kdcpolicy_plugin.h>
d738b9
+#include <syslog.h>
d738b9
 
d738b9
-int
d738b9
-against_local_policy_as(register krb5_kdc_req *request, krb5_db_entry client,
d738b9
-                        krb5_db_entry server, krb5_timestamp kdc_time,
d738b9
-                        const char **status, krb5_pa_data ***e_data)
d738b9
+typedef struct kdcpolicy_handle_st {
d738b9
+    struct krb5_kdcpolicy_vtable_st vt;
d738b9
+    krb5_kdcpolicy_moddata moddata;
d738b9
+} *kdcpolicy_handle;
d738b9
+
d738b9
+static kdcpolicy_handle *handles;
d738b9
+
d738b9
+static void
d738b9
+free_indicators(char **ais)
d738b9
 {
d738b9
-#if 0
d738b9
-    /* An AS request must include the addresses field */
d738b9
-    if (request->addresses == 0) {
d738b9
-        *status = "NO ADDRESS";
d738b9
-        return KRB5KDC_ERR_POLICY;
d738b9
-    }
d738b9
-#endif
d738b9
+    size_t i;
d738b9
 
d738b9
-    return 0;                   /* not against policy */
d738b9
+    if (ais == NULL)
d738b9
+        return;
d738b9
+    for (i = 0; ais[i] != NULL; i++)
d738b9
+        free(ais[i]);
d738b9
+    free(ais);
d738b9
+}
d738b9
+
d738b9
+/* Convert inds to a null-terminated list of C strings. */
d738b9
+static krb5_error_code
d738b9
+authind_strings(krb5_data *const *inds, char ***strs_out)
d738b9
+{
d738b9
+    krb5_error_code ret;
d738b9
+    char **list = NULL;
d738b9
+    size_t i, count;
d738b9
+
d738b9
+    *strs_out = NULL;
d738b9
+
d738b9
+    for (count = 0; inds != NULL && inds[count] != NULL; count++);
d738b9
+    list = k5calloc(count + 1, sizeof(*list), &ret;;
d738b9
+    if (list == NULL)
d738b9
+        goto error;
d738b9
+
d738b9
+    for (i = 0; i < count; i++) {
d738b9
+        list[i] = k5memdup0(inds[i]->data, inds[i]->length, &ret;;
d738b9
+        if (list[i] == NULL)
d738b9
+            goto error;
d738b9
+    }
d738b9
+
d738b9
+    *strs_out = list;
d738b9
+    return 0;
d738b9
+
d738b9
+error:
d738b9
+    free_indicators(list);
d738b9
+    return ret;
d738b9
+}
d738b9
+
d738b9
+/* Constrain times->endtime to life and times->renew_till to rlife, relative to
d738b9
+ * now. */
d738b9
+static void
d738b9
+update_ticket_times(krb5_ticket_times *times, krb5_timestamp now,
d738b9
+                    krb5_deltat life, krb5_deltat rlife)
d738b9
+{
d738b9
+    if (life)
d738b9
+        times->endtime = ts_min(ts_incr(now, life), times->endtime);
d738b9
+    if (rlife)
d738b9
+        times->renew_till = ts_min(ts_incr(now, rlife), times->renew_till);
d738b9
+}
d738b9
+
d738b9
+/* Check an AS request against kdcpolicy modules, updating times with any
d738b9
+ * module endtime constraints.  Set an appropriate status string on error. */
d738b9
+krb5_error_code
d738b9
+check_kdcpolicy_as(krb5_context context, const krb5_kdc_req *request,
d738b9
+                   const krb5_db_entry *client, const krb5_db_entry *server,
d738b9
+                   krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
d738b9
+                   krb5_ticket_times *times, const char **status)
d738b9
+{
d738b9
+    krb5_deltat life, rlife;
d738b9
+    krb5_error_code ret;
d738b9
+    kdcpolicy_handle *hp, h;
d738b9
+    char **ais = NULL;
d738b9
+
d738b9
+    *status = NULL;
d738b9
+
d738b9
+    ret = authind_strings(auth_indicators, &ais);
d738b9
+    if (ret)
d738b9
+        goto done;
d738b9
+
d738b9
+    for (hp = handles; *hp != NULL; hp++) {
d738b9
+        h = *hp;
d738b9
+        if (h->vt.check_as == NULL)
d738b9
+            continue;
d738b9
+
d738b9
+        ret = h->vt.check_as(context, h->moddata, request, client, server,
d738b9
+                             (const char **)ais, status, &life, &rlife);
d738b9
+        if (ret)
d738b9
+            goto done;
d738b9
+
d738b9
+        update_ticket_times(times, kdc_time, life, rlife);
d738b9
+    }
d738b9
+
d738b9
+done:
d738b9
+    free_indicators(ais);
d738b9
+    return ret;
d738b9
 }
d738b9
 
d738b9
 /*
d738b9
- * This is where local policy restrictions for the TGS should placed.
d738b9
+ * Check the TGS request against the local TGS policy.  Accepts an
d738b9
+ * authentication indicator for the module policy decisions.  Returns 0 and a
d738b9
+ * NULL status string on success.
d738b9
  */
d738b9
 krb5_error_code
d738b9
-against_local_policy_tgs(register krb5_kdc_req *request, krb5_db_entry server,
d738b9
-                         krb5_ticket *ticket, const char **status,
d738b9
-                         krb5_pa_data ***e_data)
d738b9
+check_kdcpolicy_tgs(krb5_context context, const krb5_kdc_req *request,
d738b9
+                    const krb5_db_entry *server, const krb5_ticket *ticket,
d738b9
+                    krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
d738b9
+                    krb5_ticket_times *times, const char **status)
d738b9
 {
d738b9
-#if 0
d738b9
-    /*
d738b9
-     * For example, if your site wants to disallow ticket forwarding,
d738b9
-     * you might do something like this:
d738b9
-     */
d738b9
+    krb5_deltat life, rlife;
d738b9
+    krb5_error_code ret;
d738b9
+    kdcpolicy_handle *hp, h;
d738b9
+    char **ais = NULL;
d738b9
 
d738b9
-    if (isflagset(request->kdc_options, KDC_OPT_FORWARDED)) {
d738b9
-        *status = "FORWARD POLICY";
d738b9
-        return KRB5KDC_ERR_POLICY;
d738b9
+    *status = NULL;
d738b9
+
d738b9
+    ret = authind_strings(auth_indicators, &ais);
d738b9
+    if (ret)
d738b9
+        goto done;
d738b9
+
d738b9
+    for (hp = handles; *hp != NULL; hp++) {
d738b9
+        h = *hp;
d738b9
+        if (h->vt.check_tgs == NULL)
d738b9
+            continue;
d738b9
+
d738b9
+        ret = h->vt.check_tgs(context, h->moddata, request, server, ticket,
d738b9
+                              (const char **)ais, status, &life, &rlife);
d738b9
+        if (ret)
d738b9
+            goto done;
d738b9
+
d738b9
+        update_ticket_times(times, kdc_time, life, rlife);
d738b9
     }
d738b9
-#endif
d738b9
 
d738b9
-    return 0;                           /* not against policy */
d738b9
+done:
d738b9
+    free_indicators(ais);
d738b9
+    return ret;
d738b9
+}
d738b9
+
d738b9
+void
d738b9
+unload_kdcpolicy_plugins(krb5_context context)
d738b9
+{
d738b9
+    kdcpolicy_handle *hp, h;
d738b9
+
d738b9
+    for (hp = handles; *hp != NULL; hp++) {
d738b9
+        h = *hp;
d738b9
+        if (h->vt.fini != NULL)
d738b9
+            h->vt.fini(context, h->moddata);
d738b9
+        free(h);
d738b9
+    }
d738b9
+    free(handles);
d738b9
+    handles = NULL;
d738b9
+}
d738b9
+
d738b9
+krb5_error_code
d738b9
+load_kdcpolicy_plugins(krb5_context context)
d738b9
+{
d738b9
+    krb5_error_code ret;
d738b9
+    krb5_plugin_initvt_fn *modules = NULL, *mod;
d738b9
+    kdcpolicy_handle h;
d738b9
+    size_t count;
d738b9
+
d738b9
+    ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_KDCPOLICY, &modules);
d738b9
+    if (ret)
d738b9
+        goto cleanup;
d738b9
+
d738b9
+    for (count = 0; modules[count] != NULL; count++);
d738b9
+    handles = k5calloc(count + 1, sizeof(*handles), &ret;;
d738b9
+    if (handles == NULL)
d738b9
+        goto cleanup;
d738b9
+
d738b9
+    count = 0;
d738b9
+    for (mod = modules; *mod != NULL; mod++) {
d738b9
+        h = k5calloc(1, sizeof(*h), &ret;;
d738b9
+        if (h == NULL)
d738b9
+            goto cleanup;
d738b9
+
d738b9
+        ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt);
d738b9
+        if (ret) {              /* Version mismatch. */
d738b9
+            TRACE_KDCPOLICY_VTINIT_FAIL(context, ret);
d738b9
+            free(h);
d738b9
+            continue;
d738b9
+        }
d738b9
+        if (h->vt.init != NULL) {
d738b9
+            ret = h->vt.init(context, &h->moddata);
d738b9
+            if (ret == KRB5_PLUGIN_NO_HANDLE) {
d738b9
+                TRACE_KADM5_AUTH_INIT_SKIP(context, h->vt.name);
d738b9
+                free(h);
d738b9
+                continue;
d738b9
+            }
d738b9
+            if (ret) {
d738b9
+                kdc_err(context, ret, _("while loading policy module %s"),
d738b9
+                        h->vt.name);
d738b9
+                free(h);
d738b9
+                goto cleanup;
d738b9
+            }
d738b9
+        }
d738b9
+        handles[count++] = h;
d738b9
+    }
d738b9
+
d738b9
+    ret = 0;
d738b9
+
d738b9
+cleanup:
d738b9
+    if (ret)
d738b9
+        unload_kdcpolicy_plugins(context);
d738b9
+    k5_plugin_free_modules(context, modules);
d738b9
+    return ret;
d738b9
 }
d738b9
diff --git a/src/kdc/policy.h b/src/kdc/policy.h
d738b9
index 6b000dc90..2a57b0a01 100644
d738b9
--- a/src/kdc/policy.h
d738b9
+++ b/src/kdc/policy.h
d738b9
@@ -26,11 +26,22 @@
d738b9
 #ifndef __KRB5_KDC_POLICY__
d738b9
 #define __KRB5_KDC_POLICY__
d738b9
 
d738b9
-extern int against_postdate_policy (krb5_timestamp);
d738b9
+krb5_error_code
d738b9
+load_kdcpolicy_plugins(krb5_context context);
d738b9
 
d738b9
-extern int against_flag_policy_as (const krb5_kdc_req *);
d738b9
+void
d738b9
+unload_kdcpolicy_plugins(krb5_context context);
d738b9
 
d738b9
-extern int against_flag_policy_tgs (const krb5_kdc_req *,
d738b9
-                                    const krb5_ticket *);
d738b9
+krb5_error_code
d738b9
+check_kdcpolicy_as(krb5_context context, const krb5_kdc_req *request,
d738b9
+                   const krb5_db_entry *client, const krb5_db_entry *server,
d738b9
+                   krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
d738b9
+                   krb5_ticket_times *times, const char **status);
d738b9
+
d738b9
+krb5_error_code
d738b9
+check_kdcpolicy_tgs(krb5_context context, const krb5_kdc_req *request,
d738b9
+                    const krb5_db_entry *server, const krb5_ticket *ticket,
d738b9
+                    krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
d738b9
+                    krb5_ticket_times *times, const char **status);
d738b9
 
d738b9
 #endif /* __KRB5_KDC_POLICY__ */
d738b9
diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c
d738b9
index d0f25d1b7..33cfbcd81 100644
d738b9
--- a/src/kdc/tgs_policy.c
d738b9
+++ b/src/kdc/tgs_policy.c
d738b9
@@ -375,11 +375,5 @@ validate_tgs_request(kdc_realm_t *kdc_active_realm,
d738b9
     if (ret && ret != KRB5_PLUGIN_OP_NOTSUPP)
d738b9
         return errcode_to_protocol(ret);
d738b9
 
d738b9
-    /* Check local policy. */
d738b9
-    errcode = against_local_policy_tgs(request, server, ticket,
d738b9
-                                       status, e_data);
d738b9
-    if (errcode)
d738b9
-        return errcode;
d738b9
-
d738b9
     return 0;
d738b9
 }
d738b9
diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c
d738b9
index 17dd6bd30..31aaf661d 100644
d738b9
--- a/src/lib/krb5/krb/plugin.c
d738b9
+++ b/src/lib/krb5/krb/plugin.c
d738b9
@@ -58,7 +58,9 @@ const char *interface_names[] = {
d738b9
     "audit",
d738b9
     "tls",
d738b9
     "kdcauthdata",
d738b9
-    "certauth"
d738b9
+    "certauth",
d738b9
+    "kadm5_auth",
d738b9
+    "kdcpolicy",
d738b9
 };
d738b9
 
d738b9
 /* Return the context's interface structure for id, or NULL if invalid. */
d738b9
diff --git a/src/plugins/kdcpolicy/test/Makefile.in b/src/plugins/kdcpolicy/test/Makefile.in
d738b9
new file mode 100644
d738b9
index 000000000..b81f1a7ce
d738b9
--- /dev/null
d738b9
+++ b/src/plugins/kdcpolicy/test/Makefile.in
d738b9
@@ -0,0 +1,20 @@
d738b9
+mydir=plugins$(S)policy$(S)test
d738b9
+BUILDTOP=$(REL)..$(S)..$(S)..
d738b9
+
d738b9
+LIBBASE=policy_test
d738b9
+LIBMAJOR=0
d738b9
+LIBMINOR=0
d738b9
+RELDIR=../plugins/kdcpolicy/test
d738b9
+SHLIB_EXPDEPS=$(KRB5_BASE_DEPLIBS)
d738b9
+SHLIB_EXPLIBS=$(KRB5_BASE_LIBS)
d738b9
+
d738b9
+STLIBOBJS=main.o
d738b9
+
d738b9
+SRCS=$(srcdir)/main.c
d738b9
+
d738b9
+all-unix: all-libs
d738b9
+install-unix:
d738b9
+clean-unix:: clean-libs clean-libobjs
d738b9
+
d738b9
+@libnover_frag@
d738b9
+@libobj_frag@
d738b9
diff --git a/src/plugins/kdcpolicy/test/deps b/src/plugins/kdcpolicy/test/deps
d738b9
new file mode 100644
d738b9
index 000000000..e69de29bb
d738b9
diff --git a/src/plugins/kdcpolicy/test/main.c b/src/plugins/kdcpolicy/test/main.c
d738b9
new file mode 100644
d738b9
index 000000000..eb8fde053
d738b9
--- /dev/null
d738b9
+++ b/src/plugins/kdcpolicy/test/main.c
d738b9
@@ -0,0 +1,111 @@
d738b9
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
d738b9
+/* include/krb5/kdcpolicy_plugin.h - KDC policy plugin interface */
d738b9
+/*
d738b9
+ * Copyright (C) 2017 by Red Hat, Inc.
d738b9
+ * All rights reserved.
d738b9
+ *
d738b9
+ * Redistribution and use in source and binary forms, with or without
d738b9
+ * modification, are permitted provided that the following conditions
d738b9
+ * are met:
d738b9
+ *
d738b9
+ * * Redistributions of source code must retain the above copyright
d738b9
+ *   notice, this list of conditions and the following disclaimer.
d738b9
+ *
d738b9
+ * * Redistributions in binary form must reproduce the above copyright
d738b9
+ *   notice, this list of conditions and the following disclaimer in
d738b9
+ *   the documentation and/or other materials provided with the
d738b9
+ *   distribution.
d738b9
+ *
d738b9
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
d738b9
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
d738b9
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
d738b9
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
d738b9
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
d738b9
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
d738b9
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
d738b9
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
d738b9
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
d738b9
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
d738b9
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
d738b9
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
d738b9
+ */
d738b9
+
d738b9
+#include "k5-int.h"
d738b9
+#include "kdb.h"
d738b9
+#include <krb5/kdcpolicy_plugin.h>
d738b9
+
d738b9
+static krb5_error_code
d738b9
+output_from_indicator(const char *const *auth_indicators,
d738b9
+                      krb5_deltat *lifetime_out,
d738b9
+                      krb5_deltat *renew_lifetime_out,
d738b9
+                      const char **status)
d738b9
+{
d738b9
+    if (auth_indicators[0] == NULL) {
d738b9
+        *status = NULL;
d738b9
+        return 0;
d738b9
+    }
d738b9
+
d738b9
+    if (strcmp(auth_indicators[0], "ONE_HOUR") == 0) {
d738b9
+        *lifetime_out = 3600;
d738b9
+        *renew_lifetime_out = *lifetime_out * 2;
d738b9
+        return 0;
d738b9
+    } else if (strcmp(auth_indicators[0], "SEVEN_HOURS") == 0) {
d738b9
+        *lifetime_out = 7 * 3600;
d738b9
+        *renew_lifetime_out = *lifetime_out * 2;
d738b9
+        return 0;
d738b9
+    }
d738b9
+
d738b9
+    *status = "LOCAL_POLICY";
d738b9
+    return KRB5KDC_ERR_POLICY;
d738b9
+}
d738b9
+
d738b9
+static krb5_error_code
d738b9
+test_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
d738b9
+              const krb5_kdc_req *request, const krb5_db_entry *client,
d738b9
+              const krb5_db_entry *server, const char *const *auth_indicators,
d738b9
+              const char **status, krb5_deltat *lifetime_out,
d738b9
+              krb5_deltat *renew_lifetime_out)
d738b9
+{
d738b9
+    if (request->client != NULL && request->client->length >= 1 &&
d738b9
+        data_eq_string(request->client->data[0], "fail")) {
d738b9
+        *status = "LOCAL_POLICY";
d738b9
+        return KRB5KDC_ERR_POLICY;
d738b9
+    }
d738b9
+    return output_from_indicator(auth_indicators, lifetime_out,
d738b9
+                                 renew_lifetime_out, status);
d738b9
+}
d738b9
+
d738b9
+static krb5_error_code
d738b9
+test_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
d738b9
+               const krb5_kdc_req *request, const krb5_db_entry *server,
d738b9
+               const krb5_ticket *ticket, const char *const *auth_indicators,
d738b9
+               const char **status, krb5_deltat *lifetime_out,
d738b9
+               krb5_deltat *renew_lifetime_out)
d738b9
+{
d738b9
+    if (request->server != NULL && request->server->length >= 1 &&
d738b9
+        data_eq_string(request->server->data[0], "fail")) {
d738b9
+        *status = "LOCAL_POLICY";
d738b9
+        return KRB5KDC_ERR_POLICY;
d738b9
+    }
d738b9
+    return output_from_indicator(auth_indicators, lifetime_out,
d738b9
+                                 renew_lifetime_out, status);
d738b9
+}
d738b9
+
d738b9
+krb5_error_code
d738b9
+kdcpolicy_test_initvt(krb5_context context, int maj_ver, int min_ver,
d738b9
+                      krb5_plugin_vtable vtable);
d738b9
+krb5_error_code
d738b9
+kdcpolicy_test_initvt(krb5_context context, int maj_ver, int min_ver,
d738b9
+                      krb5_plugin_vtable vtable)
d738b9
+{
d738b9
+    krb5_kdcpolicy_vtable vt;
d738b9
+
d738b9
+    if (maj_ver != 1)
d738b9
+        return KRB5_PLUGIN_VER_NOTSUPP;
d738b9
+
d738b9
+    vt = (krb5_kdcpolicy_vtable)vtable;
d738b9
+    vt->name = "test";
d738b9
+    vt->check_as = test_check_as;
d738b9
+    vt->check_tgs = test_check_tgs;
d738b9
+    return 0;
d738b9
+}
d738b9
diff --git a/src/plugins/kdcpolicy/test/policy_test.exports b/src/plugins/kdcpolicy/test/policy_test.exports
d738b9
new file mode 100644
d738b9
index 000000000..9682ec74f
d738b9
--- /dev/null
d738b9
+++ b/src/plugins/kdcpolicy/test/policy_test.exports
d738b9
@@ -0,0 +1 @@
d738b9
+kdcpolicy_test_initvt
d738b9
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
d738b9
index 2b3112537..a2093108b 100644
d738b9
--- a/src/tests/Makefile.in
d738b9
+++ b/src/tests/Makefile.in
d738b9
@@ -169,6 +169,7 @@ check-pytests: localauth plugorder rdreq responder s2p s4u2proxy unlockiter
d738b9
 	$(RUNPYTEST) $(srcdir)/t_tabdump.py $(PYTESTFLAGS)
d738b9
 	$(RUNPYTEST) $(srcdir)/t_certauth.py $(PYTESTFLAGS)
d738b9
 	$(RUNPYTEST) $(srcdir)/t_y2038.py $(PYTESTFLAGS)
d738b9
+	$(RUNPYTEST) $(srcdir)/t_kdcpolicy.py $(PYTESTFLAGS)
d738b9
 
d738b9
 clean:
d738b9
 	$(RM) adata etinfo forward gcred hist hooks hrealm icred kdbtest
d738b9
diff --git a/src/tests/t_kdcpolicy.py b/src/tests/t_kdcpolicy.py
d738b9
new file mode 100644
d738b9
index 000000000..6a745b959
d738b9
--- /dev/null
d738b9
+++ b/src/tests/t_kdcpolicy.py
d738b9
@@ -0,0 +1,57 @@
d738b9
+#!/usr/bin/python
d738b9
+from k5test import *
d738b9
+from datetime import datetime
d738b9
+import re
d738b9
+
d738b9
+testpreauth = os.path.join(buildtop, 'plugins', 'preauth', 'test', 'test.so')
d738b9
+testpolicy = os.path.join(buildtop, 'plugins', 'kdcpolicy', 'test',
d738b9
+                          'policy_test.so')
d738b9
+krb5_conf = {'plugins': {'kdcpreauth': {'module': 'test:' + testpreauth},
d738b9
+                         'clpreauth': {'module': 'test:' + testpreauth},
d738b9
+                         'kdcpolicy': {'module': 'test:' + testpolicy}}}
d738b9
+kdc_conf = {'realms': {'$realm': {'default_principal_flags': '+preauth',
d738b9
+                                  'max_renewable_life': '1d'}}}
d738b9
+realm = K5Realm(krb5_conf=krb5_conf, kdc_conf=kdc_conf)
d738b9
+
d738b9
+realm.run([kadminl, 'addprinc', '-pw', password('fail'), 'fail'])
d738b9
+
d738b9
+def verify_time(out, target_time):
d738b9
+    times = re.findall(r'\d\d/\d\d/\d\d \d\d:\d\d:\d\d', out)
d738b9
+    times = [datetime.strptime(t, '%m/%d/%y %H:%M:%S') for t in times]
d738b9
+    while len(times) > 0:
d738b9
+        starttime = times.pop(0)
d738b9
+        endtime = times.pop(0)
d738b9
+        renewtime = times.pop(0)
d738b9
+
d738b9
+        if str(endtime - starttime) != target_time:
d738b9
+            fail('unexpected lifetime value')
d738b9
+        if str(renewtime - endtime) != target_time:
d738b9
+            fail('unexpected renewable value')
d738b9
+
d738b9
+rflags = ['-r', '1d', '-l', '12h']
d738b9
+
d738b9
+# Test AS+TGS success path.
d738b9
+realm.kinit(realm.user_princ, password('user'),
d738b9
+            rflags + ['-X', 'indicators=SEVEN_HOURS'])
d738b9
+realm.run([kvno, realm.host_princ])
d738b9
+realm.run(['./adata', realm.host_princ], expected_msg='+97: [SEVEN_HOURS]')
d738b9
+out = realm.run([klist, realm.ccache, '-e'])
d738b9
+verify_time(out, '7:00:00')
d738b9
+
d738b9
+# Test AS+TGS success path with different values.
d738b9
+realm.kinit(realm.user_princ, password('user'),
d738b9
+            rflags + ['-X', 'indicators=ONE_HOUR'])
d738b9
+realm.run([kvno, realm.host_princ])
d738b9
+realm.run(['./adata', realm.host_princ], expected_msg='+97: [ONE_HOUR]')
d738b9
+out = realm.run([klist, realm.ccache, '-e'])
d738b9
+verify_time(out, '1:00:00')
d738b9
+
d738b9
+# Test TGS failure path (using previous creds).
d738b9
+realm.run([kvno, 'fail@%s' % realm.realm], expected_code=1,
d738b9
+          expected_msg='KDC policy rejects request')
d738b9
+
d738b9
+# Test AS failure path.
d738b9
+realm.kinit('fail@%s' % realm.realm, password('fail'),
d738b9
+            expected_code=1, expected_msg='KDC policy rejects request')
d738b9
+
d738b9
+success('kdcpolicy tests')