krb5 commit: Refactor KDC TGS processing code

[email protected]
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/a9705a1e0b2cf0cde3e6f8dee14c25ffc074c00a
commit a9705a1e0b2cf0cde3e6f8dee14c25ffc074c00a
Author: Greg Hudson <[email protected]>
Date:   Tue Aug 9 12:22:43 2022 -0400

    Refactor KDC TGS processing code
    
    Split the TGS processing code into information gathering, constraint
    and policy checking, and ticket-issuing steps, using a structure to
    hold the gathered information.  Split validate_tgs_request() into
    validate_tgs_constraints() and check_tgs_policy() for better auditing.
    Fold kdc_process_s4u2proxy_req() into check_tgs_policy(), except for
    the get_pac_princ_with_realm() step which is now performed in
    gather_tgs_req_info().  Modify some other utility functions to fit the
    new design.

 src/kdc/do_as_req.c  |    3 +-
 src/kdc/do_tgs_req.c | 1402 ++++++++++++++++++++++++++------------------------
 src/kdc/fast_util.c  |    2 +
 src/kdc/kdc_audit.c  |    2 +
 src/kdc/kdc_log.c    |    3 +-
 src/kdc/kdc_util.c   |  126 +----
 src/kdc/kdc_util.h   |   42 +-
 src/kdc/tgs_policy.c |  116 ++++-
 8 files changed, 872 insertions(+), 824 deletions(-)

diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 02b6eac04..6fb214b77 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -699,7 +699,8 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
                            state->server, &state->enc_tkt_reply.times.endtime);
 
     kdc_get_ticket_renewtime(realm, state->request, NULL, state->client,
-                             state->server, &state->enc_tkt_reply);
+                             state->server, &state->enc_tkt_reply.flags,
+                             &state->enc_tkt_reply.times);
 
     /*
      * starttime is optional, and treated as authtime if not present.
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index 63e79c082..bdf6a13ae 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -69,705 +69,114 @@
 #include "adm_proto.h"
 #include <ctype.h>
 
-static krb5_error_code
-prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *, krb5_ticket *,
-                  krb5_error_code, krb5_principal, krb5_data **, const char *,
-                  krb5_pa_data **);
-
-static krb5_error_code
-decrypt_2ndtkt(krb5_context, krb5_kdc_req *, krb5_flags, krb5_db_entry *,
-               krb5_keyblock *, const krb5_ticket **, krb5_pac *,
-               krb5_db_entry **, krb5_keyblock **, const char **);
-
-static krb5_error_code
-gen_session_key(krb5_context, krb5_kdc_req *, krb5_db_entry *,
-                krb5_keyblock *, const char **);
-
-static krb5_int32
-find_referral_tgs(kdc_realm_t *, krb5_kdc_req *, krb5_principal *);
-
-static krb5_error_code
-db_get_svc_princ(krb5_context, krb5_principal, krb5_flags,
-                 krb5_db_entry **, const char **);
-
-static krb5_error_code
-search_sprinc(kdc_realm_t *, krb5_kdc_req *, krb5_flags,
-              krb5_db_entry **, const char **);
-
-/*ARGSUSED*/
-krb5_error_code
-process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
-                const krb5_fulladdr *from, kdc_realm_t *realm,
-                krb5_data **response)
-{
-    krb5_context context = realm->realm_context;
-    krb5_keyblock * subkey = 0;
-    krb5_keyblock *header_key = NULL;
-    krb5_keyblock *stkt_server_key = NULL;
-    krb5_keyblock *subject_key;
-    krb5_db_entry *server = NULL;
-    krb5_db_entry *stkt_server = NULL;
-    krb5_db_entry *subject_server;
-    krb5_kdc_rep reply;
-    krb5_enc_kdc_rep_part reply_encpart;
-    krb5_ticket ticket_reply, *header_ticket = 0;
-    const krb5_ticket *stkt = NULL;
-    krb5_enc_tkt_part enc_tkt_reply;
-    int newtransited = 0;
-    krb5_error_code retval = 0, errcode;
-    krb5_keyblock server_keyblock, *encrypting_key;
-    krb5_timestamp kdc_time, authtime = 0;
-    krb5_keyblock session_key, local_tgt_key;
-    krb5_keyblock *reply_key = NULL;
-    krb5_principal cprinc = NULL, sprinc = NULL, altcprinc = NULL;
-    krb5_principal stkt_authdata_client = NULL;
-    krb5_last_req_entry *nolrarray[2], nolrentry;
-    const char        *status = 0;
-    krb5_enc_tkt_part *header_enc_tkt = NULL; /* TGT */
-    krb5_enc_tkt_part *subject_tkt = NULL; /* TGT or evidence ticket */
-    krb5_db_entry *client = NULL, *header_server = NULL;
-    krb5_db_entry *local_tgt, *local_tgt_storage = NULL;
-    krb5_pa_s4u_x509_user *s4u_x509_user = NULL; /* protocol transition request */
-    krb5_authdata **kdc_issued_auth_data = NULL; /* auth data issued by KDC */
-    unsigned int c_flags = 0, s_flags = 0;       /* client/server KDB flags */
-    krb5_boolean is_referral, is_crossrealm;
-    const char *emsg = NULL;
-    krb5_kvno ticket_kvno = 0;
-    struct kdc_request_state *state = NULL;
-    krb5_pa_data *pa_tgs_req; /*points into request*/
-    krb5_data scratch;
-    krb5_pa_data **e_data = NULL;
-    krb5_audit_state *au_state = NULL;
-    krb5_data **auth_indicators = NULL;
-    krb5_pac header_pac = NULL, stkt_pac = NULL, subject_pac;
-
-    memset(&reply, 0, sizeof(reply));
-    memset(&reply_encpart, 0, sizeof(reply_encpart));
-    memset(&ticket_reply, 0, sizeof(ticket_reply));
-    memset(&enc_tkt_reply, 0, sizeof(enc_tkt_reply));
-    memset(&server_keyblock, 0, sizeof(server_keyblock));
-    memset(&local_tgt_key, 0, sizeof(local_tgt_key));
-    session_key.contents = NULL;
-
-    /* Save pointer to client-requested service principal, in case of
-     * errors before a successful call to search_sprinc(). */
-    sprinc = request->server;
-
-    if (request->msg_type != KRB5_TGS_REQ) {
-        krb5_free_kdc_req(context, request);
-        return KRB5_BADMSGTYPE;
-    }
-
-    errcode = kdc_make_rstate(realm, &state);
-    if (errcode != 0)
-        goto cleanup;
-
-    /* Initialize audit state. */
-    errcode = kau_init_kdc_req(context, request, from, &au_state);
-    if (errcode)
-        goto cleanup;
-
-    /* Seed the audit trail with the request ID and basic information. */
-    kau_tgs_req(context, TRUE, au_state);
-
-    errcode = kdc_process_tgs_req(realm, request, from, pkt, &header_ticket,
-                                  &header_server, &header_key, &subkey,
-                                  &pa_tgs_req);
-    if (header_ticket && header_ticket->enc_part2)
-        cprinc = header_ticket->enc_part2->client;
-
-    if (errcode) {
-        status = "PROCESS_TGS";
-        goto cleanup;
-    }
-
-    if (!header_ticket) {
-        errcode = KRB5_NO_TKT_SUPPLIED;        /* XXX? */
-        goto cleanup;
-    }
-    errcode = kau_make_tkt_id(context, header_ticket, &au_state->tkt_in_id);
-    if (errcode)
-        goto cleanup;
-
-    scratch.length = pa_tgs_req->length;
-    scratch.data = (char *) pa_tgs_req->contents;
-    errcode = kdc_find_fast(&request, &scratch, subkey,
-                            header_ticket->enc_part2->session, state, NULL);
-    /* Reset sprinc because kdc_find_fast() can replace request. */
-    sprinc = request->server;
-    if (errcode !=0) {
-        status = "FIND_FAST";
-        goto cleanup;
-    }
-    if (sprinc == NULL) {
-        status = "NULL_SERVER";
-        errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
-        goto cleanup;
-    }
-
-    errcode = get_local_tgt(context, &sprinc->realm, header_server,
-                            &local_tgt, &local_tgt_storage, &local_tgt_key);
-    if (errcode) {
-        status = "GET_LOCAL_TGT";
-        goto cleanup;
-    }
-
-    errcode = get_verified_pac(context, header_ticket->enc_part2,
-                               header_server->princ, header_key, local_tgt,
-                               &local_tgt_key, &header_pac);
-    if (errcode) {
-        status = "HEADER_PAC";
-        goto cleanup;
-    }
-
-    /* Ignore (for now) the request modification due to FAST processing. */
-    au_state->request = request;
+struct tgs_req_info {
+    /* The decoded request.  Ownership is transferred to this structure.  This
+     * will be replaced with the inner FAST body if present. */
+    krb5_kdc_req *req;
 
     /*
-     * Pointer to the encrypted part of the header ticket, which may be
-     * replaced to point to the encrypted part of the evidence ticket
-     * if constrained delegation is used. This simplifies the number of
-     * special cases for constrained delegation.
+     * The decrypted authentication header ticket from the request's
+     * PA-TGS-REQ, the KDB entry for its server, its encryption key, the
+     * PA-TGS-REQ subkey if present, and the decoded and verified header ticket
+     * PAC if present.
      */
-    header_enc_tkt = header_ticket->enc_part2;
+    krb5_ticket *header_tkt;
+    krb5_db_entry *header_server;
+    krb5_keyblock *header_key;
+    krb5_keyblock *subkey;
+    krb5_pac header_pac;
 
     /*
-     * We've already dealt with the AP_REQ authentication, so we can
-     * use header_ticket freely.  The encrypted part (if any) has been
-     * decrypted with the session key.
+     * If a second ticket is present and this is a U2U or S4U2Proxy request,
+     * the decoded and verified PAC if present, the KDB entry for the second
+     * ticket server server, and the key used to decrypt the second ticket.
      */
-
-    au_state->stage = SRVC_PRINC;
-
-    /* XXX make sure server here has the proper realm...taken from AP_REQ
-       header? */
-
-    if (isflagset(request->kdc_options, KDC_OPT_CANONICALIZE))
-        setflag(s_flags, KRB5_KDB_FLAG_REFERRAL_OK);
-
-    errcode = search_sprinc(realm, request, s_flags, &server, &status);
-    if (errcode != 0)
-        goto cleanup;
-    sprinc = server->princ;
-
-    /* If we got a cross-realm TGS which is not the requested server, we are
-     * issuing a referral (or alternate TGT, which we treat similarly). */
-    is_referral = is_cross_tgs_principal(server->princ) &&
-        !krb5_principal_compare(context, request->server, server->princ);
-
-    au_state->stage = VALIDATE_POL;
-
-    errcode = krb5_timeofday(context, &kdc_time);
-    if (errcode)
-        goto cleanup;
-
-    is_crossrealm = !data_eq(header_server->princ->realm, sprinc->realm);
-    if (is_crossrealm)
-        setflag(c_flags, KRB5_KDB_FLAG_CROSS_REALM);
-    if (is_referral)
-        setflag(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL);
-
-    /* Check for protocol transition */
-    errcode = kdc_process_s4u2self_req(context, request, server, subkey,
-                                       header_enc_tkt->session, &s4u_x509_user,
-                                       &client, &status);
-    if (s4u_x509_user != NULL || errcode != 0) {
-        if (s4u_x509_user != NULL)
-            au_state->s4u2self_user = s4u_x509_user->user_id.user;
-        au_state->status = status;
-        kau_s4u2self(context, errcode ? FALSE : TRUE, au_state);
-        au_state->s4u2self_user = NULL;
-        if (errcode)
-            goto cleanup;
-    }
-    if (s4u_x509_user != NULL)
-        setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION);
-
-    /* For user-to-user and S4U2Proxy requests, decrypt the second ticket. */
-    errcode = decrypt_2ndtkt(context, request, c_flags, local_tgt,
-                             &local_tgt_key, &stkt, &stkt_pac, &stkt_server,
-                             &stkt_server_key, &status);
-    if (errcode)
-        goto cleanup;
-
-    errcode = validate_tgs_request(realm, request, server, header_ticket,
-                                   header_pac, stkt, stkt_pac, stkt_server,
-                                   kdc_time, s4u_x509_user, client,
-                                   is_crossrealm, is_referral, &status,
-                                   &e_data);
-    if (errcode) {
-        if (errcode == KRB5KDC_ERR_POLICY || errcode == KRB5KDC_ERR_BADOPTION)
-            au_state->violation = PROT_CONSTRAINT;
-        goto cleanup;
-    }
-
-    if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) {
-        /* Do constrained delegation protocol and authorization checks. */
-        setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION);
-
-        errcode = kdc_process_s4u2proxy_req(context, c_flags, request,
-                                            header_pac, stkt->enc_part2,
-                                            stkt_pac, stkt_server,
-                                            stkt_server_key,
-                                            header_ticket->enc_part2->client,
-                                            server, &stkt_authdata_client,
-                                            &status);
-        if (errcode == KRB5KDC_ERR_POLICY || errcode == KRB5KDC_ERR_BADOPTION)
-            au_state->violation = PROT_CONSTRAINT;
-        else if (errcode)
-            au_state->violation = LOCAL_POLICY;
-        au_state->status = status;
-        retval = kau_make_tkt_id(context, stkt, &au_state->evid_tkt_id);
-        if (retval) {
-            errcode = retval;
-            goto cleanup;
-        }
-        kau_s4u2proxy(context, errcode ? FALSE : TRUE, au_state);
-        if (errcode)
-            goto cleanup;
-
-        assert(krb5_is_tgs_principal(header_ticket->server));
-    }
-
-    au_state->stage = ISSUE_TKT;
-
-    errcode = gen_session_key(context, request, server, &session_key, &status);
-    if (errcode)
-        goto cleanup;
+    krb5_pac stkt_pac;
+    krb5_db_entry *stkt_server;
+    krb5_keyblock *stkt_server_key;
+    /* For cross-realm S4U2Proxy requests, the client principal retrieved from
+     * stkt_pac. */
+    krb5_principal stkt_pac_client;
+
+    /* Storage for the local TGT KDB entry for the service realm if that isn't
+     * the header server. */
+    krb5_db_entry *local_tgt_storage;
+    /* The decrypted first key of the local TGT entry. */
+    krb5_keyblock local_tgt_key;
+
+    /* The server KDB entry.  Normally the requested server, but for referral
+     * and alternate TGS replies this will be a cross-realm TGT entry. */
+    krb5_db_entry *server;
 
     /*
-     * subject_tkt will refer to the evidence ticket (for constrained
-     * delegation) or the TGT. The distinction from header_enc_tkt is
-     * necessary because the TGS signature only protects some fields:
-     * the others could be forged by a malicious server.
+     * The subject client KDB entry for an S4U2Self request, or the header
+     * ticket client KDB entry for other requests.  NULL if
+     * NO_AUTH_DATA_REQUIRED is set on the server KDB entry and this isn't an
+     * S4U2Self request, or if the client is in another realm and the KDB
+     * cannot map its principal name.
      */
+    krb5_db_entry *client;
 
-    if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
-        subject_tkt = stkt->enc_part2;
-        subject_pac = stkt_pac;
-        subject_server = stkt_server;
-        subject_key = stkt_server_key;
-    } else {
-        subject_tkt = header_enc_tkt;
-        subject_pac = header_pac;
-        subject_server = header_server;
-        subject_key = header_key;
-    }
-    authtime = subject_tkt->times.authtime;
-
-    /* Extract and check auth indicators from the subject ticket, except for
-     * S4U2Self requests (where the client didn't authenticate). */
-    if (s4u_x509_user == NULL) {
-        errcode = get_auth_indicators(context, subject_tkt, local_tgt,
-                                      &local_tgt_key, &auth_indicators);
-        if (errcode) {
-            status = "GET_AUTH_INDICATORS";
-            goto cleanup;
-        }
+    /* The decoded S4U2Self padata from the request, if present. */
+    krb5_pa_s4u_x509_user *s4u2self;
 
-        errcode = check_indicators(context, server, auth_indicators);
-        if (errcode) {
-            status = "HIGHER_AUTHENTICATION_REQUIRED";
-            goto cleanup;
-        }
-    }
+    /* Authentication indicators retrieved from the header ticket, for
+     * non-S4U2Self requests. */
+    krb5_data **auth_indicators;
 
-    if (is_referral)
-        ticket_reply.server = server->princ;
-    else
-        ticket_reply.server = request->server; /* XXX careful for realm... */
+    /* Storage for a transited list with the header TGT realm added, if that
+     * realm is different from the client and server realm. */
+    krb5_data new_transited;
 
-    enc_tkt_reply.flags = get_ticket_flags(request->kdc_options, client,
-                                           server, header_enc_tkt);
-    enc_tkt_reply.times.starttime = 0;
+    /* The KDB flags applicable to this request (a subset of {CROSS_REALM,
+     * ISSUING_REFERRAL, PROTOCOL_TRANSITION, CONSTRAINED_DELEGATION}). */
+    unsigned int flags;
 
-    if (s4u_x509_user != NULL && !is_referral) {
-        /* Check if we need to suppress the forwardable ticket flag. */
-        errcode = s4u2self_forwardable(context, server, &enc_tkt_reply);
-        if (errcode)
-            goto cleanup;
-    }
+    /* Booleans for two of the above flags, for convenience. */
+    krb5_boolean is_referral;
+    krb5_boolean is_crossrealm;
 
-    /* don't use new addresses unless forwarded, see below */
+    /* The authtime of subject_tkt.  On early failures this may be 0. */
+    krb5_timestamp authtime;
 
-    enc_tkt_reply.caddrs = header_enc_tkt->caddrs;
-    /* noaddrarray[0] = 0; */
-    reply_encpart.caddrs = 0;/* optional...don't put it in */
-    reply_encpart.enc_padata = NULL;
+    /* The following fields are (or contain) alias pointers and should not be
+     * freed. */
 
-    /*
-     * It should be noted that local policy may affect the
-     * processing of any of these flags.  For example, some
-     * realms may refuse to issue renewable tickets
-     */
+    /* The transited list implied by the request, aliasing new_transited or the
+     * header ticket transited field. */
+    krb5_transited transited;
 
-    if (isflagset(request->kdc_options, KDC_OPT_FORWARDED) ||
-        isflagset(request->kdc_options, KDC_OPT_PROXY)) {
+    /* Alias to the decrypted second ticket within req, if one applies to this
+     * request. */
+    const krb5_ticket *stkt;
 
-        /* include new addresses in ticket & reply */
+    /* Alias to stkt for S4U2Proxy requests, header_tkt otherwise. */
+    krb5_enc_tkt_part *subject_tkt;
 
-        enc_tkt_reply.caddrs = request->addresses;
-        reply_encpart.caddrs = request->addresses;
-    }
+    /* Alias to local_tgt_storage or header_server. */
+    krb5_db_entry *local_tgt;
 
-    if (isflagset(request->kdc_options, KDC_OPT_POSTDATED))
-        enc_tkt_reply.times.starttime = request->from;
-    else
-        enc_tkt_reply.times.starttime = kdc_time;
-
-    if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
-        assert(isflagset(c_flags, KRB5_KDB_FLAGS_S4U) == 0);
-        /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs
-           to the caller */
-        ticket_reply = *(header_ticket);
-        enc_tkt_reply = *(header_ticket->enc_part2);
-        enc_tkt_reply.authorization_data = NULL;
-        clear(enc_tkt_reply.flags, TKT_FLG_INVALID);
-    }
+    /* For either kind of S4U request, an alias to the requested client
+     * principal name. */
+    krb5_principal s4u_cprinc;
 
-    if (isflagset(request->kdc_options, KDC_OPT_RENEW)) {
-        krb5_timestamp old_starttime;
-        krb5_deltat old_life;
+    /* An alias to the client principal name we should issue the ticket for
+     * (either header_tkt->enc_part2->client or s4u_cprinc). */
+    krb5_principal tkt_client;
 
-        assert(isflagset(c_flags, KRB5_KDB_FLAGS_S4U) == 0);
-        /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs
-           to the caller */
-        ticket_reply = *(header_ticket);
-        enc_tkt_reply = *(header_ticket->enc_part2);
-        enc_tkt_reply.authorization_data = NULL;
+    /* The client principal of the PA-TGS-REQ header ticket.  On early failures
+     * this may be NULL. */
+    krb5_principal cprinc;
 
-        old_starttime = enc_tkt_reply.times.starttime ?
-            enc_tkt_reply.times.starttime : enc_tkt_reply.times.authtime;
-        old_life = ts_delta(enc_tkt_reply.times.endtime, old_starttime);
+    /* The canonicalized request server principal or referral/alternate TGT.
+     * On early failures this may be the requested server instead. */
+    krb5_principal sprinc;
 
-        enc_tkt_reply.times.starttime = kdc_time;
-        enc_tkt_reply.times.endtime =
-            ts_min(header_ticket->enc_part2->times.renew_till,
-                   ts_incr(kdc_time, old_life));
-    } else {
-        /* not a renew request */
-        enc_tkt_reply.times.starttime = kdc_time;
+};
 
-        kdc_get_ticket_endtime(realm, enc_tkt_reply.times.starttime,
-                               header_enc_tkt->times.endtime, request->till,
-                               client, server, &enc_tkt_reply.times.endtime);
-    }
-
-    kdc_get_ticket_renewtime(realm, request, header_enc_tkt, client, server,
-                             &enc_tkt_reply);
-
-    errcode = check_kdcpolicy_tgs(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
-     */
-    enc_tkt_reply.times.authtime = authtime;
-
-    /* starttime is optional, and treated as authtime if not present.
-       so we can nuke it if it matches */
-    if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime)
-        enc_tkt_reply.times.starttime = 0;
-
-    if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
-        altcprinc = s4u_x509_user->user_id.user;
-    } else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
-        /* kdc_process_s4u2proxy_req() only allows cross-realm requests if
-         * stkt_authdata_client is set. */
-        altcprinc = is_crossrealm ? stkt_authdata_client : subject_tkt->client;
-    } else {
-        altcprinc = NULL;
-    }
-    if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
-        encrypting_key = stkt->enc_part2->session;
-    } else {
-        errcode = get_first_current_key(context, server, &server_keyblock);
-        if (errcode) {
-            status = "FINDING_SERVER_KEY";
-            goto cleanup;
-        }
-        encrypting_key = &server_keyblock;
-    }
-
-    if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
-        /*
-         * For consistency with Active Directory, don't allow authorization
-         * data to be disabled if S4U2Self is requested.  The server likely
-         * needs a PAC to inspect or for an S4U2Proxy operation, even if it
-         * doesn't need authorization data in tickets received from clients.
-         */
-        clear(server->attributes, KRB5_KDB_NO_AUTH_DATA_REQUIRED);
-    }
-    if (isflagset(server->attributes, KRB5_KDB_NO_AUTH_DATA_REQUIRED) == 0) {
-        /* If we are not doing protocol transition, try to look up the subject
-         * principal so that KDB modules can add additional authdata. */
-        if (!isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
-            setflag(c_flags, KRB5_KDB_FLAG_CLIENT);
-            /* Map principals from foreign (possibly non-AD) realms */
-            setflag(c_flags, KRB5_KDB_FLAG_MAP_PRINCIPALS);
-
-            assert(client == NULL); /* should not have been set already */
-
-            errcode = krb5_db_get_principal(context, subject_tkt->client,
-                                            c_flags, &client);
-        }
-    }
-
-    if (isflagset(c_flags, KRB5_KDB_FLAGS_S4U) && !is_referral)
-        enc_tkt_reply.client = altcprinc;
-    else
-        enc_tkt_reply.client = header_enc_tkt->client;
-
-    enc_tkt_reply.session = &session_key;
-    enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
-    enc_tkt_reply.transited.tr_contents = empty_string; /* equivalent of "" */
-
-    /*
-     * Only add the realm of the presented tgt to the transited list if
-     * it is different than the server realm (cross-realm) and it is different
-     * than the realm of the client (since the realm of the client is already
-     * implicitly part of the transited list and should not be explicitly
-     * listed).
-     */
-    if (!is_crossrealm ||
-        krb5_realm_compare(context, header_ticket->server,
-                           enc_tkt_reply.client)) {
-        /* tgt issued by local realm or issued by realm of client */
-        enc_tkt_reply.transited = header_enc_tkt->transited;
-    } else {
-        /* tgt issued by some other realm and not the realm of the client */
-        /* assemble new transited field into allocated storage */
-        if (header_enc_tkt->transited.tr_type !=
-            KRB5_DOMAIN_X500_COMPRESS) {
-            status = "VALIDATE_TRANSIT_TYPE";
-            errcode = KRB5KDC_ERR_TRTYPE_NOSUPP;
-            goto cleanup;
-        }
-        memset(&enc_tkt_reply.transited, 0, sizeof(enc_tkt_reply.transited));
-        enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
-        if ((errcode =
-             add_to_transited(&header_enc_tkt->transited.tr_contents,
-                              &enc_tkt_reply.transited.tr_contents,
-                              header_ticket->server,
-                              enc_tkt_reply.client,
-                              request->server))) {
-            status = "ADD_TO_TRANSITED_LIST";
-            goto cleanup;
-        }
-        newtransited = 1;
-    }
-    if (!isflagset (request->kdc_options, KDC_OPT_DISABLE_TRANSITED_CHECK)) {
-        errcode = kdc_check_transited_list(context,
-                                           &enc_tkt_reply.transited.tr_contents,
-                                           &header_enc_tkt->client->realm,
-                                           &request->server->realm);
-        if (errcode == 0) {
-            setflag (enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED);
-        } else {
-            log_tgs_badtrans(context, cprinc, sprinc,
-                             &enc_tkt_reply.transited.tr_contents, errcode);
-        }
-    } else
-        krb5_klog_syslog(LOG_INFO, _("not checking transit path"));
-    if (realm->realm_reject_bad_transit &&
-        !isflagset(enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED)) {
-        errcode = KRB5KDC_ERR_POLICY;
-        status = "BAD_TRANSIT";
-        au_state->violation = LOCAL_POLICY;
-        goto cleanup;
-    }
-
-    errcode = handle_authdata(realm, c_flags, client, server, subject_server,
-                              local_tgt, &local_tgt_key,
-                              subkey != NULL ? subkey :
-                              header_ticket->enc_part2->session,
-                              encrypting_key, subject_key, NULL, pkt, request,
-                              altcprinc, subject_pac, subject_tkt,
-                              &auth_indicators, &enc_tkt_reply);
-    if (errcode) {
-        krb5_klog_syslog(LOG_INFO, _("TGS_REQ : handle_authdata (%d)"),
-                         errcode);
-        status = "HANDLE_AUTHDATA";
-        goto cleanup;
-    }
-
-    ticket_reply.enc_part2 = &enc_tkt_reply;
-
-    /* If we are doing user-to-user authentication, encrypt the ticket using
-     * the session key of the second ticket. */
-    if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
-        ticket_kvno = 0;
-        ticket_reply.enc_part.enctype = stkt->enc_part2->session->enctype;
-        kau_u2u(context, TRUE, au_state);
-    } else {
-        ticket_kvno = current_kvno(server);
-    }
-
-    errcode = krb5_encrypt_tkt_part(context, encrypting_key, &ticket_reply);
-    if (errcode)
-        goto cleanup;
-    ticket_reply.enc_part.kvno = ticket_kvno;
-    /* Start assembling the response */
-    au_state->stage = ENCR_REP;
-    reply.msg_type = KRB5_TGS_REP;
-    if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) &&
-        krb5int_find_pa_data(context, request->padata,
-                             KRB5_PADATA_S4U_X509_USER) != NULL) {
-        errcode = kdc_make_s4u2self_rep(context, subkey,
-                                        header_ticket->enc_part2->session,
-                                        s4u_x509_user, &reply, &reply_encpart);
-        if (errcode)
-            au_state->status = status;
-        kau_s4u2self(context, errcode ? FALSE : TRUE, au_state);
-        if (errcode)
-            goto cleanup;
-    }
-
-    reply.client = enc_tkt_reply.client;
-    reply.enc_part.kvno = 0;/* We are using the session key */
-    reply.ticket = &ticket_reply;
-
-    reply_encpart.session = &session_key;
-    reply_encpart.nonce = request->nonce;
-
-    /* copy the time fields */
-    reply_encpart.times = enc_tkt_reply.times;
-
-    nolrentry.lr_type = KRB5_LRQ_NONE;
-    nolrentry.value = 0;
-    nolrentry.magic = 0;
-    nolrarray[0] = &nolrentry;
-    nolrarray[1] = 0;
-    reply_encpart.last_req = nolrarray;        /* not available for TGS reqs */
-    reply_encpart.key_exp = 0;/* ditto */
-    reply_encpart.flags = enc_tkt_reply.flags;
-    reply_encpart.server = ticket_reply.server;
-
-    /* use the session key in the ticket, unless there's a subsession key
-       in the AP_REQ */
-    reply.enc_part.enctype = subkey ? subkey->enctype :
-        header_ticket->enc_part2->session->enctype;
-    errcode  = kdc_fast_response_handle_padata(state, request, &reply,
-                                               subkey ? subkey->enctype : header_ticket->enc_part2->session->enctype);
-    if (errcode)
-        goto cleanup;
-    errcode =kdc_fast_handle_reply_key(state,
-                                       subkey?subkey:header_ticket->enc_part2->session, &reply_key);
-    if (errcode)
-        goto cleanup;
-    errcode = return_enc_padata(context, pkt, request, reply_key, server,
-                                &reply_encpart,
-                                is_referral &&
-                                isflagset(s_flags, KRB5_KDB_FLAG_REFERRAL_OK));
-    if (errcode) {
-        status = "KDC_RETURN_ENC_PADATA";
-        goto cleanup;
-    }
-
-    errcode = kau_make_tkt_id(context, &ticket_reply, &au_state->tkt_out_id);
-    if (errcode)
-        goto cleanup;
-
-    if (kdc_fast_hide_client(state))
-        reply.client = (krb5_principal)krb5_anonymous_principal();
-    errcode = krb5_encode_kdc_rep(context, KRB5_TGS_REP, &reply_encpart,
-                                  subkey ? 1 : 0, reply_key, &reply, response);
-    if (!errcode)
-        status = "ISSUE";
-
-    memset(ticket_reply.enc_part.ciphertext.data, 0,
-           ticket_reply.enc_part.ciphertext.length);
-    free(ticket_reply.enc_part.ciphertext.data);
-    /* these parts are left on as a courtesy from krb5_encode_kdc_rep so we
-       can use them in raw form if needed.  But, we don't... */
-    memset(reply.enc_part.ciphertext.data, 0,
-           reply.enc_part.ciphertext.length);
-    free(reply.enc_part.ciphertext.data);
-
-cleanup:
-    if (status == NULL)
-        status = "UNKNOWN_REASON";
-    krb5_free_keyblock_contents(context, &server_keyblock);
-    if (reply_key)
-        krb5_free_keyblock(context, reply_key);
-    if (stkt_server_key)
-        krb5_free_keyblock(context, stkt_server_key);
-    if (errcode)
-        emsg = krb5_get_error_message(context, errcode);
-
-    if (au_state != NULL) {
-        au_state->status = status;
-        if (!errcode)
-            au_state->reply = &reply;
-        kau_tgs_req(context, errcode ? FALSE : TRUE, au_state);
-        kau_free_kdc_req(au_state);
-    }
-
-    log_tgs_req(context, from, request, &reply, cprinc, sprinc, altcprinc,
-                authtime, c_flags, status, errcode, emsg);
-    if (errcode) {
-        krb5_free_error_message(context, emsg);
-        emsg = NULL;
-    }
-
-    if (errcode && state != NULL) {
-        int got_err = 0;
-        if (status == 0) {
-            status = krb5_get_error_message(context, errcode);
-            got_err = 1;
-        }
-
-        retval = prepare_error_tgs(state, request, header_ticket, errcode,
-                                   (server != NULL) ? server->princ : NULL,
-                                   response, status, e_data);
-        if (got_err) {
-            krb5_free_error_message(context, status);
-            status = 0;
-        }
-    }
-
-    if (header_ticket != NULL)
-        krb5_free_ticket(context, header_ticket);
-    if (request != NULL)
-        krb5_free_kdc_req(context, request);
-    if (state)
-        kdc_free_rstate(state);
-    krb5_db_free_principal(context, server);
-    krb5_db_free_principal(context, stkt_server);
-    krb5_db_free_principal(context, header_server);
-    krb5_db_free_principal(context, client);
-    krb5_db_free_principal(context, local_tgt_storage);
-    if (local_tgt_key.contents != NULL)
-        krb5_free_keyblock_contents(context, &local_tgt_key);
-    if (session_key.contents != NULL)
-        krb5_free_keyblock_contents(context, &session_key);
-    if (newtransited)
-        free(enc_tkt_reply.transited.tr_contents.data);
-    if (s4u_x509_user != NULL)
-        krb5_free_pa_s4u_x509_user(context, s4u_x509_user);
-    if (kdc_issued_auth_data != NULL)
-        krb5_free_authdata(context, kdc_issued_auth_data);
-    if (subkey != NULL)
-        krb5_free_keyblock(context, subkey);
-    if (header_key != NULL)
-        krb5_free_keyblock(context, header_key);
-    if (reply.padata)
-        krb5_free_pa_data(context, reply.padata);
-    if (reply_encpart.enc_padata)
-        krb5_free_pa_data(context, reply_encpart.enc_padata);
-    if (enc_tkt_reply.authorization_data != NULL)
-        krb5_free_authdata(context, enc_tkt_reply.authorization_data);
-    krb5_free_pa_data(context, e_data);
-    k5_free_data_ptr_list(auth_indicators);
-    krb5_pac_free(context, header_pac);
-    krb5_pac_free(context, stkt_pac);
-    krb5_free_principal(context, stkt_authdata_client);
-
-    return retval;
-}
+static krb5_error_code
+db_get_svc_princ(krb5_context, krb5_principal, krb5_flags,
+                 krb5_db_entry **, const char **);
 
 static krb5_error_code
 prepare_error_tgs(struct kdc_request_state *state, krb5_kdc_req *request,
@@ -1170,3 +579,650 @@ cleanup:
     krb5_free_principal(context, reftgs);
     return ret;
 }
+
+/*
+ * Transfer ownership of *reqptr to *t and fill *t with information about the
+ * request.  Decode the PA-TGS-REQ header ticket and the second ticket if
+ * applicable, and decode and verify their PACs if present.  Decode and verify
+ * the S4U2Self request pa-data if present.  Extract authentication indicators
+ * from the subject ticket.  Construct the transited list implied by the
+ * request.
+ */
+static krb5_error_code
+gather_tgs_req_info(kdc_realm_t *realm, krb5_kdc_req **reqptr, krb5_data *pkt,
+                    const krb5_fulladdr *from,
+                    struct kdc_request_state *fast_state,
+                    krb5_audit_state *au_state, struct tgs_req_info *t,
+                    const char **status)
+{
+    krb5_context context = realm->realm_context;
+    krb5_error_code ret;
+    krb5_pa_data *pa_tgs_req;
+    unsigned int s_flags;
+    krb5_enc_tkt_part *header_enc;
+    krb5_data d;
+
+    /* Transfer ownership of *reqptr to *t. */
+    t->req = *reqptr;
+    *reqptr = NULL;
+
+    if (t->req->msg_type != KRB5_TGS_REQ)
+        return KRB5_BADMSGTYPE;
+
+    /* Initially set t->sprinc to the outer request server, for logging of
+     * early failures. */
+    t->sprinc = t->req->server;
+
+    /* Read the PA-TGS-REQ authenticator and decrypt the header ticket. */
+    ret = kdc_process_tgs_req(realm, t->req, from, pkt, &t->header_tkt,
+                              &t->header_server, &t->header_key, &t->subkey,
+                              &pa_tgs_req);
+    if (t->header_tkt != NULL && t->header_tkt->enc_part2 != NULL)
+        t->cprinc = t->header_tkt->enc_part2->client;
+    if (ret) {
+        *status = "PROCESS_TGS";
+        return ret;
+    }
+    ret = kau_make_tkt_id(context, t->header_tkt, &au_state->tkt_in_id);
+    if (ret)
+        return ret;
+    header_enc = t->header_tkt->enc_part2;
+
+    /* If PA-FX-FAST-REQUEST padata is present, replace t->req with the inner
+     * request body. */
+    d = make_data(pa_tgs_req->contents, pa_tgs_req->length);
+    ret = kdc_find_fast(&t->req, &d, t->subkey, header_enc->session,
+                        fast_state, NULL);
+    if (ret) {
+        *status = "FIND_FAST";
+        return ret;
+    }
+    /* Reset t->sprinc for the inner body and check it. */
+    t->sprinc = t->req->server;
+    if (t->sprinc == NULL) {
+        *status = "NULL_SERVER";
+        return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+    }
+
+    /* The header ticket server is usually a TGT, but if it is not, fetch the
+     * local TGT for the realm.  Get the decrypted first local TGT key. */
+    ret = get_local_tgt(context, &t->sprinc->realm, t->header_server,
+                        &t->local_tgt, &t->local_tgt_storage,
+                        &t->local_tgt_key);
+    if (ret) {
+        *status = "GET_LOCAL_TGT";
+        return ret;
+    }
+
+    /* Decode and verify the header ticket PAC. */
+    ret = get_verified_pac(context, header_enc, t->header_server->princ,
+                           t->header_key, t->local_tgt, &t->local_tgt_key,
+                           &t->header_pac);
+    if (ret) {
+        *status = "HEADER_PAC";
+        return ret;
+    }
+
+    au_state->request = t->req;
+    au_state->stage = SRVC_PRINC;
+
+    /* Look up the server principal entry, or a referral/alternate TGT.  Reset
+     * t->sprinc to the canonical server name (its final value). */
+    s_flags = (t->req->kdc_options & KDC_OPT_CANONICALIZE) ?
+        KRB5_KDB_FLAG_REFERRAL_OK : 0;
+    ret = search_sprinc(realm, t->req, s_flags, &t->server, status);
+    if (ret)
+        return ret;
+    t->sprinc = t->server->princ;
+
+    /* If we got a cross-realm TGS which is not the requested server, we are
+     * issuing a referral (or alternate TGT, which we treat similarly). */
+    if (is_cross_tgs_principal(t->server->princ) &&
+        !krb5_principal_compare(context, t->req->server, t->server->princ))
+        t->flags |= KRB5_KDB_FLAG_ISSUING_REFERRAL;
+
+    /* Mark the request as cross-realm if the header ticket server is not from
+     * this realm. */
+    if (!data_eq(t->header_server->princ->realm, t->sprinc->realm))
+        t->flags |= KRB5_KDB_FLAG_CROSS_REALM;
+
+    t->is_referral = (t->flags & KRB5_KDB_FLAG_ISSUING_REFERRAL);
+    t->is_crossrealm = (t->flags & KRB5_KDB_FLAG_CROSS_REALM);
+
+    /* If S4U2Self padata is present, read it to get the requested principal
+     * name.  Look up the requested client if it is in this realm. */
+    ret = kdc_process_s4u2self_req(context, t->req, t->server, t->subkey,
+                                   header_enc->session, &t->s4u2self,
+                                   &t->client, status);
+    if (t->s4u2self != NULL || ret) {
+        if (t->s4u2self != NULL)
+            au_state->s4u2self_user = t->s4u2self->user_id.user;
+        au_state->status = *status;
+        kau_s4u2self(context, !ret, au_state);
+        au_state->s4u2self_user = NULL;
+    }
+    if (ret)
+        return ret;
+    if (t->s4u2self != NULL) {
+        t->flags |= KRB5_KDB_FLAG_PROTOCOL_TRANSITION;
+        t->s4u_cprinc = t->s4u2self->user_id.user;
+
+        /*
+         * For consistency with Active Directory, don't allow authorization
+         * data to be disabled if S4U2Self is requested.  The requesting
+         * service likely needs a PAC for an S4U2Proxy operation, even if it
+         * doesn't need authorization data in tickets received from clients.
+         */
+        t->server->attributes &= ~KRB5_KDB_NO_AUTH_DATA_REQUIRED;
+    }
+
+    /* For U2U or S4U2Proxy requests, decrypt the second ticket and read its
+     * PAC. */
+    ret = decrypt_2ndtkt(context, t->req, t->flags, t->local_tgt,
+                         &t->local_tgt_key, &t->stkt, &t->stkt_pac,
+                         &t->stkt_server, &t->stkt_server_key, status);
+    if (ret)
+        return ret;
+
+    /* Determine the subject ticket and set the authtime for logging.  For
+     * S4U2Proxy requests determine the requested client principal. */
+    if (t->req->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
+        t->flags |= KRB5_KDB_FLAG_CONSTRAINED_DELEGATION;
+        ret = kau_make_tkt_id(context, t->stkt, &au_state->evid_tkt_id);
+        if (ret)
+            return ret;
+        if (t->is_crossrealm) {
+            /* For cross-realm S4U2PROXY requests, the second ticket is a
+             * cross TGT with the requested client principal in its PAC. */
+            if (t->stkt_pac == NULL ||
+                get_pac_princ_with_realm(context, t->stkt_pac,
+                                         &t->stkt_pac_client, NULL) != 0) {
+                au_state->status = *status = "RBCD_PAC_PRINC";
+                au_state->violation = PROT_CONSTRAINT;
+                kau_s4u2proxy(context, FALSE, au_state);
+                return KRB5KDC_ERR_BADOPTION;
+            }
+            t->s4u_cprinc = t->stkt_pac_client;
+        } else {
+            /* Otherwise the requested client is the evidence ticket client. */
+            t->s4u_cprinc = t->stkt->enc_part2->client;
+        }
+        t->subject_tkt = t->stkt->enc_part2;
+    } else {
+        t->subject_tkt = header_enc;
+    }
+    t->authtime = t->subject_tkt->times.authtime;
+
+    /* For final S4U requests (either type) the issued ticket will be for the
+     * requested name; otherwise it will be for the header ticket client. */
+    t->tkt_client = ((t->flags & KRB5_KDB_FLAGS_S4U) && !t->is_referral) ?
+        t->s4u_cprinc : header_enc->client;
+
+    if (t->s4u2self == NULL) {
+        /* Extract auth indicators from the subject ticket.  Skip this for
+         * S4U2Self requests as the subject didn't authenticate. */
+        ret = get_auth_indicators(context, t->subject_tkt, t->local_tgt,
+                                  &t->local_tgt_key, &t->auth_indicators);
+        if (ret) {
+            *status = "GET_AUTH_INDICATORS";
+            return ret;
+        }
+
+        if (!(t->server->attributes & KRB5_KDB_NO_AUTH_DATA_REQUIRED)) {
+            /* Try to look up the subject principal so that KDB modules can add
+             * additional authdata.  Ask the KDB to map foreign principals. */
+            assert(t->client == NULL);
+            (void)krb5_db_get_principal(context, t->subject_tkt->client,
+                                        t->flags | KRB5_KDB_FLAG_CLIENT |
+                                        KRB5_KDB_FLAG_MAP_PRINCIPALS,
+                                        &t->client);
+        }
+    }
+
+    /*
+     * Compute the transited list implied by the request.  Use the existing
+     * transited list if the realm of the header ticket server is the same as
+     * the subject or server realm.
+     */
+    if (!t->is_crossrealm ||
+        data_eq(t->header_tkt->server->realm, t->tkt_client->realm)) {
+        t->transited = header_enc->transited;
+    } else {
+        if (header_enc->transited.tr_type != KRB5_DOMAIN_X500_COMPRESS) {
+            *status = "VALIDATE_TRANSIT_TYPE";
+            return KRB5KDC_ERR_TRTYPE_NOSUPP;
+        }
+        ret = add_to_transited(&header_enc->transited.tr_contents,
+                               &t->new_transited, t->header_tkt->server,
+                               t->tkt_client, t->req->server);
+        if (ret) {
+            *status = "ADD_TO_TRANSITED_LIST";
+            return ret;
+        }
+        t->transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
+        t->transited.tr_contents = t->new_transited;
+    }
+
+    return 0;
+}
+
+/* Fill in *times_out with the times of the ticket to be issued.  Set the
+ * TKT_FLG_RENEWABLE bit in *tktflags if the ticket will be renewable. */
+static void
+compute_ticket_times(kdc_realm_t *realm, struct tgs_req_info *t,
+                     krb5_timestamp kdc_time, krb5_flags *tktflags,
+                     krb5_ticket_times *times)
+{
+    krb5_timestamp hstarttime;
+    krb5_deltat hlife;
+    krb5_ticket_times *htimes = &t->header_tkt->enc_part2->times;
+
+    if (t->req->kdc_options & KDC_OPT_VALIDATE) {
+        /* Validation requests preserve the header ticket times. */
+        *times = *htimes;
+        return;
+    }
+
+    /* Preserve the authtime from the subject ticket. */
+    times->authtime = t->authtime;
+
+    times->starttime = (t->req->kdc_options & KDC_OPT_POSTDATED) ?
+        t->req->from : kdc_time;
+
+    if (t->req->kdc_options & KDC_OPT_RENEW) {
+        /* Give the new ticket the same lifetime as the header ticket, but no
+         * later than the renewable end time. */
+        hstarttime = htimes->starttime ? htimes->starttime : htimes->authtime;
+        hlife = ts_delta(htimes->endtime, hstarttime);
+        times->endtime = ts_min(htimes->renew_till,
+                                ts_incr(times->starttime, hlife));
+    } else {
+        kdc_get_ticket_endtime(realm, times->starttime, htimes->endtime,
+                               t->req->till, t->client, t->server,
+                               &times->endtime);
+    }
+
+    kdc_get_ticket_renewtime(realm, t->req, t->header_tkt->enc_part2,
+                             t->client, t->server, tktflags, times);
+
+    /* starttime is optional, and treated as authtime if not present.
+     * so we can omit it if it matches. */
+    if (times->starttime == times->authtime)
+        times->starttime = 0;
+}
+
+/* Check the request in *t against semantic protocol constraints and local
+ * policy.  Determine flags and times for the ticket to be issued. */
+static krb5_error_code
+check_tgs_req(kdc_realm_t *realm, struct tgs_req_info *t,
+              krb5_audit_state *au_state, krb5_flags *tktflags,
+              krb5_ticket_times *times, const char **status,
+              krb5_pa_data ***e_data)
+{
+    krb5_context context = realm->realm_context;
+    krb5_error_code ret;
+    krb5_timestamp kdc_time;
+
+    au_state->stage = VALIDATE_POL;
+
+    ret = krb5_timeofday(context, &kdc_time);
+    if (ret)
+        return ret;
+
+    ret = check_tgs_constraints(realm, t->req, t->server, t->header_tkt,
+                                t->header_pac, t->stkt, t->stkt_pac,
+                                t->stkt_server, kdc_time, t->s4u2self,
+                                t->client, t->is_crossrealm, t->is_referral,
+                                status, e_data);
+    if (ret) {
+        au_state->violation = PROT_CONSTRAINT;
+        return ret;
+    }
+
+    ret = check_tgs_policy(realm, t->req, t->server, t->header_tkt,
+                           t->header_pac, t->stkt, t->stkt_pac,
+                           t->stkt_pac_client, t->stkt_server, kdc_time,
+                           t->is_crossrealm, t->is_referral, status, e_data);
+    if (ret) {
+        au_state->violation = LOCAL_POLICY;
+        if (t->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+            au_state->status = *status;
+            kau_s4u2proxy(context, FALSE, au_state);
+        }
+        return ret;
+    }
+
+    /* Check auth indicators from the subject ticket, except for S4U2Self
+     * requests (where the client didn't authenticate). */
+    if (t->s4u2self == NULL) {
+        ret = check_indicators(context, t->server, t->auth_indicators);
+        if (ret) {
+            *status = "HIGHER_AUTHENTICATION_REQUIRED";
+            return ret;
+        }
+    }
+
+    *tktflags = get_ticket_flags(t->req->kdc_options, t->client, t->server,
+                                 t->header_tkt->enc_part2);
+    compute_ticket_times(realm, t, kdc_time, tktflags, times);
+
+    /* For S4U2Self requests, check if we need to suppress the forwardable
+     * ticket flag. */
+    if (t->s4u2self != NULL && !t->is_referral) {
+        ret = s4u2self_forwardable(context, t->server, tktflags);
+        if (ret)
+            return ret;
+    }
+
+    /* Consult kdcpolicy modules, giving them a chance to modify the times of
+     * the issued ticket. */
+    ret = check_kdcpolicy_tgs(context, t->req, t->server, t->header_tkt,
+                              t->auth_indicators, kdc_time, times, status);
+    if (ret)
+        return ret;
+
+    if (!(t->req->kdc_options & KDC_OPT_DISABLE_TRANSITED_CHECK)) {
+        /* Check the transited path for the issued ticket and set the
+         * transited-policy-checked flag if successful. */
+        ret = kdc_check_transited_list(context, &t->transited.tr_contents,
+                                       &t->subject_tkt->client->realm,
+                                       &t->req->server->realm);
+        if (ret) {
+            /* Log the transited-check failure and continue. */
+            log_tgs_badtrans(context, t->cprinc, t->sprinc,
+                             &t->transited.tr_contents, ret);
+        } else {
+            *tktflags |= TKT_FLG_TRANSIT_POLICY_CHECKED;
+        }
+    } else {
+        krb5_klog_syslog(LOG_INFO, _("not checking transit path"));
+    }
+
+    /* By default, reject the request if the transited path was not checked
+     * successfully. */
+    if (realm->realm_reject_bad_transit &&
+        !(*tktflags & TKT_FLG_TRANSIT_POLICY_CHECKED)) {
+        *status = "BAD_TRANSIT";
+        au_state->violation = LOCAL_POLICY;
+        return KRB5KDC_ERR_POLICY;
+    }
+
+    return 0;
+}
+
+/* Construct a response issuing a ticket for the request in *t, using tktflags
+ * and *times for the ticket flags and times. */
+static krb5_error_code
+tgs_issue_ticket(kdc_realm_t *realm, struct tgs_req_info *t,
+                 krb5_flags tktflags, krb5_ticket_times *times, krb5_data *pkt,
+                 const krb5_fulladdr *from,
+                 struct kdc_request_state *fast_state,
+                 krb5_audit_state *au_state, const char **status,
+                 krb5_data **response)
+{
+    krb5_context context = realm->realm_context;
+    krb5_error_code ret;
+    krb5_keyblock session_key = { 0 }, server_key = { 0 };
+    krb5_keyblock *ticket_encrypting_key, *subject_key;
+    krb5_keyblock *initial_reply_key, *fast_reply_key = NULL;
+    krb5_enc_tkt_part enc_tkt_reply = { 0 };
+    krb5_ticket ticket_reply = { 0 };
+    krb5_enc_kdc_rep_part reply_encpart = { 0 };
+    krb5_kdc_rep reply = { 0 };
+    krb5_pac subject_pac;
+    krb5_db_entry *subject_server;
+    krb5_enc_tkt_part *header_enc_tkt = t->header_tkt->enc_part2;
+    krb5_last_req_entry nolrentry = { KV5M_LAST_REQ_ENTRY, KRB5_LRQ_NONE, 0 };
+    krb5_last_req_entry *nolrarray[2] = { &nolrentry, NULL };
+
+    au_state->stage = ISSUE_TKT;
+
+    ret = gen_session_key(context, t->req, t->server, &session_key, status);
+    if (ret)
+        goto cleanup;
+
+    if (t->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+        subject_pac = t->stkt_pac;
+        subject_server = t->stkt_server;
+        subject_key = t->stkt_server_key;
+    } else {
+        subject_pac = t->header_pac;
+        subject_server = t->header_server;
+        subject_key = t->header_key;
+    }
+
+    initial_reply_key = (t->subkey != NULL) ? t->subkey :
+        t->header_tkt->enc_part2->session;
+
+    if (t->req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
+        /* For user-to-user, encrypt the ticket with the second ticket's
+         * session key. */
+        ticket_encrypting_key = t->stkt->enc_part2->session;
+    } else {
+        /* Otherwise encrypt the ticket with the server entry's first long-term
+         * key. */
+        ret = get_first_current_key(context, t->server, &server_key);
+        if (ret) {
+            *status = "FINDING_SERVER_KEY";
+            goto cleanup;
+        }
+        ticket_encrypting_key = &server_key;
+    }
+
+    if (t->req->kdc_options & (KDC_OPT_VALIDATE | KDC_OPT_RENEW)) {
+        /* Copy the whole header ticket except for authorization data. */
+        ticket_reply = *t->header_tkt;
+        enc_tkt_reply = *t->header_tkt->enc_part2;
+        enc_tkt_reply.authorization_data = NULL;
+    } else {
+        if (t->req->kdc_options & (KDC_OPT_FORWARDED | KDC_OPT_PROXY)) {
+            /* Include the requested addresses in the ticket and reply. */
+            enc_tkt_reply.caddrs = t->req->addresses;
+            reply_encpart.caddrs = t->req->addresses;
+        } else {
+            /* Use the header ticket addresses and omit them from the reply. */
+            enc_tkt_reply.caddrs = header_enc_tkt->caddrs;
+            reply_encpart.caddrs = NULL;
+        }
+
+        ticket_reply.server = t->is_referral ? t->sprinc : t->req->server;
+    }
+
+    enc_tkt_reply.flags = tktflags;
+    enc_tkt_reply.times = *times;
+    enc_tkt_reply.client = t->tkt_client;
+    enc_tkt_reply.session = &session_key;
+    enc_tkt_reply.transited = t->transited;
+
+    ret = handle_authdata(realm, t->flags, t->client, t->server,
+                          subject_server, t->local_tgt, &t->local_tgt_key,
+                          initial_reply_key, ticket_encrypting_key,
+                          subject_key, NULL, pkt, t->req, t->s4u_cprinc,
+                          subject_pac, t->subject_tkt, &t->auth_indicators,
+                          &enc_tkt_reply);
+    if (ret) {
+        krb5_klog_syslog(LOG_INFO, _("TGS_REQ : handle_authdata (%d)"), ret);
+        *status = "HANDLE_AUTHDATA";
+        goto cleanup;
+    }
+
+    ticket_reply.enc_part2 = &enc_tkt_reply;
+
+    ret = krb5_encrypt_tkt_part(context, ticket_encrypting_key, &ticket_reply);
+    if (ret)
+        goto cleanup;
+
+    if (t->req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) {
+        ticket_reply.enc_part.kvno = 0;
+        kau_u2u(context, TRUE, au_state);
+    } else {
+        ticket_reply.enc_part.kvno = current_kvno(t->server);
+    }
+
+    au_state->stage = ENCR_REP;
+
+    if (t->s4u2self != NULL &&
+        krb5int_find_pa_data(context, t->req->padata,
+                             KRB5_PADATA_S4U_X509_USER) != NULL) {
+        /* Add an S4U2Self response to the encrypted padata (skipped if the
+         * request only included PA-FOR-USER padata). */
+        ret = kdc_make_s4u2self_rep(context, t->subkey,
+                                    t->header_tkt->enc_part2->session,
+                                    t->s4u2self, &reply, &reply_encpart);
+        if (ret)
+            goto cleanup;
+    }
+
+    reply_encpart.session = &session_key;
+    reply_encpart.nonce = t->req->nonce;
+    reply_encpart.times = enc_tkt_reply.times;
+    reply_encpart.last_req = nolrarray;
+    reply_encpart.key_exp = 0;
+    reply_encpart.flags = enc_tkt_reply.flags;
+    reply_encpart.server = ticket_reply.server;
+
+    reply.msg_type = KRB5_TGS_REP;
+    reply.client = enc_tkt_reply.client;
+    reply.ticket = &ticket_reply;
+    reply.enc_part.kvno = 0;
+    reply.enc_part.enctype = initial_reply_key->enctype;
+    ret = kdc_fast_response_handle_padata(fast_state, t->req, &reply,
+                                          initial_reply_key->enctype);
+    if (ret)
+        goto cleanup;
+    ret = kdc_fast_handle_reply_key(fast_state, initial_reply_key,
+                                    &fast_reply_key);
+    if (ret)
+        goto cleanup;
+    ret = return_enc_padata(context, pkt, t->req, fast_reply_key, t->server,
+                            &reply_encpart,
+                            t->is_referral &&
+                            (t->req->kdc_options & KDC_OPT_CANONICALIZE));
+    if (ret) {
+        *status = "KDC_RETURN_ENC_PADATA";
+        goto cleanup;
+    }
+
+    ret = kau_make_tkt_id(context, &ticket_reply, &au_state->tkt_out_id);
+    if (ret)
+        goto cleanup;
+
+    if (kdc_fast_hide_client(fast_state))
+        reply.client = (krb5_principal)krb5_anonymous_principal();
+    ret = krb5_encode_kdc_rep(context, KRB5_TGS_REP, &reply_encpart,
+                              t->subkey != NULL, fast_reply_key, &reply,
+                              response);
+    if (ret)
+        goto cleanup;
+
+    log_tgs_req(context, from, t->req, &reply, t->cprinc, t->sprinc,
+                t->s4u_cprinc, t->authtime, t->flags, "ISSUE", 0, NULL);
+    au_state->status = "ISSUE";
+    au_state->reply = &reply;
+    if (t->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)
+        kau_s4u2proxy(context, TRUE, au_state);
+    kau_tgs_req(context, TRUE, au_state);
+    au_state->reply = NULL;
+
+cleanup:
+    zapfree(ticket_reply.enc_part.ciphertext.data,
+            ticket_reply.enc_part.ciphertext.length);
+    zapfree(reply.enc_part.ciphertext.data, reply.enc_part.ciphertext.length);
+    krb5_free_pa_data(context, reply.padata);
+    krb5_free_pa_data(context, reply_encpart.enc_padata);
+    krb5_free_authdata(context, enc_tkt_reply.authorization_data);
+    krb5_free_keyblock_contents(context, &session_key);
+    krb5_free_keyblock_contents(context, &server_key);
+    krb5_free_keyblock(context, fast_reply_key);
+    return ret;
+}
+
+static void
+free_req_info(krb5_context context, struct tgs_req_info *t)
+{
+    krb5_free_kdc_req(context, t->req);
+    krb5_free_ticket(context, t->header_tkt);
+    krb5_db_free_principal(context, t->header_server);
+    krb5_free_keyblock(context, t->header_key);
+    krb5_free_keyblock(context, t->subkey);
+    krb5_pac_free(context, t->header_pac);
+    krb5_pac_free(context, t->stkt_pac);
+    krb5_db_free_principal(context, t->stkt_server);
+    krb5_free_keyblock(context, t->stkt_server_key);
+    krb5_db_free_principal(context, t->local_tgt_storage);
+    krb5_free_keyblock_contents(context, &t->local_tgt_key);
+    krb5_db_free_principal(context, t->server);
+    krb5_db_free_principal(context, t->client);
+    krb5_free_pa_s4u_x509_user(context, t->s4u2self);
+    krb5_free_principal(context, t->stkt_pac_client);
+    k5_free_data_ptr_list(t->auth_indicators);
+    krb5_free_data_contents(context, &t->new_transited);
+}
+
+krb5_error_code
+process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
+                const krb5_fulladdr *from, kdc_realm_t *realm,
+                krb5_data **response)
+{
+    krb5_context context = realm->realm_context;
+    krb5_error_code ret;
+    struct tgs_req_info t = { 0 };
+    struct kdc_request_state *fast_state = NULL;
+    krb5_audit_state *au_state = NULL;
+    krb5_pa_data **e_data = NULL;
+    krb5_flags tktflags;
+    krb5_ticket_times times = { 0 };
+    const char *emsg = NULL, *status = NULL;
+
+    ret = kdc_make_rstate(realm, &fast_state);
+    if (ret)
+        goto cleanup;
+    ret = kau_init_kdc_req(context, request, from, &au_state);
+    if (ret)
+        goto cleanup;
+    kau_tgs_req(context, TRUE, au_state);
+
+    ret = gather_tgs_req_info(realm, &request, pkt, from, fast_state, au_state,
+                              &t, &status);
+    if (ret)
+        goto cleanup;
+
+    ret = check_tgs_req(realm, &t, au_state, &tktflags, &times, &status,
+                        &e_data);
+    if (ret)
+        goto cleanup;
+
+    ret = tgs_issue_ticket(realm, &t, tktflags, &times, pkt, from, fast_state,
+                           au_state, &status, response);
+    if (ret)
+        goto cleanup;
+
+cleanup:
+    if (status == NULL)
+        status = "UNKNOWN_REASON";
+
+    if (ret) {
+        emsg = krb5_get_error_message(context, ret);
+        log_tgs_req(context, from, t.req, NULL, t.cprinc, t.sprinc,
+                    t.s4u_cprinc, t.authtime, t.flags, status, ret, emsg);
+        krb5_free_error_message(context, emsg);
+
+        if (au_state != NULL) {
+            au_state->status = status;
+            kau_tgs_req(context, FALSE, au_state);
+        }
+    }
+
+    if (ret && fast_state != NULL) {
+        ret = prepare_error_tgs(fast_state, t.req, t.header_tkt, ret,
+                                (t.server != NULL) ? t.server->princ : NULL,
+                                response, status, e_data);
+    }
+
+    krb5_free_kdc_req(context, request);
+    kdc_free_rstate(fast_state);
+    kau_free_kdc_req(au_state);
+    free_req_info(context, &t);
+    krb5_free_pa_data(context, e_data);
+    return ret;
+}
diff --git a/src/kdc/fast_util.c b/src/kdc/fast_util.c
index 9044a1844..7a6579f1d 100644
--- a/src/kdc/fast_util.c
+++ b/src/kdc/fast_util.c
@@ -262,6 +262,8 @@ kdc_make_rstate(kdc_realm_t *active_realm, struct kdc_request_state **out)
 void
 kdc_free_rstate (struct kdc_request_state *s)
 {
+    if (s == NULL)
+        return;
     if (s->armor_key)
         krb5_free_keyblock(s->realm_data->realm_context, s->armor_key);
     if (s->strengthen_key)
diff --git a/src/kdc/kdc_audit.c b/src/kdc/kdc_audit.c
index f40913dc8..2333171d7 100644
--- a/src/kdc/kdc_audit.c
+++ b/src/kdc/kdc_audit.c
@@ -206,6 +206,8 @@ kau_init_kdc_req(krb5_context context,
 void
 kau_free_kdc_req(krb5_audit_state *state)
 {
+    if (state == NULL)
+        return;
     free(state->tkt_in_id);
     free(state->tkt_out_id);
     free(state->evid_tkt_id);
diff --git a/src/kdc/kdc_log.c b/src/kdc/kdc_log.c
index f86e0780d..9a8894c9a 100644
--- a/src/kdc/kdc_log.c
+++ b/src/kdc/kdc_log.c
@@ -149,7 +149,8 @@ log_tgs_req(krb5_context ctx, const krb5_fulladdr *from,
        important).  */
     if (errcode != KRB5KDC_ERR_SERVER_NOMATCH) {
         ktypestr = ktypes2str(request->ktype, request->nktypes);
-        rep_etypestr = rep_etypes2str(reply);
+        if (reply != NULL)
+            rep_etypestr = rep_etypes2str(reply);
         krb5_klog_syslog(LOG_INFO, _("TGS_REQ (%s) %s: %s: authtime %u, %s%s "
                                      "%s for %s%s%s"),
                          ktypestr ? ktypestr : "", fromstring, status,
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index d6c61f4aa..f5cb2abf8 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -625,15 +625,6 @@ fetch_last_req_info(krb5_db_entry *dbentry, krb5_last_req_entry ***lrentry)
 }
 
 
-/* XXX!  This is a temporary place-holder */
-
-krb5_error_code
-check_hot_list(krb5_ticket *ticket)
-{
-    return 0;
-}
-
-
 /* Convert an API error code to a protocol error code. */
 int
 errcode_to_protocol(krb5_error_code code)
@@ -762,6 +753,10 @@ get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client,
 {
     krb5_flags flags;
 
+    /* Validation and renewal TGS requests preserve the header ticket flags. */
+    if ((reqflags & (KDC_OPT_VALIDATE | KDC_OPT_RENEW)) && header_enc != NULL)
+        return header_enc->flags & ~TKT_FLG_INVALID;
+
     /* Indicate support for encrypted padata (RFC 6806), and set flags based on
      * request options and the header ticket. */
     flags = OPTS2FLAGS(reqflags) | TKT_FLG_ENC_PA_REP;
@@ -1553,7 +1548,7 @@ kdc_process_s4u2self_req(krb5_context context, krb5_kdc_req *request,
  * S4U2Self tickets according to [MS-SFU] 3.2.5.1.2. */
 krb5_error_code
 s4u2self_forwardable(krb5_context context, krb5_db_entry *server,
-                     krb5_enc_tkt_part *tkt)
+                     krb5_flags *tktflags)
 {
     krb5_error_code ret;
 
@@ -1565,98 +1560,13 @@ s4u2self_forwardable(krb5_context context, krb5_db_entry *server,
      * targets for traditional S4U2Proxy. */
     ret = krb5_db_check_allowed_to_delegate(context, NULL, server, NULL);
     if (!ret)
-        tkt->flags &= ~TKT_FLG_FORWARDABLE;
+        *tktflags &= ~TKT_FLG_FORWARDABLE;
 
     if (ret == KRB5KDC_ERR_BADOPTION || ret == KRB5_PLUGIN_OP_NOTSUPP)
         return 0;
     return ret;
 }
 
-/*
- * Determine if an S4U2Proxy request is authorized.  Set **stkt_ad_info to the
- * KDB authdata handle for the second ticket if the KDB module supplied one.
- * Set *stkt_authdata_client to the subject client name if the KDB module
- * supplied one; it must do so for a cross-realm request to be authorized.
- */
-krb5_error_code
-kdc_process_s4u2proxy_req(krb5_context context, unsigned int flags,
-                          krb5_kdc_req *request, krb5_pac header_pac,
-                          const krb5_enc_tkt_part *t2enc, krb5_pac t2_pac,
-                          const krb5_db_entry *server,
-                          krb5_keyblock *server_key,
-                          krb5_const_principal server_princ,
-                          const krb5_db_entry *proxy,
-                          krb5_principal *stkt_pac_client,
-                          const char **status)
-{
-    krb5_error_code errcode;
-    krb5_boolean support_rbcd;
-    krb5_principal client_princ = t2enc->client, t2_pac_princ = NULL;
-
-    *stkt_pac_client = NULL;
-
-    /* Check if the client supports resource-based constrained delegation. */
-    errcode = kdc_get_pa_pac_rbcd(context, request->padata, &support_rbcd);
-    if (errcode)
-        return errcode;
-
-    /* For an RBCD final request, recover the reply ticket client name from
-     * the evidence ticket PAC. */
-    if (flags & KRB5_KDB_FLAG_CROSS_REALM) {
-        if (get_pac_princ_with_realm(context, t2_pac, &t2_pac_princ,
-                                     NULL) != 0) {
-            *status = "RBCD_PAC_PRINC";
-            errcode = KRB5KDC_ERR_BADOPTION;
-            goto done;
-        }
-        client_princ = t2_pac_princ;
-    }
-
-    /* If both are in the same realm, try allowed_to_delegate first. */
-    if (krb5_realm_compare(context, server->princ, request->server)) {
-
-        errcode = krb5_db_check_allowed_to_delegate(context, client_princ,
-                                                    server, request->server);
-        if (errcode != KRB5KDC_ERR_BADOPTION &&
-            errcode != KRB5_PLUGIN_OP_NOTSUPP)
-            goto done;
-
-        /* Fall back to resource-based constrained-delegation. */
-    }
-
-    if (!support_rbcd) {
-        *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
-        errcode = KRB5KDC_ERR_BADOPTION;
-        goto done;
-    }
-
-    /* If we are issuing a referral, the KDC in the resource realm will check
-     * if delegation is allowed. */
-    if (isflagset(flags, KRB5_KDB_FLAG_ISSUING_REFERRAL)) {
-        errcode = 0;
-        goto done;
-    }
-
-    errcode = krb5_db_allowed_to_delegate_from(context, client_princ,
-                                               server_princ, header_pac,
-                                               proxy);
-    if (errcode == KRB5_PLUGIN_OP_NOTSUPP) {
-        *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
-        errcode = KRB5KDC_ERR_BADOPTION;
-        goto done;
-    } else if (errcode) {
-        *status = "NOT_ALLOWED_TO_DELEGATE";
-        goto done;
-    }
-
-    *stkt_pac_client = t2_pac_princ;
-    t2_pac_princ = NULL;
-
-done:
-    krb5_free_principal(context, t2_pac_princ);
-    return errcode;
-}
-
 krb5_error_code
 kdc_check_transited_list(krb5_context context, const krb5_data *trans,
                          const krb5_data *realm1, const krb5_data *realm2)
@@ -1718,19 +1628,21 @@ kdc_get_ticket_endtime(kdc_realm_t *realm, krb5_timestamp starttime,
 }
 
 /*
- * Set tkt->renew_till to the requested renewable lifetime as modified by
- * policy.  Set the TKT_FLG_RENEWABLE flag if we set a nonzero renew_till.
- * client and tgt may be NULL.
+ * Set times->renew_till to the requested renewable lifetime as modified by
+ * policy.  Set the TKT_FLG_RENEWABLE bit in *tktflags if we set a nonzero
+ * renew_till.  *times must be filled in except for renew_till.  client and tgt
+ * may be NULL.
  */
 void
 kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
                          krb5_enc_tkt_part *tgt, krb5_db_entry *client,
-                         krb5_db_entry *server, krb5_enc_tkt_part *tkt)
+                         krb5_db_entry *server, krb5_flags *tktflags,
+                         krb5_ticket_times *times)
 {
     krb5_timestamp rtime, max_rlife;
 
-    clear(tkt->flags, TKT_FLG_RENEWABLE);
-    tkt->times.renew_till = 0;
+    *tktflags &= ~TKT_FLG_RENEWABLE;
+    times->renew_till = 0;
 
     /* Don't issue renewable tickets if the client or server don't allow it,
      * or if this is a TGS request and the TGT isn't renewable. */
@@ -1745,7 +1657,7 @@ kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
     if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE))
         rtime = request->rtime ? request->rtime : kdc_infinity;
     else if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE_OK) &&
-             ts_after(request->till, tkt->times.endtime))
+             ts_after(request->till, times->endtime))
         rtime = request->till;
     else
         return;
@@ -1756,16 +1668,16 @@ kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
     max_rlife = min(server->max_renewable_life, realm->realm_maxrlife);
     if (client != NULL)
         max_rlife = min(max_rlife, client->max_renewable_life);
-    rtime = ts_min(rtime, ts_incr(tkt->times.starttime, max_rlife));
+    rtime = ts_min(rtime, ts_incr(times->starttime, max_rlife));
 
     /* If the client only specified renewable-ok, don't issue a renewable
      * ticket unless the truncated renew time exceeds the ticket end time. */
     if (!isflagset(request->kdc_options, KDC_OPT_RENEWABLE) &&
-        !ts_after(rtime, tkt->times.endtime))
+        !ts_after(rtime, times->endtime))
         return;
 
-    setflag(tkt->flags, TKT_FLG_RENEWABLE);
-    tkt->times.renew_till = rtime;
+    *tktflags |= TKT_FLG_RENEWABLE;
+    times->renew_till = rtime;
 }
 
 /**
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 852d9c025..684201308 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -36,7 +36,6 @@
 #include "realm_data.h"
 #include "reqstate.h"
 
-krb5_error_code check_hot_list (krb5_ticket *);
 krb5_boolean krb5_is_tgs_principal (krb5_const_principal);
 krb5_boolean is_cross_tgs_principal(krb5_const_principal);
 krb5_boolean is_local_tgs_principal(krb5_const_principal);
@@ -84,14 +83,23 @@ validate_as_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
                      const char **, krb5_pa_data ***);
 
 krb5_error_code
-validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
-                     krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
-                     const krb5_ticket *stkt, krb5_pac stkt_pac,
-                     krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
-                     krb5_pa_s4u_x509_user *s4u_x509_user,
-                     krb5_db_entry *s4u2self_client,
-                     krb5_boolean is_crossrealm, krb5_boolean is_referral,
-                     const char **status, krb5_pa_data ***e_data);
+check_tgs_constraints(kdc_realm_t *realm, krb5_kdc_req *request,
+                      krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
+                      const krb5_ticket *stkt, krb5_pac stkt_pac,
+                      krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
+                      krb5_pa_s4u_x509_user *s4u_x509_user,
+                      krb5_db_entry *s4u2self_client,
+                      krb5_boolean is_crossrealm, krb5_boolean is_referral,
+                      const char **status, krb5_pa_data ***e_data);
+
+krb5_error_code
+check_tgs_policy(kdc_realm_t *realm, krb5_kdc_req *request,
+                 krb5_db_entry *server, krb5_ticket *ticket,
+                 krb5_pac pac, const krb5_ticket *stkt, krb5_pac stkt_pac,
+                 krb5_principal stkt_pac_client, krb5_db_entry *stkt_server,
+                 krb5_timestamp kdc_time, krb5_boolean is_crossrealm,
+                 krb5_boolean is_referral, const char **status,
+                 krb5_pa_data ***e_data);
 
 krb5_flags
 get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client,
@@ -275,7 +283,7 @@ kdc_process_s4u2self_req(krb5_context context, krb5_kdc_req *request,
 
 krb5_error_code
 s4u2self_forwardable(krb5_context context, krb5_db_entry *server,
-                     krb5_enc_tkt_part *enc_tkt);
+                     krb5_flags *tktflags);
 
 krb5_error_code
 kdc_make_s4u2self_rep (krb5_context context,
@@ -285,17 +293,6 @@ kdc_make_s4u2self_rep (krb5_context context,
                        krb5_kdc_rep *reply,
                        krb5_enc_kdc_rep_part *reply_encpart);
 
-krb5_error_code
-kdc_process_s4u2proxy_req(krb5_context context, unsigned int flags,
-                          krb5_kdc_req *request, krb5_pac header_pac,
-                          const krb5_enc_tkt_part *t2enc, krb5_pac t2_pac,
-                          const krb5_db_entry *server,
-                          krb5_keyblock *server_key,
-                          krb5_const_principal server_princ,
-                          const krb5_db_entry *proxy,
-                          krb5_principal *stkt_ad_client,
-                          const char **status);
-
 krb5_error_code
 kdc_check_transited_list(krb5_context context, const krb5_data *trans,
                          const krb5_data *realm1, const krb5_data *realm2);
@@ -309,7 +306,8 @@ kdc_get_ticket_endtime(kdc_realm_t *realm, krb5_timestamp now,
 void
 kdc_get_ticket_renewtime(kdc_realm_t *realm, krb5_kdc_req *request,
                          krb5_enc_tkt_part *tgt, krb5_db_entry *client,
-                         krb5_db_entry *server, krb5_enc_tkt_part *tkt);
+                         krb5_db_entry *server, krb5_flags *tktflags,
+                         krb5_ticket_times *times);
 
 void
 log_as_req(krb5_context context,
diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c
index 1a1648eb0..33a8242cf 100644
--- a/src/kdc/tgs_policy.c
+++ b/src/kdc/tgs_policy.c
@@ -517,6 +517,60 @@ check_tgs_s4u2proxy(krb5_context context, krb5_kdc_req *req,
     return 0;
 }
 
+/* Check the KDB policy for a final RBCD request. */
+static krb5_error_code
+check_s4u2proxy_policy(krb5_context context, krb5_kdc_req *req,
+                       krb5_principal desired_client,
+                       krb5_principal impersonator_name,
+                       krb5_db_entry *impersonator, krb5_pac impersonator_pac,
+                       krb5_principal resource_name, krb5_db_entry *resource,
+                       krb5_boolean is_crossrealm, krb5_boolean is_referral,
+                       const char **status)
+{
+    krb5_error_code ret;
+    krb5_boolean support_rbcd, policy_denial = FALSE;
+
+    /* Check if the client supports resource-based constrained delegation. */
+    ret = kdc_get_pa_pac_rbcd(context, req->padata, &support_rbcd);
+    if (ret)
+        return ret;
+
+    if (is_referral) {
+        if (!support_rbcd) {
+            /* The client must support RBCD for a referral to be useful. */
+            *status = "UNSUPPORTED_S4U2PROXY_REQUEST";
+            return KRB5KDC_ERR_BADOPTION;
+        }
+        /* Policy will be checked in the resource realm. */
+        return 0;
+    }
+
+    /* Try resource-based authorization if the client supports RBCD. */
+    if (support_rbcd) {
+        ret = krb5_db_allowed_to_delegate_from(context, desired_client,
+                                               impersonator_name,
+                                               impersonator_pac, resource);
+        if (ret == KRB5KDC_ERR_BADOPTION)
+            policy_denial = TRUE;
+        else if (ret != KRB5_PLUGIN_OP_NOTSUPP)
+            return ret;
+    }
+
+    /* Try traditional authorization if the requestor is in this realm. */
+    if (!is_crossrealm) {
+        ret = krb5_db_check_allowed_to_delegate(context, desired_client,
+                                                impersonator, resource_name);
+        if (ret == KRB5KDC_ERR_BADOPTION)
+            policy_denial = TRUE;
+        else if (ret != KRB5_PLUGIN_OP_NOTSUPP)
+            return ret;
+    }
+
+    *status = policy_denial ? "NOT_ALLOWED_TO_DELEGATE" :
+        "UNSUPPORTED_S4U2PROXY_REQUEST";
+    return KRB5KDC_ERR_BADOPTION;
+}
+
 static krb5_error_code
 check_tgs_u2u(krb5_context context, krb5_kdc_req *req, const krb5_ticket *stkt,
               krb5_db_entry *server, const char **status)
@@ -612,18 +666,17 @@ check_tgs_tgt(krb5_kdc_req *req, krb5_ticket *tkt, const char **status)
 }
 
 krb5_error_code
-validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
-                     krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
-                     const krb5_ticket *stkt, krb5_pac stkt_pac,
-                     krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
-                     krb5_pa_s4u_x509_user *s4u_x509_user,
-                     krb5_db_entry *s4u2self_client,
-                     krb5_boolean is_crossrealm, krb5_boolean is_referral,
-                     const char **status, krb5_pa_data ***e_data)
+check_tgs_constraints(kdc_realm_t *realm, krb5_kdc_req *request,
+                      krb5_db_entry *server, krb5_ticket *ticket, krb5_pac pac,
+                      const krb5_ticket *stkt, krb5_pac stkt_pac,
+                      krb5_db_entry *stkt_server, krb5_timestamp kdc_time,
+                      krb5_pa_s4u_x509_user *s4u_x509_user,
+                      krb5_db_entry *s4u2self_client,
+                      krb5_boolean is_crossrealm, krb5_boolean is_referral,
+                      const char **status, krb5_pa_data ***e_data)
 {
     krb5_context context = realm->realm_context;
     int errcode;
-    krb5_error_code ret;
 
     /* Depends only on request and ticket. */
     errcode = check_tgs_opts(request, ticket, status);
@@ -636,10 +689,6 @@ validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
     if (errcode != 0)
         return errcode;
 
-    errcode = check_tgs_svc_policy(request, server, ticket, kdc_time, status);
-    if (errcode != 0)
-        return errcode;
-
     if (request->kdc_options & NON_TGT_OPTION)
         errcode = check_tgs_nontgt(context, request, ticket, status);
     else
@@ -647,12 +696,6 @@ validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
     if (errcode != 0)
         return errcode;
 
-    /* Check the hot list */
-    if (check_hot_list(ticket)) {
-        *status = "HOT_LIST";
-        return(KRB5KRB_AP_ERR_REPEAT);
-    }
-
     if (s4u_x509_user != NULL) {
         errcode = check_tgs_s4u2self(realm, request, server, ticket, pac,
                                      kdc_time, s4u_x509_user, s4u2self_client,
@@ -683,9 +726,42 @@ validate_tgs_request(kdc_realm_t *realm, krb5_kdc_req *request,
             return errcode;
     }
 
+    return 0;
+}
+
+krb5_error_code
+check_tgs_policy(kdc_realm_t *realm, krb5_kdc_req *request,
+                 krb5_db_entry *server, krb5_ticket *ticket,
+                 krb5_pac pac, const krb5_ticket *stkt, krb5_pac stkt_pac,
+                 krb5_principal stkt_pac_client, krb5_db_entry *stkt_server,
+                 krb5_timestamp kdc_time, krb5_boolean is_crossrealm,
+                 krb5_boolean is_referral, const char **status,
+                 krb5_pa_data ***e_data)
+{
+    krb5_context context = realm->realm_context;
+    int errcode;
+    krb5_error_code ret;
+    krb5_principal desired_client;
+
+    errcode = check_tgs_svc_policy(request, server, ticket, kdc_time, status);
+    if (errcode != 0)
+        return errcode;
+
+    if (request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) {
+        desired_client = (stkt_pac_client != NULL) ? stkt_pac_client :
+            stkt->enc_part2->client;
+        errcode = check_s4u2proxy_policy(context, request, desired_client,
+                                         ticket->enc_part2->client,
+                                         stkt_server, pac, request->server,
+                                         server, is_crossrealm, is_referral,
+                                         status);
+        if (errcode != 0)
+            return errcode;
+    }
+
     if (check_anon(realm, ticket->enc_part2->client, request->server) != 0) {
         *status = "ANONYMOUS NOT ALLOWED";
-        return(KRB5KDC_ERR_POLICY);
+        return KRB5KDC_ERR_POLICY;
     }
 
     /* Perform KDB module policy checks. */
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.