Imported Upstream version 1.20.1
[platform/upstream/krb5.git] / src / kdc / do_as_req.c
index 23623fe..40c0ec2 100644 (file)
@@ -2,8 +2,8 @@
 /* kdc/do_as_req.c */
 /*
  * Portions Copyright (C) 2007 Apple Inc.
- * Copyright 1990,1991,2007,2008,2009 by the Massachusetts Institute of Technology.
- * All Rights Reserved.
+ * Copyright 1990, 1991, 2007, 2008, 2009, 2013, 2014 by the
+ * Massachusetts Institute of Technology.  All Rights Reserved.
  *
  * Export of this software from the United States of America may
  *   require a specific license from the United States Government.
 #endif /* HAVE_NETINET_IN_H */
 
 #include "kdc_util.h"
+#include "kdc_audit.h"
 #include "policy.h"
-#include "adm.h"
+#include <kadm5/admin.h>
 #include "adm_proto.h"
 #include "extern.h"
 
-#if APPLE_PKINIT
-#define     AS_REQ_DEBUG    0
-#if         AS_REQ_DEBUG
-#define     asReqDebug(args...)       printf(args)
-#else
-#define     asReqDebug(args...)
-#endif
-#endif /* APPLE_PKINIT */
-
 static krb5_error_code
-prepare_error_as(struct kdc_request_state *, krb5_kdc_req *,
-                 int, krb5_pa_data **, krb5_boolean, krb5_principal,
-                 krb5_data **, const char *);
+prepare_error_as(struct kdc_request_state *, krb5_kdc_req *, krb5_db_entry *,
+                 krb5_keyblock *, int, krb5_pa_data **, krb5_boolean,
+                 krb5_principal, krb5_data **, const char *);
 
 /* Determine the key-expiration value according to RFC 4120 section 5.4.2. */
 static krb5_timestamp
@@ -95,7 +87,67 @@ get_key_exp(krb5_db_entry *entry)
         return entry->pw_expiration;
     if (entry->pw_expiration == 0)
         return entry->expiration;
-    return min(entry->expiration, entry->pw_expiration);
+    return ts_min(entry->expiration, entry->pw_expiration);
+}
+
+/*
+ * Find the key in client for the most preferred enctype in req_enctypes.  Fill
+ * in *kb_out with the decrypted keyblock (which the caller must free) and set
+ * *kd_out to an alias to that key data entry.  Set *kd_out to NULL and leave
+ * *kb_out zeroed if no key is found for any of the requested enctypes.
+ * kb_out->enctype may differ from the enctype of *kd_out for DES enctypes; in
+ * this case, kb_out->enctype is the requested enctype used to match the key
+ * data entry.
+ */
+static krb5_error_code
+select_client_key(krb5_context context, krb5_db_entry *client,
+                  krb5_enctype *req_enctypes, int n_req_enctypes,
+                  krb5_keyblock *kb_out, krb5_key_data **kd_out)
+{
+    krb5_error_code ret;
+    krb5_key_data *kd;
+    krb5_enctype etype;
+    int i;
+
+    memset(kb_out, 0, sizeof(*kb_out));
+    *kd_out = NULL;
+
+    for (i = 0; i < n_req_enctypes; i++) {
+        etype = req_enctypes[i];
+        if (!krb5_c_valid_enctype(etype))
+            continue;
+        if (krb5_dbe_find_enctype(context, client, etype, -1, 0, &kd) == 0) {
+            /* Decrypt the client key data and set its enctype to the request
+             * enctype (which may differ from the key data enctype for DES). */
+            ret = krb5_dbe_decrypt_key_data(context, NULL, kd, kb_out, NULL);
+            if (ret)
+                return ret;
+            kb_out->enctype = etype;
+            *kd_out = kd;
+            return 0;
+        }
+    }
+    return 0;
+}
+
+static krb5_error_code
+lookup_client(krb5_context context, krb5_kdc_req *req, unsigned int flags,
+              krb5_db_entry **entry_out)
+{
+    krb5_pa_data *pa;
+    krb5_data cert;
+
+    *entry_out = NULL;
+    pa = krb5int_find_pa_data(context, req->padata, KRB5_PADATA_S4U_X509_USER);
+    if (pa != NULL && pa->length != 0 &&
+        req->client->type == KRB5_NT_X500_PRINCIPAL) {
+        cert = make_data(pa->contents, pa->length);
+        flags |= KRB5_KDB_FLAG_REFERRAL_OK;
+        return krb5_db_get_s4u_x509_principal(context, &cert, req->client,
+                                              flags, entry_out);
+    } else {
+        return krb5_db_get_principal(context, req->client, flags, entry_out);
+    }
 }
 
 struct as_req_state {
@@ -106,10 +158,14 @@ struct as_req_state {
     krb5_enc_tkt_part enc_tkt_reply;
     krb5_enc_kdc_rep_part reply_encpart;
     krb5_ticket ticket_reply;
+    krb5_keyblock local_tgt_key;
     krb5_keyblock server_keyblock;
     krb5_keyblock client_keyblock;
     krb5_db_entry *client;
     krb5_db_entry *server;
+    krb5_db_entry *local_tgt;
+    krb5_db_entry *local_tgt_storage;
+    krb5_key_data *client_key;
     krb5_kdc_req *request;
     struct krb5_kdcpreauth_rock_st rock;
     const char *status;
@@ -117,7 +173,6 @@ struct as_req_state {
     krb5_boolean typed_e_data;
     krb5_kdc_rep reply;
     krb5_timestamp kdc_time;
-    krb5_timestamp authtime;
     krb5_keyblock session_key;
     unsigned int c_flags;
     krb5_data *req_pkt;
@@ -125,24 +180,28 @@ struct as_req_state {
     struct kdc_request_state *rstate;
     char *sname, *cname;
     void *pa_context;
-    const krb5_fulladdr *from;
+    const krb5_fulladdr *local_addr;
+    const krb5_fulladdr *remote_addr;
+    krb5_data **auth_indicators;
 
     krb5_error_code preauth_err;
+
+    kdc_realm_t *active_realm;
+    krb5_audit_state *au_state;
 };
 
 static void
 finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
 {
-    krb5_key_data *server_key;
-    krb5_key_data *client_key;
     krb5_keyblock *as_encrypting_key = NULL;
     krb5_data *response = NULL;
     const char *emsg = 0;
     int did_log = 0;
-    register int i;
-    krb5_enctype useenctype;
     loop_respond_fn oldrespond;
     void *oldarg;
+    kdc_realm_t *kdc_active_realm = state->active_realm;
+    krb5_audit_state *au_state = state->au_state;
+    krb5_keyblock *replaced_reply_key = NULL;
 
     assert(state);
     oldrespond = state->respond;
@@ -151,72 +210,23 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
     if (errcode)
         goto egress;
 
-    if ((errcode = validate_forwardable(state->request, *state->client,
-                                        *state->server, state->kdc_time,
-                                        &state->status))) {
-        errcode += ERROR_TABLE_BASE_krb5;
-        goto egress;
-    }
+    au_state->stage = ENCR_REP;
 
     state->ticket_reply.enc_part2 = &state->enc_tkt_reply;
 
-    /*
-     * Find the server key
-     */
-    if ((errcode = krb5_dbe_find_enctype(kdc_context, state->server,
-                                         -1, /* ignore keytype   */
-                                         -1, /* Ignore salttype  */
-                                         0,  /* Get highest kvno */
-                                         &server_key))) {
-        state->status = "FINDING_SERVER_KEY";
-        goto egress;
-    }
-
-    /*
-     * Convert server->key into a real key
-     * (it may be encrypted in the database)
-     *
-     *  server_keyblock is later used to generate auth data signatures
-     */
-    if ((errcode = krb5_dbe_decrypt_key_data(kdc_context, NULL,
-                                             server_key,
-                                             &state->server_keyblock,
-                                             NULL))) {
-        state->status = "DECRYPT_SERVER_KEY";
-        goto egress;
-    }
-
-    /*
-     * Find the appropriate client key.  We search in the order specified
-     * by request keytype list.
-     */
-    client_key = NULL;
-    for (i = 0; i < state->request->nktypes; i++) {
-        useenctype = state->request->ktype[i];
-        if (!krb5_c_valid_enctype(useenctype))
-            continue;
-
-        if (!krb5_dbe_find_enctype(kdc_context, state->client,
-                                   useenctype, -1, 0, &client_key))
-            break;
-    }
-    if (!(client_key)) {
-        /* Cannot find an appropriate key */
-        state->status = "CANT_FIND_CLIENT_KEY";
-        errcode = KRB5KDC_ERR_ETYPE_NOSUPP;
+    errcode = check_kdcpolicy_as(kdc_context, state->request, state->client,
+                                 state->server, state->auth_indicators,
+                                 state->kdc_time, &state->enc_tkt_reply.times,
+                                 &state->status);
+    if (errcode)
         goto egress;
-    }
-    state->rock.client_key = client_key;
 
-    /* convert client.key_data into a real key */
-    if ((errcode = krb5_dbe_decrypt_key_data(kdc_context, NULL,
-                                             client_key,
-                                             &state->client_keyblock,
-                                             NULL))) {
-        state->status = "DECRYPT_CLIENT_KEY";
+    errcode = get_first_current_key(kdc_context, state->server,
+                                    &state->server_keyblock);
+    if (errcode) {
+        state->status = "FINDING_SERVER_KEY";
         goto egress;
     }
-    state->client_keyblock.enctype = useenctype;
 
     /* Start assembling the response */
     state->reply.msg_type = KRB5_AS_REP;
@@ -224,21 +234,13 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
     state->reply.ticket = &state->ticket_reply;
     state->reply_encpart.session = &state->session_key;
     if ((errcode = fetch_last_req_info(state->client,
-                                       &state->reply_encpart.last_req))) {
-        state->status = "FETCH_LAST_REQ";
+                                       &state->reply_encpart.last_req)))
         goto egress;
-    }
     state->reply_encpart.nonce = state->request->nonce;
     state->reply_encpart.key_exp = get_key_exp(state->client);
     state->reply_encpart.flags = state->enc_tkt_reply.flags;
     state->reply_encpart.server = state->ticket_reply.server;
-
-    /* copy the time fields EXCEPT for authtime; it's location
-     *  is used for ktime
-     */
     state->reply_encpart.times = state->enc_tkt_reply.times;
-    state->reply_encpart.times.authtime = state->authtime = state->kdc_time;
-
     state->reply_encpart.caddrs = state->enc_tkt_reply.caddrs;
     state->reply_encpart.enc_padata = NULL;
 
@@ -253,26 +255,24 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
         goto egress;
     }
 
-#if APPLE_PKINIT
-    asReqDebug("process_as_req reply realm %s name %s\n",
-               reply.client->realm.data, reply.client->data->data);
-#endif /* APPLE_PKINIT */
-
+    /* If we didn't find a client long-term key and no preauth mechanism
+     * replaced the reply key, error out now. */
+    if (state->client_keyblock.enctype == ENCTYPE_NULL) {
+        state->status = "CANT_FIND_CLIENT_KEY";
+        errcode = KRB5KDC_ERR_ETYPE_NOSUPP;
+        goto egress;
+    }
 
+    if (state->rock.replaced_reply_key)
+        replaced_reply_key = &state->client_keyblock;
 
-    errcode = handle_authdata(kdc_context,
-                              state->c_flags,
-                              state->client,
-                              state->server,
-                              state->server,
-                              &state->client_keyblock,
-                              &state->server_keyblock,
-                              &state->server_keyblock,
-                              state->req_pkt,
-                              state->request,
-                              NULL, /* for_user_princ */
-                              NULL, /* enc_tkt_request */
-                              &state->enc_tkt_reply);
+    errcode = handle_authdata(kdc_active_realm, state->c_flags, state->client,
+                              state->server, NULL, state->local_tgt,
+                              &state->local_tgt_key, &state->client_keyblock,
+                              &state->server_keyblock, NULL,
+                              replaced_reply_key, state->req_pkt,
+                              state->request, NULL, NULL, NULL,
+                              &state->auth_indicators, &state->enc_tkt_reply);
     if (errcode) {
         krb5_klog_syslog(LOG_INFO, _("AS_REQ : handle_authdata (%d)"),
                          errcode);
@@ -280,21 +280,30 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
         goto egress;
     }
 
-    errcode = krb5_encrypt_tkt_part(kdc_context, &state->server_keyblock,
-                                    &state->ticket_reply);
+    errcode = check_indicators(kdc_context, state->server,
+                               state->auth_indicators);
     if (errcode) {
-        state->status = "ENCRYPTING_TICKET";
+        state->status = "HIGHER_AUTHENTICATION_REQUIRED";
         goto egress;
     }
-    state->ticket_reply.enc_part.kvno = server_key->key_data_kvno;
+
+    errcode = krb5_encrypt_tkt_part(kdc_context, &state->server_keyblock,
+                                    &state->ticket_reply);
+    if (errcode)
+        goto egress;
+
+    errcode = kau_make_tkt_id(kdc_context, &state->ticket_reply,
+                              &au_state->tkt_out_id);
+    if (errcode)
+        goto egress;
+
+    state->ticket_reply.enc_part.kvno = current_kvno(state->server);
     errcode = kdc_fast_response_handle_padata(state->rstate,
                                               state->request,
                                               &state->reply,
                                               state->client_keyblock.enctype);
-    if (errcode) {
-        state->status = "fast response handling";
+    if (errcode)
         goto egress;
-    }
 
     /* now encode/encrypt the response */
 
@@ -302,10 +311,8 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
 
     errcode = kdc_fast_handle_reply_key(state->rstate, &state->client_keyblock,
                                         &as_encrypting_key);
-    if (errcode) {
-        state->status = "generating reply key";
+    if (errcode)
         goto egress;
-    }
     errcode = return_enc_padata(kdc_context, state->req_pkt, state->request,
                                 as_encrypting_key, state->server,
                                 &state->reply_encpart, FALSE);
@@ -314,15 +321,16 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
         goto egress;
     }
 
+    if (kdc_fast_hide_client(state->rstate))
+        state->reply.client = (krb5_principal)krb5_anonymous_principal();
     errcode = krb5_encode_kdc_rep(kdc_context, KRB5_AS_REP,
                                   &state->reply_encpart, 0,
                                   as_encrypting_key,
                                   &state->reply, &response);
-    state->reply.enc_part.kvno = client_key->key_data_kvno;
-    if (errcode) {
-        state->status = "ENCODE_KDC_REP";
+    if (state->client_key != NULL)
+        state->reply.enc_part.kvno = state->client_key->key_data_kvno;
+    if (errcode)
         goto egress;
-    }
 
     /* 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... */
@@ -330,14 +338,21 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
            state->reply.enc_part.ciphertext.length);
     free(state->reply.enc_part.ciphertext.data);
 
-    log_as_req(state->from, state->request, &state->reply,
-               state->client, state->cname, state->server,
-               state->sname, state->authtime, 0, 0, 0);
+    log_as_req(kdc_context, state->local_addr, state->remote_addr,
+               state->request, &state->reply, state->client, state->cname,
+               state->server, state->sname, state->kdc_time, 0, 0, 0);
     did_log = 1;
 
 egress:
-    if (errcode != 0)
-        assert (state->status != 0);
+    if (errcode != 0 && state->status == NULL)
+        state->status = "UNKNOWN_REASON";
+
+    au_state->status = state->status;
+    au_state->reply = &state->reply;
+    kau_as_req(kdc_context,
+              (errcode || state->preauth_err) ? FALSE : TRUE, au_state);
+    kau_free_kdc_req(au_state);
+
     free_padata_context(kdc_context, state->pa_context);
     if (as_encrypting_key)
         krb5_free_keyblock(kdc_context, as_encrypting_key);
@@ -345,8 +360,9 @@ egress:
         emsg = krb5_get_error_message(kdc_context, errcode);
 
     if (state->status) {
-        log_as_req(state->from, state->request, &state->reply, state->client,
-                   state->cname, state->server, state->sname, state->authtime,
+        log_as_req(kdc_context, state->local_addr, state->remote_addr,
+                   state->request, &state->reply, state->client,
+                   state->cname, state->server, state->sname, state->kdc_time,
                    state->status, errcode, emsg);
         did_log = 1;
     }
@@ -356,10 +372,11 @@ egress:
         }
         if (errcode != KRB5KDC_ERR_DISCARD) {
             errcode -= ERROR_TABLE_BASE_krb5;
-            if (errcode < 0 || errcode > 128)
+            if (errcode < 0 || errcode > KRB_ERR_MAX)
                 errcode = KRB_ERR_GENERIC;
 
             errcode = prepare_error_as(state->rstate, state->request,
+                                       state->local_tgt, &state->local_tgt_key,
                                        errcode, state->e_data,
                                        state->typed_e_data,
                                        ((state->client != NULL) ?
@@ -374,6 +391,8 @@ egress:
     if (state->enc_tkt_reply.authorization_data != NULL)
         krb5_free_authdata(kdc_context,
                            state->enc_tkt_reply.authorization_data);
+    if (state->local_tgt_key.contents != NULL)
+        krb5_free_keyblock_contents(kdc_context, &state->local_tgt_key);
     if (state->server_keyblock.contents != NULL)
         krb5_free_keyblock_contents(kdc_context, &state->server_keyblock);
     if (state->client_keyblock.contents != NULL)
@@ -389,6 +408,7 @@ egress:
         free(state->sname);
     krb5_db_free_principal(kdc_context, state->client);
     krb5_db_free_principal(kdc_context, state->server);
+    krb5_db_free_principal(kdc_context, state->local_tgt_storage);
     if (state->session_key.contents != NULL)
         krb5_free_keyblock_contents(kdc_context, &state->session_key);
     if (state->ticket_reply.enc_part.ciphertext.data != NULL) {
@@ -401,6 +421,7 @@ egress:
     krb5_free_data(kdc_context, state->inner_body);
     kdc_free_rstate(state->rstate);
     krb5_free_kdc_req(kdc_context, state->request);
+    k5_free_data_ptr_list(state->auth_indicators);
     assert(did_log != 0);
 
     free(state);
@@ -453,86 +474,85 @@ finish_preauth(void *arg, krb5_error_code code)
 /*ARGSUSED*/
 void
 process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
-               const krb5_fulladdr *from, verto_ctx *vctx,
-               loop_respond_fn respond, void *arg)
+               const krb5_fulladdr *local_addr,
+               const krb5_fulladdr *remote_addr, kdc_realm_t *kdc_active_realm,
+               verto_ctx *vctx, loop_respond_fn respond, void *arg)
 {
     krb5_error_code errcode;
-    krb5_timestamp rtime;
-    unsigned int s_flags = 0;
     krb5_data encoded_req_body;
     krb5_enctype useenctype;
     struct as_req_state *state;
+    krb5_audit_state *au_state = NULL;
 
-    state = malloc(sizeof(*state));
-    if (!state) {
-        (*respond)(arg, ENOMEM, NULL);
+    state = k5alloc(sizeof(*state), &errcode);
+    if (state == NULL) {
+        (*respond)(arg, errcode, NULL);
         return;
     }
-    state->session_key.contents = 0;
-    state->enc_tkt_reply.authorization_data = NULL;
-    state->reply.padata = 0;
-    memset(&state->reply, 0, sizeof(state->reply));
     state->respond = respond;
     state->arg = arg;
-    state->ticket_reply.enc_part.ciphertext.data = 0;
-    state->server_keyblock.contents = NULL;
-    state->client_keyblock.contents = NULL;
-    state->reply_encpart.enc_padata = 0;
-    state->client = NULL;
-    state->server = NULL;
     state->request = request;
-    state->e_data = NULL;
-    state->typed_e_data = FALSE;
-    state->authtime = 0;
-    state->c_flags = 0;
     state->req_pkt = req_pkt;
-    state->rstate = NULL;
-    state->sname = 0;
-    state->cname = 0;
-    state->pa_context = NULL;
-    state->from = from;
-    memset(&state->rock, 0, sizeof(state->rock));
-
-#if APPLE_PKINIT
-    asReqDebug("process_as_req top realm %s name %s\n",
-               request->client->realm.data, request->client->data->data);
-#endif /* APPLE_PKINIT */
+    state->local_addr = local_addr;
+    state->remote_addr = remote_addr;
+    state->active_realm = kdc_active_realm;
+
+    errcode = kdc_make_rstate(kdc_active_realm, &state->rstate);
+    if (errcode != 0) {
+        (*respond)(arg, errcode, NULL);
+        free(state);
+        return;
+    }
+
+    /* Initialize audit state. */
+    errcode = kau_init_kdc_req(kdc_context, state->request, remote_addr,
+                               &au_state);
+    if (errcode) {
+        (*respond)(arg, errcode, NULL);
+        kdc_free_rstate(state->rstate);
+        free(state);
+        return;
+    }
+    state->au_state = au_state;
 
     if (state->request->msg_type != KRB5_AS_REQ) {
-        state->status = "msg_type mismatch";
+        state->status = "VALIDATE_MESSAGE_TYPE";
         errcode = KRB5_BADMSGTYPE;
         goto errout;
     }
-    errcode = kdc_make_rstate(&state->rstate);
-    if (errcode != 0) {
-        state->status = "constructing state";
+
+    /* Seed the audit trail with the request ID and basic information. */
+    kau_as_req(kdc_context, TRUE, au_state);
+
+    errcode = krb5_timeofday(kdc_context, &state->kdc_time);
+    if (errcode)
         goto errout;
-    }
+
     if (fetch_asn1_field((unsigned char *) req_pkt->data,
                          1, 4, &encoded_req_body) != 0) {
         errcode = ASN1_BAD_ID;
-        state->status = "Finding req_body";
         goto errout;
     }
     errcode = kdc_find_fast(&state->request, &encoded_req_body, NULL, NULL,
                             state->rstate, &state->inner_body);
     if (errcode) {
-        state->status = "error decoding FAST";
+        state->status = "FIND_FAST";
         goto errout;
     }
     if (state->inner_body == NULL) {
         /* Not a FAST request; copy the encoded request body. */
         errcode = krb5_copy_data(kdc_context, &encoded_req_body,
                                  &state->inner_body);
-        if (errcode) {
-            state->status = "storing req body";
+        if (errcode)
             goto errout;
-        }
     }
+    au_state->request = state->request;
     state->rock.request = state->request;
     state->rock.inner_body = state->inner_body;
     state->rock.rstate = state->rstate;
     state->rock.vctx = vctx;
+    state->rock.auth_indicators = &state->auth_indicators;
+    state->rock.send_freshness_token = FALSE;
     if (!state->request->client) {
         state->status = "NULL_CLIENT";
         errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
@@ -540,11 +560,10 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
     }
     if ((errcode = krb5_unparse_name(kdc_context,
                                      state->request->client,
-                                     &state->cname))) {
-        state->status = "UNPARSING_CLIENT";
+                                     &state->cname)))
         goto errout;
-    }
     limit_string(state->cname);
+
     if (!state->request->server) {
         state->status = "NULL_SERVER";
         errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
@@ -552,32 +571,18 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
     }
     if ((errcode = krb5_unparse_name(kdc_context,
                                      state->request->server,
-                                     &state->sname))) {
-        state->status = "UNPARSING_SERVER";
+                                     &state->sname)))
         goto errout;
-    }
     limit_string(state->sname);
 
-    /*
-     * We set KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY as a hint
-     * to the backend to return naming information in lieu
-     * of cross realm TGS entries.
-     */
-    setflag(state->c_flags, KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY);
-    /*
-     * Note that according to the referrals draft we should
-     * always canonicalize enterprise principal names.
-     */
+    setflag(state->c_flags, KRB5_KDB_FLAG_CLIENT);
     if (isflagset(state->request->kdc_options, KDC_OPT_CANONICALIZE) ||
-        state->request->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
-        setflag(state->c_flags, KRB5_KDB_FLAG_CANONICALIZE);
-        setflag(state->c_flags, KRB5_KDB_FLAG_ALIAS_OK);
-    }
-    if (include_pac_p(kdc_context, state->request)) {
-        setflag(state->c_flags, KRB5_KDB_FLAG_INCLUDE_PAC);
-    }
-    errcode = krb5_db_get_principal(kdc_context, state->request->client,
-                                    state->c_flags, &state->client);
+        state->request->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL)
+        setflag(state->c_flags, KRB5_KDB_FLAG_REFERRAL_OK);
+    errcode = lookup_client(kdc_context, state->request, state->c_flags,
+                            &state->client);
+    if (errcode == KRB5_KDB_CANTLOCK_DB)
+        errcode = KRB5KDC_ERR_SVC_UNAVAILABLE;
     if (errcode == KRB5_KDB_NOENTRY) {
         state->status = "CLIENT_NOT_FOUND";
         if (vague_errors)
@@ -591,36 +596,12 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
     }
     state->rock.client = state->client;
 
-    /*
-     * If the backend returned a principal that is not in the local
-     * realm, then we need to refer the client to that realm.
-     */
-    if (!is_local_principal(state->client->princ)) {
-        /* Entry is a referral to another realm */
-        state->status = "REFERRAL";
-        errcode = KRB5KDC_ERR_WRONG_REALM;
-        goto errout;
-    }
-
-#if 0
-    /*
-     * Turn off canonicalization if client is marked DES only
-     * (unless enterprise principal name was requested)
-     */
-    if (isflagset(client->attributes, KRB5_KDB_NON_MS_PRINCIPAL) &&
-        krb5_princ_type(kdc_context,
-                        request->client) != KRB5_NT_ENTERPRISE_PRINCIPAL) {
-        clear(c_flags, KRB5_KDB_FLAG_CANONICALIZE);
-    }
-#endif
+    au_state->stage = SRVC_PRINC;
 
-    s_flags = 0;
-    setflag(s_flags, KRB5_KDB_FLAG_ALIAS_OK);
-    if (isflagset(state->request->kdc_options, KDC_OPT_CANONICALIZE)) {
-        setflag(s_flags, KRB5_KDB_FLAG_CANONICALIZE);
-    }
-    errcode = krb5_db_get_principal(kdc_context, state->request->server,
-                                    s_flags, &state->server);
+    errcode = krb5_db_get_principal(kdc_context, state->request->server, 0,
+                                    &state->server);
+    if (errcode == KRB5_KDB_CANTLOCK_DB)
+        errcode = KRB5KDC_ERR_SVC_UNAVAILABLE;
     if (errcode == KRB5_KDB_NOENTRY) {
         state->status = "SERVER_NOT_FOUND";
         errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
@@ -630,25 +611,41 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
         goto errout;
     }
 
-    if ((errcode = krb5_timeofday(kdc_context, &state->kdc_time))) {
-        state->status = "TIMEOFDAY";
+    /* If the KDB module returned a different realm for the client and server,
+     * we need to issue a client realm referral. */
+    if (!data_eq(state->server->princ->realm, state->client->princ->realm)) {
+        state->status = "REFERRAL";
+        au_state->cl_realm = &state->client->princ->realm;
+        errcode = KRB5KDC_ERR_WRONG_REALM;
+        goto errout;
+    }
+
+    errcode = get_local_tgt(kdc_context, &state->request->server->realm,
+                            state->server, &state->local_tgt,
+                            &state->local_tgt_storage, &state->local_tgt_key);
+    if (errcode) {
+        state->status = "GET_LOCAL_TGT";
         goto errout;
     }
-    state->authtime = state->kdc_time; /* for audit_as_request() */
+    state->rock.local_tgt = state->local_tgt;
+    state->rock.local_tgt_key = &state->local_tgt_key;
 
-    if ((errcode = validate_as_request(state->request, *state->client,
-                                       *state->server, state->kdc_time,
+    au_state->stage = VALIDATE_POL;
+
+    if ((errcode = validate_as_request(kdc_active_realm,
+                                       state->request, state->client,
+                                       state->server, state->kdc_time,
                                        &state->status, &state->e_data))) {
-        if (!state->status)
-            state->status = "UNKNOWN_REASON";
         errcode += ERROR_TABLE_BASE_krb5;
         goto errout;
     }
 
+    au_state->stage = ISSUE_TKT;
+
     /*
      * Select the keytype for the ticket session key.
      */
-    if ((useenctype = select_session_keytype(kdc_context, state->server,
+    if ((useenctype = select_session_keytype(kdc_active_realm, state->server,
                                              state->request->nktypes,
                                              state->request->ktype)) == 0) {
         /* unsupported ktype */
@@ -658,17 +655,15 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
     }
 
     if ((errcode = krb5_c_make_random_key(kdc_context, useenctype,
-                                          &state->session_key))) {
-        state->status = "RANDOM_KEY_FAILED";
+                                          &state->session_key)))
         goto errout;
-    }
 
     /*
      * Canonicalization is only effective if we are issuing a TGT
      * (the intention is to allow support for Windows "short" realm
      * aliases, nothing more).
      */
-    if (isflagset(s_flags, KRB5_KDB_FLAG_CANONICALIZE) &&
+    if (isflagset(state->request->kdc_options, KDC_OPT_CANONICALIZE) &&
         krb5_is_tgs_principal(state->request->server) &&
         krb5_is_tgs_principal(state->server->princ)) {
         state->ticket_reply.server = state->server->princ;
@@ -676,11 +671,11 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
         state->ticket_reply.server = state->request->server;
     }
 
-    state->enc_tkt_reply.flags = 0;
-    state->enc_tkt_reply.times.authtime = state->authtime;
-
-    setflag(state->enc_tkt_reply.flags, TKT_FLG_INITIAL);
-    setflag(state->enc_tkt_reply.flags, TKT_FLG_ENC_PA_REP);
+    /* Copy options that request the corresponding ticket flags. */
+    state->enc_tkt_reply.flags = get_ticket_flags(state->request->kdc_options,
+                                                  state->client, state->server,
+                                                  NULL);
+    state->enc_tkt_reply.times.authtime = state->kdc_time;
 
     /*
      * It should be noted that local policy may affect the
@@ -688,17 +683,8 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
      * realms may refuse to issue renewable tickets
      */
 
-    if (isflagset(state->request->kdc_options, KDC_OPT_FORWARDABLE))
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_FORWARDABLE);
-
-    if (isflagset(state->request->kdc_options, KDC_OPT_PROXIABLE))
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_PROXIABLE);
-
-    if (isflagset(state->request->kdc_options, KDC_OPT_ALLOW_POSTDATE))
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_MAY_POSTDATE);
-
     state->enc_tkt_reply.session = &state->session_key;
-    if (isflagset(state->c_flags, KRB5_KDB_FLAG_CANONICALIZE)) {
+    if (isflagset(state->request->kdc_options, KDC_OPT_CANONICALIZE)) {
         state->client_princ = *(state->client->princ);
     } else {
         state->client_princ = *(state->request->client);
@@ -709,42 +695,19 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
     state->enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
     state->enc_tkt_reply.transited.tr_contents = empty_string;
 
-    if (isflagset(state->request->kdc_options, KDC_OPT_POSTDATED)) {
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_POSTDATED);
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_INVALID);
+    if (isflagset(state->request->kdc_options, KDC_OPT_POSTDATED))
         state->enc_tkt_reply.times.starttime = state->request->from;
-    else
+    else
         state->enc_tkt_reply.times.starttime = state->kdc_time;
 
-    kdc_get_ticket_endtime(kdc_context, state->enc_tkt_reply.times.starttime,
+    kdc_get_ticket_endtime(kdc_active_realm,
+                           state->enc_tkt_reply.times.starttime,
                            kdc_infinity, state->request->till, state->client,
                            state->server, &state->enc_tkt_reply.times.endtime);
 
-    if (isflagset(state->request->kdc_options, KDC_OPT_RENEWABLE_OK) &&
-        !isflagset(state->client->attributes, KRB5_KDB_DISALLOW_RENEWABLE) &&
-        (state->enc_tkt_reply.times.endtime < state->request->till)) {
-
-        /* we set the RENEWABLE option for later processing */
-
-        setflag(state->request->kdc_options, KDC_OPT_RENEWABLE);
-        state->request->rtime = state->request->till;
-    }
-    rtime = (state->request->rtime == 0) ? kdc_infinity :
-        state->request->rtime;
-
-    if (isflagset(state->request->kdc_options, KDC_OPT_RENEWABLE)) {
-        /*
-         * XXX Should we squelch the output renew_till to be no
-         * earlier than the endtime of the ticket?
-         */
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_RENEWABLE);
-        state->enc_tkt_reply.times.renew_till =
-            min(rtime, state->enc_tkt_reply.times.starttime +
-                min(state->client->max_renewable_life,
-                    min(state->server->max_renewable_life,
-                        max_renewable_life_for_realm)));
-    } else
-        state->enc_tkt_reply.times.renew_till = 0; /* XXX */
+    kdc_get_ticket_renewtime(kdc_active_realm, state->request, NULL,
+                             state->client, state->server,
+                             &state->enc_tkt_reply);
 
     /*
      * starttime is optional, and treated as authtime if not present.
@@ -764,22 +727,38 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
                                               state->request->client,
                                               krb5_anonymous_principal())) {
             errcode = KRB5KDC_ERR_BADOPTION;
-            state->status = "Anonymous requested but anonymous "
-                "principal not used.";
+            /* Anonymous requested but anonymous principal not used.*/
+            state->status = "VALIDATE_ANONYMOUS_PRINCIPAL";
             goto errout;
         }
-        setflag(state->enc_tkt_reply.flags, TKT_FLG_ANONYMOUS);
         krb5_free_principal(kdc_context, state->request->client);
+        state->request->client = NULL;
         errcode = krb5_copy_principal(kdc_context, krb5_anonymous_principal(),
                                       &state->request->client);
-        if (errcode) {
-            state->status = "Copying anonymous principal";
+        if (errcode)
             goto errout;
-        }
         state->enc_tkt_reply.client = state->request->client;
         setflag(state->client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH);
     }
 
+    errcode = select_client_key(kdc_context, state->client,
+                                state->request->ktype, state->request->nktypes,
+                                &state->client_keyblock, &state->client_key);
+    if (errcode) {
+        state->status = "DECRYPT_CLIENT_KEY";
+        goto errout;
+    }
+    if (state->client_key != NULL)
+        state->rock.client_key = state->client_key;
+    state->rock.client_keyblock = &state->client_keyblock;
+
+    errcode = kdc_fast_read_cookie(kdc_context, state->rstate, state->request,
+                                   state->local_tgt, &state->local_tgt_key);
+    if (errcode) {
+        state->status = "READ_COOKIE";
+        goto errout;
+    }
+
     /*
      * Check the preauthentication if it is there.
      */
@@ -797,32 +776,51 @@ errout:
 }
 
 static krb5_error_code
-prepare_error_as (struct kdc_request_state *rstate, krb5_kdc_req *request,
-                  int error, krb5_pa_data **e_data, krb5_boolean typed_e_data,
-                  krb5_principal canon_client, krb5_data **response,
-                  const char *status)
+prepare_error_as(struct kdc_request_state *rstate, krb5_kdc_req *request,
+                 krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,
+                 int error, krb5_pa_data **e_data_in,
+                 krb5_boolean typed_e_data, krb5_principal canon_client,
+                 krb5_data **response, const char *status)
 {
     krb5_error errpkt;
     krb5_error_code retval;
     krb5_data *scratch = NULL, *e_data_asn1 = NULL, *fast_edata = NULL;
-
-    errpkt.ctime = request->nonce;
+    krb5_pa_data **e_data = NULL, *cookie = NULL;
+    kdc_realm_t *kdc_active_realm = rstate->realm_data;
+    size_t count;
+
+    errpkt.magic = KV5M_ERROR;
+
+    if (e_data_in != NULL) {
+        /* Add a PA-FX-COOKIE to e_data_in.  e_data is a shallow copy
+         * containing aliases. */
+        for (count = 0; e_data_in[count] != NULL; count++);
+        e_data = calloc(count + 2, sizeof(*e_data));
+        if (e_data == NULL)
+            return ENOMEM;
+        memcpy(e_data, e_data_in, count * sizeof(*e_data));
+        retval = kdc_fast_make_cookie(kdc_context, rstate, local_tgt,
+                                      local_tgt_key, request->client,
+                                      &cookie);
+        e_data[count] = cookie;
+    }
+
+    errpkt.ctime = 0;
     errpkt.cusec = 0;
 
     retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec);
     if (retval)
-        return retval;
+        goto cleanup;
     errpkt.error = error;
     errpkt.server = request->server;
-    errpkt.client = (error == KRB5KDC_ERR_WRONG_REALM) ? canon_client :
+    errpkt.client = (error == KDC_ERR_WRONG_REALM) ? canon_client :
         request->client;
     errpkt.text = string2data((char *)status);
 
     if (e_data != NULL) {
-        if (typed_e_data) {
-            retval = encode_krb5_typed_data((const krb5_typed_data **)e_data,
-                                            &e_data_asn1);
-        } else
+        if (typed_e_data)
+            retval = encode_krb5_typed_data(e_data, &e_data_asn1);
+        else
             retval = encode_krb5_padata_sequence(e_data, &e_data_asn1);
         if (retval)
             goto cleanup;
@@ -840,6 +838,8 @@ prepare_error_as (struct kdc_request_state *rstate, krb5_kdc_req *request,
     scratch = k5alloc(sizeof(*scratch), &retval);
     if (scratch == NULL)
         goto cleanup;
+    if (kdc_fast_hide_client(rstate) && errpkt.client != NULL)
+        errpkt.client = (krb5_principal)krb5_anonymous_principal();
     retval = krb5_mk_error(kdc_context, &errpkt, scratch);
     if (retval)
         goto cleanup;
@@ -851,5 +851,9 @@ cleanup:
     krb5_free_data(kdc_context, fast_edata);
     krb5_free_data(kdc_context, e_data_asn1);
     free(scratch);
+    free(e_data);
+    if (cookie != NULL)
+        free(cookie->contents);
+    free(cookie);
     return retval;
 }