summaryrefslogtreecommitdiff
path: root/source4/kdc
diff options
context:
space:
mode:
Diffstat (limited to 'source4/kdc')
-rw-r--r--source4/kdc/config.m41
-rw-r--r--source4/kdc/config.mk26
-rw-r--r--source4/kdc/hdb-ldb.c1549
-rw-r--r--source4/kdc/kdc.c779
-rw-r--r--source4/kdc/kdc.h61
-rw-r--r--source4/kdc/kpasswdd.c614
-rw-r--r--source4/kdc/pac-glue.c307
7 files changed, 3337 insertions, 0 deletions
diff --git a/source4/kdc/config.m4 b/source4/kdc/config.m4
new file mode 100644
index 0000000000..409968e8b5
--- /dev/null
+++ b/source4/kdc/config.m4
@@ -0,0 +1 @@
+SMB_ENABLE(server_service_kdc, $HAVE_KRB5)
diff --git a/source4/kdc/config.mk b/source4/kdc/config.mk
new file mode 100644
index 0000000000..2c96e22cb3
--- /dev/null
+++ b/source4/kdc/config.mk
@@ -0,0 +1,26 @@
+# KDC server subsystem
+
+#######################
+# Start SUBSYSTEM KDC
+[MODULE::KDC]
+INIT_FUNCTION = server_service_kdc_init
+SUBSYSTEM = smbd
+PRIVATE_DEPENDENCIES = \
+ HEIMDAL_KDC HDB_LDB
+# End SUBSYSTEM KDC
+#######################
+
+KDC_OBJ_FILES = $(addprefix $(kdcsrcdir)/, kdc.o kpasswdd.o)
+
+#######################
+# Start SUBSYSTEM KDC
+[SUBSYSTEM::HDB_LDB]
+CFLAGS = -Iheimdal/kdc -Iheimdal/lib/hdb
+PRIVATE_DEPENDENCIES = \
+ LIBLDB auth_sam auth_sam_reply CREDENTIALS \
+ HEIMDAL_HDB
+# End SUBSYSTEM KDC
+#######################
+
+HDB_LDB_OBJ_FILES = $(addprefix $(kdcsrcdir)/, hdb-ldb.o pac-glue.o)
+$(eval $(call proto_header_template,$(kdcsrcdir)/pac_glue.h,$(HDB_LDB_OBJ_FILES:.o=.c)))
diff --git a/source4/kdc/hdb-ldb.c b/source4/kdc/hdb-ldb.c
new file mode 100644
index 0000000000..ef3a0bcb8a
--- /dev/null
+++ b/source4/kdc/hdb-ldb.c
@@ -0,0 +1,1549 @@
+/*
+ * Copyright (c) 1999-2001, 2003, PADL Software Pty Ltd.
+ * Copyright (c) 2004, Andrew Bartlett <abartlet@samba.org>.
+ * Copyright (c) 2004, Stefan Metzmacher <metze@samba.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "includes.h"
+#include "system/time.h"
+#include "dsdb/common/flags.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "auth/auth.h"
+#include "auth/credentials/credentials.h"
+#include "auth/auth_sam.h"
+#include "util/util_ldb.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "librpc/gen_ndr/lsa.h"
+#include "libcli/auth/libcli_auth.h"
+#include "param/param.h"
+#include "events/events.h"
+#include "kdc/kdc.h"
+#include "lib/crypto/md4.h"
+
+enum hdb_ldb_ent_type
+{ HDB_LDB_ENT_TYPE_CLIENT, HDB_LDB_ENT_TYPE_SERVER,
+ HDB_LDB_ENT_TYPE_KRBTGT, HDB_LDB_ENT_TYPE_TRUST, HDB_LDB_ENT_TYPE_ANY };
+
+enum trust_direction {
+ UNKNOWN = 0,
+ INBOUND = LSA_TRUST_DIRECTION_INBOUND,
+ OUTBOUND = LSA_TRUST_DIRECTION_OUTBOUND
+};
+
+static const char *realm_ref_attrs[] = {
+ "nCName",
+ "dnsRoot",
+ NULL
+};
+
+static const char *trust_attrs[] = {
+ "trustPartner",
+ "trustAuthIncoming",
+ "trustAuthOutgoing",
+ "whenCreated",
+ "msDS-SupportedEncryptionTypes",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ NULL
+};
+
+static KerberosTime ldb_msg_find_krb5time_ldap_time(struct ldb_message *msg, const char *attr, KerberosTime default_val)
+{
+ const char *tmp;
+ const char *gentime;
+ struct tm tm;
+
+ gentime = ldb_msg_find_attr_as_string(msg, attr, NULL);
+ if (!gentime)
+ return default_val;
+
+ tmp = strptime(gentime, "%Y%m%d%H%M%SZ", &tm);
+ if (tmp == NULL) {
+ return default_val;
+ }
+
+ return timegm(&tm);
+}
+
+static HDBFlags uf2HDBFlags(krb5_context context, int userAccountControl, enum hdb_ldb_ent_type ent_type)
+{
+ HDBFlags flags = int2HDBFlags(0);
+
+ /* we don't allow kadmin deletes */
+ flags.immutable = 1;
+
+ /* mark the principal as invalid to start with */
+ flags.invalid = 1;
+
+ flags.renewable = 1;
+
+ /* All accounts are servers, but this may be disabled again in the caller */
+ flags.server = 1;
+
+ /* Account types - clear the invalid bit if it turns out to be valid */
+ if (userAccountControl & UF_NORMAL_ACCOUNT) {
+ if (ent_type == HDB_LDB_ENT_TYPE_CLIENT || ent_type == HDB_LDB_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+
+ if (userAccountControl & UF_INTERDOMAIN_TRUST_ACCOUNT) {
+ if (ent_type == HDB_LDB_ENT_TYPE_CLIENT || ent_type == HDB_LDB_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+ if (userAccountControl & UF_WORKSTATION_TRUST_ACCOUNT) {
+ if (ent_type == HDB_LDB_ENT_TYPE_CLIENT || ent_type == HDB_LDB_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+ if (userAccountControl & UF_SERVER_TRUST_ACCOUNT) {
+ if (ent_type == HDB_LDB_ENT_TYPE_CLIENT || ent_type == HDB_LDB_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+
+ /* Not permitted to act as a client if disabled */
+ if (userAccountControl & UF_ACCOUNTDISABLE) {
+ flags.client = 0;
+ }
+ if (userAccountControl & UF_LOCKOUT) {
+ flags.invalid = 1;
+ }
+/*
+ if (userAccountControl & UF_PASSWORD_NOTREQD) {
+ flags.invalid = 1;
+ }
+*/
+/*
+ UF_PASSWORD_CANT_CHANGE and UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED are irrelevent
+*/
+ if (userAccountControl & UF_TEMP_DUPLICATE_ACCOUNT) {
+ flags.invalid = 1;
+ }
+
+/* UF_DONT_EXPIRE_PASSWD and UF_USE_DES_KEY_ONLY handled in LDB_message2entry() */
+
+/*
+ if (userAccountControl & UF_MNS_LOGON_ACCOUNT) {
+ flags.invalid = 1;
+ }
+*/
+ if (userAccountControl & UF_SMARTCARD_REQUIRED) {
+ flags.require_hwauth = 1;
+ }
+ if (userAccountControl & UF_TRUSTED_FOR_DELEGATION) {
+ flags.ok_as_delegate = 1;
+ }
+ if (!(userAccountControl & UF_NOT_DELEGATED)) {
+ flags.forwardable = 1;
+ flags.proxiable = 1;
+ }
+
+ if (userAccountControl & UF_DONT_REQUIRE_PREAUTH) {
+ flags.require_preauth = 0;
+ } else {
+ flags.require_preauth = 1;
+
+ }
+ return flags;
+}
+
+static int hdb_ldb_destrutor(struct hdb_ldb_private *private)
+{
+ hdb_entry_ex *entry_ex = private->entry_ex;
+ free_hdb_entry(&entry_ex->entry);
+ return 0;
+}
+
+static void hdb_ldb_free_entry(krb5_context context, hdb_entry_ex *entry_ex)
+{
+ talloc_free(entry_ex->ctx);
+}
+
+static krb5_error_code LDB_message2entry_keys(krb5_context context,
+ struct smb_iconv_convenience *iconv_convenience,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg,
+ unsigned int userAccountControl,
+ hdb_entry_ex *entry_ex)
+{
+ krb5_error_code ret = 0;
+ enum ndr_err_code ndr_err;
+ struct samr_Password *hash;
+ const struct ldb_val *sc_val;
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsPackage *scpk = NULL;
+ bool newer_keys = false;
+ struct package_PrimaryKerberosBlob _pkb;
+ struct package_PrimaryKerberosCtr3 *pkb3 = NULL;
+ struct package_PrimaryKerberosCtr4 *pkb4 = NULL;
+ uint32_t i;
+ uint32_t allocated_keys = 0;
+
+ entry_ex->entry.keys.val = NULL;
+ entry_ex->entry.keys.len = 0;
+
+ entry_ex->entry.kvno = ldb_msg_find_attr_as_int(msg, "msDS-KeyVersionNumber", 0);
+
+ /* Get keys from the db */
+
+ hash = samdb_result_hash(mem_ctx, msg, "unicodePwd");
+ sc_val = ldb_msg_find_ldb_val(msg, "supplementalCredentials");
+
+ /* unicodePwd for enctype 0x17 (23) if present */
+ if (hash) {
+ allocated_keys++;
+ }
+
+ /* supplementalCredentials if present */
+ if (sc_val) {
+ ndr_err = ndr_pull_struct_blob_all(sc_val, mem_ctx, iconv_convenience, &scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ dump_data(0, sc_val->data, sc_val->length);
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (scb.sub.signature != SUPPLEMENTAL_CREDENTIALS_SIGNATURE) {
+ NDR_PRINT_DEBUG(supplementalCredentialsBlob, &scb);
+ ret = EINVAL;
+ goto out;
+ }
+
+ for (i=0; i < scb.sub.num_packages; i++) {
+ if (strcmp("Primary:Kerberos-Newer-Keys", scb.sub.packages[i].name) == 0) {
+ scpk = &scb.sub.packages[i];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ continue;
+ }
+ newer_keys = true;
+ break;
+ } else if (strcmp("Primary:Kerberos", scb.sub.packages[i].name) == 0) {
+ scpk = &scb.sub.packages[i];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ }
+ /*
+ * we don't break here in hope to find
+ * a Kerberos-Newer-Keys package
+ */
+ }
+ }
+ }
+ /*
+ * Primary:Kerberos-Newer-Keys or Primary:Kerberos element
+ * of supplementalCredentials
+ */
+ if (scpk) {
+ DATA_BLOB blob;
+
+ blob = strhex_to_data_blob(scpk->data);
+ if (!blob.data) {
+ ret = ENOMEM;
+ goto out;
+ }
+ talloc_steal(mem_ctx, blob.data);
+
+ /* we cannot use ndr_pull_struct_blob_all() here, as w2k and w2k3 add padding bytes */
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, iconv_convenience, &_pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ krb5_set_error_string(context, "LDB_message2entry_keys: could not parse package_PrimaryKerberosBlob");
+ krb5_warnx(context, "LDB_message2entry_keys: could not parse package_PrimaryKerberosBlob");
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (newer_keys && _pkb.version != 4) {
+ krb5_set_error_string(context, "LDB_message2entry_keys: Primary:Kerberos-Newer-Keys not version 4");
+ krb5_warnx(context, "LDB_message2entry_keys: Primary:Kerberos-Newer-Keys not version 4");
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (!newer_keys && _pkb.version != 3) {
+ krb5_set_error_string(context, "LDB_message2entry_keys: could not parse Primary:Kerberos not version 3");
+ krb5_warnx(context, "LDB_message2entry_keys: could not parse Primary:Kerberos not version 3");
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (_pkb.version == 4) {
+ pkb4 = &_pkb.ctr.ctr4;
+ allocated_keys += pkb4->num_keys;
+ } else if (_pkb.version == 3) {
+ pkb3 = &_pkb.ctr.ctr3;
+ allocated_keys += pkb3->num_keys;
+ }
+ }
+
+ if (allocated_keys == 0) {
+ /* oh, no password. Apparently (comment in
+ * hdb-ldap.c) this violates the ASN.1, but this
+ * allows an entry with no keys (yet). */
+ return 0;
+ }
+
+ /* allocate space to decode into */
+ entry_ex->entry.keys.len = 0;
+ entry_ex->entry.keys.val = calloc(allocated_keys, sizeof(Key));
+ if (entry_ex->entry.keys.val == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (hash && !(userAccountControl & UF_USE_DES_KEY_ONLY)) {
+ Key key;
+
+ key.mkvno = 0;
+ key.salt = NULL; /* No salt for this enc type */
+
+ ret = krb5_keyblock_init(context,
+ ENCTYPE_ARCFOUR_HMAC_MD5,
+ hash->hash, sizeof(hash->hash),
+ &key.key);
+ if (ret) {
+ goto out;
+ }
+
+ entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+ entry_ex->entry.keys.len++;
+ }
+
+ if (pkb4) {
+ for (i=0; i < pkb4->num_keys; i++) {
+ bool use = true;
+ Key key;
+
+ if (!pkb4->keys[i].value) continue;
+
+ if (userAccountControl & UF_USE_DES_KEY_ONLY) {
+ switch (pkb4->keys[i].keytype) {
+ case ENCTYPE_DES_CBC_CRC:
+ case ENCTYPE_DES_CBC_MD5:
+ break;
+ default:
+ use = false;
+ break;
+ }
+ }
+
+ if (!use) continue;
+
+ key.mkvno = 0;
+ key.salt = NULL;
+
+ if (pkb4->salt.string) {
+ DATA_BLOB salt;
+
+ salt = data_blob_string_const(pkb4->salt.string);
+
+ key.salt = calloc(1, sizeof(*key.salt));
+ if (key.salt == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ key.salt->type = hdb_pw_salt;
+
+ ret = krb5_data_copy(&key.salt->salt, salt.data, salt.length);
+ if (ret) {
+ free(key.salt);
+ key.salt = NULL;
+ goto out;
+ }
+ }
+
+ /* TODO: maybe pass the iteration_count somehow... */
+
+ ret = krb5_keyblock_init(context,
+ pkb4->keys[i].keytype,
+ pkb4->keys[i].value->data,
+ pkb4->keys[i].value->length,
+ &key.key);
+ if (ret) {
+ if (key.salt) {
+ free_Salt(key.salt);
+ free(key.salt);
+ key.salt = NULL;
+ }
+ goto out;
+ }
+
+ entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+ entry_ex->entry.keys.len++;
+ }
+ } else if (pkb3) {
+ for (i=0; i < pkb3->num_keys; i++) {
+ bool use = true;
+ Key key;
+
+ if (!pkb3->keys[i].value) continue;
+
+ if (userAccountControl & UF_USE_DES_KEY_ONLY) {
+ switch (pkb3->keys[i].keytype) {
+ case ENCTYPE_DES_CBC_CRC:
+ case ENCTYPE_DES_CBC_MD5:
+ break;
+ default:
+ use = false;
+ break;
+ }
+ }
+
+ if (!use) continue;
+
+ key.mkvno = 0;
+ key.salt = NULL;
+
+ if (pkb3->salt.string) {
+ DATA_BLOB salt;
+
+ salt = data_blob_string_const(pkb3->salt.string);
+
+ key.salt = calloc(1, sizeof(*key.salt));
+ if (key.salt == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ key.salt->type = hdb_pw_salt;
+
+ ret = krb5_data_copy(&key.salt->salt, salt.data, salt.length);
+ if (ret) {
+ free(key.salt);
+ key.salt = NULL;
+ goto out;
+ }
+ }
+
+ ret = krb5_keyblock_init(context,
+ pkb3->keys[i].keytype,
+ pkb3->keys[i].value->data,
+ pkb3->keys[i].value->length,
+ &key.key);
+ if (ret) {
+ if (key.salt) {
+ free_Salt(key.salt);
+ free(key.salt);
+ key.salt = NULL;
+ }
+ goto out;
+ }
+
+ entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+ entry_ex->entry.keys.len++;
+ }
+ }
+
+out:
+ if (ret != 0) {
+ entry_ex->entry.keys.len = 0;
+ }
+ if (entry_ex->entry.keys.len == 0 && entry_ex->entry.keys.val) {
+ free(entry_ex->entry.keys.val);
+ entry_ex->entry.keys.val = NULL;
+ }
+ return ret;
+}
+
+/*
+ * Construct an hdb_entry from a directory entry.
+ */
+static krb5_error_code LDB_message2entry(krb5_context context, HDB *db,
+ TALLOC_CTX *mem_ctx, krb5_const_principal principal,
+ enum hdb_ldb_ent_type ent_type,
+ struct ldb_message *msg,
+ struct ldb_message *realm_ref_msg,
+ hdb_entry_ex *entry_ex)
+{
+ unsigned int userAccountControl;
+ int i;
+ krb5_error_code ret = 0;
+ krb5_boolean is_computer = FALSE;
+ const char *dnsdomain = ldb_msg_find_attr_as_string(realm_ref_msg, "dnsRoot", NULL);
+ char *realm = strupper_talloc(mem_ctx, dnsdomain);
+ struct loadparm_context *lp_ctx = ldb_get_opaque((struct ldb_context *)db->hdb_db, "loadparm");
+ struct ldb_dn *domain_dn = samdb_result_dn((struct ldb_context *)db->hdb_db,
+ mem_ctx,
+ realm_ref_msg,
+ "nCName",
+ ldb_dn_new(mem_ctx, (struct ldb_context *)db->hdb_db, NULL));
+
+ struct hdb_ldb_private *private;
+ NTTIME acct_expiry;
+
+ struct ldb_message_element *objectclasses;
+ struct ldb_val computer_val;
+ computer_val.data = discard_const_p(uint8_t,"computer");
+ computer_val.length = strlen((const char *)computer_val.data);
+
+ objectclasses = ldb_msg_find_element(msg, "objectClass");
+
+ if (objectclasses && ldb_msg_find_val(objectclasses, &computer_val)) {
+ is_computer = TRUE;
+ }
+
+ memset(entry_ex, 0, sizeof(*entry_ex));
+
+ if (!realm) {
+ krb5_set_error_string(context, "talloc_strdup: out of memory");
+ ret = ENOMEM;
+ goto out;
+ }
+
+ private = talloc(mem_ctx, struct hdb_ldb_private);
+ if (!private) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ private->entry_ex = entry_ex;
+ private->iconv_convenience = lp_iconv_convenience(lp_ctx);
+ private->netbios_name = lp_netbios_name(lp_ctx);
+
+ talloc_set_destructor(private, hdb_ldb_destrutor);
+
+ entry_ex->ctx = private;
+ entry_ex->free_entry = hdb_ldb_free_entry;
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
+
+
+ entry_ex->entry.principal = malloc(sizeof(*(entry_ex->entry.principal)));
+ if (ent_type == HDB_LDB_ENT_TYPE_ANY && principal == NULL) {
+ const char *samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL);
+ if (!samAccountName) {
+ krb5_set_error_string(context, "LDB_message2entry: no samAccountName present");
+ ret = ENOENT;
+ goto out;
+ }
+ samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL);
+ krb5_make_principal(context, &entry_ex->entry.principal, realm, samAccountName, NULL);
+ } else {
+ char *strdup_realm;
+ ret = copy_Principal(principal, entry_ex->entry.principal);
+ if (ret) {
+ krb5_clear_error_string(context);
+ goto out;
+ }
+
+ /* While we have copied the client principal, tests
+ * show that Win2k3 returns the 'corrected' realm, not
+ * the client-specified realm. This code attempts to
+ * replace the client principal's realm with the one
+ * we determine from our records */
+
+ /* this has to be with malloc() */
+ strdup_realm = strdup(realm);
+ if (!strdup_realm) {
+ ret = ENOMEM;
+ krb5_clear_error_string(context);
+ goto out;
+ }
+ free(*krb5_princ_realm(context, entry_ex->entry.principal));
+ krb5_princ_set_realm(context, entry_ex->entry.principal, &strdup_realm);
+ }
+
+ entry_ex->entry.flags = uf2HDBFlags(context, userAccountControl, ent_type);
+
+ if (ent_type == HDB_LDB_ENT_TYPE_KRBTGT) {
+ entry_ex->entry.flags.invalid = 0;
+ entry_ex->entry.flags.server = 1;
+ entry_ex->entry.flags.forwardable = 1;
+ entry_ex->entry.flags.ok_as_delegate = 1;
+ }
+
+ if (lp_parm_bool(lp_ctx, NULL, "kdc", "require spn for service", true)) {
+ if (!is_computer && !ldb_msg_find_attr_as_string(msg, "servicePrincipalName", NULL)) {
+ entry_ex->entry.flags.server = 0;
+ }
+ }
+
+ /* use 'whenCreated' */
+ entry_ex->entry.created_by.time = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0);
+ /* use '???' */
+ entry_ex->entry.created_by.principal = NULL;
+
+ entry_ex->entry.modified_by = (Event *) malloc(sizeof(Event));
+ if (entry_ex->entry.modified_by == NULL) {
+ krb5_set_error_string(context, "malloc: out of memory");
+ ret = ENOMEM;
+ goto out;
+ }
+
+ /* use 'whenChanged' */
+ entry_ex->entry.modified_by->time = ldb_msg_find_krb5time_ldap_time(msg, "whenChanged", 0);
+ /* use '???' */
+ entry_ex->entry.modified_by->principal = NULL;
+
+ entry_ex->entry.valid_start = NULL;
+
+ acct_expiry = samdb_result_account_expires(msg);
+ if (acct_expiry == 0x7FFFFFFFFFFFFFFFULL) {
+ entry_ex->entry.valid_end = NULL;
+ } else {
+ entry_ex->entry.valid_end = malloc(sizeof(*entry_ex->entry.valid_end));
+ if (entry_ex->entry.valid_end == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ *entry_ex->entry.valid_end = nt_time_to_unix(acct_expiry);
+ }
+
+ if (ent_type != HDB_LDB_ENT_TYPE_KRBTGT) {
+ NTTIME must_change_time
+ = samdb_result_force_password_change((struct ldb_context *)db->hdb_db, mem_ctx,
+ domain_dn, msg);
+ if (must_change_time == 0x7FFFFFFFFFFFFFFFULL) {
+ entry_ex->entry.pw_end = NULL;
+ } else {
+ entry_ex->entry.pw_end = malloc(sizeof(*entry_ex->entry.pw_end));
+ if (entry_ex->entry.pw_end == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ *entry_ex->entry.pw_end = nt_time_to_unix(must_change_time);
+ }
+ } else {
+ entry_ex->entry.pw_end = NULL;
+ }
+
+ entry_ex->entry.max_life = NULL;
+
+ entry_ex->entry.max_renew = NULL;
+
+ entry_ex->entry.generation = NULL;
+
+ /* Get keys from the db */
+ ret = LDB_message2entry_keys(context, private->iconv_convenience, private, msg, userAccountControl, entry_ex);
+ if (ret) {
+ /* Could be bougus data in the entry, or out of memory */
+ goto out;
+ }
+
+ entry_ex->entry.etypes = malloc(sizeof(*(entry_ex->entry.etypes)));
+ if (entry_ex->entry.etypes == NULL) {
+ krb5_clear_error_string(context);
+ ret = ENOMEM;
+ goto out;
+ }
+ entry_ex->entry.etypes->len = entry_ex->entry.keys.len;
+ entry_ex->entry.etypes->val = calloc(entry_ex->entry.etypes->len, sizeof(int));
+ if (entry_ex->entry.etypes->val == NULL) {
+ krb5_clear_error_string(context);
+ ret = ENOMEM;
+ goto out;
+ }
+ for (i=0; i < entry_ex->entry.etypes->len; i++) {
+ entry_ex->entry.etypes->val[i] = entry_ex->entry.keys.val[i].key.keytype;
+ }
+
+
+ private->msg = talloc_steal(private, msg);
+ private->realm_ref_msg = talloc_steal(private, realm_ref_msg);
+ private->samdb = (struct ldb_context *)db->hdb_db;
+
+out:
+ if (ret != 0) {
+ /* This doesn't free ent itself, that is for the eventual caller to do */
+ hdb_free_entry(context, entry_ex);
+ } else {
+ talloc_steal(db, entry_ex->ctx);
+ }
+
+ return ret;
+}
+
+/*
+ * Construct an hdb_entry from a directory entry.
+ */
+static krb5_error_code LDB_trust_message2entry(krb5_context context, HDB *db,
+ struct loadparm_context *lp_ctx,
+ TALLOC_CTX *mem_ctx, krb5_const_principal principal,
+ enum trust_direction direction,
+ struct ldb_message *msg,
+ hdb_entry_ex *entry_ex)
+{
+
+ const char *dnsdomain;
+ char *realm;
+ char *strdup_realm;
+ DATA_BLOB password_utf16;
+ struct samr_Password password_hash;
+ const struct ldb_val *password_val;
+ struct trustAuthInOutBlob password_blob;
+ struct hdb_ldb_private *private;
+
+ enum ndr_err_code ndr_err;
+ int i, ret, trust_direction_flags;
+
+ private = talloc(mem_ctx, struct hdb_ldb_private);
+ if (!private) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ private->entry_ex = entry_ex;
+ private->iconv_convenience = lp_iconv_convenience(lp_ctx);
+ private->netbios_name = lp_netbios_name(lp_ctx);
+
+ talloc_set_destructor(private, hdb_ldb_destrutor);
+
+ entry_ex->ctx = private;
+ entry_ex->free_entry = hdb_ldb_free_entry;
+
+ /* use 'whenCreated' */
+ entry_ex->entry.created_by.time = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0);
+ /* use '???' */
+ entry_ex->entry.created_by.principal = NULL;
+
+ entry_ex->entry.valid_start = NULL;
+
+ trust_direction_flags = ldb_msg_find_attr_as_int(msg, "trustDirection", 0);
+
+ if (direction == INBOUND) {
+ realm = strupper_talloc(mem_ctx, lp_realm(lp_ctx));
+ password_val = ldb_msg_find_ldb_val(msg, "trustAuthIncoming");
+
+ } else { /* OUTBOUND */
+ dnsdomain = ldb_msg_find_attr_as_string(msg, "trustPartner", NULL);
+ realm = strupper_talloc(mem_ctx, dnsdomain);
+ password_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
+ }
+
+ if (!password_val || !(trust_direction_flags & direction)) {
+ ret = ENOENT;
+ goto out;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(password_val, mem_ctx, private->iconv_convenience, &password_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ for (i=0; i < password_blob.count; i++) {
+ if (password_blob.current->array[i].AuthType == TRUST_AUTH_TYPE_CLEAR) {
+ password_utf16 = data_blob_const(password_blob.current->array[i].AuthInfo.clear.password,
+ password_blob.current->array[i].AuthInfo.clear.size);
+ /* In the future, generate all sorts of
+ * hashes, but for now we can't safely convert
+ * the random strings windows uses into
+ * utf8 */
+
+ /* but as it is utf16 already, we can get the NT password/arcfour-hmac-md5 key */
+ mdfour(password_hash.hash, password_utf16.data, password_utf16.length);
+ break;
+ } else if (password_blob.current->array[i].AuthType == TRUST_AUTH_TYPE_NT4OWF) {
+ password_hash = password_blob.current->array[i].AuthInfo.nt4owf.password;
+ break;
+ }
+ }
+ entry_ex->entry.keys.len = 0;
+ entry_ex->entry.keys.val = NULL;
+
+ if (i < password_blob.count) {
+ Key key;
+ /* Must have found a cleartext or MD4 password */
+ entry_ex->entry.keys.val = calloc(1, sizeof(Key));
+
+ key.mkvno = 0;
+ key.salt = NULL; /* No salt for this enc type */
+
+ if (entry_ex->entry.keys.val == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = krb5_keyblock_init(context,
+ ENCTYPE_ARCFOUR_HMAC_MD5,
+ password_hash.hash, sizeof(password_hash.hash),
+ &key.key);
+
+ entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+ entry_ex->entry.keys.len++;
+ }
+
+ ret = copy_Principal(principal, entry_ex->entry.principal);
+ if (ret) {
+ krb5_clear_error_string(context);
+ goto out;
+ }
+
+ /* While we have copied the client principal, tests
+ * show that Win2k3 returns the 'corrected' realm, not
+ * the client-specified realm. This code attempts to
+ * replace the client principal's realm with the one
+ * we determine from our records */
+
+ /* this has to be with malloc() */
+ strdup_realm = strdup(realm);
+ if (!strdup_realm) {
+ ret = ENOMEM;
+ krb5_clear_error_string(context);
+ goto out;
+ }
+ free(*krb5_princ_realm(context, entry_ex->entry.principal));
+ krb5_princ_set_realm(context, entry_ex->entry.principal, &strdup_realm);
+
+ entry_ex->entry.flags = int2HDBFlags(0);
+ entry_ex->entry.flags.immutable = 1;
+ entry_ex->entry.flags.invalid = 0;
+ entry_ex->entry.flags.server = 1;
+ entry_ex->entry.flags.require_preauth = 1;
+
+ entry_ex->entry.pw_end = NULL;
+
+ entry_ex->entry.max_life = NULL;
+
+ entry_ex->entry.max_renew = NULL;
+
+ entry_ex->entry.generation = NULL;
+
+ entry_ex->entry.etypes = malloc(sizeof(*(entry_ex->entry.etypes)));
+ if (entry_ex->entry.etypes == NULL) {
+ krb5_clear_error_string(context);
+ ret = ENOMEM;
+ goto out;
+ }
+ entry_ex->entry.etypes->len = entry_ex->entry.keys.len;
+ entry_ex->entry.etypes->val = calloc(entry_ex->entry.etypes->len, sizeof(int));
+ if (entry_ex->entry.etypes->val == NULL) {
+ krb5_clear_error_string(context);
+ ret = ENOMEM;
+ goto out;
+ }
+ for (i=0; i < entry_ex->entry.etypes->len; i++) {
+ entry_ex->entry.etypes->val[i] = entry_ex->entry.keys.val[i].key.keytype;
+ }
+
+
+ private->msg = talloc_steal(private, msg);
+ private->realm_ref_msg = NULL;
+ private->samdb = (struct ldb_context *)db->hdb_db;
+
+out:
+ if (ret != 0) {
+ /* This doesn't free ent itself, that is for the eventual caller to do */
+ hdb_free_entry(context, entry_ex);
+ } else {
+ talloc_steal(db, entry_ex->ctx);
+ }
+
+ return ret;
+
+}
+
+static krb5_error_code LDB_lookup_principal(krb5_context context, struct ldb_context *ldb_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ enum hdb_ldb_ent_type ent_type,
+ struct ldb_dn *realm_dn,
+ struct ldb_message ***pmsg)
+{
+ krb5_error_code ret;
+ int lret;
+ char *filter = NULL;
+ const char * const *princ_attrs = user_attrs;
+
+ char *short_princ;
+ char *short_princ_talloc;
+
+ struct ldb_result *res = NULL;
+
+ ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &short_princ);
+
+ if (ret != 0) {
+ krb5_set_error_string(context, "LDB_lookup_principal: could not parse principal");
+ krb5_warnx(context, "LDB_lookup_principal: could not parse principal");
+ return ret;
+ }
+
+ short_princ_talloc = talloc_strdup(mem_ctx, short_princ);
+ free(short_princ);
+ if (!short_princ_talloc) {
+ krb5_set_error_string(context, "LDB_lookup_principal: talloc_strdup() failed!");
+ return ENOMEM;
+ }
+
+ switch (ent_type) {
+ case HDB_LDB_ENT_TYPE_CLIENT:
+ case HDB_LDB_ENT_TYPE_TRUST:
+ case HDB_LDB_ENT_TYPE_ANY:
+ /* Can't happen */
+ return EINVAL;
+ case HDB_LDB_ENT_TYPE_KRBTGT:
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass=user)(samAccountName=%s))",
+ KRB5_TGS_NAME);
+ break;
+ case HDB_LDB_ENT_TYPE_SERVER:
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass=user)(samAccountName=%s))",
+ short_princ_talloc);
+ break;
+ }
+
+ if (!filter) {
+ krb5_set_error_string(context, "talloc_asprintf: out of memory");
+ return ENOMEM;
+ }
+
+ lret = ldb_search(ldb_ctx, realm_dn, LDB_SCOPE_SUBTREE, filter, princ_attrs, &res);
+
+ if (lret != LDB_SUCCESS) {
+ DEBUG(3, ("Failed to search for %s: %s\n", filter, ldb_errstring(ldb_ctx)));
+ return HDB_ERR_NOENTRY;
+ } else if (res->count == 0 || res->count > 1) {
+ DEBUG(3, ("Failed find a single entry for %s: got %d\n", filter, res->count));
+ talloc_free(res);
+ return HDB_ERR_NOENTRY;
+ }
+ talloc_steal(mem_ctx, res->msgs);
+ *pmsg = res->msgs;
+ talloc_free(res);
+ return 0;
+}
+
+static krb5_error_code LDB_lookup_trust(krb5_context context, struct ldb_context *ldb_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *realm,
+ struct ldb_dn *realm_dn,
+ struct ldb_message ***pmsg)
+{
+ int lret;
+ char *filter = NULL;
+ const char * const *attrs = trust_attrs;
+
+ struct ldb_result *res = NULL;
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass=trustedDomain)(|(flatname=%s)(trustPartner=%s)))", realm, realm);
+
+ if (!filter) {
+ krb5_set_error_string(context, "talloc_asprintf: out of memory");
+ return ENOMEM;
+ }
+
+ lret = ldb_search(ldb_ctx, ldb_get_default_basedn(ldb_ctx), LDB_SCOPE_SUBTREE, filter, attrs, &res);
+
+ if (lret != LDB_SUCCESS) {
+ DEBUG(3, ("Failed to search for %s: %s\n", filter, ldb_errstring(ldb_ctx)));
+ return HDB_ERR_NOENTRY;
+ } else if (res->count == 0 || res->count > 1) {
+ DEBUG(3, ("Failed find a single entry for %s: got %d\n", filter, res->count));
+ talloc_free(res);
+ return HDB_ERR_NOENTRY;
+ }
+ talloc_steal(mem_ctx, res->msgs);
+ *pmsg = res->msgs;
+ talloc_free(res);
+ return 0;
+}
+
+static krb5_error_code LDB_lookup_realm(krb5_context context, struct ldb_context *ldb_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *realm,
+ struct ldb_message ***pmsg)
+{
+ int ret;
+ struct ldb_result *cross_ref_res;
+ struct ldb_dn *partitions_basedn = samdb_partitions_dn(ldb_ctx, mem_ctx);
+
+ ret = ldb_search_exp_fmt(ldb_ctx, mem_ctx, &cross_ref_res,
+ partitions_basedn, LDB_SCOPE_SUBTREE, realm_ref_attrs,
+ "(&(&(|(&(dnsRoot=%s)(nETBIOSName=*))(nETBIOSName=%s))(objectclass=crossRef))(ncName=*))",
+ realm, realm);
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(3, ("Failed to search to lookup realm(%s): %s\n", realm, ldb_errstring(ldb_ctx)));
+ talloc_free(cross_ref_res);
+ return HDB_ERR_NOENTRY;
+ } else if (cross_ref_res->count == 0 || cross_ref_res->count > 1) {
+ DEBUG(3, ("Failed find a single entry for realm %s: got %d\n", realm, cross_ref_res->count));
+ talloc_free(cross_ref_res);
+ return HDB_ERR_NOENTRY;
+ }
+
+ if (pmsg) {
+ *pmsg = cross_ref_res->msgs;
+ talloc_steal(mem_ctx, cross_ref_res->msgs);
+ }
+ talloc_free(cross_ref_res);
+
+ return 0;
+}
+
+
+static krb5_error_code LDB_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ if (db->hdb_master_key_set) {
+ krb5_warnx(context, "LDB_open: use of a master key incompatible with LDB\n");
+ krb5_set_error_string(context, "LDB_open: use of a master key incompatible with LDB\n");
+ return HDB_ERR_NOENTRY;
+ }
+
+ return 0;
+}
+
+static krb5_error_code LDB_close(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+static krb5_error_code LDB_lock(krb5_context context, HDB *db, int operation)
+{
+ return 0;
+}
+
+static krb5_error_code LDB_unlock(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+static krb5_error_code LDB_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+static krb5_error_code LDB_fetch_client(krb5_context context, HDB *db,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ hdb_entry_ex *entry_ex) {
+ NTSTATUS nt_status;
+ char *principal_string;
+ krb5_error_code ret;
+ struct ldb_message **msg = NULL;
+ struct ldb_message **realm_ref_msg = NULL;
+
+ ret = krb5_unparse_name(context, principal, &principal_string);
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ nt_status = sam_get_results_principal((struct ldb_context *)db->hdb_db,
+ mem_ctx, principal_string,
+ &msg, &realm_ref_msg);
+ free(principal_string);
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER)) {
+ return HDB_ERR_NOENTRY;
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
+ return ENOMEM;
+ } else if (!NT_STATUS_IS_OK(nt_status)) {
+ return EINVAL;
+ }
+
+ ret = LDB_message2entry(context, db, mem_ctx,
+ principal, HDB_LDB_ENT_TYPE_CLIENT,
+ msg[0], realm_ref_msg[0], entry_ex);
+ return ret;
+}
+
+static krb5_error_code LDB_fetch_krbtgt(krb5_context context, HDB *db,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ hdb_entry_ex *entry_ex)
+{
+ krb5_error_code ret;
+ struct ldb_message **msg = NULL;
+ struct ldb_message **realm_ref_msg_1 = NULL;
+ struct ldb_message **realm_ref_msg_2 = NULL;
+ struct ldb_dn *realm_dn;
+ const char *realm;
+
+ krb5_principal alloc_principal = NULL;
+ if (principal->name.name_string.len != 2
+ || (strcmp(principal->name.name_string.val[0], KRB5_TGS_NAME) != 0)) {
+ /* Not a krbtgt */
+ return HDB_ERR_NOENTRY;
+ }
+
+ /* krbtgt case. Either us or a trusted realm */
+
+ if ((LDB_lookup_realm(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx, principal->realm, &realm_ref_msg_1) == 0)
+ && (LDB_lookup_realm(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx, principal->name.name_string.val[1], &realm_ref_msg_2) == 0)
+ && (ldb_dn_compare(realm_ref_msg_1[0]->dn, realm_ref_msg_1[0]->dn) == 0)) {
+ /* us */
+ /* Cludge, cludge cludge. If the realm part of krbtgt/realm,
+ * is in our db, then direct the caller at our primary
+ * krbtgt */
+
+ const char *dnsdomain = ldb_msg_find_attr_as_string(realm_ref_msg_1[0], "dnsRoot", NULL);
+ char *realm_fixed = strupper_talloc(mem_ctx, dnsdomain);
+ if (!realm_fixed) {
+ krb5_set_error_string(context, "strupper_talloc: out of memory");
+ return ENOMEM;
+ }
+
+ ret = krb5_copy_principal(context, principal, &alloc_principal);
+ if (ret) {
+ return ret;
+ }
+
+ free(alloc_principal->name.name_string.val[1]);
+ alloc_principal->name.name_string.val[1] = strdup(realm_fixed);
+ talloc_free(realm_fixed);
+ if (!alloc_principal->name.name_string.val[1]) {
+ krb5_set_error_string(context, "LDB_fetch: strdup() failed!");
+ return ENOMEM;
+ }
+ principal = alloc_principal;
+ realm_dn = samdb_result_dn((struct ldb_context *)db->hdb_db, mem_ctx, realm_ref_msg_1[0], "nCName", NULL);
+
+ ret = LDB_lookup_principal(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx,
+ principal, HDB_LDB_ENT_TYPE_KRBTGT, realm_dn, &msg);
+
+ if (ret != 0) {
+ krb5_warnx(context, "LDB_fetch: could not find principal in DB");
+ krb5_set_error_string(context, "LDB_fetch: could not find principal in DB");
+ return ret;
+ }
+
+ ret = LDB_message2entry(context, db, mem_ctx,
+ principal, HDB_LDB_ENT_TYPE_KRBTGT,
+ msg[0], realm_ref_msg_1[0], entry_ex);
+ if (ret != 0) {
+ krb5_warnx(context, "LDB_fetch: message2entry failed");
+ }
+ return ret;
+
+ } else {
+ enum trust_direction direction = UNKNOWN;
+
+ struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(db->hdb_db, "loadparm"), struct loadparm_context);
+ /* Either an inbound or outbound trust */
+
+ if (strcasecmp(lp_realm(lp_ctx), principal->realm) == 0) {
+ /* look for inbound trust */
+ direction = INBOUND;
+ realm = principal->name.name_string.val[1];
+ }
+
+ if (strcasecmp(lp_realm(lp_ctx), principal->name.name_string.val[1]) == 0) {
+ /* look for outbound trust */
+ direction = OUTBOUND;
+ realm = principal->realm;
+ }
+
+ /* Trusted domains are under CN=system */
+
+ ret = LDB_lookup_trust(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx,
+ realm, realm_dn, &msg);
+
+ if (ret != 0) {
+ krb5_warnx(context, "LDB_fetch: could not find principal in DB");
+ krb5_set_error_string(context, "LDB_fetch: could not find principal in DB");
+ return ret;
+ }
+
+ ret = LDB_trust_message2entry(context, db, lp_ctx, mem_ctx,
+ principal, direction,
+ msg[0], entry_ex);
+ if (ret != 0) {
+ krb5_warnx(context, "LDB_fetch: message2entry failed");
+ }
+ return ret;
+
+
+ /* we should lookup trusted domains */
+ return HDB_ERR_NOENTRY;
+ }
+
+}
+
+static krb5_error_code LDB_fetch_server(krb5_context context, HDB *db,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ hdb_entry_ex *entry_ex)
+{
+ krb5_error_code ret;
+ const char *realm;
+ struct ldb_message **msg = NULL;
+ struct ldb_message **realm_ref_msg = NULL;
+ struct ldb_dn *partitions_basedn = samdb_partitions_dn(db->hdb_db, mem_ctx);
+ if (principal->name.name_string.len >= 2) {
+ /* 'normal server' case */
+ int ldb_ret;
+ NTSTATUS nt_status;
+ struct ldb_dn *user_dn, *domain_dn;
+ char *principal_string;
+
+ ret = krb5_unparse_name_flags(context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM,
+ &principal_string);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* At this point we may find the host is known to be
+ * in a different realm, so we should generate a
+ * referral instead */
+ nt_status = crack_service_principal_name((struct ldb_context *)db->hdb_db,
+ mem_ctx, principal_string,
+ &user_dn, &domain_dn);
+ free(principal_string);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return HDB_ERR_NOENTRY;
+ }
+
+ ldb_ret = gendb_search_dn((struct ldb_context *)db->hdb_db,
+ mem_ctx, user_dn, &msg, user_attrs);
+
+ if (ldb_ret != 1) {
+ return HDB_ERR_NOENTRY;
+ }
+
+ ldb_ret = gendb_search((struct ldb_context *)db->hdb_db,
+ mem_ctx, partitions_basedn, &realm_ref_msg, realm_ref_attrs,
+ "ncName=%s", ldb_dn_get_linearized(domain_dn));
+
+ if (ldb_ret != 1) {
+ return HDB_ERR_NOENTRY;
+ }
+
+ } else {
+ struct ldb_dn *realm_dn;
+ /* server as client principal case, but we must not lookup userPrincipalNames */
+
+ realm = krb5_principal_get_realm(context, principal);
+
+ ret = LDB_lookup_realm(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx, realm, &realm_ref_msg);
+ if (ret != 0) {
+ return HDB_ERR_NOENTRY;
+ }
+
+ realm_dn = samdb_result_dn((struct ldb_context *)db->hdb_db, mem_ctx, realm_ref_msg[0], "nCName", NULL);
+
+ ret = LDB_lookup_principal(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx,
+ principal, HDB_LDB_ENT_TYPE_SERVER, realm_dn, &msg);
+
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = LDB_message2entry(context, db, mem_ctx,
+ principal, HDB_LDB_ENT_TYPE_SERVER,
+ msg[0], realm_ref_msg[0], entry_ex);
+ if (ret != 0) {
+ krb5_warnx(context, "LDB_fetch: message2entry failed");
+ }
+
+ return ret;
+}
+
+static krb5_error_code LDB_fetch(krb5_context context, HDB *db,
+ krb5_const_principal principal,
+ unsigned flags,
+ hdb_entry_ex *entry_ex)
+{
+ krb5_error_code ret = HDB_ERR_NOENTRY;
+
+ TALLOC_CTX *mem_ctx = talloc_named(db, 0, "LDB_fetch context");
+
+ if (!mem_ctx) {
+ krb5_set_error_string(context, "LDB_fetch: talloc_named() failed!");
+ return ENOMEM;
+ }
+
+ if (flags & HDB_F_GET_CLIENT) {
+ ret = LDB_fetch_client(context, db, mem_ctx, principal, flags, entry_ex);
+ if (ret != HDB_ERR_NOENTRY) goto done;
+ }
+ if (flags & HDB_F_GET_SERVER) {
+ /* krbtgt fits into this situation for trusted realms, and for resolving different versions of our own realm name */
+ ret = LDB_fetch_krbtgt(context, db, mem_ctx, principal, flags, entry_ex);
+ if (ret != HDB_ERR_NOENTRY) goto done;
+
+ /* We return 'no entry' if it does not start with krbtgt/, so move to the common case quickly */
+ ret = LDB_fetch_server(context, db, mem_ctx, principal, flags, entry_ex);
+ if (ret != HDB_ERR_NOENTRY) goto done;
+ }
+ if (flags & HDB_F_GET_KRBTGT) {
+ ret = LDB_fetch_krbtgt(context, db, mem_ctx, principal, flags, entry_ex);
+ if (ret != HDB_ERR_NOENTRY) goto done;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static krb5_error_code LDB_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+static krb5_error_code LDB_remove(krb5_context context, HDB *db, krb5_const_principal principal)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+struct hdb_ldb_seq {
+ struct ldb_context *ctx;
+ int index;
+ int count;
+ struct ldb_message **msgs;
+ struct ldb_message **realm_ref_msgs;
+};
+
+static krb5_error_code LDB_seq(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
+{
+ krb5_error_code ret;
+ struct hdb_ldb_seq *priv = (struct hdb_ldb_seq *)db->hdb_dbc;
+ TALLOC_CTX *mem_ctx;
+ hdb_entry_ex entry_ex;
+ memset(&entry_ex, '\0', sizeof(entry_ex));
+
+ if (!priv) {
+ return HDB_ERR_NOENTRY;
+ }
+
+ mem_ctx = talloc_named(priv, 0, "LDB_seq context");
+
+ if (!mem_ctx) {
+ krb5_set_error_string(context, "LDB_seq: talloc_named() failed!");
+ return ENOMEM;
+ }
+
+ if (priv->index < priv->count) {
+ ret = LDB_message2entry(context, db, mem_ctx,
+ NULL, HDB_LDB_ENT_TYPE_ANY,
+ priv->msgs[priv->index++],
+ priv->realm_ref_msgs[0], entry);
+ } else {
+ ret = HDB_ERR_NOENTRY;
+ }
+
+ if (ret != 0) {
+ talloc_free(priv);
+ db->hdb_dbc = NULL;
+ } else {
+ talloc_free(mem_ctx);
+ }
+
+ return ret;
+}
+
+static krb5_error_code LDB_firstkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry_ex *entry)
+{
+ struct ldb_context *ldb_ctx = (struct ldb_context *)db->hdb_db;
+ struct hdb_ldb_seq *priv = (struct hdb_ldb_seq *)db->hdb_dbc;
+ char *realm;
+ struct ldb_dn *realm_dn = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_message **realm_ref_msgs = NULL;
+ krb5_error_code ret;
+ TALLOC_CTX *mem_ctx;
+ int lret;
+
+ if (priv) {
+ talloc_free(priv);
+ db->hdb_dbc = NULL;
+ }
+
+ priv = (struct hdb_ldb_seq *) talloc(db, struct hdb_ldb_seq);
+ if (!priv) {
+ krb5_set_error_string(context, "talloc: out of memory");
+ return ENOMEM;
+ }
+
+ priv->ctx = ldb_ctx;
+ priv->index = 0;
+ priv->msgs = NULL;
+ priv->realm_ref_msgs = NULL;
+ priv->count = 0;
+
+ mem_ctx = talloc_named(priv, 0, "LDB_firstkey context");
+
+ if (!mem_ctx) {
+ krb5_set_error_string(context, "LDB_firstkey: talloc_named() failed!");
+ return ENOMEM;
+ }
+
+ ret = krb5_get_default_realm(context, &realm);
+ if (ret != 0) {
+ talloc_free(priv);
+ return ret;
+ }
+
+ ret = LDB_lookup_realm(context, (struct ldb_context *)db->hdb_db,
+ mem_ctx, realm, &realm_ref_msgs);
+
+ free(realm);
+
+ if (ret != 0) {
+ talloc_free(priv);
+ krb5_warnx(context, "LDB_firstkey: could not find realm\n");
+ return HDB_ERR_NOENTRY;
+ }
+
+ realm_dn = samdb_result_dn((struct ldb_context *)db->hdb_db, mem_ctx, realm_ref_msgs[0], "nCName", NULL);
+
+ priv->realm_ref_msgs = talloc_steal(priv, realm_ref_msgs);
+
+ lret = ldb_search(ldb_ctx, realm_dn,
+ LDB_SCOPE_SUBTREE, "(objectClass=user)",
+ user_attrs, &res);
+
+ if (lret != LDB_SUCCESS) {
+ talloc_free(priv);
+ return HDB_ERR_NOENTRY;
+ }
+
+ priv->count = res->count;
+ priv->msgs = talloc_steal(priv, res->msgs);
+ talloc_free(res);
+
+ db->hdb_dbc = priv;
+
+ ret = LDB_seq(context, db, flags, entry);
+
+ if (ret != 0) {
+ talloc_free(priv);
+ db->hdb_dbc = NULL;
+ } else {
+ talloc_free(mem_ctx);
+ }
+ return ret;
+}
+
+static krb5_error_code LDB_nextkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry_ex *entry)
+{
+ return LDB_seq(context, db, flags, entry);
+}
+
+static krb5_error_code LDB_destroy(krb5_context context, HDB *db)
+{
+ talloc_free(db);
+ return 0;
+}
+
+/* This interface is to be called by the KDC, which is expecting Samba
+ * calling conventions. It is also called by a wrapper
+ * (hdb_ldb_create) from the kpasswdd -> krb5 -> keytab_hdb -> hdb
+ * code */
+
+NTSTATUS kdc_hdb_ldb_create(TALLOC_CTX *mem_ctx,
+ struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ krb5_context context, struct HDB **db, const char *arg)
+{
+ NTSTATUS nt_status;
+ struct auth_session_info *session_info;
+ *db = talloc(mem_ctx, HDB);
+ if (!*db) {
+ krb5_set_error_string(context, "malloc: out of memory");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_db = NULL;
+
+ nt_status = auth_system_session_info(*db, lp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ /* The idea here is very simple. Using Kerberos to
+ * authenticate the KDC to the LDAP server is higly likely to
+ * be circular.
+ *
+ * In future we may set this up to use EXERNAL and SSL
+ * certificates, for now it will almost certainly be NTLMSSP
+ */
+
+ cli_credentials_set_kerberos_state(session_info->credentials,
+ CRED_DONT_USE_KERBEROS);
+
+ /* Setup the link to LDB */
+ (*db)->hdb_db = samdb_connect(*db, ev_ctx, lp_ctx, session_info);
+ if ((*db)->hdb_db == NULL) {
+ DEBUG(1, ("hdb_ldb_create: Cannot open samdb for KDC backend!"));
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ (*db)->hdb_dbc = NULL;
+ (*db)->hdb_open = LDB_open;
+ (*db)->hdb_close = LDB_close;
+ (*db)->hdb_fetch = LDB_fetch;
+ (*db)->hdb_store = LDB_store;
+ (*db)->hdb_remove = LDB_remove;
+ (*db)->hdb_firstkey = LDB_firstkey;
+ (*db)->hdb_nextkey = LDB_nextkey;
+ (*db)->hdb_lock = LDB_lock;
+ (*db)->hdb_unlock = LDB_unlock;
+ (*db)->hdb_rename = LDB_rename;
+ /* we don't implement these, as we are not a lockable database */
+ (*db)->hdb__get = NULL;
+ (*db)->hdb__put = NULL;
+ /* kadmin should not be used for deletes - use other tools instead */
+ (*db)->hdb__del = NULL;
+ (*db)->hdb_destroy = LDB_destroy;
+
+ return NT_STATUS_OK;
+}
+
+krb5_error_code hdb_ldb_create(krb5_context context, struct HDB **db, const char *arg)
+{
+ NTSTATUS nt_status;
+ /* The global kdc_mem_ctx and kdc_lp_ctx, Disgusting, ugly hack, but it means one less private hook */
+ nt_status = kdc_hdb_ldb_create(kdc_mem_ctx, event_context_find(kdc_mem_ctx), kdc_lp_ctx,
+ context, db, arg);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ return 0;
+ }
+ return EINVAL;
+}
diff --git a/source4/kdc/kdc.c b/source4/kdc/kdc.c
new file mode 100644
index 0000000000..b7009b030f
--- /dev/null
+++ b/source4/kdc/kdc.c
@@ -0,0 +1,779 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC Server startup
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2008
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher 2005
+
+ 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/>.
+*/
+
+#include "includes.h"
+#include "smbd/service_task.h"
+#include "smbd/service.h"
+#include "smbd/service_stream.h"
+#include "smbd/process_model.h"
+#include "lib/events/events.h"
+#include "lib/socket/socket.h"
+#include "system/network.h"
+#include "lib/util/dlinklist.h"
+#include "lib/messaging/irpc.h"
+#include "lib/stream/packet.h"
+#include "librpc/gen_ndr/samr.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "lib/socket/netif.h"
+#include "param/param.h"
+#include "kdc/kdc.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+
+
+/* Disgusting hack to get a mem_ctx and lp_ctx into the hdb plugin, when
+ * used as a keytab */
+TALLOC_CTX *kdc_mem_ctx;
+struct loadparm_context *kdc_lp_ctx;
+
+/* hold all the info needed to send a reply */
+struct kdc_reply {
+ struct kdc_reply *next, *prev;
+ struct socket_address *dest;
+ DATA_BLOB packet;
+};
+
+typedef bool (*kdc_process_fn_t)(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ struct socket_address *peer_addr,
+ struct socket_address *my_addr,
+ int datagram);
+
+/* hold information about one kdc socket */
+struct kdc_socket {
+ struct socket_context *sock;
+ struct kdc_server *kdc;
+ struct fd_event *fde;
+
+ /* a queue of outgoing replies that have been deferred */
+ struct kdc_reply *send_queue;
+
+ kdc_process_fn_t process;
+};
+/*
+ state of an open tcp connection
+*/
+struct kdc_tcp_connection {
+ /* stream connection we belong to */
+ struct stream_connection *conn;
+
+ /* the kdc_server the connection belongs to */
+ struct kdc_server *kdc;
+
+ struct packet_context *packet;
+
+ kdc_process_fn_t process;
+};
+
+/*
+ handle fd send events on a KDC socket
+*/
+static void kdc_send_handler(struct kdc_socket *kdc_socket)
+{
+ while (kdc_socket->send_queue) {
+ struct kdc_reply *rep = kdc_socket->send_queue;
+ NTSTATUS status;
+ size_t sendlen;
+
+ status = socket_sendto(kdc_socket->sock, &rep->packet, &sendlen,
+ rep->dest);
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ break;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_BUFFER_SIZE)) {
+ /* Replace with a krb err, response to big */
+ }
+
+ DLIST_REMOVE(kdc_socket->send_queue, rep);
+ talloc_free(rep);
+ }
+
+ if (kdc_socket->send_queue == NULL) {
+ EVENT_FD_NOT_WRITEABLE(kdc_socket->fde);
+ }
+}
+
+
+/*
+ handle fd recv events on a KDC socket
+*/
+static void kdc_recv_handler(struct kdc_socket *kdc_socket)
+{
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_new(kdc_socket);
+ DATA_BLOB blob;
+ struct kdc_reply *rep;
+ DATA_BLOB reply;
+ size_t nread, dsize;
+ struct socket_address *src;
+ struct socket_address *my_addr;
+ int ret;
+
+ status = socket_pending(kdc_socket->sock, &dsize);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ blob = data_blob_talloc(tmp_ctx, NULL, dsize);
+ if (blob.data == NULL) {
+ /* hope this is a temporary low memory condition */
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ status = socket_recvfrom(kdc_socket->sock, blob.data, blob.length, &nread,
+ tmp_ctx, &src);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+ blob.length = nread;
+
+ DEBUG(10,("Received krb5 UDP packet of length %lu from %s:%u\n",
+ (long)blob.length, src->addr, (uint16_t)src->port));
+
+ my_addr = socket_get_my_addr(kdc_socket->sock, tmp_ctx);
+ if (!my_addr) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+
+ /* Call krb5 */
+ ret = kdc_socket->process(kdc_socket->kdc,
+ tmp_ctx,
+ &blob,
+ &reply,
+ src, my_addr,
+ 1 /* Datagram */);
+ if (!ret) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ /* queue a pending reply */
+ rep = talloc(kdc_socket, struct kdc_reply);
+ if (rep == NULL) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+ rep->dest = talloc_steal(rep, src);
+ rep->packet = reply;
+ talloc_steal(rep, reply.data);
+
+ if (rep->packet.data == NULL) {
+ talloc_free(rep);
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ DLIST_ADD_END(kdc_socket->send_queue, rep, struct kdc_reply *);
+ EVENT_FD_WRITEABLE(kdc_socket->fde);
+ talloc_free(tmp_ctx);
+}
+
+/*
+ handle fd events on a KDC socket
+*/
+static void kdc_socket_handler(struct event_context *ev, struct fd_event *fde,
+ uint16_t flags, void *private)
+{
+ struct kdc_socket *kdc_socket = talloc_get_type(private, struct kdc_socket);
+ if (flags & EVENT_FD_WRITE) {
+ kdc_send_handler(kdc_socket);
+ }
+ if (flags & EVENT_FD_READ) {
+ kdc_recv_handler(kdc_socket);
+ }
+}
+
+static void kdc_tcp_terminate_connection(struct kdc_tcp_connection *kdcconn, const char *reason)
+{
+ stream_terminate_connection(kdcconn->conn, reason);
+}
+
+/*
+ receive a full packet on a KDC connection
+*/
+static NTSTATUS kdc_tcp_recv(void *private, DATA_BLOB blob)
+{
+ struct kdc_tcp_connection *kdcconn = talloc_get_type(private,
+ struct kdc_tcp_connection);
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *tmp_ctx = talloc_new(kdcconn);
+ int ret;
+ DATA_BLOB input, reply;
+ struct socket_address *src_addr;
+ struct socket_address *my_addr;
+
+ talloc_steal(tmp_ctx, blob.data);
+
+ src_addr = socket_get_peer_addr(kdcconn->conn->socket, tmp_ctx);
+ if (!src_addr) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ my_addr = socket_get_my_addr(kdcconn->conn->socket, tmp_ctx);
+ if (!my_addr) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Call krb5 */
+ input = data_blob_const(blob.data + 4, blob.length - 4);
+
+ ret = kdcconn->process(kdcconn->kdc,
+ tmp_ctx,
+ &input,
+ &reply,
+ src_addr,
+ my_addr,
+ 0 /* Not datagram */);
+ if (!ret) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* and now encode the reply */
+ blob = data_blob_talloc(kdcconn, NULL, reply.length + 4);
+ if (!blob.data) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ RSIVAL(blob.data, 0, reply.length);
+ memcpy(blob.data + 4, reply.data, reply.length);
+
+ status = packet_send(kdcconn->packet, blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ /* the call isn't needed any more */
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/*
+ receive some data on a KDC connection
+*/
+static void kdc_tcp_recv_handler(struct stream_connection *conn, uint16_t flags)
+{
+ struct kdc_tcp_connection *kdcconn = talloc_get_type(conn->private,
+ struct kdc_tcp_connection);
+ packet_recv(kdcconn->packet);
+}
+
+/*
+ called on a tcp recv error
+*/
+static void kdc_tcp_recv_error(void *private, NTSTATUS status)
+{
+ struct kdc_tcp_connection *kdcconn = talloc_get_type(private, struct kdc_tcp_connection);
+ kdc_tcp_terminate_connection(kdcconn, nt_errstr(status));
+}
+
+/*
+ called when we can write to a connection
+*/
+static void kdc_tcp_send(struct stream_connection *conn, uint16_t flags)
+{
+ struct kdc_tcp_connection *kdcconn = talloc_get_type(conn->private,
+ struct kdc_tcp_connection);
+ packet_queue_run(kdcconn->packet);
+}
+
+/**
+ Wrapper for krb5_kdc_process_krb5_request, converting to/from Samba
+ calling conventions
+*/
+
+static bool kdc_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ struct socket_address *peer_addr,
+ struct socket_address *my_addr,
+ int datagram_reply)
+{
+ int ret;
+ krb5_data k5_reply;
+ krb5_data_zero(&k5_reply);
+
+ krb5_kdc_update_time(NULL);
+
+ DEBUG(10,("Received KDC packet of length %lu from %s:%d\n",
+ (long)input->length - 4, peer_addr->addr, peer_addr->port));
+
+ ret = krb5_kdc_process_krb5_request(kdc->smb_krb5_context->krb5_context,
+ kdc->config,
+ input->data, input->length,
+ &k5_reply,
+ peer_addr->addr,
+ peer_addr->sockaddr,
+ datagram_reply);
+ if (ret == -1) {
+ *reply = data_blob(NULL, 0);
+ return false;
+ }
+ if (k5_reply.length) {
+ *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
+ krb5_free_data_contents(kdc->smb_krb5_context->krb5_context, &k5_reply);
+ } else {
+ *reply = data_blob(NULL, 0);
+ }
+ return true;
+}
+
+/*
+ called when we get a new connection
+*/
+static void kdc_tcp_generic_accept(struct stream_connection *conn, kdc_process_fn_t process_fn)
+{
+ struct kdc_server *kdc = talloc_get_type(conn->private, struct kdc_server);
+ struct kdc_tcp_connection *kdcconn;
+
+ kdcconn = talloc_zero(conn, struct kdc_tcp_connection);
+ if (!kdcconn) {
+ stream_terminate_connection(conn, "kdc_tcp_accept: out of memory");
+ return;
+ }
+ kdcconn->conn = conn;
+ kdcconn->kdc = kdc;
+ kdcconn->process = process_fn;
+ conn->private = kdcconn;
+
+ kdcconn->packet = packet_init(kdcconn);
+ if (kdcconn->packet == NULL) {
+ kdc_tcp_terminate_connection(kdcconn, "kdc_tcp_accept: out of memory");
+ return;
+ }
+ packet_set_private(kdcconn->packet, kdcconn);
+ packet_set_socket(kdcconn->packet, conn->socket);
+ packet_set_callback(kdcconn->packet, kdc_tcp_recv);
+ packet_set_full_request(kdcconn->packet, packet_full_request_u32);
+ packet_set_error_handler(kdcconn->packet, kdc_tcp_recv_error);
+ packet_set_event_context(kdcconn->packet, conn->event.ctx);
+ packet_set_fde(kdcconn->packet, conn->event.fde);
+ packet_set_serialise(kdcconn->packet);
+}
+
+static void kdc_tcp_accept(struct stream_connection *conn)
+{
+ kdc_tcp_generic_accept(conn, kdc_process);
+}
+
+static const struct stream_server_ops kdc_tcp_stream_ops = {
+ .name = "kdc_tcp",
+ .accept_connection = kdc_tcp_accept,
+ .recv_handler = kdc_tcp_recv_handler,
+ .send_handler = kdc_tcp_send
+};
+
+static void kpasswdd_tcp_accept(struct stream_connection *conn)
+{
+ kdc_tcp_generic_accept(conn, kpasswdd_process);
+}
+
+static const struct stream_server_ops kpasswdd_tcp_stream_ops = {
+ .name = "kpasswdd_tcp",
+ .accept_connection = kpasswdd_tcp_accept,
+ .recv_handler = kdc_tcp_recv_handler,
+ .send_handler = kdc_tcp_send
+};
+
+/*
+ start listening on the given address
+*/
+static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address,
+ uint16_t kdc_port, uint16_t kpasswd_port)
+{
+ const struct model_ops *model_ops;
+ struct kdc_socket *kdc_socket;
+ struct kdc_socket *kpasswd_socket;
+ struct socket_address *kdc_address, *kpasswd_address;
+ NTSTATUS status;
+
+ kdc_socket = talloc(kdc, struct kdc_socket);
+ NT_STATUS_HAVE_NO_MEMORY(kdc_socket);
+
+ kpasswd_socket = talloc(kdc, struct kdc_socket);
+ NT_STATUS_HAVE_NO_MEMORY(kpasswd_socket);
+
+ status = socket_create("ip", SOCKET_TYPE_DGRAM, &kdc_socket->sock, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(kdc_socket);
+ return status;
+ }
+
+ status = socket_create("ip", SOCKET_TYPE_DGRAM, &kpasswd_socket->sock, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(kpasswd_socket);
+ return status;
+ }
+
+ kdc_socket->kdc = kdc;
+ kdc_socket->send_queue = NULL;
+ kdc_socket->process = kdc_process;
+
+ talloc_steal(kdc_socket, kdc_socket->sock);
+
+ kdc_socket->fde = event_add_fd(kdc->task->event_ctx, kdc,
+ socket_get_fd(kdc_socket->sock), EVENT_FD_READ,
+ kdc_socket_handler, kdc_socket);
+
+ kdc_address = socket_address_from_strings(kdc_socket, kdc_socket->sock->backend_name,
+ address, kdc_port);
+ NT_STATUS_HAVE_NO_MEMORY(kdc_address);
+
+ status = socket_listen(kdc_socket->sock, kdc_address, 0, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%d UDP for kdc - %s\n",
+ address, kdc_port, nt_errstr(status)));
+ talloc_free(kdc_socket);
+ return status;
+ }
+
+ kpasswd_socket->kdc = kdc;
+ kpasswd_socket->send_queue = NULL;
+ kpasswd_socket->process = kpasswdd_process;
+
+ talloc_steal(kpasswd_socket, kpasswd_socket->sock);
+
+ kpasswd_socket->fde = event_add_fd(kdc->task->event_ctx, kdc,
+ socket_get_fd(kpasswd_socket->sock), EVENT_FD_READ,
+ kdc_socket_handler, kpasswd_socket);
+
+ kpasswd_address = socket_address_from_strings(kpasswd_socket, kpasswd_socket->sock->backend_name,
+ address, kpasswd_port);
+ NT_STATUS_HAVE_NO_MEMORY(kpasswd_address);
+
+ status = socket_listen(kpasswd_socket->sock, kpasswd_address, 0, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%d UDP for kpasswd - %s\n",
+ address, kpasswd_port, nt_errstr(status)));
+ talloc_free(kpasswd_socket);
+ return status;
+ }
+
+ /* within the kdc task we want to be a single process, so
+ ask for the single process model ops and pass these to the
+ stream_setup_socket() call. */
+ model_ops = process_model_byname("single");
+ if (!model_ops) {
+ DEBUG(0,("Can't find 'single' process model_ops\n"));
+ talloc_free(kdc_socket);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = stream_setup_socket(kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ model_ops,
+ &kdc_tcp_stream_ops,
+ "ip", address, &kdc_port,
+ lp_socket_options(kdc->task->lp_ctx),
+ kdc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
+ address, kdc_port, nt_errstr(status)));
+ talloc_free(kdc_socket);
+ return status;
+ }
+
+ status = stream_setup_socket(kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ model_ops,
+ &kpasswdd_tcp_stream_ops,
+ "ip", address, &kpasswd_port,
+ lp_socket_options(kdc->task->lp_ctx),
+ kdc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
+ address, kpasswd_port, nt_errstr(status)));
+ talloc_free(kdc_socket);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ setup our listening sockets on the configured network interfaces
+*/
+static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc, struct loadparm_context *lp_ctx,
+ struct interface *ifaces)
+{
+ int num_interfaces;
+ TALLOC_CTX *tmp_ctx = talloc_new(kdc);
+ NTSTATUS status;
+ int i;
+
+ num_interfaces = iface_count(ifaces);
+
+ for (i=0; i<num_interfaces; i++) {
+ const char *address = talloc_strdup(tmp_ctx, iface_n_ip(ifaces, i));
+ status = kdc_add_socket(kdc, address, lp_krb5_port(lp_ctx),
+ lp_kpasswd_port(lp_ctx));
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+static struct krb5plugin_windc_ftable windc_plugin_table = {
+ .minor_version = KRB5_WINDC_PLUGING_MINOR,
+ .init = samba_kdc_plugin_init,
+ .fini = samba_kdc_plugin_fini,
+ .pac_generate = samba_kdc_get_pac,
+ .pac_verify = samba_kdc_reget_pac,
+ .client_access = samba_kdc_check_client_access,
+};
+
+
+static NTSTATUS kdc_check_generic_kerberos(struct irpc_message *msg,
+ struct kdc_check_generic_kerberos *r)
+{
+ struct PAC_Validate pac_validate;
+ DATA_BLOB srv_sig;
+ struct PAC_SIGNATURE_DATA kdc_sig;
+ struct kdc_server *kdc = talloc_get_type(msg->private, struct kdc_server);
+ enum ndr_err_code ndr_err;
+ krb5_enctype etype;
+ int ret;
+ hdb_entry_ex ent;
+ krb5_principal principal;
+ krb5_keyblock keyblock;
+ Key *key;
+
+ /* There is no reply to this request */
+ r->out.generic_reply = data_blob(NULL, 0);
+
+ ndr_err = ndr_pull_struct_blob(&r->in.generic_request, msg,
+ lp_iconv_convenience(kdc->task->lp_ctx),
+ &pac_validate,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+#if 0
+ /* Windows does not check this */
+ if (pac_validate.MessageType != 3) {
+ /* We don't implement any other message types - such as certificate validation - yet */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+#endif
+ if (pac_validate.ChecksumAndSignature.length != (pac_validate.ChecksumLength + pac_validate.SignatureLength)
+ || pac_validate.ChecksumAndSignature.length < pac_validate.ChecksumLength
+ || pac_validate.ChecksumAndSignature.length < pac_validate.SignatureLength ) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ srv_sig = data_blob_const(pac_validate.ChecksumAndSignature.data,
+ pac_validate.ChecksumLength);
+
+ if (pac_validate.SignatureType == CKSUMTYPE_HMAC_MD5) {
+ etype = ETYPE_ARCFOUR_HMAC_MD5;
+ } else {
+ ret = krb5_cksumtype_to_enctype(kdc->smb_krb5_context->krb5_context, pac_validate.SignatureType,
+ &etype);
+ if (ret != 0) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ }
+
+ ret = krb5_make_principal(kdc->smb_krb5_context->krb5_context, &principal,
+ lp_realm(kdc->task->lp_ctx),
+ "krbtgt", lp_realm(kdc->task->lp_ctx),
+ NULL);
+
+ if (ret != 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = kdc->config->db[0]->hdb_fetch(kdc->smb_krb5_context->krb5_context,
+ kdc->config->db[0],
+ principal,
+ HDB_F_GET_KRBTGT | HDB_F_DECRYPT,
+ &ent);
+
+ if (ret != 0) {
+ hdb_free_entry(kdc->smb_krb5_context->krb5_context, &ent);
+ krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ ret = hdb_enctype2key(kdc->smb_krb5_context->krb5_context, &ent.entry, etype, &key);
+
+ if (ret != 0) {
+ hdb_free_entry(kdc->smb_krb5_context->krb5_context, &ent);
+ krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ keyblock = key->key;
+
+ kdc_sig.type = pac_validate.SignatureType;
+ kdc_sig.signature = data_blob_const(&pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength],
+ pac_validate.SignatureLength);
+ ret = check_pac_checksum(msg, srv_sig, &kdc_sig,
+ kdc->smb_krb5_context->krb5_context, &keyblock);
+
+ hdb_free_entry(kdc->smb_krb5_context->krb5_context, &ent);
+ krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+
+ if (ret != 0) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+
+/*
+ startup the kdc task
+*/
+static void kdc_task_init(struct task_server *task)
+{
+ struct kdc_server *kdc;
+ NTSTATUS status;
+ krb5_error_code ret;
+ struct interface *ifaces;
+
+ switch (lp_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task, "kdc: no KDC required in standalone configuration");
+ return;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task, "kdc: no KDC required in member server configuration");
+ return;
+ case ROLE_DOMAIN_CONTROLLER:
+ /* Yes, we want a KDC */
+ break;
+ }
+
+ load_interfaces(task, lp_interfaces(task->lp_ctx), &ifaces);
+
+ if (iface_count(ifaces) == 0) {
+ task_server_terminate(task, "kdc: no network interfaces configured");
+ return;
+ }
+
+ task_server_set_title(task, "task[kdc]");
+
+ kdc = talloc(task, struct kdc_server);
+ if (kdc == NULL) {
+ task_server_terminate(task, "kdc: out of memory");
+ return;
+ }
+
+ kdc->task = task;
+
+ initialize_krb5_error_table();
+
+ ret = smb_krb5_init_context(kdc, task->event_ctx, task->lp_ctx, &kdc->smb_krb5_context);
+ if (ret) {
+ DEBUG(1,("kdc_task_init: krb5_init_context failed (%s)\n",
+ error_message(ret)));
+ task_server_terminate(task, "kdc: krb5_init_context failed");
+ return;
+ }
+
+ krb5_add_et_list(kdc->smb_krb5_context->krb5_context, initialize_hdb_error_table_r);
+
+ ret = krb5_kdc_get_config(kdc->smb_krb5_context->krb5_context,
+ &kdc->config);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to get KDC configuration");
+ return;
+ }
+
+ kdc->config->logf = kdc->smb_krb5_context->logf;
+ kdc->config->db = talloc(kdc, struct HDB *);
+ if (!kdc->config->db) {
+ task_server_terminate(task, "kdc: out of memory");
+ return;
+ }
+ kdc->config->num_db = 1;
+
+ status = kdc_hdb_ldb_create(kdc, task->event_ctx, task->lp_ctx,
+ kdc->smb_krb5_context->krb5_context,
+ &kdc->config->db[0], NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "kdc: hdb_ldb_create (setup KDC database) failed");
+ return;
+ }
+
+ ret = krb5_kt_register(kdc->smb_krb5_context->krb5_context, &hdb_kt_ops);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to register hdb keytab");
+ return;
+ }
+
+ /* Registar WinDC hooks */
+ ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context,
+ PLUGIN_TYPE_DATA, "windc",
+ &windc_plugin_table);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to register hdb keytab");
+ return;
+ }
+
+ krb5_kdc_windc_init(kdc->smb_krb5_context->krb5_context);
+
+ kdc_mem_ctx = kdc->smb_krb5_context;
+ kdc_lp_ctx = task->lp_ctx;
+
+ /* start listening on the configured network interfaces */
+ status = kdc_startup_interfaces(kdc, task->lp_ctx, ifaces);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "kdc failed to setup interfaces");
+ return;
+ }
+
+ status = IRPC_REGISTER(task->msg_ctx, irpc, KDC_CHECK_GENERIC_KERBEROS,
+ kdc_check_generic_kerberos, kdc);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "nbtd failed to setup monitoring");
+ return;
+ }
+
+ irpc_add_name(task->msg_ctx, "kdc_server");
+}
+
+
+/* called at smbd startup - register ourselves as a server service */
+NTSTATUS server_service_kdc_init(void)
+{
+ return register_server_service("kdc", kdc_task_init);
+}
diff --git a/source4/kdc/kdc.h b/source4/kdc/kdc.h
new file mode 100644
index 0000000000..7e82ad24c4
--- /dev/null
+++ b/source4/kdc/kdc.h
@@ -0,0 +1,61 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC structures
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+
+ 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/>.
+*/
+
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include <kdc.h>
+#include <hdb.h>
+#include <krb5/windc_plugin.h>
+#include "kdc/pac_glue.h"
+
+struct kdc_server;
+struct socket_address;
+
+extern TALLOC_CTX *kdc_mem_ctx;
+extern struct loadparm_context *kdc_lp_ctx;
+
+bool kpasswdd_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ struct socket_address *peer_addr,
+ struct socket_address *my_addr,
+ int datagram_reply);
+
+/*
+ top level context structure for the kdc server
+*/
+struct kdc_server {
+ struct task_server *task;
+ krb5_kdc_configuration *config;
+ struct smb_krb5_context *smb_krb5_context;
+};
+
+
+struct hdb_ldb_private {
+ struct ldb_context *samdb;
+ struct smb_iconv_convenience *iconv_convenience;
+ struct ldb_message *msg;
+ struct ldb_message *realm_ref_msg;
+ hdb_entry_ex *entry_ex;
+ const char *netbios_name;
+};
diff --git a/source4/kdc/kpasswdd.c b/source4/kdc/kpasswdd.c
new file mode 100644
index 0000000000..603332e69e
--- /dev/null
+++ b/source4/kdc/kpasswdd.c
@@ -0,0 +1,614 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ kpasswd Server implementation
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+
+ 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/>.
+*/
+
+#include "includes.h"
+#include "smbd/service_task.h"
+#include "lib/events/events.h"
+#include "lib/socket/socket.h"
+#include "system/network.h"
+#include "lib/util/dlinklist.h"
+#include "lib/ldb/include/ldb.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "auth/auth.h"
+#include "dsdb/samdb/samdb.h"
+#include "rpc_server/dcerpc_server.h"
+#include "rpc_server/samr/proto.h"
+#include "libcli/security/security.h"
+#include "param/param.h"
+#include "kdc/kdc.h"
+
+/* TODO: remove all SAMBA4_INTERNAL_HEIMDAL stuff from this file */
+#ifdef SAMBA4_INTERNAL_HEIMDAL
+#include "heimdal_build/kpasswdd-glue.h"
+#endif
+
+/* hold information about one kdc socket */
+struct kpasswd_socket {
+ struct socket_context *sock;
+ struct kdc_server *kdc;
+ struct fd_event *fde;
+
+ /* a queue of outgoing replies that have been deferred */
+ struct kdc_reply *send_queue;
+};
+
+/* Return true if there is a valid error packet formed in the error_blob */
+static bool kpasswdd_make_error_reply(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ uint16_t result_code,
+ const char *error_string,
+ DATA_BLOB *error_blob)
+{
+ char *error_string_utf8;
+ ssize_t len;
+
+ DEBUG(result_code ? 3 : 10, ("kpasswdd: %s\n", error_string));
+
+ len = push_utf8_talloc(mem_ctx, lp_iconv_convenience(kdc->task->lp_ctx), &error_string_utf8, error_string);
+ if (len == -1) {
+ return false;
+ }
+
+ *error_blob = data_blob_talloc(mem_ctx, NULL, 2 + len + 1);
+ if (!error_blob->data) {
+ return false;
+ }
+ RSSVAL(error_blob->data, 0, result_code);
+ memcpy(error_blob->data + 2, error_string_utf8, len + 1);
+ return true;
+}
+
+/* Return true if there is a valid error packet formed in the error_blob */
+static bool kpasswdd_make_unauth_error_reply(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ uint16_t result_code,
+ const char *error_string,
+ DATA_BLOB *error_blob)
+{
+ bool ret;
+ int kret;
+ DATA_BLOB error_bytes;
+ krb5_data k5_error_bytes, k5_error_blob;
+ ret = kpasswdd_make_error_reply(kdc, mem_ctx, result_code, error_string,
+ &error_bytes);
+ if (!ret) {
+ return false;
+ }
+ k5_error_bytes.data = error_bytes.data;
+ k5_error_bytes.length = error_bytes.length;
+ kret = krb5_mk_error(kdc->smb_krb5_context->krb5_context,
+ result_code, NULL, &k5_error_bytes,
+ NULL, NULL, NULL, NULL, &k5_error_blob);
+ if (kret) {
+ return false;
+ }
+ *error_blob = data_blob_talloc(mem_ctx, k5_error_blob.data, k5_error_blob.length);
+ krb5_data_free(&k5_error_blob);
+ if (!error_blob->data) {
+ return false;
+ }
+ return true;
+}
+
+static bool kpasswd_make_pwchange_reply(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ NTSTATUS status,
+ enum samr_RejectReason reject_reason,
+ struct samr_DomInfo1 *dominfo,
+ DATA_BLOB *error_blob)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ "No such user when changing password",
+ error_blob);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ "Not permitted to change password",
+ error_blob);
+ }
+ if (dominfo && NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) {
+ const char *reject_string;
+ switch (reject_reason) {
+ case SAMR_REJECT_TOO_SHORT:
+ reject_string = talloc_asprintf(mem_ctx, "Password too short, password must be at least %d characters long",
+ dominfo->min_password_length);
+ break;
+ case SAMR_REJECT_COMPLEXITY:
+ reject_string = "Password does not meet complexity requirements";
+ break;
+ case SAMR_REJECT_IN_HISTORY:
+ reject_string = "Password is already in password history";
+ break;
+ case SAMR_REJECT_OTHER:
+ default:
+ reject_string = talloc_asprintf(mem_ctx, "Password must be at least %d characters long, and cannot match any of your %d previous passwords",
+ dominfo->min_password_length, dominfo->password_history_length);
+ break;
+ }
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_SOFTERROR,
+ reject_string,
+ error_blob);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx, "failed to set password: %s", nt_errstr(status)),
+ error_blob);
+
+ }
+ return kpasswdd_make_error_reply(kdc, mem_ctx, KRB5_KPASSWD_SUCCESS,
+ "Password changed",
+ error_blob);
+}
+
+/*
+ A user password change
+
+ Return true if there is a valid error packet (or sucess) formed in
+ the error_blob
+*/
+static bool kpasswdd_change_password(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info *session_info,
+ const char *password,
+ DATA_BLOB *reply)
+{
+ NTSTATUS status;
+ enum samr_RejectReason reject_reason;
+ struct samr_DomInfo1 *dominfo;
+ struct ldb_context *samdb;
+
+ samdb = samdb_connect(mem_ctx, kdc->task->event_ctx, kdc->task->lp_ctx, system_session(mem_ctx, kdc->task->lp_ctx));
+ if (!samdb) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ "Failed to open samdb",
+ reply);
+ }
+
+ DEBUG(3, ("Changing password of %s\\%s (%s)\n",
+ session_info->server_info->domain_name,
+ session_info->server_info->account_name,
+ dom_sid_string(mem_ctx, session_info->security_token->user_sid)));
+
+ /* User password change */
+ status = samdb_set_password_sid(samdb, mem_ctx,
+ session_info->security_token->user_sid,
+ password, NULL, NULL,
+ true, /* this is a user password change */
+ &reject_reason,
+ &dominfo);
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ reply);
+
+}
+
+static bool kpasswd_process_request(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ uint16_t version,
+ DATA_BLOB *input,
+ DATA_BLOB *reply)
+{
+ struct auth_session_info *session_info;
+ if (!NT_STATUS_IS_OK(gensec_session_info(gensec_security,
+ &session_info))) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ "gensec_session_info failed!",
+ reply);
+ }
+
+ switch (version) {
+ case KRB5_KPASSWD_VERS_CHANGEPW:
+ {
+ char *password = talloc_strndup(mem_ctx, (const char *)input->data, input->length);
+ if (!password) {
+ return false;
+ }
+ return kpasswdd_change_password(kdc, mem_ctx, session_info,
+ password, reply);
+ break;
+ }
+ case KRB5_KPASSWD_VERS_SETPW:
+ {
+ NTSTATUS status;
+ enum samr_RejectReason reject_reason = SAMR_REJECT_OTHER;
+ struct samr_DomInfo1 *dominfo = NULL;
+ struct ldb_context *samdb;
+ struct ldb_message *msg;
+ krb5_context context = kdc->smb_krb5_context->krb5_context;
+
+ ChangePasswdDataMS chpw;
+ char *password;
+
+ krb5_principal principal;
+ char *set_password_on_princ;
+ struct ldb_dn *set_password_on_dn;
+
+ size_t len;
+ int ret;
+
+ msg = ldb_msg_new(mem_ctx);
+ if (!msg) {
+ return false;
+ }
+
+ ret = decode_ChangePasswdDataMS(input->data, input->length,
+ &chpw, &len);
+ if (ret) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "failed to decode password change structure",
+ reply);
+ }
+
+ password = talloc_strndup(mem_ctx,
+ (const char *)chpw.newpasswd.data,
+ chpw.newpasswd.length);
+ if (!password) {
+ free_ChangePasswdDataMS(&chpw);
+ return false;
+ }
+ if ((chpw.targname && !chpw.targrealm)
+ || (!chpw.targname && chpw.targrealm)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Realm and principal must be both present, or neither present",
+ reply);
+ }
+ if (chpw.targname && chpw.targrealm) {
+#ifdef SAMBA4_INTERNAL_HEIMDAL
+ if (_krb5_principalname2krb5_principal(kdc->smb_krb5_context->krb5_context,
+ &principal, *chpw.targname,
+ *chpw.targrealm) != 0) {
+ free_ChangePasswdDataMS(&chpw);
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "failed to extract principal to set",
+ reply);
+
+ }
+#else /* SAMBA4_INTERNAL_HEIMDAL */
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_BAD_VERSION,
+ "Operation Not Implemented",
+ reply);
+#endif /* SAMBA4_INTERNAL_HEIMDAL */
+ } else {
+ free_ChangePasswdDataMS(&chpw);
+ return kpasswdd_change_password(kdc, mem_ctx, session_info,
+ password, reply);
+ }
+ free_ChangePasswdDataMS(&chpw);
+
+ if (krb5_unparse_name(context, principal, &set_password_on_princ) != 0) {
+ krb5_free_principal(context, principal);
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "krb5_unparse_name failed!",
+ reply);
+ }
+
+ krb5_free_principal(context, principal);
+
+ samdb = samdb_connect(mem_ctx, kdc->task->event_ctx, kdc->task->lp_ctx, session_info);
+ if (!samdb) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ "Unable to open database!",
+ reply);
+ }
+
+ DEBUG(3, ("%s\\%s (%s) is changing password of %s\n",
+ session_info->server_info->domain_name,
+ session_info->server_info->account_name,
+ dom_sid_string(mem_ctx, session_info->security_token->user_sid),
+ set_password_on_princ));
+ ret = ldb_transaction_start(samdb);
+ if (ret) {
+ status = NT_STATUS_TRANSACTION_ABORTED;
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ SAMR_REJECT_OTHER,
+ NULL,
+ reply);
+ }
+
+ status = crack_user_principal_name(samdb, mem_ctx,
+ set_password_on_princ,
+ &set_password_on_dn, NULL);
+ free(set_password_on_princ);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_transaction_cancel(samdb);
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ SAMR_REJECT_OTHER,
+ NULL,
+ reply);
+ }
+
+ msg = ldb_msg_new(mem_ctx);
+ if (msg == NULL) {
+ ldb_transaction_cancel(samdb);
+ status = NT_STATUS_NO_MEMORY;
+ } else {
+ msg->dn = ldb_dn_copy(msg, set_password_on_dn);
+ if (!msg->dn) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Admin password set */
+ status = samdb_set_password(samdb, mem_ctx,
+ set_password_on_dn, NULL,
+ msg, password, NULL, NULL,
+ false, /* this is not a user password change */
+ &reject_reason, &dominfo);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* modify the samdb record */
+ ret = samdb_replace(samdb, mem_ctx, msg);
+ if (ret != 0) {
+ DEBUG(2,("Failed to modify record to set password on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(samdb)));
+ status = NT_STATUS_ACCESS_DENIED;
+ }
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ ret = ldb_transaction_commit(samdb);
+ if (ret != 0) {
+ DEBUG(1,("Failed to commit transaction to set password on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(samdb)));
+ status = NT_STATUS_TRANSACTION_ABORTED;
+ }
+ } else {
+ ldb_transaction_cancel(samdb);
+ }
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ reply);
+ }
+ default:
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_BAD_VERSION,
+ talloc_asprintf(mem_ctx,
+ "Protocol version %u not supported",
+ version),
+ reply);
+ }
+ return true;
+}
+
+bool kpasswdd_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ struct socket_address *peer_addr,
+ struct socket_address *my_addr,
+ int datagram_reply)
+{
+ bool ret;
+ const uint16_t header_len = 6;
+ uint16_t len;
+ uint16_t ap_req_len;
+ uint16_t krb_priv_len;
+ uint16_t version;
+ NTSTATUS nt_status;
+ DATA_BLOB ap_req, krb_priv_req;
+ DATA_BLOB krb_priv_rep = data_blob(NULL, 0);
+ DATA_BLOB ap_rep = data_blob(NULL, 0);
+ DATA_BLOB kpasswd_req, kpasswd_rep;
+ struct cli_credentials *server_credentials;
+ struct gensec_security *gensec_security;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ if (!tmp_ctx) {
+ return false;
+ }
+
+ /* Be parinoid. We need to ensure we don't just let the
+ * caller lead us into a buffer overflow */
+ if (input->length <= header_len) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ len = RSVAL(input->data, 0);
+ if (input->length != len) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ /* There are two different versions of this protocol so far,
+ * plus others in the standards pipe. Fortunetly they all
+ * take a very similar framing */
+ version = RSVAL(input->data, 2);
+ ap_req_len = RSVAL(input->data, 4);
+ if ((ap_req_len >= len) || (ap_req_len + header_len) >= len) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ krb_priv_len = len - ap_req_len;
+ ap_req = data_blob_const(&input->data[header_len], ap_req_len);
+ krb_priv_req = data_blob_const(&input->data[header_len + ap_req_len], krb_priv_len);
+
+ nt_status = gensec_server_start(tmp_ctx, kdc->task->event_ctx, kdc->task->lp_ctx, kdc->task->msg_ctx, &gensec_security);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ server_credentials = cli_credentials_init(tmp_ctx);
+ if (!server_credentials) {
+ DEBUG(1, ("Failed to init server credentials\n"));
+ return false;
+ }
+
+ /* We want the credentials subsystem to use the krb5 context
+ * we already have, rather than a new context */
+ cli_credentials_set_krb5_context(server_credentials, kdc->smb_krb5_context);
+ cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
+ nt_status = cli_credentials_set_stored_principal(server_credentials, kdc->task->event_ctx, kdc->task->lp_ctx, "kadmin/changepw");
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "Failed to obtain server credentials for kadmin/changepw: %s\n",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ nt_status = gensec_set_credentials(gensec_security, server_credentials);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ /* The kerberos PRIV packets include these addresses. MIT
+ * clients check that they are present */
+ nt_status = gensec_set_peer_addr(gensec_security, peer_addr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ nt_status = gensec_set_my_addr(gensec_security, my_addr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ /* We want the GENSEC wrap calls to generate PRIV tokens */
+ gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
+
+ nt_status = gensec_start_mech_by_name(gensec_security, "krb5");
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ /* Accept the AP-REQ and generate teh AP-REP we need for the reply */
+ nt_status = gensec_update(gensec_security, tmp_ctx, ap_req, &ap_rep);
+ if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "gensec_update failed: %s",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* Extract the data from the KRB-PRIV half of the message */
+ nt_status = gensec_unwrap(gensec_security, tmp_ctx, &krb_priv_req, &kpasswd_req);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "gensec_unwrap failed: %s",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* Figure out something to do with it (probably changing a password...) */
+ ret = kpasswd_process_request(kdc, tmp_ctx,
+ gensec_security,
+ version,
+ &kpasswd_req, &kpasswd_rep);
+ if (!ret) {
+ /* Argh! */
+ return false;
+ }
+
+ /* And wrap up the reply: This ensures that the error message
+ * or success can be verified by the client */
+ nt_status = gensec_wrap(gensec_security, tmp_ctx,
+ &kpasswd_rep, &krb_priv_rep);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "gensec_wrap failed: %s",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+reply:
+ *reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
+ if (!reply->data) {
+ return false;
+ }
+
+ RSSVAL(reply->data, 0, reply->length);
+ RSSVAL(reply->data, 2, 1); /* This is a version 1 reply, MS change/set or otherwise */
+ RSSVAL(reply->data, 4, ap_rep.length);
+ memcpy(reply->data + header_len,
+ ap_rep.data,
+ ap_rep.length);
+ memcpy(reply->data + header_len + ap_rep.length,
+ krb_priv_rep.data,
+ krb_priv_rep.length);
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c
new file mode 100644
index 0000000000..cbdbb86b1f
--- /dev/null
+++ b/source4/kdc/pac-glue.c
@@ -0,0 +1,307 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+
+ 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/>.
+*/
+
+#include "includes.h"
+#include "dsdb/common/flags.h"
+#include "lib/ldb/include/ldb.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "librpc/gen_ndr/krb5pac.h"
+#include "auth/auth.h"
+#include "auth/auth_sam.h"
+#include "auth/auth_sam_reply.h"
+#include "param/param.h"
+#include "kdc/kdc.h"
+
+struct krb5_dh_moduli;
+struct _krb5_krb_auth_data;
+
+krb5_error_code samba_kdc_plugin_init(krb5_context context, void **ptr)
+{
+ *ptr = NULL;
+ return 0;
+}
+
+void samba_kdc_plugin_fini(void *ptr)
+{
+ return;
+}
+
+static krb5_error_code make_pac(krb5_context context,
+ TALLOC_CTX *mem_ctx,
+ struct smb_iconv_convenience *iconv_convenience,
+ struct auth_serversupplied_info *server_info,
+ krb5_pac *pac)
+{
+ union PAC_INFO info;
+ struct netr_SamInfo3 *info3;
+ krb5_data pac_data;
+ NTSTATUS nt_status;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB pac_out;
+ krb5_error_code ret;
+
+ ZERO_STRUCT(info);
+
+ nt_status = auth_convert_server_info_saminfo3(mem_ctx, server_info, &info3);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("Getting Samba info failed: %s\n", nt_errstr(nt_status)));
+ return EINVAL;
+ }
+
+ info.logon_info.info = talloc_zero(mem_ctx, struct PAC_LOGON_INFO);
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ info.logon_info.info->info3 = *info3;
+
+ ndr_err = ndr_push_union_blob(&pac_out, mem_ctx, iconv_convenience, &info,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(1, ("PAC (presig) push failed: %s\n", nt_errstr(nt_status)));
+ return EINVAL;
+ }
+
+ ret = krb5_data_copy(&pac_data, pac_out.data, pac_out.length);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = krb5_pac_init(context, pac);
+ if (ret != 0) {
+ krb5_data_free(&pac_data);
+ return ret;
+ }
+
+ ret = krb5_pac_add_buffer(context, *pac, PAC_TYPE_LOGON_INFO, &pac_data);
+ krb5_data_free(&pac_data);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return ret;
+}
+
+/* Given the right private pointer from hdb_ldb, get a PAC from the attached ldb messages */
+krb5_error_code samba_kdc_get_pac(void *priv,
+ krb5_context context,
+ struct hdb_entry_ex *client,
+ krb5_pac *pac)
+{
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+ struct auth_serversupplied_info *server_info;
+ struct hdb_ldb_private *private = talloc_get_type(client->ctx, struct hdb_ldb_private);
+ TALLOC_CTX *mem_ctx = talloc_named(private, 0, "samba_get_pac context");
+ unsigned int userAccountControl;
+
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ /* The user account may be set not to want the PAC */
+ userAccountControl = ldb_msg_find_attr_as_uint(private->msg, "userAccountControl", 0);
+ if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) {
+ *pac = NULL;
+ return 0;
+ }
+
+ nt_status = authsam_make_server_info(mem_ctx, private->samdb,
+ private->netbios_name,
+ private->msg,
+ private->realm_ref_msg,
+ data_blob(NULL, 0),
+ data_blob(NULL, 0),
+ &server_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Getting user info for PAC failed: %s\n",
+ nt_errstr(nt_status)));
+ return ENOMEM;
+ }
+
+ ret = make_pac(context, mem_ctx, private->iconv_convenience, server_info, pac);
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/* Resign (and reform, including possibly new groups) a PAC */
+
+krb5_error_code samba_kdc_reget_pac(void *priv, krb5_context context,
+ const krb5_principal client_principal,
+ struct hdb_entry_ex *client,
+ struct hdb_entry_ex *server, krb5_pac *pac)
+{
+ krb5_error_code ret;
+
+ unsigned int userAccountControl;
+
+ struct hdb_ldb_private *private = talloc_get_type(server->ctx, struct hdb_ldb_private);
+
+ struct auth_serversupplied_info *server_info_out;
+
+ TALLOC_CTX *mem_ctx = talloc_named(private, 0, "samba_get_pac context");
+
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ /* The service account may be set not to want the PAC */
+ userAccountControl = ldb_msg_find_attr_as_uint(private->msg, "userAccountControl", 0);
+ if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) {
+ talloc_free(mem_ctx);
+ *pac = NULL;
+ return 0;
+ }
+
+ ret = kerberos_pac_to_server_info(mem_ctx, private->iconv_convenience,
+ *pac, context, &server_info_out);
+
+ /* We will compleatly regenerate this pac */
+ krb5_pac_free(context, *pac);
+
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ret = make_pac(context, mem_ctx, private->iconv_convenience, server_info_out, pac);
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static void samba_kdc_build_edata_reply(TALLOC_CTX *tmp_ctx, krb5_data *e_data,
+ NTSTATUS nt_status)
+{
+ PA_DATA pa;
+ unsigned char *buf;
+ size_t len;
+ krb5_error_code ret = 0;
+
+ if (!e_data)
+ return;
+
+ pa.padata_type = KRB5_PADATA_PW_SALT;
+ pa.padata_value.length = 12;
+ pa.padata_value.data = malloc(pa.padata_value.length);
+ if (!pa.padata_value.data) {
+ e_data->length = 0;
+ e_data->data = NULL;
+ return;
+ }
+
+ SIVAL(pa.padata_value.data, 0, NT_STATUS_V(nt_status));
+ SIVAL(pa.padata_value.data, 4, 0);
+ SIVAL(pa.padata_value.data, 8, 1);
+
+ ASN1_MALLOC_ENCODE(PA_DATA, buf, len, &pa, &len, ret);
+ free(pa.padata_value.data);
+
+ e_data->data = buf;
+ e_data->length = len;
+
+ return;
+}
+
+/* Given an hdb entry (and in particular it's private member), consult
+ * the account_ok routine in auth/auth_sam.c for consistancy */
+
+
+krb5_error_code samba_kdc_check_client_access(void *priv,
+ krb5_context context, hdb_entry_ex *entry_ex,
+ KDC_REQ *req,
+ krb5_data *e_data)
+{
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+ TALLOC_CTX *tmp_ctx = talloc_new(entry_ex->ctx);
+ struct hdb_ldb_private *private = talloc_get_type(entry_ex->ctx, struct hdb_ldb_private);
+ char *name, *workstation = NULL;
+ HostAddresses *addresses = req->req_body.addresses;
+ int i;
+
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ ret = krb5_unparse_name(context, entry_ex->entry.principal, &name);
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (addresses) {
+ for (i=0; i < addresses->len; i++) {
+ if (addresses->val->addr_type == KRB5_ADDRESS_NETBIOS) {
+ workstation = talloc_strndup(tmp_ctx, addresses->val->address.data, MIN(addresses->val->address.length, 15));
+ if (workstation) {
+ break;
+ }
+ }
+ }
+ }
+
+ /* Strip space padding */
+ if (workstation) {
+ i = MIN(strlen(workstation), 15);
+ for (; i > 0 && workstation[i - 1] == ' '; i--) {
+ workstation[i - 1] = '\0';
+ }
+ }
+
+ nt_status = authsam_account_ok(tmp_ctx,
+ private->samdb,
+ MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT,
+ private->msg,
+ private->realm_ref_msg,
+ workstation,
+ name);
+ free(name);
+
+ if (NT_STATUS_IS_OK(nt_status))
+ return 0;
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE))
+ ret = KRB5KDC_ERR_KEY_EXPIRED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED))
+ ret = KRB5KDC_ERR_KEY_EXPIRED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_EXPIRED))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_LOGON_HOURS))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_WORKSTATION))
+ ret = KRB5KDC_ERR_POLICY;
+ else
+ ret = KRB5KDC_ERR_POLICY;
+
+ samba_kdc_build_edata_reply(tmp_ctx, e_data, nt_status);
+
+ return ret;
+}
+