krb5 commit: Add KDC policy pluggable interface

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/d0969f6a8170344031ef58fd2a161190f1edfb96
commit d0969f6a8170344031ef58fd2a161190f1edfb96
Author: Robbie Harwood <[email protected]>
Date:   Tue Jun 27 17:15:39 2017 -0400

    Add KDC policy pluggable interface
    
    Add the header include/krb5/kdcpolicy_plugin.h, defining a pluggable
    interface for modules to deny AS and TGS requests and set maximum
    ticket lifetimes.  This interface replaces the policy.c stub functions.
    
    Add check_kdcpolicy_as() and check_kdcpolicy_tgs() as entry functions.
    Call them after auth indicators and ticket lifetimes have been
    determined.
    
    Add a test module and a test script with basic kdcpolicy tests.  Add
    plugin interface documentation in doc/plugindev/policy.rst.
    
    Also authored by Matt Rogers <[email protected]>.
    
    ticket: 8606 (new)

 doc/plugindev/index.rst                        |    1 +
 doc/plugindev/kdcpolicy.rst                    |   24 ++
 src/Makefile.in                                |    1 +
 src/configure.in                               |    1 +
 src/include/Makefile.in                        |    1 +
 src/include/k5-int.h                           |    3 +-
 src/include/k5-trace.h                         |    5 +
 src/include/krb5/kdcpolicy_plugin.h            |  128 +++++++++++
 src/kdc/do_as_req.c                            |    7 +
 src/kdc/do_tgs_req.c                           |    6 +
 src/kdc/kdc_util.c                             |    7 -
 src/kdc/kdc_util.h                             |   11 -
 src/kdc/main.c                                 |    8 +
 src/kdc/policy.c                               |  267 ++++++++++++++++++++----
 src/kdc/policy.h                               |   19 ++-
 src/kdc/tgs_policy.c                           |    6 -
 src/lib/krb5/krb/plugin.c                      |    3 +-
 src/plugins/kdcpolicy/test/Makefile.in         |   20 ++
 src/plugins/kdcpolicy/test/main.c              |  111 ++++++++++
 src/plugins/kdcpolicy/test/policy_test.exports |    1 +
 src/tests/Makefile.in                          |    1 +
 src/tests/t_kdcpolicy.py                       |   57 +++++
 22 files changed, 614 insertions(+), 74 deletions(-)

diff --git a/doc/plugindev/index.rst b/doc/plugindev/index.rst
index 9c27461..5e78346 100644
--- a/doc/plugindev/index.rst
+++ b/doc/plugindev/index.rst
@@ -33,5 +33,6 @@ Contents
    gssapi.rst
    internal.rst
    certauth.rst
+   kdcpolicy.rst
 
 .. TODO: GSSAPI mechanism plugins
diff --git a/doc/plugindev/kdcpolicy.rst b/doc/plugindev/kdcpolicy.rst
new file mode 100644
index 0000000..74f21f0
--- /dev/null
+++ b/doc/plugindev/kdcpolicy.rst
@@ -0,0 +1,24 @@
+.. _kdcpolicy_plugin:
+
+KDC policy interface (kdcpolicy)
+================================
+
+The kdcpolicy interface was first introduced in release 1.16.  It
+allows modules to veto otherwise valid AS and TGS requests or restrict
+the lifetime and renew time of the resulting ticket.  For a detailed
+description of the kdcpolicy interface, see the header file
+``<krb5/kdcpolicy_plugin.h>``.
+
+The optional **check_as** and **check_tgs** functions allow the module
+to perform access control.  Additionally, a module can create and
+destroy module data with the **init** and **fini** methods.  Module
+data objects last for the lifetime of the KDC process, and are
+provided to all other methods.  The data has the type
+krb5_kdcpolicy_moddata, which should be cast to the appropriate
+internal type.
+
+kdcpolicy modules can optionally inspect principal entries.  To do
+this, the module must also include ``<kdb.h>`` to gain access to the
+principal entry structure definition.  As the KDB interface is
+explicitly not as stable as other public interfaces, modules which do
+this may not retain compatibility across releases.
diff --git a/src/Makefile.in b/src/Makefile.in
index 7bb6b8a..ac9a2a0 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -22,6 +22,7 @@ SUBDIRS=util include lib \
 	plugins/kdb/db2 \
 	@ldap_plugin_dir@ \
 	plugins/kdb/test \
+	plugins/kdcpolicy/test \
 	plugins/preauth/otp \
 	plugins/preauth/pkinit \
 	plugins/preauth/test \
diff --git a/src/configure.in b/src/configure.in
index fd64604..10f45eb 100644
--- a/src/configure.in
+++ b/src/configure.in
@@ -1434,6 +1434,7 @@ dnl	ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test
 	plugins/kdb/db2/libdb2/recno
 	plugins/kdb/db2/libdb2/test
 	plugins/kdb/test
+	plugins/kdcpolicy/test
 	plugins/preauth/otp
 	plugins/preauth/test
 	plugins/authdata/greet_client
diff --git a/src/include/Makefile.in b/src/include/Makefile.in
index d5a50a3..cfa5794 100644
--- a/src/include/Makefile.in
+++ b/src/include/Makefile.in
@@ -144,6 +144,7 @@ install-headers-unix install: krb5/krb5.h profile.h
 	$(INSTALL_DATA) $(srcdir)/krb5/ccselect_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)ccselect_plugin.h
 	$(INSTALL_DATA) $(srcdir)/krb5/clpreauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)clpreauth_plugin.h
 	$(INSTALL_DATA) $(srcdir)/krb5/hostrealm_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)hostrealm_plugin.h
+	$(INSTALL_DATA) $(srcdir)/krb5/kdcpolicy_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)kdcpolicy_plugin.h
 	$(INSTALL_DATA) $(srcdir)/krb5/kdcpreauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)kdcpreauth_plugin.h
 	$(INSTALL_DATA) $(srcdir)/krb5/localauth_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)localauth_plugin.h
 	$(INSTALL_DATA) $(srcdir)/krb5/locate_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)locate_plugin.h
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 483c273..023e4bb 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -1158,7 +1158,8 @@ struct plugin_interface {
 #define PLUGIN_INTERFACE_KDCAUTHDATA 9
 #define PLUGIN_INTERFACE_CERTAUTH    10
 #define PLUGIN_INTERFACE_KADM5_AUTH  11
-#define PLUGIN_NUM_INTERFACES        12
+#define PLUGIN_INTERFACE_KDCPOLICY   12
+#define PLUGIN_NUM_INTERFACES        13
 
 /* Retrieve the plugin module of type interface_id and name modname,
  * storing the result into module. */
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
index 4b39ae1..390a8b7 100644
--- a/src/include/k5-trace.h
+++ b/src/include/k5-trace.h
@@ -492,4 +492,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 #define TRACE_GET_CRED_VIA_TKT_EXT_RETURN(c, ret) \
     TRACE(c, "Got cred; {kerr}", ret)
 
+#define TRACE_KDCPOLICY_VTINIT_FAIL(c, ret)                             \
+    TRACE(c, "KDC policy module failed to init vtable: {kerr}", ret)
+#define TRACE_KDCPOLICY_INIT_SKIP(c, name)                              \
+    TRACE(c, "kadm5_auth module {str} declined to initialize", name)
+
 #endif /* K5_TRACE_H */
diff --git a/src/include/krb5/kdcpolicy_plugin.h b/src/include/krb5/kdcpolicy_plugin.h
new file mode 100644
index 0000000..c7592c5
--- /dev/null
+++ b/src/include/krb5/kdcpolicy_plugin.h
@@ -0,0 +1,128 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* include/krb5/kdcpolicy_plugin.h - KDC policy plugin interface */
+/*
+ * Copyright (C) 2017 by Red Hat, 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.
+ */
+
+/*
+ * Declarations for kdcpolicy plugin module implementors.
+ *
+ * The kdcpolicy pluggable interface currently has only one supported major
+ * version, which is 1.  Major version 1 has a current minor version number of
+ * 1.
+ *
+ * kdcpolicy plugin modules should define a function named
+ * kdcpolicy_<modulename>_initvt, matching the signature:
+ *
+ *   krb5_error_code
+ *   kdcpolicy_modname_initvt(krb5_context context, int maj_ver, int min_ver,
+ *                            krb5_plugin_vtable vtable);
+ *
+ * The initvt function should:
+ *
+ * - Check that the supplied maj_ver number is supported by the module, or
+ *   return KRB5_PLUGIN_VER_NOTSUPP if it is not.
+ *
+ * - Cast the vtable pointer as appropriate for maj_ver:
+ *   maj_ver == 1: Cast to krb5_kdcpolicy_vtable
+ *
+ * - Initialize the methods of the vtable, stopping as appropriate for the
+ *   supplied min_ver.  Optional methods may be left uninitialized.
+ *
+ * Memory for the vtable is allocated by the caller, not by the module.
+ */
+
+#ifndef KRB5_POLICY_PLUGIN_H
+#define KRB5_POLICY_PLUGIN_H
+
+#include <krb5/krb5.h>
+
+/* Abstract module datatype. */
+typedef struct krb5_kdcpolicy_moddata_st *krb5_kdcpolicy_moddata;
+
+/* A module can optionally include kdb.h to inspect principal entries when
+ * authorizing requests. */
+struct _krb5_db_entry_new;
+
+/*
+ * Optional: Initialize module data.  Return 0 on success,
+ * KRB5_PLUGIN_NO_HANDLE if the module is inoperable (due to configuration, for
+ * example), and any other error code to abort KDC startup.  Optionally set
+ * *data_out to a module data object to be passed to future calls.
+ */
+typedef krb5_error_code
+(*krb5_kdcpolicy_init_fn)(krb5_context context,
+                          krb5_kdcpolicy_moddata *data_out);
+
+/* Optional: Clean up module data. */
+typedef krb5_error_code
+(*krb5_kdcpolicy_fini_fn)(krb5_context context,
+                          krb5_kdcpolicy_moddata moddata);
+
+/*
+ * Optional: return an error code and set status to an appropriate string
+ * literal to deny an AS request; otherwise return 0.  lifetime_out, if set,
+ * restricts the ticket lifetime.  renew_lifetime_out, if set, restricts the
+ * ticket renewable lifetime.
+ */
+typedef krb5_error_code
+(*krb5_kdcpolicy_check_as_fn)(krb5_context context,
+                              krb5_kdcpolicy_moddata moddata,
+                              const krb5_kdc_req *request,
+                              const struct _krb5_db_entry_new *client,
+                              const struct _krb5_db_entry_new *server,
+                              const char *const *auth_indicators,
+                              const char **status, krb5_deltat *lifetime_out,
+                              krb5_deltat *renew_lifetime_out);
+
+/*
+ * Optional: return an error code and set status to an appropriate string
+ * literal to deny a TGS request; otherwise return 0.  lifetime_out, if set,
+ * restricts the ticket lifetime.  renew_lifetime_out, if set, restricts the
+ * ticket renewable lifetime.
+ */
+typedef krb5_error_code
+(*krb5_kdcpolicy_check_tgs_fn)(krb5_context context,
+                               krb5_kdcpolicy_moddata moddata,
+                               const krb5_kdc_req *request,
+                               const struct _krb5_db_entry_new *server,
+                               const krb5_ticket *ticket,
+                               const char *const *auth_indicators,
+                               const char **status, krb5_deltat *lifetime_out,
+                               krb5_deltat *renew_lifetime_out);
+
+typedef struct krb5_kdcpolicy_vtable_st {
+    const char *name;
+    krb5_kdcpolicy_init_fn init;
+    krb5_kdcpolicy_fini_fn fini;
+    krb5_kdcpolicy_check_as_fn check_as;
+    krb5_kdcpolicy_check_tgs_fn check_tgs;
+} *krb5_kdcpolicy_vtable;
+
+#endif /* KRB5_POLICY_PLUGIN_H */
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 5d49e80..8cfb4ef 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -208,6 +208,13 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
 
     state->ticket_reply.enc_part2 = &state->enc_tkt_reply;
 
+    errcode = check_kdcpolicy_as(kdc_context, state->request, state->client,
+                                 state->server, state->auth_indicators,
+                                 state->kdc_time, &state->enc_tkt_reply.times,
+                                 &state->status);
+    if (errcode)
+        goto egress;
+
     /*
      * Find the server key
      */
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index 84445ed..b9b1073 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -510,6 +510,12 @@ process_tgs_req(struct server_handle *handle, krb5_data *pkt,
     kdc_get_ticket_renewtime(kdc_active_realm, request, header_enc_tkt, client,
                              server, &enc_tkt_reply);
 
+    errcode = check_kdcpolicy_tgs(kdc_context, request, server, header_ticket,
+                                  auth_indicators, kdc_time,
+                                  &enc_tkt_reply.times, &status);
+    if (errcode)
+        goto cleanup;
+
     /*
      * Set authtime to be the same as header or evidence ticket's
      */
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index b710aef..5455e2a 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -642,7 +642,6 @@ validate_as_request(kdc_realm_t *kdc_active_realm,
                     krb5_db_entry server, krb5_timestamp kdc_time,
                     const char **status, krb5_pa_data ***e_data)
 {
-    int errcode;
     krb5_error_code ret;
 
     /*
@@ -750,12 +749,6 @@ validate_as_request(kdc_realm_t *kdc_active_realm,
     if (ret && ret != KRB5_PLUGIN_OP_NOTSUPP)
         return errcode_to_protocol(ret);
 
-    /* Check against local policy. */
-    errcode = against_local_policy_as(request, client, server,
-                                      kdc_time, status, e_data);
-    if (errcode)
-        return errcode;
-
     return 0;
 }
 
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 3d87f36..f99efcf 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -166,17 +166,6 @@ kdc_err(krb5_context call_context, errcode_t code, const char *fmt, ...)
 #endif
     ;
 
-/* policy.c */
-int
-against_local_policy_as (krb5_kdc_req *, krb5_db_entry,
-                         krb5_db_entry, krb5_timestamp,
-                         const char **, krb5_pa_data ***);
-
-int
-against_local_policy_tgs (krb5_kdc_req *, krb5_db_entry,
-                          krb5_ticket *, const char **,
-                          krb5_pa_data ***);
-
 /* kdc_preauth.c */
 krb5_boolean
 enctype_requires_etype_info_2(krb5_enctype enctype);
diff --git a/src/kdc/main.c b/src/kdc/main.c
index ebc852b..f2226da 100644
--- a/src/kdc/main.c
+++ b/src/kdc/main.c
@@ -31,6 +31,7 @@
 #include "kdc_util.h"
 #include "kdc_audit.h"
 #include "extern.h"
+#include "policy.h"
 #include "kdc5_err.h"
 #include "kdb_kt.h"
 #include "net-server.h"
@@ -986,6 +987,12 @@ int main(int argc, char **argv)
 
     load_preauth_plugins(&shandle, kcontext, ctx);
     load_authdata_plugins(kcontext);
+    retval = load_kdcpolicy_plugins(kcontext);
+    if (retval) {
+        kdc_err(kcontext, retval, _("while loading KDC policy plugin"));
+        finish_realms();
+        return 1;
+    }
 
     retval = setup_sam();
     if (retval) {
@@ -1068,6 +1075,7 @@ int main(int argc, char **argv)
     krb5_klog_syslog(LOG_INFO, _("shutting down"));
     unload_preauth_plugins(kcontext);
     unload_authdata_plugins(kcontext);
+    unload_kdcpolicy_plugins(kcontext);
     unload_audit_modules(kcontext);
     krb5_klog_close(kcontext);
     finish_realms();
diff --git a/src/kdc/policy.c b/src/kdc/policy.c
index 6cba430..e49644e 100644
--- a/src/kdc/policy.c
+++ b/src/kdc/policy.c
@@ -1,67 +1,246 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /* kdc/policy.c - Policy decision routines for KDC */
 /*
- * Copyright 1990 by the Massachusetts Institute of Technology.
+ * Copyright (C) 2017 by Red Hat, Inc.
+ * All rights reserved.
  *
- * Export of this software from the United States of America may
- *   require a specific license from the United States Government.
- *   It is the responsibility of any person or organization contemplating
- *   export to obtain such a license before exporting.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
  *
- * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
- * distribute this software and its documentation for any purpose and
- * without fee is hereby granted, provided that the above copyright
- * notice appear in all copies and that both that copyright notice and
- * this permission notice appear in supporting documentation, and that
- * the name of M.I.T. not be used in advertising or publicity pertaining
- * to distribution of the software without specific, written prior
- * permission.  Furthermore if you modify this software you must label
- * your software as modified software and not distribute it in such a
- * fashion that it might be confused with the original M.I.T. software.
- * M.I.T. makes no representations about the suitability of
- * this software for any purpose.  It is provided "as is" without express
- * or implied warranty.
+ * * 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 "k5-int.h"
 #include "kdc_util.h"
 #include "extern.h"
+#include "policy.h"
+#include "adm_proto.h"
+#include <krb5/kdcpolicy_plugin.h>
+#include <syslog.h>
+
+typedef struct kdcpolicy_handle_st {
+    struct krb5_kdcpolicy_vtable_st vt;
+    krb5_kdcpolicy_moddata moddata;
+} *kdcpolicy_handle;
+
+static kdcpolicy_handle *handles;
+
+static void
+free_indicators(char **ais)
+{
+    size_t i;
 
-int
-against_local_policy_as(register krb5_kdc_req *request, krb5_db_entry client,
-                        krb5_db_entry server, krb5_timestamp kdc_time,
-                        const char **status, krb5_pa_data ***e_data)
+    if (ais == NULL)
+        return;
+    for (i = 0; ais[i] != NULL; i++)
+        free(ais[i]);
+    free(ais);
+}
+
+/* Convert inds to a null-terminated list of C strings. */
+static krb5_error_code
+authind_strings(krb5_data *const *inds, char ***strs_out)
 {
-#if 0
-    /* An AS request must include the addresses field */
-    if (request->addresses == 0) {
-        *status = "NO ADDRESS";
-        return KRB5KDC_ERR_POLICY;
+    krb5_error_code ret;
+    char **list = NULL;
+    size_t i, count;
+
+    *strs_out = NULL;
+
+    for (count = 0; inds != NULL && inds[count] != NULL; count++);
+    list = k5calloc(count + 1, sizeof(*list), &ret);
+    if (list == NULL)
+        goto error;
+
+    for (i = 0; i < count; i++) {
+        list[i] = k5memdup0(inds[i]->data, inds[i]->length, &ret);
+        if (list[i] == NULL)
+            goto error;
     }
-#endif
 
-    return 0;                   /* not against policy */
+    *strs_out = list;
+    return 0;
+
+error:
+    free_indicators(list);
+    return ret;
+}
+
+/* Constrain times->endtime to life and times->renew_till to rlife, relative to
+ * now. */
+static void
+update_ticket_times(krb5_ticket_times *times, krb5_timestamp now,
+                    krb5_deltat life, krb5_deltat rlife)
+{
+    if (life)
+        times->endtime = ts_min(ts_incr(now, life), times->endtime);
+    if (rlife)
+        times->renew_till = ts_min(ts_incr(now, rlife), times->renew_till);
+}
+
+/* Check an AS request against kdcpolicy modules, updating times with any
+ * module endtime constraints.  Set an appropriate status string on error. */
+krb5_error_code
+check_kdcpolicy_as(krb5_context context, const krb5_kdc_req *request,
+                   const krb5_db_entry *client, const krb5_db_entry *server,
+                   krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
+                   krb5_ticket_times *times, const char **status)
+{
+    krb5_deltat life, rlife;
+    krb5_error_code ret;
+    kdcpolicy_handle *hp, h;
+    char **ais = NULL;
+
+    *status = NULL;
+
+    ret = authind_strings(auth_indicators, &ais);
+    if (ret)
+        goto done;
+
+    for (hp = handles; *hp != NULL; hp++) {
+        h = *hp;
+        if (h->vt.check_as == NULL)
+            continue;
+
+        ret = h->vt.check_as(context, h->moddata, request, client, server,
+                             (const char **)ais, status, &life, &rlife);
+        if (ret)
+            goto done;
+
+        update_ticket_times(times, kdc_time, life, rlife);
+    }
+
+done:
+    free_indicators(ais);
+    return ret;
 }
 
 /*
- * This is where local policy restrictions for the TGS should placed.
+ * Check the TGS request against the local TGS policy.  Accepts an
+ * authentication indicator for the module policy decisions.  Returns 0 and a
+ * NULL status string on success.
  */
 krb5_error_code
-against_local_policy_tgs(register krb5_kdc_req *request, krb5_db_entry server,
-                         krb5_ticket *ticket, const char **status,
-                         krb5_pa_data ***e_data)
+check_kdcpolicy_tgs(krb5_context context, const krb5_kdc_req *request,
+                    const krb5_db_entry *server, const krb5_ticket *ticket,
+                    krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
+                    krb5_ticket_times *times, const char **status)
 {
-#if 0
-    /*
-     * For example, if your site wants to disallow ticket forwarding,
-     * you might do something like this:
-     */
-
-    if (isflagset(request->kdc_options, KDC_OPT_FORWARDED)) {
-        *status = "FORWARD POLICY";
-        return KRB5KDC_ERR_POLICY;
+    krb5_deltat life, rlife;
+    krb5_error_code ret;
+    kdcpolicy_handle *hp, h;
+    char **ais = NULL;
+
+    *status = NULL;
+
+    ret = authind_strings(auth_indicators, &ais);
+    if (ret)
+        goto done;
+
+    for (hp = handles; *hp != NULL; hp++) {
+        h = *hp;
+        if (h->vt.check_tgs == NULL)
+            continue;
+
+        ret = h->vt.check_tgs(context, h->moddata, request, server, ticket,
+                              (const char **)ais, status, &life, &rlife);
+        if (ret)
+            goto done;
+
+        update_ticket_times(times, kdc_time, life, rlife);
     }
-#endif
 
-    return 0;                           /* not against policy */
+done:
+    free_indicators(ais);
+    return ret;
+}
+
+void
+unload_kdcpolicy_plugins(krb5_context context)
+{
+    kdcpolicy_handle *hp, h;
+
+    for (hp = handles; *hp != NULL; hp++) {
+        h = *hp;
+        if (h->vt.fini != NULL)
+            h->vt.fini(context, h->moddata);
+        free(h);
+    }
+    free(handles);
+    handles = NULL;
+}
+
+krb5_error_code
+load_kdcpolicy_plugins(krb5_context context)
+{
+    krb5_error_code ret;
+    krb5_plugin_initvt_fn *modules = NULL, *mod;
+    kdcpolicy_handle h;
+    size_t count;
+
+    ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_KDCPOLICY, &modules);
+    if (ret)
+        goto cleanup;
+
+    for (count = 0; modules[count] != NULL; count++);
+    handles = k5calloc(count + 1, sizeof(*handles), &ret);
+    if (handles == NULL)
+        goto cleanup;
+
+    count = 0;
+    for (mod = modules; *mod != NULL; mod++) {
+        h = k5calloc(1, sizeof(*h), &ret);
+        if (h == NULL)
+            goto cleanup;
+
+        ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt);
+        if (ret) {              /* Version mismatch. */
+            TRACE_KDCPOLICY_VTINIT_FAIL(context, ret);
+            free(h);
+            continue;
+        }
+        if (h->vt.init != NULL) {
+            ret = h->vt.init(context, &h->moddata);
+            if (ret == KRB5_PLUGIN_NO_HANDLE) {
+                TRACE_KADM5_AUTH_INIT_SKIP(context, h->vt.name);
+                free(h);
+                continue;
+            }
+            if (ret) {
+                kdc_err(context, ret, _("while loading policy module %s"),
+                        h->vt.name);
+                free(h);
+                goto cleanup;
+            }
+        }
+        handles[count++] = h;
+    }
+
+    ret = 0;
+
+cleanup:
+    if (ret)
+        unload_kdcpolicy_plugins(context);
+    k5_plugin_free_modules(context, modules);
+    return ret;
 }
diff --git a/src/kdc/policy.h b/src/kdc/policy.h
index 6b000dc..2a57b0a 100644
--- a/src/kdc/policy.h
+++ b/src/kdc/policy.h
@@ -26,11 +26,22 @@
 #ifndef __KRB5_KDC_POLICY__
 #define __KRB5_KDC_POLICY__
 
-extern int against_postdate_policy (krb5_timestamp);
+krb5_error_code
+load_kdcpolicy_plugins(krb5_context context);
 
-extern int against_flag_policy_as (const krb5_kdc_req *);
+void
+unload_kdcpolicy_plugins(krb5_context context);
 
-extern int against_flag_policy_tgs (const krb5_kdc_req *,
-                                    const krb5_ticket *);
+krb5_error_code
+check_kdcpolicy_as(krb5_context context, const krb5_kdc_req *request,
+                   const krb5_db_entry *client, const krb5_db_entry *server,
+                   krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
+                   krb5_ticket_times *times, const char **status);
+
+krb5_error_code
+check_kdcpolicy_tgs(krb5_context context, const krb5_kdc_req *request,
+                    const krb5_db_entry *server, const krb5_ticket *ticket,
+                    krb5_data *const *auth_indicators, krb5_timestamp kdc_time,
+                    krb5_ticket_times *times, const char **status);
 
 #endif /* __KRB5_KDC_POLICY__ */
diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c
index d0f25d1..33cfbcd 100644
--- a/src/kdc/tgs_policy.c
+++ b/src/kdc/tgs_policy.c
@@ -375,11 +375,5 @@ validate_tgs_request(kdc_realm_t *kdc_active_realm,
     if (ret && ret != KRB5_PLUGIN_OP_NOTSUPP)
         return errcode_to_protocol(ret);
 
-    /* Check local policy. */
-    errcode = against_local_policy_tgs(request, server, ticket,
-                                       status, e_data);
-    if (errcode)
-        return errcode;
-
     return 0;
 }
diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c
index ea4c069..31aaf66 100644
--- a/src/lib/krb5/krb/plugin.c
+++ b/src/lib/krb5/krb/plugin.c
@@ -59,7 +59,8 @@ const char *interface_names[] = {
     "tls",
     "kdcauthdata",
     "certauth",
-    "kadm5_auth"
+    "kadm5_auth",
+    "kdcpolicy",
 };
 
 /* Return the context's interface structure for id, or NULL if invalid. */
diff --git a/src/plugins/kdcpolicy/test/Makefile.in b/src/plugins/kdcpolicy/test/Makefile.in
new file mode 100644
index 0000000..b81f1a7
--- /dev/null
+++ b/src/plugins/kdcpolicy/test/Makefile.in
@@ -0,0 +1,20 @@
+mydir=plugins$(S)policy$(S)test
+BUILDTOP=$(REL)..$(S)..$(S)..
+
+LIBBASE=policy_test
+LIBMAJOR=0
+LIBMINOR=0
+RELDIR=../plugins/kdcpolicy/test
+SHLIB_EXPDEPS=$(KRB5_BASE_DEPLIBS)
+SHLIB_EXPLIBS=$(KRB5_BASE_LIBS)
+
+STLIBOBJS=main.o
+
+SRCS=$(srcdir)/main.c
+
+all-unix: all-libs
+install-unix:
+clean-unix:: clean-libs clean-libobjs
+
+@libnover_frag@
+@libobj_frag@
diff --git a/src/plugins/kdcpolicy/test/deps b/src/plugins/kdcpolicy/test/deps
new file mode 100644
index 0000000..e69de29
diff --git a/src/plugins/kdcpolicy/test/main.c b/src/plugins/kdcpolicy/test/main.c
new file mode 100644
index 0000000..eb8fde0
--- /dev/null
+++ b/src/plugins/kdcpolicy/test/main.c
@@ -0,0 +1,111 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* include/krb5/kdcpolicy_plugin.h - KDC policy plugin interface */
+/*
+ * Copyright (C) 2017 by Red Hat, 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 "k5-int.h"
+#include "kdb.h"
+#include <krb5/kdcpolicy_plugin.h>
+
+static krb5_error_code
+output_from_indicator(const char *const *auth_indicators,
+                      krb5_deltat *lifetime_out,
+                      krb5_deltat *renew_lifetime_out,
+                      const char **status)
+{
+    if (auth_indicators[0] == NULL) {
+        *status = NULL;
+        return 0;
+    }
+
+    if (strcmp(auth_indicators[0], "ONE_HOUR") == 0) {
+        *lifetime_out = 3600;
+        *renew_lifetime_out = *lifetime_out * 2;
+        return 0;
+    } else if (strcmp(auth_indicators[0], "SEVEN_HOURS") == 0) {
+        *lifetime_out = 7 * 3600;
+        *renew_lifetime_out = *lifetime_out * 2;
+        return 0;
+    }
+
+    *status = "LOCAL_POLICY";
+    return KRB5KDC_ERR_POLICY;
+}
+
+static krb5_error_code
+test_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
+              const krb5_kdc_req *request, const krb5_db_entry *client,
+              const krb5_db_entry *server, const char *const *auth_indicators,
+              const char **status, krb5_deltat *lifetime_out,
+              krb5_deltat *renew_lifetime_out)
+{
+    if (request->client != NULL && request->client->length >= 1 &&
+        data_eq_string(request->client->data[0], "fail")) {
+        *status = "LOCAL_POLICY";
+        return KRB5KDC_ERR_POLICY;
+    }
+    return output_from_indicator(auth_indicators, lifetime_out,
+                                 renew_lifetime_out, status);
+}
+
+static krb5_error_code
+test_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
+               const krb5_kdc_req *request, const krb5_db_entry *server,
+               const krb5_ticket *ticket, const char *const *auth_indicators,
+               const char **status, krb5_deltat *lifetime_out,
+               krb5_deltat *renew_lifetime_out)
+{
+    if (request->server != NULL && request->server->length >= 1 &&
+        data_eq_string(request->server->data[0], "fail")) {
+        *status = "LOCAL_POLICY";
+        return KRB5KDC_ERR_POLICY;
+    }
+    return output_from_indicator(auth_indicators, lifetime_out,
+                                 renew_lifetime_out, status);
+}
+
+krb5_error_code
+kdcpolicy_test_initvt(krb5_context context, int maj_ver, int min_ver,
+                      krb5_plugin_vtable vtable);
+krb5_error_code
+kdcpolicy_test_initvt(krb5_context context, int maj_ver, int min_ver,
+                      krb5_plugin_vtable vtable)
+{
+    krb5_kdcpolicy_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+
+    vt = (krb5_kdcpolicy_vtable)vtable;
+    vt->name = "test";
+    vt->check_as = test_check_as;
+    vt->check_tgs = test_check_tgs;
+    return 0;
+}
diff --git a/src/plugins/kdcpolicy/test/policy_test.exports b/src/plugins/kdcpolicy/test/policy_test.exports
new file mode 100644
index 0000000..9682ec7
--- /dev/null
+++ b/src/plugins/kdcpolicy/test/policy_test.exports
@@ -0,0 +1 @@
+kdcpolicy_test_initvt
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 0013442..67d3e82 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -174,6 +174,7 @@ check-pytests: unlockiter
 	$(RUNPYTEST) $(srcdir)/t_tabdump.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_certauth.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_y2038.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_kdcpolicy.py $(PYTESTFLAGS)
 
 clean:
 	$(RM) adata etinfo forward gcred hist hooks hrealm icinterleave icred
diff --git a/src/tests/t_kdcpolicy.py b/src/tests/t_kdcpolicy.py
new file mode 100644
index 0000000..6a745b9
--- /dev/null
+++ b/src/tests/t_kdcpolicy.py
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+from k5test import *
+from datetime import datetime
+import re
+
+testpreauth = os.path.join(buildtop, 'plugins', 'preauth', 'test', 'test.so')
+testpolicy = os.path.join(buildtop, 'plugins', 'kdcpolicy', 'test',
+                          'policy_test.so')
+krb5_conf = {'plugins': {'kdcpreauth': {'module': 'test:' + testpreauth},
+                         'clpreauth': {'module': 'test:' + testpreauth},
+                         'kdcpolicy': {'module': 'test:' + testpolicy}}}
+kdc_conf = {'realms': {'$realm': {'default_principal_flags': '+preauth',
+                                  'max_renewable_life': '1d'}}}
+realm = K5Realm(krb5_conf=krb5_conf, kdc_conf=kdc_conf)
+
+realm.run([kadminl, 'addprinc', '-pw', password('fail'), 'fail'])
+
+def verify_time(out, target_time):
+    times = re.findall(r'\d\d/\d\d/\d\d \d\d:\d\d:\d\d', out)
+    times = [datetime.strptime(t, '%m/%d/%y %H:%M:%S') for t in times]
+    while len(times) > 0:
+        starttime = times.pop(0)
+        endtime = times.pop(0)
+        renewtime = times.pop(0)
+
+        if str(endtime - starttime) != target_time:
+            fail('unexpected lifetime value')
+        if str(renewtime - endtime) != target_time:
+            fail('unexpected renewable value')
+
+rflags = ['-r', '1d', '-l', '12h']
+
+# Test AS+TGS success path.
+realm.kinit(realm.user_princ, password('user'),
+            rflags + ['-X', 'indicators=SEVEN_HOURS'])
+realm.run([kvno, realm.host_princ])
+realm.run(['./adata', realm.host_princ], expected_msg='+97: [SEVEN_HOURS]')
+out = realm.run([klist, realm.ccache, '-e'])
+verify_time(out, '7:00:00')
+
+# Test AS+TGS success path with different values.
+realm.kinit(realm.user_princ, password('user'),
+            rflags + ['-X', 'indicators=ONE_HOUR'])
+realm.run([kvno, realm.host_princ])
+realm.run(['./adata', realm.host_princ], expected_msg='+97: [ONE_HOUR]')
+out = realm.run([klist, realm.ccache, '-e'])
+verify_time(out, '1:00:00')
+
+# Test TGS failure path (using previous creds).
+realm.run([kvno, 'fail@%s' % realm.realm], expected_code=1,
+          expected_msg='KDC policy rejects request')
+
+# Test AS failure path.
+realm.kinit('fail@%s' % realm.realm, password('fail'),
+            expected_code=1, expected_msg='KDC policy rejects request')
+
+success('kdcpolicy tests')
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.