summaryrefslogtreecommitdiffstats
path: root/source4/kdc/mit_samba.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--source4/kdc/mit_samba.c1066
1 files changed, 1066 insertions, 0 deletions
diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c
new file mode 100644
index 0000000..bfe9748
--- /dev/null
+++ b/source4/kdc/mit_samba.c
@@ -0,0 +1,1066 @@
+/*
+ MIT-Samba4 library
+
+ Copyright (c) 2010, Simo Sorce <idra@samba.org>
+ Copyright (c) 2014-2015 Guenther Deschner <gd@samba.org>
+ Copyright (c) 2014-2016 Andreas Schneider <asn@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define TEVENT_DEPRECATED 1
+
+#include "includes.h"
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "system/kerberos.h"
+#include <com_err.h>
+#include <kdb.h>
+#include <kadm5/kadm_err.h>
+#include "kdc/sdb.h"
+#include "kdc/sdb_kdb.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/pac_utils.h"
+#include "kdc/samba_kdc.h"
+#include "kdc/pac-glue.h"
+#include "kdc/db-glue.h"
+#include "auth/auth.h"
+#include "kdc/kpasswd_glue.h"
+#include "auth/auth_sam.h"
+
+#include "mit_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+void mit_samba_context_free(struct mit_samba_context *ctx)
+{
+ /* free heimdal's krb5_context */
+ if (ctx->context) {
+ krb5_free_context(ctx->context);
+ }
+
+ /* then free everything else */
+ talloc_free(ctx);
+}
+
+/*
+ * Implemant a callback to log to the MIT KDC log facility
+ *
+ * http://web.mit.edu/kerberos/krb5-devel/doc/plugindev/general.html#logging-from-kdc-and-kadmind-plugin-modules
+ */
+static void mit_samba_debug(void *private_ptr, int msg_level, const char *msg)
+{
+ int is_error = errno;
+
+ if (msg_level > 0) {
+ is_error = 0;
+ }
+
+ com_err("", is_error, "%s", msg);
+}
+
+int mit_samba_context_init(struct mit_samba_context **_ctx)
+{
+ NTSTATUS status;
+ struct mit_samba_context *ctx;
+ const char *s4_conf_file;
+ int ret;
+ struct samba_kdc_base_context base_ctx;
+
+ ctx = talloc_zero(NULL, struct mit_samba_context);
+ if (!ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ base_ctx.ev_ctx = tevent_context_init(ctx);
+ if (!base_ctx.ev_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_loop_allow_nesting(base_ctx.ev_ctx);
+ base_ctx.lp_ctx = loadparm_init_global(false);
+ if (!base_ctx.lp_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ debug_set_callback(NULL, mit_samba_debug);
+
+ /* init s4 configuration */
+ s4_conf_file = lpcfg_configfile(base_ctx.lp_ctx);
+ if (s4_conf_file != NULL) {
+ char *p = talloc_strdup(ctx, s4_conf_file);
+ if (p == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ lpcfg_load(base_ctx.lp_ctx, p);
+ TALLOC_FREE(p);
+ } else {
+ lpcfg_load_default(base_ctx.lp_ctx);
+ }
+
+ status = samba_kdc_setup_db_ctx(ctx, &base_ctx, &ctx->db_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* init heimdal's krb_context and log facilities */
+ ret = smb_krb5_init_context_basic(ctx,
+ ctx->db_ctx->lp_ctx,
+ &ctx->context);
+ if (ret) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret) {
+ mit_samba_context_free(ctx);
+ } else {
+ *_ctx = ctx;
+ }
+ return ret;
+}
+
+static krb5_error_code ks_is_tgs_principal(struct mit_samba_context *ctx,
+ krb5_const_principal principal)
+{
+ char *p;
+ int eq = -1;
+
+ p = smb_krb5_principal_get_comp_string(ctx, ctx->context, principal, 0);
+
+ eq = krb5_princ_size(ctx->context, principal) == 2 &&
+ (strcmp(p, KRB5_TGS_NAME) == 0);
+
+ talloc_free(p);
+
+ return eq;
+}
+
+int mit_samba_generate_salt(krb5_data *salt)
+{
+ if (salt == NULL) {
+ return EINVAL;
+ }
+
+ salt->length = 16;
+ salt->data = malloc(salt->length);
+ if (salt->data == NULL) {
+ return ENOMEM;
+ }
+
+ generate_random_buffer((uint8_t *)salt->data, salt->length);
+
+ return 0;
+}
+
+int mit_samba_generate_random_password(krb5_data *pwd)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *password;
+
+ if (pwd == NULL) {
+ return EINVAL;
+ }
+ pwd->length = 24;
+
+ tmp_ctx = talloc_named(NULL,
+ 0,
+ "mit_samba_create_principal_password context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ password = generate_random_password(tmp_ctx, pwd->length, pwd->length);
+ if (password == NULL) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ pwd->data = strdup(password);
+ talloc_free(tmp_ctx);
+ if (pwd->data == NULL) {
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+int mit_samba_get_principal(struct mit_samba_context *ctx,
+ krb5_const_principal principal,
+ unsigned int kflags,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ int ret;
+ uint32_t sflags = 0;
+ krb5_principal referral_principal = NULL;
+
+ kentry = calloc(1, sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+#if KRB5_KDB_API_VERSION >= 10
+ /*
+ * The MIT KDC code that wants the canonical name in all lookups, and
+ * takes care to canonicalize only when appropriate.
+ */
+ sflags |= SDB_F_FORCE_CANON;
+#endif
+
+#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
+ if (kflags & KRB5_KDB_FLAG_REFERRAL_OK) {
+ sflags |= SDB_F_CANON;
+ }
+
+ if (kflags & KRB5_KDB_FLAG_CLIENT) {
+ sflags |= SDB_F_GET_CLIENT;
+ sflags |= SDB_F_FOR_AS_REQ;
+ } else {
+ int equal = smb_krb5_principal_is_tgs(ctx->context, principal);
+ if (equal == -1) {
+ return ENOMEM;
+ }
+
+ if (equal) {
+ sflags |= SDB_F_GET_KRBTGT;
+ } else {
+ sflags |= SDB_F_GET_SERVER;
+ sflags |= SDB_F_FOR_TGS_REQ;
+ }
+ }
+#else /* KRB5_KDB_DAL_MAJOR_VERSION < 9 */
+ if (kflags & KRB5_KDB_FLAG_CANONICALIZE) {
+ sflags |= SDB_F_CANON;
+ }
+ if (kflags & (KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY |
+ KRB5_KDB_FLAG_INCLUDE_PAC)) {
+ /*
+ * KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY is equal to
+ * SDB_F_FOR_AS_REQ
+ *
+ * We use ANY to also allow AS_REQ for service principal names
+ * This is supported by Windows.
+ */
+ sflags |= SDB_F_GET_ANY|SDB_F_FOR_AS_REQ;
+ } else {
+ int equal = smb_krb5_principal_is_tgs(ctx->context, principal);
+ if (equal == -1) {
+ return ENOMEM;
+ }
+
+ if (equal) {
+ sflags |= SDB_F_GET_KRBTGT;
+ } else {
+ sflags |= SDB_F_GET_SERVER|SDB_F_FOR_TGS_REQ;
+ }
+ }
+#endif /* KRB5_KDB_DAL_MAJOR_VERSION */
+
+ /* always set this or the created_by data will not be populated by samba's
+ * backend and we will fail to parse the entry later */
+ sflags |= SDB_F_ADMIN_DATA;
+
+
+fetch_referral_principal:
+ ret = samba_kdc_fetch(ctx->context, ctx->db_ctx,
+ principal, sflags, 0, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_NOENTRY:
+ ret = KRB5_KDB_NOENTRY;
+ goto done;
+ case SDB_ERR_WRONG_REALM: {
+ char *dest_realm = NULL;
+ const char *our_realm = lpcfg_realm(ctx->db_ctx->lp_ctx);
+
+ if (sflags & SDB_F_FOR_AS_REQ) {
+ /*
+ * If this is a request for a TGT, we are done. The KDC
+ * will return the correct error to the client.
+ */
+ ret = 0;
+ break;
+ }
+
+ if (referral_principal != NULL) {
+ sdb_entry_free(&sentry);
+ ret = KRB5_KDB_NOENTRY;
+ goto done;
+ }
+
+ /*
+ * We get a TGS request
+ *
+ * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM
+ *
+ * to our DC for the realm
+ *
+ * ADDOM.SAMBA.EXAMPLE.COM
+ *
+ * We look up if we have and entry in the database and get an
+ * entry with the pricipal:
+ *
+ * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@SAMBA2008R2.EXAMPLE.COM
+ *
+ * and the error: SDB_ERR_WRONG_REALM.
+ *
+ * In the case of a TGS-REQ we need to return a referral ticket
+ * fo the next trust hop to the client. This ticket will have
+ * the following principal:
+ *
+ * krbtgt/SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM
+ *
+ * We just redo the lookup in the database with the referral
+ * principal and return success.
+ */
+ dest_realm = smb_krb5_principal_get_realm(
+ ctx, ctx->context, sentry.principal);
+ sdb_entry_free(&sentry);
+ if (dest_realm == NULL) {
+ ret = KRB5_KDB_NOENTRY;
+ goto done;
+ }
+
+ ret = smb_krb5_make_principal(ctx->context,
+ &referral_principal,
+ our_realm,
+ KRB5_TGS_NAME,
+ dest_realm,
+ NULL);
+ TALLOC_FREE(dest_realm);
+ if (ret != 0) {
+ goto done;
+ }
+
+ principal = referral_principal;
+ goto fetch_referral_principal;
+ }
+ case SDB_ERR_NOT_FOUND_HERE:
+ /* FIXME: RODC support */
+ default:
+ goto done;
+ }
+
+ ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry);
+
+ sdb_entry_free(&sentry);
+
+done:
+ krb5_free_principal(ctx->context, referral_principal);
+ referral_principal = NULL;
+
+ if (ret) {
+ free(kentry);
+ } else {
+ *_kentry = kentry;
+ }
+ return ret;
+}
+
+int mit_samba_get_firstkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ int ret;
+
+ kentry = malloc(sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ ret = samba_kdc_firstkey(ctx->context, ctx->db_ctx, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_NOENTRY:
+ free(kentry);
+ return KRB5_KDB_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ /* FIXME: RODC support */
+ default:
+ free(kentry);
+ return ret;
+ }
+
+ ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry);
+
+ sdb_entry_free(&sentry);
+
+ if (ret) {
+ free(kentry);
+ } else {
+ *_kentry = kentry;
+ }
+ return ret;
+}
+
+int mit_samba_get_nextkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ int ret;
+
+ kentry = malloc(sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ ret = samba_kdc_nextkey(ctx->context, ctx->db_ctx, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_NOENTRY:
+ free(kentry);
+ return KRB5_KDB_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ /* FIXME: RODC support */
+ default:
+ free(kentry);
+ return ret;
+ }
+
+ ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry);
+
+ sdb_entry_free(&sentry);
+
+ if (ret) {
+ free(kentry);
+ } else {
+ *_kentry = kentry;
+ }
+ return ret;
+}
+
+int mit_samba_get_pac(struct mit_samba_context *smb_ctx,
+ krb5_context context,
+ uint32_t flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_keyblock *replaced_reply_key,
+ krb5_pac *pac)
+{
+ TALLOC_CTX *tmp_ctx;
+ DATA_BLOB *logon_info_blob = NULL;
+ DATA_BLOB *upn_dns_info_blob = NULL;
+ DATA_BLOB *cred_ndr = NULL;
+ DATA_BLOB **cred_ndr_ptr = NULL;
+ DATA_BLOB cred_blob = data_blob_null;
+ DATA_BLOB *pcred_blob = NULL;
+ DATA_BLOB *pac_attrs_blob = NULL;
+ DATA_BLOB *requester_sid_blob = NULL;
+ NTSTATUS nt_status;
+ krb5_error_code code;
+ struct samba_kdc_entry *skdc_entry;
+ bool is_krbtgt;
+ enum samba_asserted_identity asserted_identity =
+ (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) ?
+ SAMBA_ASSERTED_IDENTITY_SERVICE :
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY;
+
+ skdc_entry = talloc_get_type_abort(client->e_data,
+ struct samba_kdc_entry);
+
+ tmp_ctx = talloc_named(smb_ctx,
+ 0,
+ "mit_samba_get_pac_data_blobs context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /* Check if we have a PREAUTH key */
+ if (replaced_reply_key != NULL) {
+ cred_ndr_ptr = &cred_ndr;
+ }
+
+ is_krbtgt = ks_is_tgs_principal(smb_ctx, server->princ);
+
+ nt_status = samba_kdc_get_pac_blobs(tmp_ctx,
+ skdc_entry,
+ asserted_identity,
+ &logon_info_blob,
+ cred_ndr_ptr,
+ &upn_dns_info_blob,
+ is_krbtgt ? &pac_attrs_blob : NULL,
+ PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY,
+ is_krbtgt ? &requester_sid_blob : NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ if (NT_STATUS_EQUAL(nt_status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ return ENOENT;
+ }
+ return EINVAL;
+ }
+
+ if (replaced_reply_key != NULL && cred_ndr != NULL) {
+ code = samba_kdc_encrypt_pac_credentials(context,
+ replaced_reply_key,
+ cred_ndr,
+ tmp_ctx,
+ &cred_blob);
+ if (code != 0) {
+ talloc_free(tmp_ctx);
+ return code;
+ }
+ pcred_blob = &cred_blob;
+ }
+
+ code = samba_make_krb5_pac(context,
+ logon_info_blob,
+ pcred_blob,
+ upn_dns_info_blob,
+ pac_attrs_blob,
+ requester_sid_blob,
+ NULL,
+ *pac);
+
+ talloc_free(tmp_ctx);
+ return code;
+}
+
+#if KRB5_KDB_DAL_MAJOR_VERSION < 9
+krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx,
+ krb5_context context,
+ int kdc_flags,
+ krb5_const_principal client_principal,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *krbtgt,
+ krb5_keyblock *krbtgt_keyblock,
+ krb5_pac *pac)
+{
+ TALLOC_CTX *tmp_ctx;
+ krb5_error_code code;
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ struct samba_kdc_entry *krbtgt_skdc_entry = NULL;
+ struct samba_kdc_entry *server_skdc_entry = NULL;
+ krb5_principal delegated_proxy_principal = NULL;
+ krb5_pac new_pac = NULL;
+ bool is_in_db = false;
+ bool is_untrusted = false;
+ uint32_t flags = SAMBA_KDC_FLAG_SKIP_PAC_BUFFER;
+
+ /* Create a memory context early so code can use talloc_stackframe() */
+ tmp_ctx = talloc_named(ctx, 0, "mit_samba_reget_pac context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (client != NULL) {
+ client_skdc_entry =
+ talloc_get_type_abort(client->e_data,
+ struct samba_kdc_entry);
+ }
+
+ if (server == NULL) {
+ code = EINVAL;
+ goto done;
+ }
+
+ server_skdc_entry =
+ talloc_get_type_abort(server->e_data,
+ struct samba_kdc_entry);
+
+ if (krbtgt == NULL) {
+ code = EINVAL;
+ goto done;
+ }
+ krbtgt_skdc_entry =
+ talloc_get_type_abort(krbtgt->e_data,
+ struct samba_kdc_entry);
+
+ code = samba_krbtgt_is_in_db(krbtgt_skdc_entry,
+ &is_in_db,
+ &is_untrusted);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (is_untrusted) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ }
+
+ if (is_in_db) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB;
+
+ }
+
+ if (kdc_flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) {
+ flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION;
+ }
+
+ if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
+ delegated_proxy_principal = discard_const(client_principal);
+ }
+
+ /* Build an updated PAC */
+ code = krb5_pac_init(context, &new_pac);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = samba_kdc_update_pac(tmp_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_skdc_entry,
+ server->princ,
+ server_skdc_entry,
+ krbtgt_skdc_entry,
+ delegated_proxy_principal,
+ *pac,
+ new_pac);
+ if (code != 0) {
+ krb5_pac_free(context, new_pac);
+ if (code == ENODATA) {
+ krb5_pac_free(context, *pac);
+ *pac = NULL;
+ code = 0;
+ }
+ goto done;
+ }
+
+ /* We now replace the pac */
+ krb5_pac_free(context, *pac);
+ *pac = new_pac;
+
+done:
+ talloc_free(tmp_ctx);
+ return code;
+}
+#else
+krb5_error_code mit_samba_update_pac(struct mit_samba_context *ctx,
+ krb5_context context,
+ int kdc_flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *krbtgt,
+ krb5_pac old_pac,
+ krb5_pac new_pac)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_error_code code;
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ struct samba_kdc_entry *server_skdc_entry = NULL;
+ struct samba_kdc_entry *krbtgt_skdc_entry = NULL;
+ bool is_in_db = false;
+ bool is_untrusted = false;
+ uint32_t flags = SAMBA_KDC_FLAG_SKIP_PAC_BUFFER;
+
+ /* Create a memory context early so code can use talloc_stackframe() */
+ tmp_ctx = talloc_named(ctx, 0, "mit_samba_update_pac context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (client != NULL) {
+ client_skdc_entry =
+ talloc_get_type_abort(client->e_data,
+ struct samba_kdc_entry);
+ }
+
+ if (krbtgt == NULL) {
+ code = EINVAL;
+ goto done;
+ }
+ krbtgt_skdc_entry =
+ talloc_get_type_abort(krbtgt->e_data,
+ struct samba_kdc_entry);
+
+ server_skdc_entry =
+ talloc_get_type_abort(server->e_data,
+ struct samba_kdc_entry);
+
+ /*
+ * If the krbtgt was generated by an RODC, and we are not that
+ * RODC, then we need to regenerate the PAC - we can't trust
+ * it, and confirm that the RODC was permitted to print this ticket
+ *
+ * Because of the samba_kdc_validate_pac_blob() step we can be
+ * sure that the record in 'client' or 'server' matches the SID in the
+ * original PAC.
+ */
+ code = samba_krbtgt_is_in_db(krbtgt_skdc_entry,
+ &is_in_db,
+ &is_untrusted);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (is_untrusted) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ }
+
+ if (is_in_db) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB;
+
+ }
+
+ if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
+ }
+
+ code = samba_kdc_update_pac(tmp_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_skdc_entry,
+ server->princ,
+ server_skdc_entry,
+ krbtgt_skdc_entry,
+ NULL,
+ old_pac,
+ new_pac);
+ if (code != 0) {
+ if (code == ENODATA) {
+ /*
+ * We can't tell the KDC to not issue a PAC. It will
+ * just return the newly allocated empty PAC.
+ */
+ code = 0;
+ }
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return code;
+}
+#endif
+
+/* provide header, function is exported but there are no public headers */
+
+krb5_error_code encode_krb5_padata_sequence(krb5_pa_data *const *rep, krb5_data **code);
+
+/* this function allocates 'data' using malloc.
+ * The caller is responsible for freeing it */
+static void samba_kdc_build_edata_reply(NTSTATUS nt_status, DATA_BLOB *e_data)
+{
+ krb5_error_code ret = 0;
+ krb5_pa_data pa, *ppa[2];
+ krb5_data *d = NULL;
+
+ if (!e_data)
+ return;
+
+ e_data->data = NULL;
+ e_data->length = 0;
+
+ pa.magic = KV5M_PA_DATA;
+ pa.pa_type = KRB5_PADATA_PW_SALT;
+ pa.length = 12;
+ pa.contents = malloc(pa.length);
+ if (!pa.contents) {
+ return;
+ }
+
+ SIVAL(pa.contents, 0, NT_STATUS_V(nt_status));
+ SIVAL(pa.contents, 4, 0);
+ SIVAL(pa.contents, 8, 1);
+
+ ppa[0] = &pa;
+ ppa[1] = NULL;
+
+ ret = encode_krb5_padata_sequence(ppa, &d);
+ free(pa.contents);
+ if (ret) {
+ return;
+ }
+
+ e_data->data = (uint8_t *)d->data;
+ e_data->length = d->length;
+
+ /* free d, not d->data - gd */
+ free(d);
+
+ return;
+}
+
+int mit_samba_check_client_access(struct mit_samba_context *ctx,
+ krb5_db_entry *client,
+ const char *client_name,
+ krb5_db_entry *server,
+ const char *server_name,
+ const char *netbios_name,
+ bool password_change,
+ DATA_BLOB *e_data)
+{
+ struct samba_kdc_entry *skdc_entry;
+ NTSTATUS nt_status;
+
+ skdc_entry = talloc_get_type(client->e_data, struct samba_kdc_entry);
+
+ nt_status = samba_kdc_check_client_access(skdc_entry,
+ client_name,
+ netbios_name,
+ password_change);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
+ return ENOMEM;
+ }
+
+ samba_kdc_build_edata_reply(nt_status, e_data);
+
+ return samba_kdc_map_policy_err(nt_status);
+ }
+
+ return 0;
+}
+
+int mit_samba_check_s4u2proxy(struct mit_samba_context *ctx,
+ const krb5_db_entry *server,
+ krb5_const_principal target_principal)
+{
+#if KRB5_KDB_DAL_MAJOR_VERSION < 9
+ return KRB5KDC_ERR_BADOPTION;
+#else
+ struct samba_kdc_entry *server_skdc_entry =
+ talloc_get_type_abort(server->e_data, struct samba_kdc_entry);
+ krb5_error_code code;
+
+ code = samba_kdc_check_s4u2proxy(ctx->context,
+ ctx->db_ctx,
+ server_skdc_entry,
+ target_principal);
+
+ return code;
+#endif
+}
+
+krb5_error_code mit_samba_check_allowed_to_delegate_from(
+ struct mit_samba_context *ctx,
+ krb5_const_principal client_principal,
+ krb5_const_principal server_principal,
+ krb5_pac header_pac,
+ const krb5_db_entry *proxy)
+{
+#if KRB5_KDB_DAL_MAJOR_VERSION < 8
+ return KRB5KDC_ERR_POLICY;
+#else
+ struct samba_kdc_entry *proxy_skdc_entry =
+ talloc_get_type_abort(proxy->e_data, struct samba_kdc_entry);
+ krb5_error_code code;
+
+ code = samba_kdc_check_s4u2proxy_rbcd(ctx->context,
+ ctx->db_ctx,
+ client_principal,
+ server_principal,
+ header_pac,
+ proxy_skdc_entry);
+
+ return code;
+#endif
+}
+
+static krb5_error_code mit_samba_change_pwd_error(krb5_context context,
+ NTSTATUS result,
+ enum samPwdChangeReason reject_reason,
+ struct samr_DomInfo1 *dominfo)
+{
+ krb5_error_code code = KADM5_PASS_Q_GENERIC;
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER)) {
+ code = KADM5_BAD_PRINCIPAL;
+ krb5_set_error_message(context,
+ code,
+ "No such user when changing password");
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
+ code = KADM5_PASS_Q_GENERIC;
+ krb5_set_error_message(context,
+ code,
+ "Not permitted to change password");
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) &&
+ dominfo != NULL) {
+ switch (reject_reason) {
+ case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT:
+ code = KADM5_PASS_Q_TOOSHORT;
+ krb5_set_error_message(context,
+ code,
+ "Password too short, password "
+ "must be at least %d characters "
+ "long.",
+ dominfo->min_password_length);
+ break;
+ case SAM_PWD_CHANGE_NOT_COMPLEX:
+ code = KADM5_PASS_Q_DICT;
+ krb5_set_error_message(context,
+ code,
+ "Password does not meet "
+ "complexity requirements");
+ break;
+ case SAM_PWD_CHANGE_PWD_IN_HISTORY:
+ code = KADM5_PASS_TOOSOON;
+ krb5_set_error_message(context,
+ code,
+ "Password is already in password "
+ "history. New password must not "
+ "match any of your %d previous "
+ "passwords.",
+ dominfo->password_history_length);
+ break;
+ default:
+ code = KADM5_PASS_Q_GENERIC;
+ krb5_set_error_message(context,
+ code,
+ "Password change rejected, "
+ "password changes may not be "
+ "permitted on this account, or "
+ "the minimum password age may "
+ "not have elapsed.");
+ break;
+ }
+ }
+
+ return code;
+}
+
+int mit_samba_kpasswd_change_password(struct mit_samba_context *ctx,
+ char *pwd,
+ krb5_db_entry *db_entry)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *tmp_ctx;
+ DATA_BLOB password;
+ enum samPwdChangeReason reject_reason;
+ struct samr_DomInfo1 *dominfo;
+ const char *error_string = NULL;
+ struct auth_user_info_dc *user_info_dc;
+ struct samba_kdc_entry *p =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+ krb5_error_code code = 0;
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(1,("mit_samba_kpasswd_change_password called with: %s\n", pwd));
+#endif
+
+ tmp_ctx = talloc_named(ctx, 0, "mit_samba_kpasswd_change_password");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ status = samba_kdc_get_user_info_from_db(p,
+ p->msg,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("samba_kdc_get_user_info_from_db failed: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ status = auth_generate_session_info(tmp_ctx,
+ ctx->db_ctx->lp_ctx,
+ ctx->db_ctx->samdb,
+ user_info_dc,
+ 0, /* session_info_flags */
+ &ctx->session_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("auth_generate_session_info failed: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ /* password is expected as UTF16 */
+
+ if (!convert_string_talloc(tmp_ctx, CH_UTF8, CH_UTF16,
+ pwd, strlen(pwd),
+ &password.data, &password.length)) {
+ DEBUG(1,("convert_string_talloc failed\n"));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ status = samdb_kpasswd_change_password(tmp_ctx,
+ ctx->db_ctx->lp_ctx,
+ ctx->db_ctx->ev_ctx,
+ ctx->session_info,
+ &password,
+ &reject_reason,
+ &dominfo,
+ &error_string,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("samdb_kpasswd_change_password failed: %s\n",
+ nt_errstr(status)));
+ code = KADM5_PASS_Q_GENERIC;
+ krb5_set_error_message(ctx->context, code, "%s", error_string);
+ goto out;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ code = mit_samba_change_pwd_error(ctx->context,
+ result,
+ reject_reason,
+ dominfo);
+ }
+
+out:
+ talloc_free(tmp_ctx);
+
+ return code;
+}
+
+void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry)
+{
+ /* struct netr_SendToSamBase *send_to_sam = NULL; */
+ struct samba_kdc_entry *p =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+ struct ldb_dn *domain_dn;
+
+ domain_dn = ldb_get_default_basedn(p->kdc_db_ctx->samdb);
+
+ authsam_logon_success_accounting(p->kdc_db_ctx->samdb,
+ p->msg,
+ domain_dn,
+ true,
+ NULL, NULL);
+ /* TODO: RODC support */
+}
+
+
+void mit_samba_update_bad_password_count(krb5_db_entry *db_entry)
+{
+ struct samba_kdc_entry *p =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+
+ authsam_update_bad_pwd_count(p->kdc_db_ctx->samdb,
+ p->msg,
+ ldb_get_default_basedn(p->kdc_db_ctx->samdb));
+}
+
+bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry)
+{
+ struct samba_kdc_entry *skdc_entry =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+
+ return samba_princ_needs_pac(skdc_entry);
+}