#include "system/filesys.h"
#include "smb_krb5.h"
#include "../librpc/gen_ndr/ndr_misc.h"
+#include "../librpc/gen_ndr/samr.h"
#include "libads/kerberos_proto.h"
#include "libads/netlogon_ping.h"
#include "secrets.h"
#include "../lib/tsocket/tsocket.h"
+#include "../libcli/util/tstream.h"
+#include "../lib/util/tevent_ntstatus.h"
#include "lib/util/asn1.h"
#include "librpc/gen_ndr/netlogon.h"
ntstatus);
}
+struct kerberos_transaction_cache {
+ struct tsocket_address *local_addr;
+ struct tsocket_address *kdc_addr;
+ uint32_t timeout_msec;
+};
+
+static NTSTATUS kerberos_transaction_cache_create(
+ const char *explicit_kdc,
+ uint32_t timeout_msec,
+ TALLOC_CTX *mem_ctx,
+ struct kerberos_transaction_cache **_kc)
+{
+ struct kerberos_transaction_cache *kc = NULL;
+ int ret;
+
+ kc = talloc_zero(mem_ctx, struct kerberos_transaction_cache);
+ if (kc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ kc->timeout_msec = timeout_msec;
+
+ /* parse the address of explicit kdc */
+ ret = tsocket_address_inet_from_strings(kc,
+ "ip",
+ explicit_kdc,
+ DEFAULT_KRB5_PORT,
+ &kc->kdc_addr);
+ if (ret != 0) {
+ NTSTATUS status = map_nt_error_from_unix_common(errno);
+ TALLOC_FREE(kc);
+ return status;
+ }
+
+ /* get an address for us to use locally */
+ ret = tsocket_address_inet_from_strings(kc,
+ "ip",
+ NULL,
+ 0,
+ &kc->local_addr);
+ if (ret != 0) {
+ NTSTATUS status = map_nt_error_from_unix_common(errno);
+ TALLOC_FREE(kc);
+ return status;
+ }
+
+ *_kc = kc;
+ return NT_STATUS_OK;
+}
+
+#ifdef HAVE_KRB5_INIT_CREDS_STEP
+
+struct kerberos_transaction_state {
+ struct tevent_context *ev;
+ struct kerberos_transaction_cache *kc;
+ struct tstream_context *stream;
+ uint8_t in_hdr[4];
+ struct iovec in_iov[2];
+ DATA_BLOB rep_blob;
+};
+
+static void kerberos_transaction_connect_done(struct tevent_req *subreq);
+
+static struct tevent_req *kerberos_transaction_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct kerberos_transaction_cache *kc,
+ const char *realm,
+ const DATA_BLOB req_blob)
+{
+ struct tevent_req *req = NULL;
+ struct kerberos_transaction_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct timeval end;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct kerberos_transaction_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->kc = kc;
+
+ PUSH_BE_U32(state->in_hdr, 0, req_blob.length);
+ state->in_iov[0].iov_base = (char *)state->in_hdr;
+ state->in_iov[0].iov_len = 4;
+ state->in_iov[1].iov_base = (char *)req_blob.data;
+ state->in_iov[1].iov_len = req_blob.length;
+
+ end = timeval_current_ofs_msec(state->kc->timeout_msec);
+ if (!tevent_req_set_endtime(req, state->ev, end)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ subreq = tstream_inet_tcp_connect_send(state,
+ state->ev,
+ state->kc->local_addr,
+ state->kc->kdc_addr);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+ tevent_req_set_callback(subreq, kerberos_transaction_connect_done, req);
+
+ return req;
+}
+
+static void kerberos_transaction_writev_done(struct tevent_req *subreq);
+static void kerberos_transaction_read_pdu_done(struct tevent_req *subreq);
+
+static void kerberos_transaction_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct kerberos_transaction_state *state =
+ tevent_req_data(req,
+ struct kerberos_transaction_state);
+ int ret, sys_errno;
+
+ ret = tstream_inet_tcp_connect_recv(subreq,
+ &sys_errno,
+ state,
+ &state->stream,
+ NULL);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ NTSTATUS status = map_nt_error_from_unix_common(sys_errno);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ subreq = tstream_writev_send(state,
+ state->ev,
+ state->stream,
+ state->in_iov, 2);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kerberos_transaction_writev_done, req);
+
+ subreq = tstream_read_pdu_blob_send(state,
+ state->ev,
+ state->stream,
+ 4, /* initial_read_size */
+ tstream_full_request_u32,
+ req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kerberos_transaction_read_pdu_done, req);
+}
+
+static void kerberos_transaction_writev_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret, sys_errno;
+
+ ret = tstream_writev_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ NTSTATUS status = map_nt_error_from_unix_common(sys_errno);
+ tevent_req_nterror(req, status);
+ return;
+ }
+}
+
+static void kerberos_transaction_read_pdu_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct kerberos_transaction_state *state =
+ tevent_req_data(req,
+ struct kerberos_transaction_state);
+ NTSTATUS status;
+
+ status = tstream_read_pdu_blob_recv(subreq, state, &state->rep_blob);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * raw blob has the length in the first 4 bytes,
+ * which we do not need here.
+ */
+ memmove(state->rep_blob.data,
+ state->rep_blob.data + 4,
+ state->rep_blob.length - 4);
+ state->rep_blob.length -= 4;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS kerberos_transaction_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *rep_blob)
+{
+ struct kerberos_transaction_state *state =
+ tevent_req_data(req,
+ struct kerberos_transaction_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ rep_blob->data = talloc_move(mem_ctx, &state->rep_blob.data);
+ rep_blob->length = state->rep_blob.length;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS kerberos_transaction(
+ struct kerberos_transaction_cache *kc,
+ const char *realm,
+ const DATA_BLOB req_blob,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *rep_blob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = kerberos_transaction_send(frame, ev, kc, realm, req_blob);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = kerberos_transaction_recv(req, mem_ctx, rep_blob);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+#endif /* HAVE_KRB5_INIT_CREDS_STEP */
+
+struct kerberos_kinit_passwords_ext_private {
+ const char *explicit_kdc;
+ uint32_t timeout_msec;
+ struct kerberos_transaction_cache *kc;
+ const char *password;
+ const struct samr_Password *nt_hash;
+};
+
+static krb5_error_code kerberos_kinit_passwords_ext_cb(krb5_context context,
+ krb5_creds *creds,
+ krb5_principal client,
+ krb5_get_init_creds_opt *options,
+ void *private_data)
+{
+ struct kerberos_kinit_passwords_ext_private *ep =
+ (struct kerberos_kinit_passwords_ext_private *)private_data;
+ TALLOC_CTX *frame = talloc_stackframe();
+ krb5_deltat start_time = 0;
+ krb5_init_creds_context ctx = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_error_code ret;
+#ifdef HAVE_KRB5_INIT_CREDS_STEP
+ DATA_BLOB rep_blob = { .length = 0, };
+#endif /* HAVE_KRB5_INIT_CREDS_STEP */
+
+ ZERO_STRUCTP(creds);
+
+ ret = krb5_init_creds_init(context,
+ client,
+ NULL, /* prompter */
+ NULL, /* prompter_data */
+ start_time,
+ options,
+ &ctx);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (ep->password != NULL) {
+ ret = krb5_init_creds_set_password(context, ctx, ep->password);
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ } else if (ep->nt_hash != NULL) {
+ const char *keytab_name = "MEMORY:kerberos_kinit_passwords_ext_cb";
+ krb5_keytab_entry entry = { .principal = client, };
+
+ ret = krb5_kt_resolve(context, keytab_name, &keytab);
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ ENCTYPE_ARCFOUR_HMAC,
+ ep->nt_hash->hash,
+ ARRAY_SIZE(ep->nt_hash->hash),
+ KRB5_KT_KEY(&entry));
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ krb5_kt_close(context, keytab);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = krb5_kt_add_entry(context, keytab, &entry);
+ krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry));
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ krb5_kt_close(context, keytab);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = krb5_init_creds_set_keytab(context, ctx, keytab);
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ krb5_kt_close(context, keytab);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ } else {
+ ret = EINVAL;
+ krb5_init_creds_free(context, ctx);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (ep->kc == NULL) {
+ /*
+ * Use the logic from the krb5 libraries
+ * to find the KDC
+ */
+ ret = krb5_init_creds_get(context, ctx);
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ if (keytab != NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ goto got_creds;
+ }
+
+#ifdef HAVE_KRB5_INIT_CREDS_STEP
+ while (true) {
+#if defined(HAVE_KRB5_REALM_TYPE)
+ /* Heimdal. */
+ krb5_realm krealm = NULL;
+#else
+ /* MIT */
+ krb5_data krealm = { .length = 0, };
+#endif
+ unsigned int flags = 0;
+ const char *realm = NULL;
+ krb5_data in = { .length = 0, };
+ krb5_data out = { .length = 0, };
+ DATA_BLOB req_blob = { .length = 0, };
+ NTSTATUS status;
+
+ in.data = (void *)rep_blob.data;
+ in.length = rep_blob.length;
+
+ flags = 0;
+ ret = krb5_init_creds_step(context,
+ ctx,
+ &in,
+ &out,
+ &krealm,
+ &flags);
+ data_blob_free(&rep_blob);
+ in = (krb5_data) { .length = 0, };
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ if (keytab != NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if ((flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) == 0) {
+ smb_krb5_free_data_contents(context, &out);
+#if defined(HAVE_KRB5_REALM_TYPE)
+ /* Heimdal. */
+ SAFE_FREE(krealm);
+#else
+ /* MIT */
+ smb_krb5_free_data_contents(context, &krealm);
+#endif
+ break;
+ }
+
+#if defined(HAVE_KRB5_REALM_TYPE)
+ /* Heimdal. */
+ realm = talloc_strdup(frame, krealm);
+ SAFE_FREE(krealm);
+#else
+ /* MIT */
+ realm = talloc_strndup(frame, krealm.data, krealm.length);
+ smb_krb5_free_data_contents(context, &krealm);
+#endif
+ if (realm == NULL) {
+ smb_krb5_free_data_contents(context, &out);
+ krb5_init_creds_free(context, ctx);
+ if (keytab != NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+
+ req_blob.data = (uint8_t *)out.data;
+ req_blob.length = out.length;
+
+ status = kerberos_transaction(ep->kc,
+ realm,
+ req_blob,
+ frame,
+ &rep_blob);
+ smb_krb5_free_data_contents(context, &out);
+ req_blob = (DATA_BLOB) { .length = 0, };
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = map_errno_from_nt_status(status);
+ krb5_init_creds_free(context, ctx);
+ if (keytab != NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+#else /* HAVE_KRB5_INIT_CREDS_STEP */
+#ifdef USING_EMBEDDED_HEIMDAL
+#error missing HAVE_KRB5_INIT_CREDS_STEP
+#endif /* USING_EMBEDDED_HEIMDAL */
+ /* Caller should already check! */
+ smb_panic("krb5_init_creds_step not available");
+#endif /* HAVE_KRB5_INIT_CREDS_STEP */
+
+got_creds:
+ ret = krb5_init_creds_get_creds(context, ctx, creds);
+ if (ret) {
+ krb5_init_creds_free(context, ctx);
+ if (keytab != NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ krb5_init_creds_free(context, ctx);
+ if (keytab != NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ simulate a kinit, putting the tgt in the given cache location.
+ cache_name == NULL is not allowed.
+ This tries all given passwords until we don't get
+ KDC_ERR_PREAUTH_FAILED.
+ If passwords[i] is NULL it falls back to nt_hashes[i]
+*/
+int kerberos_kinit_passwords_ext(const char *given_principal,
+ uint8_t num_passwords,
+ const char * const *passwords,
+ const struct samr_Password * const *nt_hashes,
+ uint8_t *used_idx,
+ const char *explicit_kdc,
+ const char *cache_name,
+ TALLOC_CTX *mem_ctx,
+ char **_canon_principal,
+ char **_canon_realm,
+ NTSTATUS *ntstatus)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct kerberos_kinit_passwords_ext_private ep = {
+ .explicit_kdc = explicit_kdc,
+ .timeout_msec = 15*1000,
+ };
+ NTSTATUS status;
+ krb5_error_code ret;
+ uint8_t i;
+ krb5_error_code first_ret = EINVAL;
+ NTSTATUS first_status = NT_STATUS_UNSUCCESSFUL;
+
+ if (num_passwords == 0) {
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+ if (num_passwords >= INT8_MAX) {
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+
+#ifndef HAVE_KRB5_INIT_CREDS_STEP
+ if (ep.explicit_kdc != NULL) {
+ DBG_ERR("Using explicit_kdc requires krb5_init_creds_step!\n");
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+#endif /* ! HAVE_KRB5_INIT_CREDS_STEP */
+
+ DBG_DEBUG("explicit_kdc[%s] given_principal[%s] "
+ "num_passwords[%u] cache_name[%s]\n",
+ ep.explicit_kdc,
+ given_principal,
+ num_passwords,
+ cache_name);
+
+ if (ep.explicit_kdc != NULL) {
+ status = kerberos_transaction_cache_create(ep.explicit_kdc,
+ ep.timeout_msec,
+ frame,
+ &ep.kc);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return map_errno_from_nt_status(status);
+ }
+ }
+
+ for (i = 0; i < num_passwords; i++) {
+ ep.password = passwords[i];
+ ep.nt_hash = nt_hashes[i];
+
+ ret = kerberos_kinit_generic_once(given_principal,
+ kerberos_kinit_passwords_ext_cb,
+ &ep,
+ 0, /* time_offset */
+ 0, /* expire_time */
+ 0, /* renew_till_time */
+ cache_name,
+ true, /* request_pac */
+ NULL, /* add_netbios_addr */
+ 0, /* renewable_time */
+ mem_ctx,
+ _canon_principal,
+ _canon_realm,
+ ntstatus);
+ if (ret == 0) {
+ *used_idx = i;
+ TALLOC_FREE(frame);
+ return 0;
+ }
+ if (i == 0) {
+ first_ret = ret;
+ first_status = *ntstatus;
+ }
+ if (ret != KRB5KDC_ERR_PREAUTH_FAILED) {
+ *used_idx = i;
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+
+ *used_idx = 0;
+ *ntstatus = first_status;
+ TALLOC_FREE(frame);
+ return first_ret;
+}
+
int ads_kdestroy(const char *cc_name)
{
krb5_error_code code;