lib/krb5_wrap: use krb5_const_principal in smb_krb5_get_pw_salt().
[kai/samba-autobuild/.git] / lib / krb5_wrap / krb5_samba.c
index 16327f1f92b5ecdba366ec286568890a425cc7d0..6d71f2223dbe0fbd50423c900768b3aa082eaea4 100644 (file)
 */
 
 #include "includes.h"
+#include "system/filesys.h"
 #include "krb5_samba.h"
-#include "librpc/gen_ndr/krb5pac.h"
 #include "lib/util/asn1.h"
 
+#ifdef HAVE_COM_ERR_H
+#include <com_err.h>
+#endif /* HAVE_COM_ERR_H */
+
 #ifndef KRB5_AUTHDATA_WIN2K_PAC
 #define KRB5_AUTHDATA_WIN2K_PAC 128
 #endif
@@ -134,51 +138,116 @@ bool setup_kaddr( krb5_address *pkaddr, struct sockaddr_storage *paddr)
 #error UNKNOWN_ADDRTYPE
 #endif
 
-#if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_C_STRING_TO_KEY)
-/* MIT */
-int create_kerberos_key_from_string_direct(krb5_context context,
-                                                 krb5_principal host_princ,
-                                                 krb5_data *password,
-                                                 krb5_keyblock *key,
-                                                 krb5_enctype enctype)
+/**
+* @brief Create a keyblock based on input parameters
+*
+* @param context       The krb5_context
+* @param host_princ    The krb5_principal to use
+* @param salt          The optional salt, if ommitted, salt is calculated with
+*                      the provided principal.
+* @param password      The krb5_data containing the password
+* @param enctype       The krb5_enctype to use for the keyblock generation
+* @param key           The returned krb5_keyblock, caller needs to free with
+*                      krb5_free_keyblock().
+*
+* @return krb5_error_code
+*/
+int smb_krb5_create_key_from_string(krb5_context context,
+                                   krb5_const_principal host_princ,
+                                   krb5_data *salt,
+                                   krb5_data *password,
+                                   krb5_enctype enctype,
+                                   krb5_keyblock *key)
 {
        int ret = 0;
-       krb5_data salt;
 
-       ret = krb5_principal2salt(context, host_princ, &salt);
-       if (ret) {
-               DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret)));
-               return ret;
+       if (host_princ == NULL && salt == NULL) {
+               return -1;
        }
-       ret = krb5_c_string_to_key(context, enctype, password, &salt, key);
-       SAFE_FREE(salt.data);
 
-       return ret;
+#if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_C_STRING_TO_KEY)
+{/* MIT */
+       krb5_data _salt;
+
+       if (salt == NULL) {
+               ret = krb5_principal2salt(context, host_princ, &_salt);
+               if (ret) {
+                       DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret)));
+                       return ret;
+               }
+       } else {
+               _salt = *salt;
+       }
+       ret = krb5_c_string_to_key(context, enctype, password, &_salt, key);
+       if (salt == NULL) {
+               SAFE_FREE(_salt.data);
+       }
 }
 #elif defined(HAVE_KRB5_GET_PW_SALT) && defined(HAVE_KRB5_STRING_TO_KEY_SALT)
+{/* Heimdal */
+       krb5_salt _salt;
+
+       if (salt == NULL) {
+               ret = krb5_get_pw_salt(context, host_princ, &_salt);
+               if (ret) {
+                       DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret)));
+                       return ret;
+               }
+       } else {
+               _salt.saltvalue = *salt;
+               _salt.salttype = KRB5_PW_SALT;
+       }
+
+       ret = krb5_string_to_key_salt(context, enctype, (const char *)password->data, _salt, key);
+       if (salt == NULL) {
+               krb5_free_salt(context, _salt);
+       }
+}
+#else
+#error UNKNOWN_CREATE_KEY_FUNCTIONS
+#endif
+       return ret;
+}
+
+/**
+* @brief Create a salt for a given principal
+*
+* @param context       The initialized krb5_context
+* @param host_princ    The krb5_principal to create the salt for
+* @param psalt         A pointer to a krb5_data struct
+*
+* caller has to free the contents of psalt with kerberos_free_data_contents
+* when function has succeeded
+*
+* @return krb5_error_code, returns 0 on success, error code otherwise
+*/
+
+int smb_krb5_get_pw_salt(krb5_context context,
+                        krb5_const_principal host_princ,
+                        krb5_data *psalt)
+#if defined(HAVE_KRB5_GET_PW_SALT)
 /* Heimdal */
-int create_kerberos_key_from_string_direct(krb5_context context,
-                                                 krb5_principal host_princ,
-                                                 krb5_data *password,
-                                                 krb5_keyblock *key,
-                                                 krb5_enctype enctype)
 {
        int ret;
        krb5_salt salt;
 
        ret = krb5_get_pw_salt(context, host_princ, &salt);
        if (ret) {
-               DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret)));
                return ret;
        }
 
-       ret = krb5_string_to_key_salt(context, enctype, (const char *)password->data, salt, key);
-       krb5_free_salt(context, salt);
+       psalt->data = salt.saltvalue.data;
+       psalt->length = salt.saltvalue.length;
 
        return ret;
 }
+#elif defined(HAVE_KRB5_PRINCIPAL2SALT)
+/* MIT */
+{
+       return krb5_principal2salt(context, host_princ, psalt);
+}
 #else
-#error UNKNOWN_CREATE_KEY_FUNCTIONS
+#error UNKNOWN_SALT_FUNCTIONS
 #endif
 
 #if defined(HAVE_KRB5_GET_PERMITTED_ENCTYPES)
@@ -227,23 +296,22 @@ bool unwrap_edata_ntstatus(TALLOC_CTX *mem_ctx,
                return false;
        }
 
-       asn1_load(data, *edata);
-       asn1_start_tag(data, ASN1_SEQUENCE(0));
-       asn1_start_tag(data, ASN1_CONTEXT(1));
-       asn1_read_Integer(data, &edata_type);
+       if (!asn1_load(data, *edata)) goto err;
+       if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) goto err;
+       if (!asn1_start_tag(data, ASN1_CONTEXT(1))) goto err;
+       if (!asn1_read_Integer(data, &edata_type)) goto err;
 
        if (edata_type != KRB5_PADATA_PW_SALT) {
                DEBUG(0,("edata is not of required type %d but of type %d\n",
                        KRB5_PADATA_PW_SALT, edata_type));
-               asn1_free(data);
-               return false;
+               goto err;
        }
 
-       asn1_start_tag(data, ASN1_CONTEXT(2));
-       asn1_read_OctetString(data, talloc_tos(), &edata_contents);
-       asn1_end_tag(data);
-       asn1_end_tag(data);
-       asn1_end_tag(data);
+       if (!asn1_start_tag(data, ASN1_CONTEXT(2))) goto err;
+       if (!asn1_read_OctetString(data, talloc_tos(), &edata_contents)) goto err;
+       if (!asn1_end_tag(data)) goto err;
+       if (!asn1_end_tag(data)) goto err;
+       if (!asn1_end_tag(data)) goto err;
        asn1_free(data);
 
        *edata_out = data_blob_talloc(mem_ctx, edata_contents.data, edata_contents.length);
@@ -251,6 +319,11 @@ bool unwrap_edata_ntstatus(TALLOC_CTX *mem_ctx,
        data_blob_free(&edata_contents);
 
        return true;
+
+  err:
+
+       asn1_free(data);
+       return false;
 }
 
 
@@ -669,6 +742,39 @@ void kerberos_free_data_contents(krb5_context context, krb5_data *pdata)
 #endif
 }
 
+/*
+ * @brief copy a buffer into a krb5_data struct
+ *
+ * @param[in] p                        The krb5_data
+ * @param[in] data             The data to copy
+ * @param[in] length           The length of the data to copy
+ * @return krb5_error_code
+ *
+ * Caller has to free krb5_data with kerberos_free_data_contents().
+ */
+
+krb5_error_code krb5_copy_data_contents(krb5_data *p,
+                                       const void *data,
+                                       size_t len)
+{
+#if defined(HAVE_KRB5_DATA_COPY)
+       return krb5_data_copy(p, data, len);
+#else
+       if (len) {
+               p->data = malloc(len);
+               if (p->data == NULL) {
+                       return ENOMEM;
+               }
+               memmove(p->data, data, len);
+       } else {
+               p->data = NULL;
+       }
+       p->length = len;
+       p->magic = KV5M_DATA;
+       return 0;
+#endif
+}
+
 /*
   get a kerberos5 ticket for the given service
 */
@@ -686,6 +792,12 @@ int cli_krb5_get_ticket(TALLOC_CTX *mem_ctx,
        krb5_ccache ccdef = NULL;
        krb5_auth_context auth_context = NULL;
        krb5_enctype enc_types[] = {
+#ifdef HAVE_ENCTYPE_AES256_CTS_HMAC_SHA1_96
+               ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+#endif
+#ifdef HAVE_ENCTYPE_AES128_CTS_HMAC_SHA1_96
+               ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+#endif
                ENCTYPE_ARCFOUR_HMAC,
                ENCTYPE_DES_CBC_MD5,
                ENCTYPE_DES_CBC_CRC,
@@ -800,6 +912,43 @@ done:
 }
 #endif
 
+/*
+ * @brief Get talloced string component of a principal
+ *
+ * @param[in] mem_ctx          The TALLOC_CTX
+ * @param[in] context          The krb5_context
+ * @param[in] principal                The principal
+ * @param[in] component                The component
+ * @return string component
+ *
+ * Caller must talloc_free if the return value is not NULL.
+ *
+ */
+
+/* caller has to free returned string with free() */
+char *smb_krb5_principal_get_comp_string(TALLOC_CTX *mem_ctx,
+                                        krb5_context context,
+                                        krb5_const_principal principal,
+                                        unsigned int component)
+{
+#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING)
+       return talloc_strdup(mem_ctx, krb5_principal_get_comp_string(context, principal, component));
+#else
+       krb5_data *data;
+
+       if (component >= krb5_princ_size(context, principal)) {
+               return NULL;
+       }
+
+       data = krb5_princ_component(context, principal, component);
+       if (data == NULL) {
+               return NULL;
+       }
+
+       return talloc_strndup(mem_ctx, data->data, data->length);
+#endif
+}
+
 /* Prototypes */
 
  krb5_error_code smb_krb5_renew_ticket(const char *ccache_string,      /* FILE:/tmp/krb5cc_0 */
@@ -811,7 +960,7 @@ done:
        krb5_context context = NULL;
        krb5_ccache ccache = NULL;
        krb5_principal client = NULL;
-       krb5_creds creds, creds_in, *creds_out = NULL;
+       krb5_creds creds, creds_in;
 
        ZERO_STRUCT(creds);
        ZERO_STRUCT(creds_in);
@@ -871,12 +1020,7 @@ done:
 
 done:
        krb5_free_cred_contents(context, &creds_in);
-
-       if (creds_out) {
-               krb5_free_creds(context, creds_out);
-       } else {
-               krb5_free_cred_contents(context, &creds);
-       }
+       krb5_free_cred_contents(context, &creds);
 
        if (client) {
                krb5_free_principal(context, client);
@@ -1333,6 +1477,15 @@ static krb5_error_code smb_krb5_get_credentials_for_user_opt(krb5_context contex
 #endif /* HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE */
 
 #ifdef HAVE_KRB5_GET_CREDENTIALS_FOR_USER
+
+#if !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER
+krb5_error_code KRB5_CALLCONV
+krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
+                              krb5_ccache ccache, krb5_creds *in_creds,
+                              krb5_data *subject_cert,
+                              krb5_creds **out_creds);
+#endif /* !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER */
+
 static krb5_error_code smb_krb5_get_credentials_for_user(krb5_context context,
                                                         krb5_ccache ccache,
                                                         krb5_principal me,
@@ -1343,14 +1496,6 @@ static krb5_error_code smb_krb5_get_credentials_for_user(krb5_context context,
        krb5_error_code ret;
        krb5_creds in_creds;
 
-#if !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER
-krb5_error_code KRB5_CALLCONV
-krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
-                              krb5_ccache ccache, krb5_creds *in_creds,
-                              krb5_data *subject_cert,
-                              krb5_creds **out_creds);
-#endif /* !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER */
-
        ZERO_STRUCT(in_creds);
 
        if (impersonate_princ) {
@@ -1400,7 +1545,9 @@ krb5_error_code smb_krb5_get_credentials(krb5_context context,
        krb5_error_code ret;
        krb5_creds *creds = NULL;
 
-       *out_creds = NULL;
+       if (out_creds != NULL) {
+               *out_creds = NULL;
+       }
 
        if (impersonate_princ) {
 #ifdef HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE /* Heimdal */
@@ -1437,124 +1584,717 @@ krb5_error_code smb_krb5_get_credentials(krb5_context context,
        return ret;
 }
 
-/*
- * smb_krb5_get_creds
- *
- * @brief Get krb5 credentials for a server
- *
- * @param[in] server_s         The string name of the service
- * @param[in] time_offset      The offset to the KDCs time in seconds (optional)
- * @param[in] cc               The krb5 credential cache string name (optional)
- * @param[in] impersonate_princ_s The string principal name to impersonate (optional)
- * @param[out] creds_p         The returned krb5_creds structure
- * @return krb5_error_code
- *
- */
-krb5_error_code smb_krb5_get_creds(const char *server_s,
-                                  time_t time_offset,
-                                  const char *cc,
-                                  const char *impersonate_princ_s,
-                                  krb5_creds **creds_p)
+krb5_error_code smb_krb5_keyblock_init_contents(krb5_context context,
+                                               krb5_enctype enctype,
+                                               const void *data,
+                                               size_t length,
+                                               krb5_keyblock *key)
 {
-       krb5_error_code ret;
-       krb5_context context = NULL;
-       krb5_principal me = NULL;
-       krb5_principal server = NULL;
-       krb5_principal impersonate_princ = NULL;
-       krb5_creds *creds = NULL;
-       krb5_ccache ccache = NULL;
+#if defined(HAVE_KRB5_KEYBLOCK_INIT)
+       return krb5_keyblock_init(context, enctype, data, length, key);
+#else
+       memset(key, 0, sizeof(krb5_keyblock));
+       KRB5_KEY_DATA(key) = SMB_MALLOC(length);
+       if (NULL == KRB5_KEY_DATA(key)) {
+               return ENOMEM;
+       }
+       memcpy(KRB5_KEY_DATA(key), data, length);
+       KRB5_KEY_LENGTH(key) = length;
+       KRB5_KEY_TYPE(key) = enctype;
+       return 0;
+#endif
+}
 
-       *creds_p = NULL;
+/*
+  simulate a kinit, putting the tgt in the given credentials cache.
+  Orignally by remus@snapserver.com
 
-       initialize_krb5_error_table();
-       ret = krb5_init_context(&context);
-       if (ret) {
+  This version is built to use a keyblock, rather than needing the
+  original password.
+
+  The impersonate_principal is the principal if NULL, or the principal
+  to impersonate
+
+  The target_service defaults to the krbtgt if NULL, but could be
+   kpasswd/realm or the local service (if we are doing s4u2self)
+*/
+krb5_error_code kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc,
+                                          krb5_principal principal,
+                                          krb5_keyblock *keyblock,
+                                          const char *target_service,
+                                          krb5_get_init_creds_opt *krb_options,
+                                          time_t *expire_time,
+                                          time_t *kdc_time)
+{
+       krb5_error_code code = 0;
+       krb5_creds my_creds;
+
+#if defined(HAVE_KRB5_GET_INIT_CREDS_KEYBLOCK)
+       code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal,
+                                           keyblock, 0, target_service,
+                                           krb_options);
+#elif defined(HAVE_KRB5_GET_INIT_CREDS_KEYTAB)
+{
+#define SMB_CREDS_KEYTAB "MEMORY:tmp_smb_creds_XXXXXX"
+       char tmp_name[sizeof(SMB_CREDS_KEYTAB)];
+       krb5_keytab_entry entry;
+       krb5_keytab keytab;
+       mode_t mask;
+
+       memset(&entry, 0, sizeof(entry));
+       entry.principal = principal;
+       *(KRB5_KT_KEY(&entry)) = *keyblock;
+
+       memcpy(tmp_name, SMB_CREDS_KEYTAB, sizeof(SMB_CREDS_KEYTAB));
+       mask = umask(S_IRWXO | S_IRWXG);
+       mktemp(tmp_name);
+       umask(mask);
+       if (tmp_name[0] == 0) {
+               return KRB5_KT_BADNAME;
+       }
+       code = krb5_kt_resolve(ctx, tmp_name, &keytab);
+       if (code) {
+               return code;
+       }
+
+       code = krb5_kt_add_entry(ctx, keytab, &entry);
+       if (code) {
+               (void)krb5_kt_close(ctx, keytab);
                goto done;
        }
 
-       if (time_offset != 0) {
-               krb5_set_real_time(context, time(NULL) + time_offset, 0);
+       code = krb5_get_init_creds_keytab(ctx, &my_creds, principal,
+                                         keytab, 0, target_service,
+                                         krb_options);
+       (void)krb5_kt_close(ctx, keytab);
+}
+#else
+#error krb5_get_init_creds_keyblock not available!
+#endif
+       if (code) {
+               return code;
        }
 
-       ret = krb5_cc_resolve(context, cc ? cc :
-               krb5_cc_default_name(context), &ccache);
-       if (ret) {
+       code = krb5_cc_initialize(ctx, cc, principal);
+       if (code) {
                goto done;
        }
 
-       ret = krb5_cc_get_principal(context, ccache, &me);
-       if (ret) {
+       code = krb5_cc_store_cred(ctx, cc, &my_creds);
+       if (code) {
                goto done;
        }
 
-       ret = smb_krb5_parse_name(context, server_s, &server);
-       if (ret) {
-               goto done;
+       if (expire_time) {
+               *expire_time = (time_t) my_creds.times.endtime;
        }
 
-       if (impersonate_princ_s) {
-               ret = smb_krb5_parse_name(context, impersonate_princ_s,
-                                         &impersonate_princ);
-               if (ret) {
-                       goto done;
-               }
+       if (kdc_time) {
+               *kdc_time = (time_t) my_creds.times.starttime;
        }
 
-       ret = smb_krb5_get_credentials(context, ccache,
-                                      me, server, impersonate_princ,
-                                      &creds);
-       if (ret) {
+       code = 0;
+done:
+       krb5_free_cred_contents(ctx, &my_creds);
+       return code;
+}
+
+krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc,
+                                          krb5_principal principal,
+                                          const char *password,
+                                          const char *target_service,
+                                          krb5_get_init_creds_opt *krb_options,
+                                          time_t *expire_time,
+                                          time_t *kdc_time)
+{
+       krb5_error_code code = 0;
+       krb5_creds my_creds;
+
+       code = krb5_get_init_creds_password(ctx, &my_creds, principal,
+                                           password, NULL, NULL, 0,
+                                           target_service, krb_options);
+       if (code) {
+               return code;
+       }
+
+       code = krb5_cc_initialize(ctx, cc, principal);
+       if (code) {
                goto done;
        }
 
-       ret = krb5_cc_store_cred(context, ccache, creds);
-       if (ret) {
+       code = krb5_cc_store_cred(ctx, cc, &my_creds);
+       if (code) {
                goto done;
        }
 
-       if (creds_p) {
-               *creds_p = creds;
+       if (expire_time) {
+               *expire_time = (time_t) my_creds.times.endtime;
        }
 
-       DEBUG(1,("smb_krb5_get_creds: got ticket for %s\n",
-               server_s));
+       if (kdc_time) {
+               *kdc_time = (time_t) my_creds.times.starttime;
+       }
 
-       if (impersonate_princ_s) {
-               char *client = NULL;
+       code = 0;
+done:
+       krb5_free_cred_contents(ctx, &my_creds);
+       return code;
+}
 
-               ret = smb_krb5_unparse_name(talloc_tos(), context, creds->client, &client);
-               if (ret) {
-                       goto done;
+#ifdef SAMBA4_USES_HEIMDAL
+/*
+  simulate a kinit, putting the tgt in the given credentials cache.
+  Orignally by remus@snapserver.com
+
+  The impersonate_principal is the principal
+
+  The self_service, should be the local service (for S4U2Self if
+  impersonate_principal is given).
+
+  The target_service defaults to the krbtgt if NULL, but could be
+  kpasswd/realm or a remote service (for S4U2Proxy)
+
+*/
+krb5_error_code kerberos_kinit_s4u2_cc(krb5_context ctx,
+                                       krb5_ccache store_cc,
+                                       krb5_principal init_principal,
+                                       const char *init_password,
+                                       krb5_principal impersonate_principal,
+                                       const char *self_service,
+                                       const char *target_service,
+                                       krb5_get_init_creds_opt *krb_options,
+                                       time_t *expire_time,
+                                       time_t *kdc_time)
+{
+       krb5_error_code code = 0;
+       krb5_get_creds_opt options;
+       krb5_principal store_principal;
+       krb5_creds store_creds;
+       krb5_creds *s4u2self_creds;
+       Ticket s4u2self_ticket;
+       size_t s4u2self_ticketlen;
+       krb5_creds *s4u2proxy_creds;
+       krb5_principal self_princ;
+       bool s4u2proxy;
+       krb5_principal target_princ;
+       krb5_ccache tmp_cc;
+       const char *self_realm;
+       krb5_principal blacklist_principal = NULL;
+       krb5_principal whitelist_principal = NULL;
+
+       code = krb5_get_init_creds_password(ctx, &store_creds,
+                                           init_principal,
+                                           init_password,
+                                           NULL, NULL,
+                                           0,
+                                           NULL,
+                                           krb_options);
+       if (code != 0) {
+               return code;
+       }
+
+       store_principal = init_principal;
+
+       /*
+        * We are trying S4U2Self now:
+        *
+        * As we do not want to expose our TGT in the
+        * krb5_ccache, which is also holds the impersonated creds.
+        *
+        * Some low level krb5/gssapi function might use the TGT
+        * identity and let the client act as our machine account.
+        *
+        * We need to avoid that and use a temporary krb5_ccache
+        * in order to pass our TGT to the krb5_get_creds() function.
+        */
+       code = krb5_cc_new_unique(ctx, NULL, NULL, &tmp_cc);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       code = krb5_cc_initialize(ctx, tmp_cc, store_creds.client);
+       if (code != 0) {
+               krb5_cc_destroy(ctx, tmp_cc);
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       code = krb5_cc_store_cred(ctx, tmp_cc, &store_creds);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       /*
+        * we need to remember the client principal of our
+        * TGT and make sure the KDC does not return this
+        * in the impersonated tickets. This can happen
+        * if the KDC does not support S4U2Self and S4U2Proxy.
+        */
+       blacklist_principal = store_creds.client;
+       store_creds.client = NULL;
+       krb5_free_cred_contents(ctx, &store_creds);
+
+       /*
+        * Check if we also need S4U2Proxy or if S4U2Self is
+        * enough in order to get a ticket for the target.
+        */
+       if (target_service == NULL) {
+               s4u2proxy = false;
+       } else if (strcmp(target_service, self_service) == 0) {
+               s4u2proxy = false;
+       } else {
+               s4u2proxy = true;
+       }
+
+       /*
+        * For S4U2Self we need our own service principal,
+        * which belongs to our own realm (available on
+        * our client principal).
+        */
+       self_realm = krb5_principal_get_realm(ctx, init_principal);
+
+       code = krb5_parse_name(ctx, self_service, &self_princ);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_principal_set_realm(ctx, self_princ, self_realm);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds_opt_alloc(ctx, &options);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       if (s4u2proxy) {
+               /*
+                * If we want S4U2Proxy, we need the forwardable flag
+                * on the S4U2Self ticket.
+                */
+               krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
+       }
+
+       code = krb5_get_creds_opt_set_impersonate(ctx, options,
+                                                 impersonate_principal);
+       if (code != 0) {
+               krb5_get_creds_opt_free(ctx, options);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds(ctx, options, tmp_cc,
+                             self_princ, &s4u2self_creds);
+       krb5_get_creds_opt_free(ctx, options);
+       krb5_free_principal(ctx, self_princ);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       if (!s4u2proxy) {
+               krb5_cc_destroy(ctx, tmp_cc);
+
+               /*
+                * Now make sure we store the impersonated principal
+                * and creds instead of the TGT related stuff
+                * in the krb5_ccache of the caller.
+                */
+               code = krb5_copy_creds_contents(ctx, s4u2self_creds,
+                                               &store_creds);
+               krb5_free_creds(ctx, s4u2self_creds);
+               if (code != 0) {
+                       return code;
                }
-               DEBUGADD(1,("smb_krb5_get_creds: using S4U2SELF impersonation as %s\n",
-                       client));
-               TALLOC_FREE(client);
+
+               /*
+                * It's important to store the principal the KDC
+                * returned, as otherwise the caller would not find
+                * the S4U2Self ticket in the krb5_ccache lookup.
+                */
+               store_principal = store_creds.client;
+               goto store;
        }
 
- done:
-       if (!context) {
-               return ret;
+       /*
+        * We are trying S4U2Proxy:
+        *
+        * We need the ticket from the S4U2Self step
+        * and our TGT in order to get the delegated ticket.
+        */
+       code = decode_Ticket((const uint8_t *)s4u2self_creds->ticket.data,
+                            s4u2self_creds->ticket.length,
+                            &s4u2self_ticket,
+                            &s4u2self_ticketlen);
+       if (code != 0) {
+               krb5_free_creds(ctx, s4u2self_creds);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
        }
 
-       if (creds && ret) {
-               krb5_free_creds(context, creds);
+       /*
+        * we need to remember the client principal of the
+        * S4U2Self stage and as it needs to match the one we
+        * will get for the S4U2Proxy stage. We need this
+        * in order to detect KDCs which does not support S4U2Proxy.
+        */
+       whitelist_principal = s4u2self_creds->client;
+       s4u2self_creds->client = NULL;
+       krb5_free_creds(ctx, s4u2self_creds);
+
+       /*
+        * For S4U2Proxy we also got a target service principal,
+        * which also belongs to our own realm (available on
+        * our client principal).
+        */
+       code = krb5_parse_name(ctx, target_service, &target_princ);
+       if (code != 0) {
+               free_Ticket(&s4u2self_ticket);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_principal_set_realm(ctx, target_princ, self_realm);
+       if (code != 0) {
+               free_Ticket(&s4u2self_ticket);
+               krb5_free_principal(ctx, target_princ);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds_opt_alloc(ctx, &options);
+       if (code != 0) {
+               free_Ticket(&s4u2self_ticket);
+               krb5_free_principal(ctx, target_princ);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
+       krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_CONSTRAINED_DELEGATION);
+
+       code = krb5_get_creds_opt_set_ticket(ctx, options, &s4u2self_ticket);
+       free_Ticket(&s4u2self_ticket);
+       if (code != 0) {
+               krb5_get_creds_opt_free(ctx, options);
+               krb5_free_principal(ctx, target_princ);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds(ctx, options, tmp_cc,
+                             target_princ, &s4u2proxy_creds);
+       krb5_get_creds_opt_free(ctx, options);
+       krb5_free_principal(ctx, target_princ);
+       krb5_cc_destroy(ctx, tmp_cc);
+       if (code != 0) {
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               return code;
+       }
+
+       /*
+        * Now make sure we store the impersonated principal
+        * and creds instead of the TGT related stuff
+        * in the krb5_ccache of the caller.
+        */
+       code = krb5_copy_creds_contents(ctx, s4u2proxy_creds,
+                                       &store_creds);
+       krb5_free_creds(ctx, s4u2proxy_creds);
+       if (code != 0) {
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               return code;
        }
-       if (server) {
-               krb5_free_principal(context, server);
+
+       /*
+        * It's important to store the principal the KDC
+        * returned, as otherwise the caller would not find
+        * the S4U2Self ticket in the krb5_ccache lookup.
+        */
+       store_principal = store_creds.client;
+
+ store:
+       if (blacklist_principal &&
+           krb5_principal_compare(ctx, store_creds.client, blacklist_principal)) {
+               char *sp = NULL;
+               char *ip = NULL;
+
+               code = krb5_unparse_name(ctx, blacklist_principal, &sp);
+               if (code != 0) {
+                       sp = NULL;
+               }
+               code = krb5_unparse_name(ctx, impersonate_principal, &ip);
+               if (code != 0) {
+                       ip = NULL;
+               }
+               DEBUG(1, ("kerberos_kinit_password_cc: "
+                         "KDC returned self principal[%s] while impersonating [%s]\n",
+                         sp?sp:"<no memory>",
+                         ip?ip:"<no memory>"));
+
+               SAFE_FREE(sp);
+               SAFE_FREE(ip);
+
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_cred_contents(ctx, &store_creds);
+               return KRB5_FWD_BAD_PRINCIPAL;
        }
-       if (me) {
-               krb5_free_principal(context, me);
+       if (blacklist_principal) {
+               krb5_free_principal(ctx, blacklist_principal);
        }
-       if (impersonate_princ) {
-               krb5_free_principal(context, impersonate_princ);
+
+       if (whitelist_principal &&
+           !krb5_principal_compare(ctx, store_creds.client, whitelist_principal)) {
+               char *sp = NULL;
+               char *ep = NULL;
+
+               code = krb5_unparse_name(ctx, store_creds.client, &sp);
+               if (code != 0) {
+                       sp = NULL;
+               }
+               code = krb5_unparse_name(ctx, whitelist_principal, &ep);
+               if (code != 0) {
+                       ep = NULL;
+               }
+               DEBUG(1, ("kerberos_kinit_password_cc: "
+                         "KDC returned wrong principal[%s] we expected [%s]\n",
+                         sp?sp:"<no memory>",
+                         ep?ep:"<no memory>"));
+
+               SAFE_FREE(sp);
+               SAFE_FREE(ep);
+
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_cred_contents(ctx, &store_creds);
+               return KRB5_FWD_BAD_PRINCIPAL;
        }
-       if (ccache) {
-               krb5_cc_close(context, ccache);
+       if (whitelist_principal) {
+               krb5_free_principal(ctx, whitelist_principal);
        }
-       krb5_free_context(context);
 
-       return ret;
+       code = krb5_cc_initialize(ctx, store_cc, store_principal);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       code = krb5_cc_store_cred(ctx, store_cc, &store_creds);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       if (expire_time) {
+               *expire_time = (time_t) store_creds.times.endtime;
+       }
+
+       if (kdc_time) {
+               *kdc_time = (time_t) store_creds.times.starttime;
+       }
+
+       krb5_free_cred_contents(ctx, &store_creds);
+
+       return 0;
 }
+#endif
+
+#if !defined(HAVE_KRB5_MAKE_PRINCIPAL) && defined(HAVE_KRB5_BUILD_PRINCIPAL_ALLOC_VA)
+krb5_error_code smb_krb5_make_principal(krb5_context context,
+                                       krb5_principal *principal,
+                                       const char *_realm, ...)
+{
+       krb5_error_code code;
+       bool free_realm;
+       char *realm;
+       va_list ap;
+
+       if (_realm) {
+               realm = _realm;
+               free_realm = false;
+       } else {
+               code = krb5_get_default_realm(context, &realm);
+               if (code) {
+                       return code;
+               }
+               free_realm = true;
+       }
+
+       va_start(ap, _realm);
+       code = krb5_build_principal_alloc_va(context, principal,
+                                            strlen(realm), realm,
+                                            ap);
+       va_end(ap);
+
+       if (free_realm) {
+               krb5_free_default_realm(context, realm);
+       }
+
+       return code;
+}
+#endif
+
+#if !defined(HAVE_KRB5_CC_GET_LIFETIME) && defined(HAVE_KRB5_CC_RETRIEVE_CRED)
+/**
+ * @brief Get the lifetime of the initial ticket in the cache.
+ *
+ * @param[in]  context  The kerberos context.
+ *
+ * @param[in]  id       The credential cache to get the ticket lifetime.
+ *
+ * @param[out] t        A pointer to a time value to store the lifetime.
+ *
+ * @return              0 on success, a krb5_error_code on error.
+ */
+krb5_error_code smb_krb5_cc_get_lifetime(krb5_context context,
+                                        krb5_ccache id,
+                                        time_t *t)
+{
+       krb5_cc_cursor cursor;
+       krb5_error_code kerr;
+       krb5_creds cred;
+       krb5_timestamp now;
+
+       *t = 0;
+
+       kerr = krb5_timeofday(context, &now);
+       if (kerr) {
+               return kerr;
+       }
+
+       kerr = krb5_cc_start_seq_get(context, id, &cursor);
+       if (kerr) {
+               return kerr;
+       }
+
+       while ((kerr = krb5_cc_next_cred(context, id, &cursor, &cred)) == 0) {
+#ifndef HAVE_FLAGS_IN_KRB5_CREDS
+               if (cred.ticket_flags & TKT_FLG_INITIAL) {
+#else
+               if (cred.flags.b.initial) {
+#endif
+                       if (now < cred.times.endtime) {
+                               *t = (time_t) (cred.times.endtime - now);
+                       }
+                       krb5_free_cred_contents(context, &cred);
+                       break;
+               }
+               krb5_free_cred_contents(context, &cred);
+       }
+
+       krb5_cc_end_seq_get(context, id, &cursor);
+
+       return kerr;
+}
+#endif /* HAVE_KRB5_CC_GET_LIFETIME */
+
+#if !defined(HAVE_KRB5_FREE_CHECKSUM_CONTENTS) && defined(HAVE_FREE_CHECKSUM)
+void smb_krb5_free_checksum_contents(krb5_context ctx, krb5_checksum *cksum)
+{
+       free_Checksum(cksum);
+}
+#endif
+
+krb5_error_code smb_krb5_make_pac_checksum(TALLOC_CTX *mem_ctx,
+                                          DATA_BLOB *pac_data,
+                                          krb5_context context,
+                                          const krb5_keyblock *keyblock,
+                                          uint32_t *sig_type,
+                                          DATA_BLOB *sig_blob)
+{
+       krb5_error_code ret;
+       krb5_checksum cksum;
+#if defined(HAVE_KRB5_CRYPTO_INIT) && defined(HAVE_KRB5_CREATE_CHECKSUM)
+       krb5_crypto crypto;
+
+
+       ret = krb5_crypto_init(context,
+                              keyblock,
+                              0,
+                              &crypto);
+       if (ret) {
+               DEBUG(0,("krb5_crypto_init() failed: %s\n",
+                         smb_get_krb5_error_message(context, ret, mem_ctx)));
+               return ret;
+       }
+       ret = krb5_create_checksum(context,
+                                  crypto,
+                                  KRB5_KU_OTHER_CKSUM,
+                                  0,
+                                  pac_data->data,
+                                  pac_data->length,
+                                  &cksum);
+       if (ret) {
+               DEBUG(2, ("PAC Verification failed: %s\n",
+                         smb_get_krb5_error_message(context, ret, mem_ctx)));
+       }
+
+       krb5_crypto_destroy(context, crypto);
+
+       if (ret) {
+               return ret;
+       }
+
+       *sig_type = cksum.cksumtype;
+       *sig_blob = data_blob_talloc(mem_ctx,
+                                       cksum.checksum.data,
+                                       cksum.checksum.length);
+#elif defined(HAVE_KRB5_C_MAKE_CHECKSUM)
+       krb5_data input;
+
+       input.data = (char *)pac_data->data;
+       input.length = pac_data->length;
+
+       ret = krb5_c_make_checksum(context,
+                                  0,
+                                  keyblock,
+                                  KRB5_KEYUSAGE_APP_DATA_CKSUM,
+                                  &input,
+                                  &cksum);
+       if (ret) {
+               DEBUG(2, ("PAC Verification failed: %s\n",
+                         smb_get_krb5_error_message(context, ret, mem_ctx)));
+               return ret;
+       }
+
+       *sig_type = cksum.checksum_type;
+       *sig_blob = data_blob_talloc(mem_ctx,
+                                       cksum.contents,
+                                       cksum.length);
+
+#else
+#error krb5_create_checksum or krb5_c_make_checksum not available
+#endif /* HAVE_KRB5_C_MAKE_CHECKSUM */
+       smb_krb5_free_checksum_contents(context, &cksum);
+
+       return 0;
+}
+
 
 /*
  * smb_krb5_principal_get_realm
@@ -1565,22 +2305,68 @@ krb5_error_code smb_krb5_get_creds(const char *server_s,
  * @param[in] principal                The principal
  * @return pointer to the realm
  *
+ * Caller must free if the return value is not NULL.
+ *
  */
 
 char *smb_krb5_principal_get_realm(krb5_context context,
-                                  krb5_principal principal)
+                                  krb5_const_principal principal)
 {
 #ifdef HAVE_KRB5_PRINCIPAL_GET_REALM /* Heimdal */
-       return discard_const_p(char, krb5_principal_get_realm(context, principal));
+       return strdup(discard_const_p(char, krb5_principal_get_realm(context, principal)));
 #elif defined(krb5_princ_realm) /* MIT */
        krb5_data *realm;
        realm = krb5_princ_realm(context, principal);
-       return discard_const_p(char, realm->data);
+       return strndup(realm->data, realm->length);
+#else
+#error UNKNOWN_GET_PRINC_REALM_FUNCTIONS
+#endif
+}
+
+/*
+ * smb_krb5_principal_set_realm
+ *
+ * @brief Get realm of a principal
+ *
+ * @param[in] context          The krb5_context
+ * @param[in] principal                The principal
+ * @param[in] realm            The realm
+ * @return                     0 on success, a krb5_error_code on error.
+ *
+ */
+
+krb5_error_code smb_krb5_principal_set_realm(krb5_context context,
+                                            krb5_principal principal,
+                                            const char *realm)
+{
+#ifdef HAVE_KRB5_PRINCIPAL_SET_REALM /* Heimdal */
+       return krb5_principal_set_realm(context, principal, realm);
+#elif defined(krb5_princ_realm) && defined(krb5_princ_set_realm) /* MIT */
+       krb5_error_code ret;
+       krb5_data data;
+       krb5_data *old_data;
+
+       old_data = krb5_princ_realm(context, principal);
+
+       ret = krb5_copy_data_contents(&data,
+                                     realm,
+                                     strlen(realm));
+       if (ret) {
+               return ret;
+       }
+
+       /* free realm before setting */
+       free(old_data->data);
+
+       krb5_princ_set_realm(context, principal, &data);
+
+       return ret;
 #else
-       return NULL;
+#error UNKNOWN_PRINC_SET_REALM_FUNCTION
 #endif
 }
 
+
 /************************************************************************
  Routine to get the default realm from the kerberos credentials cache.
  Caller must free if the return value is not NULL.
@@ -1743,6 +2529,101 @@ char *smb_get_krb5_error_message(krb5_context context,
        return ret;
 }
 
+
+/**
+* @brief Return the kerberos library setting for "libdefaults:allow_weak_crypto"
+*
+* @param context       The krb5_context
+*
+* @return krb5_boolean
+*
+* Function returns true if weak crypto is allowd, false if not
+*/
+
+krb5_boolean smb_krb5_get_allowed_weak_crypto(krb5_context context)
+#if defined(HAVE_KRB5_CONFIG_GET_BOOL_DEFAULT)
+{
+       return krb5_config_get_bool_default(context,
+                                           NULL,
+                                           FALSE,
+                                           "libdefaults",
+                                           "allow_weak_crypto",
+                                           NULL);
+}
+#elif defined(HAVE_PROFILE_H) && defined(HAVE_KRB5_GET_PROFILE)
+{
+#include <profile.h>
+       krb5_error_code ret;
+       krb5_boolean ret_default = false;
+       profile_t profile;
+       int ret_profile;
+
+       ret = krb5_get_profile(context,
+                              &profile);
+       if (ret) {
+               return ret_default;
+       }
+
+       ret = profile_get_boolean(profile,
+                                 "libdefaults",
+                                 "allow_weak_crypto",
+                                 NULL, /* subsubname */
+                                 ret_default, /* def_val */
+                                 &ret_profile /* *ret_default */);
+       if (ret) {
+               return ret_default;
+       }
+
+       profile_release(profile);
+
+       return ret_profile;
+}
+#else
+#error UNKNOWN_KRB5_CONFIG_ROUTINES
+#endif
+
+/**
+* @brief Return the type of a krb5_principal
+*
+* @param context       The krb5_context
+* @param principal     The const krb5_principal
+*
+* @return integer type of the principal
+*/
+int smb_krb5_principal_get_type(krb5_context context,
+                               krb5_const_principal principal)
+{
+#ifdef HAVE_KRB5_PRINCIPAL_GET_TYPE /* Heimdal */
+       return krb5_principal_get_type(context, principal);
+#elif defined(krb5_princ_type) /* MIT */
+       return krb5_princ_type(context, principal);
+#else
+#error UNKNOWN_PRINC_GET_TYPE_FUNCTION
+#endif
+}
+
+/**
+* @brief Generate a krb5 warning, forwarding to com_err
+*
+* @param context       The krb5_context
+* @param fmt           The message format
+* @param ...           The message arguments
+*
+* @return
+*/
+#if !defined(HAVE_KRB5_WARNX)
+krb5_error_code krb5_warnx(krb5_context context, const char *fmt, ...)
+{
+       va_list args;
+
+       va_start(args, fmt);
+       com_err_va("kdb_samba", errno, fmt, args);
+       va_end(args);
+
+       return 0;
+}
+#endif
+
 #else /* HAVE_KRB5 */
  /* this saves a few linking headaches */
  int cli_krb5_get_ticket(TALLOC_CTX *mem_ctx,