krb5 commit: Refactor KDC TGS processing code

ghudson at mit.edu ghudson at mit.edu
Mon Oct 3 21:04:02 EDT 2022


https://github.com/krb5/krb5/commit/a9705a1e0b2cf0cde3e6f8dee14c25ffc074c00a
commit a9705a1e0b2cf0cde3e6f8dee14c25ffc074c00a
Author: Greg Hudson <ghudson at mit.edu>
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. */


More information about the cvs-krb5 mailing list