heimdal: handle referrals for 3 part DRSUAPI SPNs
[sfrench/samba-autobuild/.git] / source4 / heimdal / kdc / krb5tgs.c
index 4d6be60f68fe2d40012604b2069a72dadaee161d..96ee9ccc30aa1b6f078381dd364293e5ec35bbdd 100644 (file)
@@ -1,45 +1,43 @@
 /*
- * Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
- * (Royal Institute of Technology, Stockholm, Sweden). 
- * All rights reserved. 
+ * Copyright (c) 1997-2008 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
  *
- * Redistribution and use in source and binary forms, with or without 
- * modification, are permitted provided that the following conditions 
- * are met: 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
  *
- * 1. Redistributions of source code must retain the above copyright 
- *    notice, this list of conditions and the following disclaimer. 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
  *
- * 2. Redistributions in binary form must reproduce the above copyright 
- *    notice, this list of conditions and the following disclaimer in the 
- *    documentation and/or other materials provided with the distribution. 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
  *
- * 3. Neither the name of the Institute nor the names of its contributors 
- *    may be used to endorse or promote products derived from this software 
- *    without specific prior written permission. 
+ * 3. Neither the name of the Institute nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
  *
- * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
- * SUCH DAMAGE. 
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
  */
 
 #include "kdc_locl.h"
 
-RCSID("$Id: krb5tgs.c 21262 2007-06-21 15:18:37Z lha $");
-
 /*
  * return the realm of a krbtgt-ticket or NULL
  */
 
-static Realm 
+static Realm
 get_krbtgt_realm(const PrincipalName *p)
 {
     if(p->name_string.len == 2
@@ -66,7 +64,7 @@ find_KRB5SignedPath(krb5_context context,
     AuthorizationData child;
     krb5_error_code ret;
     int pos;
-       
+
     if (ad == NULL || ad->len == 0)
        return KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
 
@@ -80,8 +78,8 @@ find_KRB5SignedPath(krb5_context context,
                                   &child,
                                   NULL);
     if (ret) {
-       krb5_set_error_string(context, "Failed to decode "
-                             "IF_RELEVANT with %d", ret);
+       krb5_set_error_message(context, ret, "Failed to decode "
+                              "IF_RELEVANT with %d", ret);
        return ret;
     }
 
@@ -106,28 +104,31 @@ _kdc_add_KRB5SignedPath(krb5_context context,
                        krb5_kdc_configuration *config,
                        hdb_entry_ex *krbtgt,
                        krb5_enctype enctype,
+                       krb5_principal client,
                        krb5_const_principal server,
-                       KRB5SignedPathPrincipals *principals,
+                       krb5_principals principals,
                        EncTicketPart *tkt)
 {
     krb5_error_code ret;
     KRB5SignedPath sp;
     krb5_data data;
     krb5_crypto crypto = NULL;
-    size_t size;
+    size_t size = 0;
 
     if (server && principals) {
-       ret = add_KRB5SignedPathPrincipals(principals, server);
+       ret = add_Principals(principals, server);
        if (ret)
            return ret;
     }
 
     {
        KRB5SignedPathData spd;
-       
-       spd.encticket = *tkt;
+
+       spd.client = client;
+       spd.authtime = tkt->authtime;
        spd.delegated = principals;
-       
+       spd.method_data = NULL;
+
        ASN1_MALLOC_ENCODE(KRB5SignedPathData, data.data, data.length,
                           &spd, &size, ret);
        if (ret)
@@ -153,6 +154,7 @@ _kdc_add_KRB5SignedPath(krb5_context context,
 
     sp.etype = enctype;
     sp.delegated = principals;
+    sp.method_data = NULL;
 
     ret = krb5_create_checksum(context, crypto, KRB5_KU_KRB5SIGNEDPATH, 0,
                               data.data, data.length, &sp.cksum);
@@ -168,7 +170,7 @@ _kdc_add_KRB5SignedPath(krb5_context context,
     if (data.length != size)
        krb5_abortx(context, "internal asn.1 encoder error");
 
-    
+
     /*
      * Add IF-RELEVANT(KRB5SignedPath) to the last slot in
      * authorization data field.
@@ -185,39 +187,36 @@ static krb5_error_code
 check_KRB5SignedPath(krb5_context context,
                     krb5_kdc_configuration *config,
                     hdb_entry_ex *krbtgt,
+                    krb5_principal cp,
                     EncTicketPart *tkt,
-                    KRB5SignedPathPrincipals **delegated,
-                    int require_signedpath)
+                    krb5_principals *delegated,
+                    int *signedpath)
 {
     krb5_error_code ret;
     krb5_data data;
     krb5_crypto crypto = NULL;
 
-    *delegated = NULL;
+    if (delegated)
+       *delegated = NULL;
 
     ret = find_KRB5SignedPath(context, tkt->authorization_data, &data);
     if (ret == 0) {
        KRB5SignedPathData spd;
        KRB5SignedPath sp;
-       AuthorizationData *ad;
-       size_t size;
+       size_t size = 0;
 
        ret = decode_KRB5SignedPath(data.data, data.length, &sp, NULL);
        krb5_data_free(&data);
        if (ret)
            return ret;
 
-       spd.encticket = *tkt;
-       /* the KRB5SignedPath is the last entry */
-       ad = spd.encticket.authorization_data;
-       if (--ad->len == 0)
-           spd.encticket.authorization_data = NULL;
+       spd.client = cp;
+       spd.authtime = tkt->authtime;
        spd.delegated = sp.delegated;
+       spd.method_data = sp.method_data;
 
        ASN1_MALLOC_ENCODE(KRB5SignedPathData, data.data, data.length,
                           &spd, &size, ret);
-       ad->len++;
-       spd.encticket.authorization_data = ad;
        if (ret) {
            free_KRB5SignedPath(&sp);
            return ret;
@@ -236,17 +235,19 @@ check_KRB5SignedPath(krb5_context context,
                return ret;
            }
        }
-       ret = krb5_verify_checksum(context, crypto, KRB5_KU_KRB5SIGNEDPATH, 
-                                  data.data, data.length, 
+       ret = krb5_verify_checksum(context, crypto, KRB5_KU_KRB5SIGNEDPATH,
+                                  data.data, data.length,
                                   &sp.cksum);
        krb5_crypto_destroy(context, crypto);
        free(data.data);
        if (ret) {
            free_KRB5SignedPath(&sp);
-           return ret;
+           kdc_log(context, config, 5,
+                   "KRB5SignedPath not signed correctly, not marking as signed");
+           return 0;
        }
 
-       if (sp.delegated) {
+       if (delegated && sp.delegated) {
 
            *delegated = malloc(sizeof(*sp.delegated));
            if (*delegated == NULL) {
@@ -254,7 +255,7 @@ check_KRB5SignedPath(krb5_context context,
                return ENOMEM;
            }
 
-           ret = copy_KRB5SignedPathPrincipals(*delegated, sp.delegated);
+           ret = copy_Principals(*delegated, sp.delegated);
            if (ret) {
                free_KRB5SignedPath(&sp);
                free(*delegated);
@@ -263,10 +264,8 @@ check_KRB5SignedPath(krb5_context context,
            }
        }
        free_KRB5SignedPath(&sp);
-       
-    } else {
-       if (require_signedpath)
-           return KRB5KDC_ERR_BADOPTION;
+
+       *signedpath = 1;
     }
 
     return 0;
@@ -280,13 +279,17 @@ static krb5_error_code
 check_PAC(krb5_context context,
          krb5_kdc_configuration *config,
          const krb5_principal client_principal,
+         const krb5_principal delegated_proxy_principal,
          hdb_entry_ex *client,
          hdb_entry_ex *server,
-         const EncryptionKey *server_key,
-         const EncryptionKey *krbtgt_key,
+         hdb_entry_ex *krbtgt,
+         const EncryptionKey *server_check_key,
+         const EncryptionKey *krbtgt_check_key,
+         const EncryptionKey *server_sign_key,
+         const EncryptionKey *krbtgt_sign_key,
          EncTicketPart *tkt,
          krb5_data *rspac,
-         int *require_signedpath)
+         int *signedpath)
 {
     AuthorizationData *ad = tkt->authorization_data;
     unsigned i, j;
@@ -306,13 +309,14 @@ check_PAC(krb5_context context,
                                       &child,
                                       NULL);
        if (ret) {
-           krb5_set_error_string(context, "Failed to decode "
-                                 "IF_RELEVANT with %d", ret);
+           krb5_set_error_message(context, ret, "Failed to decode "
+                                  "IF_RELEVANT with %d", ret);
            return ret;
        }
        for (j = 0; j < child.len; j++) {
 
            if (child.val[j].ad_type == KRB5_AUTHDATA_WIN2K_PAC) {
+               int signed_pac = 0;
                krb5_pac pac;
 
                /* Found PAC */
@@ -324,26 +328,34 @@ check_PAC(krb5_context context,
                if (ret)
                    return ret;
 
-               ret = krb5_pac_verify(context, pac, tkt->authtime, 
+               ret = krb5_pac_verify(context, pac, tkt->authtime,
                                      client_principal,
-                                     krbtgt_key, NULL);
+                                     server_check_key, krbtgt_check_key);
                if (ret) {
                    krb5_pac_free(context, pac);
                    return ret;
                }
 
-               ret = _kdc_pac_verify(context, client_principal, 
-                                     client, server, &pac);
+               ret = _kdc_pac_verify(context, client_principal,
+                                     delegated_proxy_principal,
+                                     client, server, krbtgt, &pac, &signed_pac);
                if (ret) {
                    krb5_pac_free(context, pac);
                    return ret;
                }
-               *require_signedpath = 0;
-
-               ret = _krb5_pac_sign(context, pac, tkt->authtime,
-                                    client_principal,
-                                    server_key, krbtgt_key, rspac);
 
+               /*
+                * Only re-sign PAC if we could verify it with the PAC
+                * function. The no-verify case happens when we get in
+                * a PAC from cross realm from a Windows domain and
+                * that there is no PAC verification function.
+                */
+               if (signed_pac) {
+                   *signedpath = 1;
+                   ret = _krb5_pac_sign(context, pac, tkt->authtime,
+                                        client_principal,
+                                        server_sign_key, krbtgt_sign_key, rspac);
+               }
                krb5_pac_free(context, pac);
 
                return ret;
@@ -359,12 +371,12 @@ check_PAC(krb5_context context,
  */
 
 static krb5_error_code
-check_tgs_flags(krb5_context context,        
+check_tgs_flags(krb5_context context,
                krb5_kdc_configuration *config,
                KDC_REQ_BODY *b, const EncTicketPart *tgt, EncTicketPart *et)
 {
     KDCOptions f = b->kdc_options;
-       
+
     if(f.validate){
        if(!tgt->flags.invalid || tgt->starttime == NULL){
            kdc_log(context, config, 0,
@@ -379,7 +391,7 @@ check_tgs_flags(krb5_context context,
        /* XXX  tkt = tgt */
        et->flags.invalid = 0;
     }else if(tgt->flags.invalid){
-       kdc_log(context, config, 0, 
+       kdc_log(context, config, 0,
                "Ticket-granting ticket has INVALID flag set");
        return KRB5KRB_AP_ERR_TKT_INVALID;
     }
@@ -403,7 +415,7 @@ check_tgs_flags(krb5_context context,
     }
     if(tgt->flags.forwarded)
        et->flags.forwarded = 1;
-       
+
     if(f.proxiable){
        if(!tgt->flags.proxiable){
            kdc_log(context, config, 0,
@@ -448,7 +460,7 @@ check_tgs_flags(krb5_context context,
     }
 
     if(f.renewable){
-       if(!tgt->flags.renewable){
+       if(!tgt->flags.renewable || tgt->renew_till == NULL){
            kdc_log(context, config, 0,
                    "Bad request for renewable ticket");
            return KRB5KDC_ERR_BADOPTION;
@@ -473,8 +485,8 @@ check_tgs_flags(krb5_context context,
        et->endtime = *et->starttime + old_life;
        if (et->renew_till != NULL)
            et->endtime = min(*et->renew_till, et->endtime);
-    }      
-    
+    }
+
 #if 0
     /* checks for excess flags */
     if(f.request_anonymous && !config->allow_anonymous){
@@ -487,42 +499,98 @@ check_tgs_flags(krb5_context context,
 }
 
 /*
- *
+ * Determine if constrained delegation is allowed from this client to this server
  */
 
 static krb5_error_code
-check_constrained_delegation(krb5_context context, 
+check_constrained_delegation(krb5_context context,
                             krb5_kdc_configuration *config,
+                            HDB *clientdb,
                             hdb_entry_ex *client,
-                            krb5_const_principal server)
+                            hdb_entry_ex *server,
+                            krb5_const_principal target)
 {
     const HDB_Ext_Constrained_delegation_acl *acl;
     krb5_error_code ret;
-    int i;
+    size_t i;
 
-    ret = hdb_entry_get_ConstrainedDelegACL(&client->entry, &acl);
-    if (ret) {
-       krb5_clear_error_string(context);
+    /*
+     * constrained_delegation (S4U2Proxy) only works within
+     * the same realm. We use the already canonicalized version
+     * of the principals here, while "target" is the principal
+     * provided by the client.
+     */
+    if(!krb5_realm_compare(context, client->entry.principal, server->entry.principal)) {
+       ret = KRB5KDC_ERR_BADOPTION;
+       kdc_log(context, config, 0,
+           "Bad request for constrained delegation");
        return ret;
     }
 
-    if (acl) {
-       for (i = 0; i < acl->len; i++) {
-           if (krb5_principal_compare(context, server, &acl->val[i]) == TRUE)
-               return 0;
+    if (clientdb->hdb_check_constrained_delegation) {
+       ret = clientdb->hdb_check_constrained_delegation(context, clientdb, client, target);
+       if (ret == 0)
+           return 0;
+    } else {
+       /* if client delegates to itself, that ok */
+       if (krb5_principal_compare(context, client->entry.principal, server->entry.principal) == TRUE)
+           return 0;
+
+       ret = hdb_entry_get_ConstrainedDelegACL(&client->entry, &acl);
+       if (ret) {
+           krb5_clear_error_message(context);
+           return ret;
        }
+
+       if (acl) {
+           for (i = 0; i < acl->len; i++) {
+               if (krb5_principal_compare(context, target, &acl->val[i]) == TRUE)
+                   return 0;
+           }
+       }
+       ret = KRB5KDC_ERR_BADOPTION;
     }
     kdc_log(context, config, 0,
            "Bad request for constrained delegation");
-    return KRB5KDC_ERR_BADOPTION;
+    return ret;
 }
 
 /*
+ * Determine if s4u2self is allowed from this client to this server
  *
+ * For example, regardless of the principal being impersonated, if the
+ * 'client' and 'server' are the same, then it's safe.
  */
 
 static krb5_error_code
-verify_flags (krb5_context context, 
+check_s4u2self(krb5_context context,
+              krb5_kdc_configuration *config,
+              HDB *clientdb,
+              hdb_entry_ex *client,
+              krb5_const_principal server)
+{
+    krb5_error_code ret;
+
+    /* if client does a s4u2self to itself, that ok */
+    if (krb5_principal_compare(context, client->entry.principal, server) == TRUE)
+       return 0;
+
+    if (clientdb->hdb_check_s4u2self) {
+       ret = clientdb->hdb_check_s4u2self(context, clientdb, client, server);
+       if (ret == 0)
+           return 0;
+    } else {
+       ret = KRB5KDC_ERR_BADOPTION;
+    }
+    return ret;
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+verify_flags (krb5_context context,
              krb5_kdc_configuration *config,
              const EncTicketPart *et,
              const char *pstr)
@@ -543,19 +611,19 @@ verify_flags (krb5_context context,
  */
 
 static krb5_error_code
-fix_transited_encoding(krb5_context context, 
+fix_transited_encoding(krb5_context context,
                       krb5_kdc_configuration *config,
                       krb5_boolean check_policy,
-                      const TransitedEncoding *tr, 
-                      EncTicketPart *et, 
-                      const char *client_realm, 
-                      const char *server_realm, 
+                      const TransitedEncoding *tr,
+                      EncTicketPart *et,
+                      const char *client_realm,
+                      const char *server_realm,
                       const char *tgt_realm)
 {
     krb5_error_code ret = 0;
     char **realms, **tmp;
-    int num_realms;
-    int i;
+    unsigned int num_realms;
+    size_t i;
 
     switch (tr->tr_type) {
     case DOMAIN_X500_COMPRESS:
@@ -576,9 +644,9 @@ fix_transited_encoding(krb5_context context,
        return KRB5KDC_ERR_TRTYPE_NOSUPP;
     }
 
-    ret = krb5_domain_x500_decode(context, 
+    ret = krb5_domain_x500_decode(context,
                                  tr->contents,
-                                 &realms, 
+                                 &realms,
                                  &num_realms,
                                  client_realm,
                                  server_realm);
@@ -589,7 +657,7 @@ fix_transited_encoding(krb5_context context,
     }
     if(strcmp(client_realm, tgt_realm) && strcmp(server_realm, tgt_realm)) {
        /* not us, so add the previous realm to transited set */
-       if (num_realms < 0 || num_realms + 1 > UINT_MAX/sizeof(*realms)) {
+       if (num_realms + 1 > UINT_MAX/sizeof(*realms)) {
            ret = ERANGE;
            goto free_realms;
        }
@@ -607,7 +675,7 @@ fix_transited_encoding(krb5_context context,
        num_realms++;
     }
     if(num_realms == 0) {
-       if(strcmp(client_realm, server_realm)) 
+       if(strcmp(client_realm, server_realm))
            kdc_log(context, config, 0,
                    "cross-realm %s -> %s", client_realm, server_realm);
     } else {
@@ -630,11 +698,11 @@ fix_transited_encoding(krb5_context context,
        }
     }
     if(check_policy) {
-       ret = krb5_check_transited(context, client_realm, 
-                                  server_realm, 
+       ret = krb5_check_transited(context, client_realm,
+                                  server_realm,
                                   realms, num_realms, NULL);
        if(ret) {
-           krb5_warn(context, ret, "cross-realm %s -> %s", 
+           krb5_warn(context, ret, "cross-realm %s -> %s",
                      client_realm, server_realm);
            goto free_realms;
        }
@@ -653,23 +721,27 @@ fix_transited_encoding(krb5_context context,
 
 
 static krb5_error_code
-tgs_make_reply(krb5_context context, 
+tgs_make_reply(krb5_context context,
               krb5_kdc_configuration *config,
-              KDC_REQ_BODY *b, 
+              KDC_REQ_BODY *b,
               krb5_const_principal tgt_name,
-              const EncTicketPart *tgt, 
+              const EncTicketPart *tgt,
+              const krb5_keyblock *replykey,
+              int rk_is_subkey,
               const EncryptionKey *serverkey,
               const krb5_keyblock *sessionkey,
               krb5_kvno kvno,
               AuthorizationData *auth_data,
-              hdb_entry_ex *server, 
-              const char *server_name, 
-              hdb_entry_ex *client, 
-              krb5_principal client_principal, 
+              hdb_entry_ex *server,
+              krb5_principal server_principal,
+              const char *server_name,
+              hdb_entry_ex *client,
+              krb5_principal client_principal,
               hdb_entry_ex *krbtgt,
               krb5_enctype krbtgt_etype,
-              KRB5SignedPathPrincipals *spp,
+              krb5_principals spp,
               const krb5_data *rspac,
+              const METHOD_DATA *enc_pa_data,
               const char **e_text,
               krb5_data *reply)
 {
@@ -678,11 +750,12 @@ tgs_make_reply(krb5_context context,
     EncTicketPart et;
     KDCOptions f = b->kdc_options;
     krb5_error_code ret;
-    
+    int is_weak = 0;
+
     memset(&rep, 0, sizeof(rep));
     memset(&et, 0, sizeof(et));
     memset(&ek, 0, sizeof(ek));
-    
+
     rep.pvno = 5;
     rep.msg_type = krb_tgs_rep;
 
@@ -691,7 +764,7 @@ tgs_make_reply(krb5_context context,
     et.endtime = min(tgt->endtime, *b->till);
     ALLOC(et.starttime);
     *et.starttime = kdc_time;
-    
+
     ret = check_tgs_flags(context, config, b, tgt, &et);
     if(ret)
        goto out;
@@ -715,23 +788,22 @@ tgs_make_reply(krb5_context context,
 #define PRINCIPAL_FORCE_TRANSITED_CHECK(P)             0
 #define PRINCIPAL_ALLOW_DISABLE_TRANSITED_CHECK(P)     0
 
-    ret = fix_transited_encoding(context, config, 
+    ret = fix_transited_encoding(context, config,
                                 !f.disable_transited_check ||
                                 GLOBAL_FORCE_TRANSITED_CHECK ||
                                 PRINCIPAL_FORCE_TRANSITED_CHECK(server) ||
-                                !((GLOBAL_ALLOW_PER_PRINCIPAL && 
+                                !((GLOBAL_ALLOW_PER_PRINCIPAL &&
                                    PRINCIPAL_ALLOW_DISABLE_TRANSITED_CHECK(server)) ||
                                   GLOBAL_ALLOW_DISABLE_TRANSITED_CHECK),
                                 &tgt->transited, &et,
-                                *krb5_princ_realm(context, client_principal),
-                                *krb5_princ_realm(context, server->entry.principal),
-                                *krb5_princ_realm(context, krbtgt->entry.principal));
+                                krb5_principal_get_realm(context, client_principal),
+                                krb5_principal_get_realm(context, server->entry.principal),
+                                krb5_principal_get_realm(context, krbtgt->entry.principal));
     if(ret)
        goto out;
 
-    copy_Realm(krb5_princ_realm(context, server->entry.principal), 
-              &rep.ticket.realm);
-    _krb5_principal2principalname(&rep.ticket.sname, server->entry.principal);
+    copy_Realm(&server_principal->realm, &rep.ticket.realm);
+    _krb5_principal2principalname(&rep.ticket.sname, server_principal);
     copy_Realm(&tgt_name->realm, &rep.crealm);
 /*
     if (f.request_anonymous)
@@ -754,8 +826,10 @@ tgs_make_reply(krb5_context context,
            life = min(life, *server->entry.max_life);
        et.endtime = *et.starttime + life;
     }
-    if(f.renewable_ok && tgt->flags.renewable && 
-       et.renew_till == NULL && et.endtime < *b->till){
+    if(f.renewable_ok && tgt->flags.renewable &&
+       et.renew_till == NULL && et.endtime < *b->till &&
+       tgt->renew_till != NULL)
+    {
        et.flags.renewable = 1;
        ALLOC(et.renew_till);
        *et.renew_till = *b->till;
@@ -769,13 +843,13 @@ tgs_make_reply(krb5_context context,
            renew = min(renew, *server->entry.max_renew);
        *et.renew_till = et.authtime + renew;
     }
-           
+
     if(et.renew_till){
        *et.renew_till = min(*et.renew_till, *tgt->renew_till);
        *et.starttime = min(*et.starttime, *et.renew_till);
        et.endtime = min(et.endtime, *et.renew_till);
     }
-    
+
     *et.starttime = min(*et.starttime, et.endtime);
 
     if(*et.starttime == et.endtime){
@@ -787,22 +861,43 @@ tgs_make_reply(krb5_context context,
        et.renew_till = NULL;
        et.flags.renewable = 0;
     }
-    
+
     et.flags.pre_authent = tgt->flags.pre_authent;
     et.flags.hw_authent  = tgt->flags.hw_authent;
     et.flags.anonymous   = tgt->flags.anonymous;
     et.flags.ok_as_delegate = server->entry.flags.ok_as_delegate;
-           
+
+    if(rspac->length) {
+       /*
+        * No not need to filter out the any PAC from the
+        * auth_data since it's signed by the KDC.
+        */
+       ret = _kdc_tkt_add_if_relevant_ad(context, &et,
+                                         KRB5_AUTHDATA_WIN2K_PAC, rspac);
+       if (ret)
+           goto out;
+    }
+
     if (auth_data) {
-       /* XXX Check enc-authorization-data */
-       et.authorization_data = calloc(1, sizeof(*et.authorization_data));
+       unsigned int i = 0;
+
+       /* XXX check authdata */
+
        if (et.authorization_data == NULL) {
-           ret = ENOMEM;
-           goto out;
+           et.authorization_data = calloc(1, sizeof(*et.authorization_data));
+           if (et.authorization_data == NULL) {
+               ret = ENOMEM;
+               krb5_set_error_message(context, ret, "malloc: out of memory");
+               goto out;
+           }
+       }
+       for(i = 0; i < auth_data->len ; i++) {
+           ret = add_AuthorizationData(et.authorization_data, &auth_data->val[i]);
+           if (ret) {
+               krb5_set_error_message(context, ret, "malloc: out of memory");
+               goto out;
+           }
        }
-       ret = copy_AuthorizationData(auth_data, et.authorization_data);
-       if (ret)
-           goto out;
 
        /* Filter out type KRB5SignedPath */
        ret = find_KRB5SignedPath(context, et.authorization_data, NULL);
@@ -819,24 +914,12 @@ tgs_make_reply(krb5_context context,
        }
     }
 
-    if(rspac->length) {
-       /*
-        * No not need to filter out the any PAC from the
-        * auth_data since its signed by the KDC.
-        */
-       ret = _kdc_tkt_add_if_relevant_ad(context, &et,
-                                         KRB5_AUTHDATA_WIN2K_PAC,
-                                         rspac);
-       if (ret)
-           goto out;
-    }
-
     ret = krb5_copy_keyblock_contents(context, sessionkey, &et.key);
     if (ret)
        goto out;
-    et.crealm = tgt->crealm;
+    et.crealm = tgt_name->realm;
     et.cname = tgt_name->name;
-           
+
     ek.key = et.key;
     /* MIT must have at least one last_req */
     ek.last_req.len = 1;
@@ -853,8 +936,8 @@ tgs_make_reply(krb5_context context,
     ek.renew_till = et.renew_till;
     ek.srealm = rep.ticket.realm;
     ek.sname = rep.ticket.sname;
-    
-    _kdc_log_timestamp(context, config, "TGS-REQ", et.authtime, et.starttime, 
+
+    _kdc_log_timestamp(context, config, "TGS-REQ", et.authtime, et.starttime,
                       et.endtime, et.renew_till);
 
     /* Don't sign cross realm tickets, they can't be checked anyway */
@@ -866,6 +949,7 @@ tgs_make_reply(krb5_context context,
                                          config,
                                          krbtgt,
                                          krbtgt_etype,
+                                         client_principal,
                                          NULL,
                                          spp,
                                          &et);
@@ -874,6 +958,25 @@ tgs_make_reply(krb5_context context,
        }
     }
 
+    if (enc_pa_data->len) {
+       rep.padata = calloc(1, sizeof(*rep.padata));
+       if (rep.padata == NULL) {
+           ret = ENOMEM;
+           goto out;
+       }
+       ret = copy_METHOD_DATA(enc_pa_data, rep.padata);
+       if (ret)
+           goto out;
+    }
+
+    if (krb5_enctype_valid(context, et.key.keytype) != 0
+       && _kdc_is_weak_exception(server->entry.principal, et.key.keytype))
+    {
+       krb5_enctype_enable(context, et.key.keytype);
+       is_weak = 1;
+    }
+
+
     /* It is somewhat unclear where the etype in the following
        encryption should come from. What we have is a session
        key in the passed tgt, and a list of preferred etypes
@@ -884,10 +987,14 @@ tgs_make_reply(krb5_context context,
        CAST session key. Should the DES3 etype be added to the
        etype list, even if we don't want a session key with
        DES3? */
-    ret = _kdc_encode_reply(context, config, 
+    ret = _kdc_encode_reply(context, config,
                            &rep, &et, &ek, et.key.keytype,
-                           kvno, 
-                           serverkey, 0, &tgt->key, e_text, reply);
+                           kvno,
+                           serverkey, 0, replykey, rk_is_subkey,
+                           e_text, reply);
+    if (is_weak)
+       krb5_enctype_disable(context, et.key.keytype);
+
 out:
     free_TGS_REP(&rep);
     free_TransitedEncoding(&et.transited);
@@ -906,20 +1013,20 @@ out:
 }
 
 static krb5_error_code
-tgs_check_authenticator(krb5_context context, 
+tgs_check_authenticator(krb5_context context,
                        krb5_kdc_configuration *config,
                        krb5_auth_context ac,
-                       KDC_REQ_BODY *b, 
+                       KDC_REQ_BODY *b,
                        const char **e_text,
                        krb5_keyblock *key)
 {
     krb5_authenticator auth;
-    size_t len;
+    size_t len = 0;
     unsigned char *buf;
     size_t buf_size;
     krb5_error_code ret;
     krb5_crypto crypto;
-    
+
     krb5_auth_con_getauthenticator(context, ac, &auth);
     if(auth->cksum == NULL){
        kdc_log(context, config, 0, "No authenticator in request");
@@ -936,17 +1043,18 @@ tgs_check_authenticator(krb5_context context,
        ||
 #endif
  !krb5_checksum_is_collision_proof(context, auth->cksum->cksumtype)) {
-       kdc_log(context, config, 0, "Bad checksum type in authenticator: %d", 
+       kdc_log(context, config, 0, "Bad checksum type in authenticator: %d",
                auth->cksum->cksumtype);
        ret =  KRB5KRB_AP_ERR_INAPP_CKSUM;
        goto out;
     }
-               
+
     /* XXX should not re-encode this */
     ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, b, &len, ret);
     if(ret){
-       kdc_log(context, config, 0, "Failed to encode KDC-REQ-BODY: %s", 
-               krb5_get_err_text(context, ret));
+       const char *msg = krb5_get_error_message(context, ret);
+       kdc_log(context, config, 0, "Failed to encode KDC-REQ-BODY: %s", msg);
+       krb5_free_error_message(context, msg);
        goto out;
     }
     if(buf_size != len) {
@@ -958,23 +1066,25 @@ tgs_check_authenticator(krb5_context context,
     }
     ret = krb5_crypto_init(context, key, 0, &crypto);
     if (ret) {
+       const char *msg = krb5_get_error_message(context, ret);
        free(buf);
-       kdc_log(context, config, 0, "krb5_crypto_init failed: %s",
-               krb5_get_err_text(context, ret));
+       kdc_log(context, config, 0, "krb5_crypto_init failed: %s", msg);
+       krb5_free_error_message(context, msg);
        goto out;
     }
     ret = krb5_verify_checksum(context,
                               crypto,
                               KRB5_KU_TGS_REQ_AUTH_CKSUM,
-                              buf, 
+                              buf,
                               len,
                               auth->cksum);
     free(buf);
     krb5_crypto_destroy(context, crypto);
     if(ret){
+       const char *msg = krb5_get_error_message(context, ret);
        kdc_log(context, config, 0,
-               "Failed to verify authenticator checksum: %s", 
-               krb5_get_err_text(context, ret));
+               "Failed to verify authenticator checksum: %s", msg);
+       krb5_free_error_message(context, msg);
     }
 out:
     free_Authenticator(auth);
@@ -991,27 +1101,55 @@ find_rpath(krb5_context context, Realm crealm, Realm srealm)
 {
     const char *new_realm = krb5_config_get_string(context,
                                                   NULL,
-                                                  "capaths", 
+                                                  "capaths",
                                                   crealm,
                                                   srealm,
                                                   NULL);
     return new_realm;
 }
-           
+
 
 static krb5_boolean
-need_referral(krb5_context context, krb5_principal server, krb5_realm **realms)
+need_referral(krb5_context context, krb5_kdc_configuration *config,
+             const KDCOptions * const options, krb5_principal server,
+             krb5_realm **realms)
 {
-    if(server->name.name_type != KRB5_NT_SRV_INST ||
-       server->name.name_string.len != 2)
+    const char *name;
+
+    if(!options->canonicalize && server->name.name_type != KRB5_NT_SRV_INST)
        return FALSE;
-    return _krb5_get_host_realm_int(context, server->name.name_string.val[1],
-                                   FALSE, realms) == 0;
+
+    if (server->name.name_string.len == 1)
+       name = server->name.name_string.val[0];
+    else if (server->name.name_string.len == 3 &&
+            strcasecmp("E3514235-4B06-11D1-AB04-00C04FC2DCD2", server->name.name_string.val[0]) == 0) {
+       /*
+         This is used to give referrals for the
+         E3514235-4B06-11D1-AB04-00C04FC2DCD2/NTDSGUID/DNSDOMAIN
+         SPN form, which is used for inter-domain communication in AD
+        */
+       name = server->name.name_string.val[2];
+       kdc_log(context, config, 0, "Giving 3 part DRSUAPI referral for %s", name);
+       *realms = malloc(sizeof(char *)*2);
+       if (*realms == NULL) {
+           krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
+           return FALSE;
+       }
+       (*realms)[0] = strdup(name);
+       (*realms)[1] = NULL;
+       return TRUE;
+    } else if (server->name.name_string.len > 1)
+       name = server->name.name_string.val[1];
+    else
+       return FALSE;
+
+    kdc_log(context, config, 0, "Searching referral for %s", name);
+
+    return _krb5_get_host_realm_int(context, name, FALSE, realms) == 0;
 }
 
 static krb5_error_code
-tgs_parse_request(krb5_context context, 
+tgs_parse_request(krb5_context context,
                  krb5_kdc_configuration *config,
                  KDC_REQ_BODY *b,
                  const PA_DATA *tgs_req,
@@ -1023,8 +1161,11 @@ tgs_parse_request(krb5_context context,
                  const struct sockaddr *from_addr,
                  time_t **csec,
                  int **cusec,
-                 AuthorizationData **auth_data)
+                 AuthorizationData **auth_data,
+                 krb5_keyblock **replykey,
+                 int *rk_is_subkey)
 {
+    static char failed[] = "<unparse_name failed>";
     krb5_ap_req ap_req;
     krb5_error_code ret;
     krb5_principal princ;
@@ -1033,16 +1174,20 @@ tgs_parse_request(krb5_context context,
     krb5_flags verify_ap_req_flags;
     krb5_crypto crypto;
     Key *tkey;
+    krb5_keyblock *subkey = NULL;
+    unsigned usage;
 
     *auth_data = NULL;
     *csec  = NULL;
     *cusec = NULL;
+    *replykey = NULL;
 
     memset(&ap_req, 0, sizeof(ap_req));
     ret = krb5_decode_ap_req(context, &tgs_req->padata_value, &ap_req);
     if(ret){
-       kdc_log(context, config, 0, "Failed to decode AP-REQ: %s", 
-               krb5_get_err_text(context, ret));
+       const char *msg = krb5_get_error_message(context, ret);
+       kdc_log(context, config, 0, "Failed to decode AP-REQ: %s", msg);
+       krb5_free_error_message(context, msg);
        goto out;
     }
 
@@ -1052,39 +1197,51 @@ tgs_parse_request(krb5_context context,
        ret = KRB5KDC_ERR_POLICY; /* ? */
        goto out;
     }
-    
+
     _krb5_principalname2krb5_principal(context,
                                       &princ,
                                       ap_req.ticket.sname,
                                       ap_req.ticket.realm);
-    
-    ret = _kdc_db_fetch(context, config, princ, HDB_F_GET_KRBTGT, NULL, krbtgt);
 
-    if(ret) {
+    ret = _kdc_db_fetch(context, config, princ, HDB_F_GET_KRBTGT, ap_req.ticket.enc_part.kvno, NULL, krbtgt);
+
+    if(ret == HDB_ERR_NOT_FOUND_HERE) {
+       char *p;
+       ret = krb5_unparse_name(context, princ, &p);
+       if (ret != 0)
+           p = failed;
+       krb5_free_principal(context, princ);
+       kdc_log(context, config, 5, "Ticket-granting ticket account %s does not have secrets at this KDC, need to proxy", p);
+       if (ret == 0)
+           free(p);
+       ret = HDB_ERR_NOT_FOUND_HERE;
+       goto out;
+    } else if(ret){
+       const char *msg = krb5_get_error_message(context, ret);
        char *p;
        ret = krb5_unparse_name(context, princ, &p);
        if (ret != 0)
-           p = "<unparse_name failed>";
+           p = failed;
        krb5_free_principal(context, princ);
        kdc_log(context, config, 0,
-               "Ticket-granting ticket not found in database: %s: %s",
-               p, krb5_get_err_text(context, ret));
+               "Ticket-granting ticket not found in database: %s", msg);
+       krb5_free_error_message(context, msg);
        if (ret == 0)
            free(p);
        ret = KRB5KRB_AP_ERR_NOT_US;
        goto out;
     }
-    
-    if(ap_req.ticket.enc_part.kvno && 
+
+    if(ap_req.ticket.enc_part.kvno &&
        *ap_req.ticket.enc_part.kvno != (*krbtgt)->entry.kvno){
        char *p;
 
        ret = krb5_unparse_name (context, princ, &p);
        krb5_free_principal(context, princ);
        if (ret != 0)
-           p = "<unparse_name failed>";
+           p = failed;
        kdc_log(context, config, 0,
-               "Ticket kvno = %d, DB kvno = %d (%s)", 
+               "Ticket kvno = %d, DB kvno = %d (%s)",
                *ap_req.ticket.enc_part.kvno,
                (*krbtgt)->entry.kvno,
                p);
@@ -1096,20 +1253,23 @@ tgs_parse_request(krb5_context context,
 
     *krbtgt_etype = ap_req.ticket.enc_part.etype;
 
-    ret = hdb_enctype2key(context, &(*krbtgt)->entry, 
+    ret = hdb_enctype2key(context, &(*krbtgt)->entry,
                          ap_req.ticket.enc_part.etype, &tkey);
     if(ret){
-       char *str, *p;
+       char *str = NULL, *p = NULL;
+
        krb5_enctype_to_string(context, ap_req.ticket.enc_part.etype, &str);
        krb5_unparse_name(context, princ, &p);
-       kdc_log(context, config, 0,
-               "No server key with enctype %s found for %s", str, p);
+       kdc_log(context, config, 0,
+               "No server key with enctype %s found for %s",
+               str ? str : "<unknown enctype>",
+               p ? p : "<unparse_name failed>");
        free(str);
        free(p);
        ret = KRB5KRB_AP_ERR_BADKEYVER;
        goto out;
     }
-    
+
     if (b->kdc_options.validate)
        verify_ap_req_flags = KRB5_VERIFY_AP_REQ_IGNORE_INVALID;
     else
@@ -1124,11 +1284,12 @@ tgs_parse_request(krb5_context context,
                              &ap_req_options,
                              ticket,
                              KRB5_KU_TGS_REQ_AUTH);
-                            
+
     krb5_free_principal(context, princ);
     if(ret) {
-       kdc_log(context, config, 0, "Failed to verify AP-REQ: %s", 
-               krb5_get_err_text(context, ret));
+       const char *msg = krb5_get_error_message(context, ret);
+       kdc_log(context, config, 0, "Failed to verify AP-REQ: %s", msg);
+       krb5_free_error_message(context, msg);
        goto out;
     }
 
@@ -1155,62 +1316,71 @@ tgs_parse_request(krb5_context context,
        }
     }
 
-    ret = tgs_check_authenticator(context, config, 
+    ret = tgs_check_authenticator(context, config,
                                  ac, b, e_text, &(*ticket)->ticket.key);
     if (ret) {
        krb5_auth_con_free(context, ac);
        goto out;
     }
 
-    if (b->enc_authorization_data) {
-       krb5_keyblock *subkey;
-       krb5_data ad;
-       ret = krb5_auth_con_getremotesubkey(context,
-                                           ac,
-                                           &subkey);
-       if(ret){
-           krb5_auth_con_free(context, ac);
-           kdc_log(context, config, 0, "Failed to get remote subkey: %s", 
-                   krb5_get_err_text(context, ret));
-           goto out;
-       }
-       if(subkey == NULL){
-           ret = krb5_auth_con_getkey(context, ac, &subkey);
-           if(ret) {
-               krb5_auth_con_free(context, ac);
-               kdc_log(context, config, 0, "Failed to get session key: %s", 
-                       krb5_get_err_text(context, ret));
-               goto out;
-           }
-       }
-       if(subkey == NULL){
+    usage = KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY;
+    *rk_is_subkey = 1;
+
+    ret = krb5_auth_con_getremotesubkey(context, ac, &subkey);
+    if(ret){
+       const char *msg = krb5_get_error_message(context, ret);
+       krb5_auth_con_free(context, ac);
+       kdc_log(context, config, 0, "Failed to get remote subkey: %s", msg);
+       krb5_free_error_message(context, msg);
+       goto out;
+    }
+    if(subkey == NULL){
+       usage = KRB5_KU_TGS_REQ_AUTH_DAT_SESSION;
+       *rk_is_subkey = 0;
+
+       ret = krb5_auth_con_getkey(context, ac, &subkey);
+       if(ret) {
+           const char *msg = krb5_get_error_message(context, ret);
            krb5_auth_con_free(context, ac);
-           kdc_log(context, config, 0,
-                   "Failed to get key for enc-authorization-data");
-           ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
+           kdc_log(context, config, 0, "Failed to get session key: %s", msg);
+           krb5_free_error_message(context, msg);
            goto out;
        }
+    }
+    if(subkey == NULL){
+       krb5_auth_con_free(context, ac);
+       kdc_log(context, config, 0,
+               "Failed to get key for enc-authorization-data");
+       ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
+       goto out;
+    }
+
+    *replykey = subkey;
+
+    if (b->enc_authorization_data) {
+       krb5_data ad;
+
        ret = krb5_crypto_init(context, subkey, 0, &crypto);
        if (ret) {
+           const char *msg = krb5_get_error_message(context, ret);
            krb5_auth_con_free(context, ac);
-           kdc_log(context, config, 0, "krb5_crypto_init failed: %s",
-                   krb5_get_err_text(context, ret));
+           kdc_log(context, config, 0, "krb5_crypto_init failed: %s", msg);
+           krb5_free_error_message(context, msg);
            goto out;
        }
        ret = krb5_decrypt_EncryptedData (context,
                                          crypto,
-                                         KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY,
+                                         usage,
                                          b->enc_authorization_data,
                                          &ad);
        krb5_crypto_destroy(context, crypto);
        if(ret){
            krb5_auth_con_free(context, ac);
-           kdc_log(context, config, 0, 
+           kdc_log(context, config, 0,
                    "Failed to decrypt enc-authorization-data");
            ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
            goto out;
        }
-       krb5_free_keyblock(context, subkey);
        ALLOC(*auth_data);
        if (*auth_data == NULL) {
            krb5_auth_con_free(context, ac);
@@ -1229,62 +1399,153 @@ tgs_parse_request(krb5_context context,
     }
 
     krb5_auth_con_free(context, ac);
-    
+
 out:
     free_AP_REQ(&ap_req);
-    
+
     return ret;
 }
 
 static krb5_error_code
-tgs_build_reply(krb5_context context, 
+build_server_referral(krb5_context context,
+                     krb5_kdc_configuration *config,
+                     krb5_crypto session,
+                     krb5_const_realm referred_realm,
+                     const PrincipalName *true_principal_name,
+                     const PrincipalName *requested_principal,
+                     krb5_data *outdata)
+{
+    PA_ServerReferralData ref;
+    krb5_error_code ret;
+    EncryptedData ed;
+    krb5_data data;
+    size_t size = 0;
+
+    memset(&ref, 0, sizeof(ref));
+
+    if (referred_realm) {
+       ALLOC(ref.referred_realm);
+       if (ref.referred_realm == NULL)
+           goto eout;
+       *ref.referred_realm = strdup(referred_realm);
+       if (*ref.referred_realm == NULL)
+           goto eout;
+    }
+    if (true_principal_name) {
+       ALLOC(ref.true_principal_name);
+       if (ref.true_principal_name == NULL)
+           goto eout;
+       ret = copy_PrincipalName(true_principal_name, ref.true_principal_name);
+       if (ret)
+           goto eout;
+    }
+    if (requested_principal) {
+       ALLOC(ref.requested_principal_name);
+       if (ref.requested_principal_name == NULL)
+           goto eout;
+       ret = copy_PrincipalName(requested_principal,
+                                ref.requested_principal_name);
+       if (ret)
+           goto eout;
+    }
+
+    ASN1_MALLOC_ENCODE(PA_ServerReferralData,
+                      data.data, data.length,
+                      &ref, &size, ret);
+    free_PA_ServerReferralData(&ref);
+    if (ret)
+       return ret;
+    if (data.length != size)
+       krb5_abortx(context, "internal asn.1 encoder error");
+
+    ret = krb5_encrypt_EncryptedData(context, session,
+                                    KRB5_KU_PA_SERVER_REFERRAL,
+                                    data.data, data.length,
+                                    0 /* kvno */, &ed);
+    free(data.data);
+    if (ret)
+       return ret;
+
+    ASN1_MALLOC_ENCODE(EncryptedData,
+                      outdata->data, outdata->length,
+                      &ed, &size, ret);
+    free_EncryptedData(&ed);
+    if (ret)
+       return ret;
+    if (outdata->length != size)
+       krb5_abortx(context, "internal asn.1 encoder error");
+
+    return 0;
+eout:
+    free_PA_ServerReferralData(&ref);
+    krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+    return ENOMEM;
+}
+
+static krb5_error_code
+tgs_build_reply(krb5_context context,
                krb5_kdc_configuration *config,
-               KDC_REQ *req, 
+               KDC_REQ *req,
                KDC_REQ_BODY *b,
                hdb_entry_ex *krbtgt,
                krb5_enctype krbtgt_etype,
+               const krb5_keyblock *replykey,
+               int rk_is_subkey,
                krb5_ticket *ticket,
                krb5_data *reply,
                const char *from,
                const char **e_text,
-               AuthorizationData *auth_data,
-               const struct sockaddr *from_addr,
-               int datagram_reply)
+               AuthorizationData **auth_data,
+               const struct sockaddr *from_addr)
 {
     krb5_error_code ret;
-    krb5_principal cp = NULL, sp = NULL;
-    krb5_principal client_principal = NULL;
-    char *spn = NULL, *cpn = NULL;
-    hdb_entry_ex *server = NULL, *client = NULL;
+    krb5_principal cp = NULL, sp = NULL, tp = NULL, dp = NULL;
+    krb5_principal krbtgt_principal = NULL;
+    char *spn = NULL, *cpn = NULL, *tpn = NULL, *dpn = NULL;
+    hdb_entry_ex *server = NULL, *client = NULL, *s4u2self_impersonated_client = NULL;
+    HDB *clientdb, *s4u2self_impersonated_clientdb;
+    krb5_realm ref_realm = NULL;
     EncTicketPart *tgt = &ticket->ticket;
-    KRB5SignedPathPrincipals *spp = NULL;
+    krb5_principals spp = NULL;
     const EncryptionKey *ekey;
     krb5_keyblock sessionkey;
     krb5_kvno kvno;
     krb5_data rspac;
-    int cross_realm = 0;
+
+    hdb_entry_ex *krbtgt_out = NULL;
+
+    METHOD_DATA enc_pa_data;
 
     PrincipalName *s;
     Realm r;
     int nloop = 0;
     EncTicketPart adtkt;
     char opt_str[128];
-    int require_signedpath = 0;
+    int signedpath = 0;
+
+    Key *tkey_check;
+    Key *tkey_sign;
+    Key *tkey_krbtgt_check = NULL;
+    int flags = HDB_F_FOR_TGS_REQ;
 
     memset(&sessionkey, 0, sizeof(sessionkey));
     memset(&adtkt, 0, sizeof(adtkt));
     krb5_data_zero(&rspac);
+    memset(&enc_pa_data, 0, sizeof(enc_pa_data));
 
     s = b->sname;
     r = b->realm;
 
+    if (b->kdc_options.canonicalize)
+       flags |= HDB_F_CANON;
+
     if(b->kdc_options.enc_tkt_in_skey){
        Ticket *t;
        hdb_entry_ex *uu;
        krb5_principal p;
        Key *uukey;
-           
-       if(b->additional_tickets == NULL || 
+
+       if(b->additional_tickets == NULL ||
           b->additional_tickets->len == 0){
            ret = KRB5KDC_ERR_BADOPTION; /* ? */
            kdc_log(context, config, 0,
@@ -1299,8 +1560,8 @@ tgs_build_reply(krb5_context context,
            goto out;
        }
        _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm);
-       ret = _kdc_db_fetch(context, config, p, 
-                           HDB_F_GET_CLIENT|HDB_F_GET_SERVER, 
+       ret = _kdc_db_fetch(context, config, p,
+                           HDB_F_GET_KRBTGT, t->enc_part.kvno,
                            NULL, &uu);
        krb5_free_principal(context, p);
        if(ret){
@@ -1308,7 +1569,7 @@ tgs_build_reply(krb5_context context,
                ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
            goto out;
        }
-       ret = hdb_enctype2key(context, &uu->entry, 
+       ret = hdb_enctype2key(context, &uu->entry,
                              t->enc_part.etype, &uukey);
        if(ret){
            _kdc_free_ent(context, uu);
@@ -1329,7 +1590,7 @@ tgs_build_reply(krb5_context context,
     }
 
     _krb5_principalname2krb5_principal(context, &sp, *s, r);
-    ret = krb5_unparse_name(context, sp, &spn);        
+    ret = krb5_unparse_name(context, sp, &spn);
     if (ret)
        goto out;
     _krb5_principalname2krb5_principal(context, &cp, tgt->cname, tgt->crealm);
@@ -1341,7 +1602,7 @@ tgs_build_reply(krb5_context context,
                   opt_str, sizeof(opt_str));
     if(*opt_str)
        kdc_log(context, config, 0,
-               "TGS-REQ %s from %s for %s [%s]", 
+               "TGS-REQ %s from %s for %s [%s]",
                cpn, from, spn, opt_str);
     else
        kdc_log(context, config, 0,
@@ -1352,10 +1613,14 @@ tgs_build_reply(krb5_context context,
      */
 
 server_lookup:
-    ret = _kdc_db_fetch(context, config, sp, HDB_F_GET_SERVER, NULL, &server);
+    ret = _kdc_db_fetch(context, config, sp, HDB_F_GET_SERVER | flags,
+                       NULL, NULL, &server);
 
-    if(ret){
-       const char *new_rlm;
+    if(ret == HDB_ERR_NOT_FOUND_HERE) {
+       kdc_log(context, config, 5, "target %s does not have secrets at this KDC, need to proxy", sp);
+       goto out;
+    } else if(ret){
+       const char *new_rlm, *msg;
        Realm req_rlm;
        krb5_realm *realms;
 
@@ -1364,19 +1629,23 @@ server_lookup:
                new_rlm = find_rpath(context, tgt->crealm, req_rlm);
                if(new_rlm) {
                    kdc_log(context, config, 5, "krbtgt for realm %s "
-                           "not found, trying %s", 
+                           "not found, trying %s",
                            req_rlm, new_rlm);
                    krb5_free_principal(context, sp);
                    free(spn);
-                   krb5_make_principal(context, &sp, r, 
+                   krb5_make_principal(context, &sp, r,
                                        KRB5_TGS_NAME, new_rlm, NULL);
-                   ret = krb5_unparse_name(context, sp, &spn); 
+                   ret = krb5_unparse_name(context, sp, &spn);
                    if (ret)
                        goto out;
+
+                   if (ref_realm)
+                       free(ref_realm);
+                   ref_realm = strdup(new_rlm);
                    goto server_lookup;
                }
            }
-       } else if(need_referral(context, sp, &realms)) {
+       } else if(need_referral(context, config, &b->kdc_options, sp, &realms)) {
            if (strcmp(realms[0], sp->realm) != 0) {
                kdc_log(context, config, 5,
                        "Returning a referral to realm %s for "
@@ -1389,22 +1658,162 @@ server_lookup:
                ret = krb5_unparse_name(context, sp, &spn);
                if (ret)
                    goto out;
+
+               if (ref_realm)
+                   free(ref_realm);
+               ref_realm = strdup(realms[0]);
+
                krb5_free_host_realm(context, realms);
                goto server_lookup;
            }
            krb5_free_host_realm(context, realms);
        }
+       msg = krb5_get_error_message(context, ret);
        kdc_log(context, config, 0,
-               "Server not found in database: %s: %s", spn,
-               krb5_get_err_text(context, ret));
+               "Server not found in database: %s: %s", spn, msg);
+       krb5_free_error_message(context, msg);
        if (ret == HDB_ERR_NOENTRY)
            ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
        goto out;
     }
 
-    ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT, NULL, &client);
+    /*
+     * Select enctype, return key and kvno.
+     */
+
+    {
+       krb5_enctype etype;
+
+       if(b->kdc_options.enc_tkt_in_skey) {
+           size_t i;
+           ekey = &adtkt.key;
+           for(i = 0; i < b->etype.len; i++)
+               if (b->etype.val[i] == adtkt.key.keytype)
+                   break;
+           if(i == b->etype.len) {
+               kdc_log(context, config, 0,
+                       "Addition ticket have not matching etypes");
+               krb5_clear_error_message(context);
+               ret = KRB5KDC_ERR_ETYPE_NOSUPP;
+               goto out;
+           }
+           etype = b->etype.val[i];
+           kvno = 0;
+       } else {
+           Key *skey;
+
+           ret = _kdc_find_etype(context,
+                                 config->tgs_use_strongest_session_key, FALSE,
+                                 server, b->etype.val, b->etype.len, NULL,
+                                 &skey);
+           if(ret) {
+               kdc_log(context, config, 0,
+                       "Server (%s) has no support for etypes", spn);
+               goto out;
+           }
+           ekey = &skey->key;
+           etype = skey->key.keytype;
+           kvno = server->entry.kvno;
+       }
+
+       ret = krb5_generate_random_keyblock(context, etype, &sessionkey);
+       if (ret)
+           goto out;
+    }
+
+    /*
+     * Check that service is in the same realm as the krbtgt. If it's
+     * not the same, it's someone that is using a uni-directional trust
+     * backward.
+     */
+
+    /*
+     * Validate authoriation data
+     */
+
+    ret = hdb_enctype2key(context, &krbtgt->entry,
+                         krbtgt_etype, &tkey_check);
+    if(ret) {
+       kdc_log(context, config, 0,
+                   "Failed to find key for krbtgt PAC check");
+       goto out;
+    }
+
+    /* Now refetch the primary krbtgt, and get the current kvno (the
+     * sign check may have been on an old kvno, and the server may
+     * have been an incoming trust) */
+    ret = krb5_make_principal(context, &krbtgt_principal,
+                             krb5_principal_get_comp_string(context,
+                                                            krbtgt->entry.principal,
+                                                            1),
+                             KRB5_TGS_NAME,
+                             krb5_principal_get_comp_string(context,
+                                                            krbtgt->entry.principal,
+                                                            1), NULL);
+    if(ret) {
+       kdc_log(context, config, 0,
+                   "Failed to generate krbtgt principal");
+       goto out;
+    }
+
+    ret = _kdc_db_fetch(context, config, krbtgt_principal, HDB_F_GET_KRBTGT, NULL, NULL, &krbtgt_out);
+    krb5_free_principal(context, krbtgt_principal);
+    if (ret) {
+       krb5_error_code ret2;
+       char *ktpn, *ktpn2;
+       ret = krb5_unparse_name(context, krbtgt->entry.principal, &ktpn);
+       ret2 = krb5_unparse_name(context, krbtgt_principal, &ktpn2);
+       kdc_log(context, config, 0,
+               "Request with wrong krbtgt: %s, %s not found in our database",
+               (ret == 0) ? ktpn : "<unknown>", (ret2 == 0) ? ktpn2 : "<unknown>");
+       if(ret == 0)
+           free(ktpn);
+       if(ret2 == 0)
+           free(ktpn2);
+       ret = KRB5KRB_AP_ERR_NOT_US;
+       goto out;
+    }
+
+    /* The first realm is the realm of the service, the second is
+     * krbtgt/<this>/@REALM component of the krbtgt DN the request was
+     * encrypted to.  The redirection via the krbtgt_out entry allows
+     * the DB to possibly correct the case of the realm (Samba4 does
+     * this) before the strcmp() */
+    if (strcmp(krb5_principal_get_realm(context, server->entry.principal),
+              krb5_principal_get_realm(context, krbtgt_out->entry.principal)) != 0) {
+       char *ktpn;
+       ret = krb5_unparse_name(context, krbtgt_out->entry.principal, &ktpn);
+       kdc_log(context, config, 0,
+               "Request with wrong krbtgt: %s",
+               (ret == 0) ? ktpn : "<unknown>");
+       if(ret == 0)
+           free(ktpn);
+       ret = KRB5KRB_AP_ERR_NOT_US;
+    }
+
+    ret = hdb_enctype2key(context, &krbtgt_out->entry,
+                         krbtgt_etype, &tkey_sign);
     if(ret) {
-       const char *krbtgt_realm; 
+       kdc_log(context, config, 0,
+                   "Failed to find key for krbtgt PAC signature");
+       goto out;
+    }
+
+    /* Check if we would know the krbtgt key for the PAC.  We would
+     * only know this if the krbtgt principal was the same (ie, in our
+     * realm, regardless of KVNO) */
+    if (krb5_principal_compare(context, krbtgt_out->entry.principal, krbtgt->entry.principal)) {
+       tkey_krbtgt_check = tkey_check;
+    }
+
+    ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags,
+                       NULL, &clientdb, &client);
+    if(ret == HDB_ERR_NOT_FOUND_HERE) {
+       /* This is OK, we are just trying to find out if they have
+        * been disabled or deleted in the meantime, missing secrets
+        * is OK */
+    } else if(ret){
+       const char *krbtgt_realm, *msg;
 
        /*
         * If the client belongs to the same realm as our krbtgt, it
@@ -1412,9 +1821,7 @@ server_lookup:
         *
         */
 
-       krbtgt_realm = 
-           krb5_principal_get_comp_string(context, 
-                                          krbtgt->entry.principal, 1);
+       krbtgt_realm = krb5_principal_get_realm(context, krbtgt_out->entry.principal);
 
        if(strcmp(krb5_principal_get_realm(context, cp), krbtgt_realm) == 0) {
            if (ret == HDB_ERR_NOENTRY)
@@ -1423,53 +1830,64 @@ server_lookup:
                    cpn);
            goto out;
        }
-       
-       kdc_log(context, config, 1, "Client not found in database: %s: %s",
-               cpn, krb5_get_err_text(context, ret));
 
-       cross_realm = 1;
+       msg = krb5_get_error_message(context, ret);
+       kdc_log(context, config, 1, "Client not found in database: %s", msg);
+       krb5_free_error_message(context, msg);
     }
-    
-    /*
-     * Check that service is in the same realm as the krbtgt. If its
-     * not the same, its someone that is using a uni-directional trust
-     * backward.
-     */
-    
-    if (strcmp(krb5_principal_get_realm(context, sp),
-              krb5_principal_get_comp_string(context, 
-                                             krbtgt->entry.principal, 
-                                             1)) != 0) {
-       char *tpn;
-       ret = krb5_unparse_name(context, krbtgt->entry.principal, &tpn);
+
+    ret = check_PAC(context, config, cp, NULL,
+                   client, server, krbtgt,
+                   &tkey_check->key,
+                   tkey_krbtgt_check ? &tkey_krbtgt_check->key : NULL,
+                   ekey, &tkey_sign->key,
+                   tgt, &rspac, &signedpath);
+    if (ret) {
+       const char *msg = krb5_get_error_message(context, ret);
        kdc_log(context, config, 0,
-               "Request with wrong krbtgt: %s",
-               (ret == 0) ? tpn : "<unknown>");
-       if(ret == 0)
-           free(tpn);
-       ret = KRB5KRB_AP_ERR_NOT_US;
+               "Verify PAC failed for %s (%s) from %s with %s",
+               spn, cpn, from, msg);
+       krb5_free_error_message(context, msg);
+       goto out;
+    }
+
+    /* also check the krbtgt for signature */
+    ret = check_KRB5SignedPath(context,
+                              config,
+                              krbtgt,
+                              cp,
+                              tgt,
+                              &spp,
+                              &signedpath);
+    if (ret) {
+       const char *msg = krb5_get_error_message(context, ret);
+       kdc_log(context, config, 0,
+               "KRB5SignedPath check failed for %s (%s) from %s with %s",
+               spn, cpn, from, msg);
+       krb5_free_error_message(context, msg);
        goto out;
     }
 
     /*
-     *
+     * Process request
      */
 
-    client_principal = cp;
+    /* by default the tgt principal matches the client principal */
+    tp = cp;
+    tpn = cpn;
 
     if (client) {
        const PA_DATA *sdata;
        int i = 0;
 
-       sdata = _kdc_find_padata(req, &i, KRB5_PADATA_S4U2SELF);
+       sdata = _kdc_find_padata(req, &i, KRB5_PADATA_FOR_USER);
        if (sdata) {
            krb5_crypto crypto;
            krb5_data datack;
            PA_S4U2Self self;
-           char *selfcpn = NULL;
            const char *str;
 
-           ret = decode_PA_S4U2Self(sdata->padata_value.data, 
+           ret = decode_PA_S4U2Self(sdata->padata_value.data,
                                     sdata->padata_value.length,
                                     &self, NULL);
            if (ret) {
@@ -1483,52 +1901,97 @@ server_lookup:
 
            ret = krb5_crypto_init(context, &tgt->key, 0, &crypto);
            if (ret) {
+               const char *msg = krb5_get_error_message(context, ret);
                free_PA_S4U2Self(&self);
                krb5_data_free(&datack);
-               kdc_log(context, config, 0, "krb5_crypto_init failed: %s",
-                       krb5_get_err_text(context, ret));
+               kdc_log(context, config, 0, "krb5_crypto_init failed: %s", msg);
+               krb5_free_error_message(context, msg);
                goto out;
            }
 
            ret = krb5_verify_checksum(context,
                                       crypto,
                                       KRB5_KU_OTHER_CKSUM,
-                                      datack.data, 
-                                      datack.length, 
+                                      datack.data,
+                                      datack.length,
                                       &self.cksum);
            krb5_data_free(&datack);
            krb5_crypto_destroy(context, crypto);
            if (ret) {
+               const char *msg = krb5_get_error_message(context, ret);
                free_PA_S4U2Self(&self);
-               kdc_log(context, config, 0, 
-                       "krb5_verify_checksum failed for S4U2Self: %s",
-                       krb5_get_err_text(context, ret));
+               kdc_log(context, config, 0,
+                       "krb5_verify_checksum failed for S4U2Self: %s", msg);
+               krb5_free_error_message(context, msg);
                goto out;
            }
 
            ret = _krb5_principalname2krb5_principal(context,
-                                                    &client_principal,
+                                                    &tp,
                                                     self.name,
                                                     self.realm);
            free_PA_S4U2Self(&self);
            if (ret)
                goto out;
 
-           ret = krb5_unparse_name(context, client_principal, &selfcpn);       
+           ret = krb5_unparse_name(context, tp, &tpn);
            if (ret)
                goto out;
 
+           /* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */
+           if(rspac.data) {
+               krb5_pac p = NULL;
+               krb5_data_free(&rspac);
+               ret = _kdc_db_fetch(context, config, tp, HDB_F_GET_CLIENT | flags,
+                                   NULL, &s4u2self_impersonated_clientdb, &s4u2self_impersonated_client);
+               if (ret) {
+                   const char *msg;
+
+                   /*
+                    * If the client belongs to the same realm as our krbtgt, it
+                    * should exist in the local database.
+                    *
+                    */
+
+                   if (ret == HDB_ERR_NOENTRY)
+                       ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+                   msg = krb5_get_error_message(context, ret);
+                   kdc_log(context, config, 1,
+                           "S2U4Self principal to impersonate %s not found in database: %s",
+                           tpn, msg);
+                   krb5_free_error_message(context, msg);
+                   goto out;
+               }
+               ret = _kdc_pac_generate(context, s4u2self_impersonated_client, &p);
+               if (ret) {
+                   kdc_log(context, config, 0, "PAC generation failed for -- %s",
+                           tpn);
+                   goto out;
+               }
+               if (p != NULL) {
+                   ret = _krb5_pac_sign(context, p, ticket->ticket.authtime,
+                                        s4u2self_impersonated_client->entry.principal,
+                                        ekey, &tkey_sign->key,
+                                        &rspac);
+                   krb5_pac_free(context, p);
+                   if (ret) {
+                       kdc_log(context, config, 0, "PAC signing failed for -- %s",
+                               tpn);
+                       goto out;
+                   }
+               }
+           }
+
            /*
             * Check that service doing the impersonating is
             * requesting a ticket to it-self.
             */
-           if (krb5_principal_compare(context, cp, sp) != TRUE) {
+           ret = check_s4u2self(context, config, clientdb, client, sp);
+           if (ret) {
                kdc_log(context, config, 0, "S4U2Self: %s is not allowed "
-                       "to impersonate some other user "
+                       "to impersonate to service "
                        "(tried for user %s to service %s)",
-                       cpn, selfcpn, spn);
-               free(selfcpn);
-               ret = KRB5KDC_ERR_BADOPTION; /* ? */
+                       cpn, tpn, spn);
                goto out;
            }
 
@@ -1544,8 +2007,7 @@ server_lookup:
                str = "";
            }
            kdc_log(context, config, 0, "s4u2self %s impersonating %s to "
-                   "service %s %s", cpn, selfcpn, spn, str);
-           free(selfcpn);
+                   "service %s %s", cpn, tpn, spn, str);
        }
     }
 
@@ -1558,13 +2020,25 @@ server_lookup:
        && b->additional_tickets->len != 0
        && b->kdc_options.enc_tkt_in_skey == 0)
     {
+       int ad_signedpath = 0;
        Key *clientkey;
        Ticket *t;
-       char *str;
+
+       /*
+        * Require that the KDC have issued the service's krbtgt (not
+        * self-issued ticket with kimpersonate(1).
+        */
+       if (!signedpath) {
+           ret = KRB5KDC_ERR_BADOPTION;
+           kdc_log(context, config, 0,
+                   "Constrained delegation done on service ticket %s/%s",
+                   cpn, spn);
+           goto out;
+       }
 
        t = &b->additional_tickets->val[0];
 
-       ret = hdb_enctype2key(context, &client->entry, 
+       ret = hdb_enctype2key(context, &client->entry,
                              t->enc_part.etype, &clientkey);
        if(ret){
            ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */
@@ -1575,90 +2049,127 @@ server_lookup:
        if (ret) {
            kdc_log(context, config, 0,
                    "failed to decrypt ticket for "
-                   "constrained delegation from %s to %s ", spn, cpn);
+                   "constrained delegation from %s to %s ", cpn, spn);
            goto out;
        }
 
-       /* check that ticket is valid */
+       ret = _krb5_principalname2krb5_principal(context,
+                                                &tp,
+                                                adtkt.cname,
+                                                adtkt.crealm);
+       if (ret)
+           goto out;
 
+       ret = krb5_unparse_name(context, tp, &tpn);
+       if (ret)
+           goto out;
+
+       ret = _krb5_principalname2krb5_principal(context,
+                                                &dp,
+                                                t->sname,
+                                                t->realm);
+       if (ret)
+           goto out;
+
+       ret = krb5_unparse_name(context, dp, &dpn);
+       if (ret)
+           goto out;
+
+       /* check that ticket is valid */
        if (adtkt.flags.forwardable == 0) {
            kdc_log(context, config, 0,
                    "Missing forwardable flag on ticket for "
-                   "constrained delegation from %s to %s ", spn, cpn);
-           ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */
+                   "constrained delegation from %s (%s) as %s to %s ",
+                   cpn, dpn, tpn, spn);
+           ret = KRB5KDC_ERR_BADOPTION;
            goto out;
        }
 
-       ret = check_constrained_delegation(context, config, client, sp);
+       ret = check_constrained_delegation(context, config, clientdb,
+                                          client, server, sp);
        if (ret) {
            kdc_log(context, config, 0,
-                   "constrained delegation from %s to %s not allowed", 
-                   spn, cpn);
+                   "constrained delegation from %s (%s) as %s to %s not allowed",
+                   cpn, dpn, tpn, spn);
            goto out;
        }
 
-       ret = _krb5_principalname2krb5_principal(context,
-                                                &client_principal,
-                                                adtkt.cname,
-                                                adtkt.crealm);
-       if (ret)
+       ret = verify_flags(context, config, &adtkt, tpn);
+       if (ret) {
            goto out;
+       }
 
-       ret = krb5_unparse_name(context, client_principal, &str);
-       if (ret)
-           goto out;
+       krb5_data_free(&rspac);
 
-       ret = verify_flags(context, config, &adtkt, str);
+       /*
+        * generate the PAC for the user.
+        *
+        * TODO: pass in t->sname and t->realm and build
+        * a S4U_DELEGATION_INFO blob to the PAC.
+        */
+       ret = check_PAC(context, config, tp, dp,
+                       client, server, krbtgt,
+                       &clientkey->key, &tkey_check->key,
+                       ekey, &tkey_sign->key,
+                       &adtkt, &rspac, &ad_signedpath);
        if (ret) {
-           free(str);
+           const char *msg = krb5_get_error_message(context, ret);
+           kdc_log(context, config, 0,
+                   "Verify delegated PAC failed to %s for client"
+                   "%s (%s) as %s from %s with %s",
+                   spn, cpn, dpn, tpn, from, msg);
+           krb5_free_error_message(context, msg);
            goto out;
        }
 
        /*
-        * Check KRB5SignedPath in authorization data and add new entry to
-        * make sure servers can't fake a ticket to us.
+        * Check that the KDC issued the user's ticket.
         */
-
        ret = check_KRB5SignedPath(context,
                                   config,
                                   krbtgt,
+                                  cp,
                                   &adtkt,
-                                  &spp,
-                                  1);
+                                  NULL,
+                                  &ad_signedpath);
        if (ret) {
+           const char *msg = krb5_get_error_message(context, ret);
            kdc_log(context, config, 0,
                    "KRB5SignedPath check from service %s failed "
-                   "for delegation to %s for client %s "
+                   "for delegation to %s for client %s (%s)"
                    "from %s failed with %s",
-                   spn, str, cpn, from, krb5_get_err_text(context, ret));
-           free(str);
+                   spn, tpn, dpn, cpn, from, msg);
+           krb5_free_error_message(context, msg);
            goto out;
        }
 
-       kdc_log(context, config, 0, "constrained delegation for %s "
-               "from %s to %s", str, cpn, spn);
-       free(str);
+       if (!ad_signedpath) {
+           ret = KRB5KDC_ERR_BADOPTION;
+           kdc_log(context, config, 0,
+                   "Ticket not signed with PAC nor SignedPath service %s failed "
+                   "for delegation to %s for client %s (%s)"
+                   "from %s",
+                   spn, tpn, dpn, cpn, from);
+           goto out;
+       }
 
-       /* 
-        * Also require that the KDC have issue the service's krbtgt
-        * used to do the request. 
-        */
-       require_signedpath = 1;
+       kdc_log(context, config, 0, "constrained delegation for %s "
+               "from %s (%s) to %s", tpn, cpn, dpn, spn);
     }
 
     /*
      * Check flags
      */
 
-    ret = _kdc_check_flags(context, config, 
-                          client, cpn,
-                          server, spn,
-                          FALSE);
+    ret = kdc_check_flags(context, config,
+                         client, cpn,
+                         server, spn,
+                         FALSE);
     if(ret)
        goto out;
 
-    if((b->kdc_options.validate || b->kdc_options.renew) && 
-       !krb5_principal_compare(context, 
+    if((b->kdc_options.validate || b->kdc_options.renew) &&
+       !krb5_principal_compare(context,
                               krbtgt->entry.principal,
                               server->entry.principal)){
        kdc_log(context, config, 0, "Inconsistent request.");
@@ -1672,123 +2183,99 @@ server_lookup:
        kdc_log(context, config, 0, "Request from wrong address");
        goto out;
     }
-       
+
     /*
-     * Select enctype, return key and kvno.
+     * If this is an referral, add server referral data to the
+     * auth_data reply .
      */
+    if (ref_realm) {
+       PA_DATA pa;
+       krb5_crypto crypto;
 
-    {
-       krb5_enctype etype;
+       kdc_log(context, config, 0,
+               "Adding server referral to %s", ref_realm);
 
-       if(b->kdc_options.enc_tkt_in_skey) {
-           int i;
-           ekey = &adtkt.key;
-           for(i = 0; i < b->etype.len; i++)
-               if (b->etype.val[i] == adtkt.key.keytype)
-                   break;
-           if(i == b->etype.len) {
-               krb5_clear_error_string(context);
-               return KRB5KDC_ERR_ETYPE_NOSUPP;
-           }
-           etype = b->etype.val[i];
-           kvno = 0;
-       } else {
-           Key *skey;
-           
-           ret = _kdc_find_etype(context, server, b->etype.val, b->etype.len,
-                                 &skey, &etype);
-           if(ret) {
-               kdc_log(context, config, 0, 
-                       "Server (%s) has no support for etypes", spp);
-               return ret;
-           }
-           ekey = &skey->key;
-           kvno = server->entry.kvno;
-       }
-       
-       ret = krb5_generate_random_keyblock(context, etype, &sessionkey);
+       ret = krb5_crypto_init(context, &sessionkey, 0, &crypto);
        if (ret)
            goto out;
-    }
-
-    /* check PAC if not cross realm and if there is one */
-    if (!cross_realm) {
-       Key *tkey;
 
-       ret = hdb_enctype2key(context, &krbtgt->entry, 
-                             krbtgt_etype, &tkey);
-       if(ret) {
+       ret = build_server_referral(context, config, crypto, ref_realm,
+                                   NULL, s, &pa.padata_value);
+       krb5_crypto_destroy(context, crypto);
+       if (ret) {
            kdc_log(context, config, 0,
-                   "Failed to find key for krbtgt PAC check");
+                   "Failed building server referral");
            goto out;
        }
+       pa.padata_type = KRB5_PADATA_SERVER_REFERRAL;
 
-       ret = check_PAC(context, config, client_principal, 
-                       client, server, ekey, &tkey->key,
-                       tgt, &rspac, &require_signedpath);
+       ret = add_METHOD_DATA(&enc_pa_data, &pa);
+       krb5_data_free(&pa.padata_value);
        if (ret) {
            kdc_log(context, config, 0,
-                   "Verify PAC failed for %s (%s) from %s with %s",
-                   spn, cpn, from, krb5_get_err_text(context, ret));
+                   "Add server referral METHOD-DATA failed");
            goto out;
        }
     }
 
-    /* also check the krbtgt for signature */
-    ret = check_KRB5SignedPath(context,
-                              config,
-                              krbtgt,
-                              tgt,
-                              &spp,
-                              require_signedpath);
-    if (ret) {
-       kdc_log(context, config, 0,
-               "KRB5SignedPath check failed for %s (%s) from %s with %s",
-               spn, cpn, from, krb5_get_err_text(context, ret));
-       goto out;
-    }
-
     /*
      *
      */
 
     ret = tgs_make_reply(context,
-                        config, 
-                        b, 
-                        client_principal,
-                        tgt, 
+                        config,
+                        b,
+                        tp,
+                        tgt,
+                        replykey,
+                        rk_is_subkey,
                         ekey,
                         &sessionkey,
                         kvno,
-                        auth_data,
-                        server, 
+                        *auth_data,
+                        server,
+                        server->entry.principal,
                         spn,
-                        client, 
-                        cp, 
-                        krbtgt
+                        client,
+                        cp,
+                        krbtgt_out,
                         krbtgt_etype,
                         spp,
                         &rspac,
+                        &enc_pa_data,
                         e_text,
                         reply);
-       
+
 out:
+    if (tpn != cpn)
+           free(tpn);
     free(spn);
     free(cpn);
-           
+    if (dpn)
+       free(dpn);
+
     krb5_data_free(&rspac);
     krb5_free_keyblock_contents(context, &sessionkey);
+    if(krbtgt_out)
+       _kdc_free_ent(context, krbtgt_out);
     if(server)
        _kdc_free_ent(context, server);
     if(client)
        _kdc_free_ent(context, client);
+    if(s4u2self_impersonated_client)
+       _kdc_free_ent(context, s4u2self_impersonated_client);
 
-    if (client_principal && client_principal != cp)
-       krb5_free_principal(context, client_principal);
+    if (tp && tp != cp)
+       krb5_free_principal(context, tp);
     if (cp)
        krb5_free_principal(context, cp);
+    if (dp)
+       krb5_free_principal(context, dp);
     if (sp)
        krb5_free_principal(context, sp);
+    if (ref_realm)
+       free(ref_realm);
+    free_METHOD_DATA(&enc_pa_data);
 
     free_EncTicketPart(&adtkt);
 
@@ -1800,9 +2287,9 @@ out:
  */
 
 krb5_error_code
-_kdc_tgs_rep(krb5_context context, 
+_kdc_tgs_rep(krb5_context context,
             krb5_kdc_configuration *config,
-            KDC_REQ *req, 
+            KDC_REQ *req,
             krb5_data *data,
             const char *from,
             struct sockaddr *from_addr,
@@ -1818,6 +2305,8 @@ _kdc_tgs_rep(krb5_context context,
     const char *e_text = NULL;
     krb5_enctype krbtgt_etype = ETYPE_NULL;
 
+    krb5_keyblock *replykey = NULL;
+    int rk_is_subkey = 0;
     time_t *csec = NULL;
     int *cusec = NULL;
 
@@ -1827,17 +2316,17 @@ _kdc_tgs_rep(krb5_context context,
                "TGS-REQ from %s without PA-DATA", from);
        goto out;
     }
-    
+
     tgs_req = _kdc_find_padata(req, &i, KRB5_PADATA_TGS_REQ);
 
     if(tgs_req == NULL){
        ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
-       
-       kdc_log(context, config, 0, 
+
+       kdc_log(context, config, 0,
                "TGS-REQ from %s without PA-TGS-REQ", from);
        goto out;
     }
-    ret = tgs_parse_request(context, config, 
+    ret = tgs_parse_request(context, config,
                            &req->req_body, tgs_req,
                            &krbtgt,
                            &krbtgt_etype,
@@ -1845,9 +2334,15 @@ _kdc_tgs_rep(krb5_context context,
                            &e_text,
                            from, from_addr,
                            &csec, &cusec,
-                           &auth_data);
+                           &auth_data,
+                           &replykey,
+                           &rk_is_subkey);
+    if (ret == HDB_ERR_NOT_FOUND_HERE) {
+       /* kdc_log() is called in tgs_parse_request() */
+       goto out;
+    }
     if (ret) {
-       kdc_log(context, config, 0, 
+       kdc_log(context, config, 0,
                "Failed parsing TGS-REQ from %s", from);
        goto out;
     }
@@ -1858,15 +2353,16 @@ _kdc_tgs_rep(krb5_context context,
                          &req->req_body,
                          krbtgt,
                          krbtgt_etype,
+                         replykey,
+                         rk_is_subkey,
                          ticket,
                          data,
                          from,
                          &e_text,
-                         auth_data,
-                         from_addr,
-                         datagram_reply);
+                         &auth_data,
+                         from_addr);
     if (ret) {
-       kdc_log(context, config, 0, 
+       kdc_log(context, config, 0,
                "Failed building TGS-REP to %s", from);
        goto out;
     }
@@ -1879,7 +2375,9 @@ _kdc_tgs_rep(krb5_context context,
     }
 
 out:
-    if(ret && data->data == NULL){
+    if (replykey)
+       krb5_free_keyblock(context, replykey);
+    if(ret && ret != HDB_ERR_NOT_FOUND_HERE && data->data == NULL){
        krb5_mk_error(context,
                      ret,
                      NULL,
@@ -1889,6 +2387,7 @@ out:
                      csec,
                      cusec,
                      data);
+       ret = 0;
     }
     free(csec);
     free(cusec);
@@ -1902,5 +2401,5 @@ out:
        free(auth_data);
     }
 
-    return 0;
+    return ret;
 }