s3:libads: Fix code spelling
[samba.git] / source3 / libads / kerberos.c
index adf1175dbe41db63e3ba35fa71238d959309cd89..50f4a6de3c625223b8926e970b49f7a8bc237f91 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
    kerberos utility library
    Copyright (C) Andrew Tridgell 2001
 */
 
 #include "includes.h"
+#include "libsmb/namequery.h"
 #include "system/filesys.h"
 #include "smb_krb5.h"
 #include "../librpc/gen_ndr/ndr_misc.h"
 #include "libads/kerberos_proto.h"
+#include "libads/cldap.h"
 #include "secrets.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/asn1.h"
 
 #ifdef HAVE_KRB5
 
-#define DEFAULT_KRB5_PORT 88
-
 #define LIBADS_CCACHE_NAME "MEMORY:libads"
 
 /*
-  we use a prompter to avoid a crash bug in the kerberos libs when 
+  we use a prompter to avoid a crash bug in the kerberos libs when
   dealing with empty passwords
   this prompter is just a string copy ...
 */
-static krb5_error_code 
+static krb5_error_code
 kerb_prompter(krb5_context ctx, void *data,
               const char *name,
               const char *banner,
@@ -47,6 +49,43 @@ kerb_prompter(krb5_context ctx, void *data,
               krb5_prompt prompts[])
 {
        if (num_prompts == 0) return 0;
+       if (num_prompts == 2) {
+               /*
+                * only heimdal has a prompt type and we need to deal with it here to
+                * avoid loops.
+                *
+                * removing the prompter completely is not an option as at least these
+                * versions would crash: heimdal-1.0.2 and heimdal-1.1. Later heimdal
+                * version have looping detection and return with a proper error code.
+                */
+
+#if defined(HAVE_KRB5_PROMPT_TYPE) /* Heimdal */
+                if (prompts[0].type == KRB5_PROMPT_TYPE_NEW_PASSWORD &&
+                    prompts[1].type == KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN) {
+                       /*
+                        * We don't want to change passwords here. We're
+                        * called from heimal when the KDC returns
+                        * KRB5KDC_ERR_KEY_EXPIRED, but at this point we don't
+                        * have the chance to ask the user for a new
+                        * password. If we return 0 (i.e. success), we will be
+                        * spinning in the endless for-loop in
+                        * change_password() in
+                        * source4/heimdal/lib/krb5/init_creds_pw.c:526ff
+                        */
+                       return KRB5KDC_ERR_KEY_EXPIRED;
+               }
+#elif defined(HAVE_KRB5_GET_PROMPT_TYPES) /* MIT */
+               krb5_prompt_type *prompt_types = NULL;
+
+               prompt_types = krb5_get_prompt_types(ctx);
+               if (prompt_types != NULL) {
+                       if (prompt_types[0] == KRB5_PROMPT_TYPE_NEW_PASSWORD &&
+                           prompt_types[1] == KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN) {
+                               return KRB5KDC_ERR_KEY_EXP;
+                       }
+               }
+#endif
+       }
 
        memset(prompts[0].reply->data, '\0', prompts[0].reply->length);
        if (prompts[0].reply->length > 0) {
@@ -61,102 +100,12 @@ kerb_prompter(krb5_context ctx, void *data,
        return 0;
 }
 
- static bool smb_krb5_get_ntstatus_from_krb5_error(krb5_error *error,
-                                                  NTSTATUS *nt_status)
-{
-       DATA_BLOB edata;
-       DATA_BLOB unwrapped_edata;
-       TALLOC_CTX *mem_ctx;
-       struct KRB5_EDATA_NTSTATUS parsed_edata;
-       enum ndr_err_code ndr_err;
-
-#ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR
-       edata = data_blob(error->e_data->data, error->e_data->length);
-#else
-       edata = data_blob(error->e_data.data, error->e_data.length);
-#endif /* HAVE_E_DATA_POINTER_IN_KRB5_ERROR */
-
-#ifdef DEVELOPER
-       dump_data(10, edata.data, edata.length);
-#endif /* DEVELOPER */
-
-       mem_ctx = talloc_init("smb_krb5_get_ntstatus_from_krb5_error");
-       if (mem_ctx == NULL) {
-               data_blob_free(&edata);
-               return False;
-       }
-
-       if (!unwrap_edata_ntstatus(mem_ctx, &edata, &unwrapped_edata)) {
-               data_blob_free(&edata);
-               TALLOC_FREE(mem_ctx);
-               return False;
-       }
-
-       data_blob_free(&edata);
-
-       ndr_err = ndr_pull_struct_blob_all(&unwrapped_edata, mem_ctx, 
-               &parsed_edata, (ndr_pull_flags_fn_t)ndr_pull_KRB5_EDATA_NTSTATUS);
-       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-               data_blob_free(&unwrapped_edata);
-               TALLOC_FREE(mem_ctx);
-               return False;
-       }
-
-       data_blob_free(&unwrapped_edata);
-
-       if (nt_status) {
-               *nt_status = parsed_edata.ntstatus;
-       }
-
-       TALLOC_FREE(mem_ctx);
-
-       return True;
-}
-
- static bool smb_krb5_get_ntstatus_from_krb5_error_init_creds_opt(krb5_context ctx, 
-                                                                 krb5_get_init_creds_opt *opt, 
-                                                                 NTSTATUS *nt_status)
-{
-       bool ret = False;
-       krb5_error *error = NULL;
-
-#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_GET_ERROR
-       ret = krb5_get_init_creds_opt_get_error(ctx, opt, &error);
-       if (ret) {
-               DEBUG(1,("krb5_get_init_creds_opt_get_error gave: %s\n", 
-                       error_message(ret)));
-               return False;
-       }
-#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_GET_ERROR */
-
-       if (!error) {
-               DEBUG(1,("no krb5_error\n"));
-               return False;
-       }
-
-#ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR
-       if (!error->e_data) {
-#else
-       if (error->e_data.data == NULL) {
-#endif /* HAVE_E_DATA_POINTER_IN_KRB5_ERROR */
-               DEBUG(1,("no edata in krb5_error\n")); 
-               krb5_free_error(ctx, error);
-               return False;
-       }
-
-       ret = smb_krb5_get_ntstatus_from_krb5_error(error, nt_status);
-
-       krb5_free_error(ctx, error);
-
-       return ret;
-}
-
 /*
   simulate a kinit, putting the tgt in the given cache location. If cache_name == NULL
   place in default cache location.
   remus@snapserver.com
 */
-int kerberos_kinit_password_ext(const char *principal,
+int kerberos_kinit_password_ext(const char *given_principal,
                                const char *password,
                                int time_offset,
                                time_t *expire_time,
@@ -165,45 +114,64 @@ int kerberos_kinit_password_ext(const char *principal,
                                bool request_pac,
                                bool add_netbios_addr,
                                time_t renewable_time,
+                               TALLOC_CTX *mem_ctx,
+                               char **_canon_principal,
+                               char **_canon_realm,
                                NTSTATUS *ntstatus)
 {
+       TALLOC_CTX *frame = talloc_stackframe();
        krb5_context ctx = NULL;
        krb5_error_code code = 0;
        krb5_ccache cc = NULL;
        krb5_principal me = NULL;
+       krb5_principal canon_princ = NULL;
        krb5_creds my_creds;
        krb5_get_init_creds_opt *opt = NULL;
        smb_krb5_addresses *addr = NULL;
+       char *canon_principal = NULL;
+       char *canon_realm = NULL;
 
        ZERO_STRUCT(my_creds);
 
-       initialize_krb5_error_table();
-       if ((code = krb5_init_context(&ctx)))
-               goto out;
+       code = smb_krb5_init_context_common(&ctx);
+       if (code != 0) {
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(code));
+               TALLOC_FREE(frame);
+               return code;
+       }
 
        if (time_offset != 0) {
                krb5_set_real_time(ctx, time(NULL) + time_offset, 0);
        }
 
-       DEBUG(10,("kerberos_kinit_password: as %s using [%s] as ccache and config [%s]\n",
-                       principal,
-                       cache_name ? cache_name: krb5_cc_default_name(ctx),
-                       getenv("KRB5_CONFIG")));
+       DBG_DEBUG("as %s using [%s] as ccache and config [%s]\n",
+                 given_principal,
+                 cache_name ? cache_name: krb5_cc_default_name(ctx),
+                 getenv("KRB5_CONFIG"));
 
        if ((code = krb5_cc_resolve(ctx, cache_name ? cache_name : krb5_cc_default_name(ctx), &cc))) {
                goto out;
        }
 
-       if ((code = smb_krb5_parse_name(ctx, principal, &me))) {
+       if ((code = smb_krb5_parse_name(ctx, given_principal, &me))) {
                goto out;
        }
 
-       if ((code = smb_krb5_get_init_creds_opt_alloc(ctx, &opt))) {
+       if ((code = krb5_get_init_creds_opt_alloc(ctx, &opt))) {
                goto out;
        }
 
        krb5_get_init_creds_opt_set_renew_life(opt, renewable_time);
        krb5_get_init_creds_opt_set_forwardable(opt, True);
+
+       /* Turn on canonicalization for lower case realm support */
+#ifdef SAMBA4_USES_HEIMDAL
+       krb5_get_init_creds_opt_set_win2k(ctx, opt, true);
+       krb5_get_init_creds_opt_set_canonicalize(ctx, opt, true);
+#else /* MIT */
+       krb5_get_init_creds_opt_set_canonicalize(opt, true);
+#endif /* MIT */
 #if 0
        /* insane testing */
        krb5_get_init_creds_opt_set_tkt_life(opt, 60);
@@ -217,19 +185,38 @@ int kerberos_kinit_password_ext(const char *principal,
        }
 #endif
        if (add_netbios_addr) {
-               if ((code = smb_krb5_gen_netbios_krb5_address(&addr))) {
+               if ((code = smb_krb5_gen_netbios_krb5_address(&addr,
+                                                       lp_netbios_name()))) {
                        goto out;
                }
                krb5_get_init_creds_opt_set_address_list(opt, addr->addrs);
        }
 
-       if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, discard_const_p(char,password), 
+       if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, discard_const_p(char,password),
                                                 kerb_prompter, discard_const_p(char, password),
                                                 0, NULL, opt))) {
                goto out;
        }
 
-       if ((code = krb5_cc_initialize(ctx, cc, me))) {
+       canon_princ = my_creds.client;
+
+       code = smb_krb5_unparse_name(frame,
+                                    ctx,
+                                    canon_princ,
+                                    &canon_principal);
+       if (code != 0) {
+               goto out;
+       }
+
+       DBG_DEBUG("%s mapped to %s\n", given_principal, canon_principal);
+
+       canon_realm = smb_krb5_principal_get_realm(frame, ctx, canon_princ);
+       if (canon_realm == NULL) {
+               code = ENOMEM;
+               goto out;
+       }
+
+       if ((code = krb5_cc_initialize(ctx, cc, canon_princ))) {
                goto out;
        }
 
@@ -244,25 +231,21 @@ int kerberos_kinit_password_ext(const char *principal,
        if (renew_till_time) {
                *renew_till_time = (time_t) my_creds.times.renew_till;
        }
+
+       if (_canon_principal != NULL) {
+               *_canon_principal = talloc_move(mem_ctx, &canon_principal);
+       }
+       if (_canon_realm != NULL) {
+               *_canon_realm = talloc_move(mem_ctx, &canon_realm);
+       }
  out:
        if (ntstatus) {
-
-               NTSTATUS status;
-
                /* fast path */
                if (code == 0) {
                        *ntstatus = NT_STATUS_OK;
                        goto cleanup;
                }
 
-               /* try to get ntstatus code out of krb5_error when we have it
-                * inside the krb5_get_init_creds_opt - gd */
-
-               if (opt && smb_krb5_get_ntstatus_from_krb5_error_init_creds_opt(ctx, opt, &status)) {
-                       *ntstatus = status;
-                       goto cleanup;
-               }
-
                /* fall back to self-made-mapping */
                *ntstatus = krb5_to_nt_status(code);
        }
@@ -275,8 +258,8 @@ int kerberos_kinit_password_ext(const char *principal,
        if (addr) {
                smb_krb5_free_addresses(ctx, addr);
        }
-       if (opt) {
-               smb_krb5_get_init_creds_opt_free(ctx, opt);
+       if (opt) {
+               krb5_get_init_creds_opt_free(ctx, opt);
        }
        if (cc) {
                krb5_cc_close(ctx, cc);
@@ -284,6 +267,7 @@ int kerberos_kinit_password_ext(const char *principal,
        if (ctx) {
                krb5_free_context(ctx);
        }
+       TALLOC_FREE(frame);
        return code;
 }
 
@@ -293,10 +277,10 @@ int ads_kdestroy(const char *cc_name)
        krb5_context ctx = NULL;
        krb5_ccache cc = NULL;
 
-       initialize_krb5_error_table();
-       if ((code = krb5_init_context (&ctx))) {
-               DEBUG(3, ("ads_kdestroy: kdb5_init_context failed: %s\n", 
-                       error_message(code)));
+       code = smb_krb5_init_context_common(&ctx);
+       if (code != 0) {
+               DBG_ERR("kerberos init context failed (%s)\n",
+                       error_message(code));
                return code;
        }
 
@@ -315,7 +299,7 @@ int ads_kdestroy(const char *cc_name)
        }
 
        if ((code = krb5_cc_destroy (ctx, cc))) {
-               DEBUG(3, ("ads_kdestroy: krb5_cc_destroy failed: %s\n", 
+               DEBUG(3, ("ads_kdestroy: krb5_cc_destroy failed: %s\n",
                        error_message(code)));
        }
 
@@ -323,522 +307,378 @@ int ads_kdestroy(const char *cc_name)
        return code;
 }
 
-/************************************************************************
- Routine to fetch the salting principal for a service.  Active
- Directory may use a non-obvious principal name to generate the salt
- when it determines the key to use for encrypting tickets for a service,
- and hopefully we detected that when we joined the domain.
- ************************************************************************/
-
-static char *kerberos_secrets_fetch_salting_principal(const char *service, int enctype)
+int create_kerberos_key_from_string(krb5_context context,
+                                       krb5_principal host_princ,
+                                       krb5_principal salt_princ,
+                                       krb5_data *password,
+                                       krb5_keyblock *key,
+                                       krb5_enctype enctype,
+                                       bool no_salt)
 {
-       char *key = NULL;
-       char *ret = NULL;
-
-       if (asprintf(&key, "%s/%s/enctype=%d",
-                    SECRETS_SALTING_PRINCIPAL, service, enctype) == -1) {
-               return NULL;
-       }
-       ret = (char *)secrets_fetch(key, NULL);
-       SAFE_FREE(key);
+       int ret;
+       /*
+        * Check if we've determined that the KDC is salting keys for this
+        * principal/enctype in a non-obvious way.  If it is, try to match
+        * its behavior.
+        */
+       if (no_salt) {
+               KRB5_KEY_DATA(key) = (KRB5_KEY_DATA_CAST *)SMB_MALLOC(password->length);
+               if (!KRB5_KEY_DATA(key)) {
+                       return ENOMEM;
+               }
+               memcpy(KRB5_KEY_DATA(key), password->data, password->length);
+               KRB5_KEY_LENGTH(key) = password->length;
+               KRB5_KEY_TYPE(key) = enctype;
+               return 0;
+       }
+       ret = smb_krb5_create_key_from_string(context,
+                                             salt_princ ? salt_princ : host_princ,
+                                             NULL,
+                                             password,
+                                             enctype,
+                                             key);
        return ret;
 }
 
 /************************************************************************
- Return the standard DES salt key
 ************************************************************************/
 
-char* kerberos_standard_des_salt( void )
+int kerberos_kinit_password(const char *principal,
+                           const char *password,
+                           int time_offset,
+                           const char *cache_name)
 {
-       fstring salt;
-
-       fstr_sprintf( salt, "host/%s.%s@", lp_netbios_name(), lp_realm() );
-       strlower_m( salt );
-       fstrcat( salt, lp_realm() );
-
-       return SMB_STRDUP( salt );
+       return kerberos_kinit_password_ext(principal,
+                                          password,
+                                          time_offset,
+                                          0,
+                                          0,
+                                          cache_name,
+                                          False,
+                                          False,
+                                          0,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          NULL);
 }
 
 /************************************************************************
 ************************************************************************/
 
-static char* des_salt_key( void )
-{
-       char *key;
-
-       if (asprintf(&key, "%s/DES/%s", SECRETS_SALTING_PRINCIPAL,
-                    lp_realm()) == -1) {
-               return NULL;
-       }
+/************************************************************************
+ Create a string list of available kdc's, possibly searching by sitename.
+ Does DNS queries.
 
-       return key;
-}
+ If "sitename" is given, the DC's in that site are listed first.
 
-/************************************************************************
 ************************************************************************/
 
-bool kerberos_secrets_store_des_salt( const char* salt )
+static void add_sockaddr_unique(struct sockaddr_storage *addrs, size_t *num_addrs,
+                               const struct sockaddr_storage *addr)
 {
-       char* key;
-       bool ret;
+       size_t i;
 
-       if ( (key = des_salt_key()) == NULL ) {
-               DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n"));
-               return False;
-       }
-
-       if ( !salt ) {
-               DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n"));
-               secrets_delete( key );
-               return True;
+       for (i=0; i<*num_addrs; i++) {
+               if (sockaddr_equal((const struct sockaddr *)&addrs[i],
+                                  (const struct sockaddr *)addr)) {
+                       return;
+               }
        }
-
-       DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt));
-
-       ret = secrets_store( key, salt, strlen(salt)+1 );
-
-       SAFE_FREE( key );
-
-       return ret;
+       addrs[i] = *addr;
+       *num_addrs += 1;
 }
 
-/************************************************************************
-************************************************************************/
+/* print_canonical_sockaddr prints an ipv6 addr in the form of
+* [ipv6.addr]. This string, when put in a generated krb5.conf file is not
+* always properly dealt with by some older krb5 libraries. Adding the hard-coded
+* portnumber workarounds the issue. - gd */
 
-char* kerberos_secrets_fetch_des_salt( void )
+static char *print_canonical_sockaddr_with_port(TALLOC_CTX *mem_ctx,
+                                               const struct sockaddr_storage *pss)
 {
-       char *salt, *key;
+       char *str = NULL;
 
-       if ( (key = des_salt_key()) == NULL ) {
-               DEBUG(0,("kerberos_secrets_fetch_des_salt: failed to generate key!\n"));
-               return False;
+       str = print_canonical_sockaddr(mem_ctx, pss);
+       if (str == NULL) {
+               return NULL;
        }
 
-       salt = (char*)secrets_fetch( key, NULL );
-
-       SAFE_FREE( key );
+       if (pss->ss_family != AF_INET6) {
+               return str;
+       }
 
-       return salt;
+#if defined(HAVE_IPV6)
+       str = talloc_asprintf_append(str, ":88");
+#endif
+       return str;
 }
 
-/************************************************************************
- Routine to get the default realm from the kerberos credentials cache.
- Caller must free if the return value is not NULL.
-************************************************************************/
-
-char *kerberos_get_default_realm_from_ccache( void )
+static char *get_kdc_ip_string(char *mem_ctx,
+               const char *realm,
+               const char *sitename,
+               const struct sockaddr_storage *pss)
 {
-       char *realm = NULL;
-       krb5_context ctx = NULL;
-       krb5_ccache cc = NULL;
-       krb5_principal princ = NULL;
+       TALLOC_CTX *frame = talloc_stackframe();
+       size_t i;
+       struct samba_sockaddr *ip_sa_site = NULL;
+       struct samba_sockaddr *ip_sa_nonsite = NULL;
+       struct samba_sockaddr sa = {0};
+       size_t count_site = 0;
+       size_t count_nonsite;
+       size_t num_dcs;
+       struct sockaddr_storage *dc_addrs = NULL;
+       struct tsocket_address **dc_addrs2 = NULL;
+       const struct tsocket_address * const *dc_addrs3 = NULL;
+       char *result = NULL;
+       struct netlogon_samlogon_response **responses = NULL;
+       NTSTATUS status;
+       bool ok;
+       char *kdc_str = NULL;
+       char *canon_sockaddr = NULL;
 
-       initialize_krb5_error_table();
-       if (krb5_init_context(&ctx)) {
-               return NULL;
-       }
+       SMB_ASSERT(pss != NULL);
 
-       DEBUG(5,("kerberos_get_default_realm_from_ccache: "
-               "Trying to read krb5 cache: %s\n",
-               krb5_cc_default_name(ctx)));
-       if (krb5_cc_default(ctx, &cc)) {
-               DEBUG(0,("kerberos_get_default_realm_from_ccache: "
-                       "failed to read default cache\n"));
+       canon_sockaddr = print_canonical_sockaddr_with_port(frame, pss);
+       if (canon_sockaddr == NULL) {
                goto out;
        }
-       if (krb5_cc_get_principal(ctx, cc, &princ)) {
-               DEBUG(0,("kerberos_get_default_realm_from_ccache: "
-                       "failed to get default principal\n"));
+
+       kdc_str = talloc_asprintf(frame,
+                                 "\t\tkdc = %s\n",
+                                 canon_sockaddr);
+       if (kdc_str == NULL) {
                goto out;
        }
 
-#if defined(HAVE_KRB5_PRINCIPAL_GET_REALM)
-       realm = SMB_STRDUP(krb5_principal_get_realm(ctx, princ));
-#elif defined(HAVE_KRB5_PRINC_REALM)
-       {
-               krb5_data *realm_data = krb5_princ_realm(ctx, princ);
-               realm = SMB_STRNDUP(realm_data->data, realm_data->length);
+       ok = sockaddr_storage_to_samba_sockaddr(&sa, pss);
+       if (!ok) {
+               goto out;
        }
-#endif
 
-  out:
+       /*
+        * First get the KDC's only in this site, the rest will be
+        * appended later
+        */
 
-       if (ctx) {
-               if (princ) {
-                       krb5_free_principal(ctx, princ);
-               }
-               if (cc) {
-                       krb5_cc_close(ctx, cc);
+       if (sitename) {
+               status = get_kdc_list(frame,
+                                       realm,
+                                       sitename,
+                                       &ip_sa_site,
+                                       &count_site);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DBG_ERR("get_kdc_list fail %s\n",
+                               nt_errstr(status));
+                       goto out;
                }
-               krb5_free_context(ctx);
+               DBG_DEBUG("got %zu addresses from site %s search\n",
+                       count_site,
+                       sitename);
        }
 
-       return realm;
-}
-
-/************************************************************************
- Routine to get the realm from a given DNS name. Returns malloc'ed memory.
- Caller must free() if the return value is not NULL.
-************************************************************************/
-
-char *kerberos_get_realm_from_hostname(const char *hostname)
-{
-#if defined(HAVE_KRB5_GET_HOST_REALM) && defined(HAVE_KRB5_FREE_HOST_REALM)
-#if defined(HAVE_KRB5_REALM_TYPE)
-       /* Heimdal. */
-       krb5_realm *realm_list = NULL;
-#else
-       /* MIT */
-       char **realm_list = NULL;
-#endif
-       char *realm = NULL;
-       krb5_error_code kerr;
-       krb5_context ctx = NULL;
+       /* Get all KDC's. */
 
-       initialize_krb5_error_table();
-       if (krb5_init_context(&ctx)) {
-               return NULL;
+       status = get_kdc_list(frame,
+                                       realm,
+                                       NULL,
+                                       &ip_sa_nonsite,
+                                       &count_nonsite);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("get_kdc_list (site-less) fail %s\n",
+                       nt_errstr(status));
+               goto out;
        }
+       DBG_DEBUG("got %zu addresses from site-less search\n", count_nonsite);
 
-       kerr = krb5_get_host_realm(ctx, hostname, &realm_list);
-       if (kerr != 0) {
-               DEBUG(3,("kerberos_get_realm_from_hostname %s: "
-                       "failed %s\n",
-                       hostname ? hostname : "(NULL)",
-                       error_message(kerr) ));
+       if (count_site + count_nonsite < count_site) {
+               /* Wrap check. */
+               DBG_ERR("get_kdc_list_talloc (site-less) fail wrap error\n");
                goto out;
        }
 
-       if (realm_list && realm_list[0]) {
-               realm = SMB_STRDUP(realm_list[0]);
+
+       dc_addrs = talloc_array(talloc_tos(), struct sockaddr_storage,
+                               count_site + count_nonsite);
+       if (dc_addrs == NULL) {
+               goto out;
        }
 
-  out:
+       num_dcs = 0;
 
-       if (ctx) {
-               if (realm_list) {
-                       krb5_free_host_realm(ctx, realm_list);
-                       realm_list = NULL;
+       for (i = 0; i < count_site; i++) {
+               if (!sockaddr_equal(&sa.u.sa, &ip_sa_site[i].u.sa)) {
+                       add_sockaddr_unique(dc_addrs, &num_dcs,
+                                           &ip_sa_site[i].u.ss);
                }
-               krb5_free_context(ctx);
-               ctx = NULL;
        }
-       return realm;
-#else
-       return NULL;
-#endif
-}
-
-/************************************************************************
- Routine to get the salting principal for this service.  This is 
- maintained for backwards compatibilty with releases prior to 3.0.24.
- Since we store the salting principal string only at join, we may have 
- to look for the older tdb keys.  Caller must free if return is not null.
- ************************************************************************/
-
-krb5_principal kerberos_fetch_salt_princ_for_host_princ(krb5_context context,
-                                                       krb5_principal host_princ,
-                                                       int enctype)
-{
-       char *unparsed_name = NULL, *salt_princ_s = NULL;
-       krb5_principal ret_princ = NULL;
 
-       /* lookup new key first */
-
-       if ( (salt_princ_s = kerberos_secrets_fetch_des_salt()) == NULL ) {
-
-               /* look under the old key.  If this fails, just use the standard key */
-
-               if (smb_krb5_unparse_name(talloc_tos(), context, host_princ, &unparsed_name) != 0) {
-                       return (krb5_principal)NULL;
-               }
-               if ((salt_princ_s = kerberos_secrets_fetch_salting_principal(unparsed_name, enctype)) == NULL) {
-                       /* fall back to host/machine.realm@REALM */
-                       salt_princ_s = kerberos_standard_des_salt();
+       for (i = 0; i < count_nonsite; i++) {
+               if (!sockaddr_equal(&sa.u.sa, &ip_sa_nonsite[i].u.sa)) {
+                       add_sockaddr_unique(dc_addrs, &num_dcs,
+                                           &ip_sa_nonsite[i].u.ss);
                }
        }
 
-       if (smb_krb5_parse_name(context, salt_princ_s, &ret_princ) != 0) {
-               ret_princ = NULL;
+       DBG_DEBUG("%zu additional KDCs to test\n", num_dcs);
+       if (num_dcs == 0) {
+               /*
+                * We do not have additional KDCs, but we have the one passed
+                * in via `pss`. So just use that one and leave.
+                */
+               result = talloc_move(mem_ctx, &kdc_str);
+               goto out;
        }
 
-       TALLOC_FREE(unparsed_name);
-       SAFE_FREE(salt_princ_s);
+       dc_addrs2 = talloc_zero_array(talloc_tos(),
+                                     struct tsocket_address *,
+                                     num_dcs);
+       if (dc_addrs2 == NULL) {
+               goto out;
+       }
 
-       return ret_princ;
-}
+       for (i=0; i<num_dcs; i++) {
+               char addr[INET6_ADDRSTRLEN];
+               int ret;
 
-/************************************************************************
- Routine to set the salting principal for this service.  Active
- Directory may use a non-obvious principal name to generate the salt
- when it determines the key to use for encrypting tickets for a service,
- and hopefully we detected that when we joined the domain.
- Setting principal to NULL deletes this entry.
- ************************************************************************/
-
-bool kerberos_secrets_store_salting_principal(const char *service,
-                                             int enctype,
-                                             const char *principal)
-{
-       char *key = NULL;
-       bool ret = False;
-       krb5_context context = NULL;
-       krb5_principal princ = NULL;
-       char *princ_s = NULL;
-       char *unparsed_name = NULL;
-       krb5_error_code code;
+               print_sockaddr(addr, sizeof(addr), &dc_addrs[i]);
 
-       if (((code = krb5_init_context(&context)) != 0) || (context == NULL)) {
-               DEBUG(5, ("kerberos_secrets_store_salting_pricipal: kdb5_init_context failed: %s\n",
-                         error_message(code)));
-               return False;
-       }
-       if (strchr_m(service, '@')) {
-               if (asprintf(&princ_s, "%s", service) == -1) {
-                       goto out;
-               }
-       } else {
-               if (asprintf(&princ_s, "%s@%s", service, lp_realm()) == -1) {
+               ret = tsocket_address_inet_from_strings(dc_addrs2, "ip",
+                                                       addr, LDAP_PORT,
+                                                       &dc_addrs2[i]);
+               if (ret != 0) {
+                       status = map_nt_error_from_unix(errno);
+                       DEBUG(2,("Failed to create tsocket_address for %s - %s\n",
+                                addr, nt_errstr(status)));
                        goto out;
                }
        }
 
-       if (smb_krb5_parse_name(context, princ_s, &princ) != 0) {
-               goto out;
-       }
-       if (smb_krb5_unparse_name(talloc_tos(), context, princ, &unparsed_name) != 0) {
-               goto out;
-       }
+       dc_addrs3 = (const struct tsocket_address * const *)dc_addrs2;
 
-       if (asprintf(&key, "%s/%s/enctype=%d",
-                    SECRETS_SALTING_PRINCIPAL, unparsed_name, enctype)
-           == -1) {
-               goto out;
-       }
+       status = cldap_multi_netlogon(talloc_tos(),
+                       dc_addrs3, num_dcs,
+                       realm, lp_netbios_name(),
+                       NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX,
+                       MIN(num_dcs, 3), timeval_current_ofs(3, 0), &responses);
+       TALLOC_FREE(dc_addrs2);
+       dc_addrs3 = NULL;
 
-       if ((principal != NULL) && (strlen(principal) > 0)) {
-               ret = secrets_store(key, principal, strlen(principal) + 1);
-       } else {
-               ret = secrets_delete(key);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10,("get_kdc_ip_string: cldap_multi_netlogon failed: "
+                         "%s\n", nt_errstr(status)));
+               goto out;
        }
 
- out:
+       for (i=0; i<num_dcs; i++) {
+               char *new_kdc_str;
 
-       SAFE_FREE(key);
-       SAFE_FREE(princ_s);
-       TALLOC_FREE(unparsed_name);
+               if (responses[i] == NULL) {
+                       continue;
+               }
 
-       if (princ) {
-               krb5_free_principal(context, princ);
+               /* Append to the string - inefficient but not done often. */
+               new_kdc_str = talloc_asprintf_append(
+                               kdc_str,
+                               "\t\tkdc = %s\n",
+                               print_canonical_sockaddr_with_port(
+                                       mem_ctx, &dc_addrs[i]));
+               if (new_kdc_str == NULL) {
+                       goto out;
+               }
+               kdc_str = new_kdc_str;
        }
 
-       if (context) {
-               krb5_free_context(context);
+       result = talloc_move(mem_ctx, &kdc_str);
+out:
+       if (result != NULL) {
+               DBG_DEBUG("Returning\n%s\n", result);
+       } else {
+               DBG_NOTICE("Failed to get KDC ip address\n");
        }
 
-       return ret;
-}
-
-
-/************************************************************************
-************************************************************************/
-
-int kerberos_kinit_password(const char *principal,
-                           const char *password,
-                           int time_offset,
-                           const char *cache_name)
-{
-       return kerberos_kinit_password_ext(principal, 
-                                          password, 
-                                          time_offset, 
-                                          0, 
-                                          0,
-                                          cache_name,
-                                          False,
-                                          False,
-                                          0,
-                                          NULL);
+       TALLOC_FREE(frame);
+       return result;
 }
 
 /************************************************************************
+ Create  a specific krb5.conf file in the private directory pointing
+ at a specific kdc for a realm. Keyed off domain name. Sets
+ KRB5_CONFIG environment variable to point to this file. Must be
+ run as root or will fail (which is a good thing :-).
 ************************************************************************/
 
-static char *print_kdc_line(char *mem_ctx,
-                       const char *prev_line,
-                       const struct sockaddr_storage *pss,
-                       const char *kdc_name)
+#if !defined(SAMBA4_USES_HEIMDAL) /* MIT version */
+static char *get_enctypes(TALLOC_CTX *mem_ctx)
 {
-       char *kdc_str = NULL;
+       char *aes_enctypes = NULL;
+       const char *legacy_enctypes = "";
+       char *enctypes = NULL;
 
-       if (pss->ss_family == AF_INET) {
-               kdc_str = talloc_asprintf(mem_ctx, "%s\tkdc = %s\n",
-                                       prev_line,
-                                        print_canonical_sockaddr(mem_ctx, pss));
-       } else {
-               char addr[INET6_ADDRSTRLEN];
-               uint16_t port = get_sockaddr_port(pss);
-
-               DEBUG(10,("print_kdc_line: IPv6 case for kdc_name: %s, port: %d\n",
-                       kdc_name, port));
-
-               if (port != 0 && port != DEFAULT_KRB5_PORT) {
-                       /* Currently for IPv6 we can't specify a non-default
-                          krb5 port with an address, as this requires a ':'.
-                          Resolve to a name. */
-                       char hostname[MAX_DNS_NAME_LENGTH];
-                       int ret = sys_getnameinfo((const struct sockaddr *)pss,
-                                       sizeof(*pss),
-                                       hostname, sizeof(hostname),
-                                       NULL, 0,
-                                       NI_NAMEREQD);
-                       if (ret) {
-                               DEBUG(0,("print_kdc_line: can't resolve name "
-                                       "for kdc with non-default port %s. "
-                                       "Error %s\n.",
-                                       print_canonical_sockaddr(mem_ctx, pss),
-                                       gai_strerror(ret)));
-                               return NULL;
-                       }
-                       /* Success, use host:port */
-                       kdc_str = talloc_asprintf(mem_ctx,
-                                       "%s\tkdc = %s:%u\n",
-                                       prev_line,
-                                       hostname,
-                                       (unsigned int)port);
-               } else {
+       aes_enctypes = talloc_strdup(mem_ctx, "");
+       if (aes_enctypes == NULL) {
+               goto done;
+       }
 
-                       /* no krb5 lib currently supports "kdc = ipv6 address"
-                        * at all, so just fill in just the kdc_name if we have
-                        * it and let the krb5 lib figure out the appropriate
-                        * ipv6 address - gd */
-
-                       if (kdc_name) {
-                               kdc_str = talloc_asprintf(mem_ctx, "%s\tkdc = %s\n",
-                                               prev_line, kdc_name);
-                       } else {
-                               kdc_str = talloc_asprintf(mem_ctx, "%s\tkdc = %s\n",
-                                               prev_line,
-                                               print_sockaddr(addr,
-                                                       sizeof(addr),
-                                                       pss));
-                       }
+       if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+           lp_kerberos_encryption_types() == KERBEROS_ETYPES_STRONG) {
+               aes_enctypes = talloc_asprintf_append(
+                   aes_enctypes, "%s", "aes256-cts-hmac-sha1-96 ");
+               if (aes_enctypes == NULL) {
+                       goto done;
+               }
+               aes_enctypes = talloc_asprintf_append(
+                   aes_enctypes, "%s", "aes128-cts-hmac-sha1-96");
+               if (aes_enctypes == NULL) {
+                       goto done;
                }
        }
-       return kdc_str;
-}
 
-/************************************************************************
- Create a string list of available kdc's, possibly searching by sitename.
- Does DNS queries.
-
- If "sitename" is given, the DC's in that site are listed first.
-
-************************************************************************/
+       if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_ALLOWED &&
+           (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+            lp_kerberos_encryption_types() == KERBEROS_ETYPES_LEGACY)) {
+               legacy_enctypes = "RC4-HMAC";
+       }
 
-static char *get_kdc_ip_string(char *mem_ctx,
-               const char *realm,
-               const char *sitename,
-               const struct sockaddr_storage *pss,
-               const char *kdc_name)
+       enctypes =
+           talloc_asprintf(mem_ctx, "\tdefault_tgs_enctypes = %s %s\n"
+                                    "\tdefault_tkt_enctypes = %s %s\n"
+                                    "\tpreferred_enctypes = %s %s\n",
+                           aes_enctypes, legacy_enctypes, aes_enctypes,
+                           legacy_enctypes, aes_enctypes, legacy_enctypes);
+done:
+       TALLOC_FREE(aes_enctypes);
+       return enctypes;
+}
+#else /* Heimdal version */
+static char *get_enctypes(TALLOC_CTX *mem_ctx)
 {
-       int i;
-       struct ip_service *ip_srv_site = NULL;
-       struct ip_service *ip_srv_nonsite = NULL;
-       int count_site = 0;
-       int count_nonsite;
-       char *kdc_str = print_kdc_line(mem_ctx, "", pss, kdc_name);
+       const char *aes_enctypes = "";
+       const char *legacy_enctypes = "";
+       char *enctypes = NULL;
 
-       if (kdc_str == NULL) {
-               return NULL;
+       if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+           lp_kerberos_encryption_types() == KERBEROS_ETYPES_STRONG) {
+               aes_enctypes =
+                   "aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96";
        }
 
-       /*
-        * First get the KDC's only in this site, the rest will be
-        * appended later
-        */
-
-       if (sitename) {
-
-               get_kdc_list(realm, sitename, &ip_srv_site, &count_site);
-
-               for (i = 0; i < count_site; i++) {
-                       if (sockaddr_equal((struct sockaddr *)&ip_srv_site[i].ss,
-                                                  (struct sockaddr *)pss)) {
-                               continue;
-                       }
-                       /* Append to the string - inefficient
-                        * but not done often. */
-                       kdc_str = print_kdc_line(mem_ctx,
-                                               kdc_str,
-                                               &ip_srv_site[i].ss,
-                                               NULL);
-                       if (!kdc_str) {
-                               SAFE_FREE(ip_srv_site);
-                               return NULL;
-                       }
-               }
+       if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+           lp_kerberos_encryption_types() == KERBEROS_ETYPES_LEGACY) {
+               legacy_enctypes = "arcfour-hmac-md5";
        }
 
-       /* Get all KDC's. */
-
-       get_kdc_list(realm, NULL, &ip_srv_nonsite, &count_nonsite);
+       enctypes = talloc_asprintf(mem_ctx, "\tdefault_etypes = %s %s\n",
+                                  aes_enctypes, legacy_enctypes);
 
-       for (i = 0; i < count_nonsite; i++) {
-               int j;
-
-               if (sockaddr_equal((struct sockaddr *)&ip_srv_nonsite[i].ss, (struct sockaddr *)pss)) {
-                       continue;
-               }
-
-               /* Ensure this isn't an IP already seen (YUK! this is n*n....) */
-               for (j = 0; j < count_site; j++) {
-                       if (sockaddr_equal((struct sockaddr *)&ip_srv_nonsite[i].ss,
-                                               (struct sockaddr *)&ip_srv_site[j].ss)) {
-                               break;
-                       }
-                       /* As the lists are sorted we can break early if nonsite > site. */
-                       if (ip_service_compare(&ip_srv_nonsite[i], &ip_srv_site[j]) > 0) {
-                               break;
-                       }
-               }
-               if (j != i) {
-                       continue;
-               }
-
-               /* Append to the string - inefficient but not done often. */
-               kdc_str = print_kdc_line(mem_ctx,
-                               kdc_str,
-                               &ip_srv_nonsite[i].ss,
-                               NULL);
-               if (!kdc_str) {
-                       SAFE_FREE(ip_srv_site);
-                       SAFE_FREE(ip_srv_nonsite);
-                       return NULL;
-               }
-       }
-
-
-       SAFE_FREE(ip_srv_site);
-       SAFE_FREE(ip_srv_nonsite);
-
-       DEBUG(10,("get_kdc_ip_string: Returning %s\n",
-               kdc_str ));
-
-       return kdc_str;
+       return enctypes;
 }
-
-/************************************************************************
- Create  a specific krb5.conf file in the private directory pointing
- at a specific kdc for a realm. Keyed off domain name. Sets
- KRB5_CONFIG environment variable to point to this file. Must be
- run as root or will fail (which is a good thing :-).
-************************************************************************/
+#endif
 
 bool create_local_private_krb5_conf_for_domain(const char *realm,
                                                const char *domain,
                                                const char *sitename,
-                                               struct sockaddr_storage *pss,
-                                               const char *kdc_name)
+                                               const struct sockaddr_storage *pss)
 {
        char *dname;
        char *tmpname = NULL;
@@ -850,12 +690,25 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
        int fd;
        char *realm_upper = NULL;
        bool result = false;
+       char *enctypes = NULL;
+       const char *include_system_krb5 = "";
+       mode_t mask;
 
        if (!lp_create_krb5_conf()) {
                return false;
        }
 
-       dname = lock_path("smb_krb5");
+       if (realm == NULL) {
+               DEBUG(0, ("No realm has been specified! Do you really want to "
+                         "join an Active Directory server?\n"));
+               return false;
+       }
+
+       if (domain == NULL || pss == NULL) {
+               return false;
+       }
+
+       dname = lock_path(talloc_tos(), "smb_krb5");
        if (!dname) {
                return false;
        }
@@ -866,7 +719,7 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
                goto done;
        }
 
-       tmpname = lock_path("smb_tmp_krb5.XXXXXX");
+       tmpname = lock_path(talloc_tos(), "smb_tmp_krb5.XXXXXX");
        if (!tmpname) {
                goto done;
        }
@@ -880,21 +733,51 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
                fname, realm, domain ));
 
        realm_upper = talloc_strdup(fname, realm);
-       strupper_m(realm_upper);
+       if (!strupper_m(realm_upper)) {
+               goto done;
+       }
 
-       kdc_ip_string = get_kdc_ip_string(dname, realm, sitename, pss, kdc_name);
+       kdc_ip_string = get_kdc_ip_string(dname, realm, sitename, pss);
        if (!kdc_ip_string) {
                goto done;
        }
 
-       file_contents = talloc_asprintf(fname,
-                                       "[libdefaults]\n\tdefault_realm = %s\n"
-                                       "\tdefault_tgs_enctypes = RC4-HMAC DES-CBC-CRC DES-CBC-MD5\n"
-                                       "\tdefault_tkt_enctypes = RC4-HMAC DES-CBC-CRC DES-CBC-MD5\n"
-                                       "\tpreferred_enctypes = RC4-HMAC DES-CBC-CRC DES-CBC-MD5\n\n"
-                                       "[realms]\n\t%s = {\n"
-                                       "\t%s\t}\n",
-                                       realm_upper, realm_upper, kdc_ip_string);
+       enctypes = get_enctypes(fname);
+       if (enctypes == NULL) {
+               goto done;
+       }
+
+#if !defined(SAMBA4_USES_HEIMDAL)
+       if (lp_include_system_krb5_conf()) {
+               include_system_krb5 = "include /etc/krb5.conf";
+       }
+#endif
+
+       /*
+        * We are setting 'dns_lookup_kdc' to true, because we want to lookup
+        * KDCs which are not configured via DNS SRV records, eg. if we do:
+        *
+        *     net ads join -Uadmin@otherdomain
+        */
+       file_contents =
+           talloc_asprintf(fname,
+                           "[libdefaults]\n"
+                           "\tdefault_realm = %s\n"
+                           "%s"
+                           "\tdns_lookup_realm = false\n"
+                           "\tdns_lookup_kdc = true\n\n"
+                           "[realms]\n\t%s = {\n"
+                           "%s\t}\n"
+                           "\t%s = {\n"
+                           "%s\t}\n"
+                           "%s\n",
+                           realm_upper,
+                           enctypes,
+                           realm_upper,
+                           kdc_ip_string,
+                           domain,
+                           kdc_ip_string,
+                           include_system_krb5);
 
        if (!file_contents) {
                goto done;
@@ -902,11 +785,13 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
 
        flen = strlen(file_contents);
 
+       mask = umask(S_IRWXO | S_IRWXG);
        fd = mkstemp(tmpname);
+       umask(mask);
        if (fd == -1) {
-               DEBUG(0,("create_local_private_krb5_conf_for_domain: smb_mkstemp failed,"
-                       " for file %s. Errno %s\n",
-                       tmpname, strerror(errno) ));
+               DBG_ERR("mkstemp failed, for file %s. Errno %s\n",
+                       tmpname,
+                       strerror(errno));
                goto done;
        }
 
@@ -943,9 +828,8 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
                goto done;
        }
 
-       DEBUG(5,("create_local_private_krb5_conf_for_domain: wrote "
-               "file %s with realm %s KDC list = %s\n",
-               fname, realm_upper, kdc_ip_string));
+       DBG_INFO("wrote file %s with realm %s KDC list:\n%s\n",
+                fname, realm_upper, kdc_ip_string);
 
        /* Set the environment variable to this file. */
        setenv("KRB5_CONFIG", fname, 1);
@@ -996,7 +880,7 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
                                goto done; /* Not a fatal error. */
                        }
 
-                       /* Yes, this is a race conditon... too bad. */
+                       /* Yes, this is a race condition... too bad. */
                        if (rename(SYSTEM_KRB5_CONF_PATH, newpath) == -1) {
                                DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
                                        "of %s to %s failed. Errno %s\n",