diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c index ac5f339244c..75803500d31 100644 --- a/source3/libads/kerberos.c +++ b/source3/libads/kerberos.c @@ -26,10 +26,13 @@ #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" @@ -349,6 +352,583 @@ int kerberos_kinit_password_ext(const char *given_principal, 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; diff --git a/source3/libads/kerberos_proto.h b/source3/libads/kerberos_proto.h index cc8af444ceb..a96211c7289 100644 --- a/source3/libads/kerberos_proto.h +++ b/source3/libads/kerberos_proto.h @@ -33,6 +33,7 @@ #include "system/kerberos.h" struct PAC_DATA_CTR; +struct samr_Password; #define DEFAULT_KRB5_PORT 88 @@ -53,6 +54,17 @@ int kerberos_kinit_password_ext(const char *given_principal, char **_canon_principal, char **_canon_realm, NTSTATUS *ntstatus); +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); int ads_kdestroy(const char *cc_name); int kerberos_kinit_password(const char *principal, diff --git a/wscript_configure_embedded_heimdal b/wscript_configure_embedded_heimdal index 45f47721dec..c1488e5506e 100644 --- a/wscript_configure_embedded_heimdal +++ b/wscript_configure_embedded_heimdal @@ -13,3 +13,5 @@ conf.RECURSE('third_party/heimdal_build') # when this will be available also in # system libraries... conf.define('HAVE_CLIENT_GSS_C_CHANNEL_BOUND_FLAG', 1) + +conf.define('HAVE_KRB5_INIT_CREDS_STEP', 1) diff --git a/wscript_configure_system_heimdal b/wscript_configure_system_heimdal index b6ca9e98c7e..c320a76ea17 100644 --- a/wscript_configure_system_heimdal +++ b/wscript_configure_system_heimdal @@ -62,6 +62,7 @@ conf.define('USING_SYSTEM_HEIMDAL', 1) conf.CHECK_FUNCS(''' krb5_get_init_creds_opt_set_fast_ccache krb5_get_init_creds_opt_set_fast_flags + krb5_init_creds_step ''', lib='krb5', headers='krb5.h') diff --git a/wscript_configure_system_mitkrb5 b/wscript_configure_system_mitkrb5 index e1fac843f0c..58b9fb802d0 100644 --- a/wscript_configure_system_mitkrb5 +++ b/wscript_configure_system_mitkrb5 @@ -170,6 +170,7 @@ conf.CHECK_FUNCS(''' krb5_free_string krb5_get_init_creds_opt_set_fast_ccache krb5_get_init_creds_opt_set_fast_flags + krb5_init_creds_step ''', lib='krb5 k5crypto', headers='krb5.h')