summaryrefslogtreecommitdiff
path: root/source4/dsdb
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb')
-rw-r--r--source4/dsdb/common/flag_mapping.c143
-rw-r--r--source4/dsdb/common/flags.h137
-rw-r--r--source4/dsdb/common/sidmap.c612
-rw-r--r--source4/dsdb/common/util.c1994
-rw-r--r--source4/dsdb/config.mk63
-rw-r--r--source4/dsdb/repl/drepl_out_helpers.c442
-rw-r--r--source4/dsdb/repl/drepl_out_helpers.h26
-rw-r--r--source4/dsdb/repl/drepl_out_pull.c154
-rw-r--r--source4/dsdb/repl/drepl_partitions.c272
-rw-r--r--source4/dsdb/repl/drepl_periodic.c109
-rw-r--r--source4/dsdb/repl/drepl_service.c189
-rw-r--r--source4/dsdb/repl/drepl_service.h175
-rw-r--r--source4/dsdb/repl/replicated_objects.c417
-rw-r--r--source4/dsdb/samdb/cracknames.c1290
-rw-r--r--source4/dsdb/samdb/ldb_modules/anr.c307
-rw-r--r--source4/dsdb/samdb/ldb_modules/config.mk313
-rw-r--r--source4/dsdb/samdb/ldb_modules/dsdb_cache.c42
-rw-r--r--source4/dsdb/samdb/ldb_modules/extended_dn.c369
-rw-r--r--source4/dsdb/samdb/ldb_modules/instancetype.c124
-rw-r--r--source4/dsdb/samdb/ldb_modules/kludge_acl.c461
-rw-r--r--source4/dsdb/samdb/ldb_modules/linked_attributes.c953
-rw-r--r--source4/dsdb/samdb/ldb_modules/local_password.c852
-rw-r--r--source4/dsdb/samdb/ldb_modules/naming_fsmo.c123
-rw-r--r--source4/dsdb/samdb/ldb_modules/normalise.c162
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectclass.c1207
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectguid.c254
-rw-r--r--source4/dsdb/samdb/ldb_modules/partition.c1101
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_hash.c2281
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_modules.h3
-rw-r--r--source4/dsdb/samdb/ldb_modules/pdc_fsmo.c121
-rw-r--r--source4/dsdb/samdb/ldb_modules/proxy.c337
-rw-r--r--source4/dsdb/samdb/ldb_modules/ranged_results.c207
-rw-r--r--source4/dsdb/samdb/ldb_modules/repl_meta_data.c1588
-rw-r--r--source4/dsdb/samdb/ldb_modules/rootdse.c441
-rw-r--r--source4/dsdb/samdb/ldb_modules/samba3sam.c932
-rw-r--r--source4/dsdb/samdb/ldb_modules/samldb.c825
-rw-r--r--source4/dsdb/samdb/ldb_modules/schema_fsmo.c413
-rw-r--r--source4/dsdb/samdb/ldb_modules/show_deleted.c201
-rw-r--r--source4/dsdb/samdb/ldb_modules/simple_ldap_map.c716
-rw-r--r--source4/dsdb/samdb/ldb_modules/subtree_delete.c260
-rw-r--r--source4/dsdb/samdb/ldb_modules/subtree_rename.c285
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/samba3sam.py1046
-rw-r--r--source4/dsdb/samdb/ldb_modules/update_keytab.c211
-rw-r--r--source4/dsdb/samdb/samdb.c296
-rw-r--r--source4/dsdb/samdb/samdb.h100
-rw-r--r--source4/dsdb/samdb/samdb_privilege.c121
-rw-r--r--source4/dsdb/schema/schema.h183
-rw-r--r--source4/dsdb/schema/schema_description.c316
-rw-r--r--source4/dsdb/schema/schema_init.c1411
-rw-r--r--source4/dsdb/schema/schema_query.c344
-rw-r--r--source4/dsdb/schema/schema_set.c409
-rw-r--r--source4/dsdb/schema/schema_syntax.c1426
52 files changed, 26764 insertions, 0 deletions
diff --git a/source4/dsdb/common/flag_mapping.c b/source4/dsdb/common/flag_mapping.c
new file mode 100644
index 0000000000..4a2a079e45
--- /dev/null
+++ b/source4/dsdb/common/flag_mapping.c
@@ -0,0 +1,143 @@
+/*
+ Unix SMB/CIFS implementation.
+ helper mapping functions for the SAMDB server
+
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "librpc/gen_ndr/samr.h"
+#include "dsdb/common/flags.h"
+
+/*
+translated the ACB_CTRL Flags to UserFlags (userAccountControl)
+*/
+/* mapping between ADS userAccountControl and SAMR acct_flags */
+static const struct {
+ uint32_t uf;
+ uint32_t acb;
+} acct_flags_map[] = {
+ { UF_ACCOUNTDISABLE, ACB_DISABLED },
+ { UF_HOMEDIR_REQUIRED, ACB_HOMDIRREQ },
+ { UF_PASSWD_NOTREQD, ACB_PWNOTREQ },
+ { UF_TEMP_DUPLICATE_ACCOUNT, ACB_TEMPDUP },
+ { UF_NORMAL_ACCOUNT, ACB_NORMAL },
+ { UF_MNS_LOGON_ACCOUNT, ACB_MNS },
+ { UF_INTERDOMAIN_TRUST_ACCOUNT, ACB_DOMTRUST },
+ { UF_WORKSTATION_TRUST_ACCOUNT, ACB_WSTRUST },
+ { UF_SERVER_TRUST_ACCOUNT, ACB_SVRTRUST },
+ { UF_DONT_EXPIRE_PASSWD, ACB_PWNOEXP },
+ { UF_LOCKOUT, ACB_AUTOLOCK },
+ { UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, ACB_ENC_TXT_PWD_ALLOWED },
+ { UF_SMARTCARD_REQUIRED, ACB_SMARTCARD_REQUIRED },
+ { UF_TRUSTED_FOR_DELEGATION, ACB_TRUSTED_FOR_DELEGATION },
+ { UF_NOT_DELEGATED, ACB_NOT_DELEGATED },
+ { UF_USE_DES_KEY_ONLY, ACB_USE_DES_KEY_ONLY},
+ { UF_DONT_REQUIRE_PREAUTH, ACB_DONT_REQUIRE_PREAUTH },
+ { UF_PASSWORD_EXPIRED, ACB_PW_EXPIRED },
+ { UF_NO_AUTH_DATA_REQUIRED, ACB_NO_AUTH_DATA_REQD }
+};
+
+uint32_t samdb_acb2uf(uint32_t acb)
+{
+ uint32_t i, ret = 0;
+ for (i=0;i<ARRAY_SIZE(acct_flags_map);i++) {
+ if (acct_flags_map[i].acb & acb) {
+ ret |= acct_flags_map[i].uf;
+ }
+ }
+ return ret;
+}
+
+/*
+translated the UserFlags (userAccountControl) to ACB_CTRL Flags
+*/
+uint32_t samdb_uf2acb(uint32_t uf)
+{
+ uint32_t i;
+ uint32_t ret = 0;
+ for (i=0;i<ARRAY_SIZE(acct_flags_map);i++) {
+ if (acct_flags_map[i].uf & uf) {
+ ret |= acct_flags_map[i].acb;
+ }
+ }
+ return ret;
+}
+
+/*
+get the accountType from the UserFlags
+*/
+uint32_t samdb_uf2atype(uint32_t uf)
+{
+ uint32_t atype = 0x00000000;
+
+ if (uf & UF_NORMAL_ACCOUNT) atype = ATYPE_NORMAL_ACCOUNT;
+ else if (uf & UF_TEMP_DUPLICATE_ACCOUNT) atype = ATYPE_NORMAL_ACCOUNT;
+ else if (uf & UF_SERVER_TRUST_ACCOUNT) atype = ATYPE_WORKSTATION_TRUST;
+ else if (uf & UF_WORKSTATION_TRUST_ACCOUNT) atype = ATYPE_WORKSTATION_TRUST;
+ else if (uf & UF_INTERDOMAIN_TRUST_ACCOUNT) atype = ATYPE_INTERDOMAIN_TRUST;
+
+ return atype;
+}
+
+/*
+get the accountType from the groupType
+*/
+uint32_t samdb_gtype2atype(uint32_t gtype)
+{
+ uint32_t atype = 0x00000000;
+
+ switch(gtype) {
+ case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+ atype = ATYPE_SECURITY_LOCAL_GROUP;
+ break;
+ case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
+ atype = ATYPE_SECURITY_LOCAL_GROUP;
+ break;
+ case GTYPE_SECURITY_GLOBAL_GROUP:
+ atype = ATYPE_SECURITY_GLOBAL_GROUP;
+ break;
+
+ case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
+ atype = ATYPE_DISTRIBUTION_GLOBAL_GROUP;
+ break;
+ case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
+ atype = ATYPE_DISTRIBUTION_UNIVERSAL_GROUP;
+ break;
+ case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
+ atype = ATYPE_DISTRIBUTION_LOCAL_GROUP;
+ break;
+ }
+
+ return atype;
+}
+
+/* turn a sAMAccountType into a SID_NAME_USE */
+enum lsa_SidType samdb_atype_map(uint32_t atype)
+{
+ switch (atype & 0xF0000000) {
+ case ATYPE_GLOBAL_GROUP:
+ return SID_NAME_DOM_GRP;
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ return SID_NAME_ALIAS;
+ case ATYPE_ACCOUNT:
+ return SID_NAME_USER;
+ default:
+ DEBUG(1,("hmm, need to map account type 0x%x\n", atype));
+ }
+ return SID_NAME_UNKNOWN;
+}
diff --git a/source4/dsdb/common/flags.h b/source4/dsdb/common/flags.h
new file mode 100644
index 0000000000..dd8081732c
--- /dev/null
+++ b/source4/dsdb/common/flags.h
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+ User/Group specific flags
+
+ Copyright (C) Andrew Tridgell 2001-2003
+
+ 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/>.
+*/
+
+/* UserFlags for userAccountControl */
+#define UF_SCRIPT 0x00000001 /* NT or Lan Manager Login script must be executed */
+#define UF_ACCOUNTDISABLE 0x00000002
+#define UF_00000004 0x00000004
+#define UF_HOMEDIR_REQUIRED 0x00000008
+
+#define UF_LOCKOUT 0x00000010
+#define UF_PASSWD_NOTREQD 0x00000020
+#define UF_PASSWD_CANT_CHANGE 0x00000040
+#define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x00000080
+
+#define UF_TEMP_DUPLICATE_ACCOUNT 0x00000100 /* Local user account in usrmgr */
+#define UF_NORMAL_ACCOUNT 0x00000200
+#define UF_00000400 0x00000400
+#define UF_INTERDOMAIN_TRUST_ACCOUNT 0x00000800
+
+#define UF_WORKSTATION_TRUST_ACCOUNT 0x00001000
+#define UF_SERVER_TRUST_ACCOUNT 0x00002000
+#define UF_00004000 0x00004000
+#define UF_00008000 0x00008000
+
+#define UF_DONT_EXPIRE_PASSWD 0x00010000
+#define UF_MNS_LOGON_ACCOUNT 0x00020000
+#define UF_SMARTCARD_REQUIRED 0x00040000
+#define UF_TRUSTED_FOR_DELEGATION 0x00080000
+
+#define UF_NOT_DELEGATED 0x00100000
+#define UF_USE_DES_KEY_ONLY 0x00200000
+#define UF_DONT_REQUIRE_PREAUTH 0x00400000
+#define UF_PASSWORD_EXPIRED 0x00800000
+
+#define UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION 0x01000000
+#define UF_NO_AUTH_DATA_REQUIRED 0x02000000
+
+/* sAMAccountType */
+#define ATYPE_NORMAL_ACCOUNT 0x30000000 /* 805306368 */
+#define ATYPE_WORKSTATION_TRUST 0x30000001 /* 805306369 */
+#define ATYPE_INTERDOMAIN_TRUST 0x30000002 /* 805306370 */
+#define ATYPE_SECURITY_GLOBAL_GROUP 0x10000000 /* 268435456 */
+#define ATYPE_DISTRIBUTION_GLOBAL_GROUP 0x10000001 /* 268435457 */
+#define ATYPE_DISTRIBUTION_UNIVERSAL_GROUP ATYPE_DISTRIBUTION_GLOBAL_GROUP
+#define ATYPE_SECURITY_LOCAL_GROUP 0x20000000 /* 536870912 */
+#define ATYPE_DISTRIBUTION_LOCAL_GROUP 0x20000001 /* 536870913 */
+
+#define ATYPE_ACCOUNT ATYPE_NORMAL_ACCOUNT /* 0x30000000 805306368 */
+#define ATYPE_GLOBAL_GROUP ATYPE_SECURITY_GLOBAL_GROUP /* 0x10000000 268435456 */
+#define ATYPE_LOCAL_GROUP ATYPE_SECURITY_LOCAL_GROUP /* 0x20000000 536870912 */
+
+/* groupType */
+#define GROUP_TYPE_BUILTIN_LOCAL_GROUP 0x00000001
+#define GROUP_TYPE_ACCOUNT_GROUP 0x00000002
+#define GROUP_TYPE_RESOURCE_GROUP 0x00000004
+#define GROUP_TYPE_UNIVERSAL_GROUP 0x00000008
+#define GROUP_TYPE_APP_BASIC_GROUP 0x00000010
+#define GROUP_TYPE_APP_QUERY_GROUP 0x00000020
+#define GROUP_TYPE_SECURITY_ENABLED 0x80000000
+
+#define GTYPE_SECURITY_BUILTIN_LOCAL_GROUP ( \
+ /* 0x80000005 -2147483643 */ \
+ GROUP_TYPE_BUILTIN_LOCAL_GROUP| \
+ GROUP_TYPE_RESOURCE_GROUP| \
+ GROUP_TYPE_SECURITY_ENABLED \
+ )
+#define GTYPE_SECURITY_DOMAIN_LOCAL_GROUP ( \
+ /* 0x80000004 -2147483644 */ \
+ GROUP_TYPE_RESOURCE_GROUP| \
+ GROUP_TYPE_SECURITY_ENABLED \
+ )
+#define GTYPE_SECURITY_GLOBAL_GROUP ( \
+ /* 0x80000002 -2147483646 */ \
+ GROUP_TYPE_ACCOUNT_GROUP| \
+ GROUP_TYPE_SECURITY_ENABLED \
+ )
+#define GTYPE_SECURITY_UNIVERSAL_GROUP ( \
+ /* 0x80000008 -2147483656 */ \
+ GROUP_TYPE_UNIVERSAL_GROUP| \
+ GROUP_TYPE_SECURITY_ENABLED \
+ )
+#define GTYPE_DISTRIBUTION_GLOBAL_GROUP 0x00000002 /* 2 */
+#define GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP 0x00000004 /* 4 */
+#define GTYPE_DISTRIBUTION_UNIVERSAL_GROUP 0x00000008 /* 8 */
+
+#define INSTANCE_TYPE_IS_NC_HEAD 0x00000001
+#define INSTANCE_TYPE_UNINSTANT 0x00000002
+#define INSTANCE_TYPE_WRITE 0x00000004
+#define INSTANCE_TYPE_NC_ABOVE 0x00000008
+#define INSTANCE_TYPE_NC_COMING 0x00000010
+#define INSTANCE_TYPE_NC_GOING 0x00000020
+
+#define SYSTEM_FLAG_CR_NTDS_NC 0x00000001
+#define SYSTEM_FLAG_CR_NTDS_DOMAIN 0x00000002
+#define SYSTEM_FLAG_CR_NTDS_NOT_GC_REPLICATED 0x00000004
+#define SYSTEM_FLAG_SCHEMA_BASE_OBJECT 0x00000010
+#define SYSTEM_FLAG_ATTR_IS_RDN 0x00000020
+#define SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE 0x02000000
+#define SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE 0x04000000
+#define SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME 0x08000000
+#define SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE 0x10000000
+#define SYSTEM_FLAG_CONFIG_ALLOW_MOVE 0x20000000
+#define SYSTEM_FLAG_CONFIG_ALLOW_RENAME 0x40000000
+#define SYSTEM_FLAG_DISALLOW_DELTE 0x80000000
+
+#define SEARCH_FLAG_ATTINDEX 0x0000001
+#define SEARCH_FLAG_PDNTATTINDEX 0x0000002
+#define SEARCH_FLAG_ANR 0x0000004
+#define SEARCH_FLAG_PRESERVEONDELETE 0x0000008
+#define SEARCH_FLAG_COPY 0x0000010
+#define SEARCH_FLAG_TUPLEINDEX 0x0000020
+#define SEARCH_FLAG_SUBTREEATTRINDEX 0x0000040
+#define SEARCH_FLAG_CONFIDENTIAL 0x0000080
+#define SEARCH_FLAG_NEVERVALUEAUDIT 0x0000100
+#define SEARCH_FLAG_RODC_ATTRIBUTE 0x0000200
+
+#define DS_BEHAVIOR_WIN2000 0
+#define DS_BEHAVIOR_WIN2003_INTERIM 1
+#define DS_BEHAVIOR_WIN2003 2
+#define DS_BEHAVIOR_WIN2008 3
diff --git a/source4/dsdb/common/sidmap.c b/source4/dsdb/common/sidmap.c
new file mode 100644
index 0000000000..20bba7a0d9
--- /dev/null
+++ b/source4/dsdb/common/sidmap.c
@@ -0,0 +1,612 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ mapping routines for SID <-> unix uid/gid
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "system/passwd.h"
+#include "dsdb/common/flags.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "lib/ldb/include/ldb.h"
+#include "util/util_ldb.h"
+#include "libcli/security/security.h"
+#include "param/param.h"
+
+/*
+ these are used for the fallback local uid/gid to sid mapping
+ code.
+*/
+#define SIDMAP_LOCAL_USER_BASE 0x80000000
+#define SIDMAP_LOCAL_GROUP_BASE 0xC0000000
+#define SIDMAP_MAX_LOCAL_UID 0x3fffffff
+#define SIDMAP_MAX_LOCAL_GID 0x3fffffff
+
+/*
+ private context for sid mapping routines
+*/
+struct sidmap_context {
+ struct ldb_context *samctx;
+};
+
+/*
+ open a sidmap context - use talloc_free to close
+*/
+struct sidmap_context *sidmap_open(TALLOC_CTX *mem_ctx, struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx)
+{
+ struct sidmap_context *sidmap;
+ sidmap = talloc(mem_ctx, struct sidmap_context);
+ if (sidmap == NULL) {
+ return NULL;
+ }
+ sidmap->samctx = samdb_connect(sidmap, ev_ctx, lp_ctx, system_session(sidmap, lp_ctx));
+ if (sidmap->samctx == NULL) {
+ talloc_free(sidmap);
+ return NULL;
+ }
+
+ return sidmap;
+}
+
+
+/*
+ check the sAMAccountType field of a search result to see if
+ the account is a user account
+*/
+static bool is_user_account(struct ldb_message *res)
+{
+ uint_t atype = samdb_result_uint(res, "sAMAccountType", 0);
+ if (atype && (!(atype & ATYPE_ACCOUNT))) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ check the sAMAccountType field of a search result to see if
+ the account is a group account
+*/
+static bool is_group_account(struct ldb_message *res)
+{
+ uint_t atype = samdb_result_uint(res, "sAMAccountType", 0);
+ if (atype && atype == ATYPE_NORMAL_ACCOUNT) {
+ return false;
+ }
+ return true;
+}
+
+
+
+/*
+ return the dom_sid of our primary domain
+*/
+static NTSTATUS sidmap_primary_domain_sid(struct sidmap_context *sidmap,
+ TALLOC_CTX *mem_ctx, struct dom_sid **sid)
+{
+ const char *attrs[] = { "objectSid", NULL };
+ int ret;
+ struct ldb_message **res = NULL;
+
+ ret = gendb_search_dn(sidmap->samctx, mem_ctx, NULL, &res, attrs);
+ if (ret != 1) {
+ talloc_free(res);
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ *sid = samdb_result_dom_sid(mem_ctx, res[0], "objectSid");
+ talloc_free(res);
+ if (*sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ map a sid to a unix uid
+*/
+NTSTATUS sidmap_sid_to_unixuid(struct sidmap_context *sidmap,
+ const struct dom_sid *sid, uid_t *uid)
+{
+ const char *attrs[] = { "sAMAccountName", "uidNumber",
+ "sAMAccountType", "unixName", NULL };
+ int ret;
+ const char *s;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message **res;
+ struct dom_sid *domain_sid;
+ NTSTATUS status;
+
+ tmp_ctx = talloc_new(sidmap);
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "objectSid=%s",
+ ldap_encode_ndr_dom_sid(tmp_ctx, sid));
+
+ if (ret != 1) {
+ goto allocated_sid;
+ }
+
+ /* make sure its a user, not a group */
+ if (!is_user_account(res[0])) {
+ DEBUG(0,("sid_to_unixuid: sid %s is not an account!\n",
+ dom_sid_string(tmp_ctx, sid)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_SID;
+ }
+
+ /* first try to get the uid directly */
+ s = samdb_result_string(res[0], "uidNumber", NULL);
+ if (s != NULL) {
+ *uid = strtoul(s, NULL, 0);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ /* next try via the UnixName attribute */
+ s = samdb_result_string(res[0], "unixName", NULL);
+ if (s != NULL) {
+ struct passwd *pwd = getpwnam(s);
+ if (!pwd) {
+ DEBUG(0,("unixName %s for sid %s does not exist as a local user\n", s,
+ dom_sid_string(tmp_ctx, sid)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ *uid = pwd->pw_uid;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ /* finally try via the sAMAccountName attribute */
+ s = samdb_result_string(res[0], "sAMAccountName", NULL);
+ if (s != NULL) {
+ struct passwd *pwd = getpwnam(s);
+ if (!pwd) {
+ DEBUG(0,("sAMAccountName '%s' for sid %s does not exist as a local user\n",
+ s, dom_sid_string(tmp_ctx, sid)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ *uid = pwd->pw_uid;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+
+allocated_sid:
+ status = sidmap_primary_domain_sid(sidmap, tmp_ctx, &domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ if (dom_sid_in_domain(domain_sid, sid)) {
+ uint32_t rid = sid->sub_auths[sid->num_auths-1];
+ if (rid >= SIDMAP_LOCAL_USER_BASE &&
+ rid < SIDMAP_LOCAL_GROUP_BASE) {
+ *uid = rid - SIDMAP_LOCAL_USER_BASE;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+ }
+
+
+ DEBUG(0,("sid_to_unixuid: no uidNumber, unixName or sAMAccountName for sid %s\n",
+ dom_sid_string(tmp_ctx, sid)));
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NONE_MAPPED;
+}
+
+
+/*
+ see if a sid is a group - very inefficient!
+*/
+bool sidmap_sid_is_group(struct sidmap_context *sidmap, struct dom_sid *sid)
+{
+ const char *attrs[] = { "sAMAccountType", NULL };
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message **res;
+ NTSTATUS status;
+ struct dom_sid *domain_sid;
+ bool is_group;
+
+ tmp_ctx = talloc_new(sidmap);
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "objectSid=%s", ldap_encode_ndr_dom_sid(tmp_ctx, sid));
+ if (ret == 1) {
+ is_group = is_group_account(res[0]);
+ talloc_free(tmp_ctx);
+ return is_group;
+ }
+
+ status = sidmap_primary_domain_sid(sidmap, tmp_ctx, &domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ if (dom_sid_in_domain(domain_sid, sid)) {
+ uint32_t rid = sid->sub_auths[sid->num_auths-1];
+ if (rid >= SIDMAP_LOCAL_GROUP_BASE) {
+ talloc_free(tmp_ctx);
+ return true;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/*
+ map a sid to a unix gid
+*/
+NTSTATUS sidmap_sid_to_unixgid(struct sidmap_context *sidmap,
+ const struct dom_sid *sid, gid_t *gid)
+{
+ const char *attrs[] = { "sAMAccountName", "gidNumber",
+ "unixName", "sAMAccountType", NULL };
+ int ret;
+ const char *s;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message **res;
+ NTSTATUS status;
+ struct dom_sid *domain_sid;
+
+ tmp_ctx = talloc_new(sidmap);
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "objectSid=%s", ldap_encode_ndr_dom_sid(tmp_ctx, sid));
+ if (ret != 1) {
+ goto allocated_sid;
+ }
+
+ /* make sure its not a user */
+ if (!is_group_account(res[0])) {
+ DEBUG(0,("sid_to_unixgid: sid %s is a ATYPE_NORMAL_ACCOUNT\n",
+ dom_sid_string(tmp_ctx, sid)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_SID;
+ }
+
+ /* first try to get the gid directly */
+ s = samdb_result_string(res[0], "gidNumber", NULL);
+ if (s != NULL) {
+ *gid = strtoul(s, NULL, 0);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ /* next try via the UnixName attribute */
+ s = samdb_result_string(res[0], "unixName", NULL);
+ if (s != NULL) {
+ struct group *grp = getgrnam(s);
+ if (!grp) {
+ DEBUG(0,("unixName '%s' for sid %s does not exist as a local group\n",
+ s, dom_sid_string(tmp_ctx, sid)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+ *gid = grp->gr_gid;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ /* finally try via the sAMAccountName attribute */
+ s = samdb_result_string(res[0], "sAMAccountName", NULL);
+ if (s != NULL) {
+ struct group *grp = getgrnam(s);
+ if (!grp) {
+ DEBUG(0,("sAMAccountName '%s' for sid %s does not exist as a local group\n", s, dom_sid_string(tmp_ctx, sid)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+ *gid = grp->gr_gid;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+allocated_sid:
+ status = sidmap_primary_domain_sid(sidmap, tmp_ctx, &domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ if (dom_sid_in_domain(domain_sid, sid)) {
+ uint32_t rid = sid->sub_auths[sid->num_auths-1];
+ if (rid >= SIDMAP_LOCAL_GROUP_BASE) {
+ *gid = rid - SIDMAP_LOCAL_GROUP_BASE;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+ }
+
+ DEBUG(0,("sid_to_unixgid: no gidNumber, unixName or sAMAccountName for sid %s\n",
+ dom_sid_string(tmp_ctx, sid)));
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NONE_MAPPED;
+}
+
+
+/*
+ map a unix uid to a dom_sid
+ the returned sid is allocated in the supplied mem_ctx
+*/
+NTSTATUS sidmap_uid_to_sid(struct sidmap_context *sidmap,
+ TALLOC_CTX *mem_ctx,
+ const uid_t uid, struct dom_sid **sid)
+{
+ const char *attrs[] = { "sAMAccountName", "objectSid", "sAMAccountType", NULL };
+ int ret, i;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message **res;
+ struct passwd *pwd;
+ struct dom_sid *domain_sid;
+ NTSTATUS status;
+
+ /*
+ we search for the mapping in the following order:
+
+ - check if the uid is in the dynamic uid range assigned for winbindd
+ use. If it is, then look in winbindd sid mapping
+ database (not implemented yet)
+ - look for a user account in samdb that has uidNumber set to the
+ given uid
+ - look for a user account in samdb that has unixName or
+ sAMAccountName set to the name given by getpwuid()
+ - assign a SID by adding the uid to SIDMAP_LOCAL_USER_BASE in the local
+ domain
+ */
+
+
+ tmp_ctx = talloc_new(mem_ctx);
+
+
+ /*
+ step 2: look for a user account in samdb that has uidNumber set to the
+ given uid
+ */
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "uidNumber=%u", (unsigned int)uid);
+ for (i=0;i<ret;i++) {
+ if (!is_user_account(res[i])) continue;
+
+ *sid = samdb_result_dom_sid(mem_ctx, res[i], "objectSid");
+ talloc_free(tmp_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(*sid);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ step 3: look for a user account in samdb that has unixName
+ or sAMAccountName set to the name given by getpwuid()
+ */
+ pwd = getpwuid(uid);
+ if (pwd == NULL) {
+ goto allocate_sid;
+ }
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "(|(unixName=%s)(sAMAccountName=%s))",
+ pwd->pw_name, pwd->pw_name);
+ for (i=0;i<ret;i++) {
+ if (!is_user_account(res[i])) continue;
+
+ *sid = samdb_result_dom_sid(mem_ctx, res[i], "objectSid");
+ talloc_free(tmp_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(*sid);
+ return NT_STATUS_OK;
+ }
+
+
+ /*
+ step 4: assign a SID by adding the uid to
+ SIDMAP_LOCAL_USER_BASE in the local domain
+ */
+allocate_sid:
+ if (uid > SIDMAP_MAX_LOCAL_UID) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ status = sidmap_primary_domain_sid(sidmap, tmp_ctx, &domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ *sid = dom_sid_add_rid(mem_ctx, domain_sid, SIDMAP_LOCAL_USER_BASE + uid);
+ talloc_free(tmp_ctx);
+
+ if (*sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ map a unix gid to a dom_sid
+ the returned sid is allocated in the supplied mem_ctx
+*/
+NTSTATUS sidmap_gid_to_sid(struct sidmap_context *sidmap,
+ TALLOC_CTX *mem_ctx,
+ const gid_t gid, struct dom_sid **sid)
+{
+ const char *attrs[] = { "sAMAccountName", "objectSid", "sAMAccountType", NULL };
+ int ret, i;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message **res;
+ struct group *grp;
+ struct dom_sid *domain_sid;
+ NTSTATUS status;
+
+ /*
+ we search for the mapping in the following order:
+
+ - check if the gid is in the dynamic gid range assigned for winbindd
+ use. If it is, then look in winbindd sid mapping
+ database (not implemented yet)
+ - look for a group account in samdb that has gidNumber set to the
+ given gid
+ - look for a group account in samdb that has unixName or
+ sAMAccountName set to the name given by getgrgid()
+ - assign a SID by adding the gid to SIDMAP_LOCAL_GROUP_BASE in the local
+ domain
+ */
+
+
+ tmp_ctx = talloc_new(sidmap);
+
+
+ /*
+ step 2: look for a group account in samdb that has gidNumber set to the
+ given gid
+ */
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "gidNumber=%u", (unsigned int)gid);
+ for (i=0;i<ret;i++) {
+ if (!is_group_account(res[i])) continue;
+
+ *sid = samdb_result_dom_sid(mem_ctx, res[i], "objectSid");
+ talloc_free(tmp_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(*sid);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ step 3: look for a group account in samdb that has unixName
+ or sAMAccountName set to the name given by getgrgid()
+ */
+ grp = getgrgid(gid);
+ if (grp == NULL) {
+ goto allocate_sid;
+ }
+
+ ret = gendb_search(sidmap->samctx, tmp_ctx, NULL, &res, attrs,
+ "(|(unixName=%s)(sAMAccountName=%s))",
+ grp->gr_name, grp->gr_name);
+ for (i=0;i<ret;i++) {
+ if (!is_group_account(res[i])) continue;
+
+ *sid = samdb_result_dom_sid(mem_ctx, res[i], "objectSid");
+ talloc_free(tmp_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(*sid);
+ return NT_STATUS_OK;
+ }
+
+
+ /*
+ step 4: assign a SID by adding the gid to
+ SIDMAP_LOCAL_GROUP_BASE in the local domain
+ */
+allocate_sid:
+ if (gid > SIDMAP_MAX_LOCAL_GID) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ status = sidmap_primary_domain_sid(sidmap, tmp_ctx, &domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ *sid = dom_sid_add_rid(mem_ctx, domain_sid, SIDMAP_LOCAL_GROUP_BASE + gid);
+ talloc_free(tmp_ctx);
+
+ if (*sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ check if a sid is in the range of auto-allocated SIDs from our primary domain,
+ and if it is, then return the name and atype
+*/
+NTSTATUS sidmap_allocated_sid_lookup(struct sidmap_context *sidmap,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ const char **name,
+ enum lsa_SidType *rtype)
+{
+ NTSTATUS status;
+ struct dom_sid *domain_sid;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ uint32_t rid, atype;
+
+ status = sidmap_primary_domain_sid(sidmap, tmp_ctx, &domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ if (!dom_sid_in_domain(domain_sid, sid)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ talloc_free(tmp_ctx);
+
+ rid = sid->sub_auths[sid->num_auths-1];
+ if (rid < SIDMAP_LOCAL_USER_BASE) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if (rid < SIDMAP_LOCAL_GROUP_BASE) {
+ struct passwd *pwd;
+ uid_t uid = rid - SIDMAP_LOCAL_USER_BASE;
+ atype = ATYPE_NORMAL_ACCOUNT;
+ *rtype = samdb_atype_map(atype);
+
+ pwd = getpwuid(uid);
+ if (pwd == NULL) {
+ *name = talloc_asprintf(mem_ctx, "uid%u", uid);
+ } else {
+ *name = talloc_strdup(mem_ctx, pwd->pw_name);
+ }
+ } else {
+ struct group *grp;
+ gid_t gid = rid - SIDMAP_LOCAL_GROUP_BASE;
+ atype = ATYPE_LOCAL_GROUP;
+ *rtype = samdb_atype_map(atype);
+ grp = getgrgid(gid);
+ if (grp == NULL) {
+ *name = talloc_asprintf(mem_ctx, "gid%u", gid);
+ } else {
+ *name = talloc_strdup(mem_ctx, grp->gr_name);
+ }
+ }
+
+ if (*name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c
new file mode 100644
index 0000000000..fa8276e7b4
--- /dev/null
+++ b/source4/dsdb/common/util.c
@@ -0,0 +1,1994 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Volker Lendecke 2004
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+ Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
+
+ 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 "events.h"
+#include "ldb.h"
+#include "ldb_errors.h"
+#include "lib/util/util_ldb.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/common/flags.h"
+#include "dsdb/common/proto.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "param/param.h"
+#include "libcli/auth/libcli_auth.h"
+
+/*
+ search the sam for the specified attributes in a specific domain, filter on
+ objectSid being in domain_sid.
+*/
+int samdb_search_domain(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ struct ldb_message ***res,
+ const char * const *attrs,
+ const struct dom_sid *domain_sid,
+ const char *format, ...) _PRINTF_ATTRIBUTE(7,8)
+{
+ va_list ap;
+ int i, count;
+
+ va_start(ap, format);
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn,
+ res, attrs, format, ap);
+ va_end(ap);
+
+ i=0;
+
+ while (i<count) {
+ struct dom_sid *entry_sid;
+
+ entry_sid = samdb_result_dom_sid(mem_ctx, (*res)[i], "objectSid");
+
+ if ((entry_sid == NULL) ||
+ (!dom_sid_in_domain(domain_sid, entry_sid))) {
+ /* Delete that entry from the result set */
+ (*res)[i] = (*res)[count-1];
+ count -= 1;
+ talloc_free(entry_sid);
+ continue;
+ }
+ talloc_free(entry_sid);
+ i += 1;
+ }
+
+ return count;
+}
+
+/*
+ search the sam for a single string attribute in exactly 1 record
+*/
+const char *samdb_search_string_v(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ const char *attr_name,
+ const char *format, va_list ap) _PRINTF_ATTRIBUTE(5,0)
+{
+ int count;
+ const char *attrs[2] = { NULL, NULL };
+ struct ldb_message **res = NULL;
+
+ attrs[0] = attr_name;
+
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, attrs, format, ap);
+ if (count > 1) {
+ DEBUG(1,("samdb: search for %s %s not single valued (count=%d)\n",
+ attr_name, format, count));
+ }
+ if (count != 1) {
+ talloc_free(res);
+ return NULL;
+ }
+
+ return samdb_result_string(res[0], attr_name, NULL);
+}
+
+
+/*
+ search the sam for a single string attribute in exactly 1 record
+*/
+const char *samdb_search_string(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ const char *attr_name,
+ const char *format, ...) _PRINTF_ATTRIBUTE(5,6)
+{
+ va_list ap;
+ const char *str;
+
+ va_start(ap, format);
+ str = samdb_search_string_v(sam_ldb, mem_ctx, basedn, attr_name, format, ap);
+ va_end(ap);
+
+ return str;
+}
+
+struct ldb_dn *samdb_search_dn(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ const char *format, ...) _PRINTF_ATTRIBUTE(4,5)
+{
+ va_list ap;
+ struct ldb_dn *ret;
+ struct ldb_message **res = NULL;
+ int count;
+
+ va_start(ap, format);
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, NULL, format, ap);
+ va_end(ap);
+
+ if (count != 1) return NULL;
+
+ ret = talloc_steal(mem_ctx, res[0]->dn);
+ talloc_free(res);
+
+ return ret;
+}
+
+/*
+ search the sam for a dom_sid attribute in exactly 1 record
+*/
+struct dom_sid *samdb_search_dom_sid(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ const char *attr_name,
+ const char *format, ...) _PRINTF_ATTRIBUTE(5,6)
+{
+ va_list ap;
+ int count;
+ struct ldb_message **res;
+ const char *attrs[2] = { NULL, NULL };
+ struct dom_sid *sid;
+
+ attrs[0] = attr_name;
+
+ va_start(ap, format);
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, attrs, format, ap);
+ va_end(ap);
+ if (count > 1) {
+ DEBUG(1,("samdb: search for %s %s not single valued (count=%d)\n",
+ attr_name, format, count));
+ }
+ if (count != 1) {
+ talloc_free(res);
+ return NULL;
+ }
+ sid = samdb_result_dom_sid(mem_ctx, res[0], attr_name);
+ talloc_free(res);
+ return sid;
+}
+
+/*
+ return the count of the number of records in the sam matching the query
+*/
+int samdb_search_count(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ const char *format, ...) _PRINTF_ATTRIBUTE(4,5)
+{
+ va_list ap;
+ struct ldb_message **res;
+ const char * const attrs[] = { NULL };
+ int ret;
+
+ va_start(ap, format);
+ ret = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, attrs, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+
+/*
+ search the sam for a single integer attribute in exactly 1 record
+*/
+uint_t samdb_search_uint(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ uint_t default_value,
+ struct ldb_dn *basedn,
+ const char *attr_name,
+ const char *format, ...) _PRINTF_ATTRIBUTE(6,7)
+{
+ va_list ap;
+ int count;
+ struct ldb_message **res;
+ const char *attrs[2] = { NULL, NULL };
+
+ attrs[0] = attr_name;
+
+ va_start(ap, format);
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, attrs, format, ap);
+ va_end(ap);
+
+ if (count != 1) {
+ return default_value;
+ }
+
+ return samdb_result_uint(res[0], attr_name, default_value);
+}
+
+/*
+ search the sam for a single signed 64 bit integer attribute in exactly 1 record
+*/
+int64_t samdb_search_int64(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ int64_t default_value,
+ struct ldb_dn *basedn,
+ const char *attr_name,
+ const char *format, ...) _PRINTF_ATTRIBUTE(6,7)
+{
+ va_list ap;
+ int count;
+ struct ldb_message **res;
+ const char *attrs[2] = { NULL, NULL };
+
+ attrs[0] = attr_name;
+
+ va_start(ap, format);
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, attrs, format, ap);
+ va_end(ap);
+
+ if (count != 1) {
+ return default_value;
+ }
+
+ return samdb_result_int64(res[0], attr_name, default_value);
+}
+
+/*
+ search the sam for multipe records each giving a single string attribute
+ return the number of matches, or -1 on error
+*/
+int samdb_search_string_multiple(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *basedn,
+ const char ***strs,
+ const char *attr_name,
+ const char *format, ...) _PRINTF_ATTRIBUTE(6,7)
+{
+ va_list ap;
+ int count, i;
+ const char *attrs[2] = { NULL, NULL };
+ struct ldb_message **res = NULL;
+
+ attrs[0] = attr_name;
+
+ va_start(ap, format);
+ count = gendb_search_v(sam_ldb, mem_ctx, basedn, &res, attrs, format, ap);
+ va_end(ap);
+
+ if (count <= 0) {
+ return count;
+ }
+
+ /* make sure its single valued */
+ for (i=0;i<count;i++) {
+ if (res[i]->num_elements != 1) {
+ DEBUG(1,("samdb: search for %s %s not single valued\n",
+ attr_name, format));
+ talloc_free(res);
+ return -1;
+ }
+ }
+
+ *strs = talloc_array(mem_ctx, const char *, count+1);
+ if (! *strs) {
+ talloc_free(res);
+ return -1;
+ }
+
+ for (i=0;i<count;i++) {
+ (*strs)[i] = samdb_result_string(res[i], attr_name, NULL);
+ }
+ (*strs)[count] = NULL;
+
+ return count;
+}
+
+/*
+ pull a uint from a result set.
+*/
+uint_t samdb_result_uint(const struct ldb_message *msg, const char *attr, uint_t default_value)
+{
+ return ldb_msg_find_attr_as_uint(msg, attr, default_value);
+}
+
+/*
+ pull a (signed) int64 from a result set.
+*/
+int64_t samdb_result_int64(const struct ldb_message *msg, const char *attr, int64_t default_value)
+{
+ return ldb_msg_find_attr_as_int64(msg, attr, default_value);
+}
+
+/*
+ pull a string from a result set.
+*/
+const char *samdb_result_string(const struct ldb_message *msg, const char *attr,
+ const char *default_value)
+{
+ return ldb_msg_find_attr_as_string(msg, attr, default_value);
+}
+
+struct ldb_dn *samdb_result_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
+ const char *attr, struct ldb_dn *default_value)
+{
+ struct ldb_dn *ret_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, msg, attr);
+ if (!ret_dn) {
+ return default_value;
+ }
+ return ret_dn;
+}
+
+/*
+ pull a rid from a objectSid in a result set.
+*/
+uint32_t samdb_result_rid_from_sid(TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
+ const char *attr, uint32_t default_value)
+{
+ struct dom_sid *sid;
+ uint32_t rid;
+
+ sid = samdb_result_dom_sid(mem_ctx, msg, attr);
+ if (sid == NULL) {
+ return default_value;
+ }
+ rid = sid->sub_auths[sid->num_auths-1];
+ talloc_free(sid);
+ return rid;
+}
+
+/*
+ pull a dom_sid structure from a objectSid in a result set.
+*/
+struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
+ const char *attr)
+{
+ const struct ldb_val *v;
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+ v = ldb_msg_find_ldb_val(msg, attr);
+ if (v == NULL) {
+ return NULL;
+ }
+ sid = talloc(mem_ctx, struct dom_sid);
+ if (sid == NULL) {
+ return NULL;
+ }
+ ndr_err = ndr_pull_struct_blob(v, sid, NULL, sid,
+ (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(sid);
+ return NULL;
+ }
+ return sid;
+}
+
+/*
+ pull a guid structure from a objectGUID in a result set.
+*/
+struct GUID samdb_result_guid(const struct ldb_message *msg, const char *attr)
+{
+ const struct ldb_val *v;
+ enum ndr_err_code ndr_err;
+ struct GUID guid;
+ TALLOC_CTX *mem_ctx;
+
+ ZERO_STRUCT(guid);
+
+ v = ldb_msg_find_ldb_val(msg, attr);
+ if (!v) return guid;
+
+ mem_ctx = talloc_named_const(NULL, 0, "samdb_result_guid");
+ if (!mem_ctx) return guid;
+ ndr_err = ndr_pull_struct_blob(v, mem_ctx, NULL, &guid,
+ (ndr_pull_flags_fn_t)ndr_pull_GUID);
+ talloc_free(mem_ctx);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return guid;
+ }
+
+ return guid;
+}
+
+/*
+ pull a sid prefix from a objectSid in a result set.
+ this is used to find the domain sid for a user
+*/
+struct dom_sid *samdb_result_sid_prefix(TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
+ const char *attr)
+{
+ struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, msg, attr);
+ if (!sid || sid->num_auths < 1) return NULL;
+ sid->num_auths--;
+ return sid;
+}
+
+/*
+ pull a NTTIME in a result set.
+*/
+NTTIME samdb_result_nttime(struct ldb_message *msg, const char *attr, NTTIME default_value)
+{
+ return ldb_msg_find_attr_as_uint64(msg, attr, default_value);
+}
+
+/*
+ * Windows uses both 0 and 9223372036854775807 (0x7FFFFFFFFFFFFFFFULL) to
+ * indicate an account doesn't expire.
+ *
+ * When Windows initially creates an account, it sets
+ * accountExpires = 9223372036854775807 (0x7FFFFFFFFFFFFFFF). However,
+ * when changing from an account having a specific expiration date to
+ * that account never expiring, it sets accountExpires = 0.
+ *
+ * Consolidate that logic here to allow clearer logic for account expiry in
+ * the rest of the code.
+ */
+NTTIME samdb_result_account_expires(struct ldb_message *msg)
+{
+ NTTIME ret = ldb_msg_find_attr_as_uint64(msg, "accountExpires",
+ 0);
+
+ if (ret == 0)
+ ret = 0x7FFFFFFFFFFFFFFFULL;
+
+ return ret;
+}
+
+/*
+ pull a uint64_t from a result set.
+*/
+uint64_t samdb_result_uint64(struct ldb_message *msg, const char *attr, uint64_t default_value)
+{
+ return ldb_msg_find_attr_as_uint64(msg, attr, default_value);
+}
+
+
+/*
+ construct the allow_password_change field from the PwdLastSet attribute and the
+ domain password settings
+*/
+NTTIME samdb_result_allow_password_change(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ const char *attr)
+{
+ uint64_t attr_time = samdb_result_uint64(msg, attr, 0);
+ int64_t minPwdAge;
+
+ if (attr_time == 0) {
+ return 0;
+ }
+
+ minPwdAge = samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn, "minPwdAge", NULL);
+
+ /* yes, this is a -= not a += as minPwdAge is stored as the negative
+ of the number of 100-nano-seconds */
+ attr_time -= minPwdAge;
+
+ return attr_time;
+}
+
+/*
+ construct the force_password_change field from the PwdLastSet
+ attribute, the userAccountControl and the domain password settings
+*/
+NTTIME samdb_result_force_password_change(struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg)
+{
+ uint64_t attr_time = samdb_result_uint64(msg, "pwdLastSet", 0);
+ uint32_t userAccountControl = samdb_result_uint64(msg, "userAccountControl", 0);
+ int64_t maxPwdAge;
+
+ /* Machine accounts don't expire, and there is a flag for 'no expiry' */
+ if (!(userAccountControl & UF_NORMAL_ACCOUNT)
+ || (userAccountControl & UF_DONT_EXPIRE_PASSWD)) {
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ if (attr_time == 0) {
+ return 0;
+ }
+
+ maxPwdAge = samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn, "maxPwdAge", NULL);
+ if (maxPwdAge == 0) {
+ return 0x7FFFFFFFFFFFFFFFULL;
+ } else {
+ attr_time -= maxPwdAge;
+ }
+
+ return attr_time;
+}
+
+/*
+ pull a samr_Password structutre from a result set.
+*/
+struct samr_Password *samdb_result_hash(TALLOC_CTX *mem_ctx, struct ldb_message *msg, const char *attr)
+{
+ struct samr_Password *hash = NULL;
+ const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr);
+ if (val && (val->length >= sizeof(hash->hash))) {
+ hash = talloc(mem_ctx, struct samr_Password);
+ memcpy(hash->hash, val->data, MIN(val->length, sizeof(hash->hash)));
+ }
+ return hash;
+}
+
+/*
+ pull an array of samr_Password structutres from a result set.
+*/
+uint_t samdb_result_hashes(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr, struct samr_Password **hashes)
+{
+ uint_t count = 0;
+ const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr);
+ int i;
+
+ *hashes = NULL;
+ if (!val) {
+ return 0;
+ }
+ count = val->length / 16;
+ if (count == 0) {
+ return 0;
+ }
+
+ *hashes = talloc_array(mem_ctx, struct samr_Password, count);
+ if (! *hashes) {
+ return 0;
+ }
+
+ for (i=0;i<count;i++) {
+ memcpy((*hashes)[i].hash, (i*16)+(char *)val->data, 16);
+ }
+
+ return count;
+}
+
+NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ struct samr_Password **lm_pwd, struct samr_Password **nt_pwd)
+{
+ struct samr_Password *lmPwdHash, *ntPwdHash;
+ if (nt_pwd) {
+ int num_nt;
+ num_nt = samdb_result_hashes(mem_ctx, msg, "unicodePwd", &ntPwdHash);
+ if (num_nt == 0) {
+ *nt_pwd = NULL;
+ } else if (num_nt > 1) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ } else {
+ *nt_pwd = &ntPwdHash[0];
+ }
+ }
+ if (lm_pwd) {
+ int num_lm;
+ num_lm = samdb_result_hashes(mem_ctx, msg, "dBCSPwd", &lmPwdHash);
+ if (num_lm == 0) {
+ *lm_pwd = NULL;
+ } else if (num_lm > 1) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ } else {
+ *lm_pwd = &lmPwdHash[0];
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ pull a samr_LogonHours structutre from a result set.
+*/
+struct samr_LogonHours samdb_result_logon_hours(TALLOC_CTX *mem_ctx, struct ldb_message *msg, const char *attr)
+{
+ struct samr_LogonHours hours;
+ const int units_per_week = 168;
+ const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr);
+ ZERO_STRUCT(hours);
+ hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week);
+ if (!hours.bits) {
+ return hours;
+ }
+ hours.units_per_week = units_per_week;
+ memset(hours.bits, 0xFF, units_per_week);
+ if (val) {
+ memcpy(hours.bits, val->data, MIN(val->length, units_per_week));
+ }
+ return hours;
+}
+
+/*
+ pull a set of account_flags from a result set.
+
+ This requires that the attributes:
+ pwdLastSet
+ userAccountControl
+ be included in 'msg'
+*/
+uint32_t samdb_result_acct_flags(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg, struct ldb_dn *domain_dn)
+{
+ uint32_t userAccountControl = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
+ uint32_t acct_flags = samdb_uf2acb(userAccountControl);
+ NTTIME must_change_time;
+ NTTIME now;
+
+ must_change_time = samdb_result_force_password_change(sam_ctx, mem_ctx,
+ domain_dn, msg);
+
+ /* Test account expire time */
+ unix_to_nt_time(&now, time(NULL));
+ /* check for expired password */
+ if (must_change_time < now) {
+ acct_flags |= ACB_PW_EXPIRED;
+ }
+ return acct_flags;
+}
+
+
+/* Find an attribute, with a particular value */
+
+/* The current callers of this function expect a very specific
+ * behaviour: In particular, objectClass subclass equivilance is not
+ * wanted. This means that we should not lookup the schema for the
+ * comparison function */
+struct ldb_message_element *samdb_find_attribute(struct ldb_context *ldb,
+ const struct ldb_message *msg,
+ const char *name, const char *value)
+{
+ int i;
+ struct ldb_message_element *el = ldb_msg_find_element(msg, name);
+
+ if (!el) {
+ return NULL;
+ }
+
+ for (i=0;i<el->num_values;i++) {
+ if (ldb_attr_cmp(value, (char *)el->values[i].data) == 0) {
+ return el;
+ }
+ }
+
+ return NULL;
+}
+
+int samdb_find_or_add_value(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value)
+{
+ if (samdb_find_attribute(ldb, msg, name, set_value) == NULL) {
+ return samdb_msg_add_string(ldb, msg, msg, name, set_value);
+ }
+ return LDB_SUCCESS;
+}
+
+int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value)
+{
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(msg, name);
+ if (el) {
+ return LDB_SUCCESS;
+ }
+
+ return samdb_msg_add_string(ldb, msg, msg, name, set_value);
+}
+
+
+
+/*
+ add a string element to a message
+*/
+int samdb_msg_add_string(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, const char *str)
+{
+ char *s = talloc_strdup(mem_ctx, str);
+ char *a = talloc_strdup(mem_ctx, attr_name);
+ if (s == NULL || a == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ return ldb_msg_add_string(msg, a, s);
+}
+
+/*
+ add a dom_sid element to a message
+*/
+int samdb_msg_add_dom_sid(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, struct dom_sid *sid)
+{
+ struct ldb_val v;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(&v, mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(sam_ldb, "loadparm")),
+ sid,
+ (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return -1;
+ }
+ return ldb_msg_add_value(msg, attr_name, &v, NULL);
+}
+
+
+/*
+ add a delete element operation to a message
+*/
+int samdb_msg_add_delete(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name)
+{
+ /* we use an empty replace rather than a delete, as it allows for
+ samdb_replace() to be used everywhere */
+ return ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_REPLACE, NULL);
+}
+
+/*
+ add a add attribute value to a message
+*/
+int samdb_msg_add_addval(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, const char *value)
+{
+ struct ldb_message_element *el;
+ char *a, *v;
+ int ret;
+ a = talloc_strdup(mem_ctx, attr_name);
+ if (a == NULL)
+ return -1;
+ v = talloc_strdup(mem_ctx, value);
+ if (v == NULL)
+ return -1;
+ ret = ldb_msg_add_string(msg, a, v);
+ if (ret != 0)
+ return ret;
+ el = ldb_msg_find_element(msg, a);
+ if (el == NULL)
+ return -1;
+ el->flags = LDB_FLAG_MOD_ADD;
+ return 0;
+}
+
+/*
+ add a delete attribute value to a message
+*/
+int samdb_msg_add_delval(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, const char *value)
+{
+ struct ldb_message_element *el;
+ char *a, *v;
+ int ret;
+ a = talloc_strdup(mem_ctx, attr_name);
+ if (a == NULL)
+ return -1;
+ v = talloc_strdup(mem_ctx, value);
+ if (v == NULL)
+ return -1;
+ ret = ldb_msg_add_string(msg, a, v);
+ if (ret != 0)
+ return ret;
+ el = ldb_msg_find_element(msg, a);
+ if (el == NULL)
+ return -1;
+ el->flags = LDB_FLAG_MOD_DELETE;
+ return 0;
+}
+
+/*
+ add a int element to a message
+*/
+int samdb_msg_add_int(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, int v)
+{
+ const char *s = talloc_asprintf(mem_ctx, "%d", v);
+ return samdb_msg_add_string(sam_ldb, mem_ctx, msg, attr_name, s);
+}
+
+/*
+ add a uint_t element to a message
+*/
+int samdb_msg_add_uint(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, uint_t v)
+{
+ const char *s = talloc_asprintf(mem_ctx, "%u", v);
+ return samdb_msg_add_string(sam_ldb, mem_ctx, msg, attr_name, s);
+}
+
+/*
+ add a (signed) int64_t element to a message
+*/
+int samdb_msg_add_int64(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, int64_t v)
+{
+ const char *s = talloc_asprintf(mem_ctx, "%lld", (long long)v);
+ return samdb_msg_add_string(sam_ldb, mem_ctx, msg, attr_name, s);
+}
+
+/*
+ add a uint64_t element to a message
+*/
+int samdb_msg_add_uint64(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, uint64_t v)
+{
+ const char *s = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)v);
+ return samdb_msg_add_string(sam_ldb, mem_ctx, msg, attr_name, s);
+}
+
+/*
+ add a samr_Password element to a message
+*/
+int samdb_msg_add_hash(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, struct samr_Password *hash)
+{
+ struct ldb_val val;
+ val.data = talloc_memdup(mem_ctx, hash->hash, 16);
+ if (!val.data) {
+ return -1;
+ }
+ val.length = 16;
+ return ldb_msg_add_value(msg, attr_name, &val, NULL);
+}
+
+/*
+ add a samr_Password array to a message
+*/
+int samdb_msg_add_hashes(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, struct samr_Password *hashes, uint_t count)
+{
+ struct ldb_val val;
+ int i;
+ val.data = talloc_array_size(mem_ctx, 16, count);
+ val.length = count*16;
+ if (!val.data) {
+ return -1;
+ }
+ for (i=0;i<count;i++) {
+ memcpy(i*16 + (char *)val.data, hashes[i].hash, 16);
+ }
+ return ldb_msg_add_value(msg, attr_name, &val, NULL);
+}
+
+/*
+ add a acct_flags element to a message
+*/
+int samdb_msg_add_acct_flags(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, uint32_t v)
+{
+ return samdb_msg_add_uint(sam_ldb, mem_ctx, msg, attr_name, samdb_acb2uf(v));
+}
+
+/*
+ add a logon_hours element to a message
+*/
+int samdb_msg_add_logon_hours(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, struct samr_LogonHours *hours)
+{
+ struct ldb_val val;
+ val.length = hours->units_per_week / 8;
+ val.data = hours->bits;
+ return ldb_msg_add_value(msg, attr_name, &val, NULL);
+}
+
+/*
+ add a general value element to a message
+*/
+int samdb_msg_add_value(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, const struct ldb_val *val)
+{
+ return ldb_msg_add_value(msg, attr_name, val, NULL);
+}
+
+/*
+ sets a general value element to a message
+*/
+int samdb_msg_set_value(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, const struct ldb_val *val)
+{
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(msg, attr_name);
+ if (el) {
+ el->num_values = 0;
+ }
+ return ldb_msg_add_value(msg, attr_name, val, NULL);
+}
+
+/*
+ set a string element in a message
+*/
+int samdb_msg_set_string(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char *attr_name, const char *str)
+{
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(msg, attr_name);
+ if (el) {
+ el->num_values = 0;
+ }
+ return samdb_msg_add_string(sam_ldb, mem_ctx, msg, attr_name, str);
+}
+
+/*
+ replace elements in a record
+*/
+int samdb_replace(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg)
+{
+ int i;
+
+ /* mark all the message elements as LDB_FLAG_MOD_REPLACE */
+ for (i=0;i<msg->num_elements;i++) {
+ msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ /* modify the samdb record */
+ return ldb_modify(sam_ldb, msg);
+}
+
+/*
+ return a default security descriptor
+*/
+struct security_descriptor *samdb_default_security_descriptor(TALLOC_CTX *mem_ctx)
+{
+ struct security_descriptor *sd;
+
+ sd = security_descriptor_initialise(mem_ctx);
+
+ return sd;
+}
+
+struct ldb_dn *samdb_base_dn(struct ldb_context *sam_ctx)
+{
+ return ldb_get_default_basedn(sam_ctx);
+}
+
+struct ldb_dn *samdb_config_dn(struct ldb_context *sam_ctx)
+{
+ return ldb_get_config_basedn(sam_ctx);
+}
+
+struct ldb_dn *samdb_schema_dn(struct ldb_context *sam_ctx)
+{
+ return ldb_get_schema_basedn(sam_ctx);
+}
+
+struct ldb_dn *samdb_root_dn(struct ldb_context *sam_ctx)
+{
+ return ldb_get_root_basedn(sam_ctx);
+}
+
+struct ldb_dn *samdb_partitions_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx)
+{
+ struct ldb_dn *new_dn;
+
+ new_dn = ldb_dn_copy(mem_ctx, samdb_config_dn(sam_ctx));
+ if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Partitions")) {
+ talloc_free(new_dn);
+ return NULL;
+ }
+ return new_dn;
+}
+
+struct ldb_dn *samdb_sites_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx)
+{
+ struct ldb_dn *new_dn;
+
+ new_dn = ldb_dn_copy(mem_ctx, samdb_config_dn(sam_ctx));
+ if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Sites")) {
+ talloc_free(new_dn);
+ return NULL;
+ }
+ return new_dn;
+}
+
+/*
+ work out the domain sid for the current open ldb
+*/
+const struct dom_sid *samdb_domain_sid(struct ldb_context *ldb)
+{
+ TALLOC_CTX *tmp_ctx;
+ const struct dom_sid *domain_sid;
+ const char *attrs[] = {
+ "objectSid",
+ NULL
+ };
+ struct ldb_result *res;
+ int ret;
+
+ /* see if we have a cached copy */
+ domain_sid = (struct dom_sid *)ldb_get_opaque(ldb, "cache.domain_sid");
+ if (domain_sid) {
+ return domain_sid;
+ }
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+ ret = ldb_search_exp_fmt(ldb, tmp_ctx, &res, ldb_get_default_basedn(ldb), LDB_SCOPE_BASE, attrs, "objectSid=*");
+
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ if (res->count != 1) {
+ goto failed;
+ }
+
+ domain_sid = samdb_result_dom_sid(tmp_ctx, res->msgs[0], "objectSid");
+ if (domain_sid == NULL) {
+ goto failed;
+ }
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.domain_sid", discard_const_p(struct dom_sid, domain_sid)) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, domain_sid);
+ talloc_free(tmp_ctx);
+
+ return domain_sid;
+
+failed:
+ DEBUG(1,("Failed to find domain_sid for open ldb\n"));
+ talloc_free(tmp_ctx);
+ return NULL;
+}
+
+bool samdb_set_domain_sid(struct ldb_context *ldb, const struct dom_sid *dom_sid_in)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct dom_sid *dom_sid_new;
+ struct dom_sid *dom_sid_old;
+
+ /* see if we have a cached copy */
+ dom_sid_old = talloc_get_type(ldb_get_opaque(ldb,
+ "cache.domain_sid"), struct dom_sid);
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+ dom_sid_new = dom_sid_dup(tmp_ctx, dom_sid_in);
+ if (!dom_sid_new) {
+ goto failed;
+ }
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.domain_sid", dom_sid_new) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, dom_sid_new);
+ talloc_free(tmp_ctx);
+ talloc_free(dom_sid_old);
+
+ return true;
+
+failed:
+ DEBUG(1,("Failed to set our own cached domain SID in the ldb!\n"));
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/* Obtain the short name of the flexible single master operator
+ * (FSMO), such as the PDC Emulator */
+const char *samdb_result_fsmo_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, const struct ldb_message *msg,
+ const char *attr)
+{
+ /* Format is cn=NTDS Settings,cn=<NETBIOS name of FSMO>,.... */
+ struct ldb_dn *fsmo_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, msg, attr);
+ const struct ldb_val *val = ldb_dn_get_component_val(fsmo_dn, 1);
+ const char *name = ldb_dn_get_component_name(fsmo_dn, 1);
+
+ if (!name || (ldb_attr_cmp(name, "cn") != 0)) {
+ /* Ensure this matches the format. This gives us a
+ * bit more confidence that a 'cn' value will be a
+ * ascii string */
+ return NULL;
+ }
+ if (val) {
+ return (char *)val->data;
+ }
+ return NULL;
+}
+
+/*
+ work out the ntds settings dn for the current open ldb
+*/
+struct ldb_dn *samdb_ntds_settings_dn(struct ldb_context *ldb)
+{
+ TALLOC_CTX *tmp_ctx;
+ const char *root_attrs[] = { "dsServiceName", NULL };
+ int ret;
+ struct ldb_result *root_res;
+ struct ldb_dn *settings_dn;
+
+ /* see if we have a cached copy */
+ settings_dn = (struct ldb_dn *)ldb_get_opaque(ldb, "cache.settings_dn");
+ if (settings_dn) {
+ return settings_dn;
+ }
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+
+ ret = ldb_search(ldb, ldb_dn_new(tmp_ctx, ldb, ""), LDB_SCOPE_BASE, NULL, root_attrs, &root_res);
+ if (ret) {
+ DEBUG(1,("Searching for dsServiceName in rootDSE failed: %s\n",
+ ldb_errstring(ldb)));
+ goto failed;
+ }
+ talloc_steal(tmp_ctx, root_res);
+
+ if (root_res->count != 1) {
+ goto failed;
+ }
+
+ settings_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, root_res->msgs[0], "dsServiceName");
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.settings_dn", settings_dn) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, settings_dn);
+ talloc_free(tmp_ctx);
+
+ return settings_dn;
+
+failed:
+ DEBUG(1,("Failed to find our own NTDS Settings DN in the ldb!\n"));
+ talloc_free(tmp_ctx);
+ return NULL;
+}
+
+/*
+ work out the ntds settings invocationId for the current open ldb
+*/
+const struct GUID *samdb_ntds_invocation_id(struct ldb_context *ldb)
+{
+ TALLOC_CTX *tmp_ctx;
+ const char *attrs[] = { "invocationId", NULL };
+ int ret;
+ struct ldb_result *res;
+ struct GUID *invocation_id;
+
+ /* see if we have a cached copy */
+ invocation_id = (struct GUID *)ldb_get_opaque(ldb, "cache.invocation_id");
+ if (invocation_id) {
+ return invocation_id;
+ }
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+ ret = ldb_search(ldb, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, NULL, attrs, &res);
+ if (ret) {
+ goto failed;
+ }
+ talloc_steal(tmp_ctx, res);
+
+ if (res->count != 1) {
+ goto failed;
+ }
+
+ invocation_id = talloc(tmp_ctx, struct GUID);
+ if (!invocation_id) {
+ goto failed;
+ }
+
+ *invocation_id = samdb_result_guid(res->msgs[0], "invocationId");
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, invocation_id);
+ talloc_free(tmp_ctx);
+
+ return invocation_id;
+
+failed:
+ DEBUG(1,("Failed to find our own NTDS Settings invocationId in the ldb!\n"));
+ talloc_free(tmp_ctx);
+ return NULL;
+}
+
+bool samdb_set_ntds_invocation_id(struct ldb_context *ldb, const struct GUID *invocation_id_in)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct GUID *invocation_id_new;
+ struct GUID *invocation_id_old;
+
+ /* see if we have a cached copy */
+ invocation_id_old = (struct GUID *)ldb_get_opaque(ldb,
+ "cache.invocation_id");
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+ invocation_id_new = talloc(tmp_ctx, struct GUID);
+ if (!invocation_id_new) {
+ goto failed;
+ }
+
+ *invocation_id_new = *invocation_id_in;
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id_new) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, invocation_id_new);
+ talloc_free(tmp_ctx);
+ talloc_free(invocation_id_old);
+
+ return true;
+
+failed:
+ DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n"));
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/*
+ work out the ntds settings objectGUID for the current open ldb
+*/
+const struct GUID *samdb_ntds_objectGUID(struct ldb_context *ldb)
+{
+ TALLOC_CTX *tmp_ctx;
+ const char *attrs[] = { "objectGUID", NULL };
+ int ret;
+ struct ldb_result *res;
+ struct GUID *ntds_guid;
+
+ /* see if we have a cached copy */
+ ntds_guid = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid");
+ if (ntds_guid) {
+ return ntds_guid;
+ }
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+ ret = ldb_search(ldb, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, NULL, attrs, &res);
+ if (ret) {
+ goto failed;
+ }
+ talloc_steal(tmp_ctx, res);
+
+ if (res->count != 1) {
+ goto failed;
+ }
+
+ ntds_guid = talloc(tmp_ctx, struct GUID);
+ if (!ntds_guid) {
+ goto failed;
+ }
+
+ *ntds_guid = samdb_result_guid(res->msgs[0], "objectGUID");
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.ntds_guid", ntds_guid) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, ntds_guid);
+ talloc_free(tmp_ctx);
+
+ return ntds_guid;
+
+failed:
+ DEBUG(1,("Failed to find our own NTDS Settings objectGUID in the ldb!\n"));
+ talloc_free(tmp_ctx);
+ return NULL;
+}
+
+bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct GUID *ntds_guid_new;
+ struct GUID *ntds_guid_old;
+
+ /* see if we have a cached copy */
+ ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid");
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ goto failed;
+ }
+
+ ntds_guid_new = talloc(tmp_ctx, struct GUID);
+ if (!ntds_guid_new) {
+ goto failed;
+ }
+
+ *ntds_guid_new = *ntds_guid_in;
+
+ /* cache the domain_sid in the ldb */
+ if (ldb_set_opaque(ldb, "cache.ntds_guid", ntds_guid_new) != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ talloc_steal(ldb, ntds_guid_new);
+ talloc_free(tmp_ctx);
+ talloc_free(ntds_guid_old);
+
+ return true;
+
+failed:
+ DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n"));
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/*
+ work out the server dn for the current open ldb
+*/
+struct ldb_dn *samdb_server_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
+{
+ return ldb_dn_get_parent(mem_ctx, samdb_ntds_settings_dn(ldb));
+}
+
+/*
+ work out the server dn for the current open ldb
+*/
+struct ldb_dn *samdb_server_site_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
+{
+ struct ldb_dn *server_dn;
+ struct ldb_dn *server_site_dn;
+
+ server_dn = samdb_server_dn(ldb, mem_ctx);
+ if (!server_dn) return NULL;
+
+ server_site_dn = ldb_dn_get_parent(mem_ctx, server_dn);
+
+ talloc_free(server_dn);
+ return server_site_dn;
+}
+
+/*
+ work out if we are the PDC for the domain of the current open ldb
+*/
+bool samdb_is_pdc(struct ldb_context *ldb)
+{
+ const char *dom_attrs[] = { "fSMORoleOwner", NULL };
+ int ret;
+ struct ldb_result *dom_res;
+ TALLOC_CTX *tmp_ctx;
+ bool is_pdc;
+ struct ldb_dn *pdc;
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed in samdb_is_pdc"));
+ return false;
+ }
+
+ ret = ldb_search(ldb, ldb_get_default_basedn(ldb), LDB_SCOPE_BASE, NULL, dom_attrs, &dom_res);
+ if (ret) {
+ DEBUG(1,("Searching for fSMORoleOwner in %s failed: %s\n",
+ ldb_dn_get_linearized(ldb_get_default_basedn(ldb)),
+ ldb_errstring(ldb)));
+ goto failed;
+ }
+ talloc_steal(tmp_ctx, dom_res);
+ if (dom_res->count != 1) {
+ goto failed;
+ }
+
+ pdc = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, dom_res->msgs[0], "fSMORoleOwner");
+
+ if (ldb_dn_compare(samdb_ntds_settings_dn(ldb), pdc) == 0) {
+ is_pdc = true;
+ } else {
+ is_pdc = false;
+ }
+
+ talloc_free(tmp_ctx);
+
+ return is_pdc;
+
+failed:
+ DEBUG(1,("Failed to find if we are the PDC for this ldb\n"));
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/*
+ work out if we are a Global Catalog server for the domain of the current open ldb
+*/
+bool samdb_is_gc(struct ldb_context *ldb)
+{
+ const char *attrs[] = { "options", NULL };
+ int ret, options;
+ struct ldb_result *res;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed in samdb_is_pdc"));
+ return false;
+ }
+
+ /* Query cn=ntds settings,.... */
+ ret = ldb_search(ldb, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, NULL, attrs, &res);
+ if (ret) {
+ return false;
+ }
+ if (res->count != 1) {
+ talloc_free(res);
+ return false;
+ }
+
+ options = ldb_msg_find_attr_as_int(res->msgs[0], "options", 0);
+ talloc_free(res);
+ talloc_free(tmp_ctx);
+
+ /* if options attribute has the 0x00000001 flag set, then enable the global catlog */
+ if (options & 0x000000001) {
+ return true;
+ }
+ return false;
+}
+
+/* Find a domain object in the parents of a particular DN. */
+int samdb_search_for_parent_domain(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn *dn,
+ struct ldb_dn **parent_dn, const char **errstring)
+{
+ TALLOC_CTX *local_ctx;
+ struct ldb_dn *sdn = dn;
+ struct ldb_result *res = NULL;
+ int ret = 0;
+ const char *attrs[] = { NULL };
+
+ local_ctx = talloc_new(mem_ctx);
+ if (local_ctx == NULL) return LDB_ERR_OPERATIONS_ERROR;
+
+ while ((sdn = ldb_dn_get_parent(local_ctx, sdn))) {
+ ret = ldb_search(ldb, sdn, LDB_SCOPE_BASE,
+ "(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain))", attrs, &res);
+ if (ret == LDB_SUCCESS) {
+ talloc_steal(local_ctx, res);
+ if (res->count == 1) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (ret != LDB_SUCCESS) {
+ *errstring = talloc_asprintf(mem_ctx, "Error searching for parent domain of %s, failed searching for %s: %s",
+ ldb_dn_get_linearized(dn),
+ ldb_dn_get_linearized(sdn),
+ ldb_errstring(ldb));
+ talloc_free(local_ctx);
+ return ret;
+ }
+ if (res->count != 1) {
+ *errstring = talloc_asprintf(mem_ctx, "Invalid dn (%s), not child of a domain object",
+ ldb_dn_get_linearized(dn));
+ talloc_free(local_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *parent_dn = talloc_steal(mem_ctx, res->msgs[0]->dn);
+ talloc_free(local_ctx);
+ return ret;
+}
+
+/*
+ check that a password is sufficiently complex
+*/
+static bool samdb_password_complexity_ok(const char *pass)
+{
+ return check_password_quality(pass);
+}
+
+
+
+/*
+ set the user password using plaintext, obeying any user or domain
+ password restrictions
+
+ note that this function doesn't actually store the result in the
+ database, it just fills in the "mod" structure with ldb modify
+ elements to setup the correct change when samdb_replace() is
+ called. This allows the caller to combine the change with other
+ changes (as is needed by some of the set user info levels)
+
+ The caller should probably have a transaction wrapping this
+*/
+NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *user_dn,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *mod,
+ const char *new_pass,
+ struct samr_Password *lmNewHash,
+ struct samr_Password *ntNewHash,
+ bool user_change,
+ enum samr_RejectReason *reject_reason,
+ struct samr_DomInfo1 **_dominfo)
+{
+ const char * const user_attrs[] = { "userAccountControl", "lmPwdHistory",
+ "ntPwdHistory",
+ "dBCSPwd", "unicodePwd",
+ "objectSid",
+ "pwdLastSet", NULL };
+ const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength",
+ "maxPwdAge", "minPwdAge",
+ "minPwdLength", NULL };
+ NTTIME pwdLastSet;
+ int64_t minPwdAge;
+ uint_t minPwdLength, pwdProperties, pwdHistoryLength;
+ uint_t userAccountControl;
+ struct samr_Password *sambaLMPwdHistory, *sambaNTPwdHistory, *lmPwdHash, *ntPwdHash;
+ struct samr_Password local_lmNewHash, local_ntNewHash;
+ int sambaLMPwdHistory_len, sambaNTPwdHistory_len;
+ struct dom_sid *domain_sid;
+ struct ldb_message **res;
+ bool restrictions;
+ int count;
+ time_t now = time(NULL);
+ NTTIME now_nt;
+ int i;
+
+ /* we need to know the time to compute password age */
+ unix_to_nt_time(&now_nt, now);
+
+ /* pull all the user parameters */
+ count = gendb_search_dn(ctx, mem_ctx, user_dn, &res, user_attrs);
+ if (count != 1) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ userAccountControl = samdb_result_uint(res[0], "userAccountControl", 0);
+ sambaLMPwdHistory_len = samdb_result_hashes(mem_ctx, res[0],
+ "lmPwdHistory", &sambaLMPwdHistory);
+ sambaNTPwdHistory_len = samdb_result_hashes(mem_ctx, res[0],
+ "ntPwdHistory", &sambaNTPwdHistory);
+ lmPwdHash = samdb_result_hash(mem_ctx, res[0], "dBCSPwd");
+ ntPwdHash = samdb_result_hash(mem_ctx, res[0], "unicodePwd");
+ pwdLastSet = samdb_result_uint64(res[0], "pwdLastSet", 0);
+
+ /* Only non-trust accounts have restrictions (possibly this
+ * test is the wrong way around, but I like to be restrictive
+ * if possible */
+ restrictions = !(userAccountControl & (UF_INTERDOMAIN_TRUST_ACCOUNT
+ |UF_WORKSTATION_TRUST_ACCOUNT
+ |UF_SERVER_TRUST_ACCOUNT));
+
+ if (domain_dn) {
+ /* pull the domain parameters */
+ count = gendb_search_dn(ctx, mem_ctx, domain_dn, &res, domain_attrs);
+ if (count != 1) {
+ DEBUG(2, ("samdb_set_password: Domain DN %s is invalid, for user %s\n",
+ ldb_dn_get_linearized(domain_dn),
+ ldb_dn_get_linearized(user_dn)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ } else {
+ /* work out the domain sid, and pull the domain from there */
+ domain_sid = samdb_result_sid_prefix(mem_ctx, res[0], "objectSid");
+ if (domain_sid == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ count = gendb_search(ctx, mem_ctx, NULL, &res, domain_attrs,
+ "(objectSid=%s)",
+ ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
+ if (count != 1) {
+ DEBUG(2, ("samdb_set_password: Could not find domain to match SID: %s, for user %s\n",
+ dom_sid_string(mem_ctx, domain_sid),
+ ldb_dn_get_linearized(user_dn)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ }
+
+ pwdProperties = samdb_result_uint(res[0], "pwdProperties", 0);
+ pwdHistoryLength = samdb_result_uint(res[0], "pwdHistoryLength", 0);
+ minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0);
+ minPwdAge = samdb_result_int64(res[0], "minPwdAge", 0);
+
+ if (_dominfo) {
+ struct samr_DomInfo1 *dominfo;
+ /* on failure we need to fill in the reject reasons */
+ dominfo = talloc(mem_ctx, struct samr_DomInfo1);
+ if (dominfo == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ dominfo->min_password_length = minPwdLength;
+ dominfo->password_properties = pwdProperties;
+ dominfo->password_history_length = pwdHistoryLength;
+ dominfo->max_password_age = minPwdAge;
+ dominfo->min_password_age = minPwdAge;
+ *_dominfo = dominfo;
+ }
+
+ if (restrictions && new_pass) {
+
+ /* check the various password restrictions */
+ if (restrictions && minPwdLength > strlen_m(new_pass)) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_TOO_SHORT;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ /* possibly check password complexity */
+ if (restrictions && pwdProperties & DOMAIN_PASSWORD_COMPLEX &&
+ !samdb_password_complexity_ok(new_pass)) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_COMPLEXITY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ /* compute the new nt and lm hashes */
+ if (E_deshash(new_pass, local_lmNewHash.hash)) {
+ lmNewHash = &local_lmNewHash;
+ }
+ if (!E_md4hash(new_pass, local_ntNewHash.hash)) {
+ /* If we can't convert this password to UCS2, then we should not accept it */
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ ntNewHash = &local_ntNewHash;
+ }
+
+ if (user_change) {
+ /* are all password changes disallowed? */
+ if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ /* can this user change password? */
+ if (userAccountControl & UF_PASSWD_CANT_CHANGE) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ /* yes, this is a minus. The ages are in negative 100nsec units! */
+ if (pwdLastSet - minPwdAge > now_nt) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ /* check the immediately past password */
+ if (pwdHistoryLength > 0) {
+ if (lmNewHash && lmPwdHash && memcmp(lmNewHash->hash, lmPwdHash->hash, 16) == 0) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_IN_HISTORY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ if (ntNewHash && ntPwdHash && memcmp(ntNewHash->hash, ntPwdHash->hash, 16) == 0) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_IN_HISTORY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+ /* check the password history */
+ sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len, pwdHistoryLength);
+ sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len, pwdHistoryLength);
+
+ for (i=0; lmNewHash && i<sambaLMPwdHistory_len;i++) {
+ if (memcmp(lmNewHash->hash, sambaLMPwdHistory[i].hash, 16) == 0) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_IN_HISTORY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+ for (i=0; ntNewHash && i<sambaNTPwdHistory_len;i++) {
+ if (memcmp(ntNewHash->hash, sambaNTPwdHistory[i].hash, 16) == 0) {
+ if (reject_reason) {
+ *reject_reason = SAMR_REJECT_IN_HISTORY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+ }
+
+#define CHECK_RET(x) do { if (x != 0) return NT_STATUS_NO_MEMORY; } while(0)
+
+ /* the password is acceptable. Start forming the new fields */
+ if (new_pass) {
+ /* if we know the cleartext, then only set it.
+ * Modules in ldb will set all the appropriate
+ * hashes */
+ CHECK_RET(samdb_msg_add_string(ctx, mem_ctx, mod,
+ "userPassword", new_pass));
+ } else {
+ /* We don't have the cleartext, so delete the old one
+ * and set what we have of the hashes */
+ CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "userPassword"));
+
+ if (lmNewHash) {
+ CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "dBCSPwd", lmNewHash));
+ } else {
+ CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "dBCSPwd"));
+ }
+
+ if (ntNewHash) {
+ CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "unicodePwd", ntNewHash));
+ } else {
+ CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "unicodePwd"));
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ set the user password using plaintext, obeying any user or domain
+ password restrictions
+
+ This wrapper function takes a SID as input, rather than a user DN,
+ and actually performs the password change
+
+*/
+NTSTATUS samdb_set_password_sid(struct ldb_context *ctx, TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ const char *new_pass,
+ struct samr_Password *lmNewHash,
+ struct samr_Password *ntNewHash,
+ bool user_change,
+ enum samr_RejectReason *reject_reason,
+ struct samr_DomInfo1 **_dominfo)
+{
+ NTSTATUS nt_status;
+ struct ldb_dn *user_dn;
+ struct ldb_message *msg;
+ int ret;
+
+ ret = ldb_transaction_start(ctx);
+ if (ret) {
+ DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(ctx)));
+ return NT_STATUS_TRANSACTION_ABORTED;
+ }
+
+ user_dn = samdb_search_dn(ctx, mem_ctx, NULL,
+ "(&(objectSid=%s)(objectClass=user))",
+ ldap_encode_ndr_dom_sid(mem_ctx, user_sid));
+ if (!user_dn) {
+ ldb_transaction_cancel(ctx);
+ DEBUG(3, ("samdb_set_password_sid: SID %s not found in samdb, returning NO_SUCH_USER\n",
+ dom_sid_string(mem_ctx, user_sid)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ msg = ldb_msg_new(mem_ctx);
+ if (msg == NULL) {
+ ldb_transaction_cancel(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ msg->dn = ldb_dn_copy(msg, user_dn);
+ if (!msg->dn) {
+ ldb_transaction_cancel(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = samdb_set_password(ctx, mem_ctx,
+ user_dn, NULL,
+ msg, new_pass,
+ lmNewHash, ntNewHash,
+ user_change, /* This is a password set, not change */
+ reject_reason, _dominfo);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ldb_transaction_cancel(ctx);
+ return nt_status;
+ }
+
+ /* modify the samdb record */
+ ret = samdb_replace(ctx, mem_ctx, msg);
+ if (ret != 0) {
+ ldb_transaction_cancel(ctx);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ ret = ldb_transaction_commit(ctx);
+ if (ret != 0) {
+ DEBUG(0,("Failed to commit transaction to change password on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ctx)));
+ return NT_STATUS_TRANSACTION_ABORTED;
+ }
+ return NT_STATUS_OK;
+}
+
+
+
+NTSTATUS samdb_create_foreign_security_principal(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid, struct ldb_dn **ret_dn)
+{
+ struct ldb_message *msg;
+ struct ldb_dn *basedn;
+ const char *sidstr;
+ int ret;
+
+ sidstr = dom_sid_string(mem_ctx, sid);
+ NT_STATUS_HAVE_NO_MEMORY(sidstr);
+
+ /* We might have to create a ForeignSecurityPrincipal, even if this user
+ * is in our own domain */
+
+ msg = ldb_msg_new(mem_ctx);
+ if (msg == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* TODO: Hmmm. This feels wrong. How do I find the base dn to
+ * put the ForeignSecurityPrincipals? d_state->domain_dn does
+ * not work, this is wrong for the Builtin domain, there's no
+ * cn=For...,cn=Builtin,dc={BASEDN}. -- vl
+ */
+
+ basedn = samdb_search_dn(sam_ctx, mem_ctx, NULL,
+ "(&(objectClass=container)(cn=ForeignSecurityPrincipals))");
+
+ if (basedn == NULL) {
+ DEBUG(0, ("Failed to find DN for "
+ "ForeignSecurityPrincipal container\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* add core elements to the ldb_message for the alias */
+ msg->dn = ldb_dn_copy(mem_ctx, basedn);
+ if ( ! ldb_dn_add_child_fmt(msg->dn, "CN=%s", sidstr))
+ return NT_STATUS_NO_MEMORY;
+
+ samdb_msg_add_string(sam_ctx, mem_ctx, msg,
+ "objectClass",
+ "foreignSecurityPrincipal");
+
+ /* create the alias */
+ ret = ldb_add(sam_ctx, msg);
+ if (ret != 0) {
+ DEBUG(0,("Failed to create foreignSecurityPrincipal "
+ "record %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(sam_ctx)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ *ret_dn = msg->dn;
+ return NT_STATUS_OK;
+}
+
+
+/*
+ Find the DN of a domain, assuming it to be a dotted.dns name
+*/
+
+struct ldb_dn *samdb_dns_domain_to_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, const char *dns_domain)
+{
+ int i;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ const char *binary_encoded;
+ const char **split_realm;
+ struct ldb_dn *dn;
+
+ if (!tmp_ctx) {
+ return NULL;
+ }
+
+ split_realm = str_list_make(tmp_ctx, dns_domain, ".");
+ if (!split_realm) {
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+ dn = ldb_dn_new(mem_ctx, ldb, NULL);
+ for (i=0; split_realm[i]; i++) {
+ binary_encoded = ldb_binary_encode_string(tmp_ctx, split_realm[i]);
+ if (!ldb_dn_add_base_fmt(dn, "dc=%s", binary_encoded)) {
+ DEBUG(2, ("Failed to add dc=%s element to DN %s\n",
+ binary_encoded, ldb_dn_get_linearized(dn)));
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+ }
+ if (!ldb_dn_validate(dn)) {
+ DEBUG(2, ("Failed to validated DN %s\n",
+ ldb_dn_get_linearized(dn)));
+ return NULL;
+ }
+ return dn;
+}
+/*
+ Find the DN of a domain, be it the netbios or DNS name
+*/
+
+struct ldb_dn *samdb_domain_to_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ const char *domain_name)
+{
+ const char * const domain_ref_attrs[] = {
+ "ncName", NULL
+ };
+ const char * const domain_ref2_attrs[] = {
+ NULL
+ };
+ struct ldb_result *res_domain_ref;
+ char *escaped_domain = ldb_binary_encode_string(mem_ctx, domain_name);
+ /* find the domain's DN */
+ int ret_domain = ldb_search_exp_fmt(ldb, mem_ctx,
+ &res_domain_ref,
+ samdb_partitions_dn(ldb, mem_ctx),
+ LDB_SCOPE_ONELEVEL,
+ domain_ref_attrs,
+ "(&(nETBIOSName=%s)(objectclass=crossRef))",
+ escaped_domain);
+ if (ret_domain != 0) {
+ return NULL;
+ }
+
+ if (res_domain_ref->count == 0) {
+ ret_domain = ldb_search_exp_fmt(ldb, mem_ctx,
+ &res_domain_ref,
+ samdb_dns_domain_to_dn(ldb, mem_ctx, domain_name),
+ LDB_SCOPE_BASE,
+ domain_ref2_attrs,
+ "(objectclass=domain)");
+ if (ret_domain != 0) {
+ return NULL;
+ }
+
+ if (res_domain_ref->count == 1) {
+ return res_domain_ref->msgs[0]->dn;
+ }
+ return NULL;
+ }
+
+ if (res_domain_ref->count > 1) {
+ DEBUG(0,("Found %d records matching domain [%s]\n",
+ ret_domain, domain_name));
+ return NULL;
+ }
+
+ return samdb_result_dn(ldb, mem_ctx, res_domain_ref->msgs[0], "nCName", NULL);
+
+}
diff --git a/source4/dsdb/config.mk b/source4/dsdb/config.mk
new file mode 100644
index 0000000000..63e8a77ce4
--- /dev/null
+++ b/source4/dsdb/config.mk
@@ -0,0 +1,63 @@
+# Directory Service subsystem
+
+mkinclude samdb/ldb_modules/config.mk
+
+################################################
+# Start SUBSYSTEM SAMDB
+[SUBSYSTEM::SAMDB]
+PUBLIC_DEPENDENCIES = HEIMDAL_KRB5
+PRIVATE_DEPENDENCIES = LIBNDR NDR_MISC NDR_DRSUAPI NDR_DRSBLOBS NSS_WRAPPER \
+ auth_system_session LDAP_ENCODE LIBCLI_AUTH LIBNDR \
+ SAMDB_SCHEMA LDB_WRAP SAMDB_COMMON
+
+
+SAMDB_OBJ_FILES = $(addprefix $(dsdbsrcdir)/, \
+ samdb/samdb.o \
+ samdb/samdb_privilege.o \
+ samdb/cracknames.o \
+ repl/replicated_objects.o)
+
+$(eval $(call proto_header_template,$(dsdbsrcdir)/samdb/samdb_proto.h,$(SAMDB_OBJ_FILES:.o=.c)))
+# PUBLIC_HEADERS += dsdb/samdb/samdb.h
+
+[SUBSYSTEM::SAMDB_COMMON]
+PRIVATE_DEPENDENCIES = LIBLDB
+
+SAMDB_COMMON_OBJ_FILES = $(addprefix $(dsdbsrcdir)/common/, \
+ sidmap.o \
+ flag_mapping.o \
+ util.o)
+$(eval $(call proto_header_template,$(dsdbsrcdir)/common/proto.h,$(SAMDB_COMMON_OBJ_FILES:.o=.c)))
+
+[SUBSYSTEM::SAMDB_SCHEMA]
+PRIVATE_DEPENDENCIES = SAMDB_COMMON NDR_DRSUAPI NDR_DRSBLOBS
+
+SAMDB_SCHEMA_OBJ_FILES = $(addprefix $(dsdbsrcdir)/schema/, \
+ schema_init.o \
+ schema_set.o \
+ schema_query.o \
+ schema_syntax.o \
+ schema_description.o)
+
+$(eval $(call proto_header_template,$(dsdbsrcdir)/schema/proto.h,$(SAMDB_SCHEMA_OBJ_FILES:.o=.c)))
+# PUBLIC_HEADERS += dsdb/schema/schema.h
+
+#######################
+# Start SUBSYSTEM DREPL_SRV
+[MODULE::DREPL_SRV]
+INIT_FUNCTION = server_service_drepl_init
+SUBSYSTEM = smbd
+PRIVATE_DEPENDENCIES = \
+ SAMDB \
+ process_model
+# End SUBSYSTEM DREPL_SRV
+#######################
+
+DREPL_SRV_OBJ_FILES = $(addprefix $(dsdbsrcdir)/repl/, \
+ drepl_service.o \
+ drepl_periodic.o \
+ drepl_partitions.o \
+ drepl_out_pull.o \
+ drepl_out_helpers.o)
+
+$(eval $(call proto_header_template,$(dsdbsrcdir)/repl/drepl_service_proto.h,$(DREPL_SRV_OBJ_FILES:.o=.c)))
diff --git a/source4/dsdb/repl/drepl_out_helpers.c b/source4/dsdb/repl/drepl_out_helpers.c
new file mode 100644
index 0000000000..80b398ef5c
--- /dev/null
+++ b/source4/dsdb/repl/drepl_out_helpers.c
@@ -0,0 +1,442 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service helper function for outgoing traffic
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/samdb/samdb.h"
+#include "auth/auth.h"
+#include "smbd/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/repl/drepl_service.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "libcli/composite/composite.h"
+#include "auth/gensec/gensec.h"
+
+struct dreplsrv_out_drsuapi_state {
+ struct composite_context *creq;
+
+ struct dreplsrv_out_connection *conn;
+
+ struct dreplsrv_drsuapi_connection *drsuapi;
+
+ struct drsuapi_DsBindInfoCtr bind_info_ctr;
+ struct drsuapi_DsBind bind_r;
+};
+
+static void dreplsrv_out_drsuapi_connect_recv(struct composite_context *creq);
+
+static struct composite_context *dreplsrv_out_drsuapi_send(struct dreplsrv_out_connection *conn)
+{
+ struct composite_context *c;
+ struct composite_context *creq;
+ struct dreplsrv_out_drsuapi_state *st;
+
+ c = composite_create(conn, conn->service->task->event_ctx);
+ if (c == NULL) return NULL;
+
+ st = talloc_zero(c, struct dreplsrv_out_drsuapi_state);
+ if (composite_nomem(st, c)) return c;
+
+ c->private_data = st;
+
+ st->creq = c;
+ st->conn = conn;
+ st->drsuapi = conn->drsuapi;
+
+ if (st->drsuapi && !st->drsuapi->pipe->conn->dead) {
+ composite_done(c);
+ return c;
+ } else if (st->drsuapi && st->drsuapi->pipe->conn->dead) {
+ talloc_free(st->drsuapi);
+ conn->drsuapi = NULL;
+ }
+
+ st->drsuapi = talloc_zero(st, struct dreplsrv_drsuapi_connection);
+ if (composite_nomem(st->drsuapi, c)) return c;
+
+ creq = dcerpc_pipe_connect_b_send(st, conn->binding, &ndr_table_drsuapi,
+ conn->service->system_session_info->credentials,
+ c->event_ctx, conn->service->task->lp_ctx);
+ composite_continue(c, creq, dreplsrv_out_drsuapi_connect_recv, st);
+
+ return c;
+}
+
+static void dreplsrv_out_drsuapi_bind_send(struct dreplsrv_out_drsuapi_state *st);
+
+static void dreplsrv_out_drsuapi_connect_recv(struct composite_context *creq)
+{
+ struct dreplsrv_out_drsuapi_state *st = talloc_get_type(creq->async.private_data,
+ struct dreplsrv_out_drsuapi_state);
+ struct composite_context *c = st->creq;
+
+ c->status = dcerpc_pipe_connect_b_recv(creq, st->drsuapi, &st->drsuapi->pipe);
+ if (!composite_is_ok(c)) return;
+
+ c->status = gensec_session_key(st->drsuapi->pipe->conn->security_state.generic_state,
+ &st->drsuapi->gensec_skey);
+ if (!composite_is_ok(c)) return;
+
+ dreplsrv_out_drsuapi_bind_send(st);
+}
+
+static void dreplsrv_out_drsuapi_bind_recv(struct rpc_request *req);
+
+static void dreplsrv_out_drsuapi_bind_send(struct dreplsrv_out_drsuapi_state *st)
+{
+ struct composite_context *c = st->creq;
+ struct rpc_request *req;
+
+ st->bind_info_ctr.length = 28;
+ st->bind_info_ctr.info.info28 = st->conn->service->bind_info28;
+
+ st->bind_r.in.bind_guid = &st->conn->service->ntds_guid;
+ st->bind_r.in.bind_info = &st->bind_info_ctr;
+ st->bind_r.out.bind_handle = &st->drsuapi->bind_handle;
+
+ req = dcerpc_drsuapi_DsBind_send(st->drsuapi->pipe, st, &st->bind_r);
+ composite_continue_rpc(c, req, dreplsrv_out_drsuapi_bind_recv, st);
+}
+
+static void dreplsrv_out_drsuapi_bind_recv(struct rpc_request *req)
+{
+ struct dreplsrv_out_drsuapi_state *st = talloc_get_type(req->async.private_data,
+ struct dreplsrv_out_drsuapi_state);
+ struct composite_context *c = st->creq;
+
+ c->status = dcerpc_ndr_request_recv(req);
+ if (!composite_is_ok(c)) return;
+
+ if (!W_ERROR_IS_OK(st->bind_r.out.result)) {
+ composite_error(c, werror_to_ntstatus(st->bind_r.out.result));
+ return;
+ }
+
+ ZERO_STRUCT(st->drsuapi->remote_info28);
+ if (st->bind_r.out.bind_info) {
+ switch (st->bind_r.out.bind_info->length) {
+ case 24: {
+ struct drsuapi_DsBindInfo24 *info24;
+ info24 = &st->bind_r.out.bind_info->info.info24;
+ st->drsuapi->remote_info28.supported_extensions = info24->supported_extensions;
+ st->drsuapi->remote_info28.site_guid = info24->site_guid;
+ st->drsuapi->remote_info28.pid = info24->pid;
+ st->drsuapi->remote_info28.repl_epoch = 0;
+ break;
+ }
+ case 48: {
+ struct drsuapi_DsBindInfo48 *info48;
+ info48 = &st->bind_r.out.bind_info->info.info48;
+ st->drsuapi->remote_info28.supported_extensions = info48->supported_extensions;
+ st->drsuapi->remote_info28.site_guid = info48->site_guid;
+ st->drsuapi->remote_info28.pid = info48->pid;
+ st->drsuapi->remote_info28.repl_epoch = info48->repl_epoch;
+ break;
+ }
+ case 28:
+ st->drsuapi->remote_info28 = st->bind_r.out.bind_info->info.info28;
+ break;
+ }
+ }
+
+ composite_done(c);
+}
+
+static NTSTATUS dreplsrv_out_drsuapi_recv(struct composite_context *c)
+{
+ NTSTATUS status;
+ struct dreplsrv_out_drsuapi_state *st = talloc_get_type(c->private_data,
+ struct dreplsrv_out_drsuapi_state);
+
+ status = composite_wait(c);
+
+ if (NT_STATUS_IS_OK(status)) {
+ st->conn->drsuapi = talloc_steal(st->conn, st->drsuapi);
+ }
+
+ talloc_free(c);
+ return status;
+}
+
+struct dreplsrv_op_pull_source_state {
+ struct composite_context *creq;
+
+ struct dreplsrv_out_operation *op;
+
+ struct dreplsrv_drsuapi_connection *drsuapi;
+
+ bool have_all;
+
+ uint32_t ctr_level;
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1;
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6;
+};
+
+static void dreplsrv_op_pull_source_connect_recv(struct composite_context *creq);
+
+struct composite_context *dreplsrv_op_pull_source_send(struct dreplsrv_out_operation *op)
+{
+ struct composite_context *c;
+ struct composite_context *creq;
+ struct dreplsrv_op_pull_source_state *st;
+
+ c = composite_create(op, op->service->task->event_ctx);
+ if (c == NULL) return NULL;
+
+ st = talloc_zero(c, struct dreplsrv_op_pull_source_state);
+ if (composite_nomem(st, c)) return c;
+
+ st->creq = c;
+ st->op = op;
+
+ creq = dreplsrv_out_drsuapi_send(op->source_dsa->conn);
+ composite_continue(c, creq, dreplsrv_op_pull_source_connect_recv, st);
+
+ return c;
+}
+
+static void dreplsrv_op_pull_source_get_changes_send(struct dreplsrv_op_pull_source_state *st);
+
+static void dreplsrv_op_pull_source_connect_recv(struct composite_context *creq)
+{
+ struct dreplsrv_op_pull_source_state *st = talloc_get_type(creq->async.private_data,
+ struct dreplsrv_op_pull_source_state);
+ struct composite_context *c = st->creq;
+
+ c->status = dreplsrv_out_drsuapi_recv(creq);
+ if (!composite_is_ok(c)) return;
+
+ dreplsrv_op_pull_source_get_changes_send(st);
+}
+
+static void dreplsrv_op_pull_source_get_changes_recv(struct rpc_request *req);
+
+static void dreplsrv_op_pull_source_get_changes_send(struct dreplsrv_op_pull_source_state *st)
+{
+ struct composite_context *c = st->creq;
+ struct repsFromTo1 *rf1 = st->op->source_dsa->repsFrom1;
+ struct dreplsrv_service *service = st->op->service;
+ struct dreplsrv_partition *partition = st->op->source_dsa->partition;
+ struct dreplsrv_drsuapi_connection *drsuapi = st->op->source_dsa->conn->drsuapi;
+ struct rpc_request *req;
+ struct drsuapi_DsGetNCChanges *r;
+
+ r = talloc(st, struct drsuapi_DsGetNCChanges);
+ if (composite_nomem(r, c)) return;
+
+ r->in.level = talloc(r, int32_t);
+ if (composite_nomem(r->in.level, c)) return;
+ r->out.level = talloc(r, int32_t);
+ if (composite_nomem(r->out.level, c)) return;
+
+ r->in.bind_handle = &drsuapi->bind_handle;
+ if (drsuapi->remote_info28.supported_extensions & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8) {
+ *r->in.level = 8;
+ r->in.req.req8.destination_dsa_guid = service->ntds_guid;
+ r->in.req.req8.source_dsa_invocation_id = rf1->source_dsa_invocation_id;
+ r->in.req.req8.naming_context = &partition->nc;
+ r->in.req.req8.highwatermark = rf1->highwatermark;
+ r->in.req.req8.uptodateness_vector = NULL;/*&partition->uptodatevector_ex;*/
+ r->in.req.req8.replica_flags = rf1->replica_flags;
+ r->in.req.req8.max_object_count = 133;
+ r->in.req.req8.max_ndr_size = 1336811;
+ r->in.req.req8.extended_op = DRSUAPI_EXOP_NONE;
+ r->in.req.req8.fsmo_info = 0;
+ r->in.req.req8.partial_attribute_set = NULL;
+ r->in.req.req8.partial_attribute_set_ex = NULL;
+ r->in.req.req8.mapping_ctr.num_mappings = 0;
+ r->in.req.req8.mapping_ctr.mappings = NULL;
+ } else {
+ *r->in.level = 5;
+ r->in.req.req5.destination_dsa_guid = service->ntds_guid;
+ r->in.req.req5.source_dsa_invocation_id = rf1->source_dsa_invocation_id;
+ r->in.req.req5.naming_context = &partition->nc;
+ r->in.req.req5.highwatermark = rf1->highwatermark;
+ r->in.req.req5.uptodateness_vector = NULL;/*&partition->uptodatevector_ex;*/
+ r->in.req.req5.replica_flags = rf1->replica_flags;
+ r->in.req.req5.max_object_count = 133;
+ r->in.req.req5.max_ndr_size = 1336770;
+ r->in.req.req5.extended_op = DRSUAPI_EXOP_NONE;
+ r->in.req.req5.fsmo_info = 0;
+ }
+
+ req = dcerpc_drsuapi_DsGetNCChanges_send(drsuapi->pipe, r, r);
+ composite_continue_rpc(c, req, dreplsrv_op_pull_source_get_changes_recv, st);
+}
+
+static void dreplsrv_op_pull_source_apply_changes_send(struct dreplsrv_op_pull_source_state *st,
+ struct drsuapi_DsGetNCChanges *r,
+ uint32_t ctr_level,
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1,
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6);
+
+static void dreplsrv_op_pull_source_get_changes_recv(struct rpc_request *req)
+{
+ struct dreplsrv_op_pull_source_state *st = talloc_get_type(req->async.private_data,
+ struct dreplsrv_op_pull_source_state);
+ struct composite_context *c = st->creq;
+ struct drsuapi_DsGetNCChanges *r = talloc_get_type(req->ndr.struct_ptr,
+ struct drsuapi_DsGetNCChanges);
+ uint32_t ctr_level = 0;
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1 = NULL;
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6 = NULL;
+
+ c->status = dcerpc_ndr_request_recv(req);
+ if (!composite_is_ok(c)) return;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ composite_error(c, werror_to_ntstatus(r->out.result));
+ return;
+ }
+
+ if (*r->out.level == 1) {
+ ctr_level = 1;
+ ctr1 = &r->out.ctr.ctr1;
+ } else if (*r->out.level == 2 &&
+ r->out.ctr.ctr2.mszip1.ts) {
+ ctr_level = 1;
+ ctr1 = &r->out.ctr.ctr2.mszip1.ts->ctr1;
+ } else if (*r->out.level == 6) {
+ ctr_level = 6;
+ ctr6 = &r->out.ctr.ctr6;
+ } else if (*r->out.level == 7 &&
+ r->out.ctr.ctr7.level == 6 &&
+ r->out.ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_MSZIP &&
+ r->out.ctr.ctr7.ctr.mszip6.ts) {
+ ctr_level = 6;
+ ctr6 = &r->out.ctr.ctr7.ctr.mszip6.ts->ctr6;
+ } else if (*r->out.level == 7 &&
+ r->out.ctr.ctr7.level == 6 &&
+ r->out.ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_XPRESS &&
+ r->out.ctr.ctr7.ctr.xpress6.ts) {
+ ctr_level = 6;
+ ctr6 = &r->out.ctr.ctr7.ctr.xpress6.ts->ctr6;
+ } else {
+ composite_error(c, werror_to_ntstatus(WERR_BAD_NET_RESP));
+ return;
+ }
+
+ if (!ctr1 && !ctr6) {
+ composite_error(c, werror_to_ntstatus(WERR_BAD_NET_RESP));
+ return;
+ }
+
+ if (ctr_level == 6) {
+ if (!W_ERROR_IS_OK(ctr6->drs_error)) {
+ composite_error(c, werror_to_ntstatus(ctr6->drs_error));
+ return;
+ }
+ }
+
+ dreplsrv_op_pull_source_apply_changes_send(st, r, ctr_level, ctr1, ctr6);
+}
+
+static void dreplsrv_op_pull_source_apply_changes_send(struct dreplsrv_op_pull_source_state *st,
+ struct drsuapi_DsGetNCChanges *r,
+ uint32_t ctr_level,
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1,
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6)
+{
+ struct composite_context *c = st->creq;
+ struct repsFromTo1 rf1 = *st->op->source_dsa->repsFrom1;
+ struct dreplsrv_service *service = st->op->service;
+ struct dreplsrv_partition *partition = st->op->source_dsa->partition;
+ struct dreplsrv_drsuapi_connection *drsuapi = st->op->source_dsa->conn->drsuapi;
+ const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr;
+ uint32_t object_count;
+ struct drsuapi_DsReplicaObjectListItemEx *first_object;
+ uint32_t linked_attributes_count;
+ struct drsuapi_DsReplicaLinkedAttribute *linked_attributes;
+ const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector;
+ bool more_data = false;
+ WERROR status;
+
+ switch (ctr_level) {
+ case 1:
+ mapping_ctr = &ctr1->mapping_ctr;
+ object_count = ctr1->object_count;
+ first_object = ctr1->first_object;
+ linked_attributes_count = 0;
+ linked_attributes = NULL;
+ rf1.highwatermark = ctr1->new_highwatermark;
+ uptodateness_vector = NULL; /* TODO: map it */
+ more_data = ctr1->more_data;
+ break;
+ case 6:
+ mapping_ctr = &ctr6->mapping_ctr;
+ object_count = ctr6->object_count;
+ first_object = ctr6->first_object;
+ linked_attributes_count = ctr6->linked_attributes_count;
+ linked_attributes = ctr6->linked_attributes;
+ rf1.highwatermark = ctr6->new_highwatermark;
+ uptodateness_vector = ctr6->uptodateness_vector;
+ more_data = ctr6->more_data;
+ break;
+ default:
+ composite_error(c, werror_to_ntstatus(WERR_BAD_NET_RESP));
+ return;
+ }
+
+ status = dsdb_extended_replicated_objects_commit(service->samdb,
+ partition->nc.dn,
+ mapping_ctr,
+ object_count,
+ first_object,
+ linked_attributes_count,
+ linked_attributes,
+ &rf1,
+ uptodateness_vector,
+ &drsuapi->gensec_skey,
+ st, NULL);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("Failed to commit objects: %s\n", win_errstr(status)));
+ composite_error(c, werror_to_ntstatus(status));
+ return;
+ }
+
+ /* if it applied fine, we need to update the highwatermark */
+ *st->op->source_dsa->repsFrom1 = rf1;
+
+ /*
+ * TODO: update our uptodatevector!
+ */
+
+ if (more_data) {
+ dreplsrv_op_pull_source_get_changes_send(st);
+ return;
+ }
+
+ composite_done(c);
+}
+
+WERROR dreplsrv_op_pull_source_recv(struct composite_context *c)
+{
+ NTSTATUS status;
+
+ status = composite_wait(c);
+
+ talloc_free(c);
+ return ntstatus_to_werror(status);
+}
diff --git a/source4/dsdb/repl/drepl_out_helpers.h b/source4/dsdb/repl/drepl_out_helpers.h
new file mode 100644
index 0000000000..626112b82f
--- /dev/null
+++ b/source4/dsdb/repl/drepl_out_helpers.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service helper function for outgoing traffic
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/>.
+
+*/
+
+#ifndef DREPL_OUT_HELPERS_H
+#define DREPL_OUT_HELPERS_H
+
+
+#endif /* DREPL_OUT_HELPERS_H */
diff --git a/source4/dsdb/repl/drepl_out_pull.c b/source4/dsdb/repl/drepl_out_pull.c
new file mode 100644
index 0000000000..f33959243e
--- /dev/null
+++ b/source4/dsdb/repl/drepl_out_pull.c
@@ -0,0 +1,154 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service outgoing Pull-Replication
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/samdb/samdb.h"
+#include "auth/auth.h"
+#include "smbd/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/repl/drepl_service.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "libcli/composite/composite.h"
+
+static WERROR dreplsrv_schedule_partition_pull_source(struct dreplsrv_service *s,
+ struct dreplsrv_partition *p,
+ struct dreplsrv_partition_source_dsa *source,
+ TALLOC_CTX *mem_ctx)
+{
+ struct dreplsrv_out_operation *op;
+
+ op = talloc_zero(mem_ctx, struct dreplsrv_out_operation);
+ W_ERROR_HAVE_NO_MEMORY(op);
+
+ op->service = s;
+ op->source_dsa = source;
+
+ DLIST_ADD_END(s->ops.pending, op, struct dreplsrv_out_operation *);
+ talloc_steal(s, op);
+ return WERR_OK;
+}
+
+static WERROR dreplsrv_schedule_partition_pull(struct dreplsrv_service *s,
+ struct dreplsrv_partition *p,
+ TALLOC_CTX *mem_ctx)
+{
+ WERROR status;
+ struct dreplsrv_partition_source_dsa *cur;
+
+ for (cur = p->sources; cur; cur = cur->next) {
+ status = dreplsrv_schedule_partition_pull_source(s, p, cur, mem_ctx);
+ W_ERROR_NOT_OK_RETURN(status);
+ }
+
+ return WERR_OK;
+}
+
+WERROR dreplsrv_schedule_pull_replication(struct dreplsrv_service *s, TALLOC_CTX *mem_ctx)
+{
+ WERROR status;
+ struct dreplsrv_partition *p;
+
+ for (p = s->partitions; p; p = p->next) {
+ status = dreplsrv_schedule_partition_pull(s, p, mem_ctx);
+ W_ERROR_NOT_OK_RETURN(status);
+ }
+
+ return WERR_OK;
+}
+
+static void dreplsrv_pending_op_callback(struct dreplsrv_out_operation *op)
+{
+ struct repsFromTo1 *rf = op->source_dsa->repsFrom1;
+ struct dreplsrv_service *s = op->service;
+ time_t t;
+ NTTIME now;
+
+ t = time(NULL);
+ unix_to_nt_time(&now, t);
+
+ rf->result_last_attempt = dreplsrv_op_pull_source_recv(op->creq);
+ if (W_ERROR_IS_OK(rf->result_last_attempt)) {
+ rf->consecutive_sync_failures = 0;
+ rf->last_success = now;
+ DEBUG(2,("dreplsrv_op_pull_source(%s)\n",
+ win_errstr(rf->result_last_attempt)));
+ goto done;
+ }
+
+ rf->consecutive_sync_failures++;
+
+ DEBUG(1,("dreplsrv_op_pull_source(%s/%s) failures[%u]\n",
+ win_errstr(rf->result_last_attempt),
+ nt_errstr(werror_to_ntstatus(rf->result_last_attempt)),
+ rf->consecutive_sync_failures));
+
+done:
+ talloc_free(op);
+ s->ops.current = NULL;
+ dreplsrv_run_pending_ops(s);
+}
+
+static void dreplsrv_pending_op_callback_creq(struct composite_context *creq)
+{
+ struct dreplsrv_out_operation *op = talloc_get_type(creq->async.private_data,
+ struct dreplsrv_out_operation);
+ dreplsrv_pending_op_callback(op);
+}
+
+void dreplsrv_run_pending_ops(struct dreplsrv_service *s)
+{
+ struct dreplsrv_out_operation *op;
+ time_t t;
+ NTTIME now;
+
+ if (s->ops.current) {
+ /* if there's still one running, we're done */
+ return;
+ }
+
+ if (!s->ops.pending) {
+ /* if there're no pending operations, we're done */
+ return;
+ }
+
+ t = time(NULL);
+ unix_to_nt_time(&now, t);
+
+ op = s->ops.pending;
+ s->ops.current = op;
+ DLIST_REMOVE(s->ops.pending, op);
+
+ op->source_dsa->repsFrom1->last_attempt = now;
+
+ op->creq = dreplsrv_op_pull_source_send(op);
+ if (!op->creq) {
+ dreplsrv_pending_op_callback(op);
+ return;
+ }
+
+ op->creq->async.fn = dreplsrv_pending_op_callback_creq;
+ op->creq->async.private_data = op;
+}
diff --git a/source4/dsdb/repl/drepl_partitions.c b/source4/dsdb/repl/drepl_partitions.c
new file mode 100644
index 0000000000..8a1c5aa31a
--- /dev/null
+++ b/source4/dsdb/repl/drepl_partitions.c
@@ -0,0 +1,272 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/samdb/samdb.h"
+#include "auth/auth.h"
+#include "smbd/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/repl/drepl_service.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+
+static WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s);
+
+WERROR dreplsrv_load_partitions(struct dreplsrv_service *s)
+{
+ WERROR status;
+ struct ldb_dn *basedn;
+ struct ldb_result *r;
+ struct ldb_message_element *el;
+ static const char *attrs[] = { "namingContexts", NULL };
+ uint32_t i;
+ int ret;
+
+ basedn = ldb_dn_new(s, s->samdb, NULL);
+ W_ERROR_HAVE_NO_MEMORY(basedn);
+
+ ret = ldb_search(s->samdb, basedn, LDB_SCOPE_BASE,
+ "(objectClass=*)", attrs, &r);
+ talloc_free(basedn);
+ if (ret != LDB_SUCCESS) {
+ return WERR_FOOBAR;
+ } else if (r->count != 1) {
+ talloc_free(r);
+ return WERR_FOOBAR;
+ }
+ talloc_steal(s, r);
+
+ el = ldb_msg_find_element(r->msgs[0], "namingContexts");
+ if (!el) {
+ return WERR_FOOBAR;
+ }
+
+ for (i=0; el && i < el->num_values; i++) {
+ const char *v = (const char *)el->values[i].data;
+ struct ldb_dn *pdn;
+ struct dreplsrv_partition *p;
+
+ pdn = ldb_dn_new(s, s->samdb, v);
+ if (!ldb_dn_validate(pdn)) {
+ return WERR_FOOBAR;
+ }
+
+ p = talloc_zero(s, struct dreplsrv_partition);
+ W_ERROR_HAVE_NO_MEMORY(p);
+
+ p->dn = talloc_steal(p, pdn);
+
+ DLIST_ADD(s->partitions, p);
+
+ DEBUG(2, ("dreplsrv_partition[%s] loaded\n", v));
+ }
+
+ talloc_free(r);
+
+ status = dreplsrv_refresh_partitions(s);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ return WERR_OK;
+}
+
+static WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
+ const struct repsFromTo1 *rft,
+ struct dreplsrv_out_connection **_conn)
+{
+ struct dreplsrv_out_connection *cur, *conn = NULL;
+ const char *hostname;
+
+ if (!rft->other_info) {
+ return WERR_FOOBAR;
+ }
+
+ if (!rft->other_info->dns_name) {
+ return WERR_FOOBAR;
+ }
+
+ hostname = rft->other_info->dns_name;
+
+ for (cur = s->connections; cur; cur = cur->next) {
+ if (strcmp(cur->binding->host, hostname) == 0) {
+ conn = cur;
+ break;
+ }
+ }
+
+ if (!conn) {
+ NTSTATUS nt_status;
+ char *binding_str;
+
+ conn = talloc_zero(s, struct dreplsrv_out_connection);
+ W_ERROR_HAVE_NO_MEMORY(conn);
+
+ conn->service = s;
+
+ binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
+ hostname);
+ W_ERROR_HAVE_NO_MEMORY(binding_str);
+ nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding);
+ talloc_free(binding_str);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return ntstatus_to_werror(nt_status);
+ }
+
+ DLIST_ADD_END(s->connections, conn, struct dreplsrv_out_connection *);
+
+ DEBUG(2,("dreplsrv_out_connection_attach(%s): create\n", conn->binding->host));
+ } else {
+ DEBUG(2,("dreplsrv_out_connection_attach(%s): attach\n", conn->binding->host));
+ }
+
+ *_conn = conn;
+ return WERR_OK;
+}
+
+static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s,
+ struct dreplsrv_partition *p,
+ const struct ldb_val *val)
+{
+ WERROR status;
+ enum ndr_err_code ndr_err;
+ struct dreplsrv_partition_source_dsa *source;
+
+ source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
+ W_ERROR_HAVE_NO_MEMORY(source);
+
+ ndr_err = ndr_pull_struct_blob(val, source,
+ lp_iconv_convenience(s->task->lp_ctx), &source->_repsFromBlob,
+ (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(nt_status);
+ }
+ /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
+ if (source->_repsFromBlob.version != 1) {
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+
+ source->partition = p;
+ source->repsFrom1 = &source->_repsFromBlob.ctr.ctr1;
+
+ status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ DLIST_ADD_END(p->sources, source, struct dreplsrv_partition_source_dsa *);
+ return WERR_OK;
+}
+
+static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
+ struct dreplsrv_partition *p,
+ TALLOC_CTX *mem_ctx)
+{
+ WERROR status;
+ const struct ldb_val *ouv_value;
+ struct replUpToDateVectorBlob ouv;
+ struct dom_sid *nc_sid;
+ struct ldb_message_element *orf_el = NULL;
+ struct ldb_result *r;
+ uint32_t i;
+ int ret;
+ static const char *attrs[] = {
+ "objectSid",
+ "objectGUID",
+ "replUpToDateVector",
+ "repsFrom",
+ NULL
+ };
+
+ DEBUG(2, ("dreplsrv_refresh_partition(%s)\n",
+ ldb_dn_get_linearized(p->dn)));
+
+ ret = ldb_search(s->samdb, p->dn, LDB_SCOPE_BASE,
+ "(objectClass=*)", attrs, &r);
+ if (ret != LDB_SUCCESS) {
+ return WERR_FOOBAR;
+ } else if (r->count != 1) {
+ talloc_free(r);
+ return WERR_FOOBAR;
+ }
+ talloc_steal(mem_ctx, r);
+
+ ZERO_STRUCT(p->nc);
+ p->nc.dn = ldb_dn_alloc_linearized(p, p->dn);
+ W_ERROR_HAVE_NO_MEMORY(p->nc.dn);
+ p->nc.guid = samdb_result_guid(r->msgs[0], "objectGUID");
+ nc_sid = samdb_result_dom_sid(p, r->msgs[0], "objectSid");
+ if (nc_sid) {
+ p->nc.sid = *nc_sid;
+ }
+
+ ouv_value = ldb_msg_find_ldb_val(r->msgs[0], "replUpToDateVector");
+ if (ouv_value) {
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_struct_blob(ouv_value, mem_ctx,
+ lp_iconv_convenience(s->task->lp_ctx), &ouv,
+ (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(nt_status);
+ }
+ /* NDR_PRINT_DEBUG(replUpToDateVectorBlob, &ouv); */
+ if (ouv.version != 2) {
+ return WERR_DS_DRA_INTERNAL_ERROR;
+ }
+
+ p->uptodatevector.count = ouv.ctr.ctr2.count;
+ p->uptodatevector.reserved = ouv.ctr.ctr2.reserved;
+ p->uptodatevector.cursors = talloc_steal(p, ouv.ctr.ctr2.cursors);
+ }
+
+ /*
+ * TODO: add our own uptodatevector cursor
+ */
+
+
+ orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom");
+ if (orf_el) {
+ for (i=0; i < orf_el->num_values; i++) {
+ status = dreplsrv_partition_add_source_dsa(s, p, &orf_el->values[i]);
+ W_ERROR_NOT_OK_RETURN(status);
+ }
+ }
+
+ talloc_free(r);
+
+ return WERR_OK;
+}
+
+static WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
+{
+ WERROR status;
+ struct dreplsrv_partition *p;
+
+ for (p = s->partitions; p; p = p->next) {
+ status = dreplsrv_refresh_partition(s, p, p);
+ W_ERROR_NOT_OK_RETURN(status);
+ }
+
+ return WERR_OK;
+}
diff --git a/source4/dsdb/repl/drepl_periodic.c b/source4/dsdb/repl/drepl_periodic.c
new file mode 100644
index 0000000000..8745e210a1
--- /dev/null
+++ b/source4/dsdb/repl/drepl_periodic.c
@@ -0,0 +1,109 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service periodic handling
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/samdb/samdb.h"
+#include "auth/auth.h"
+#include "smbd/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/repl/drepl_service.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+
+static void dreplsrv_periodic_run(struct dreplsrv_service *service);
+
+static void dreplsrv_periodic_handler_te(struct event_context *ev, struct timed_event *te,
+ struct timeval t, void *ptr)
+{
+ struct dreplsrv_service *service = talloc_get_type(ptr, struct dreplsrv_service);
+ WERROR status;
+
+ service->periodic.te = NULL;
+
+ dreplsrv_periodic_run(service);
+
+ status = dreplsrv_periodic_schedule(service, service->periodic.interval);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(service->task, win_errstr(status));
+ return;
+ }
+}
+
+WERROR dreplsrv_periodic_schedule(struct dreplsrv_service *service, uint32_t next_interval)
+{
+ TALLOC_CTX *tmp_mem;
+ struct timed_event *new_te;
+ struct timeval next_time;
+
+ /* prevent looping */
+ if (next_interval == 0) next_interval = 1;
+
+ next_time = timeval_current_ofs(next_interval, 50);
+
+ if (service->periodic.te) {
+ /*
+ * if the timestamp of the new event is higher,
+ * as current next we don't need to reschedule
+ */
+ if (timeval_compare(&next_time, &service->periodic.next_event) > 0) {
+ return WERR_OK;
+ }
+ }
+
+ /* reset the next scheduled timestamp */
+ service->periodic.next_event = next_time;
+
+ new_te = event_add_timed(service->task->event_ctx, service,
+ service->periodic.next_event,
+ dreplsrv_periodic_handler_te, service);
+ W_ERROR_HAVE_NO_MEMORY(new_te);
+
+ tmp_mem = talloc_new(service);
+ DEBUG(2,("dreplsrv_periodic_schedule(%u) %sscheduled for: %s\n",
+ next_interval,
+ (service->periodic.te?"re":""),
+ nt_time_string(tmp_mem, timeval_to_nttime(&next_time))));
+ talloc_free(tmp_mem);
+
+ talloc_free(service->periodic.te);
+ service->periodic.te = new_te;
+
+ return WERR_OK;
+}
+
+static void dreplsrv_periodic_run(struct dreplsrv_service *service)
+{
+ TALLOC_CTX *mem_ctx;
+
+ DEBUG(2,("dreplsrv_periodic_run(): schedule pull replication\n"));
+
+ mem_ctx = talloc_new(service);
+ dreplsrv_schedule_pull_replication(service, mem_ctx);
+ talloc_free(mem_ctx);
+
+ DEBUG(2,("dreplsrv_periodic_run(): run pending_ops\n"));
+
+ dreplsrv_run_pending_ops(service);
+}
diff --git a/source4/dsdb/repl/drepl_service.c b/source4/dsdb/repl/drepl_service.c
new file mode 100644
index 0000000000..3611258ca5
--- /dev/null
+++ b/source4/dsdb/repl/drepl_service.c
@@ -0,0 +1,189 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/samdb/samdb.h"
+#include "auth/auth.h"
+#include "smbd/service.h"
+#include "lib/events/events.h"
+#include "lib/messaging/irpc.h"
+#include "dsdb/repl/drepl_service.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+
+static WERROR dreplsrv_init_creds(struct dreplsrv_service *service)
+{
+ NTSTATUS status;
+
+ status = auth_system_session_info(service, service->task->lp_ctx,
+ &service->system_session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dreplsrv_connect_samdb(struct dreplsrv_service *service, struct loadparm_context *lp_ctx)
+{
+ const struct GUID *ntds_guid;
+ struct drsuapi_DsBindInfo28 *bind_info28;
+
+ service->samdb = samdb_connect(service, service->task->event_ctx, lp_ctx, service->system_session_info);
+ if (!service->samdb) {
+ return WERR_DS_SERVICE_UNAVAILABLE;
+ }
+
+ ntds_guid = samdb_ntds_objectGUID(service->samdb);
+ if (!ntds_guid) {
+ return WERR_DS_SERVICE_UNAVAILABLE;
+ }
+
+ service->ntds_guid = *ntds_guid;
+
+ bind_info28 = &service->bind_info28;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2;
+#if 0
+ if (s->domain_behavior_version == 2) {
+ /* TODO: find out how this is really triggered! */
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION;
+ }
+#endif
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_00100000;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7;
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT;
+#if 0 /* we don't support XPRESS compression yet */
+ bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_XPRESS_COMPRESS;
+#endif
+ /* TODO: fill in site_guid */
+ bind_info28->site_guid = GUID_zero();
+ /* TODO: find out how this is really triggered! */
+ bind_info28->pid = 0;
+ bind_info28->repl_epoch = 0;
+
+ return WERR_OK;
+}
+
+/*
+ startup the dsdb replicator service task
+*/
+static void dreplsrv_task_init(struct task_server *task)
+{
+ WERROR status;
+ struct dreplsrv_service *service;
+ uint32_t periodic_startup_interval;
+
+ switch (lp_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task, "dreplsrv: no DSDB replication required in standalone configuration");
+ return;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task, "dreplsrv: no DSDB replication required in domain member configuration");
+ return;
+ case ROLE_DOMAIN_CONTROLLER:
+ /* Yes, we want DSDB replication */
+ break;
+ }
+
+ task_server_set_title(task, "task[dreplsrv]");
+
+ service = talloc_zero(task, struct dreplsrv_service);
+ if (!service) {
+ task_server_terminate(task, "dreplsrv_task_init: out of memory");
+ return;
+ }
+ service->task = task;
+ service->startup_time = timeval_current();
+ task->private = service;
+
+ status = dreplsrv_init_creds(service);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "dreplsrv: Failed to obtain server credentials: %s\n",
+ win_errstr(status)));
+ return;
+ }
+
+ status = dreplsrv_connect_samdb(service, task->lp_ctx);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "dreplsrv: Failed to connect to local samdb: %s\n",
+ win_errstr(status)));
+ return;
+ }
+
+ status = dreplsrv_load_partitions(service);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "dreplsrv: Failed to load partitions: %s\n",
+ win_errstr(status)));
+ return;
+ }
+
+ periodic_startup_interval = lp_parm_int(task->lp_ctx, NULL, "dreplsrv", "periodic_startup_interval", 15); /* in seconds */
+ service->periodic.interval = lp_parm_int(task->lp_ctx, NULL, "dreplsrv", "periodic_interval", 300); /* in seconds */
+
+ status = dreplsrv_periodic_schedule(service, periodic_startup_interval);
+ if (!W_ERROR_IS_OK(status)) {
+ task_server_terminate(task, talloc_asprintf(task,
+ "dreplsrv: Failed to periodic schedule: %s\n",
+ win_errstr(status)));
+ return;
+ }
+
+ irpc_add_name(task->msg_ctx, "dreplsrv");
+}
+
+/*
+ register ourselves as a available server
+*/
+NTSTATUS server_service_drepl_init(void)
+{
+ return register_server_service("drepl", dreplsrv_task_init);
+}
diff --git a/source4/dsdb/repl/drepl_service.h b/source4/dsdb/repl/drepl_service.h
new file mode 100644
index 0000000000..0847a016f7
--- /dev/null
+++ b/source4/dsdb/repl/drepl_service.h
@@ -0,0 +1,175 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB replication service
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/>.
+
+*/
+
+#ifndef _DSDB_REPL_DREPL_SERVICE_H_
+#define _DSDB_REPL_DREPL_SERVICE_H_
+
+#include "librpc/gen_ndr/ndr_drsuapi_c.h"
+
+struct dreplsrv_service;
+struct dreplsrv_partition;
+
+struct dreplsrv_drsuapi_connection {
+ /*
+ * this pipe pointer is also the indicator
+ * for a valid connection
+ */
+ struct dcerpc_pipe *pipe;
+
+ DATA_BLOB gensec_skey;
+ struct drsuapi_DsBindInfo28 remote_info28;
+ struct policy_handle bind_handle;
+};
+
+struct dreplsrv_out_connection {
+ struct dreplsrv_out_connection *prev, *next;
+
+ struct dreplsrv_service *service;
+
+ /*
+ * the binding for the outgoing connection
+ */
+ struct dcerpc_binding *binding;
+
+ /* the out going connection to the source dsa */
+ struct dreplsrv_drsuapi_connection *drsuapi;
+};
+
+struct dreplsrv_partition_source_dsa {
+ struct dreplsrv_partition_source_dsa *prev, *next;
+
+ struct dreplsrv_partition *partition;
+
+ /*
+ * the cached repsFrom value for this source dsa
+ *
+ * it needs to be updated after each DsGetNCChanges() call
+ * to the source dsa
+ *
+ * repsFrom1 == &_repsFromBlob.ctr.ctr1
+ */
+ struct repsFromToBlob _repsFromBlob;
+ struct repsFromTo1 *repsFrom1;
+
+ /* the reference to the source_dsa and its outgoing connection */
+ struct dreplsrv_out_connection *conn;
+};
+
+struct dreplsrv_partition {
+ struct dreplsrv_partition *prev, *next;
+
+ struct dreplsrv_service *service;
+
+ /* the dn of the partition */
+ struct ldb_dn *dn;
+ struct drsuapi_DsReplicaObjectIdentifier nc;
+
+ /*
+ * uptodate vector needs to be updated before and after each DsGetNCChanges() call
+ *
+ * - before: we need to use our own invocationId together with our highestCommitedUsn
+ * - after: we need to merge in the remote uptodatevector, to avoid reading it again
+ */
+ struct replUpToDateVectorCtr2 uptodatevector;
+ struct drsuapi_DsReplicaCursorCtrEx uptodatevector_ex;
+
+ /*
+ * a linked list of all source dsa's we replicate from
+ */
+ struct dreplsrv_partition_source_dsa *sources;
+};
+
+struct dreplsrv_out_operation {
+ struct dreplsrv_out_operation *prev, *next;
+
+ struct dreplsrv_service *service;
+
+ struct dreplsrv_partition_source_dsa *source_dsa;
+
+ struct composite_context *creq;
+};
+
+struct dreplsrv_service {
+ /* the whole drepl service is in one task */
+ struct task_server *task;
+
+ /* the time the service was started */
+ struct timeval startup_time;
+
+ /*
+ * system session info
+ * with machine account credentials
+ */
+ struct auth_session_info *system_session_info;
+
+ /*
+ * a connection to the local samdb
+ */
+ struct ldb_context *samdb;
+
+ /* the guid of our NTDS Settings object, which never changes! */
+ struct GUID ntds_guid;
+ /*
+ * the struct holds the values used for outgoing DsBind() calls,
+ * so that we need to set them up only once
+ */
+ struct drsuapi_DsBindInfo28 bind_info28;
+
+ /* some stuff for periodic processing */
+ struct {
+ /*
+ * the interval between to periodic runs
+ */
+ uint32_t interval;
+
+ /*
+ * the timestamp for the next event,
+ * this is the timstamp passed to event_add_timed()
+ */
+ struct timeval next_event;
+
+ /* here we have a reference to the timed event the schedules the periodic stuff */
+ struct timed_event *te;
+ } periodic;
+
+ /*
+ * the list of partitions we need to replicate
+ */
+ struct dreplsrv_partition *partitions;
+
+ /*
+ * the list of cached connections
+ */
+ struct dreplsrv_out_connection *connections;
+
+ struct {
+ /* the pointer to the current active operation */
+ struct dreplsrv_out_operation *current;
+
+ /* the list of pending operations */
+ struct dreplsrv_out_operation *pending;
+ } ops;
+};
+
+#include "dsdb/repl/drepl_out_helpers.h"
+#include "dsdb/repl/drepl_service_proto.h"
+
+#endif /* _DSDB_REPL_DREPL_SERVICE_H_ */
diff --git a/source4/dsdb/repl/replicated_objects.c b/source4/dsdb/repl/replicated_objects.c
new file mode 100644
index 0000000000..552d83f411
--- /dev/null
+++ b/source4/dsdb/repl/replicated_objects.c
@@ -0,0 +1,417 @@
+/*
+ Unix SMB/CIFS mplementation.
+ Helper functions for applying replicated objects
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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/samdb/samdb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/crypto/crypto.h"
+#include "libcli/auth/libcli_auth.h"
+#include "param/param.h"
+
+static WERROR dsdb_decrypt_attribute_value(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *gensec_skey,
+ bool rid_crypt,
+ uint32_t rid,
+ DATA_BLOB *in,
+ DATA_BLOB *out)
+{
+ DATA_BLOB confounder;
+ DATA_BLOB enc_buffer;
+
+ struct MD5Context md5;
+ uint8_t _enc_key[16];
+ DATA_BLOB enc_key;
+
+ DATA_BLOB dec_buffer;
+
+ uint32_t crc32_given;
+ uint32_t crc32_calc;
+ DATA_BLOB checked_buffer;
+
+ DATA_BLOB plain_buffer;
+
+ /*
+ * users with rid == 0 should not exist
+ */
+ if (rid_crypt && rid == 0) {
+ return WERR_DS_DRA_INVALID_PARAMETER;
+ }
+
+ /*
+ * the first 16 bytes at the beginning are the confounder
+ * followed by the 4 byte crc32 checksum
+ */
+ if (in->length < 20) {
+ return WERR_DS_DRA_INVALID_PARAMETER;
+ }
+ confounder = data_blob_const(in->data, 16);
+ enc_buffer = data_blob_const(in->data + 16, in->length - 16);
+
+ /*
+ * build the encryption key md5 over the session key followed
+ * by the confounder
+ *
+ * here the gensec session key is used and
+ * not the dcerpc ncacn_ip_tcp "SystemLibraryDTC" key!
+ */
+ enc_key = data_blob_const(_enc_key, sizeof(_enc_key));
+ MD5Init(&md5);
+ MD5Update(&md5, gensec_skey->data, gensec_skey->length);
+ MD5Update(&md5, confounder.data, confounder.length);
+ MD5Final(enc_key.data, &md5);
+
+ /*
+ * copy the encrypted buffer part and
+ * decrypt it using the created encryption key using arcfour
+ */
+ dec_buffer = data_blob_const(enc_buffer.data, enc_buffer.length);
+ arcfour_crypt_blob(dec_buffer.data, dec_buffer.length, &enc_key);
+
+ /*
+ * the first 4 byte are the crc32 checksum
+ * of the remaining bytes
+ */
+ crc32_given = IVAL(dec_buffer.data, 0);
+ crc32_calc = crc32_calc_buffer(dec_buffer.data + 4 , dec_buffer.length - 4);
+ if (crc32_given != crc32_calc) {
+ return WERR_SEC_E_DECRYPT_FAILURE;
+ }
+ checked_buffer = data_blob_const(dec_buffer.data + 4, dec_buffer.length - 4);
+
+ plain_buffer = data_blob_talloc(mem_ctx, checked_buffer.data, checked_buffer.length);
+ W_ERROR_HAVE_NO_MEMORY(plain_buffer.data);
+
+ /*
+ * The following rid_crypt obfuscation isn't session specific
+ * and not really needed here, because we allways know the rid of the
+ * user account.
+ *
+ * But for the rest of samba it's easier when we remove this static
+ * obfuscation here
+ */
+ if (rid_crypt) {
+ uint32_t i, num_hashes;
+
+ if ((checked_buffer.length % 16) != 0) {
+ return WERR_DS_DRA_INVALID_PARAMETER;
+ }
+
+ num_hashes = plain_buffer.length / 16;
+ for (i = 0; i < num_hashes; i++) {
+ uint32_t offset = i * 16;
+ sam_rid_crypt(rid, checked_buffer.data + offset, plain_buffer.data + offset, 0);
+ }
+ }
+
+ *out = plain_buffer;
+ return WERR_OK;
+}
+
+static WERROR dsdb_decrypt_attribute(const DATA_BLOB *gensec_skey,
+ uint32_t rid,
+ struct drsuapi_DsReplicaAttribute *attr)
+{
+ WERROR status;
+ TALLOC_CTX *mem_ctx;
+ DATA_BLOB *enc_data;
+ DATA_BLOB plain_data;
+ bool rid_crypt = false;
+
+ if (attr->value_ctr.num_values == 0) {
+ return WERR_OK;
+ }
+
+ switch (attr->attid) {
+ case DRSUAPI_ATTRIBUTE_dBCSPwd:
+ case DRSUAPI_ATTRIBUTE_unicodePwd:
+ case DRSUAPI_ATTRIBUTE_ntPwdHistory:
+ case DRSUAPI_ATTRIBUTE_lmPwdHistory:
+ rid_crypt = true;
+ break;
+ case DRSUAPI_ATTRIBUTE_supplementalCredentials:
+ case DRSUAPI_ATTRIBUTE_priorValue:
+ case DRSUAPI_ATTRIBUTE_currentValue:
+ case DRSUAPI_ATTRIBUTE_trustAuthOutgoing:
+ case DRSUAPI_ATTRIBUTE_trustAuthIncoming:
+ case DRSUAPI_ATTRIBUTE_initialAuthOutgoing:
+ case DRSUAPI_ATTRIBUTE_initialAuthIncoming:
+ break;
+ default:
+ return WERR_OK;
+ }
+
+ if (attr->value_ctr.num_values > 1) {
+ return WERR_DS_DRA_INVALID_PARAMETER;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ return WERR_DS_DRA_INVALID_PARAMETER;
+ }
+
+ mem_ctx = attr->value_ctr.values[0].blob;
+ enc_data = attr->value_ctr.values[0].blob;
+
+ status = dsdb_decrypt_attribute_value(mem_ctx,
+ gensec_skey,
+ rid_crypt,
+ rid,
+ enc_data,
+ &plain_data);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ talloc_free(attr->value_ctr.values[0].blob->data);
+ *attr->value_ctr.values[0].blob = plain_data;
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_convert_object(struct ldb_context *ldb,
+ const struct dsdb_schema *schema,
+ struct dsdb_extended_replicated_objects *ctr,
+ const struct drsuapi_DsReplicaObjectListItemEx *in,
+ const DATA_BLOB *gensec_skey,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_extended_replicated_object *out)
+{
+ NTSTATUS nt_status;
+ enum ndr_err_code ndr_err;
+ WERROR status;
+ uint32_t i;
+ struct ldb_message *msg;
+ struct replPropertyMetaDataBlob *md;
+ struct ldb_val guid_value;
+ NTTIME whenChanged = 0;
+ time_t whenChanged_t;
+ const char *whenChanged_s;
+ const char *rdn_name = NULL;
+ const struct ldb_val *rdn_value = NULL;
+ const struct dsdb_attribute *rdn_attr = NULL;
+ uint32_t rdn_attid;
+ struct drsuapi_DsReplicaAttribute *name_a = NULL;
+ struct drsuapi_DsReplicaMetaData *name_d = NULL;
+ struct replPropertyMetaData1 *rdn_m = NULL;
+ struct dom_sid *sid = NULL;
+ uint32_t rid = 0;
+ int ret;
+
+ if (!in->object.identifier) {
+ return WERR_FOOBAR;
+ }
+
+ if (!in->object.identifier->dn || !in->object.identifier->dn[0]) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->object.attribute_ctr.num_attributes != 0 && !in->meta_data_ctr) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->object.attribute_ctr.num_attributes != in->meta_data_ctr->count) {
+ return WERR_FOOBAR;
+ }
+
+ sid = &in->object.identifier->sid;
+ if (sid->num_auths > 0) {
+ rid = sid->sub_auths[sid->num_auths - 1];
+ }
+
+ msg = ldb_msg_new(mem_ctx);
+ W_ERROR_HAVE_NO_MEMORY(msg);
+
+ msg->dn = ldb_dn_new(msg, ldb, in->object.identifier->dn);
+ W_ERROR_HAVE_NO_MEMORY(msg->dn);
+
+ rdn_name = ldb_dn_get_rdn_name(msg->dn);
+ rdn_attr = dsdb_attribute_by_lDAPDisplayName(schema, rdn_name);
+ if (!rdn_attr) {
+ return WERR_FOOBAR;
+ }
+ rdn_attid = rdn_attr->attributeID_id;
+ rdn_value = ldb_dn_get_rdn_val(msg->dn);
+
+ msg->num_elements = in->object.attribute_ctr.num_attributes;
+ msg->elements = talloc_array(msg, struct ldb_message_element,
+ msg->num_elements);
+ W_ERROR_HAVE_NO_MEMORY(msg->elements);
+
+ md = talloc(mem_ctx, struct replPropertyMetaDataBlob);
+ W_ERROR_HAVE_NO_MEMORY(md);
+
+ md->version = 1;
+ md->reserved = 0;
+ md->ctr.ctr1.count = in->meta_data_ctr->count;
+ md->ctr.ctr1.reserved = 0;
+ md->ctr.ctr1.array = talloc_array(mem_ctx,
+ struct replPropertyMetaData1,
+ md->ctr.ctr1.count + 1); /* +1 because of the RDN attribute */
+ W_ERROR_HAVE_NO_MEMORY(md->ctr.ctr1.array);
+
+ for (i=0; i < in->meta_data_ctr->count; i++) {
+ struct drsuapi_DsReplicaAttribute *a;
+ struct drsuapi_DsReplicaMetaData *d;
+ struct replPropertyMetaData1 *m;
+ struct ldb_message_element *e;
+
+ a = &in->object.attribute_ctr.attributes[i];
+ d = &in->meta_data_ctr->meta_data[i];
+ m = &md->ctr.ctr1.array[i];
+ e = &msg->elements[i];
+
+ status = dsdb_decrypt_attribute(gensec_skey, rid, a);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ status = dsdb_attribute_drsuapi_to_ldb(schema, a, msg->elements, e);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ m->attid = a->attid;
+ m->version = d->version;
+ m->originating_change_time = d->originating_change_time;
+ m->originating_invocation_id = d->originating_invocation_id;
+ m->originating_usn = d->originating_usn;
+ m->local_usn = 0;
+
+ if (d->originating_change_time > whenChanged) {
+ whenChanged = d->originating_change_time;
+ }
+
+ if (a->attid == DRSUAPI_ATTRIBUTE_name) {
+ name_a = a;
+ name_d = d;
+ rdn_m = &md->ctr.ctr1.array[md->ctr.ctr1.count];
+ }
+ }
+
+ if (rdn_m) {
+ ret = ldb_msg_add_value(msg, rdn_attr->lDAPDisplayName, rdn_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return WERR_FOOBAR;
+ }
+
+ rdn_m->attid = rdn_attid;
+ rdn_m->version = name_d->version;
+ rdn_m->originating_change_time = name_d->originating_change_time;
+ rdn_m->originating_invocation_id = name_d->originating_invocation_id;
+ rdn_m->originating_usn = name_d->originating_usn;
+ rdn_m->local_usn = 0;
+ md->ctr.ctr1.count++;
+
+ }
+
+ whenChanged_t = nt_time_to_unix(whenChanged);
+ whenChanged_s = ldb_timestring(msg, whenChanged_t);
+ W_ERROR_HAVE_NO_MEMORY(whenChanged_s);
+
+ ndr_err = ndr_push_struct_blob(&guid_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
+ &in->object.identifier->guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(nt_status);
+ }
+
+ out->msg = msg;
+ out->guid_value = guid_value;
+ out->when_changed = whenChanged_s;
+ out->meta_data = md;
+ return WERR_OK;
+}
+
+WERROR dsdb_extended_replicated_objects_commit(struct ldb_context *ldb,
+ const char *partition_dn,
+ const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr,
+ uint32_t object_count,
+ const struct drsuapi_DsReplicaObjectListItemEx *first_object,
+ uint32_t linked_attributes_count,
+ const struct drsuapi_DsReplicaLinkedAttribute *linked_attributes,
+ const struct repsFromTo1 *source_dsa,
+ const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector,
+ const DATA_BLOB *gensec_skey,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_extended_replicated_objects **_out)
+{
+ WERROR status;
+ const struct dsdb_schema *schema;
+ struct dsdb_extended_replicated_objects *out;
+ struct ldb_result *ext_res;
+ const struct drsuapi_DsReplicaObjectListItemEx *cur;
+ uint32_t i;
+ int ret;
+
+ schema = dsdb_get_schema(ldb);
+ if (!schema) {
+ return WERR_DS_SCHEMA_NOT_LOADED;
+ }
+
+ status = dsdb_verify_oid_mappings_drsuapi(schema, mapping_ctr);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ out = talloc_zero(mem_ctx, struct dsdb_extended_replicated_objects);
+ W_ERROR_HAVE_NO_MEMORY(out);
+ out->version = DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION;
+
+ out->partition_dn = ldb_dn_new(out, ldb, partition_dn);
+ W_ERROR_HAVE_NO_MEMORY(out->partition_dn);
+
+ out->source_dsa = source_dsa;
+ out->uptodateness_vector= uptodateness_vector;
+
+ out->num_objects = object_count;
+ out->objects = talloc_array(out,
+ struct dsdb_extended_replicated_object,
+ out->num_objects);
+ W_ERROR_HAVE_NO_MEMORY(out->objects);
+
+ for (i=0, cur = first_object; cur; cur = cur->next_object, i++) {
+ if (i == out->num_objects) {
+ return WERR_FOOBAR;
+ }
+
+ status = dsdb_convert_object(ldb, schema, out, cur, gensec_skey, out->objects, &out->objects[i]);
+ W_ERROR_NOT_OK_RETURN(status);
+ }
+ if (i != out->num_objects) {
+ return WERR_FOOBAR;
+ }
+
+ /* TODO: handle linked attributes */
+
+ ret = ldb_extended(ldb, DSDB_EXTENDED_REPLICATED_OBJECTS_OID, out, &ext_res);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,("Failed to apply records: %s: %s\n",
+ ldb_errstring(ldb), ldb_strerror(ret)));
+ talloc_free(out);
+ return WERR_FOOBAR;
+ }
+ talloc_free(ext_res);
+
+ if (_out) {
+ *_out = out;
+ } else {
+ talloc_free(out);
+ }
+
+ return WERR_OK;
+}
diff --git a/source4/dsdb/samdb/cracknames.c b/source4/dsdb/samdb/cracknames.c
new file mode 100644
index 0000000000..369b06c305
--- /dev/null
+++ b/source4/dsdb/samdb/cracknames.c
@@ -0,0 +1,1290 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ endpoint server for the drsuapi pipe
+ DsCrackNames()
+
+ Copyright (C) Stefan Metzmacher 2004
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-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 "librpc/gen_ndr/drsuapi.h"
+#include "rpc_server/common/common.h"
+#include "lib/events/events.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "auth/auth.h"
+#include "util/util_ldb.h"
+#include "dsdb/samdb/samdb.h"
+#include "param/param.h"
+
+static WERROR DsCrackNameOneFilter(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
+ struct ldb_dn *name_dn, const char *name,
+ const char *domain_filter, const char *result_filter,
+ struct drsuapi_DsNameInfo1 *info1);
+static WERROR DsCrackNameOneSyntactical(TALLOC_CTX *mem_ctx,
+ uint32_t format_offered, uint32_t format_desired,
+ struct ldb_dn *name_dn, const char *name,
+ struct drsuapi_DsNameInfo1 *info1);
+
+static WERROR dns_domain_from_principal(TALLOC_CTX *mem_ctx, struct smb_krb5_context *smb_krb5_context,
+ const char *name,
+ struct drsuapi_DsNameInfo1 *info1)
+{
+ krb5_error_code ret;
+ krb5_principal principal;
+ /* perhaps it's a principal with a realm, so return the right 'domain only' response */
+ char **realm;
+ ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
+ KRB5_PRINCIPAL_PARSE_MUST_REALM, &principal);
+ if (ret) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+
+ /* This isn't an allocation assignemnt, so it is free'ed with the krb5_free_principal */
+ realm = krb5_princ_realm(smb_krb5_context->krb5_context, principal);
+
+ info1->dns_domain_name = talloc_strdup(mem_ctx, *realm);
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+
+ W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name);
+
+ info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY;
+ return WERR_OK;
+}
+
+static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(krb5_context context, struct ldb_context *ldb_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *alias_from,
+ char **alias_to)
+{
+ int i;
+ int ret;
+ struct ldb_result *res;
+ struct ldb_message_element *spnmappings;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *service_dn;
+ char *service_dn_str;
+
+ const char *directory_attrs[] = {
+ "sPNMappings",
+ NULL
+ };
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ }
+
+ service_dn = ldb_dn_new(tmp_ctx, ldb_ctx, "CN=Directory Service,CN=Windows NT,CN=Services");
+ if ( ! ldb_dn_add_base(service_dn, samdb_config_dn(ldb_ctx))) {
+ return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ }
+ service_dn_str = ldb_dn_alloc_linearized(tmp_ctx, service_dn);
+ if ( ! service_dn_str) {
+ return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ }
+
+ ret = ldb_search(ldb_ctx, service_dn, LDB_SCOPE_BASE, "(objectClass=nTDSService)",
+ directory_attrs, &res);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(1, ("ldb_search: dn: %s not found: %s", service_dn_str, ldb_errstring(ldb_ctx)));
+ return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ } else if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(1, ("ldb_search: dn: %s not found", service_dn_str));
+ return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ } else if (res->count != 1) {
+ talloc_free(res);
+ DEBUG(1, ("ldb_search: dn: %s not found", service_dn_str));
+ return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ }
+ talloc_steal(tmp_ctx, res);
+
+ spnmappings = ldb_msg_find_element(res->msgs[0], "sPNMappings");
+ if (!spnmappings || spnmappings->num_values == 0) {
+ DEBUG(1, ("ldb_search: dn: %s no sPNMappings attribute", service_dn_str));
+ talloc_free(tmp_ctx);
+ return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ }
+
+ for (i = 0; i < spnmappings->num_values; i++) {
+ char *mapping, *p, *str;
+ mapping = talloc_strdup(tmp_ctx,
+ (const char *)spnmappings->values[i].data);
+ if (!mapping) {
+ DEBUG(1, ("LDB_lookup_spn_alias: ldb_search: dn: %s did not have an sPNMapping\n", service_dn_str));
+ talloc_free(tmp_ctx);
+ return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ }
+
+ /* C string manipulation sucks */
+
+ p = strchr(mapping, '=');
+ if (!p) {
+ DEBUG(1, ("ldb_search: dn: %s sPNMapping malformed: %s\n",
+ service_dn_str, mapping));
+ talloc_free(tmp_ctx);
+ return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ }
+ p[0] = '\0';
+ p++;
+ do {
+ str = p;
+ p = strchr(p, ',');
+ if (p) {
+ p[0] = '\0';
+ p++;
+ }
+ if (strcasecmp(str, alias_from) == 0) {
+ *alias_to = mapping;
+ talloc_steal(mem_ctx, mapping);
+ talloc_free(tmp_ctx);
+ return DRSUAPI_DS_NAME_STATUS_OK;
+ }
+ } while (p);
+ }
+ DEBUG(4, ("LDB_lookup_spn_alias: no alias for service %s applicable\n", alias_from));
+ talloc_free(tmp_ctx);
+ return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+}
+
+/* When cracking a ServicePrincipalName, many services may be served
+ * by the host/ servicePrincipalName. The incoming query is for cifs/
+ * but we translate it here, and search on host/. This is done after
+ * the cifs/ entry has been searched for, making this a fallback */
+
+static WERROR DsCrackNameSPNAlias(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
+ const char *name, struct drsuapi_DsNameInfo1 *info1)
+{
+ WERROR wret;
+ krb5_error_code ret;
+ krb5_principal principal;
+ const char *service, *dns_name;
+ char *new_service;
+ char *new_princ;
+ enum drsuapi_DsNameStatus namestatus;
+
+ /* parse principal */
+ ret = krb5_parse_name_flags(smb_krb5_context->krb5_context,
+ name, KRB5_PRINCIPAL_PARSE_NO_REALM, &principal);
+ if (ret) {
+ DEBUG(2, ("Could not parse principal: %s: %s",
+ name, smb_get_krb5_error_message(smb_krb5_context->krb5_context,
+ ret, mem_ctx)));
+ return WERR_NOMEM;
+ }
+
+ /* grab cifs/, http/ etc */
+
+ /* This is checked for in callers, but be safe */
+ if (principal->name.name_string.len < 2) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+ service = principal->name.name_string.val[0];
+ dns_name = principal->name.name_string.val[1];
+
+ /* MAP it */
+ namestatus = LDB_lookup_spn_alias(smb_krb5_context->krb5_context,
+ sam_ctx, mem_ctx,
+ service, &new_service);
+
+ if (namestatus == DRSUAPI_DS_NAME_STATUS_NOT_FOUND) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY;
+ info1->dns_domain_name = talloc_strdup(mem_ctx, dns_name);
+ if (!info1->dns_domain_name) {
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_NOMEM;
+ }
+ return WERR_OK;
+ } else if (namestatus != DRSUAPI_DS_NAME_STATUS_OK) {
+ info1->status = namestatus;
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_OK;
+ }
+
+ /* ooh, very nasty playing around in the Principal... */
+ free(principal->name.name_string.val[0]);
+ principal->name.name_string.val[0] = strdup(new_service);
+ if (!principal->name.name_string.val[0]) {
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_NOMEM;
+ }
+
+ /* reform principal */
+ ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &new_princ);
+
+ if (ret) {
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_NOMEM;
+ }
+
+ wret = DsCrackNameOneName(sam_ctx, mem_ctx, format_flags, format_offered, format_desired,
+ new_princ, info1);
+ free(new_princ);
+ if (W_ERROR_IS_OK(wret) && (info1->status == DRSUAPI_DS_NAME_STATUS_NOT_FOUND)) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY;
+ info1->dns_domain_name = talloc_strdup(mem_ctx, dns_name);
+ if (!info1->dns_domain_name) {
+ wret = WERR_NOMEM;
+ }
+ }
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return wret;
+}
+
+/* Subcase of CrackNames, for the userPrincipalName */
+
+static WERROR DsCrackNameUPN(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
+ const char *name, struct drsuapi_DsNameInfo1 *info1)
+{
+ int ldb_ret;
+ WERROR status;
+ const char *domain_filter = NULL;
+ const char *result_filter = NULL;
+ krb5_error_code ret;
+ krb5_principal principal;
+ char **realm;
+ char *unparsed_name_short;
+ const char *domain_attrs[] = { NULL };
+ struct ldb_result *domain_res = NULL;
+
+ /* Prevent recursion */
+ if (!name) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+
+ ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
+ KRB5_PRINCIPAL_PARSE_MUST_REALM, &principal);
+ if (ret) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+
+ realm = krb5_princ_realm(smb_krb5_context->krb5_context, principal);
+
+ ldb_ret = ldb_search_exp_fmt(sam_ctx, mem_ctx, &domain_res,
+ samdb_partitions_dn(sam_ctx, mem_ctx),
+ LDB_SCOPE_ONELEVEL,
+ domain_attrs,
+ "(&(&(|(&(dnsRoot=%s)(nETBIOSName=*))(nETBIOSName=%s))(objectclass=crossRef))(ncName=*))",
+ ldb_binary_encode_string(mem_ctx, *realm),
+ ldb_binary_encode_string(mem_ctx, *realm));
+
+ if (ldb_ret != LDB_SUCCESS) {
+ DEBUG(2, ("DsCrackNameUPN domain ref search failed: %s", ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ switch (domain_res->count) {
+ case 1:
+ break;
+ case 0:
+ return dns_domain_from_principal(mem_ctx, smb_krb5_context,
+ name, info1);
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+
+ ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &unparsed_name_short);
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+
+ if (ret) {
+ free(unparsed_name_short);
+ return WERR_NOMEM;
+ }
+
+ /* This may need to be extended for more userPrincipalName variations */
+ result_filter = talloc_asprintf(mem_ctx, "(&(objectClass=user)(samAccountName=%s))",
+ ldb_binary_encode_string(mem_ctx, unparsed_name_short));
+
+ domain_filter = talloc_asprintf(mem_ctx, "(distinguishedName=%s)", ldb_dn_get_linearized(domain_res->msgs[0]->dn));
+
+ if (!result_filter || !domain_filter) {
+ free(unparsed_name_short);
+ return WERR_NOMEM;
+ }
+ status = DsCrackNameOneFilter(sam_ctx, mem_ctx,
+ smb_krb5_context,
+ format_flags, format_offered, format_desired,
+ NULL, unparsed_name_short, domain_filter, result_filter,
+ info1);
+ free(unparsed_name_short);
+
+ return status;
+}
+
+/* Crack a single 'name', from format_offered into format_desired, returning the result in info1 */
+
+WERROR DsCrackNameOneName(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
+ const char *name, struct drsuapi_DsNameInfo1 *info1)
+{
+ krb5_error_code ret;
+ const char *domain_filter = NULL;
+ const char *result_filter = NULL;
+ struct ldb_dn *name_dn = NULL;
+
+ struct smb_krb5_context *smb_krb5_context;
+ ret = smb_krb5_init_context(mem_ctx,
+ ldb_get_event_context(sam_ctx),
+ (struct loadparm_context *)ldb_get_opaque(sam_ctx, "loadparm"),
+ &smb_krb5_context);
+
+ if (ret) {
+ return WERR_NOMEM;
+ }
+
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ info1->dns_domain_name = NULL;
+ info1->result_name = NULL;
+
+ if (!name) {
+ return WERR_INVALID_PARAM;
+ }
+
+ /* TODO: - fill the correct names in all cases!
+ * - handle format_flags
+ */
+
+ /* here we need to set the domain_filter and/or the result_filter */
+ switch (format_offered) {
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
+ {
+ char *str, *s, *account;
+
+ if (strlen(name) == 0) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ str = talloc_strdup(mem_ctx, name);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ if (format_offered == DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX) {
+ /* Look backwards for the \n, and replace it with / */
+ s = strrchr(str, '\n');
+ if (!s) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+ s[0] = '/';
+ }
+
+ s = strchr(str, '/');
+ if (!s) {
+ /* there must be at least one / */
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ s[0] = '\0';
+ s++;
+
+ domain_filter = talloc_asprintf(mem_ctx, "(&(objectClass=crossRef)(ncName=%s))",
+ ldb_dn_get_linearized(samdb_dns_domain_to_dn(sam_ctx, mem_ctx, str)));
+ W_ERROR_HAVE_NO_MEMORY(domain_filter);
+
+ /* There may not be anything after the domain component (search for the domain itself) */
+ if (s[0]) {
+
+ account = strrchr(s, '/');
+ if (!account) {
+ account = s;
+ } else {
+ account++;
+ }
+ account = ldb_binary_encode_string(mem_ctx, account);
+ W_ERROR_HAVE_NO_MEMORY(account);
+ result_filter = talloc_asprintf(mem_ctx, "(name=%s)",
+ account);
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+ }
+ break;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: {
+ char *p;
+ char *domain;
+ const char *account = NULL;
+
+ domain = talloc_strdup(mem_ctx, name);
+ W_ERROR_HAVE_NO_MEMORY(domain);
+
+ p = strchr(domain, '\\');
+ if (!p) {
+ /* invalid input format */
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+ p[0] = '\0';
+
+ if (p[1]) {
+ account = &p[1];
+ }
+
+ domain_filter = talloc_asprintf(mem_ctx,
+ "(&(&(nETBIOSName=%s)(objectclass=crossRef))(ncName=*))",
+ ldb_binary_encode_string(mem_ctx, domain));
+ W_ERROR_HAVE_NO_MEMORY(domain_filter);
+ if (account) {
+ result_filter = talloc_asprintf(mem_ctx, "(sAMAccountName=%s)",
+ ldb_binary_encode_string(mem_ctx, account));
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+ }
+
+ talloc_free(domain);
+ break;
+ }
+
+ /* A LDAP DN as a string */
+ case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: {
+ domain_filter = NULL;
+ name_dn = ldb_dn_new(mem_ctx, sam_ctx, name);
+ if (! ldb_dn_validate(name_dn)) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+ break;
+ }
+
+ /* A GUID as a string */
+ case DRSUAPI_DS_NAME_FORMAT_GUID: {
+ struct GUID guid;
+ char *ldap_guid;
+ NTSTATUS nt_status;
+ domain_filter = NULL;
+
+ nt_status = GUID_from_string(name, &guid);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+
+ ldap_guid = ldap_encode_ndr_GUID(mem_ctx, &guid);
+ if (!ldap_guid) {
+ return WERR_NOMEM;
+ }
+ result_filter = talloc_asprintf(mem_ctx, "(objectGUID=%s)",
+ ldap_guid);
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+ break;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_DISPLAY: {
+ domain_filter = NULL;
+
+ result_filter = talloc_asprintf(mem_ctx, "(|(displayName=%s)(samAccountName=%s))",
+ ldb_binary_encode_string(mem_ctx, name),
+ ldb_binary_encode_string(mem_ctx, name));
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+ break;
+ }
+
+ /* A S-1234-5678 style string */
+ case DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY: {
+ struct dom_sid *sid = dom_sid_parse_talloc(mem_ctx, name);
+ char *ldap_sid;
+
+ domain_filter = NULL;
+ if (!sid) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+ ldap_sid = ldap_encode_ndr_dom_sid(mem_ctx,
+ sid);
+ if (!ldap_sid) {
+ return WERR_NOMEM;
+ }
+ result_filter = talloc_asprintf(mem_ctx, "(objectSid=%s)",
+ ldap_sid);
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+ break;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: {
+ krb5_principal principal;
+ char *unparsed_name;
+ ret = krb5_parse_name(smb_krb5_context->krb5_context, name, &principal);
+ if (ret) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+
+ domain_filter = NULL;
+
+ ret = krb5_unparse_name(smb_krb5_context->krb5_context, principal, &unparsed_name);
+ if (ret) {
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_NOMEM;
+ }
+
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ result_filter = talloc_asprintf(mem_ctx, "(&(objectClass=user)(userPrincipalName=%s))",
+ ldb_binary_encode_string(mem_ctx, unparsed_name));
+
+ free(unparsed_name);
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+ break;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: {
+ krb5_principal principal;
+ char *unparsed_name_short;
+ char *service;
+ ret = krb5_parse_name(smb_krb5_context->krb5_context, name, &principal);
+ if (ret == 0 && principal->name.name_string.len < 2) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_OK;
+ }
+ ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
+ KRB5_PRINCIPAL_PARSE_NO_REALM, &principal);
+ if (ret) {
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+
+ return dns_domain_from_principal(mem_ctx, smb_krb5_context,
+ name, info1);
+ }
+
+ domain_filter = NULL;
+
+ ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &unparsed_name_short);
+ if (ret) {
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ return WERR_NOMEM;
+ }
+
+ service = principal->name.name_string.val[0];
+ if ((principal->name.name_string.len == 2) && (strcasecmp(service, "host") == 0)) {
+ /* the 'cn' attribute is just the leading part of the name */
+ char *computer_name;
+ computer_name = talloc_strndup(mem_ctx, principal->name.name_string.val[1],
+ strcspn(principal->name.name_string.val[1], "."));
+ if (computer_name == NULL) {
+ return WERR_NOMEM;
+ }
+
+ result_filter = talloc_asprintf(mem_ctx, "(|(&(servicePrincipalName=%s)(objectClass=user))(&(cn=%s)(objectClass=computer)))",
+ ldb_binary_encode_string(mem_ctx, unparsed_name_short),
+ ldb_binary_encode_string(mem_ctx, computer_name));
+ } else {
+ result_filter = talloc_asprintf(mem_ctx, "(&(servicePrincipalName=%s)(objectClass=user))",
+ ldb_binary_encode_string(mem_ctx, unparsed_name_short));
+ }
+ krb5_free_principal(smb_krb5_context->krb5_context, principal);
+ free(unparsed_name_short);
+ W_ERROR_HAVE_NO_MEMORY(result_filter);
+
+ break;
+ }
+ default: {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+
+ }
+
+ if (format_flags & DRSUAPI_DS_NAME_FLAG_SYNTACTICAL_ONLY) {
+ return DsCrackNameOneSyntactical(mem_ctx, format_offered, format_desired,
+ name_dn, name, info1);
+ }
+
+ return DsCrackNameOneFilter(sam_ctx, mem_ctx,
+ smb_krb5_context,
+ format_flags, format_offered, format_desired,
+ name_dn, name,
+ domain_filter, result_filter,
+ info1);
+}
+
+/* Subcase of CrackNames. It is possible to translate a LDAP-style DN
+ * (FQDN_1779) into a canoical name without actually searching the
+ * database */
+
+static WERROR DsCrackNameOneSyntactical(TALLOC_CTX *mem_ctx,
+ uint32_t format_offered, uint32_t format_desired,
+ struct ldb_dn *name_dn, const char *name,
+ struct drsuapi_DsNameInfo1 *info1)
+{
+ char *cracked;
+ if (format_offered != DRSUAPI_DS_NAME_FORMAT_FQDN_1779) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NO_SYNTACTICAL_MAPPING;
+ return WERR_OK;
+ }
+
+ switch (format_desired) {
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
+ cracked = ldb_dn_canonical_string(mem_ctx, name_dn);
+ break;
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
+ cracked = ldb_dn_canonical_ex_string(mem_ctx, name_dn);
+ break;
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NO_SYNTACTICAL_MAPPING;
+ return WERR_OK;
+ }
+ info1->status = DRSUAPI_DS_NAME_STATUS_OK;
+ info1->result_name = cracked;
+ if (!cracked) {
+ return WERR_NOMEM;
+ }
+
+ return WERR_OK;
+}
+
+/* Given a filter for the domain, and one for the result, perform the
+ * ldb search. The format offered and desired flags change the
+ * behaviours, including what attributes to return.
+ *
+ * The smb_krb5_context is required because we use the krb5 libs for principal parsing
+ */
+
+static WERROR DsCrackNameOneFilter(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
+ struct ldb_dn *name_dn, const char *name,
+ const char *domain_filter, const char *result_filter,
+ struct drsuapi_DsNameInfo1 *info1)
+{
+ int ldb_ret;
+ struct ldb_result *domain_res = NULL;
+ const char * const *domain_attrs;
+ const char * const *result_attrs;
+ struct ldb_message **result_res = NULL;
+ struct ldb_message *result = NULL;
+ struct ldb_dn *result_basedn = NULL;
+ int i;
+ char *p;
+ struct ldb_dn *partitions_basedn = samdb_partitions_dn(sam_ctx, mem_ctx);
+
+ const char * const _domain_attrs_1779[] = { "ncName", "dnsRoot", NULL};
+ const char * const _result_attrs_null[] = { NULL };
+
+ const char * const _domain_attrs_canonical[] = { "ncName", "dnsRoot", NULL};
+ const char * const _result_attrs_canonical[] = { "canonicalName", NULL };
+
+ const char * const _domain_attrs_nt4[] = { "ncName", "dnsRoot", "nETBIOSName", NULL};
+ const char * const _result_attrs_nt4[] = { "sAMAccountName", "objectSid", "objectClass", NULL};
+
+ const char * const _domain_attrs_guid[] = { "ncName", "dnsRoot", NULL};
+ const char * const _result_attrs_guid[] = { "objectGUID", NULL};
+
+ const char * const _domain_attrs_display[] = { "ncName", "dnsRoot", NULL};
+ const char * const _result_attrs_display[] = { "displayName", "samAccountName", NULL};
+
+ const char * const _domain_attrs_none[] = { "ncName", "dnsRoot" , NULL};
+ const char * const _result_attrs_none[] = { NULL};
+
+ /* here we need to set the attrs lists for domain and result lookups */
+ switch (format_desired) {
+ case DRSUAPI_DS_NAME_FORMAT_FQDN_1779:
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
+ domain_attrs = _domain_attrs_1779;
+ result_attrs = _result_attrs_null;
+ break;
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
+ domain_attrs = _domain_attrs_canonical;
+ result_attrs = _result_attrs_canonical;
+ break;
+ case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT:
+ domain_attrs = _domain_attrs_nt4;
+ result_attrs = _result_attrs_nt4;
+ break;
+ case DRSUAPI_DS_NAME_FORMAT_GUID:
+ domain_attrs = _domain_attrs_guid;
+ result_attrs = _result_attrs_guid;
+ break;
+ case DRSUAPI_DS_NAME_FORMAT_DISPLAY:
+ domain_attrs = _domain_attrs_display;
+ result_attrs = _result_attrs_display;
+ break;
+ default:
+ domain_attrs = _domain_attrs_none;
+ result_attrs = _result_attrs_none;
+ break;
+ }
+
+ if (domain_filter) {
+ /* if we have a domain_filter look it up and set the result_basedn and the dns_domain_name */
+ ldb_ret = ldb_search_exp_fmt(sam_ctx, mem_ctx, &domain_res,
+ partitions_basedn,
+ LDB_SCOPE_ONELEVEL,
+ domain_attrs,
+ "%s", domain_filter);
+
+ if (ldb_ret != LDB_SUCCESS) {
+ DEBUG(2, ("DsCrackNameOneFilter domain ref search failed: %s", ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ switch (domain_res->count) {
+ case 1:
+ break;
+ case 0:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+
+ info1->dns_domain_name = samdb_result_string(domain_res->msgs[0], "dnsRoot", NULL);
+ W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name);
+ info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY;
+ } else {
+ info1->dns_domain_name = NULL;
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ }
+
+ if (result_filter) {
+ int ret;
+ struct ldb_result *res;
+ if (domain_res) {
+ result_basedn = samdb_result_dn(sam_ctx, mem_ctx, domain_res->msgs[0], "ncName", NULL);
+
+ ret = ldb_search_exp_fmt(sam_ctx, mem_ctx, &res,
+ result_basedn, LDB_SCOPE_SUBTREE,
+ result_attrs, "%s", result_filter);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(result_res);
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+ ldb_ret = res->count;
+ result_res = res->msgs;
+ } else {
+ /* search with the 'phantom root' flag */
+ struct ldb_request *req;
+
+ res = talloc_zero(mem_ctx, struct ldb_result);
+ W_ERROR_HAVE_NO_MEMORY(res);
+
+ ret = ldb_build_search_req(&req, sam_ctx, mem_ctx,
+ ldb_get_root_basedn(sam_ctx),
+ LDB_SCOPE_SUBTREE,
+ result_filter,
+ result_attrs,
+ NULL,
+ res,
+ ldb_search_default_callback);
+ if (ret == LDB_SUCCESS) {
+ struct ldb_search_options_control *search_options;
+ search_options = talloc(req, struct ldb_search_options_control);
+ W_ERROR_HAVE_NO_MEMORY(search_options);
+ search_options->search_options = LDB_SEARCH_OPTION_PHANTOM_ROOT;
+
+ ret = ldb_request_add_control(req, LDB_CONTROL_SEARCH_OPTIONS_OID, false, search_options);
+ }
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ ldb_set_timeout(sam_ctx, req, 0); /* use default timeout */
+
+ ret = ldb_request(sam_ctx, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(req);
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(2, ("DsCrackNameOneFilter phantom root search failed: %s",
+ ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+ ldb_ret = res->count;
+ result_res = res->msgs;
+ }
+ } else if (format_offered == DRSUAPI_DS_NAME_FORMAT_FQDN_1779) {
+ ldb_ret = gendb_search_dn(sam_ctx, mem_ctx, name_dn, &result_res,
+ result_attrs);
+ } else if (domain_res) {
+ name_dn = samdb_result_dn(sam_ctx, mem_ctx, domain_res->msgs[0], "ncName", NULL);
+ ldb_ret = gendb_search_dn(sam_ctx, mem_ctx, name_dn, &result_res,
+ result_attrs);
+ } else {
+ /* Can't happen */
+ DEBUG(0, ("LOGIC ERROR: DsCrackNameOneFilter domain ref search not availible: This can't happen..."));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ switch (ldb_ret) {
+ case 1:
+ result = result_res[0];
+ break;
+ case 0:
+ switch (format_offered) {
+ case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL:
+ return DsCrackNameSPNAlias(sam_ctx, mem_ctx,
+ smb_krb5_context,
+ format_flags, format_offered, format_desired,
+ name, info1);
+
+ case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL:
+ return DsCrackNameUPN(sam_ctx, mem_ctx, smb_krb5_context,
+ format_flags, format_offered, format_desired,
+ name, info1);
+ }
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ case -1:
+ DEBUG(2, ("DsCrackNameOneFilter result search failed: %s", ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ default:
+ switch (format_offered) {
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
+ {
+ const char *canonical_name = NULL; /* Not required, but we get warnings... */
+ /* We may need to manually filter further */
+ for (i = 0; i < ldb_ret; i++) {
+ switch (format_offered) {
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
+ canonical_name = ldb_dn_canonical_string(mem_ctx, result_res[i]->dn);
+ break;
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
+ canonical_name = ldb_dn_canonical_ex_string(mem_ctx, result_res[i]->dn);
+ break;
+ }
+ if (strcasecmp_m(canonical_name, name) == 0) {
+ result = result_res[i];
+ break;
+ }
+ }
+ if (!result) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ }
+ }
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+ }
+
+ info1->dns_domain_name = ldb_dn_canonical_string(mem_ctx, result->dn);
+ W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name);
+ p = strchr(info1->dns_domain_name, '/');
+ if (p) {
+ p[0] = '\0';
+ }
+
+ /* here we can use result and domain_res[0] */
+ switch (format_desired) {
+ case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: {
+ info1->result_name = ldb_dn_alloc_linearized(mem_ctx, result->dn);
+ W_ERROR_HAVE_NO_MEMORY(info1->result_name);
+
+ info1->status = DRSUAPI_DS_NAME_STATUS_OK;
+ return WERR_OK;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL: {
+ info1->result_name = samdb_result_string(result, "canonicalName", NULL);
+ info1->status = DRSUAPI_DS_NAME_STATUS_OK;
+ return WERR_OK;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: {
+ /* Not in the virtual ldb attribute */
+ return DsCrackNameOneSyntactical(mem_ctx,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX,
+ result->dn, name, info1);
+ }
+ case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: {
+
+ const struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, result, "objectSid");
+ const char *_acc = "", *_dom = "";
+
+ if (samdb_find_attribute(sam_ctx, result, "objectClass", "domain")) {
+
+ ldb_ret = ldb_search_exp_fmt(sam_ctx, mem_ctx, &domain_res,
+ partitions_basedn,
+ LDB_SCOPE_ONELEVEL,
+ domain_attrs,
+ "(ncName=%s)", ldb_dn_get_linearized(result->dn));
+
+ if (ldb_ret != LDB_SUCCESS) {
+ DEBUG(2, ("DsCrackNameOneFilter domain ref search failed: %s", ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ switch (domain_res->count) {
+ case 1:
+ break;
+ case 0:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+ _dom = samdb_result_string(domain_res->msgs[0], "nETBIOSName", NULL);
+ W_ERROR_HAVE_NO_MEMORY(_dom);
+ } else {
+ _acc = samdb_result_string(result, "sAMAccountName", NULL);
+ if (!_acc) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING;
+ return WERR_OK;
+ }
+ if (dom_sid_in_domain(dom_sid_parse_talloc(mem_ctx, SID_BUILTIN), sid)) {
+ _dom = "BUILTIN";
+ } else {
+ const char *attrs[] = { NULL };
+ struct ldb_result *domain_res2;
+ struct dom_sid *dom_sid = dom_sid_dup(mem_ctx, sid);
+ if (!dom_sid) {
+ return WERR_OK;
+ }
+ dom_sid->num_auths--;
+ ldb_ret = ldb_search_exp_fmt(sam_ctx, mem_ctx, &domain_res,
+ NULL,
+ LDB_SCOPE_BASE,
+ attrs,
+ "(&(objectSid=%s)(objectClass=domain))",
+ ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
+
+ if (ldb_ret != LDB_SUCCESS) {
+ DEBUG(2, ("DsCrackNameOneFilter domain search failed: %s", ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ switch (domain_res->count) {
+ case 1:
+ break;
+ case 0:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+
+ ldb_ret = ldb_search_exp_fmt(sam_ctx, mem_ctx, &domain_res2,
+ partitions_basedn,
+ LDB_SCOPE_ONELEVEL,
+ domain_attrs,
+ "(ncName=%s)", ldb_dn_get_linearized(domain_res->msgs[0]->dn));
+
+ if (ldb_ret != LDB_SUCCESS) {
+ DEBUG(2, ("DsCrackNameOneFilter domain ref search failed: %s", ldb_errstring(sam_ctx)));
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+
+ switch (domain_res2->count) {
+ case 1:
+ break;
+ case 0:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ return WERR_OK;
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+ _dom = samdb_result_string(domain_res2->msgs[0], "nETBIOSName", NULL);
+ W_ERROR_HAVE_NO_MEMORY(_dom);
+ }
+ }
+
+ info1->result_name = talloc_asprintf(mem_ctx, "%s\\%s", _dom, _acc);
+ W_ERROR_HAVE_NO_MEMORY(info1->result_name);
+
+ info1->status = DRSUAPI_DS_NAME_STATUS_OK;
+ return WERR_OK;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_GUID: {
+ struct GUID guid;
+
+ guid = samdb_result_guid(result, "objectGUID");
+
+ info1->result_name = GUID_string2(mem_ctx, &guid);
+ W_ERROR_HAVE_NO_MEMORY(info1->result_name);
+
+ info1->status = DRSUAPI_DS_NAME_STATUS_OK;
+ return WERR_OK;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_DISPLAY: {
+ info1->result_name = samdb_result_string(result, "displayName", NULL);
+ if (!info1->result_name) {
+ info1->result_name = samdb_result_string(result, "sAMAccountName", NULL);
+ }
+ if (!info1->result_name) {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
+ } else {
+ info1->status = DRSUAPI_DS_NAME_STATUS_OK;
+ }
+ return WERR_OK;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: {
+ info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
+ return WERR_OK;
+ }
+ case DRSUAPI_DS_NAME_FORMAT_DNS_DOMAIN:
+ case DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY: {
+ info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
+ return WERR_OK;
+ }
+ default:
+ info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING;
+ return WERR_OK;
+ }
+}
+
+/* Given a user Principal Name (such as foo@bar.com),
+ * return the user and domain DNs. This is used in the KDC to then
+ * return the Keys and evaluate policy */
+
+NTSTATUS crack_user_principal_name(struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *user_principal_name,
+ struct ldb_dn **user_dn,
+ struct ldb_dn **domain_dn)
+{
+ WERROR werr;
+ struct drsuapi_DsNameInfo1 info1;
+ werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
+ DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ user_principal_name,
+ &info1);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+ switch (info1.status) {
+ case DRSUAPI_DS_NAME_STATUS_OK:
+ break;
+ case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+ case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+ case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+ return NT_STATUS_NO_SUCH_USER;
+ case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *user_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
+
+ if (domain_dn) {
+ werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
+ DRSUAPI_DS_NAME_FORMAT_CANONICAL,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ talloc_asprintf(mem_ctx, "%s/",
+ info1.dns_domain_name),
+ &info1);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+ switch (info1.status) {
+ case DRSUAPI_DS_NAME_STATUS_OK:
+ break;
+ case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+ case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+ case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+ return NT_STATUS_NO_SUCH_USER;
+ case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *domain_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
+ }
+
+ return NT_STATUS_OK;
+
+}
+
+/* Given a Service Principal Name (such as host/foo.bar.com@BAR.COM),
+ * return the user and domain DNs. This is used in the KDC to then
+ * return the Keys and evaluate policy */
+
+NTSTATUS crack_service_principal_name(struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *service_principal_name,
+ struct ldb_dn **user_dn,
+ struct ldb_dn **domain_dn)
+{
+ WERROR werr;
+ struct drsuapi_DsNameInfo1 info1;
+ werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
+ DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ service_principal_name,
+ &info1);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+ switch (info1.status) {
+ case DRSUAPI_DS_NAME_STATUS_OK:
+ break;
+ case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+ case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+ case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+ return NT_STATUS_NO_SUCH_USER;
+ case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *user_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
+
+ if (domain_dn) {
+ werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
+ DRSUAPI_DS_NAME_FORMAT_CANONICAL,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ talloc_asprintf(mem_ctx, "%s/",
+ info1.dns_domain_name),
+ &info1);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+ switch (info1.status) {
+ case DRSUAPI_DS_NAME_STATUS_OK:
+ break;
+ case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+ case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+ case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+ return NT_STATUS_NO_SUCH_USER;
+ case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *domain_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
+ }
+
+ return NT_STATUS_OK;
+
+}
+
+NTSTATUS crack_name_to_nt4_name(TALLOC_CTX *mem_ctx,
+ struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ uint32_t format_offered,
+ const char *name,
+ const char **nt4_domain, const char **nt4_account)
+{
+ WERROR werr;
+ struct drsuapi_DsNameInfo1 info1;
+ struct ldb_context *ldb;
+ char *p;
+
+ /* Handle anonymous bind */
+ if (!name || !*name) {
+ *nt4_domain = "";
+ *nt4_account = "";
+ return NT_STATUS_OK;
+ }
+
+ ldb = samdb_connect(mem_ctx, ev_ctx, lp_ctx, system_session(mem_ctx, lp_ctx));
+ if (ldb == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ werr = DsCrackNameOneName(ldb, mem_ctx, 0,
+ format_offered,
+ DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT,
+ name,
+ &info1);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+ switch (info1.status) {
+ case DRSUAPI_DS_NAME_STATUS_OK:
+ break;
+ case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+ case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+ case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+ return NT_STATUS_NO_SUCH_USER;
+ case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *nt4_domain = talloc_strdup(mem_ctx, info1.result_name);
+
+ p = strchr(*nt4_domain, '\\');
+ if (!p) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ p[0] = '\0';
+
+ if (p[1]) {
+ *nt4_account = talloc_strdup(mem_ctx, &p[1]);
+ }
+
+ if (!*nt4_account || !*nt4_domain) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS crack_auto_name_to_nt4_name(TALLOC_CTX *mem_ctx,
+ struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ const char *name,
+ const char **nt4_domain,
+ const char **nt4_account)
+{
+ uint32_t format_offered = DRSUAPI_DS_NAME_FORMAT_UKNOWN;
+
+ /* Handle anonymous bind */
+ if (!name || !*name) {
+ *nt4_domain = "";
+ *nt4_account = "";
+ return NT_STATUS_OK;
+ }
+
+ if (strchr_m(name, '=')) {
+ format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779;
+ } else if (strchr_m(name, '@')) {
+ format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL;
+ } else if (strchr_m(name, '\\')) {
+ format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT;
+ } else if (strchr_m(name, '/')) {
+ format_offered = DRSUAPI_DS_NAME_FORMAT_CANONICAL;
+ }
+
+ return crack_name_to_nt4_name(mem_ctx, ev_ctx, lp_ctx, format_offered, name, nt4_domain, nt4_account);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/anr.c b/source4/dsdb/samdb/ldb_modules/anr.c
new file mode 100644
index 0000000000..4e2c527fe9
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/anr.c
@@ -0,0 +1,307 @@
+/*
+ ldb database library
+
+ Copyright (C) Amdrew Bartlett <abartlet@samba.org> 2007
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb anr module
+ *
+ * Description: module to implement 'ambiguous name resolution'
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb_includes.h"
+#include "dsdb/samdb/samdb.h"
+
+/**
+ * Make a and 'and' or 'or' tree from the two supplied elements
+ */
+struct ldb_parse_tree *make_parse_list(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, enum ldb_parse_op op,
+ struct ldb_parse_tree *first_arm, struct ldb_parse_tree *second_arm)
+{
+ struct ldb_parse_tree *list;
+
+ list = talloc(mem_ctx, struct ldb_parse_tree);
+ if (list == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ list->operation = op;
+
+ list->u.list.num_elements = 2;
+ list->u.list.elements = talloc_array(list, struct ldb_parse_tree *, 2);
+ if (!list->u.list.elements) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ list->u.list.elements[0] = talloc_steal(list, first_arm);
+ list->u.list.elements[1] = talloc_steal(list, second_arm);
+ return list;
+}
+
+/**
+ * Make an equality or prefix match tree, from the attribute, operation and matching value supplied
+ */
+struct ldb_parse_tree *make_match_tree(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, enum ldb_parse_op op,
+ const char *attr, const DATA_BLOB *match)
+{
+ struct ldb_parse_tree *match_tree;
+
+ match_tree = talloc(mem_ctx, struct ldb_parse_tree);
+
+ /* Depending on what type of match was selected, fill in the right part of the union */
+
+ match_tree->operation = op;
+ switch (op) {
+ case LDB_OP_SUBSTRING:
+ match_tree->u.substring.attr = attr;
+
+ match_tree->u.substring.start_with_wildcard = 0;
+ match_tree->u.substring.end_with_wildcard = 1;
+ match_tree->u.substring.chunks = talloc_array(match_tree, struct ldb_val *, 2);
+
+ if (match_tree->u.substring.chunks == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ match_tree->u.substring.chunks[0] = match;
+ match_tree->u.substring.chunks[1] = NULL;
+ break;
+ case LDB_OP_EQUALITY:
+ match_tree->u.equality.attr = attr;
+ match_tree->u.equality.value = *match;
+ break;
+ }
+ return match_tree;
+}
+
+struct anr_context {
+ bool found_anr;
+ struct ldb_module *module;
+};
+
+/**
+ * Given the match for an 'ambigious name resolution' query, create a
+ * parse tree with an 'or' of all the anr attributes in the schema.
+ */
+
+typedef struct ldb_parse_tree *(*anr_parse_tree_callback_t)(TALLOC_CTX *mem_ctx,
+ const struct ldb_val *match,
+ void *context);
+
+
+/**
+ * Callback function to do the heavy lifting for the for the parse tree walker
+ */
+struct ldb_parse_tree *anr_replace_callback(TALLOC_CTX *mem_ctx,
+ const struct ldb_val *match,
+ void *context)
+{
+ struct ldb_parse_tree *tree = NULL;
+ struct anr_context *anr_context = talloc_get_type(context, struct anr_context);
+ struct ldb_module *module = anr_context->module;
+ struct ldb_parse_tree *match_tree;
+ uint8_t *p;
+ enum ldb_parse_op op;
+ struct dsdb_attribute *cur;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ ldb_asprintf_errstring(module->ldb, "no schema with which to construct anr filter");
+ return NULL;
+ }
+
+ anr_context->found_anr = true;
+
+ if (match->length > 1 && match->data[0] == '=') {
+ DATA_BLOB *match2 = talloc(tree, DATA_BLOB);
+ *match2 = data_blob_const(match->data+1, match->length - 1);
+ if (match2 == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ match = match2;
+ op = LDB_OP_EQUALITY;
+ } else {
+ op = LDB_OP_SUBSTRING;
+ }
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (!(cur->searchFlags & SEARCH_FLAG_ANR)) continue;
+ match_tree = make_match_tree(module, mem_ctx, op, cur->lDAPDisplayName, match);
+
+ if (tree) {
+ /* Inject an 'or' with the current tree */
+ tree = make_parse_list(module, mem_ctx, LDB_OP_OR, tree, match_tree);
+ if (tree == NULL) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ } else {
+ tree = match_tree;
+ }
+ }
+
+
+ /* If the search term has a space in it,
+ split it up at the first space. */
+
+ p = memchr(match->data, ' ', match->length);
+
+ if (p) {
+ struct ldb_parse_tree *first_split_filter, *second_split_filter, *split_filters, *match_tree_1, *match_tree_2;
+ DATA_BLOB *first_match = talloc(tree, DATA_BLOB);
+ DATA_BLOB *second_match = talloc(tree, DATA_BLOB);
+ if (!first_match || !second_match) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ *first_match = data_blob_const(match->data, p-match->data);
+ *second_match = data_blob_const(p+1, match->length - (p-match->data) - 1);
+
+ /* Add (|(&(givenname=first)(sn=second))(&(givenname=second)(sn=first))) */
+
+ match_tree_1 = make_match_tree(module, mem_ctx, op, "givenName", first_match);
+ match_tree_2 = make_match_tree(module, mem_ctx, op, "sn", second_match);
+
+ first_split_filter = make_parse_list(module, context, LDB_OP_AND, match_tree_1, match_tree_2);
+ if (first_split_filter == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+
+ match_tree_1 = make_match_tree(module, mem_ctx, op, "sn", first_match);
+ match_tree_2 = make_match_tree(module, mem_ctx, op, "givenName", second_match);
+
+ second_split_filter = make_parse_list(module, context, LDB_OP_AND, match_tree_1, match_tree_2);
+ if (second_split_filter == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+
+ split_filters = make_parse_list(module, mem_ctx, LDB_OP_OR,
+ first_split_filter, second_split_filter);
+ if (split_filters == NULL) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+
+ if (tree) {
+ /* Inject an 'or' with the current tree */
+ tree = make_parse_list(module, mem_ctx, LDB_OP_OR, tree, split_filters);
+ } else {
+ tree = split_filters;
+ }
+ }
+ return tree;
+}
+
+/*
+ replace any occurances of an attribute with a new, generated attribute tree
+*/
+struct ldb_parse_tree *anr_replace_subtrees(struct ldb_parse_tree *tree,
+ const char *attr,
+ anr_parse_tree_callback_t callback,
+ void *context)
+{
+ int i;
+ struct ldb_parse_tree *tmp;
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ case LDB_OP_OR:
+ for (i=0;i<tree->u.list.num_elements;i++) {
+ tmp = anr_replace_subtrees(tree->u.list.elements[i],
+ attr, callback, context);
+ if (tmp) tree->u.list.elements[i] = tmp;
+ }
+ break;
+ case LDB_OP_NOT:
+ tmp = anr_replace_subtrees(tree->u.isnot.child, attr, callback, context);
+ if (tmp) tree->u.isnot.child = tmp;
+ break;
+ case LDB_OP_EQUALITY:
+ if (ldb_attr_cmp(tree->u.equality.attr, attr) == 0) {
+ tmp = callback(tree, &tree->u.equality.value,
+ context);
+ if (tmp) tree = tmp;
+ }
+ break;
+ case LDB_OP_SUBSTRING:
+ if (ldb_attr_cmp(tree->u.substring.attr, attr) == 0) {
+ if (tree->u.substring.start_with_wildcard == 0 &&
+ tree->u.substring.end_with_wildcard == 1 &&
+ tree->u.substring.chunks[0] != NULL &&
+ tree->u.substring.chunks[1] == NULL) {
+ tmp = callback(tree, tree->u.substring.chunks[0], context);
+ if (tmp) tree = tmp;
+ }
+ }
+ break;
+ }
+ return tree;
+}
+
+/* search */
+static int anr_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_parse_tree *anr_tree;
+ struct anr_context *context = talloc(req, struct anr_context);
+ if (!context) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ context->module = module;
+ context->found_anr = false;
+
+#if 0
+ printf("oldanr : %s\n", ldb_filter_from_tree (0, req->op.search.tree));
+#endif
+
+ /* Yes, this is a problem with req->op.search.tree being const... */
+ anr_tree = anr_replace_subtrees(req->op.search.tree, "anr", anr_replace_callback, context);
+ if (!anr_tree) {
+ talloc_free(context);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (context->found_anr) {
+ /* The above function modifies the tree if it finds "anr", so no
+ * point just setting this on the down_req */
+#if 0
+ printf("newtree: %s\n", ldb_filter_from_tree (0, anr_tree));
+#endif
+ req->op.search.tree = talloc_steal(req, anr_tree);
+ } else {
+ if (anr_tree != req->op.search.tree) {
+ talloc_free(anr_tree);
+ }
+ talloc_free(context);
+ }
+ return ldb_next_request(module, req);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_anr_module_ops = {
+ .name = "anr",
+ .search = anr_search
+};
diff --git a/source4/dsdb/samdb/ldb_modules/config.mk b/source4/dsdb/samdb/ldb_modules/config.mk
new file mode 100644
index 0000000000..00e4f1af92
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/config.mk
@@ -0,0 +1,313 @@
+################################################
+# Start MODULE ldb_objectguid
+[MODULE::ldb_objectguid]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR NDR_MISC
+INIT_FUNCTION = LDB_MODULE(objectguid)
+# End MODULE ldb_objectguid
+################################################
+
+ldb_objectguid_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/objectguid.o
+
+################################################
+# Start MODULE ldb_repl_meta_data
+[MODULE::ldb_repl_meta_data]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS \
+ LIBNDR NDR_MISC NDR_DRSUAPI \
+ NDR_DRSBLOBS LIBNDR
+INIT_FUNCTION = LDB_MODULE(repl_meta_data)
+# End MODULE ldb_repl_meta_data
+################################################
+
+ldb_repl_meta_data_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/repl_meta_data.o
+
+################################################
+# Start MODULE ldb_dsdb_cache
+[MODULE::ldb_dsdb_cache]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(dsdb_cache)
+# End MODULE ldb_dsdb_cache
+################################################
+
+ldb_dsdb_cache_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/dsdb_cache.o
+
+################################################
+# Start MODULE ldb_schema_fsmo
+[MODULE::ldb_schema_fsmo]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(schema_fsmo)
+# End MODULE ldb_schema_fsmo
+################################################
+
+ldb_schema_fsmo_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/schema_fsmo.o
+
+################################################
+# Start MODULE ldb_naming_fsmo
+[MODULE::ldb_naming_fsmo]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(naming_fsmo)
+# End MODULE ldb_naming_fsmo
+################################################
+
+ldb_naming_fsmo_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/naming_fsmo.o
+
+################################################
+# Start MODULE ldb_pdc_fsmo
+[MODULE::ldb_pdc_fsmo]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(pdc_fsmo)
+# End MODULE ldb_pdc_fsmo
+################################################
+
+ldb_pdc_fsmo_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/pdc_fsmo.o
+
+################################################
+# Start MODULE ldb_samldb
+[MODULE::ldb_samldb]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LDAP_ENCODE NDR_MISC SAMDB
+INIT_FUNCTION = LDB_MODULE(samldb)
+#
+# End MODULE ldb_samldb
+################################################
+
+ldb_samldb_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/samldb.o
+
+################################################
+# Start MODULE ldb_samba3sam
+[MODULE::ldb_samba3sam]
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(samba3sam)
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SMBPASSWD \
+ NSS_WRAPPER LIBSECURITY NDR_SECURITY
+# End MODULE ldb_samldb
+################################################
+
+ldb_samba3sam_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/samba3sam.o
+
+################################################
+# Start MODULE ldb_simple_ldap_map
+[MODULE::ldb_simple_ldap_map]
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(entryuuid),LDB_MODULE(nsuniqueid)
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR NDR_MISC
+ENABLE = YES
+ALIASES = entryuuid nsuniqueid
+# End MODULE ldb_entryuuid
+################################################
+
+ldb_simple_ldap_map_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/simple_ldap_map.o
+
+# ################################################
+# # Start MODULE ldb_proxy
+# [MODULE::ldb_proxy]
+# SUBSYSTEM = LIBLDB
+# INIT_FUNCTION = LDB_MODULE(proxy)
+# OBJ_FILES = \
+# proxy.o
+#
+# # End MODULE ldb_proxy
+# ################################################
+
+
+################################################
+# Start MODULE ldb_rootdse
+[MODULE::ldb_rootdse]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB
+INIT_FUNCTION = LDB_MODULE(rootdse)
+# End MODULE ldb_rootdse
+################################################
+
+ldb_rootdse_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/rootdse.o
+
+################################################
+# Start MODULE ldb_password_hash
+[MODULE::ldb_password_hash]
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(password_hash)
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB LDAP_ENCODE \
+ LIBCLI_AUTH NDR_DRSBLOBS KERBEROS \
+ HEIMDAL_HDB_KEYS HEIMDAL_KRB5
+# End MODULE ldb_password_hash
+################################################
+
+ldb_password_hash_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/password_hash.o
+
+################################################
+# Start MODULE ldb_local_password
+[MODULE::ldb_local_password]
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR SAMDB
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(local_password)
+# End MODULE ldb_local_password
+################################################
+
+ldb_local_password_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/local_password.o
+
+################################################
+# Start MODULE ldb_kludge_acl
+[MODULE::ldb_kludge_acl]
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSECURITY SAMDB
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(kludge_acl)
+
+# End MODULE ldb_kludge_acl
+################################################
+
+ldb_kludge_acl_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/kludge_acl.o
+
+################################################
+# Start MODULE ldb_extended_dn
+[MODULE::ldb_extended_dn]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR LIBSECURITY SAMDB
+INIT_FUNCTION = LDB_MODULE(extended_dn)
+# End MODULE ldb_extended_dn
+################################################
+
+ldb_extended_dn_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/extended_dn.o
+
+################################################
+# Start MODULE ldb_show_deleted
+[MODULE::ldb_show_deleted]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(show_deleted)
+# End MODULE ldb_show_deleted
+################################################
+
+ldb_show_deleted_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/show_deleted.o
+
+################################################
+# Start MODULE ldb_partition
+[MODULE::ldb_partition]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB
+INIT_FUNCTION = LDB_MODULE(partition)
+# End MODULE ldb_partition
+################################################
+
+ldb_partition_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/partition.o
+
+################################################
+# Start MODULE ldb_update_kt
+[MODULE::ldb_update_keytab]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS CREDENTIALS
+#Also depends on credentials, but that would loop
+INIT_FUNCTION = LDB_MODULE(update_keytab)
+# End MODULE ldb_update_kt
+################################################
+
+ldb_update_keytab_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/update_keytab.o
+
+################################################
+# Start MODULE ldb_objectclass
+[MODULE::ldb_objectclass]
+INIT_FUNCTION = LDB_MODULE(objectclass)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSECURITY NDR_SECURITY SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_objectclass
+################################################
+
+ldb_objectclass_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/objectclass.o
+
+################################################
+# Start MODULE ldb_subtree_rename
+[MODULE::ldb_subtree_rename]
+INIT_FUNCTION = LDB_MODULE(subtree_rename)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_subtree_rename
+################################################
+
+ldb_subtree_rename_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/subtree_rename.o
+
+################################################
+# Start MODULE ldb_subtree_rename
+[MODULE::ldb_subtree_delete]
+INIT_FUNCTION = LDB_MODULE(subtree_delete)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_subtree_rename
+################################################
+
+ldb_subtree_delete_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/subtree_delete.o
+
+################################################
+# Start MODULE ldb_linked_attributes
+[MODULE::ldb_linked_attributes]
+INIT_FUNCTION = LDB_MODULE(linked_attributes)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_linked_attributes
+################################################
+
+ldb_linked_attributes_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/linked_attributes.o
+
+################################################
+# Start MODULE ldb_ranged_results
+[MODULE::ldb_ranged_results]
+INIT_FUNCTION = LDB_MODULE(ranged_results)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_ranged_results
+################################################
+
+ldb_ranged_results_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/ranged_results.o
+
+################################################
+# Start MODULE ldb_anr
+[MODULE::ldb_anr]
+INIT_FUNCTION = LDB_MODULE(anr)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSAMBA-UTIL SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_anr
+################################################
+
+ldb_anr_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/anr.o
+
+################################################
+# Start MODULE ldb_normalise
+[MODULE::ldb_normalise]
+INIT_FUNCTION = LDB_MODULE(normalise)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSAMBA-UTIL SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_normalise
+################################################
+
+ldb_normalise_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/normalise.o
+
+################################################
+# Start MODULE ldb_instancetype
+[MODULE::ldb_instancetype]
+INIT_FUNCTION = LDB_MODULE(instancetype)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSAMBA-UTIL SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_instancetype
+################################################
+
+ldb_instancetype_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/instancetype.o
+
diff --git a/source4/dsdb/samdb/ldb_modules/dsdb_cache.c b/source4/dsdb/samdb/ldb_modules/dsdb_cache.c
new file mode 100644
index 0000000000..e60605dce1
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/dsdb_cache.c
@@ -0,0 +1,42 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The Module that loads some DSDB related things
+ into memory. E.g. it loads the dsdb_schema struture
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+
+static int dsdb_cache_init(struct ldb_module *module)
+{
+ /* TODO: load the schema */
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_dsdb_cache_module_ops = {
+ .name = "dsdb_cache",
+ .init_context = dsdb_cache_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn.c b/source4/dsdb/samdb/ldb_modules/extended_dn.c
new file mode 100644
index 0000000000..84bf5e4843
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/extended_dn.c
@@ -0,0 +1,369 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb extended dn control module
+ *
+ * Description: this module builds a special dn
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+
+#include <time.h>
+
+static bool is_attr_in_list(const char * const * attrs, const char *attr)
+{
+ int i;
+
+ for (i = 0; attrs[i]; i++) {
+ if (strcasecmp(attrs[i], attr) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+static char **copy_attrs(void *mem_ctx, const char * const * attrs)
+{
+ char **new;
+ int i, num;
+
+ for (num = 0; attrs[num]; num++);
+
+ new = talloc_array(mem_ctx, char *, num + 1);
+ if (!new) return NULL;
+
+ for(i = 0; i < num; i++) {
+ new[i] = talloc_strdup(new, attrs[i]);
+ if (!new[i]) {
+ talloc_free(new);
+ return NULL;
+ }
+ }
+ new[i] = NULL;
+
+ return new;
+}
+
+static bool add_attrs(void *mem_ctx, char ***attrs, const char *attr)
+{
+ char **new;
+ int num;
+
+ for (num = 0; (*attrs)[num]; num++);
+
+ new = talloc_realloc(mem_ctx, *attrs, char *, num + 2);
+ if (!new) return false;
+
+ *attrs = new;
+
+ new[num] = talloc_strdup(new, attr);
+ if (!new[num]) return false;
+
+ new[num + 1] = NULL;
+
+ return true;
+}
+
+static bool inject_extended_dn(struct ldb_message *msg,
+ struct ldb_context *ldb,
+ int type,
+ bool remove_guid,
+ bool remove_sid)
+{
+ const struct ldb_val *val;
+ struct GUID guid;
+ struct dom_sid *sid;
+ const DATA_BLOB *guid_blob;
+ const DATA_BLOB *sid_blob;
+ char *object_guid;
+ char *object_sid;
+ char *new_dn;
+
+ guid_blob = ldb_msg_find_ldb_val(msg, "objectGUID");
+ sid_blob = ldb_msg_find_ldb_val(msg, "objectSID");
+
+ if (!guid_blob)
+ return false;
+
+ switch (type) {
+ case 0:
+ /* return things in hexadecimal format */
+ if (sid_blob) {
+ const char *lower_guid_hex = strlower_talloc(msg, data_blob_hex_string(msg, guid_blob));
+ const char *lower_sid_hex = strlower_talloc(msg, data_blob_hex_string(msg, sid_blob));
+ if (!lower_guid_hex || !lower_sid_hex) {
+ return false;
+ }
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;<SID=%s>;%s",
+ lower_guid_hex,
+ lower_sid_hex,
+ ldb_dn_get_linearized(msg->dn));
+ } else {
+ const char *lower_guid_hex = strlower_talloc(msg, data_blob_hex_string(msg, guid_blob));
+ if (!lower_guid_hex) {
+ return false;
+ }
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;%s",
+ lower_guid_hex,
+ ldb_dn_get_linearized(msg->dn));
+ }
+
+ break;
+ case 1:
+ /* retrieve object_guid */
+ guid = samdb_result_guid(msg, "objectGUID");
+ object_guid = GUID_string(msg, &guid);
+
+ /* retrieve object_sid */
+ object_sid = NULL;
+ sid = samdb_result_dom_sid(msg, msg, "objectSID");
+ if (sid) {
+ object_sid = dom_sid_string(msg, sid);
+ if (!object_sid)
+ return false;
+
+ }
+
+ /* Normal, sane format */
+ if (object_sid) {
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;<SID=%s>;%s",
+ object_guid, object_sid,
+ ldb_dn_get_linearized(msg->dn));
+ } else {
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;%s",
+ object_guid,
+ ldb_dn_get_linearized(msg->dn));
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (!new_dn) {
+ return false;
+ }
+
+ if (remove_guid) {
+ ldb_msg_remove_attr(msg, "objectGUID");
+ }
+
+ if (sid_blob && remove_sid) {
+ ldb_msg_remove_attr(msg, "objectSID");
+ }
+
+ msg->dn = ldb_dn_new(msg, ldb, new_dn);
+ if (! ldb_dn_validate(msg->dn))
+ return false;
+
+ val = ldb_msg_find_ldb_val(msg, "distinguishedName");
+ if (val) {
+ ldb_msg_remove_attr(msg, "distinguishedName");
+ if (ldb_msg_add_steal_string(msg, "distinguishedName", new_dn))
+ return false;
+ }
+
+ return true;
+}
+
+/* search */
+struct extended_context {
+
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ const char * const *attrs;
+ bool remove_guid;
+ bool remove_sid;
+ int extended_type;
+};
+
+static int extended_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct extended_context *ac;
+
+ ac = talloc_get_type(context, struct extended_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ /* for each record returned post-process to add any derived
+ attributes that have been asked for */
+ if (!inject_extended_dn(ares->message, ldb, ac->extended_type, ac->remove_guid, ac->remove_sid)) {
+ goto error;
+ }
+ }
+
+ return ac->up_callback(ldb, ac->up_context, ares);
+
+error:
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+static int extended_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control;
+ struct ldb_extended_dn_control *extended_ctrl = NULL;
+ struct ldb_control **saved_controls;
+ struct extended_context *ac;
+ struct ldb_request *down_req;
+ char **new_attrs;
+ int ret;
+
+ /* check if there's an extended dn control */
+ control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID);
+ if (control == NULL) {
+ /* not found go on */
+ return ldb_next_request(module, req);
+ }
+
+ if (control->data) {
+ extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control);
+ if (!extended_ctrl) {
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ }
+
+ ac = talloc(req, struct extended_context);
+ if (ac == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->module = module;
+ ac->up_context = req->context;
+ ac->up_callback = req->callback;
+ ac->attrs = req->op.search.attrs;
+ ac->remove_guid = false;
+ ac->remove_sid = false;
+ if (extended_ctrl) {
+ ac->extended_type = extended_ctrl->type;
+ } else {
+ ac->extended_type = 0;
+ }
+
+ down_req = talloc_zero(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->operation = req->operation;
+ down_req->op.search.base = req->op.search.base;
+ down_req->op.search.scope = req->op.search.scope;
+ down_req->op.search.tree = req->op.search.tree;
+
+ /* check if attrs only is specified, in that case check wether we need to modify them */
+ if (req->op.search.attrs) {
+ if (! is_attr_in_list(req->op.search.attrs, "objectGUID")) {
+ ac->remove_guid = true;
+ }
+ if (! is_attr_in_list(req->op.search.attrs, "objectSID")) {
+ ac->remove_sid = true;
+ }
+ if (ac->remove_guid || ac->remove_sid) {
+ new_attrs = copy_attrs(down_req, req->op.search.attrs);
+ if (new_attrs == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (ac->remove_guid) {
+ if (!add_attrs(down_req, &new_attrs, "objectGUID"))
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (ac->remove_sid) {
+ if (!add_attrs(down_req, &new_attrs, "objectSID"))
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->op.search.attrs = (const char * const *)new_attrs;
+ }
+ }
+
+ down_req->controls = req->controls;
+
+ /* save it locally and remove it from the list */
+ /* we do not need to replace them later as we
+ * are keeping the original req intact */
+ if (!save_controls(control, down_req, &saved_controls)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->context = ac;
+ down_req->callback = extended_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int extended_init(struct ldb_module *module)
+{
+ struct ldb_request *req;
+ int ret;
+
+ req = talloc(module, struct ldb_request);
+ if (req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ req->operation = LDB_REQ_REGISTER_CONTROL;
+ req->op.reg_control.oid = LDB_CONTROL_EXTENDED_DN_OID;
+ req->controls = NULL;
+
+ ret = ldb_request(module->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "extended_dn: Unable to register control with rootdse!\n");
+ talloc_free(req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(req);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_module_ops = {
+ .name = "extended_dn",
+ .search = extended_search,
+ .init_context = extended_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/instancetype.c b/source4/dsdb/samdb/ldb_modules/instancetype.c
new file mode 100644
index 0000000000..fd5aa5e18a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/instancetype.c
@@ -0,0 +1,124 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb instancetype module
+ *
+ * Description: add an instanceType onto every new record
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.h"
+
+/* add_record: add instancetype attribute */
+static int instancetype_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ uint32_t instance_type;
+ int ret;
+ const struct ldb_control *partition_ctrl;
+ const struct dsdb_control_current_partition *partition;
+
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "instancetype_add_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (!partition_ctrl) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "instancetype_add: no current partition control found");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ partition = talloc_get_type(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+ SMB_ASSERT(partition && partition->version == DSDB_CONTROL_CURRENT_PARTITION_VERSION);
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * TODO: calculate correct instance type
+ */
+ instance_type = INSTANCE_TYPE_WRITE;
+ if (ldb_dn_compare(partition->dn, msg->dn) == 0) {
+ instance_type |= INSTANCE_TYPE_IS_NC_HEAD;
+ if (ldb_dn_compare(msg->dn, samdb_base_dn(module->ldb)) != 0) {
+ instance_type |= INSTANCE_TYPE_NC_ABOVE;
+ }
+ }
+
+ ret = ldb_msg_add_fmt(msg, "instanceType", "%u", instance_type);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_instancetype_module_ops = {
+ .name = "instancetype",
+ .add = instancetype_add,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/kludge_acl.c b/source4/dsdb/samdb/ldb_modules/kludge_acl.c
new file mode 100644
index 0000000000..bc998a835a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/kludge_acl.c
@@ -0,0 +1,461 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett 2005
+ Copyright (C) Simo Sorce 2006
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb kludge ACL module
+ *
+ * Description: Simple module to enforce a simple form of access
+ * control, sufficient for securing a default Samba4
+ * installation.
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "auth/auth.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+
+/* Kludge ACL rules:
+ *
+ * - System can read passwords
+ * - Administrators can write anything
+ * - Users can read anything that is not a password
+ *
+ */
+
+struct kludge_private_data {
+ const char **password_attrs;
+};
+
+static enum security_user_level what_is_user(struct ldb_module *module)
+{
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(module->ldb, "sessionInfo");
+ return security_session_user_level(session_info);
+}
+
+static const char *user_name(TALLOC_CTX *mem_ctx, struct ldb_module *module)
+{
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(module->ldb, "sessionInfo");
+ if (!session_info) {
+ return "UNKNOWN (NULL)";
+ }
+
+ return talloc_asprintf(mem_ctx, "%s\\%s",
+ session_info->server_info->domain_name,
+ session_info->server_info->account_name);
+}
+
+/* search */
+struct kludge_acl_context {
+
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ enum security_user_level user_type;
+ bool allowedAttributes;
+ bool allowedAttributesEffective;
+ bool allowedChildClasses;
+ bool allowedChildClassesEffective;
+ const char **attrs;
+};
+
+/* read all objectClasses */
+
+static int kludge_acl_allowedAttributes(struct ldb_context *ldb, struct ldb_message *msg,
+ const char *attrName)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_message_element *allowedAttributes;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ TALLOC_CTX *mem_ctx;
+ char **objectclass_list, **attr_list;
+ int i, ret;
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* Must remove any existing attribute, or else confusion reins */
+ ldb_msg_remove_attr(msg, attrName);
+ ret = ldb_msg_add_empty(msg, attrName, 0, &allowedAttributes);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ mem_ctx = talloc_new(msg);
+ if (!mem_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* To ensure that oc_el is valid, we must look for it after
+ we alter the element array in ldb_msg_add_empty() */
+ oc_el = ldb_msg_find_element(msg, "objectClass");
+
+ objectclass_list = talloc_array(mem_ctx, char *, oc_el->num_values + 1);
+ if (!objectclass_list) {
+ ldb_oom(ldb);
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; oc_el && i < oc_el->num_values; i++) {
+ objectclass_list[i] = (char *)oc_el->values[i].data;
+ }
+ objectclass_list[i] = NULL;
+
+ attr_list = dsdb_full_attribute_list(mem_ctx, schema, (const char **)objectclass_list, DSDB_SCHEMA_ALL);
+ if (!attr_list) {
+ ldb_asprintf_errstring(ldb, "kludge_acl: Failed to get list of attributes create %s attribute", attrName);
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; attr_list && attr_list[i]; i++) {
+ ldb_msg_add_string(msg, attrName, attr_list[i]);
+ }
+ talloc_free(mem_ctx);
+ return 0;
+
+}
+/* read all objectClasses */
+
+static int kludge_acl_childClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const char *attrName)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_message_element *allowedClasses;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ const struct dsdb_class *class;
+ int i, j, ret;
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* Must remove any existing attribute, or else confusion reins */
+ ldb_msg_remove_attr(msg, attrName);
+ ret = ldb_msg_add_empty(msg, attrName, 0, &allowedClasses);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* To ensure that oc_el is valid, we must look for it after
+ we alter the element array in ldb_msg_add_empty() */
+ oc_el = ldb_msg_find_element(msg, "objectClass");
+
+ for (i=0; oc_el && i < oc_el->num_values; i++) {
+ class = dsdb_class_by_lDAPDisplayName(schema, (const char *)oc_el->values[i].data);
+ if (!class) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+
+ for (j=0; class->possibleInferiors && class->possibleInferiors[j]; j++) {
+ ldb_msg_add_string(msg, attrName, class->possibleInferiors[j]);
+ }
+ }
+
+ if (allowedClasses->num_values > 1) {
+ qsort(allowedClasses->values,
+ allowedClasses->num_values,
+ sizeof(*allowedClasses->values),
+ (comparison_fn_t)data_blob_cmp);
+
+ for (i=1 ; i < allowedClasses->num_values; i++) {
+ struct ldb_val *val1 = &allowedClasses->values[i-1];
+ struct ldb_val *val2 = &allowedClasses->values[i];
+ if (data_blob_cmp(val1, val2) == 0) {
+ memmove(val1, val2, (allowedClasses->num_values - i) * sizeof( struct ldb_val));
+ allowedClasses->num_values--;
+ i--;
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+/* find all attributes allowed by all these objectClasses */
+
+static int kludge_acl_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct kludge_acl_context *ac;
+ struct kludge_private_data *data;
+ int i, ret;
+
+ ac = talloc_get_type(context, struct kludge_acl_context);
+ data = talloc_get_type(ac->module->private_data, struct kludge_private_data);
+
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return ac->up_callback(ldb, ac->up_context, ares);
+ }
+
+ if (ac->allowedAttributes) {
+ ret = kludge_acl_allowedAttributes(ldb, ares->message, "allowedAttributes");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+
+ }
+ }
+ if (ac->allowedChildClasses) {
+ ret = kludge_acl_childClasses(ldb, ares->message, "allowedChildClasses");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (data && data->password_attrs) /* if we are not initialized just get through */
+ {
+ switch (ac->user_type) {
+ case SECURITY_SYSTEM:
+ if (ac->allowedAttributesEffective) {
+ ret = kludge_acl_allowedAttributes(ldb, ares->message, "allowedAttributesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (ac->allowedChildClassesEffective) {
+ ret = kludge_acl_childClasses(ldb, ares->message, "allowedChildClassesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ break;
+ case SECURITY_ADMINISTRATOR:
+ if (ac->allowedAttributesEffective) {
+ ret = kludge_acl_allowedAttributes(ldb, ares->message, "allowedAttributesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (ac->allowedChildClassesEffective) {
+ ret = kludge_acl_childClasses(ldb, ares->message, "allowedChildClassesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ /* fall though */
+ default:
+ /* remove password attributes */
+ for (i = 0; data->password_attrs[i]; i++) {
+ ldb_msg_remove_attr(ares->message, data->password_attrs[i]);
+ }
+ }
+ }
+
+ if ((ac->allowedAttributes || ac->allowedAttributesEffective
+ || ac->allowedChildClasses || ac->allowedChildClassesEffective) &&
+ (!ldb_attr_in_list(ac->attrs, "objectClass") &&
+ !ldb_attr_in_list(ac->attrs, "*"))) {
+ ldb_msg_remove_attr(ares->message, "objectClass");
+ }
+
+ return ac->up_callback(ldb, ac->up_context, ares);
+}
+
+static int kludge_acl_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct kludge_acl_context *ac;
+ struct ldb_request *down_req;
+ struct kludge_private_data *data;
+ int ret, i;
+
+ req->handle = NULL;
+
+ ac = talloc(req, struct kludge_acl_context);
+ if (ac == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data = talloc_get_type(module->private_data, struct kludge_private_data);
+
+ ac->module = module;
+ ac->up_context = req->context;
+ ac->up_callback = req->callback;
+ ac->user_type = what_is_user(module);
+ ac->attrs = req->op.search.attrs;
+
+ down_req = talloc_zero(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->operation = req->operation;
+ down_req->op.search.base = req->op.search.base;
+ down_req->op.search.scope = req->op.search.scope;
+ down_req->op.search.tree = req->op.search.tree;
+ down_req->op.search.attrs = req->op.search.attrs;
+
+ ac->allowedAttributes = ldb_attr_in_list(req->op.search.attrs, "allowedAttributes");
+
+ ac->allowedAttributesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedAttributesEffective");
+
+ ac->allowedChildClasses = ldb_attr_in_list(req->op.search.attrs, "allowedChildClasses");
+
+ ac->allowedChildClassesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedChildClassesEffective");
+
+ if (ac->allowedAttributes || ac->allowedAttributesEffective || ac->allowedChildClasses || ac->allowedChildClassesEffective) {
+ down_req->op.search.attrs
+ = ldb_attr_list_copy_add(down_req, down_req->op.search.attrs, "objectClass");
+ }
+
+ /* FIXME: I hink we should copy the tree and keep the original
+ * unmodified. SSS */
+ /* replace any attributes in the parse tree that are private,
+ so we don't allow a search for 'userPassword=penguin',
+ just as we would not allow that attribute to be returned */
+ switch (ac->user_type) {
+ case SECURITY_SYSTEM:
+ break;
+ default:
+ /* remove password attributes */
+ for (i = 0; data && data->password_attrs && data->password_attrs[i]; i++) {
+ ldb_parse_tree_attr_replace(down_req->op.search.tree,
+ data->password_attrs[i],
+ "kludgeACLredactedattribute");
+ }
+ }
+
+ down_req->controls = req->controls;
+
+ down_req->context = ac;
+ down_req->callback = kludge_acl_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+/* ANY change type */
+static int kludge_acl_change(struct ldb_module *module, struct ldb_request *req)
+{
+ enum security_user_level user_type = what_is_user(module);
+ switch (user_type) {
+ case SECURITY_SYSTEM:
+ case SECURITY_ADMINISTRATOR:
+ return ldb_next_request(module, req);
+ default:
+ ldb_asprintf_errstring(module->ldb,
+ "kludge_acl_change: "
+ "attempted database modify not permitted. "
+ "User %s is not SYSTEM or an administrator",
+ user_name(req, module));
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+}
+
+static int kludge_acl_init(struct ldb_module *module)
+{
+ int ret, i;
+ TALLOC_CTX *mem_ctx = talloc_new(module);
+ static const char *attrs[] = { "passwordAttribute", NULL };
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ldb_message_element *password_attributes;
+
+ struct kludge_private_data *data;
+
+ data = talloc(module, struct kludge_private_data);
+ if (data == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data->password_attrs = NULL;
+ module->private_data = data;
+
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(module->ldb, ldb_dn_new(mem_ctx, module->ldb, "@KLUDGEACL"),
+ LDB_SCOPE_BASE,
+ NULL, attrs,
+ &res);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ talloc_steal(mem_ctx, res);
+ if (res->count == 0) {
+ goto done;
+ }
+
+ if (res->count > 1) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ msg = res->msgs[0];
+
+ password_attributes = ldb_msg_find_element(msg, "passwordAttribute");
+ if (!password_attributes) {
+ goto done;
+ }
+ data->password_attrs = talloc_array(data, const char *, password_attributes->num_values + 1);
+ if (!data->password_attrs) {
+ talloc_free(mem_ctx);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i=0; i < password_attributes->num_values; i++) {
+ data->password_attrs[i] = (const char *)password_attributes->values[i].data;
+ talloc_steal(data->password_attrs, password_attributes->values[i].data);
+ }
+ data->password_attrs[i] = NULL;
+
+done:
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_kludge_acl_module_ops = {
+ .name = "kludge_acl",
+ .search = kludge_acl_search,
+ .add = kludge_acl_change,
+ .modify = kludge_acl_change,
+ .del = kludge_acl_change,
+ .rename = kludge_acl_change,
+ .extended = kludge_acl_change,
+ .init_context = kludge_acl_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/linked_attributes.c b/source4/dsdb/samdb/ldb_modules/linked_attributes.c
new file mode 100644
index 0000000000..e64472432d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/linked_attributes.c
@@ -0,0 +1,953 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb linked_attributes module
+ *
+ * Description: Module to ensure linked attribute pairs remain in sync
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+
+struct linked_attributes_context {
+ enum la_step {LA_SEARCH, LA_DO_OPS, LA_DO_ORIG} step;
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *search_req;
+ struct ldb_request **down_req;
+ struct ldb_request *orig_down_req;
+
+ int num_requests;
+ int finished_requests;
+
+ const char **linked_attrs;
+};
+
+struct replace_context {
+ struct linked_attributes_context *ac;
+ struct ldb_message_element *el;
+};
+
+static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares);
+
+static struct linked_attributes_context *linked_attributes_init_handle(struct ldb_request *req,
+ struct ldb_module *module)
+{
+ struct linked_attributes_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct linked_attributes_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->handle = h;
+ ac->orig_req = req;
+
+ ac->orig_down_req = talloc(ac, struct ldb_request);
+ if (!ac->orig_down_req) {
+ ldb_oom(ac->module->ldb);
+ return NULL;
+ }
+
+ *ac->orig_down_req = *req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+/* Common routine to handle reading the attributes and creating a
+ * series of modify requests */
+
+static int setup_modifies(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ struct linked_attributes_context *ac,
+ const struct ldb_message *msg,
+ struct ldb_dn *olddn, struct ldb_dn *newdn)
+{
+ int i, j, ret = LDB_SUCCESS;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ /* Look up each of the returned attributes */
+ /* Find their schema */
+ /* And it is an actual entry: now create a series of modify requests */
+ for (i=0; i < msg->num_elements; i++) {
+ int otherid;
+ const struct dsdb_attribute *target_attr;
+ const struct ldb_message_element *el = &msg->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s is not a valid attribute in schema", el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, but if it's not linked they maybe we just got an extra return on our search... */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ /* Depending on which direction this link is in, we need to find it's partner */
+ if ((schema_attr->linkID & 1) == 1) {
+ otherid = schema_attr->linkID - 1;
+ } else {
+ otherid = schema_attr->linkID + 1;
+ }
+
+ /* Now find the target attribute */
+ target_attr = dsdb_attribute_by_linkID(schema, otherid);
+ if (!target_attr) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s does not have valid link target", el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* For each value being moded, we need to setup the modify */
+ for (j=0; j < el->num_values; j++) {
+ struct ldb_message_element *ret_el;
+ struct ldb_request *new_req;
+ struct ldb_message *new_msg;
+
+ /* Create a spot in the list for the requests */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Create the modify request */
+ new_msg = ldb_msg_new(ac->down_req);
+ if (!new_msg) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ new_msg->dn = ldb_dn_from_ldb_val(new_msg, ldb, &el->values[j]);
+ if (!new_msg->dn) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s value %s was not a valid DN", msg->elements[i].name,
+ el->values[j].data);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+
+ if (olddn) {
+ ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
+ LDB_FLAG_MOD_DELETE, &ret_el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
+ if (!ret_el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(olddn));
+ ret_el->num_values = 1;
+ }
+
+ if (newdn) {
+ ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
+ LDB_FLAG_MOD_ADD, &ret_el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
+ if (!ret_el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(newdn));
+ ret_el->num_values = 1;
+ }
+
+ ret = ldb_build_mod_req(&new_req, ldb, ac->down_req,
+ new_msg,
+ NULL,
+ NULL,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, new_msg);
+
+ ldb_set_timeout_from_prev_req(ldb, ac->orig_req, new_req);
+
+ ac->down_req[ac->num_requests] = new_req;
+ ac->num_requests++;
+
+
+ /* Run the new request */
+ ret = ldb_next_request(ac->module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+/* add */
+static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
+{
+ int i;
+ struct linked_attributes_context *ac;
+
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->step = LA_DO_OPS;
+
+ /* Need to ensure we only have forward links being specified */
+ for (i=0; i < req->op.add.message->num_elements; i++) {
+ const struct ldb_message_element *el = &req->op.add.message->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s is not a valid attribute in schema", req->op.add.message->elements[i].name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, not find out if it is linked */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ if ((schema_attr->linkID & 1) == 1) {
+ /* Odd is for the target. Illigal to modify */
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s must not be modified directly, it is a linked attribute", req->op.add.message->elements[i].name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Even link IDs are for the originating attribute */
+ }
+
+ /* Now call the common routine to setup the modifies across all the attributes */
+ return setup_modifies(module->ldb, ac, ac, req->op.add.message, NULL, req->op.add.message->dn);
+}
+
+struct merge {
+ struct ldb_dn *dn;
+ bool add;
+ bool ignore;
+};
+
+static int merge_cmp(struct merge *merge1, struct merge *merge2) {
+ int ret;
+ ret = ldb_dn_compare(merge1->dn, merge2->dn);
+ if (ret == 0) {
+ if (merge1->add == merge2->add) {
+ return 0;
+ }
+ if (merge1->add == true) {
+ return 1;
+ }
+ return -1;
+ }
+ return ret;
+}
+
+static int linked_attributes_mod_replace_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct replace_context *ac2 = talloc_get_type(context, struct replace_context);
+ struct linked_attributes_context *ac = ac2->ac;
+
+ /* OK, we have one search result here: */
+
+ /* Only entries are interesting, and we only want the olddn */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, ac->orig_req->op.mod.message->dn) == 0) {
+ /* only bother at all if there were some linked attributes found */
+ struct ldb_message_element *search_el
+ = ldb_msg_find_element(ares->message,
+ ac2->el->name);
+
+ /* See if this element already exists */
+ if (search_el) {
+
+ struct merge *merged_list = NULL;
+
+ int ret, size = 0, i;
+ struct ldb_message *msg = ldb_msg_new(ac);
+ if (!msg) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Add all the existing elements, marking as 'proposed for delete' by setting .add = false */
+ for (i=0; i < search_el->num_values; i++) {
+ merged_list = talloc_realloc(ares, merged_list, struct merge, size + 1);
+ merged_list[size].dn = ldb_dn_from_ldb_val(merged_list, ldb, &search_el->values[i]);
+ merged_list[size].add = false;
+ merged_list[size].ignore = false;
+ size++;
+ }
+
+ /* Add all the new replacement elements, marking as 'proposed for add' by setting .add = true */
+ for (i=0; i < ac2->el->num_values; i++) {
+ merged_list = talloc_realloc(ares, merged_list, struct merge, size + 1);
+ merged_list[size].dn = ldb_dn_from_ldb_val(merged_list, ldb, &ac2->el->values[i]);
+ merged_list[size].add = true;
+ merged_list[size].ignore = false;
+ size++;
+ }
+
+ /* Sort the list, so we can pick out an add and delete for the same DN, and eliminate them */
+ qsort(merged_list, size,
+ sizeof(*merged_list),
+ (comparison_fn_t)merge_cmp);
+
+ /* Now things are sorted, it is trivial to mark pairs of DNs as 'ignore' */
+ for (i=0; i + 1 < size; i++) {
+ if (ldb_dn_compare(merged_list[i].dn,
+ merged_list[i+1].dn) == 0
+ /* Fortunetly the sort also sorts 'add == false' first */
+ && merged_list[i].add == false
+ && merged_list[i+1].add == true) {
+
+ /* Mark as ignore, so we include neither in the actual operations */
+ merged_list[i].ignore = true;
+ merged_list[i+1].ignore = true;
+ }
+ }
+
+ /* Arrange to delete anything the search found that we don't re-add */
+ for (i=0; i < size; i++) {
+ if (merged_list[i].ignore == false
+ && merged_list[i].add == false) {
+ ldb_msg_add_steal_string(msg, search_el->name,
+ ldb_dn_get_linearized(merged_list[i].dn));
+ }
+ }
+
+ /* The DN to set on the linked attributes is the original DN of the modify message */
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ ret = setup_modifies(ac->module->ldb, ac2, ac, msg, ares->message->dn, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Now add links for all the actually new elements */
+ for (i=0; i < size; i++) {
+ if (merged_list[i].ignore == false && merged_list[i].add == true) {
+ ldb_msg_add_steal_string(msg, search_el->name,
+ ldb_dn_get_linearized(merged_list[i].dn));
+ }
+ }
+
+ ret = setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_free(merged_list);
+
+ } else {
+ /* Looks like it doesn't exist, process like an 'add' */
+ struct ldb_message *msg = ldb_msg_new(ac);
+ if (!msg) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg->num_elements = 1;
+ msg->elements = ac2->el;
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ return setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ac->orig_req->op.mod.message->dn);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ } else if (ares->type == LDB_REPLY_ENTRY) {
+ /* Guh? We only asked for this DN */
+ return LDB_ERR_OPERATIONS_ERROR;
+
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+
+}
+/* modify */
+static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Look over list of modifications */
+ /* Find if any are for linked attributes */
+ /* Determine the effect of the modification */
+ /* Apply the modify to the linked entry */
+
+ int i, j;
+ struct linked_attributes_context *ac;
+
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* prepare the first operation */
+ ac->step = LA_DO_OPS;
+
+ for (i=0; i < req->op.mod.message->num_elements; i++) {
+ int ret;
+ struct ldb_request *new_req;
+ const struct dsdb_attribute *target_attr;
+ const struct ldb_message_element *el = &req->op.mod.message->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s is not a valid attribute in schema", req->op.mod.message->elements[i].name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, not find out if it is linked */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ if ((schema_attr->linkID & 1) == 1) {
+ /* Odd is for the target. Illigal to modify */
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s must not be modified directly, it is a linked attribute", req->op.mod.message->elements[i].name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Even link IDs are for the originating attribute */
+
+ /* Now find the target attribute */
+ target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
+ if (!target_attr) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s does not have valid link target", req->op.mod.message->elements[i].name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* Replace with new set of values */
+ if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)
+ && el->num_values > 0) {
+ struct replace_context *ac2 = talloc(ac, struct replace_context);
+ const char **attrs = talloc_array(ac, const char *, 2);
+ if (!attrs || !ac2) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ attrs[0] = el->name;
+ attrs[1] = NULL;
+
+ ac2->ac = ac;
+ ac2->el = el;
+
+ /* We need to setup a search, compare with the list, and then setup add/del as required */
+
+ /* The callback does all the hard work here */
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac2,
+ linked_attributes_mod_replace_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Create a spot in the list for the requests */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+ ac->num_requests++;
+
+ ret = ldb_next_request(module, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ continue;
+
+ /* Delete all values case */
+ } else if (((el->flags & LDB_FLAG_MOD_MASK) & (LDB_FLAG_MOD_DELETE|LDB_FLAG_MOD_REPLACE))
+ && el->num_values == 0) {
+ const char **attrs = talloc_array(ac, const char *, 2);
+ if (!attrs) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ attrs[0] = el->name;
+ attrs[1] = NULL;
+
+ /* We need to setup a search, and then setup del as required */
+
+ /* The callback does all the hard work here, acting identically to if we had delted the whole entry */
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ linked_attributes_rename_del_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Create a spot in the list for the requests */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+ ac->num_requests++;
+
+ ret = ldb_next_request(module, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ continue;
+ }
+
+ /* Prepare the modify (mod element) on the targets, for a normal modify request */
+
+ /* For each value being moded, we need to setup the modify */
+ for (j=0; j < el->num_values; j++) {
+ /* Create the modify request */
+ struct ldb_message *new_msg = ldb_msg_new(ac);
+ if (!new_msg) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ new_msg->dn = ldb_dn_from_ldb_val(new_msg, module->ldb, &el->values[j]);
+ if (!new_msg->dn) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
+ el->values[j].data);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+
+ ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
+ el->flags & LDB_FLAG_MOD_MASK, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
+ ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_mod_req(&new_req, module->ldb, ac,
+ new_msg,
+ NULL,
+ NULL,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, new_msg);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Now add it to the list */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+ ac->num_requests++;
+
+ /* Run the new request */
+ ret = ldb_next_request(module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
+ struct ldb_dn *olddn, *newdn;
+
+ switch (ac->orig_req->operation) {
+ case LDB_DELETE:
+ {
+ olddn = ac->orig_req->op.del.dn;
+ newdn = NULL;
+ break;
+ }
+ /* This isn't the general modify case, just the modify when we are asked to delete all values */
+ case LDB_MODIFY:
+ {
+ olddn = ac->orig_req->op.mod.message->dn;
+ newdn = NULL;
+ break;
+ }
+ case LDB_RENAME:
+ {
+ olddn = ac->orig_req->op.rename.olddn;
+ newdn = ac->orig_req->op.rename.newdn;
+ break;
+ }
+ default:
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+
+ /* OK, we have one search result here: */
+
+ /* Only entries are interesting, and we only want the olddn */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, olddn) == 0) {
+ /* only bother at all if there were some linked attributes found */
+ if (ares->message->num_elements > 0) {
+ return setup_modifies(ldb, ac, ac,
+ ares->message, olddn, newdn);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ } else if (ares->type == LDB_REPLY_ENTRY) {
+ /* Guh? We only asked for this DN */
+ return LDB_ERR_OPERATIONS_ERROR;
+
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+
+}
+/* rename */
+static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Look up list of linked attributes */
+ const char **attrs;
+ WERROR werr;
+ int ret;
+ struct linked_attributes_context *ac;
+ struct ldb_request *new_req;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for the entry
+ - Wait for these result to appear
+ - In the callback for the result, issue a modify request based on the linked attributes found
+ - Wait for each modify result
+ - Regain our sainity
+ */
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
+ if (!W_ERROR_IS_OK(werr)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.rename.olddn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ linked_attributes_rename_del_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->search_req = new_req;
+ ac->step = LA_SEARCH;
+ return ldb_next_request(module, new_req);
+}
+
+/* delete */
+static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Look up list of linked attributes */
+ const char **attrs;
+ WERROR werr;
+ int ret;
+ struct ldb_request *new_req;
+ struct linked_attributes_context *ac;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for the entry
+ - Wait for these result to appear
+ - In the callback for the result, issue a modify request based on the linked attributes found
+ - Wait for each modify result
+ - Regain our sainity
+ */
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
+ if (!W_ERROR_IS_OK(werr)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ };
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.del.dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ linked_attributes_rename_del_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->search_req = new_req;
+ ac->step = LA_SEARCH;
+ return ldb_next_request(module, new_req);
+}
+
+
+static int linked_attributes_wait_none(struct ldb_handle *handle) {
+ struct linked_attributes_context *ac;
+ int i, ret = LDB_ERR_OPERATIONS_ERROR;
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
+
+ switch (ac->step) {
+ case LA_SEARCH:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ ac->step = LA_DO_OPS;
+ return LDB_SUCCESS;
+
+ case LA_DO_OPS:
+ for (i=0; i < ac->num_requests; i++) {
+ ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req[i]->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ }
+
+ /* Now run the original request */
+ ac->step = LA_DO_ORIG;
+ return ldb_next_request(ac->module, ac->orig_down_req);
+
+ case LA_DO_ORIG:
+ ret = ldb_wait(ac->orig_down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->orig_down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->orig_down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->orig_down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ ret = LDB_SUCCESS;
+ }
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+
+}
+
+static int linked_attributes_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = linked_attributes_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return linked_attributes_wait_all(handle);
+ } else {
+ return linked_attributes_wait_none(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_linked_attributes_module_ops = {
+ .name = "linked_attributes",
+ .add = linked_attributes_add,
+ .modify = linked_attributes_modify,
+ .del = linked_attributes_delete,
+ .rename = linked_attributes_rename,
+ .wait = linked_attributes_wait,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/local_password.c b/source4/dsdb/samdb/ldb_modules/local_password.c
new file mode 100644
index 0000000000..a411c01513
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/local_password.c
@@ -0,0 +1,852 @@
+/*
+ ldb database module
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb local_password module
+ *
+ * Description: correctly update hash values based on changes to userPassword and friends
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/ldb_modules/password_modules.h"
+
+#define PASSWORD_GUID_ATTR "masterGUID"
+
+/* This module maintains a local password database, seperate from the main LDAP server.
+
+ This allows the password database to be syncronised in a multi-master
+ fashion, seperate to the more difficult concerns of the main
+ database. (With passwords, the last writer always wins)
+
+ Each incoming add/modify is split into a remote, and a local request, done in that order.
+
+ We maintain a list of attributes that are kept locally:
+ */
+
+static const char * const password_attrs[] = {
+ "supplementalCredentials",
+ "unicodePwd",
+ "dBCSPwd",
+ "lmPwdHistory",
+ "ntPwdHistory",
+ "msDS-KeyVersionNumber",
+ "pwdLastSet"
+};
+
+/* And we merge them back into search requests when asked to do so */
+
+struct lpdb_context {
+
+ enum lpdb_type {LPDB_ADD, LPDB_MOD, LPDB_SEARCH} type;
+ enum lpdb_step {LPDB_ADD_REMOTE, LPDB_MOD_REMOTE, LPDB_MOD_SEARCH_SELF, LPDB_LOCAL, LPDB_SEARCH_REMOTE} step;
+
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+ struct ldb_request *remote_req;
+ struct ldb_request *search_req;
+ struct ldb_request *local_req;
+
+ struct ldb_message *local_message;
+
+ bool added_objectGUID;
+ bool added_objectClass;
+
+ struct ldb_reply *search_res;
+};
+
+struct lpdb_local_search_context {
+ struct lpdb_context *ac;
+ struct ldb_reply *remote_res;
+ struct ldb_reply *local_res;
+};
+
+static struct ldb_handle *lpdb_init_handle(struct ldb_request *req, struct ldb_module *module, enum lpdb_type type)
+{
+ struct lpdb_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct lpdb_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = (void *)ac;
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->type = type;
+ ac->module = module;
+ ac->orig_req = req;
+
+ return h;
+}
+
+/* Add a record, splitting password attributes from the user's main
+ * record */
+
+static int local_password_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct lpdb_context *ac;
+ struct ldb_message *remote_message;
+ struct ldb_message *local_message;
+ struct GUID objectGUID;
+ int i;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_add\n");
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.add.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ if (ldb_msg_find_element(req->op.add.message, password_attrs[i])) {
+ break;
+ }
+ }
+
+ /* It didn't match any of our password attributes, go on */
+ if (i == ARRAY_SIZE(password_attrs)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* TODO: remove this when userPassword will be in schema */
+ if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
+ ldb_asprintf_errstring(module->ldb,
+ "Cannot relocate a password on entry: %s, does not have objectClass 'person'",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* From here, we assume we have password attributes to split off */
+ h = lpdb_init_handle(req, module, LPDB_ADD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ ac->orig_req = req;
+
+ ac->remote_req = talloc(ac, struct ldb_request);
+ if (ac->remote_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->remote_req) = *(ac->orig_req);
+
+ remote_message = ldb_msg_copy_shallow(ac->remote_req, ac->orig_req->op.add.message);
+ if (remote_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove any password attributes from the remote message */
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ ldb_msg_remove_attr(remote_message, password_attrs[i]);
+ }
+
+ ac->remote_req->op.add.message = remote_message;
+
+ ac->remote_req->context = NULL;
+ ac->remote_req->callback = NULL;
+
+ ac->local_req = talloc(ac, struct ldb_request);
+ if (ac->local_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->local_req) = *(ac->orig_req);
+ local_message = ldb_msg_copy_shallow(ac->local_req, ac->orig_req->op.add.message);
+ if (local_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove anything seen in the remote message from the local
+ * message (leaving only password attributes) */
+ for (i=0;i<ac->remote_req->op.add.message->num_elements;i++) {
+ ldb_msg_remove_attr(local_message, ac->remote_req->op.add.message->elements[i].name);
+ }
+
+ /* We must have an objectGUID already, or we don't know where
+ * to add the password. This may be changed to an 'add and
+ * search', to allow the directory to create the objectGUID */
+ if (ldb_msg_find_ldb_val(ac->orig_req->op.add.message, "objectGUID") == NULL) {
+ ldb_set_errstring(module->ldb,
+ "no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* Find the objectGUID to use as the key */
+ objectGUID = samdb_result_guid(ac->orig_req->op.add.message, "objectGUID");
+
+ local_message->dn = ldb_dn_new(local_message, module->ldb, LOCAL_BASE);
+ ldb_dn_add_child_fmt(local_message->dn, PASSWORD_GUID_ATTR "=%s", GUID_string(local_message, &objectGUID));
+
+ ac->local_req->op.add.message = local_message;
+
+ ac->local_req->context = NULL;
+ ac->local_req->callback = NULL;
+
+ ac->step = LPDB_ADD_REMOTE;
+
+ /* Return our own handle do deal with this call */
+ req->handle = h;
+
+ return ldb_next_request(module, ac->remote_req);
+}
+
+/* After adding the remote entry, add the local one */
+static int local_password_add_local(struct ldb_handle *h) {
+
+ struct lpdb_context *ac;
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = LPDB_LOCAL;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->local_req);
+
+ /* perform the local add */
+ return ldb_next_request(ac->module, ac->local_req);
+}
+
+static int local_password_mod_search_self(struct ldb_handle *h);
+
+static int local_password_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct lpdb_context *ac;
+ struct ldb_message *remote_message;
+ struct ldb_message *local_message;
+ int i;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_modify\n");
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.mod.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ if (ldb_msg_find_element(req->op.add.message, password_attrs[i])) {
+ break;
+ }
+ }
+
+ /* It didn't match any of our password attributes, then we have nothing to do here */
+ if (i == ARRAY_SIZE(password_attrs)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* From here, we assume we have password attributes to split off */
+ h = lpdb_init_handle(req, module, LPDB_MOD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ ac->orig_req = req;
+
+ ac->remote_req = talloc(ac, struct ldb_request);
+ if (ac->remote_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->remote_req) = *(ac->orig_req);
+ remote_message = ldb_msg_copy_shallow(ac->remote_req, ac->orig_req->op.mod.message);
+ if (remote_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove any password attributes from the remote message */
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ ldb_msg_remove_attr(remote_message, password_attrs[i]);
+ }
+
+ ac->remote_req->op.mod.message = remote_message;
+
+ ac->remote_req->context = NULL;
+ ac->remote_req->callback = NULL;
+
+ ac->local_req = talloc(ac, struct ldb_request);
+ if (ac->local_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->local_req) = *(ac->orig_req);
+ local_message = ldb_msg_copy_shallow(ac->local_req, ac->orig_req->op.mod.message);
+ if (local_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove anything seen in the remote message from the local
+ * message (leaving only password attributes) */
+ for (i=0;i<ac->remote_req->op.mod.message->num_elements;i++) {
+ ldb_msg_remove_attr(local_message, ac->remote_req->op.mod.message->elements[i].name);
+ }
+
+ ac->local_req->op.mod.message = local_message;
+ ac->local_message = local_message;
+
+ ac->local_req->context = NULL;
+ ac->local_req->callback = NULL;
+
+ ac->step = LPDB_MOD_REMOTE;
+
+ /* Return our own handle do deal with this call */
+ req->handle = h;
+
+ return ldb_next_request(module, ac->remote_req);
+}
+
+/* Called when we search for our oen entry. Stores the one entry we
+ * expect (as it is a base search) on the context pointer */
+static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct lpdb_context *ac;
+
+ ac = talloc_get_type(context, struct lpdb_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* On a modify, we don't have the objectGUID handy, so we need to
+ * search our DN for it */
+static int local_password_mod_search_self(struct ldb_handle *h) {
+
+ struct lpdb_context *ac;
+ static const char * const attrs[] = { "objectGUID", "objectClass", NULL };
+
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ /* prepare the search operation */
+ ac->search_req = talloc_zero(ac, struct ldb_request);
+ if (ac->search_req == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_req->operation = LDB_SEARCH;
+ ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
+ ac->search_req->op.search.scope = LDB_SCOPE_BASE;
+ ac->search_req->op.search.tree = ldb_parse_tree(ac->orig_req, NULL);
+ if (ac->search_req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Invalid search filter");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->search_req->op.search.attrs = attrs;
+ ac->search_req->controls = NULL;
+ ac->search_req->context = ac;
+ ac->search_req->callback = get_self_callback;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = LPDB_MOD_SEARCH_SELF;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+/* After we find out the objectGUID for the entry, modify the local
+ * password database as required */
+static int local_password_mod_local(struct ldb_handle *h) {
+
+ struct lpdb_context *ac;
+ struct GUID objectGUID;
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ /* if it is not an entry of type person this is an error */
+ /* TODO: remove this when these things are checked in the schema */
+ if (!ac->search_res) {
+ ldb_asprintf_errstring(ac->module->ldb,
+ "entry just modified (%s) not found!",
+ ldb_dn_get_linearized(ac->remote_req->op.mod.message->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (!ldb_msg_check_string_attribute(ac->search_res->message, "objectClass", "person")) {
+ /* Not relevent to us */
+ return LDB_SUCCESS;
+ }
+
+ if (ldb_msg_find_ldb_val(ac->search_res->message, "objectGUID") == NULL) {
+ ldb_set_errstring(ac->module->ldb,
+ "no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ objectGUID = samdb_result_guid(ac->search_res->message, "objectGUID");
+
+ ac->local_message->dn = ldb_dn_new(ac, ac->module->ldb, LOCAL_BASE);
+ ldb_dn_add_child_fmt(ac->local_message->dn, PASSWORD_GUID_ATTR "=%s", GUID_string(ac, &objectGUID));
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = LPDB_LOCAL;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->local_req);
+
+ /* perform the local update */
+ return ldb_next_request(ac->module, ac->local_req);
+}
+
+
+static int lpdb_local_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct lpdb_local_search_context *local_context;
+
+ local_context = talloc_get_type(context, struct lpdb_local_search_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ int i;
+ if (local_context->local_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results to base search for password entry!");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ local_context->local_res = ares;
+
+ /* Make sure never to return the internal key attribute to the caller */
+ ldb_msg_remove_attr(ares->message, PASSWORD_GUID_ATTR);
+
+ talloc_steal(local_context->remote_res->message->elements, ares->message->elements);
+ for (i=0; i < ares->message->num_elements; i++) {
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(local_context->remote_res->message,
+ ares->message->elements[i].name);
+ if (!el) {
+ if (ldb_msg_add_empty(local_context->remote_res->message,
+ ares->message->elements[i].name, 0, &el) != LDB_SUCCESS) {
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *el = ares->message->elements[i];
+ }
+ }
+ return local_context->ac->orig_req->callback(ldb,
+ local_context->ac->orig_req->context,
+ local_context->remote_res);
+ }
+ case LDB_REPLY_DONE:
+ {
+ /* Fire off the callback if there was no local entry, so we get the rest returned */
+ if (local_context->local_res == NULL) {
+ return local_context->ac->orig_req->callback(ldb,
+ local_context->ac->orig_req->context,
+ local_context->remote_res);
+ }
+ return LDB_SUCCESS;
+ break;
+ }
+ default:
+ {
+ talloc_free(ares);
+ ldb_set_errstring(ldb, "Unexpected result type in base search for password entry!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+}
+
+/* For each entry returned in a remote search, do a local base search,
+ * based on the objectGUID we asked for as an additional attribute */
+static int lpdb_remote_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct lpdb_context *ac;
+
+ ac = talloc_get_type(context, struct lpdb_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ struct ldb_request *req;
+ struct lpdb_local_search_context *local_context;
+ struct GUID objectGUID;
+
+ /* No point searching further if it's not a 'person' entry */
+ if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
+
+ /* Make sure to remove anything we added */
+ if (ac->added_objectGUID) {
+ ldb_msg_remove_attr(ares->message, "objectGUID");
+ }
+
+ if (ac->added_objectClass) {
+ ldb_msg_remove_attr(ares->message, "objectClass");
+ }
+
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ }
+
+ if (ldb_msg_find_ldb_val(ares->message, "objectGUID") == NULL) {
+ ldb_set_errstring(ac->module->ldb,
+ "no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ objectGUID = samdb_result_guid(ares->message, "objectGUID");
+
+ if (ac->added_objectGUID) {
+ ldb_msg_remove_attr(ares->message, "objectGUID");
+ }
+
+ if (ac->added_objectClass) {
+ ldb_msg_remove_attr(ares->message, "objectClass");
+ }
+
+ req = talloc_zero(ac, struct ldb_request);
+ if (!req) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ local_context = talloc(ac, struct lpdb_local_search_context);
+ if (!local_context) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ local_context->ac = ac;
+ local_context->remote_res = ares;
+ local_context->local_res = NULL;
+
+ req->op.search.base = ldb_dn_new(ac, ac->module->ldb, LOCAL_BASE);
+ if ( ! ldb_dn_add_child_fmt(req->op.search.base, PASSWORD_GUID_ATTR "=%s", GUID_string(ac, &objectGUID))) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ req->operation = LDB_SEARCH;
+ req->op.search.scope = LDB_SCOPE_BASE;
+ req->op.search.tree = ldb_parse_tree(req, NULL);
+ if (req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Out of Memory");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ req->op.search.attrs = ac->orig_req->op.search.attrs;
+ req->controls = NULL;
+ req->context = ac;
+ req->callback = get_self_callback;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, req);
+
+ req->context = local_context;
+ req->callback = lpdb_local_search_callback;
+
+ return ldb_next_request(ac->module, req);
+ } else {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ }
+}
+
+/* Search for passwords and other attributes. The passwords are
+ * local, but the other attributes are remote, and we need to glue the
+ * two search spaces back togeather */
+
+static int local_password_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct lpdb_context *ac;
+ int i;
+ int ret;
+ const char * const *search_attrs = NULL;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_search\n");
+
+ if (ldb_dn_is_special(req->op.search.base)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is searching for the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.search.base) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ if (req->op.search.attrs && (!ldb_attr_in_list(req->op.search.attrs, "*"))) {
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ if (ldb_attr_in_list(req->op.search.attrs, password_attrs[i])) {
+ break;
+ }
+ }
+
+ /* It didn't match any of our password attributes, go on */
+ if (i == ARRAY_SIZE(password_attrs)) {
+ return ldb_next_request(module, req);
+ }
+ }
+
+ h = lpdb_init_handle(req, module, LPDB_SEARCH);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ ac->orig_req = req;
+
+ ac->remote_req = talloc(ac, struct ldb_request);
+ if (ac->remote_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remote search is for all attributes: if the remote LDAP server has these attributes, then it overrides the local database */
+ *(ac->remote_req) = *(ac->orig_req);
+
+ /* Return our own handle do deal with this call */
+ ac->remote_req->handle = h;
+
+ ac->remote_req->context = ac;
+ ac->remote_req->callback = lpdb_remote_search_callback;
+
+ if (req->op.search.attrs && !ldb_attr_in_list(req->op.search.attrs, "*")) {
+ if (!ldb_attr_in_list(req->op.search.attrs, "objectGUID")) {
+ search_attrs = ldb_attr_list_copy_add(req, req->op.search.attrs, "objectGUID");
+ ac->added_objectGUID = true;
+ if (!search_attrs) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else {
+ search_attrs = req->op.search.attrs;
+ }
+ if (!ldb_attr_in_list(search_attrs, "objectClass")) {
+ search_attrs = ldb_attr_list_copy_add(req, search_attrs, "objectClass");
+ ac->added_objectClass = true;
+ if (!search_attrs) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ } else {
+ search_attrs = req->op.search.attrs;
+ }
+
+ ac->remote_req->op.search.attrs = search_attrs;
+
+ ldb_set_timeout_from_prev_req(module->ldb, ac->orig_req, ac->remote_req);
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = LPDB_SEARCH_REMOTE;
+
+ /* perform the search */
+ ret = ldb_next_request(module, ac->remote_req);
+
+ if (ret == LDB_SUCCESS) {
+ req->handle = ac->remote_req->handle;
+ }
+
+ return ret;
+}
+
+static int lpdb_wait(struct ldb_handle *handle) {
+ struct lpdb_context *ac;
+ int ret;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct lpdb_context);
+
+ switch (ac->step) {
+ case LPDB_ADD_REMOTE:
+ ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->remote_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->remote_req->handle->status;
+ goto done;
+ }
+
+ if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* original request done, go on */
+ return local_password_add_local(handle);
+
+ case LPDB_MOD_REMOTE:
+ ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->remote_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->remote_req->handle->status;
+ goto done;
+ }
+
+ if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* original request done, go on */
+ return local_password_mod_search_self(handle);
+
+ case LPDB_MOD_SEARCH_SELF:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* original request done, go on */
+ return local_password_mod_local(handle);
+
+ case LPDB_LOCAL:
+ ret = ldb_wait(ac->local_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->local_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->local_req->handle->status;
+ goto done;
+ }
+
+ if (ac->local_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case LPDB_SEARCH_REMOTE:
+ ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->remote_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->remote_req->handle->status;
+ goto done;
+ }
+
+ if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int lpdb_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = lpdb_wait(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int local_password_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return lpdb_wait_all(handle);
+ } else {
+ return lpdb_wait(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_local_password_module_ops = {
+ .name = "local_password",
+ .add = local_password_add,
+ .modify = local_password_modify,
+ .search = local_password_search,
+ .wait = local_password_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/naming_fsmo.c b/source4/dsdb/samdb/ldb_modules/naming_fsmo.c
new file mode 100644
index 0000000000..084540f68d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/naming_fsmo.c
@@ -0,0 +1,123 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The module that handles the Domain Naming FSMO Role Owner
+ checkings
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/util/dlinklist.h"
+
+static int naming_fsmo_init(struct ldb_module *module)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ldb_dn *naming_dn;
+ struct dsdb_naming_fsmo *naming_fsmo;
+ struct ldb_result *naming_res;
+ int ret;
+ static const char *naming_attrs[] = {
+ "fSMORoleOwner",
+ NULL
+ };
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ naming_dn = samdb_partitions_dn(module->ldb, mem_ctx);
+ if (!naming_dn) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "naming_fsmo_init: no partitions dn present: (skip loading of naming contexts details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ naming_fsmo = talloc_zero(mem_ctx, struct dsdb_naming_fsmo);
+ if (!naming_fsmo) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ module->private_data = naming_fsmo;
+
+ ret = ldb_search(module->ldb, naming_dn,
+ LDB_SCOPE_BASE,
+ NULL, naming_attrs,
+ &naming_res);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "naming_fsmo_init: no partitions dn present: (skip loading of naming contexts details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "naming_fsmo_init: failed to search the cross-ref container: %s: %s",
+ ldb_strerror(ret), ldb_errstring(module->ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, naming_res);
+ if (naming_res->count == 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "naming_fsmo_init: no cross-ref container present: (skip loading of naming contexts details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ } else if (naming_res->count > 1) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "naming_fsmo_init: [%u] cross-ref containers found on a base search",
+ naming_res->count);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ naming_fsmo->master_dn = ldb_msg_find_attr_as_dn(module->ldb, naming_fsmo, naming_res->msgs[0], "fSMORoleOwner");
+ if (ldb_dn_compare(samdb_ntds_settings_dn(module->ldb), naming_fsmo->master_dn) == 0) {
+ naming_fsmo->we_are_master = true;
+ } else {
+ naming_fsmo->we_are_master = false;
+ }
+
+ if (ldb_set_opaque(module->ldb, "dsdb_naming_fsmo", naming_fsmo) != LDB_SUCCESS) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_steal(module, naming_fsmo);
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE,
+ "naming_fsmo_init: we are master: %s\n",
+ (naming_fsmo->we_are_master?"yes":"no"));
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_naming_fsmo_module_ops = {
+ .name = "naming_fsmo",
+ .init_context = naming_fsmo_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/normalise.c b/source4/dsdb/samdb/ldb_modules/normalise.c
new file mode 100644
index 0000000000..3306fd3c33
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/normalise.c
@@ -0,0 +1,162 @@
+/*
+ ldb database library
+
+ Copyright (C) Amdrew Bartlett <abartlet@samba.org> 2007-2008
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb normalisation module
+ *
+ * Description: module to ensure all DNs and attribute names are normalised
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+
+/* Fix up the DN to be in the standard form, taking particular care to match the parent DN
+
+ This should mean that if the parent is:
+ CN=Users,DC=samba,DC=example,DC=com
+ and a proposed child is
+ cn=Admins ,cn=USERS,dc=Samba,dc=example,dc=COM
+
+ The resulting DN should be:
+
+ CN=Admins,CN=Users,DC=samba,DC=example,DC=com
+
+ */
+static int fix_dn(struct ldb_dn *dn)
+{
+ int i, ret;
+ char *upper_rdn_attr;
+
+ for (i=0; i < ldb_dn_get_comp_num(dn); i++) {
+ /* We need the attribute name in upper case */
+ upper_rdn_attr = strupper_talloc(dn,
+ ldb_dn_get_component_name(dn, i));
+ if (!upper_rdn_attr) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* And replace it with CN=foo (we need the attribute in upper case */
+ ret = ldb_dn_set_component(dn, i, upper_rdn_attr,
+ *ldb_dn_get_component_val(dn, i));
+ talloc_free(upper_rdn_attr);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int normalise_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ struct ldb_request *orig_req = talloc_get_type(context, struct ldb_request);
+ TALLOC_CTX *mem_ctx;
+ int i, j, ret;
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ if (!schema) {
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ mem_ctx = talloc_new(ares);
+ if (!mem_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* OK, we have one of *many* search results passing by here,
+ * but we should get them one at a time */
+
+ ret = fix_dn(ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ for (i = 0; i < ares->message->num_elements; i++) {
+ const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, ares->message->elements[i].name);
+ if (!attribute) {
+ continue;
+ }
+ /* Look to see if this attributeSyntax is a DN */
+ if (!((strcmp(attribute->attributeSyntax_oid, "2.5.5.1") == 0) ||
+ (strcmp(attribute->attributeSyntax_oid, "2.5.5.7") == 0))) {
+ continue;
+ }
+ for (j = 0; j < ares->message->elements[i].num_values; j++) {
+ const char *dn_str;
+ struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, ldb, &ares->message->elements[i].values[j]);
+ if (!dn) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = fix_dn(dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ dn_str = talloc_steal(ares->message->elements[i].values, ldb_dn_get_linearized(dn));
+ ares->message->elements[i].values[j] = data_blob_string_const(dn_str);
+ talloc_free(dn);
+ }
+ }
+ talloc_free(mem_ctx);
+ return orig_req->callback(ldb, orig_req->context, ares);
+}
+
+/* search */
+static int normalise_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_request *down_req = talloc(req, struct ldb_request);
+ if (!down_req) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+ down_req->context = req;
+ down_req->callback = normalise_search_callback;
+
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+}
+
+
+_PUBLIC_ const struct ldb_module_ops ldb_normalise_module_ops = {
+ .name = "normalise",
+ .search = normalise_search,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c
new file mode 100644
index 0000000000..b048a8d8e1
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectclass.c
@@ -0,0 +1,1207 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: objectClass sorting module
+ *
+ * Description:
+ * - sort the objectClass attribute into the class
+ * hierarchy,
+ * - fix DNs and attributes into 'standard' case
+ * - Add objectCategory and ntSecurityDescriptor defaults
+ *
+ * Author: Andrew Bartlett
+ */
+
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "auth/auth.h"
+#include "param/param.h"
+
+struct oc_context {
+
+ enum oc_step {OC_DO_REQ, OC_SEARCH_SELF, OC_DO_MOD,
+ OC_SEARCH_ADD_PARENT, OC_DO_ADD,
+ OC_SEARCH_RENAME_PARENT, OC_DO_RENAME} step;
+
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *down_req;
+
+ struct ldb_request *search_req;
+ struct ldb_reply *search_res;
+
+ struct ldb_request *add_req;
+ struct ldb_request *mod_req;
+ struct ldb_request *rename_req;
+};
+
+struct class_list {
+ struct class_list *prev, *next;
+ const struct dsdb_class *objectclass;
+};
+
+static int objectclass_do_add(struct ldb_handle *h);
+
+static struct ldb_handle *oc_init_handle(struct ldb_request *req, struct ldb_module *module)
+{
+ struct oc_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct oc_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = (void *)ac;
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->module = module;
+ ac->orig_req = req;
+
+ return h;
+}
+
+/* Sort objectClasses into correct order, and validate that all
+ * objectClasses specified actually exist in the schema
+ */
+
+static int objectclass_sort(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_message *msg, /* so that when we create new elements, we put it on the right parent */
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *objectclass_element,
+ struct class_list **sorted_out)
+{
+ int i;
+ int layer;
+ struct class_list *sorted = NULL, *parent_class = NULL,
+ *subclass = NULL, *unsorted = NULL, *current, *poss_subclass, *poss_parent, *new_parent;
+ /* DESIGN:
+ *
+ * We work on 4 different 'bins' (implemented here as linked lists):
+ *
+ * * sorted: the eventual list, in the order we wish to push
+ * into the database. This is the only ordered list.
+ *
+ * * parent_class: The current parent class 'bin' we are
+ * trying to find subclasses for
+ *
+ * * subclass: The subclasses we have found so far
+ *
+ * * unsorted: The remaining objectClasses
+ *
+ * The process is a matter of filtering objectClasses up from
+ * unsorted into sorted. Order is irrelevent in the later 3 'bins'.
+ *
+ * We start with 'top' (found and promoted to parent_class
+ * initially). Then we find (in unsorted) all the direct
+ * subclasses of 'top'. parent_classes is concatenated onto
+ * the end of 'sorted', and subclass becomes the list in
+ * parent_class.
+ *
+ * We then repeat, until we find no more subclasses. Any left
+ * over classes are added to the end.
+ *
+ */
+
+ /* Firstly, dump all the objectClass elements into the
+ * unsorted bin, except for 'top', which is special */
+ for (i=0; i < objectclass_element->num_values; i++) {
+ current = talloc(mem_ctx, struct class_list);
+ if (!current) {
+ ldb_set_errstring(module->ldb, "objectclass: out of memory allocating objectclass list");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ current->objectclass = dsdb_class_by_lDAPDisplayName(schema, (const char *)objectclass_element->values[i].data);
+ if (!current->objectclass) {
+ ldb_asprintf_errstring(module->ldb, "objectclass %s is not a valid objectClass in schema", (const char *)objectclass_element->values[i].data);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* this is the root of the tree. We will start
+ * looking for subclasses from here */
+ if (ldb_attr_cmp("top", current->objectclass->lDAPDisplayName) == 0) {
+ DLIST_ADD_END(parent_class, current, struct class_list *);
+ } else {
+ DLIST_ADD_END(unsorted, current, struct class_list *);
+ }
+ }
+
+ if (parent_class == NULL) {
+ current = talloc(mem_ctx, struct class_list);
+ current->objectclass = dsdb_class_by_lDAPDisplayName(schema, "top");
+ DLIST_ADD_END(parent_class, current, struct class_list *);
+ }
+
+ /* For each object: find parent chain */
+ for (current = unsorted; schema && current; current = current->next) {
+ for (poss_parent = unsorted; poss_parent; poss_parent = poss_parent->next) {
+ if (ldb_attr_cmp(poss_parent->objectclass->lDAPDisplayName, current->objectclass->subClassOf) == 0) {
+ break;
+ }
+ }
+ /* If we didn't get to the end of the list, we need to add this parent */
+ if (poss_parent || (ldb_attr_cmp("top", current->objectclass->subClassOf) == 0)) {
+ continue;
+ }
+
+ new_parent = talloc(mem_ctx, struct class_list);
+ new_parent->objectclass = dsdb_class_by_lDAPDisplayName(schema, current->objectclass->subClassOf);
+ DLIST_ADD_END(unsorted, new_parent, struct class_list *);
+ }
+
+ /* DEBUGGING aid: how many layers are we down now? */
+ layer = 0;
+ do {
+ layer++;
+ /* Find all the subclasses of classes in the
+ * parent_classes. Push them onto the subclass list */
+
+ /* Ensure we don't bother if there are no unsorted entries left */
+ for (current = parent_class; schema && unsorted && current; current = current->next) {
+ /* Walk the list of possible subclasses in unsorted */
+ for (poss_subclass = unsorted; poss_subclass; ) {
+ struct class_list *next;
+
+ /* Save the next pointer, as the DLIST_ macros will change poss_subclass->next */
+ next = poss_subclass->next;
+
+ if (ldb_attr_cmp(poss_subclass->objectclass->subClassOf, current->objectclass->lDAPDisplayName) == 0) {
+ DLIST_REMOVE(unsorted, poss_subclass);
+ DLIST_ADD(subclass, poss_subclass);
+
+ break;
+ }
+ poss_subclass = next;
+ }
+ }
+
+ /* Now push the parent_classes as sorted, we are done with
+ these. Add to the END of the list by concatenation */
+ DLIST_CONCATENATE(sorted, parent_class, struct class_list *);
+
+ /* and now find subclasses of these */
+ parent_class = subclass;
+ subclass = NULL;
+
+ /* If we didn't find any subclasses we will fall out
+ * the bottom here */
+ } while (parent_class);
+
+ if (!unsorted) {
+ *sorted_out = sorted;
+ return LDB_SUCCESS;
+ }
+
+ if (!schema) {
+ /* If we don't have schema yet, then just merge the lists again */
+ DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
+ *sorted_out = sorted;
+ return LDB_SUCCESS;
+ }
+
+ /* This shouldn't happen, and would break MMC, perhaps there
+ * was no 'top', a conflict in the objectClasses or some other
+ * schema error?
+ */
+ ldb_asprintf_errstring(module->ldb, "objectclass %s is not a valid objectClass in objectClass chain", unsorted->objectclass->lDAPDisplayName);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+}
+
+static DATA_BLOB *get_sd(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ const struct dsdb_class *objectclass)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB *linear_sd;
+ struct auth_session_info *session_info
+ = ldb_get_opaque(module->ldb, "sessionInfo");
+ struct security_descriptor *sd;
+ struct dom_sid *domain_sid = samdb_domain_sid(module->ldb);
+
+ if (!objectclass->defaultSecurityDescriptor || !domain_sid) {
+ return NULL;
+ }
+
+ sd = sddl_decode(mem_ctx,
+ objectclass->defaultSecurityDescriptor,
+ domain_sid);
+
+ if (!sd || !session_info || !session_info->security_token) {
+ return NULL;
+ }
+
+ sd->owner_sid = session_info->security_token->user_sid;
+ sd->group_sid = session_info->security_token->group_sid;
+
+ linear_sd = talloc(mem_ctx, DATA_BLOB);
+ if (!linear_sd) {
+ return NULL;
+ }
+
+ ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ sd,
+ (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NULL;
+ }
+
+ return linear_sd;
+
+}
+
+static int get_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct oc_context *ac;
+
+ ac = talloc_get_type(context, struct oc_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_res = talloc_move(ac, &ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* Fix up the DN to be in the standard form, taking particular care to match the parent DN
+
+ This should mean that if the parent is:
+ CN=Users,DC=samba,DC=example,DC=com
+ and a proposed child is
+ cn=Admins ,cn=USERS,dc=Samba,dc=example,dc=COM
+
+ The resulting DN should be:
+
+ CN=Admins,CN=Users,DC=samba,DC=example,DC=com
+
+ */
+static int fix_dn(TALLOC_CTX *mem_ctx,
+ struct ldb_dn *newdn, struct ldb_dn *parent_dn,
+ struct ldb_dn **fixed_dn)
+{
+ char *upper_rdn_attr;
+ /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ *fixed_dn = ldb_dn_copy(mem_ctx, parent_dn);
+
+ /* We need the attribute name in upper case */
+ upper_rdn_attr = strupper_talloc(*fixed_dn,
+ ldb_dn_get_rdn_name(newdn));
+ if (!upper_rdn_attr) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Create a new child */
+ if (ldb_dn_add_child_fmt(*fixed_dn, "X=X") == false) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* And replace it with CN=foo (we need the attribute in upper case */
+ return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr,
+ *ldb_dn_get_rdn_val(newdn));
+}
+
+/* Fix all attribute names to be in the correct case, and check they are all valid per the schema */
+static int fix_attributes(struct ldb_context *ldb, const struct dsdb_schema *schema, struct ldb_message *msg)
+{
+ int i;
+ for (i=0; i < msg->num_elements; i++) {
+ const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, msg->elements[i].name);
+ if (!attribute) {
+ ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name);
+ return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
+ }
+ msg->elements[i].name = attribute->lDAPDisplayName;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
+{
+
+ static const char * const attrs[] = { NULL };
+
+ struct ldb_handle *h;
+ struct oc_context *ac;
+ struct ldb_dn *parent_dn;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_add\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Need to object to this, but cn=rootdse doesn't hae an objectClass... */
+ if (ldb_msg_find_element(req->op.add.message,
+ "objectClass") == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ h = oc_init_handle(req, module);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ /* If there isn't a parent, just go on to the add processing */
+ if (ldb_dn_get_comp_num(ac->orig_req->op.add.message->dn) == 1) {
+ return objectclass_do_add(h);
+ }
+
+ parent_dn = ldb_dn_get_parent(ac, ac->orig_req->op.add.message->dn);
+ if (parent_dn == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&ac->search_req, module->ldb,
+ ac, parent_dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(ac->search_req, parent_dn);
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = OC_SEARCH_ADD_PARENT;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_add(struct ldb_handle *h)
+{
+ const struct dsdb_schema *schema;
+ struct oc_context *ac;
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ TALLOC_CTX *mem_ctx;
+ struct class_list *sorted, *current;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+ schema = dsdb_get_schema(ac->module->ldb);
+
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->add_req = talloc(ac, struct ldb_request);
+ if (ac->add_req == NULL) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *ac->add_req = *ac->orig_req;
+
+ ac->add_req->op.add.message = msg = ldb_msg_copy_shallow(ac->add_req, ac->orig_req->op.add.message);
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->add_req);
+
+ /* Check we have a valid parent */
+ if (ac->search_res == NULL) {
+ if (ldb_dn_compare(ldb_get_root_basedn(ac->module->ldb), ac->orig_req->op.add.message->dn) == 0) {
+ /* Allow the tree to be started */
+
+ /* but don't keep any error string, it's meaningless */
+ ldb_set_errstring(ac->module->ldb, NULL);
+ } else {
+ ldb_asprintf_errstring(ac->module->ldb, "objectclass: Cannot add %s, parent does not exist!",
+ ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else {
+
+ /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ ret = fix_dn(msg,
+ ac->orig_req->op.add.message->dn,
+ ac->search_res->message->dn,
+ &msg->dn);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ac->module->ldb, "Could not munge DN %s into normal form",
+ ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+ return ret;
+ }
+
+ /* TODO: Check this is a valid child to this parent,
+ * by reading the allowedChildClasses and
+ * allowedChildClasssesEffective attributes */
+
+ }
+
+ if (schema) {
+ ret = fix_attributes(ac->module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* This is now the objectClass list from the database */
+ objectclass_element = ldb_msg_find_element(msg, "objectClass");
+
+ if (!objectclass_element) {
+ /* Where did it go? bail now... */
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = objectclass_sort(ac->module, schema, msg, mem_ctx, objectclass_element, &sorted);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ldb_msg_remove_attr(msg, "objectClass");
+ ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* We must completely replace the existing objectClass entry,
+ * because we need it sorted */
+
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ac->module->ldb,
+ "objectclass: could not re-add sorted "
+ "objectclass to modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ /* Last one is the critical one */
+ if (!current->next) {
+ struct ldb_message_element *el;
+ int32_t systemFlags = 0;
+ if (!ldb_msg_find_element(msg, "objectCategory")) {
+ ldb_msg_add_string(msg, "objectCategory",
+ current->objectclass->defaultObjectCategory);
+ }
+ if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly") && (current->objectclass->defaultHidingValue == true)) {
+ ldb_msg_add_string(msg, "showInAdvancedViewOnly",
+ "TRUE");
+ }
+ if (!ldb_msg_find_element(msg, "nTSecurityDescriptor")) {
+ DATA_BLOB *sd = get_sd(ac->module, mem_ctx, current->objectclass);
+ if (sd) {
+ ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd);
+ }
+ }
+
+ /* There are very special rules for systemFlags, see MS-ADTS 3.1.1.5.2.4 */
+ el = ldb_msg_find_element(msg, "systemFlags");
+
+ systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
+
+ if (el) {
+ /* Only these flags may be set by a client, but we can't tell between a client and our provision at this point */
+ /* systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_MOVE | SYSTEM_FLAG_CONFIG_LIMITED_MOVE); */
+ ldb_msg_remove_element(msg, el);
+ }
+
+ /* This flag is only allowed on attributeSchema objects */
+ if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "attributeSchema") == 0) {
+ systemFlags &= ~SYSTEM_FLAG_ATTR_IS_RDN;
+ }
+
+ if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "server") == 0) {
+ systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE | SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE);
+ } else if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "site") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "serverContainer") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "ntDSDSA") == 0) {
+ systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
+
+ } else if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "siteLink") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "siteLinkBridge") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "nTDSConnection") == 0) {
+ systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
+ }
+
+ /* TODO: If parent object is site or subnet, also add (SYSTEM_FLAG_CONFIG_ALLOW_RENAME) */
+
+ if (el || systemFlags != 0) {
+ samdb_msg_add_int(ac->module->ldb, msg, msg, "systemFlags", systemFlags);
+ }
+ }
+ }
+ }
+
+ talloc_free(mem_ctx);
+ ret = ldb_msg_sanity_check(ac->module->ldb, msg);
+
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = OC_DO_ADD;
+
+ /* perform the add */
+ return ldb_next_request(ac->module, ac->add_req);
+}
+
+static int objectclass_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_modify\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Without schema, there isn't much to do here */
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+ objectclass_element = ldb_msg_find_element(req->op.mod.message, "objectClass");
+
+ /* If no part of this touches the objectClass, then we don't
+ * need to make any changes. */
+
+ /* If the only operation is the deletion of the objectClass
+ * then go on with just fixing the attribute case */
+ if (!objectclass_element) {
+ struct ldb_request *down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of memory!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req; /* copy the request */
+
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+
+ if (down_req->op.mod.message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fix_attributes(module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+ }
+
+ switch (objectclass_element->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_DELETE:
+ if (objectclass_element->num_values == 0) {
+ return LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED;
+ }
+ break;
+ case LDB_FLAG_MOD_REPLACE:
+ {
+ struct ldb_request *down_req;
+ struct class_list *sorted, *current;
+ TALLOC_CTX *mem_ctx;
+ mem_ctx = talloc_new(req);
+ if (mem_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* prepare the first operation */
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of memory!");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req; /* copy the request */
+
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+
+ if (down_req->op.mod.message == NULL) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fix_attributes(module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ret = objectclass_sort(module, schema, msg, mem_ctx, objectclass_element, &sorted);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* We must completely replace the existing objectClass entry,
+ * because we need it sorted */
+
+ ldb_msg_remove_attr(msg, "objectClass");
+ ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(module->ldb, "objectclass: could not re-add sorted objectclass to modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ talloc_free(mem_ctx);
+
+ ret = ldb_msg_sanity_check(module->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+ }
+ }
+
+ /* This isn't the default branch of the switch, but a 'in any
+ * other case'. When a delete isn't for all objectClasses for
+ * example
+ */
+ {
+ struct ldb_handle *h;
+ struct oc_context *ac;
+
+ h = oc_init_handle(req, module);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ /* prepare the first operation */
+ ac->down_req = talloc(ac, struct ldb_request);
+ if (ac->down_req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->down_req) = *req; /* copy the request */
+
+ ac->down_req->op.mod.message = msg = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
+
+ if (ac->down_req->op.mod.message == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fix_attributes(ac->module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ac->module->ldb);
+ return ret;
+ }
+
+ ac->down_req->context = NULL;
+ ac->down_req->callback = NULL;
+ ldb_set_timeout_from_prev_req(module->ldb, req, ac->down_req);
+
+ ac->step = OC_DO_REQ;
+
+ return ldb_next_request(module, ac->down_req);
+ }
+}
+
+static int objectclass_search_self(struct ldb_handle *h)
+{
+ int ret;
+ struct oc_context *ac;
+ static const char * const attrs[] = { "objectClass", NULL };
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ ret = ldb_build_search_req(&ac->search_req, ac->module->ldb,
+ ac, ac->orig_req->op.mod.message->dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = OC_SEARCH_SELF;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_mod(struct ldb_handle *h) {
+
+ const struct dsdb_schema *schema;
+ struct oc_context *ac;
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ TALLOC_CTX *mem_ctx;
+ struct class_list *sorted, *current;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+ schema = dsdb_get_schema(ac->module->ldb);
+
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->mod_req = talloc(ac, struct ldb_request);
+ if (ac->mod_req == NULL) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->mod_req->operation = LDB_MODIFY;
+ ac->mod_req->controls = NULL;
+ ac->mod_req->context = ac;
+ ac->mod_req->callback = NULL;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->mod_req);
+
+ /* use a new message structure */
+ ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
+ if (msg == NULL) {
+ ldb_set_errstring(ac->module->ldb, "objectclass: could not create new modify msg");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* This is now the objectClass list from the database */
+ objectclass_element = ldb_msg_find_element(ac->search_res->message,
+ "objectClass");
+ if (!objectclass_element) {
+ /* Where did it go? bail now... */
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* modify dn */
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ ret = objectclass_sort(ac->module, schema, msg, mem_ctx, objectclass_element, &sorted);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* We must completely replace the existing objectClass entry.
+ * We could do a constrained add/del, but we are meant to be
+ * in a transaction... */
+
+ ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ac->module->ldb, "objectclass: could not clear objectclass in modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ac->module->ldb, "objectclass: could not re-add sorted objectclass to modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ ret = ldb_msg_sanity_check(ac->module->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = OC_DO_MOD;
+
+ talloc_free(mem_ctx);
+ /* perform the search */
+ return ldb_next_request(ac->module, ac->mod_req);
+}
+
+static int objectclass_rename(struct ldb_module *module, struct ldb_request *req)
+{
+
+ static const char * const attrs[] = { NULL };
+
+ struct ldb_handle *h;
+ struct oc_context *ac;
+ struct ldb_dn *parent_dn;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
+
+ if (ldb_dn_is_special(req->op.rename.newdn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* Firstly ensure we are not trying to rename it to be a child of itself */
+ if ((ldb_dn_compare_base(req->op.rename.olddn, req->op.rename.newdn) == 0)
+ && (ldb_dn_compare(req->op.rename.olddn, req->op.rename.newdn) != 0)) {
+ ldb_asprintf_errstring(module->ldb, "Cannot rename %s to be a child of itself",
+ ldb_dn_get_linearized(req->op.rename.olddn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ h = oc_init_handle(req, module);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ parent_dn = ldb_dn_get_parent(ac, ac->orig_req->op.rename.newdn);
+ if (parent_dn == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_build_search_req(&ac->search_req, module->ldb,
+ ac, parent_dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal(ac->search_req, parent_dn);
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = OC_SEARCH_RENAME_PARENT;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_rename(struct ldb_handle *h)
+{
+ struct oc_context *ac;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ ac->rename_req = talloc(ac, struct ldb_request);
+ if (ac->rename_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *ac->rename_req = *ac->orig_req;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->rename_req);
+
+ /* Check we have a valid parent */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ac->module->ldb, "objectclass: Cannot rename %s, parent does not exist!",
+ ldb_dn_get_linearized(ac->orig_req->op.rename.newdn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ ret = fix_dn(ac->rename_req,
+ ac->orig_req->op.rename.newdn,
+ ac->search_res->message->dn,
+ &ac->rename_req->op.rename.newdn);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* TODO: Check this is a valid child to this parent,
+ * by reading the allowedChildClasses and
+ * allowedChildClasssesEffective attributes */
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = OC_DO_RENAME;
+
+ /* perform the rename */
+ return ldb_next_request(ac->module, ac->rename_req);
+}
+
+static int oc_wait(struct ldb_handle *handle) {
+ struct oc_context *ac;
+ int ret;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct oc_context);
+
+ switch (ac->step) {
+ case OC_DO_REQ:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* mods done, go on */
+ return objectclass_search_self(handle);
+
+ case OC_SEARCH_SELF:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* self search done, go on */
+ return objectclass_do_mod(handle);
+
+ case OC_DO_MOD:
+ ret = ldb_wait(ac->mod_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->mod_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->mod_req->handle->status;
+ goto done;
+ }
+
+ if (ac->mod_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case OC_SEARCH_ADD_PARENT:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS
+ && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* parent search done, go on */
+ return objectclass_do_add(handle);
+
+ case OC_DO_ADD:
+ ret = ldb_wait(ac->add_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->add_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->add_req->handle->status;
+ goto done;
+ }
+
+ if (ac->add_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case OC_SEARCH_RENAME_PARENT:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* parent search done, go on */
+ return objectclass_do_rename(handle);
+
+ case OC_DO_RENAME:
+ ret = ldb_wait(ac->rename_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->rename_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->rename_req->handle->status;
+ goto done;
+ }
+
+ if (ac->rename_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int oc_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = oc_wait(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int objectclass_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return oc_wait_all(handle);
+ } else {
+ return oc_wait(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = {
+ .name = "objectclass",
+ .add = objectclass_add,
+ .modify = objectclass_modify,
+ .rename = objectclass_rename,
+ .wait = objectclass_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/objectguid.c b/source4/dsdb/samdb/ldb_modules/objectguid.c
new file mode 100644
index 0000000000..f62839389d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectguid.c
@@ -0,0 +1,254 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb objectguid module
+ *
+ * Description: add a unique objectGUID onto every new record
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "param/param.h"
+
+static struct ldb_message_element *objectguid_find_attribute(const struct ldb_message *msg, const char *name)
+{
+ int i;
+
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(name, msg->elements[i].name) == 0) {
+ return &msg->elements[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ add a time element to a record
+*/
+static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
+{
+ struct ldb_message_element *el;
+ char *s;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ s = ldb_timestring(msg, t);
+ if (s == NULL) {
+ return -1;
+ }
+
+ if (ldb_msg_add_string(msg, attr, s) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+/*
+ add a uint64_t element to a record
+*/
+static int add_uint64_element(struct ldb_message *msg, const char *attr, uint64_t v)
+{
+ struct ldb_message_element *el;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ if (ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)v) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+/* add_record: add objectGUID attribute */
+static int objectguid_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_request *down_req;
+ struct ldb_message_element *attribute;
+ struct ldb_message *msg;
+ struct ldb_val v;
+ struct GUID guid;
+ uint64_t seq_num;
+ enum ndr_err_code ndr_err;
+ int ret;
+ time_t t = time(NULL);
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if ((attribute = objectguid_find_attribute(req->op.add.message, "objectGUID")) != NULL ) {
+ return ldb_next_request(module, req);
+ }
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* a new GUID */
+ guid = GUID_random();
+
+ ndr_err = ndr_push_struct_blob(&v, msg,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_value(msg, "objectGUID", &v, NULL);
+ if (ret) {
+ talloc_free(down_req);
+ return ret;
+ }
+
+ if (add_time_element(msg, "whenCreated", t) != 0 ||
+ add_time_element(msg, "whenChanged", t) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(msg, "uSNCreated", seq_num) != 0 ||
+ add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+/* modify_record: update timestamps */
+static int objectguid_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ int ret;
+ time_t t = time(NULL);
+ uint64_t seq_num;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (add_time_element(msg, "whenChanged", t) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_objectguid_module_ops = {
+ .name = "objectguid",
+ .add = objectguid_add,
+ .modify = objectguid_modify,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/partition.c b/source4/dsdb/samdb/ldb_modules/partition.c
new file mode 100644
index 0000000000..9cae6ab7b5
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/partition.c
@@ -0,0 +1,1101 @@
+
+/*
+ Partitions ldb module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ * NOTICE: this module is NOT released under the GNU LGPL license as
+ * other ldb code. This module is release under the GNU GPL v3 or
+ * later license.
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb partitions module
+ *
+ * Description: Implement LDAP partitions
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "dsdb/samdb/samdb.h"
+
+struct partition_private_data {
+ struct dsdb_control_current_partition **partitions;
+ struct ldb_dn **replicate;
+};
+
+struct partition_context {
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+
+ struct ldb_request **down_req;
+ int num_requests;
+ int finished_requests;
+};
+
+static struct partition_context *partition_init_handle(struct ldb_request *req, struct ldb_module *module)
+{
+ struct partition_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct partition_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->orig_req = req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+static struct ldb_module *make_module_for_next_request(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct ldb_module *module)
+{
+ struct ldb_module *current;
+ static const struct ldb_module_ops ops; /* zero */
+ current = talloc_zero(mem_ctx, struct ldb_module);
+ if (current == NULL) {
+ return module;
+ }
+
+ current->ldb = ldb;
+ current->ops = &ops;
+ current->prev = NULL;
+ current->next = module;
+ return current;
+}
+
+static struct dsdb_control_current_partition *find_partition(struct partition_private_data *data,
+ struct ldb_dn *dn)
+{
+ int i;
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if (ldb_dn_compare_base(data->partitions[i]->dn, dn) == 0) {
+ return data->partitions[i];
+ }
+ }
+
+ return NULL;
+};
+
+/**
+ * fire the caller's callback for every entry, but only send 'done' once.
+ */
+static int partition_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct partition_context *ac;
+
+ ac = talloc_get_type(context, struct partition_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ } else {
+ ac->finished_requests++;
+ if (ac->finished_requests == ac->num_requests) {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+ }
+}
+
+/**
+ * only fire the 'last' callback, and only for START-TLS for now
+ */
+static int partition_other_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct partition_context *ac;
+
+ ac = talloc_get_type(context, struct partition_context);
+
+ if (!ac->orig_req->callback) {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ if (!ares
+ || (ares->type == LDB_REPLY_EXTENDED
+ && strcmp(ares->response->oid, LDB_EXTENDED_START_TLS_OID))) {
+ ac->finished_requests++;
+ if (ac->finished_requests == ac->num_requests) {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+ ldb_set_errstring(ldb, "partition_other_callback: Unknown reply type, only supports START_TLS");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+
+static int partition_send_request(struct partition_context *ac,
+ struct dsdb_control_current_partition *partition)
+{
+ int ret;
+ struct ldb_module *backend;
+ struct ldb_request *req;
+
+ if (partition) {
+ backend = make_module_for_next_request(ac, ac->module->ldb, partition->module);
+ } else {
+ backend = ac->module;
+ }
+
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ req = ac->down_req[ac->num_requests] = talloc(ac, struct ldb_request);
+ if (req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *req = *ac->orig_req; /* copy the request */
+
+ if (req->controls) {
+ req->controls
+ = talloc_memdup(req,
+ ac->orig_req->controls, talloc_get_size(ac->orig_req->controls));
+ if (req->controls == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ if (req->operation == LDB_SEARCH) {
+ /* If the search is for 'more' than this partition,
+ * then change the basedn, so a remote LDAP server
+ * doesn't object */
+ if (partition) {
+ if (ldb_dn_compare_base(partition->dn, req->op.search.base) != 0) {
+ req->op.search.base = partition->dn;
+ }
+ } else {
+ req->op.search.base = NULL;
+ }
+ req->callback = partition_search_callback;
+ req->context = ac;
+ } else {
+ req->callback = partition_other_callback;
+ req->context = ac;
+ }
+
+ if (partition) {
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Spray off search requests the backend */
+ ret = ldb_next_request(backend, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->num_requests++;
+ return LDB_SUCCESS;
+}
+
+/**
+ * Send a request down to all the partitions
+ */
+static int partition_send_all(struct ldb_module *module,
+ struct partition_context *ac,
+ struct ldb_request *req)
+{
+ int i;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ int ret = partition_send_request(ac, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ ret = partition_send_request(ac, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/**
+ * Figure out which backend a request needs to be aimed at. Some
+ * requests must be replicated to all backends
+ */
+static int partition_replicate(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
+{
+ unsigned i;
+ int ret;
+ struct dsdb_control_current_partition *partition;
+ struct ldb_module *backend;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+
+ if (req->operation != LDB_SEARCH) {
+ /* Is this a special DN, we need to replicate to every backend? */
+ for (i=0; data->replicate && data->replicate[i]; i++) {
+ if (ldb_dn_compare(data->replicate[i],
+ dn) == 0) {
+ struct partition_context *ac;
+
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return partition_send_all(module, ac, req);
+ }
+ }
+ }
+
+ /* Otherwise, we need to find the partition to fire it to */
+
+ /* Find partition */
+ partition = find_partition(data, dn);
+ if (!partition) {
+ /*
+ * if we haven't found a matching partition
+ * pass the request to the main ldb
+ *
+ * TODO: we should maybe return an error here
+ * if it's not a special dn
+ */
+
+ return ldb_next_request(module, req);
+ }
+
+ backend = make_module_for_next_request(req, module->ldb, partition->module);
+ if (!backend) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* issue request */
+ return ldb_next_request(backend, req);
+}
+
+/* search */
+static int partition_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control **saved_controls;
+
+ /* Find backend */
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ /* issue request */
+
+ /* (later) consider if we should be searching multiple
+ * partitions (for 'invisible' partition behaviour */
+ struct ldb_control *search_control = ldb_request_get_control(req, LDB_CONTROL_SEARCH_OPTIONS_OID);
+ struct ldb_control *domain_scope_control = ldb_request_get_control(req, LDB_CONTROL_DOMAIN_SCOPE_OID);
+
+ struct ldb_search_options_control *search_options = NULL;
+ if (search_control) {
+ search_options = talloc_get_type(search_control->data, struct ldb_search_options_control);
+ }
+
+ /* Remove the domain_scope control, so we don't confuse a backend server */
+ if (domain_scope_control && !save_controls(domain_scope_control, req, &saved_controls)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* TODO:
+ Generate referrals (look for a partition under this DN) if we don't have the above control specified
+ */
+
+ if (search_options && (search_options->search_options & LDB_SEARCH_OPTION_PHANTOM_ROOT)) {
+ int ret, i;
+ struct partition_context *ac;
+ if ((search_options->search_options & ~LDB_SEARCH_OPTION_PHANTOM_ROOT) == 0) {
+ /* We have processed this flag, so we are done with this control now */
+
+ /* Remove search control, so we don't confuse a backend server */
+ if (search_control && !save_controls(search_control, req, &saved_controls)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Search from the base DN */
+ if (!req->op.search.base || ldb_dn_is_null(req->op.search.base)) {
+ return partition_send_all(module, ac, req);
+ }
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ /* Find all partitions under the search base */
+ if (ldb_dn_compare_base(req->op.search.base, data->partitions[i]->dn) == 0) {
+ ret = partition_send_request(ac, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* Perhaps we didn't match any partitions. Try the main partition, only */
+ if (ac->num_requests == 0) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ return LDB_SUCCESS;
+ } else {
+ /* Handle this like all other requests */
+ if (search_control && (search_options->search_options & ~LDB_SEARCH_OPTION_PHANTOM_ROOT) == 0) {
+ /* We have processed this flag, so we are done with this control now */
+
+ /* Remove search control, so we don't confuse a backend server */
+ if (search_control && !save_controls(search_control, req, &saved_controls)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ return partition_replicate(module, req, req->op.search.base);
+ }
+}
+
+/* add */
+static int partition_add(struct ldb_module *module, struct ldb_request *req)
+{
+ return partition_replicate(module, req, req->op.add.message->dn);
+}
+
+/* modify */
+static int partition_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ return partition_replicate(module, req, req->op.mod.message->dn);
+}
+
+/* delete */
+static int partition_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ return partition_replicate(module, req, req->op.del.dn);
+}
+
+/* rename */
+static int partition_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ int i, matched = -1;
+ /* Find backend */
+ struct dsdb_control_current_partition *backend, *backend2;
+
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ if (!data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ backend = find_partition(data, req->op.rename.olddn);
+ backend2 = find_partition(data, req->op.rename.newdn);
+
+ if ((backend && !backend2) || (!backend && backend2)) {
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ if (backend != backend2) {
+ ldb_asprintf_errstring(module->ldb,
+ "Cannot rename from %s in %s to %s in %s: %s",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ ldb_dn_get_linearized(backend->dn),
+ ldb_dn_get_linearized(req->op.rename.newdn),
+ ldb_dn_get_linearized(backend2->dn),
+ ldb_strerror(LDB_ERR_AFFECTS_MULTIPLE_DSAS));
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if (ldb_dn_compare_base(req->op.rename.olddn, data->partitions[i]->dn) == 0) {
+ matched = i;
+ }
+ }
+
+ if (matched > 0) {
+ ldb_asprintf_errstring(module->ldb,
+ "Cannot rename from %s to %s, subtree rename would cross partition %s: %s",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ ldb_dn_get_linearized(req->op.rename.newdn),
+ ldb_dn_get_linearized(data->partitions[matched]->dn),
+ ldb_strerror(LDB_ERR_AFFECTS_MULTIPLE_DSAS));
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ return partition_replicate(module, req, req->op.rename.olddn);
+}
+
+/* start a transaction */
+static int partition_start_trans(struct ldb_module *module)
+{
+ int i, ret;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_start_trans(next);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ /* Back it out, if it fails on one */
+ for (i--; i >= 0; i--) {
+ next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+ ldb_next_del_trans(next);
+ talloc_free(next);
+ }
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/* end a transaction */
+static int partition_end_trans(struct ldb_module *module)
+{
+ int i, ret, ret2 = LDB_SUCCESS;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ ret = ldb_next_end_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_end_trans(next);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ ret2 = ret;
+ }
+ }
+
+ if (ret != LDB_SUCCESS) {
+ /* Back it out, if it fails on one */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+ ldb_next_del_trans(next);
+ talloc_free(next);
+ }
+ }
+ return ret;
+}
+
+/* delete a transaction */
+static int partition_del_trans(struct ldb_module *module)
+{
+ int i, ret, ret2 = LDB_SUCCESS;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ ret = ldb_next_del_trans(module);
+ if (ret != LDB_SUCCESS) {
+ ret2 = ret;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_del_trans(next);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ ret2 = ret;
+ }
+ }
+ return ret2;
+}
+
+static int partition_sequence_number(struct ldb_module *module, struct ldb_request *req)
+{
+ int i, ret;
+ uint64_t seq_number = 0;
+ uint64_t timestamp_sequence = 0;
+ uint64_t timestamp = 0;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+
+ switch (req->op.seq_num.type) {
+ case LDB_SEQ_NEXT:
+ case LDB_SEQ_HIGHEST_SEQ:
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (req->op.seq_num.flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
+ timestamp_sequence = req->op.seq_num.seq_num;
+ } else {
+ seq_number = seq_number + req->op.seq_num.seq_num;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_next_request(next, req);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (req->op.seq_num.flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
+ timestamp_sequence = MAX(timestamp_sequence, req->op.seq_num.seq_num);
+ } else {
+ seq_number = seq_number + req->op.seq_num.seq_num;
+ }
+ }
+ /* fall though */
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ {
+ struct ldb_request *date_req = talloc(req, struct ldb_request);
+ if (!date_req) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *date_req = *req;
+ date_req->op.seq_num.flags = LDB_SEQ_HIGHEST_TIMESTAMP;
+
+ ret = ldb_next_request(module, date_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ timestamp = date_req->op.seq_num.seq_num;
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_request(next, date_req);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ timestamp = MAX(timestamp, date_req->op.seq_num.seq_num);
+ }
+ break;
+ }
+ }
+
+ switch (req->op.seq_num.flags) {
+ case LDB_SEQ_NEXT:
+ case LDB_SEQ_HIGHEST_SEQ:
+
+ req->op.seq_num.flags = 0;
+
+ /* Has someone above set a timebase sequence? */
+ if (timestamp_sequence) {
+ req->op.seq_num.seq_num = (((unsigned long long)timestamp << 24) | (seq_number & 0xFFFFFF));
+ } else {
+ req->op.seq_num.seq_num = seq_number;
+ }
+
+ if (timestamp_sequence > req->op.seq_num.seq_num) {
+ req->op.seq_num.seq_num = timestamp_sequence;
+ req->op.seq_num.flags |= LDB_SEQ_TIMESTAMP_SEQUENCE;
+ }
+
+ req->op.seq_num.flags |= LDB_SEQ_GLOBAL_SEQUENCE;
+ break;
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ req->op.seq_num.seq_num = timestamp;
+ break;
+ }
+
+ switch (req->op.seq_num.flags) {
+ case LDB_SEQ_NEXT:
+ req->op.seq_num.seq_num++;
+ }
+ return LDB_SUCCESS;
+}
+
+static int partition_extended_replicated_objects(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_extended_replicated_objects *ext;
+
+ ext = talloc_get_type(req->op.extended.data, struct dsdb_extended_replicated_objects);
+ if (!ext) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "partition_extended_replicated_objects: invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ if (ext->version != DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "partition_extended_replicated_objects: extended data invalid version [%u != %u]\n",
+ ext->version, DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ return partition_replicate(module, req, ext->partition_dn);
+}
+
+static int partition_extended_schema_update_now(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_control_current_partition *partition;
+ struct partition_private_data *data;
+ struct ldb_dn *schema_dn;
+ struct partition_context *ac;
+ struct ldb_module *backend;
+ int ret;
+
+ schema_dn = talloc_get_type(req->op.extended.data, struct ldb_dn);
+ if (!schema_dn) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "partition_extended: invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ data = talloc_get_type(module->private_data, struct partition_private_data);
+ if (!data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ partition = find_partition( data, schema_dn );
+ if (!partition) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ backend = make_module_for_next_request(req, module->ldb, partition->module);
+ if (!backend) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(backend, req);
+}
+
+
+/* extended */
+static int partition_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct partition_context *ac;
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_REPLICATED_OBJECTS_OID) == 0) {
+ return partition_extended_replicated_objects(module, req);
+ }
+
+ /* forward schemaUpdateNow operation to schema_fsmo module*/
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) == 0) {
+ return partition_extended_schema_update_now( module, req );
+ }
+
+ /*
+ * as the extended operation has no dn
+ * we need to send it to all partitions
+ */
+
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return partition_send_all(module, ac, req);
+}
+
+static int sort_compare(void *void1,
+ void *void2, void *opaque)
+{
+ struct dsdb_control_current_partition **pp1 =
+ (struct dsdb_control_current_partition **)void1;
+ struct dsdb_control_current_partition **pp2 =
+ (struct dsdb_control_current_partition **)void2;
+ struct dsdb_control_current_partition *partition1 = talloc_get_type(*pp1,
+ struct dsdb_control_current_partition);
+ struct dsdb_control_current_partition *partition2 = talloc_get_type(*pp2,
+ struct dsdb_control_current_partition);
+
+ return ldb_dn_compare(partition1->dn, partition2->dn);
+}
+
+static int partition_init(struct ldb_module *module)
+{
+ int ret, i;
+ TALLOC_CTX *mem_ctx = talloc_new(module);
+ const char *attrs[] = { "partition", "replicateEntries", "modules", NULL };
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ldb_message_element *partition_attributes;
+ struct ldb_message_element *replicate_attributes;
+ struct ldb_message_element *modules_attributes;
+
+ struct partition_private_data *data;
+
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data = talloc(mem_ctx, struct partition_private_data);
+ if (data == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(module->ldb, ldb_dn_new(mem_ctx, module->ldb, "@PARTITION"),
+ LDB_SCOPE_BASE,
+ NULL, attrs,
+ &res);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, res);
+ if (res->count == 0) {
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ if (res->count > 1) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ msg = res->msgs[0];
+
+ partition_attributes = ldb_msg_find_element(msg, "partition");
+ if (!partition_attributes) {
+ ldb_set_errstring(module->ldb, "partition_init: no partitions specified");
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ data->partitions = talloc_array(data, struct dsdb_control_current_partition *, partition_attributes->num_values + 1);
+ if (!data->partitions) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i=0; i < partition_attributes->num_values; i++) {
+ char *base = talloc_strdup(data->partitions, (char *)partition_attributes->values[i].data);
+ char *p = strchr(base, ':');
+ if (!p) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition record (missing ':'): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ p[0] = '\0';
+ p++;
+ if (!p[0]) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition record (missing backend database): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ data->partitions[i] = talloc(data->partitions, struct dsdb_control_current_partition);
+ if (!data->partitions[i]) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ data->partitions[i]->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION;
+
+ data->partitions[i]->dn = ldb_dn_new(data->partitions[i], module->ldb, base);
+ if (!data->partitions[i]->dn) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: invalid DN in partition record: %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ data->partitions[i]->backend = samdb_relative_path(module->ldb,
+ data->partitions[i],
+ p);
+ if (!data->partitions[i]->backend) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: unable to determine an relative path for partition: %s", base);
+ talloc_free(mem_ctx);
+ }
+ ret = ldb_connect_backend(module->ldb, data->partitions[i]->backend, NULL, &data->partitions[i]->module);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ data->partitions[i] = NULL;
+
+ /* sort these into order, most to least specific */
+ ldb_qsort(data->partitions, partition_attributes->num_values, sizeof(*data->partitions),
+ module->ldb, sort_compare);
+
+ for (i=0; data->partitions[i]; i++) {
+ struct ldb_request *req;
+ req = talloc_zero(mem_ctx, struct ldb_request);
+ if (req == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "partition: Out of memory!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ req->operation = LDB_REQ_REGISTER_PARTITION;
+ req->op.reg_partition.dn = data->partitions[i]->dn;
+
+ ret = ldb_request(module->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "partition: Unable to register partition with rootdse!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OTHER;
+ }
+ talloc_free(req);
+ }
+
+ replicate_attributes = ldb_msg_find_element(msg, "replicateEntries");
+ if (!replicate_attributes) {
+ data->replicate = NULL;
+ } else {
+ data->replicate = talloc_array(data, struct ldb_dn *, replicate_attributes->num_values + 1);
+ if (!data->replicate) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < replicate_attributes->num_values; i++) {
+ data->replicate[i] = ldb_dn_from_ldb_val(data->replicate, module->ldb, &replicate_attributes->values[i]);
+ if (!ldb_dn_validate(data->replicate[i])) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid DN in partition replicate record: %s",
+ replicate_attributes->values[i].data);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ data->replicate[i] = NULL;
+ }
+
+ /* Make the private data available to any searches the modules may trigger in initialisation */
+ module->private_data = data;
+ talloc_steal(module, data);
+
+ modules_attributes = ldb_msg_find_element(msg, "modules");
+ if (modules_attributes) {
+ for (i=0; i < modules_attributes->num_values; i++) {
+ struct ldb_dn *base_dn;
+ int partition_idx;
+ struct dsdb_control_current_partition *partition = NULL;
+ const char **modules = NULL;
+
+ char *base = talloc_strdup(data->partitions, (char *)modules_attributes->values[i].data);
+ char *p = strchr(base, ':');
+ if (!p) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition module record (missing ':'): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ p[0] = '\0';
+ p++;
+ if (!p[0]) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition module record (missing backend database): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ modules = ldb_modules_list_from_string(module->ldb, mem_ctx,
+ p);
+
+ base_dn = ldb_dn_new(mem_ctx, module->ldb, base);
+ if (!ldb_dn_validate(base_dn)) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (partition_idx = 0; data->partitions[partition_idx]; partition_idx++) {
+ if (ldb_dn_compare(data->partitions[partition_idx]->dn, base_dn) == 0) {
+ partition = data->partitions[partition_idx];
+ break;
+ }
+ }
+
+ if (!partition) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition module record (no such partition): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = ldb_load_modules_list(module->ldb, modules, partition->module, &partition->module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "loading backend for %s failed: %s",
+ base, ldb_errstring(module->ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ ret = ldb_init_module_chain(module->ldb, partition->module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "initialising backend for %s failed: %s",
+ base, ldb_errstring(module->ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ }
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+static int partition_wait_none(struct ldb_handle *handle) {
+ struct partition_context *ac;
+ int ret;
+ int i;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct partition_context);
+
+ for (i=0; i < ac->num_requests; i++) {
+ ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req[i]->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+
+static int partition_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = partition_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int partition_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return partition_wait_all(handle);
+ } else {
+ return partition_wait_none(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_partition_module_ops = {
+ .name = "partition",
+ .init_context = partition_init,
+ .search = partition_search,
+ .add = partition_add,
+ .modify = partition_modify,
+ .del = partition_delete,
+ .rename = partition_rename,
+ .extended = partition_extended,
+ .sequence_number = partition_sequence_number,
+ .start_transaction = partition_start_trans,
+ .end_transaction = partition_end_trans,
+ .del_transaction = partition_del_trans,
+ .wait = partition_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c
new file mode 100644
index 0000000000..69783aefa8
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c
@@ -0,0 +1,2281 @@
+/*
+ ldb database module
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb password_hash module
+ *
+ * Description: correctly update hash values based on changes to userPassword and friends
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_private.h"
+#include "librpc/gen_ndr/misc.h"
+#include "librpc/gen_ndr/samr.h"
+#include "libcli/auth/libcli_auth.h"
+#include "libcli/security/security.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "system/time.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.h"
+#include "dsdb/samdb/ldb_modules/password_modules.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/crypto/crypto.h"
+#include "param/param.h"
+
+/* If we have decided there is reason to work on this request, then
+ * setup all the password hash types correctly.
+ *
+ * If the administrator doesn't want the userPassword stored (set in the
+ * domain and per-account policies) then we must strip that out before
+ * we do the first operation.
+ *
+ * Once this is done (which could update anything at all), we
+ * calculate the password hashes.
+ *
+ * This function must not only update the unicodePwd, dBCSPwd and
+ * supplementalCredentials fields, it must also atomicly increment the
+ * msDS-KeyVersionNumber. We should be in a transaction, so all this
+ * should be quite safe...
+ *
+ * Finally, if the administrator has requested that a password history
+ * be maintained, then this should also be written out.
+ *
+ */
+
+struct ph_context {
+
+ enum ph_type {PH_ADD, PH_MOD} type;
+ enum ph_step {PH_ADD_SEARCH_DOM, PH_ADD_DO_ADD, PH_MOD_DO_REQ, PH_MOD_SEARCH_SELF, PH_MOD_SEARCH_DOM, PH_MOD_DO_MOD} step;
+
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *dom_req;
+ struct ldb_reply *dom_res;
+
+ struct ldb_request *down_req;
+
+ struct ldb_request *search_req;
+ struct ldb_reply *search_res;
+
+ struct ldb_request *mod_req;
+
+ struct dom_sid *domain_sid;
+};
+
+struct domain_data {
+ bool store_cleartext;
+ uint_t pwdProperties;
+ uint_t pwdHistoryLength;
+ char *netbios_domain;
+ char *dns_domain;
+ char *realm;
+};
+
+struct setup_password_fields_io {
+ struct ph_context *ac;
+ struct domain_data *domain;
+ struct smb_krb5_context *smb_krb5_context;
+
+ /* infos about the user account */
+ struct {
+ uint32_t user_account_control;
+ const char *sAMAccountName;
+ const char *user_principal_name;
+ bool is_computer;
+ } u;
+
+ /* new credentials */
+ struct {
+ const char *cleartext;
+ struct samr_Password *nt_hash;
+ struct samr_Password *lm_hash;
+ } n;
+
+ /* old credentials */
+ struct {
+ uint32_t nt_history_len;
+ struct samr_Password *nt_history;
+ uint32_t lm_history_len;
+ struct samr_Password *lm_history;
+ const struct ldb_val *supplemental;
+ struct supplementalCredentialsBlob scb;
+ uint32_t kvno;
+ } o;
+
+ /* generated credentials */
+ struct {
+ struct samr_Password *nt_hash;
+ struct samr_Password *lm_hash;
+ uint32_t nt_history_len;
+ struct samr_Password *nt_history;
+ uint32_t lm_history_len;
+ struct samr_Password *lm_history;
+ const char *salt;
+ DATA_BLOB aes_256;
+ DATA_BLOB aes_128;
+ DATA_BLOB des_md5;
+ DATA_BLOB des_crc;
+ struct ldb_val supplemental;
+ NTTIME last_set;
+ uint32_t kvno;
+ } g;
+};
+
+static int setup_nt_fields(struct setup_password_fields_io *io)
+{
+ uint32_t i;
+
+ io->g.nt_hash = io->n.nt_hash;
+
+ if (io->domain->pwdHistoryLength == 0) {
+ return LDB_SUCCESS;
+ }
+
+ /* We might not have an old NT password */
+ io->g.nt_history = talloc_array(io->ac,
+ struct samr_Password,
+ io->domain->pwdHistoryLength);
+ if (!io->g.nt_history) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < MIN(io->domain->pwdHistoryLength-1, io->o.nt_history_len); i++) {
+ io->g.nt_history[i+1] = io->o.nt_history[i];
+ }
+ io->g.nt_history_len = i + 1;
+
+ if (io->g.nt_hash) {
+ io->g.nt_history[0] = *io->g.nt_hash;
+ } else {
+ /*
+ * TODO: is this correct?
+ * the simular behavior is correct for the lm history case
+ */
+ E_md4hash("", io->g.nt_history[0].hash);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_lm_fields(struct setup_password_fields_io *io)
+{
+ uint32_t i;
+
+ io->g.lm_hash = io->n.lm_hash;
+
+ if (io->domain->pwdHistoryLength == 0) {
+ return LDB_SUCCESS;
+ }
+
+ /* We might not have an old NT password */
+ io->g.lm_history = talloc_array(io->ac,
+ struct samr_Password,
+ io->domain->pwdHistoryLength);
+ if (!io->g.lm_history) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < MIN(io->domain->pwdHistoryLength-1, io->o.lm_history_len); i++) {
+ io->g.lm_history[i+1] = io->o.lm_history[i];
+ }
+ io->g.lm_history_len = i + 1;
+
+ if (io->g.lm_hash) {
+ io->g.lm_history[0] = *io->g.lm_hash;
+ } else {
+ E_deshash("", io->g.lm_history[0].hash);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_kerberos_keys(struct setup_password_fields_io *io)
+{
+ krb5_error_code krb5_ret;
+ Principal *salt_principal;
+ krb5_salt salt;
+ krb5_keyblock key;
+
+ /* Many, many thanks to lukeh@padl.com for this
+ * algorithm, described in his Nov 10 2004 mail to
+ * samba-technical@samba.org */
+
+ /*
+ * Determine a salting principal
+ */
+ if (io->u.is_computer) {
+ char *name;
+ char *saltbody;
+
+ name = talloc_strdup(io->ac, io->u.sAMAccountName);
+ if (!name) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (name[strlen(name)-1] == '$') {
+ name[strlen(name)-1] = '\0';
+ }
+
+ saltbody = talloc_asprintf(io->ac, "%s.%s", name, io->domain->dns_domain);
+ if (!saltbody) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ krb5_ret = krb5_make_principal(io->smb_krb5_context->krb5_context,
+ &salt_principal,
+ io->domain->realm, "host",
+ saltbody, NULL);
+ } else if (io->u.user_principal_name) {
+ char *user_principal_name;
+ char *p;
+
+ user_principal_name = talloc_strdup(io->ac, io->u.user_principal_name);
+ if (!user_principal_name) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ p = strchr(user_principal_name, '@');
+ if (p) {
+ p[0] = '\0';
+ }
+
+ krb5_ret = krb5_make_principal(io->smb_krb5_context->krb5_context,
+ &salt_principal,
+ io->domain->realm, user_principal_name,
+ NULL);
+ } else {
+ krb5_ret = krb5_make_principal(io->smb_krb5_context->krb5_context,
+ &salt_principal,
+ io->domain->realm, io->u.sAMAccountName,
+ NULL);
+ }
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a salting principal failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create salt from salt_principal
+ */
+ krb5_ret = krb5_get_pw_salt(io->smb_krb5_context->krb5_context,
+ salt_principal, &salt);
+ krb5_free_principal(io->smb_krb5_context->krb5_context, salt_principal);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of krb5_salt failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* create a talloc copy */
+ io->g.salt = talloc_strndup(io->ac,
+ salt.saltvalue.data,
+ salt.saltvalue.length);
+ krb5_free_salt(io->smb_krb5_context->krb5_context, salt);
+ if (!io->g.salt) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ salt.saltvalue.data = discard_const(io->g.salt);
+ salt.saltvalue.length = strlen(io->g.salt);
+
+ /*
+ * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a aes256-cts-hmac-sha1-96 key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.aes_256 = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.aes_256.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create ENCTYPE_AES128_CTS_HMAC_SHA1_96 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a aes128-cts-hmac-sha1-96 key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.aes_128 = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.aes_128.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create ENCTYPE_DES_CBC_MD5 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_DES_CBC_MD5,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a des-cbc-md5 key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.des_md5 = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.des_md5.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create ENCTYPE_DES_CBC_CRC key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_DES_CBC_CRC,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a des-cbc-crc key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.des_crc = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.des_crc.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_kerberos(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryKerberosBlob *pkb)
+{
+ struct package_PrimaryKerberosCtr3 *pkb3 = &pkb->ctr.ctr3;
+ struct supplementalCredentialsPackage *old_scp = NULL;
+ struct package_PrimaryKerberosBlob _old_pkb;
+ struct package_PrimaryKerberosCtr3 *old_pkb3 = NULL;
+ uint32_t i;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * prepare generation of keys
+ *
+ * ENCTYPE_DES_CBC_MD5
+ * ENCTYPE_DES_CBC_CRC
+ */
+ pkb->version = 3;
+ pkb3->salt.string = io->g.salt;
+ pkb3->num_keys = 2;
+ pkb3->keys = talloc_array(io->ac,
+ struct package_PrimaryKerberosKey3,
+ pkb3->num_keys);
+ if (!pkb3->keys) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pkb3->keys[0].keytype = ENCTYPE_DES_CBC_MD5;
+ pkb3->keys[0].value = &io->g.des_md5;
+ pkb3->keys[1].keytype = ENCTYPE_DES_CBC_CRC;
+ pkb3->keys[1].value = &io->g.des_crc;
+
+ /* initialize the old keys to zero */
+ pkb3->num_old_keys = 0;
+ pkb3->old_keys = NULL;
+
+ /* if there're no old keys, then we're done */
+ if (!old_scb) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0; i < old_scb->sub.num_packages; i++) {
+ if (strcmp("Primary:Kerberos", old_scb->sub.packages[i].name) != 0) {
+ continue;
+ }
+
+ if (!old_scb->sub.packages[i].data || !old_scb->sub.packages[i].data[0]) {
+ continue;
+ }
+
+ old_scp = &old_scb->sub.packages[i];
+ break;
+ }
+ /* Primary:Kerberos element of supplementalCredentials */
+ if (old_scp) {
+ DATA_BLOB blob;
+
+ blob = strhex_to_data_blob(old_scp->data);
+ if (!blob.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_steal(io->ac, blob.data);
+
+ /* TODO: use ndr_pull_struct_blob_all(), when the ndr layer handles it correct with relative pointers */
+ ndr_err = ndr_pull_struct_blob(&blob, io->ac, lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")), &_old_pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos: "
+ "failed to pull old package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (_old_pkb.version != 3) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos: "
+ "package_PrimaryKerberosBlob version[%u] expected[3]",
+ _old_pkb.version);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_pkb3 = &_old_pkb.ctr.ctr3;
+ }
+
+ /* if we didn't found the old keys we're done */
+ if (!old_pkb3) {
+ return LDB_SUCCESS;
+ }
+
+ /* fill in the old keys */
+ pkb3->num_old_keys = old_pkb3->num_keys;
+ pkb3->old_keys = old_pkb3->keys;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_kerberos_newer(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryKerberosBlob *pkb)
+{
+ struct package_PrimaryKerberosCtr4 *pkb4 = &pkb->ctr.ctr4;
+ struct supplementalCredentialsPackage *old_scp = NULL;
+ struct package_PrimaryKerberosBlob _old_pkb;
+ struct package_PrimaryKerberosCtr4 *old_pkb4 = NULL;
+ uint32_t i;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * prepare generation of keys
+ *
+ * ENCTYPE_AES256_CTS_HMAC_SHA1_96
+ * ENCTYPE_AES128_CTS_HMAC_SHA1_96
+ * ENCTYPE_DES_CBC_MD5
+ * ENCTYPE_DES_CBC_CRC
+ */
+ pkb->version = 4;
+ pkb4->salt.string = io->g.salt;
+ pkb4->default_iteration_count = 4096;
+ pkb4->num_keys = 4;
+
+ pkb4->keys = talloc_array(io->ac,
+ struct package_PrimaryKerberosKey4,
+ pkb4->num_keys);
+ if (!pkb4->keys) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pkb4->keys[0].iteration_count = 4096;
+ pkb4->keys[0].keytype = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ pkb4->keys[0].value = &io->g.aes_256;
+ pkb4->keys[1].iteration_count = 4096;
+ pkb4->keys[1].keytype = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+ pkb4->keys[1].value = &io->g.aes_128;
+ pkb4->keys[2].iteration_count = 4096;
+ pkb4->keys[2].keytype = ENCTYPE_DES_CBC_MD5;
+ pkb4->keys[2].value = &io->g.des_md5;
+ pkb4->keys[3].iteration_count = 4096;
+ pkb4->keys[3].keytype = ENCTYPE_DES_CBC_CRC;
+ pkb4->keys[3].value = &io->g.des_crc;
+
+ /* initialize the old keys to zero */
+ pkb4->num_old_keys = 0;
+ pkb4->old_keys = NULL;
+ pkb4->num_older_keys = 0;
+ pkb4->older_keys = NULL;
+
+ /* if there're no old keys, then we're done */
+ if (!old_scb) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0; i < old_scb->sub.num_packages; i++) {
+ if (strcmp("Primary:Kerberos-Newer-Keys", old_scb->sub.packages[i].name) != 0) {
+ continue;
+ }
+
+ if (!old_scb->sub.packages[i].data || !old_scb->sub.packages[i].data[0]) {
+ continue;
+ }
+
+ old_scp = &old_scb->sub.packages[i];
+ break;
+ }
+ /* Primary:Kerberos-Newer-Keys element of supplementalCredentials */
+ if (old_scp) {
+ DATA_BLOB blob;
+
+ blob = strhex_to_data_blob(old_scp->data);
+ if (!blob.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_steal(io->ac, blob.data);
+
+ /* TODO: use ndr_pull_struct_blob_all(), when the ndr layer handles it correct with relative pointers */
+ ndr_err = ndr_pull_struct_blob(&blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &_old_pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos_newer: "
+ "failed to pull old package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (_old_pkb.version != 4) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos_newer: "
+ "package_PrimaryKerberosBlob version[%u] expected[4]",
+ _old_pkb.version);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_pkb4 = &_old_pkb.ctr.ctr4;
+ }
+
+ /* if we didn't found the old keys we're done */
+ if (!old_pkb4) {
+ return LDB_SUCCESS;
+ }
+
+ /* fill in the old keys */
+ pkb4->num_old_keys = old_pkb4->num_keys;
+ pkb4->old_keys = old_pkb4->keys;
+ pkb4->num_older_keys = old_pkb4->num_old_keys;
+ pkb4->older_keys = old_pkb4->old_keys;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_wdigest(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryWDigestBlob *pdb)
+{
+ DATA_BLOB sAMAccountName;
+ DATA_BLOB sAMAccountName_l;
+ DATA_BLOB sAMAccountName_u;
+ const char *user_principal_name = io->u.user_principal_name;
+ DATA_BLOB userPrincipalName;
+ DATA_BLOB userPrincipalName_l;
+ DATA_BLOB userPrincipalName_u;
+ DATA_BLOB netbios_domain;
+ DATA_BLOB netbios_domain_l;
+ DATA_BLOB netbios_domain_u;
+ DATA_BLOB dns_domain;
+ DATA_BLOB dns_domain_l;
+ DATA_BLOB dns_domain_u;
+ DATA_BLOB cleartext;
+ DATA_BLOB digest;
+ DATA_BLOB delim;
+ DATA_BLOB backslash;
+ uint8_t i;
+ struct {
+ DATA_BLOB *user;
+ DATA_BLOB *realm;
+ DATA_BLOB *nt4dom;
+ } wdigest[] = {
+ /*
+ * See
+ * http://technet2.microsoft.com/WindowsServer/en/library/717b450c-f4a0-4cc9-86f4-cc0633aae5f91033.mspx?mfr=true
+ * for what precalculated hashes are supposed to be stored...
+ *
+ * I can't reproduce all values which should contain "Digest" as realm,
+ * am I doing something wrong or is w2k3 just broken...?
+ *
+ * W2K3 fills in following for a user:
+ *
+ * dn: CN=NewUser,OU=newtop,DC=sub1,DC=w2k3,DC=vmnet1,DC=vm,DC=base
+ * sAMAccountName: NewUser2Sam
+ * userPrincipalName: NewUser2Princ@sub1.w2k3.vmnet1.vm.base
+ *
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * b7ec9da91062199aee7d121e6710fe23 => newuser2sam:sub1:TestPwd2007
+ * 17d290bc5c9f463fac54c37a8cea134d => NEWUSER2SAM:SUB1:TestPwd2007
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * 5d57e7823938348127322e08cd81bcb5 => NewUser2Sam:sub1:TestPwd2007
+ * 07dd701bf8a011ece585de3d47237140 => NEWUSER2SAM:sub1:TestPwd2007
+ * e14fb0eb401498d2cb33c9aae1cc7f37 => newuser2sam:SUB1:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * f52da1266a6bdd290ffd48b2c823dda7 => newuser2sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * d2b42f171248cec37a3c5c6b55404062 => NEWUSER2SAM:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * fff8d790ff6c152aaeb6ebe17b4021de => NewUser2Sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * 2a7563c3715bc418d626dabef378c008 => NEWUSER2SAM:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * c8e9557a87cd4200fda0c11d2fa03f96 => newuser2sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 221c55284451ae9b3aacaa2a3c86f10f => NewUser2Princ@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * 74e1be668853d4324d38c07e2acfb8ea => (w2k3 has a bug here!) newuser2princ@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * e1e244ab7f098e3ae1761be7f9229bbb => NEWUSER2PRINC@SUB1.W2K3.VMNET1.VM.BASE::TestPwd2007
+ * 86db637df42513039920e605499c3af6 => SUB1\NewUser2Sam::TestPwd2007
+ * f5e43474dfaf067fee8197a253debaa2 => sub1\newuser2sam::TestPwd2007
+ * 2ecaa8382e2518e4b77a52422b279467 => SUB1\NEWUSER2SAM::TestPwd2007
+ * 31dc704d3640335b2123d4ee28aa1f11 => ??? changes with NewUser2Sam => NewUser1Sam
+ * 36349f5cecd07320fb3bb0e119230c43 => ??? changes with NewUser2Sam => NewUser1Sam
+ * 12adf019d037fb535c01fd0608e78d9d => ??? changes with NewUser2Sam => NewUser1Sam
+ * 6feecf8e724906f3ee1105819c5105a1 => ??? changes with NewUser2Princ => NewUser1Princ
+ * 6c6911f3de6333422640221b9c51ff1f => ??? changes with NewUser2Princ => NewUser1Princ
+ * 4b279877e742895f9348ac67a8de2f69 => ??? changes with NewUser2Princ => NewUser1Princ
+ * db0c6bff069513e3ebb9870d29b57490 => ??? changes with NewUser2Sam => NewUser1Sam
+ * 45072621e56b1c113a4e04a8ff68cd0e => ??? changes with NewUser2Sam => NewUser1Sam
+ * 11d1220abc44a9c10cf91ef4a9c1de02 => ??? changes with NewUser2Sam => NewUser1Sam
+ *
+ * dn: CN=NewUser,OU=newtop,DC=sub1,DC=w2k3,DC=vmnet1,DC=vm,DC=base
+ * sAMAccountName: NewUser2Sam
+ *
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * b7ec9da91062199aee7d121e6710fe23 => newuser2sam:sub1:TestPwd2007
+ * 17d290bc5c9f463fac54c37a8cea134d => NEWUSER2SAM:SUB1:TestPwd2007
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * 5d57e7823938348127322e08cd81bcb5 => NewUser2Sam:sub1:TestPwd2007
+ * 07dd701bf8a011ece585de3d47237140 => NEWUSER2SAM:sub1:TestPwd2007
+ * e14fb0eb401498d2cb33c9aae1cc7f37 => newuser2sam:SUB1:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * f52da1266a6bdd290ffd48b2c823dda7 => newuser2sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * d2b42f171248cec37a3c5c6b55404062 => NEWUSER2SAM:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * fff8d790ff6c152aaeb6ebe17b4021de => NewUser2Sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * 2a7563c3715bc418d626dabef378c008 => NEWUSER2SAM:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * c8e9557a87cd4200fda0c11d2fa03f96 => newuser2sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 8a140d30b6f0a5912735dc1e3bc993b4 => NewUser2Sam@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * 86d95b2faae6cae4ec261e7fbaccf093 => (here w2k3 is correct) newuser2sam@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * dfeff1493110220efcdfc6362e5f5450 => NEWUSER2SAM@SUB1.W2K3.VMNET1.VM.BASE::TestPwd2007
+ * 86db637df42513039920e605499c3af6 => SUB1\NewUser2Sam::TestPwd2007
+ * f5e43474dfaf067fee8197a253debaa2 => sub1\newuser2sam::TestPwd2007
+ * 2ecaa8382e2518e4b77a52422b279467 => SUB1\NEWUSER2SAM::TestPwd2007
+ * 31dc704d3640335b2123d4ee28aa1f11 => ???M1 changes with NewUser2Sam => NewUser1Sam
+ * 36349f5cecd07320fb3bb0e119230c43 => ???M1.L changes with newuser2sam => newuser1sam
+ * 12adf019d037fb535c01fd0608e78d9d => ???M1.U changes with NEWUSER2SAM => NEWUSER1SAM
+ * 569b4533f2d9e580211dd040e5e360a8 => ???M2 changes with NewUser2Princ => NewUser1Princ
+ * 52528bddf310a587c5d7e6a9ae2cbb20 => ???M2.L changes with newuser2princ => newuser1princ
+ * 4f629a4f0361289ca4255ab0f658fcd5 => ???M3 changes with NewUser2Princ => NewUser1Princ (doesn't depend on case of userPrincipal )
+ * db0c6bff069513e3ebb9870d29b57490 => ???M4 changes with NewUser2Sam => NewUser1Sam
+ * 45072621e56b1c113a4e04a8ff68cd0e => ???M5 changes with NewUser2Sam => NewUser1Sam (doesn't depend on case of sAMAccountName)
+ * 11d1220abc44a9c10cf91ef4a9c1de02 => ???M4.U changes with NEWUSER2SAM => NEWUSER1SAM
+ */
+
+ /*
+ * sAMAccountName, netbios_domain
+ */
+ {
+ .user = &sAMAccountName,
+ .realm = &netbios_domain,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &netbios_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &netbios_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &netbios_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &netbios_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &netbios_domain_l,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &netbios_domain_u,
+ },
+ /*
+ * sAMAccountName, dns_domain
+ */
+ {
+ .user = &sAMAccountName,
+ .realm = &dns_domain,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &dns_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &dns_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &dns_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &dns_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &dns_domain_l,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &dns_domain_u,
+ },
+ /*
+ * userPrincipalName, no realm
+ */
+ {
+ .user = &userPrincipalName,
+ },
+ {
+ /*
+ * NOTE: w2k3 messes this up, if the user has a real userPrincipalName,
+ * the fallback to the sAMAccountName based userPrincipalName is correct
+ */
+ .user = &userPrincipalName_l,
+ },
+ {
+ .user = &userPrincipalName_u,
+ },
+ /*
+ * nt4dom\sAMAccountName, no realm
+ */
+ {
+ .user = &sAMAccountName,
+ .nt4dom = &netbios_domain
+ },
+ {
+ .user = &sAMAccountName_l,
+ .nt4dom = &netbios_domain_l
+ },
+ {
+ .user = &sAMAccountName_u,
+ .nt4dom = &netbios_domain_u
+ },
+
+ /*
+ * the following ones are guessed depending on the technet2 article
+ * but not reproducable on a w2k3 server
+ */
+ /* sAMAccountName with "Digest" realm */
+ {
+ .user = &sAMAccountName,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &digest
+ },
+ /* userPrincipalName with "Digest" realm */
+ {
+ .user = &userPrincipalName,
+ .realm = &digest
+ },
+ {
+ .user = &userPrincipalName_l,
+ .realm = &digest
+ },
+ {
+ .user = &userPrincipalName_u,
+ .realm = &digest
+ },
+ /* nt4dom\\sAMAccountName with "Digest" realm */
+ {
+ .user = &sAMAccountName,
+ .nt4dom = &netbios_domain,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_l,
+ .nt4dom = &netbios_domain_l,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_u,
+ .nt4dom = &netbios_domain_u,
+ .realm = &digest
+ },
+ };
+
+ /* prepare DATA_BLOB's used in the combinations array */
+ sAMAccountName = data_blob_string_const(io->u.sAMAccountName);
+ sAMAccountName_l = data_blob_string_const(strlower_talloc(io->ac, io->u.sAMAccountName));
+ if (!sAMAccountName_l.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ sAMAccountName_u = data_blob_string_const(strupper_talloc(io->ac, io->u.sAMAccountName));
+ if (!sAMAccountName_u.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if the user doesn't have a userPrincipalName, create one (with lower case realm) */
+ if (!user_principal_name) {
+ user_principal_name = talloc_asprintf(io->ac, "%s@%s",
+ io->u.sAMAccountName,
+ io->domain->dns_domain);
+ if (!user_principal_name) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ userPrincipalName = data_blob_string_const(user_principal_name);
+ userPrincipalName_l = data_blob_string_const(strlower_talloc(io->ac, user_principal_name));
+ if (!userPrincipalName_l.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ userPrincipalName_u = data_blob_string_const(strupper_talloc(io->ac, user_principal_name));
+ if (!userPrincipalName_u.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ netbios_domain = data_blob_string_const(io->domain->netbios_domain);
+ netbios_domain_l = data_blob_string_const(strlower_talloc(io->ac, io->domain->netbios_domain));
+ if (!netbios_domain_l.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ netbios_domain_u = data_blob_string_const(strupper_talloc(io->ac, io->domain->netbios_domain));
+ if (!netbios_domain_u.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ dns_domain = data_blob_string_const(io->domain->dns_domain);
+ dns_domain_l = data_blob_string_const(io->domain->dns_domain);
+ dns_domain_u = data_blob_string_const(io->domain->realm);
+
+ cleartext = data_blob_string_const(io->n.cleartext);
+
+ digest = data_blob_string_const("Digest");
+
+ delim = data_blob_string_const(":");
+ backslash = data_blob_string_const("\\");
+
+ pdb->num_hashes = ARRAY_SIZE(wdigest);
+ pdb->hashes = talloc_array(io->ac, struct package_PrimaryWDigestHash, pdb->num_hashes);
+ if (!pdb->hashes) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < ARRAY_SIZE(wdigest); i++) {
+ struct MD5Context md5;
+ MD5Init(&md5);
+ if (wdigest[i].nt4dom) {
+ MD5Update(&md5, wdigest[i].nt4dom->data, wdigest[i].nt4dom->length);
+ MD5Update(&md5, backslash.data, backslash.length);
+ }
+ MD5Update(&md5, wdigest[i].user->data, wdigest[i].user->length);
+ MD5Update(&md5, delim.data, delim.length);
+ if (wdigest[i].realm) {
+ MD5Update(&md5, wdigest[i].realm->data, wdigest[i].realm->length);
+ }
+ MD5Update(&md5, delim.data, delim.length);
+ MD5Update(&md5, cleartext.data, cleartext.length);
+ MD5Final(pdb->hashes[i].hash, &md5);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_supplemental_field(struct setup_password_fields_io *io)
+{
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsBlob _old_scb;
+ struct supplementalCredentialsBlob *old_scb = NULL;
+ /* Packages + (Kerberos-Newer-Keys, Kerberos, WDigest and CLEARTEXT) */
+ uint32_t num_names = 0;
+ const char *names[1+4];
+ uint32_t num_packages = 0;
+ struct supplementalCredentialsPackage packages[1+4];
+ /* Packages */
+ struct supplementalCredentialsPackage *pp = NULL;
+ struct package_PackagesBlob pb;
+ DATA_BLOB pb_blob;
+ char *pb_hexstr;
+ /* Primary:Kerberos-Newer-Keys */
+ const char **nkn = NULL;
+ struct supplementalCredentialsPackage *pkn = NULL;
+ struct package_PrimaryKerberosBlob pknb;
+ DATA_BLOB pknb_blob;
+ char *pknb_hexstr;
+ /* Primary:Kerberos */
+ const char **nk = NULL;
+ struct supplementalCredentialsPackage *pk = NULL;
+ struct package_PrimaryKerberosBlob pkb;
+ DATA_BLOB pkb_blob;
+ char *pkb_hexstr;
+ /* Primary:WDigest */
+ const char **nd = NULL;
+ struct supplementalCredentialsPackage *pd = NULL;
+ struct package_PrimaryWDigestBlob pdb;
+ DATA_BLOB pdb_blob;
+ char *pdb_hexstr;
+ /* Primary:CLEARTEXT */
+ const char **nc = NULL;
+ struct supplementalCredentialsPackage *pc = NULL;
+ struct package_PrimaryCLEARTEXTBlob pcb;
+ DATA_BLOB pcb_blob;
+ char *pcb_hexstr;
+ int ret;
+ enum ndr_err_code ndr_err;
+ uint8_t zero16[16];
+ bool do_newer_keys = false;
+ bool do_cleartext = false;
+
+ ZERO_STRUCT(zero16);
+ ZERO_STRUCT(names);
+
+ if (!io->n.cleartext) {
+ /*
+ * when we don't have a cleartext password
+ * we can't setup a supplementalCredential value
+ */
+ return LDB_SUCCESS;
+ }
+
+ /* if there's an old supplementaCredentials blob then parse it */
+ if (io->o.supplemental) {
+ ndr_err = ndr_pull_struct_blob_all(io->o.supplemental, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &_old_scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to pull old supplementalCredentialsBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (_old_scb.sub.signature == SUPPLEMENTAL_CREDENTIALS_SIGNATURE) {
+ old_scb = &_old_scb;
+ } else {
+ ldb_debug(io->ac->module->ldb, LDB_DEBUG_ERROR,
+ "setup_supplemental_field: "
+ "supplementalCredentialsBlob signature[0x%04X] expected[0x%04X]",
+ _old_scb.sub.signature, SUPPLEMENTAL_CREDENTIALS_SIGNATURE);
+ }
+ }
+
+ /* TODO: do the correct check for this, it maybe depends on the functional level? */
+ do_newer_keys = lp_parm_bool(ldb_get_opaque(io->ac->module->ldb, "loadparm"),
+ NULL, "password_hash", "create_aes_key", false);
+
+ if (io->domain->store_cleartext &&
+ (io->u.user_account_control & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) {
+ do_cleartext = true;
+ }
+
+ /*
+ * The ordering is this
+ *
+ * Primary:Kerberos-Newer-Keys (optional)
+ * Primary:Kerberos
+ * Primary:WDigest
+ * Primary:CLEARTEXT (optional)
+ *
+ * And the 'Packages' package is insert before the last
+ * other package.
+ */
+ if (do_newer_keys) {
+ /* Primary:Kerberos-Newer-Keys */
+ nkn = &names[num_names++];
+ pkn = &packages[num_packages++];
+ }
+
+ /* Primary:Kerberos */
+ nk = &names[num_names++];
+ pk = &packages[num_packages++];
+
+ if (!do_cleartext) {
+ /* Packages */
+ pp = &packages[num_packages++];
+ }
+
+ /* Primary:WDigest */
+ nd = &names[num_names++];
+ pd = &packages[num_packages++];
+
+ if (do_cleartext) {
+ /* Packages */
+ pp = &packages[num_packages++];
+
+ /* Primary:CLEARTEXT */
+ nc = &names[num_names++];
+ pc = &packages[num_packages++];
+ }
+
+ if (pkn) {
+ /*
+ * setup 'Primary:Kerberos-Newer-Keys' element
+ */
+ *nkn = "Kerberos-Newer-Keys";
+
+ ret = setup_primary_kerberos_newer(io, old_scb, &pknb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pknb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pknb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryKerberosNeverBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pknb_hexstr = data_blob_hex_string(io->ac, &pknb_blob);
+ if (!pknb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pkn->name = "Primary:Kerberos-Newer-Keys";
+ pkn->reserved = 1;
+ pkn->data = pknb_hexstr;
+ }
+
+ /*
+ * setup 'Primary:Kerberos' element
+ */
+ *nk = "Kerberos";
+
+ ret = setup_primary_kerberos(io, old_scb, &pkb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pkb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pkb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pkb_hexstr = data_blob_hex_string(io->ac, &pkb_blob);
+ if (!pkb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pk->name = "Primary:Kerberos";
+ pk->reserved = 1;
+ pk->data = pkb_hexstr;
+
+ /*
+ * setup 'Primary:WDigest' element
+ */
+ *nd = "WDigest";
+
+ ret = setup_primary_wdigest(io, old_scb, &pdb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pdb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pdb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryWDigestBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryWDigestBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pdb_hexstr = data_blob_hex_string(io->ac, &pdb_blob);
+ if (!pdb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pd->name = "Primary:WDigest";
+ pd->reserved = 1;
+ pd->data = pdb_hexstr;
+
+ /*
+ * setup 'Primary:CLEARTEXT' element
+ */
+ if (pc) {
+ *nc = "CLEARTEXT";
+
+ pcb.cleartext = io->n.cleartext;
+
+ ndr_err = ndr_push_struct_blob(&pcb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pcb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryCLEARTEXTBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryCLEARTEXTBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pcb_hexstr = data_blob_hex_string(io->ac, &pcb_blob);
+ if (!pcb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pc->name = "Primary:CLEARTEXT";
+ pc->reserved = 1;
+ pc->data = pcb_hexstr;
+ }
+
+ /*
+ * setup 'Packages' element
+ */
+ pb.names = names;
+ ndr_err = ndr_push_struct_blob(&pb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pb,
+ (ndr_push_flags_fn_t)ndr_push_package_PackagesBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PackagesBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pb_hexstr = data_blob_hex_string(io->ac, &pb_blob);
+ if (!pb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pp->name = "Packages";
+ pp->reserved = 2;
+ pp->data = pb_hexstr;
+
+ /*
+ * setup 'supplementalCredentials' value
+ */
+ ZERO_STRUCT(scb);
+ scb.sub.num_packages = num_packages;
+ scb.sub.packages = packages;
+
+ ndr_err = ndr_push_struct_blob(&io->g.supplemental, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &scb,
+ (ndr_push_flags_fn_t)ndr_push_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push supplementalCredentialsBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_last_set_field(struct setup_password_fields_io *io)
+{
+ /* set it as now */
+ unix_to_nt_time(&io->g.last_set, time(NULL));
+
+ return LDB_SUCCESS;
+}
+
+static int setup_kvno_field(struct setup_password_fields_io *io)
+{
+ /* increment by one */
+ io->g.kvno = io->o.kvno + 1;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_password_fields(struct setup_password_fields_io *io)
+{
+ bool ok;
+ int ret;
+
+ /*
+ * refuse the change if someone want to change the cleartext
+ * and supply his own hashes at the same time...
+ */
+ if (io->n.cleartext && (io->n.nt_hash || io->n.lm_hash)) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_password_fields: "
+ "it's only allowed to set the cleartext password or the password hashes");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (io->n.cleartext) {
+ struct samr_Password *hash;
+
+ hash = talloc(io->ac, struct samr_Password);
+ if (!hash) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* compute the new nt hash */
+ ok = E_md4hash(io->n.cleartext, hash->hash);
+ if (ok) {
+ io->n.nt_hash = hash;
+ } else {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_password_fields: "
+ "failed to generate nthash from cleartext password");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ if (io->n.cleartext) {
+ struct samr_Password *hash;
+
+ hash = talloc(io->ac, struct samr_Password);
+ if (!hash) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* compute the new lm hash */
+ ok = E_deshash(io->n.cleartext, hash->hash);
+ if (ok) {
+ io->n.lm_hash = hash;
+ } else {
+ talloc_free(hash->hash);
+ }
+ }
+
+ if (io->n.cleartext) {
+ ret = setup_kerberos_keys(io);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = setup_nt_fields(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_lm_fields(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_supplemental_field(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_last_set_field(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_kvno_field(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static struct ldb_handle *ph_init_handle(struct ldb_request *req, struct ldb_module *module, enum ph_type type)
+{
+ struct ph_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct ph_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = (void *)ac;
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->type = type;
+ ac->module = module;
+ ac->orig_req = req;
+
+ return h;
+}
+
+static int get_domain_data_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct ph_context *ac;
+
+ ac = talloc_get_type(context, struct ph_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->dom_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->dom_res = talloc_steal(ac, ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int build_domain_data_request(struct ph_context *ac)
+{
+ /* attrs[] is returned from this function in
+ ac->dom_req->op.search.attrs, so it must be static, as
+ otherwise the compiler can put it on the stack */
+ static const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", NULL };
+ char *filter;
+
+ ac->dom_req = talloc_zero(ac, struct ldb_request);
+ if (ac->dom_req == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->dom_req->operation = LDB_SEARCH;
+ ac->dom_req->op.search.base = ldb_get_default_basedn(ac->module->ldb);
+ ac->dom_req->op.search.scope = LDB_SCOPE_SUBTREE;
+
+ filter = talloc_asprintf(ac->dom_req,
+ "(&(objectSid=%s)(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain)))",
+ ldap_encode_ndr_dom_sid(ac->dom_req, ac->domain_sid));
+ if (filter == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ talloc_free(ac->dom_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->dom_req->op.search.tree = ldb_parse_tree(ac->dom_req, filter);
+ if (ac->dom_req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Invalid search filter");
+ talloc_free(ac->dom_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->dom_req->op.search.attrs = attrs;
+ ac->dom_req->controls = NULL;
+ ac->dom_req->context = ac;
+ ac->dom_req->callback = get_domain_data_callback;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->dom_req);
+
+ return LDB_SUCCESS;
+}
+
+static struct domain_data *get_domain_data(struct ldb_module *module, void *ctx, struct ldb_reply *res)
+{
+ struct domain_data *data;
+ const char *tmp;
+ struct ph_context *ac;
+ char *p;
+
+ ac = talloc_get_type(ctx, struct ph_context);
+
+ data = talloc_zero(ac, struct domain_data);
+ if (data == NULL) {
+ return NULL;
+ }
+
+ if (res == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Could not find this user's domain: %s!\n", dom_sid_string(data, ac->domain_sid));
+ talloc_free(data);
+ return NULL;
+ }
+
+ data->pwdProperties= samdb_result_uint(res->message, "pwdProperties", 0);
+ data->store_cleartext = data->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT;
+ data->pwdHistoryLength = samdb_result_uint(res->message, "pwdHistoryLength", 0);
+
+ /* For a domain DN, this puts things in dotted notation */
+ /* For builtin domains, this will give details for the host,
+ * but that doesn't really matter, as it's just used for salt
+ * and kerberos principals, which don't exist here */
+
+ tmp = ldb_dn_canonical_string(ctx, res->message->dn);
+ if (!tmp) {
+ return NULL;
+ }
+
+ /* But it puts a trailing (or just before 'builtin') / on things, so kill that */
+ p = strchr(tmp, '/');
+ if (p) {
+ p[0] = '\0';
+ }
+
+ if (tmp != NULL) {
+ data->dns_domain = strlower_talloc(data, tmp);
+ if (data->dns_domain == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ data->realm = strupper_talloc(data, tmp);
+ if (data->realm == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ p = strchr(tmp, '.');
+ if (p) {
+ p[0] = '\0';
+ }
+ data->netbios_domain = strupper_talloc(data, tmp);
+ if (data->netbios_domain == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ }
+
+ return data;
+}
+
+static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct ph_context *ac;
+ struct ldb_message_element *sambaAttr;
+ struct ldb_message_element *ntAttr;
+ struct ldb_message_element *lmAttr;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add\n");
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.add.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ /* nobody must touch this fields */
+ if (ldb_msg_find_element(req->op.add.message, "ntPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "lmPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "supplementalCredentials")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* If no part of this ADD touches the userPassword, or the NT
+ * or LM hashes, then we don't need to make any changes. */
+
+ sambaAttr = ldb_msg_find_element(req->op.mod.message, "userPassword");
+ ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd");
+ lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd");
+
+ if ((!sambaAttr) && (!ntAttr) && (!lmAttr)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* if it is not an entry of type person its an error */
+ /* TODO: remove this when userPassword will be in schema */
+ if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
+ ldb_set_errstring(module->ldb, "Cannot set a password on entry that does not have objectClass 'person'");
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* check userPassword is single valued here */
+ /* TODO: remove this when userPassword will be single valued in schema */
+ if (sambaAttr && sambaAttr->num_values > 1) {
+ ldb_set_errstring(module->ldb, "mupltiple values for userPassword not allowed!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (ntAttr && (ntAttr->num_values > 1)) {
+ ldb_set_errstring(module->ldb, "mupltiple values for unicodePwd not allowed!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (lmAttr && (lmAttr->num_values > 1)) {
+ ldb_set_errstring(module->ldb, "mupltiple values for dBCSPwd not allowed!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (sambaAttr && sambaAttr->num_values == 0) {
+ ldb_set_errstring(module->ldb, "userPassword must have a value!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (ntAttr && (ntAttr->num_values == 0)) {
+ ldb_set_errstring(module->ldb, "unicodePwd must have a value!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (lmAttr && (lmAttr->num_values == 0)) {
+ ldb_set_errstring(module->ldb, "dBCSPwd must have a value!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ h = ph_init_handle(req, module, PH_ADD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* get user domain data */
+ ac->domain_sid = samdb_result_sid_prefix(ac, req->op.add.message, "objectSid");
+ if (ac->domain_sid == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = build_domain_data_request(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step = PH_ADD_SEARCH_DOM;
+
+ req->handle = h;
+
+ return ldb_next_request(module, ac->dom_req);
+}
+
+static int password_hash_add_do_add(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ struct domain_data *domain;
+ struct smb_krb5_context *smb_krb5_context;
+ struct ldb_message *msg;
+ struct setup_password_fields_io io;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ domain = get_domain_data(ac->module, ac, ac->dom_res);
+ if (domain == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->down_req = talloc(ac, struct ldb_request);
+ if (ac->down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->down_req) = *(ac->orig_req);
+ ac->down_req->op.add.message = msg = ldb_msg_copy_shallow(ac->down_req, ac->orig_req->op.add.message);
+ if (ac->down_req->op.add.message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Some operations below require kerberos contexts */
+ if (smb_krb5_init_context(ac->down_req,
+ ldb_get_opaque(h->module->ldb, "EventContext"),
+ (struct loadparm_context *)ldb_get_opaque(h->module->ldb, "loadparm"),
+ &smb_krb5_context) != 0) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ZERO_STRUCT(io);
+ io.ac = ac;
+ io.domain = domain;
+ io.smb_krb5_context = smb_krb5_context;
+
+ io.u.user_account_control = samdb_result_uint(msg, "userAccountControl", 0);
+ io.u.sAMAccountName = samdb_result_string(msg, "samAccountName", NULL);
+ io.u.user_principal_name = samdb_result_string(msg, "userPrincipalName", NULL);
+ io.u.is_computer = ldb_msg_check_string_attribute(msg, "objectClass", "computer");
+
+ io.n.cleartext = samdb_result_string(msg, "userPassword", NULL);
+ io.n.nt_hash = samdb_result_hash(io.ac, msg, "unicodePwd");
+ io.n.lm_hash = samdb_result_hash(io.ac, msg, "dBCSPwd");
+
+ /* remove attributes */
+ if (io.n.cleartext) ldb_msg_remove_attr(msg, "userPassword");
+ if (io.n.nt_hash) ldb_msg_remove_attr(msg, "unicodePwd");
+ if (io.n.lm_hash) ldb_msg_remove_attr(msg, "dBCSPwd");
+ ldb_msg_remove_attr(msg, "pwdLastSet");
+ io.o.kvno = samdb_result_uint(msg, "msDs-KeyVersionNumber", 1) - 1;
+ ldb_msg_remove_attr(msg, "msDs-KeyVersionNumber");
+
+ ret = setup_password_fields(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (io.g.nt_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "unicodePwd", io.g.nt_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "dBCSPwd", io.g.lm_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.nt_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "ntPwdHistory",
+ io.g.nt_history,
+ io.g.nt_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "lmPwdHistory",
+ io.g.lm_history,
+ io.g.lm_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.supplemental.length > 0) {
+ ret = ldb_msg_add_value(msg, "supplementalCredentials",
+ &io.g.supplemental, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = samdb_msg_add_uint64(ac->module->ldb, ac, msg,
+ "pwdLastSet",
+ io.g.last_set);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = samdb_msg_add_uint(ac->module->ldb, ac, msg,
+ "msDs-KeyVersionNumber",
+ io.g.kvno);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = PH_ADD_DO_ADD;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->down_req);
+
+ /* perform the operation */
+ return ldb_next_request(ac->module, ac->down_req);
+}
+
+static int password_hash_mod_search_self(struct ldb_handle *h);
+
+static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct ph_context *ac;
+ struct ldb_message_element *sambaAttr;
+ struct ldb_message_element *ntAttr;
+ struct ldb_message_element *lmAttr;
+ struct ldb_message *msg;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify\n");
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.mod.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ /* nobody must touch password Histories */
+ if (ldb_msg_find_element(req->op.add.message, "ntPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "lmPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "supplementalCredentials")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ sambaAttr = ldb_msg_find_element(req->op.mod.message, "userPassword");
+ ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd");
+ lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd");
+
+ /* If no part of this touches the userPassword OR unicodePwd and/or dBCSPwd, then we don't
+ * need to make any changes. For password changes/set there should
+ * be a 'delete' or a 'modify' on this attribute. */
+ if ((!sambaAttr) && (!ntAttr) && (!lmAttr)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* check passwords are single valued here */
+ /* TODO: remove this when passwords will be single valued in schema */
+ if (sambaAttr && (sambaAttr->num_values > 1)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (ntAttr && (ntAttr->num_values > 1)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (lmAttr && (lmAttr->num_values > 1)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ h = ph_init_handle(req, module, PH_MOD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ /* prepare the first operation */
+ ac->down_req = talloc_zero(ac, struct ldb_request);
+ if (ac->down_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of memory!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->down_req) = *req; /* copy the request */
+
+ /* use a new message structure so that we can modify it */
+ ac->down_req->op.mod.message = msg = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
+
+ /* - remove any imodification to the password from the first commit
+ * we will make the real modification later */
+ if (sambaAttr) ldb_msg_remove_attr(msg, "userPassword");
+ if (ntAttr) ldb_msg_remove_attr(msg, "unicodePwd");
+ if (lmAttr) ldb_msg_remove_attr(msg, "dBCSPwd");
+
+ /* if there was nothing else to be modify skip to next step */
+ if (msg->num_elements == 0) {
+ talloc_free(ac->down_req);
+ ac->down_req = NULL;
+ return password_hash_mod_search_self(h);
+ }
+
+ ac->down_req->context = NULL;
+ ac->down_req->callback = NULL;
+
+ ac->step = PH_MOD_DO_REQ;
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, ac->down_req);
+
+ return ldb_next_request(module, ac->down_req);
+}
+
+static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct ph_context *ac;
+
+ ac = talloc_get_type(context, struct ph_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if it is not an entry of type person this is an error */
+ /* TODO: remove this when userPassword will be in schema */
+ if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
+ ldb_set_errstring(ldb, "Object class violation");
+ talloc_free(ares);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int password_hash_mod_search_self(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ static const char * const attrs[] = { "userAccountControl", "lmPwdHistory",
+ "ntPwdHistory",
+ "objectSid", "msDS-KeyVersionNumber",
+ "objectClass", "userPrincipalName",
+ "sAMAccountName",
+ "dBCSPwd", "unicodePwd",
+ "supplementalCredentials",
+ NULL };
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* prepare the search operation */
+ ac->search_req = talloc_zero(ac, struct ldb_request);
+ if (ac->search_req == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_req->operation = LDB_SEARCH;
+ ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
+ ac->search_req->op.search.scope = LDB_SCOPE_BASE;
+ ac->search_req->op.search.tree = ldb_parse_tree(ac->search_req, NULL);
+ if (ac->search_req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Invalid search filter");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->search_req->op.search.attrs = attrs;
+ ac->search_req->controls = NULL;
+ ac->search_req->context = ac;
+ ac->search_req->callback = get_self_callback;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = PH_MOD_SEARCH_SELF;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int password_hash_mod_search_dom(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* get object domain sid */
+ ac->domain_sid = samdb_result_sid_prefix(ac, ac->search_res->message, "objectSid");
+ if (ac->domain_sid == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* get user domain data */
+ ret = build_domain_data_request(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step = PH_MOD_SEARCH_DOM;
+
+ return ldb_next_request(ac->module, ac->dom_req);
+}
+
+static int password_hash_mod_do_mod(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ struct domain_data *domain;
+ struct smb_krb5_context *smb_krb5_context;
+ struct ldb_message *msg;
+ struct ldb_message *orig_msg;
+ struct ldb_message *searched_msg;
+ struct setup_password_fields_io io;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ domain = get_domain_data(ac->module, ac, ac->dom_res);
+ if (domain == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->mod_req = talloc(ac, struct ldb_request);
+ if (ac->mod_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->mod_req) = *(ac->orig_req);
+
+ /* use a new message structure so that we can modify it */
+ ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
+ if (msg == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* modify dn */
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ /* Some operations below require kerberos contexts */
+ if (smb_krb5_init_context(ac->mod_req,
+ ldb_get_opaque(h->module->ldb, "EventContext"),
+ (struct loadparm_context *)ldb_get_opaque(h->module->ldb, "loadparm"),
+ &smb_krb5_context) != 0) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ orig_msg = discard_const(ac->orig_req->op.mod.message);
+ searched_msg = ac->search_res->message;
+
+ ZERO_STRUCT(io);
+ io.ac = ac;
+ io.domain = domain;
+ io.smb_krb5_context = smb_krb5_context;
+
+ io.u.user_account_control = samdb_result_uint(searched_msg, "userAccountControl", 0);
+ io.u.sAMAccountName = samdb_result_string(searched_msg, "samAccountName", NULL);
+ io.u.user_principal_name = samdb_result_string(searched_msg, "userPrincipalName", NULL);
+ io.u.is_computer = ldb_msg_check_string_attribute(searched_msg, "objectClass", "computer");
+
+ io.n.cleartext = samdb_result_string(orig_msg, "userPassword", NULL);
+ io.n.nt_hash = samdb_result_hash(io.ac, orig_msg, "unicodePwd");
+ io.n.lm_hash = samdb_result_hash(io.ac, orig_msg, "dBCSPwd");
+
+ io.o.kvno = samdb_result_uint(searched_msg, "msDs-KeyVersionNumber", 0);
+ io.o.nt_history_len = samdb_result_hashes(io.ac, searched_msg, "ntPwdHistory", &io.o.nt_history);
+ io.o.lm_history_len = samdb_result_hashes(io.ac, searched_msg, "lmPwdHistory", &io.o.lm_history);
+ io.o.supplemental = ldb_msg_find_ldb_val(searched_msg, "supplementalCredentials");
+
+ ret = setup_password_fields(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* make sure we replace all the old attributes */
+ ret = ldb_msg_add_empty(msg, "unicodePwd", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "dBCSPwd", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "ntPwdHistory", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "lmPwdHistory", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "supplementalCredentials", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "msDs-KeyVersionNumber", LDB_FLAG_MOD_REPLACE, NULL);
+
+ if (io.g.nt_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "unicodePwd", io.g.nt_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "dBCSPwd", io.g.lm_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.nt_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "ntPwdHistory",
+ io.g.nt_history,
+ io.g.nt_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "lmPwdHistory",
+ io.g.lm_history,
+ io.g.lm_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.supplemental.length > 0) {
+ ret = ldb_msg_add_value(msg, "supplementalCredentials",
+ &io.g.supplemental, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = samdb_msg_add_uint64(ac->module->ldb, ac, msg,
+ "pwdLastSet",
+ io.g.last_set);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = samdb_msg_add_uint(ac->module->ldb, ac, msg,
+ "msDs-KeyVersionNumber",
+ io.g.kvno);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = PH_MOD_DO_MOD;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->mod_req);
+
+ /* perform the search */
+ return ldb_next_request(ac->module, ac->mod_req);
+}
+
+static int ph_wait(struct ldb_handle *handle) {
+ struct ph_context *ac;
+ int ret;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct ph_context);
+
+ switch (ac->step) {
+ case PH_ADD_SEARCH_DOM:
+ ret = ldb_wait(ac->dom_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->dom_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->dom_req->handle->status;
+ goto done;
+ }
+
+ if (ac->dom_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* domain search done, go on */
+ return password_hash_add_do_add(handle);
+
+ case PH_ADD_DO_ADD:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case PH_MOD_DO_REQ:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* non-password mods done, go on */
+ return password_hash_mod_search_self(handle);
+
+ case PH_MOD_SEARCH_SELF:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ if (ac->search_res == NULL) {
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /* self search done, go on */
+ return password_hash_mod_search_dom(handle);
+
+ case PH_MOD_SEARCH_DOM:
+ ret = ldb_wait(ac->dom_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->dom_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->dom_req->handle->status;
+ goto done;
+ }
+
+ if (ac->dom_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* domain search done, go on */
+ return password_hash_mod_do_mod(handle);
+
+ case PH_MOD_DO_MOD:
+ ret = ldb_wait(ac->mod_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->mod_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->mod_req->handle->status;
+ goto done;
+ }
+
+ if (ac->mod_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int ph_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = ph_wait(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int password_hash_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return ph_wait_all(handle);
+ } else {
+ return ph_wait(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_password_hash_module_ops = {
+ .name = "password_hash",
+ .add = password_hash_add,
+ .modify = password_hash_modify,
+ .wait = password_hash_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/password_modules.h b/source4/dsdb/samdb/ldb_modules/password_modules.h
new file mode 100644
index 0000000000..40d0144416
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/password_modules.h
@@ -0,0 +1,3 @@
+/* We store these passwords under this base DN: */
+
+#define LOCAL_BASE "cn=Passwords"
diff --git a/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c b/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c
new file mode 100644
index 0000000000..09d56d77c9
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c
@@ -0,0 +1,121 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The module that handles the PDC FSMO Role Owner checkings
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/util/dlinklist.h"
+
+static int pdc_fsmo_init(struct ldb_module *module)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ldb_dn *pdc_dn;
+ struct dsdb_pdc_fsmo *pdc_fsmo;
+ struct ldb_result *pdc_res;
+ int ret;
+ static const char *pdc_attrs[] = {
+ "fSMORoleOwner",
+ NULL
+ };
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pdc_dn = samdb_base_dn(module->ldb);
+ if (!pdc_dn) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "pdc_fsmo_init: no domain dn present: (skip loading of domain details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ pdc_fsmo = talloc_zero(mem_ctx, struct dsdb_pdc_fsmo);
+ if (!pdc_fsmo) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ module->private_data = pdc_fsmo;
+
+ ret = ldb_search(module->ldb, pdc_dn,
+ LDB_SCOPE_BASE,
+ NULL, pdc_attrs,
+ &pdc_res);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "pdc_fsmo_init: no domain object present: (skip loading of domain details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ } else if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "pdc_fsmo_init: failed to search the domain object: %d:%s",
+ ret, ldb_strerror(ret));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, pdc_res);
+ if (pdc_res->count == 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "pdc_fsmo_init: no domain object present: (skip loading of domain details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ } else if (pdc_res->count > 1) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "pdc_fsmo_init: [%u] domain objects found on a base search",
+ pdc_res->count);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ pdc_fsmo->master_dn = ldb_msg_find_attr_as_dn(module->ldb, mem_ctx, pdc_res->msgs[0], "fSMORoleOwner");
+ if (ldb_dn_compare(samdb_ntds_settings_dn(module->ldb), pdc_fsmo->master_dn) == 0) {
+ pdc_fsmo->we_are_master = true;
+ } else {
+ pdc_fsmo->we_are_master = false;
+ }
+
+ if (ldb_set_opaque(module->ldb, "dsdb_pdc_fsmo", pdc_fsmo) != LDB_SUCCESS) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_steal(module, pdc_fsmo);
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE,
+ "pdc_fsmo_init: we are master: %s\n",
+ (pdc_fsmo->we_are_master?"yes":"no"));
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_pdc_fsmo_module_ops = {
+ .name = "pdc_fsmo",
+ .init_context = pdc_fsmo_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/proxy.c b/source4/dsdb/samdb/ldb_modules/proxy.c
new file mode 100644
index 0000000000..0d065425ca
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/proxy.c
@@ -0,0 +1,337 @@
+/*
+ samdb proxy module
+
+ Copyright (C) Andrew Tridgell 2005
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ ldb proxy module. At startup this looks for a record like this:
+
+ dn=@PROXYINFO
+ url=destination url
+ olddn = basedn to proxy in upstream server
+ newdn = basedn in local server
+ username = username to connect to upstream
+ password = password for upstream
+
+ NOTE: this module is a complete hack at this stage. I am committing it just
+ so others can know how I am investigating mmc support
+
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "auth/credentials/credentials.h"
+
+struct proxy_data {
+ struct ldb_context *upstream;
+ struct ldb_dn *olddn;
+ struct ldb_dn *newdn;
+ const char **oldstr;
+ const char **newstr;
+};
+
+
+/*
+ load the @PROXYINFO record
+*/
+static int load_proxy_info(struct ldb_module *module)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ struct ldb_dn *dn;
+ struct ldb_result *res = NULL;
+ int ret;
+ const char *olddn, *newdn, *url, *username, *password, *oldstr, *newstr;
+ struct cli_credentials *creds;
+
+
+ /* see if we have already loaded it */
+ if (proxy->upstream != NULL) {
+ return 0;
+ }
+
+ dn = ldb_dn_new(proxy, module->ldb, "@PROXYINFO");
+ if (dn == NULL) {
+ goto failed;
+ }
+ ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE, NULL, NULL, &res);
+ talloc_free(dn);
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Can't find @PROXYINFO\n");
+ goto failed;
+ }
+
+ url = ldb_msg_find_attr_as_string(res->msgs[0], "url", NULL);
+ olddn = ldb_msg_find_attr_as_string(res->msgs[0], "olddn", NULL);
+ newdn = ldb_msg_find_attr_as_string(res->msgs[0], "newdn", NULL);
+ username = ldb_msg_find_attr_as_string(res->msgs[0], "username", NULL);
+ password = ldb_msg_find_attr_as_string(res->msgs[0], "password", NULL);
+ oldstr = ldb_msg_find_attr_as_string(res->msgs[0], "oldstr", NULL);
+ newstr = ldb_msg_find_attr_as_string(res->msgs[0], "newstr", NULL);
+
+ if (url == NULL || olddn == NULL || newdn == NULL || username == NULL || password == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Need url, olddn, newdn, oldstr, newstr, username and password in @PROXYINFO\n");
+ goto failed;
+ }
+
+ proxy->olddn = ldb_dn_new(proxy, module->ldb, olddn);
+ if (proxy->olddn == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode olddn '%s'\n", olddn);
+ goto failed;
+ }
+
+ proxy->newdn = ldb_dn_new(proxy, module->ldb, newdn);
+ if (proxy->newdn == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode newdn '%s'\n", newdn);
+ goto failed;
+ }
+
+ proxy->upstream = ldb_init(proxy);
+ if (proxy->upstream == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+
+ proxy->oldstr = str_list_make(proxy, oldstr, ", ");
+ if (proxy->oldstr == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+
+ proxy->newstr = str_list_make(proxy, newstr, ", ");
+ if (proxy->newstr == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+
+ /* setup credentials for connection */
+ creds = cli_credentials_init(proxy->upstream);
+ if (creds == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+ cli_credentials_guess(creds, ldb_get_opaque(module->ldb, "loadparm"));
+ cli_credentials_set_username(creds, username, CRED_SPECIFIED);
+ cli_credentials_set_password(creds, password, CRED_SPECIFIED);
+
+ ldb_set_opaque(proxy->upstream, "credentials", creds);
+
+ ret = ldb_connect(proxy->upstream, url, 0, NULL);
+ if (ret != 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url);
+ goto failed;
+ }
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy connected to %s\n", url);
+
+ talloc_free(res);
+
+ return 0;
+
+failed:
+ talloc_free(res);
+ talloc_free(proxy->olddn);
+ talloc_free(proxy->newdn);
+ talloc_free(proxy->upstream);
+ proxy->upstream = NULL;
+ return -1;
+}
+
+
+/*
+ convert a binary blob
+*/
+static void proxy_convert_blob(TALLOC_CTX *mem_ctx, struct ldb_val *v,
+ const char *oldstr, const char *newstr)
+{
+ int len1, len2, len3;
+ uint8_t *olddata = v->data;
+ char *p = strcasestr((char *)v->data, oldstr);
+
+ len1 = (p - (char *)v->data);
+ len2 = strlen(newstr);
+ len3 = v->length - (p+strlen(oldstr) - (char *)v->data);
+ v->length = len1+len2+len3;
+ v->data = talloc_size(mem_ctx, v->length);
+ memcpy(v->data, olddata, len1);
+ memcpy(v->data+len1, newstr, len2);
+ memcpy(v->data+len1+len2, olddata + len1 + strlen(oldstr), len3);
+}
+
+/*
+ convert a returned value
+*/
+static void proxy_convert_value(struct ldb_module *module, struct ldb_message *msg, struct ldb_val *v)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ int i;
+ for (i=0;proxy->oldstr[i];i++) {
+ char *p = strcasestr((char *)v->data, proxy->oldstr[i]);
+ if (p == NULL) continue;
+ proxy_convert_blob(msg, v, proxy->oldstr[i], proxy->newstr[i]);
+ }
+}
+
+
+/*
+ convert a returned value
+*/
+static struct ldb_parse_tree *proxy_convert_tree(struct ldb_module *module,
+ struct ldb_parse_tree *tree)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ int i;
+ char *expression = ldb_filter_from_tree(module, tree);
+ for (i=0;proxy->newstr[i];i++) {
+ struct ldb_val v;
+ char *p = strcasestr(expression, proxy->newstr[i]);
+ if (p == NULL) continue;
+ v.data = (uint8_t *)expression;
+ v.length = strlen(expression)+1;
+ proxy_convert_blob(module, &v, proxy->newstr[i], proxy->oldstr[i]);
+ return ldb_parse_tree(module, (const char *)v.data);
+ }
+ return tree;
+}
+
+
+
+/*
+ convert a returned record
+*/
+static void proxy_convert_record(struct ldb_module *module, struct ldb_message *msg)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ int attr, v;
+
+ /* fix the message DN */
+ if (ldb_dn_compare_base(module->ldb, proxy->olddn, msg->dn) == 0) {
+ ldb_dn_remove_base_components(msg->dn, ldb_dn_get_comp_num(proxy->olddn));
+ ldb_dn_add_base(msg->dn, proxy->newdn);
+ }
+
+ /* fix any attributes */
+ for (attr=0;attr<msg->num_elements;attr++) {
+ for (v=0;v<msg->elements[attr].num_values;v++) {
+ proxy_convert_value(module, msg, &msg->elements[attr].values[v]);
+ }
+ }
+
+ /* fix any DN components */
+ for (attr=0;attr<msg->num_elements;attr++) {
+ for (v=0;v<msg->elements[attr].num_values;v++) {
+ proxy_convert_value(module, msg, &msg->elements[attr].values[v]);
+ }
+ }
+}
+
+/* search */
+static int proxy_search_bytree(struct ldb_module *module, struct ldb_request *req)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ struct ldb_request *newreq;
+ struct ldb_dn *base;
+ int ret, i;
+
+ if (req->op.search.base == NULL ||
+ (req->op.search.base->comp_num == 1 &&
+ req->op.search.base->components[0].name[0] == '@')) {
+ goto passthru;
+ }
+
+ if (load_proxy_info(module) != 0) {
+ return -1;
+ }
+
+ /* see if the dn is within olddn */
+ if (ldb_dn_compare_base(module->ldb, proxy->newdn, req->op.search.base) != 0) {
+ goto passthru;
+ }
+
+ newreq = talloc(module, struct ldb_request);
+ if (newreq == NULL) {
+ return -1;
+ }
+
+ newreq->op.search.tree = proxy_convert_tree(module, req->op.search.tree);
+
+ /* convert the basedn of this search */
+ base = ldb_dn_copy(proxy, req->op.search.base);
+ if (base == NULL) {
+ talloc_free(newreq);
+ goto failed;
+ }
+ ldb_dn_remove_base_components(base, ldb_dn_get_comp_num(proxy->newdn));
+ ldb_dn_add_base(base, proxy->olddn);
+
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxying: '%s' with dn '%s' \n",
+ ldb_filter_from_tree(proxy, newreq->op.search.tree), ldb_dn_get_linearized(newreq->op.search.base));
+ for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "attr: '%s'\n", req->op.search.attrs[i]);
+ }
+
+ newreq->op.search.base = base;
+ newreq->op.search.scope = req->op.search.scope;
+ newreq->op.search.attrs = req->op.search.attrs;
+ newreq->op.search.res = req->op.search.res;
+ newreq->controls = req->controls;
+ ret = ldb_request(proxy->upstream, newreq);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(module->ldb, ldb_errstring(proxy->upstream));
+ talloc_free(newreq);
+ return -1;
+ }
+
+ for (i = 0; i < newreq->op.search.res->count; i++) {
+ printf("# record %d\n", i+1);
+
+ proxy_convert_record(module, newreq->op.search.res->msgs[i]);
+ }
+
+ talloc_free(newreq);
+ return ret;
+
+failed:
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy failed for %s\n",
+ ldb_dn_get_linearized(req->op.search.base));
+
+passthru:
+ return ldb_next_request(module, req);
+}
+
+static int proxy_request(struct ldb_module *module, struct ldb_request *req)
+{
+ switch (req->operation) {
+
+ case LDB_REQ_SEARCH:
+ return proxy_search_bytree(module, req);
+
+ default:
+ return ldb_next_request(module, req);
+
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_proxy_module_ops = {
+ .name = "proxy",
+ .request = proxy_request
+};
diff --git a/source4/dsdb/samdb/ldb_modules/ranged_results.c b/source4/dsdb/samdb/ldb_modules/ranged_results.c
new file mode 100644
index 0000000000..c6ebea1044
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/ranged_results.c
@@ -0,0 +1,207 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb ranged results module
+ *
+ * Description: munge AD-style 'ranged results' requests into
+ * requests for all values in an attribute, then return the range to
+ * the client.
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "ldb_includes.h"
+
+struct rr_context {
+ struct ldb_request *orig_req;
+ struct ldb_request *down_req;
+};
+
+static int rr_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct rr_context *rr_context = talloc_get_type(context, struct rr_context);
+ struct ldb_request *orig_req = rr_context->orig_req;
+ int i, j;
+
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return rr_context->orig_req->callback(ldb, rr_context->orig_req->context, ares);
+ }
+
+ /* Find those that are range requests from the attribute list */
+ for (i = 0; orig_req->op.search.attrs[i]; i++) {
+ char *p, *new_attr;
+ const char *end_str;
+ unsigned int start, end, orig_num_values;
+ struct ldb_message_element *el;
+ struct ldb_val *orig_values;
+ p = strchr(orig_req->op.search.attrs[i], ';');
+ if (!p) {
+ continue;
+ }
+ if (strncasecmp(p, ";range=", strlen(";range=")) != 0) {
+ continue;
+ }
+ if (sscanf(p, ";range=%u-%u", &start, &end) == 2) {
+ } else if (sscanf(p, ";range=%u-*", &start) == 1) {
+ end = (unsigned int)-1;
+ } else {
+ continue;
+ }
+ new_attr = talloc_strndup(orig_req,
+ orig_req->op.search.attrs[i],
+ (unsigned int)(p-orig_req->op.search.attrs[i]));
+
+ if (!new_attr) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el = ldb_msg_find_element(ares->message, new_attr);
+ talloc_free(new_attr);
+ if (!el) {
+ continue;
+ }
+ if (start > end) {
+ ldb_asprintf_errstring(ldb, "range request error: start must not be greater than end");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (end >= (el->num_values - 1)) {
+ /* Need to leave the requested attribute in
+ * there (so add an empty one to match) */
+ end_str = "*";
+ end = el->num_values - 1;
+ } else {
+ end_str = talloc_asprintf(el, "%u", end);
+ if (!end_str) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ /* If start is greater then where we noe find the end to be */
+ if (start > end) {
+ el->num_values = 0;
+ el->values = NULL;
+ } else {
+ orig_values = el->values;
+ orig_num_values = el->num_values;
+
+ if ((start + end < start) || (start + end < end)) {
+ ldb_asprintf_errstring(ldb, "range request error: start or end would overflow!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ el->num_values = 0;
+
+ el->values = talloc_array(el, struct ldb_val, (end - start) + 1);
+ if (!el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (j=start; j <= end; j++) {
+ el->values[el->num_values] = orig_values[j];
+ el->num_values++;
+ }
+ }
+ el->name = talloc_asprintf(el, "%s;range=%u-%s", el->name, start, end_str);
+ if (!el->name) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ return rr_context->orig_req->callback(ldb, rr_context->orig_req->context, ares);
+
+}
+
+/* search */
+static int rr_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int i;
+ unsigned int start, end;
+ const char **new_attrs = NULL;
+ struct rr_context *context;
+ bool found_rr = false;
+
+ /* Strip the range request from the attribute */
+ for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) {
+ char *p;
+ new_attrs = talloc_realloc(req, new_attrs, const char *, i+2);
+ new_attrs[i] = req->op.search.attrs[i];
+ new_attrs[i+1] = NULL;
+ p = strchr(req->op.search.attrs[i], ';');
+ if (!p) {
+ continue;
+ }
+ if (strncasecmp(p, ";range=", strlen(";range=")) != 0) {
+ continue;
+ }
+ if (sscanf(p, ";range=%u-%u", &start, &end) == 2) {
+ } else if (sscanf(p, ";range=%u-*", &start) == 1) {
+ end = (unsigned int)-1;
+ } else {
+ ldb_asprintf_errstring(module->ldb, "range request error: range requst malformed");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (start > end) {
+ ldb_asprintf_errstring(module->ldb, "range request error: start must not be greater than end");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ found_rr = true;
+ new_attrs[i] = talloc_strndup(new_attrs,
+ req->op.search.attrs[i],
+ (unsigned int)(p-req->op.search.attrs[i]));
+
+ if (!new_attrs[i]) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ if (found_rr) {
+ int ret;
+ context = talloc(req, struct rr_context);
+ context->orig_req = req;
+ context->down_req = talloc(context, struct ldb_request);
+ *context->down_req = *req;
+
+ context->down_req->op.search.attrs = new_attrs;
+
+ context->down_req->callback = rr_search_callback;
+ context->down_req->context = context;
+
+ ret = ldb_next_request(module, context->down_req);
+
+ /* We don't need to implement our own 'wait' function, so pass the handle along */
+ if (ret == LDB_SUCCESS) {
+ req->handle = context->down_req->handle;
+ }
+ return ret;
+ }
+
+ /* No change, just run the original request as if we were never here */
+ return ldb_next_request(module, req);
+}
+
+const struct ldb_module_ops ldb_ranged_results_module_ops = {
+ .name = "ranged_results",
+ .search = rr_search,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
new file mode 100644
index 0000000000..dd5faf837a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -0,0 +1,1588 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb repl_meta_data module
+ *
+ * Description: - add a unique objectGUID onto every new record,
+ * - handle whenCreated, whenChanged timestamps
+ * - handle uSNCreated, uSNChanged numbers
+ * - handle replPropertyMetaData attribute
+ *
+ * Author: Simo Sorce
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+
+struct replmd_replicated_request {
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ const struct dsdb_schema *schema;
+
+ struct dsdb_extended_replicated_objects *objs;
+
+ uint32_t index_current;
+
+ struct {
+ TALLOC_CTX *mem_ctx;
+ struct ldb_request *search_req;
+ struct ldb_message *search_msg;
+ int search_ret;
+ struct ldb_request *change_req;
+ int change_ret;
+ } sub;
+};
+
+static struct replmd_replicated_request *replmd_replicated_init_handle(struct ldb_module *module,
+ struct ldb_request *req,
+ struct dsdb_extended_replicated_objects *objs)
+{
+ struct replmd_replicated_request *ar;
+ struct ldb_handle *h;
+ const struct dsdb_schema *schema;
+
+ schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "replmd_replicated_init_handle: no loaded schema found\n");
+ return NULL;
+ }
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+ h->state = LDB_ASYNC_PENDING;
+ h->status = LDB_SUCCESS;
+
+ ar = talloc_zero(h, struct replmd_replicated_request);
+ if (ar == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ar;
+
+ ar->module = module;
+ ar->handle = h;
+ ar->orig_req = req;
+ ar->schema = schema;
+ ar->objs = objs;
+
+ req->handle = h;
+
+ return ar;
+}
+
+/*
+ add a time element to a record
+*/
+static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
+{
+ struct ldb_message_element *el;
+ char *s;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ s = ldb_timestring(msg, t);
+ if (s == NULL) {
+ return -1;
+ }
+
+ if (ldb_msg_add_string(msg, attr, s) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+/*
+ add a uint64_t element to a record
+*/
+static int add_uint64_element(struct ldb_message *msg, const char *attr, uint64_t v)
+{
+ struct ldb_message_element *el;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ if (ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)v) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMetaData1 *m1,
+ const struct replPropertyMetaData1 *m2,
+ const uint32_t *rdn_attid)
+{
+ if (m1->attid == m2->attid) {
+ return 0;
+ }
+
+ /*
+ * the rdn attribute should be at the end!
+ * so we need to return a value greater than zero
+ * which means m1 is greater than m2
+ */
+ if (m1->attid == *rdn_attid) {
+ return 1;
+ }
+
+ /*
+ * the rdn attribute should be at the end!
+ * so we need to return a value less than zero
+ * which means m2 is greater than m1
+ */
+ if (m2->attid == *rdn_attid) {
+ return -1;
+ }
+
+ return m1->attid - m2->attid;
+}
+
+static void replmd_replPropertyMetaDataCtr1_sort(struct replPropertyMetaDataCtr1 *ctr1,
+ const uint32_t *rdn_attid)
+{
+ ldb_qsort(ctr1->array, ctr1->count, sizeof(struct replPropertyMetaData1),
+ discard_const_p(void, rdn_attid), (ldb_qsort_cmp_fn_t)replmd_replPropertyMetaData1_attid_sort);
+}
+
+static int replmd_ldb_message_element_attid_sort(const struct ldb_message_element *e1,
+ const struct ldb_message_element *e2,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_attribute *a1;
+ const struct dsdb_attribute *a2;
+
+ /*
+ * TODO: make this faster by caching the dsdb_attribute pointer
+ * on the ldb_messag_element
+ */
+
+ a1 = dsdb_attribute_by_lDAPDisplayName(schema, e1->name);
+ a2 = dsdb_attribute_by_lDAPDisplayName(schema, e2->name);
+
+ /*
+ * TODO: remove this check, we should rely on e1 and e2 having valid attribute names
+ * in the schema
+ */
+ if (!a1 || !a2) {
+ return strcasecmp(e1->name, e2->name);
+ }
+
+ return a1->attributeID_id - a2->attributeID_id;
+}
+
+static void replmd_ldb_message_sort(struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ ldb_qsort(msg->elements, msg->num_elements, sizeof(struct ldb_message_element),
+ discard_const_p(void, schema), (ldb_qsort_cmp_fn_t)replmd_ldb_message_element_attid_sort);
+}
+
+static int replmd_prepare_originating(struct ldb_module *module, struct ldb_request *req,
+ struct ldb_dn *dn, const char *fn_name,
+ int (*fn)(struct ldb_module *,
+ struct ldb_request *,
+ const struct dsdb_schema *))
+{
+ const struct dsdb_schema *schema;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "%s: no dsdb_schema loaded",
+ fn_name);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ return fn(module, req, schema);
+}
+
+static int replmd_add_originating(struct ldb_module *module,
+ struct ldb_request *req,
+ const struct dsdb_schema *schema)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ const struct dsdb_attribute *rdn_attr = NULL;
+ struct GUID guid;
+ struct ldb_val guid_value;
+ struct replPropertyMetaDataBlob nmd;
+ struct ldb_val nmd_value;
+ uint64_t seq_num;
+ const struct GUID *our_invocation_id;
+ time_t t = time(NULL);
+ NTTIME now;
+ char *time_str;
+ int ret;
+ uint32_t i, ni=0;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "replmd_add_originating\n");
+
+ if (ldb_msg_find_element(req->op.add.message, "objectGUID")) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "replmd_add_originating: it's not allowed to add an object with objectGUID\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* a new GUID */
+ guid = GUID_random();
+
+ /* get our invicationId */
+ our_invocation_id = samdb_ntds_invocation_id(module->ldb);
+ if (!our_invocation_id) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "replmd_add_originating: unable to find invocationId\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* create a copy of the request */
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* generated times */
+ unix_to_nt_time(&now, t);
+ time_str = ldb_timestring(msg, t);
+ if (!time_str) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * remove autogenerated attributes
+ */
+ ldb_msg_remove_attr(msg, "whenCreated");
+ ldb_msg_remove_attr(msg, "whenChanged");
+ ldb_msg_remove_attr(msg, "uSNCreated");
+ ldb_msg_remove_attr(msg, "uSNChanged");
+ ldb_msg_remove_attr(msg, "replPropertyMetaData");
+
+ /*
+ * readd replicated attributes
+ */
+ ret = ldb_msg_add_string(msg, "whenCreated", time_str);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the replication meta_data */
+ ZERO_STRUCT(nmd);
+ nmd.version = 1;
+ nmd.ctr.ctr1.count = msg->num_elements;
+ nmd.ctr.ctr1.array = talloc_array(msg,
+ struct replPropertyMetaData1,
+ nmd.ctr.ctr1.count);
+ if (!nmd.ctr.ctr1.array) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < msg->num_elements; i++) {
+ struct ldb_message_element *e = &msg->elements[i];
+ struct replPropertyMetaData1 *m = &nmd.ctr.ctr1.array[ni];
+ const struct dsdb_attribute *sa;
+
+ if (e->name[0] == '@') continue;
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema, e->name);
+ if (!sa) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "replmd_add_originating: attribute '%s' not defined in schema\n",
+ e->name);
+ talloc_free(down_req);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ((sa->systemFlags & 0x00000001) || (sa->systemFlags & 0x00000004)) {
+ /* if the attribute is not replicated (0x00000001)
+ * or constructed (0x00000004) it has no metadata
+ */
+ continue;
+ }
+
+ m->attid = sa->attributeID_id;
+ m->version = 1;
+ m->originating_change_time = now;
+ m->originating_invocation_id = *our_invocation_id;
+ m->originating_usn = seq_num;
+ m->local_usn = seq_num;
+ ni++;
+
+ if (ldb_attr_cmp(e->name, ldb_dn_get_rdn_name(msg->dn))) {
+ rdn_attr = sa;
+ }
+ }
+
+ /* fix meta data count */
+ nmd.ctr.ctr1.count = ni;
+
+ /*
+ * sort meta data array, and move the rdn attribute entry to the end
+ */
+ replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, &rdn_attr->attributeID_id);
+
+ /* generated NDR encoded values */
+ ndr_err = ndr_push_struct_blob(&guid_value, msg,
+ NULL,
+ &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ndr_err = ndr_push_struct_blob(&nmd_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ &nmd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * add the autogenerated values
+ */
+ ret = ldb_msg_add_value(msg, "objectGUID", &guid_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_string(msg, "whenChanged", time_str);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = samdb_msg_add_uint64(module->ldb, msg, msg, "uSNCreated", seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = samdb_msg_add_uint64(module->ldb, msg, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &nmd_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * sort the attributes by attid before storing the object
+ */
+ replmd_ldb_message_sort(msg, schema);
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int replmd_add(struct ldb_module *module, struct ldb_request *req)
+{
+ return replmd_prepare_originating(module, req, req->op.add.message->dn,
+ "replmd_add", replmd_add_originating);
+}
+
+static int replmd_modify_originating(struct ldb_module *module,
+ struct ldb_request *req,
+ const struct dsdb_schema *schema)
+{
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ int ret;
+ time_t t = time(NULL);
+ uint64_t seq_num;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "replmd_modify_originating\n");
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* TODO:
+ * - get the whole old object
+ * - if the old object doesn't exist report an error
+ * - give an error when a readonly attribute should
+ * be modified
+ * - merge the changed into the old object
+ * if the caller set values to the same value
+ * ignore the attribute, return success when no
+ * attribute was changed
+ * - calculate the new replPropertyMetaData attribute
+ */
+
+ if (add_time_element(msg, "whenChanged", t) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /* TODO:
+ * - sort the attributes by attid with replmd_ldb_message_sort()
+ * - replace the old object with the newly constructed one
+ */
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ return replmd_prepare_originating(module, req, req->op.mod.message->dn,
+ "replmd_modify", replmd_modify_originating);
+}
+
+static int replmd_replicated_request_reply_helper(struct replmd_replicated_request *ar, int ret)
+{
+ struct ldb_reply *ares = NULL;
+
+ ar->handle->status = ret;
+ ar->handle->state = LDB_ASYNC_DONE;
+
+ if (!ar->orig_req->callback) {
+ return LDB_SUCCESS;
+ }
+
+ /* we're done and need to report the success to the caller */
+ ares = talloc_zero(ar, struct ldb_reply);
+ if (!ares) {
+ ar->handle->status = LDB_ERR_OPERATIONS_ERROR;
+ ar->handle->state = LDB_ASYNC_DONE;
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ares->type = LDB_REPLY_EXTENDED;
+ ares->response = NULL;
+
+ return ar->orig_req->callback(ar->module->ldb, ar->orig_req->context, ares);
+}
+
+static int replmd_replicated_request_done(struct replmd_replicated_request *ar)
+{
+ return replmd_replicated_request_reply_helper(ar, LDB_SUCCESS);
+}
+
+static int replmd_replicated_request_error(struct replmd_replicated_request *ar, int ret)
+{
+ return replmd_replicated_request_reply_helper(ar, ret);
+}
+
+static int replmd_replicated_request_werror(struct replmd_replicated_request *ar, WERROR status)
+{
+ int ret = LDB_ERR_OTHER;
+ /* TODO: do some error mapping */
+ return replmd_replicated_request_reply_helper(ar, ret);
+}
+
+static int replmd_replicated_apply_next(struct replmd_replicated_request *ar);
+
+static int replmd_replicated_apply_add_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return replmd_replicated_apply_next(ar);
+#else
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replPropertyMetaDataBlob *md;
+ struct ldb_val md_value;
+ uint32_t i;
+ uint64_t seq_num;
+ int ret;
+
+ /*
+ * TODO: check if the parent object exist
+ */
+
+ /*
+ * TODO: handle the conflict case where an object with the
+ * same name exist
+ */
+
+ msg = ar->objs->objects[ar->index_current].msg;
+ md = ar->objs->objects[ar->index_current].meta_data;
+
+ ret = ldb_sequence_number(ar->module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = ldb_msg_add_value(msg, "objectGUID", &ar->objs->objects[ar->index_current].guid_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = ldb_msg_add_string(msg, "whenChanged", ar->objs->objects[ar->index_current].when_changed);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = samdb_msg_add_uint64(ar->module->ldb, msg, msg, "uSNCreated", seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = samdb_msg_add_uint64(ar->module->ldb, msg, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /*
+ * the meta data array is already sorted by the caller
+ */
+ for (i=0; i < md->ctr.ctr1.count; i++) {
+ md->ctr.ctr1.array[i].local_usn = seq_num;
+ }
+ ndr_err = ndr_push_struct_blob(&md_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ md,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &md_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ replmd_ldb_message_sort(msg, ar->schema);
+
+ ret = ldb_build_add_req(&ar->sub.change_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ msg,
+ NULL,
+ ar,
+ replmd_replicated_apply_add_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.change_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ar->module->ldb, "Failed to add replicated object %s: %s", ldb_dn_get_linearized(ar->sub.change_req->op.add.message->dn),
+ ldb_errstring(ar->module->ldb));
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ar->sub.change_ret = ldb_wait(ar->sub.change_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ar->module->ldb, "Failed while waiting on add replicated object %s: %s", ldb_dn_get_linearized(ar->sub.change_req->op.add.message->dn),
+ ldb_errstring(ar->module->ldb));
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replPropertyMetaData1_conflict_compare(struct replPropertyMetaData1 *m1,
+ struct replPropertyMetaData1 *m2)
+{
+ int ret;
+
+ if (m1->version != m2->version) {
+ return m1->version - m2->version;
+ }
+
+ if (m1->originating_change_time != m2->originating_change_time) {
+ return m1->originating_change_time - m2->originating_change_time;
+ }
+
+ ret = GUID_compare(&m1->originating_invocation_id, &m2->originating_invocation_id);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return m1->originating_usn - m2->originating_usn;
+}
+
+static int replmd_replicated_apply_merge_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return LDB_SUCCESS;
+#else
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replPropertyMetaDataBlob *rmd;
+ struct replPropertyMetaDataBlob omd;
+ const struct ldb_val *omd_value;
+ struct replPropertyMetaDataBlob nmd;
+ struct ldb_val nmd_value;
+ uint32_t i,j,ni=0;
+ uint32_t removed_attrs = 0;
+ uint64_t seq_num;
+ int ret;
+
+ msg = ar->objs->objects[ar->index_current].msg;
+ rmd = ar->objs->objects[ar->index_current].meta_data;
+ ZERO_STRUCT(omd);
+ omd.version = 1;
+
+ /*
+ * TODO: add rename conflict handling
+ */
+ if (ldb_dn_compare(msg->dn, ar->sub.search_msg->dn) != 0) {
+ ldb_debug_set(ar->module->ldb, LDB_DEBUG_FATAL, "replmd_replicated_apply_merge[%u]: rename not supported",
+ ar->index_current);
+ ldb_debug(ar->module->ldb, LDB_DEBUG_FATAL, "%s => %s\n",
+ ldb_dn_get_linearized(ar->sub.search_msg->dn),
+ ldb_dn_get_linearized(msg->dn));
+ return replmd_replicated_request_werror(ar, WERR_NOT_SUPPORTED);
+ }
+
+ ret = ldb_sequence_number(ar->module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /* find existing meta data */
+ omd_value = ldb_msg_find_ldb_val(ar->sub.search_msg, "replPropertyMetaData");
+ if (omd_value) {
+ ndr_err = ndr_pull_struct_blob(omd_value, ar->sub.mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")), &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ if (omd.version != 1) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+ }
+
+ ZERO_STRUCT(nmd);
+ nmd.version = 1;
+ nmd.ctr.ctr1.count = omd.ctr.ctr1.count + rmd->ctr.ctr1.count;
+ nmd.ctr.ctr1.array = talloc_array(ar->sub.mem_ctx,
+ struct replPropertyMetaData1,
+ nmd.ctr.ctr1.count);
+ if (!nmd.ctr.ctr1.array) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ /* first copy the old meta data */
+ for (i=0; i < omd.ctr.ctr1.count; i++) {
+ nmd.ctr.ctr1.array[ni] = omd.ctr.ctr1.array[i];
+ ni++;
+ }
+
+ /* now merge in the new meta data */
+ for (i=0; i < rmd->ctr.ctr1.count; i++) {
+ bool found = false;
+
+ rmd->ctr.ctr1.array[i].local_usn = seq_num;
+
+ for (j=0; j < ni; j++) {
+ int cmp;
+
+ if (rmd->ctr.ctr1.array[i].attid != nmd.ctr.ctr1.array[j].attid) {
+ continue;
+ }
+
+ cmp = replmd_replPropertyMetaData1_conflict_compare(&rmd->ctr.ctr1.array[i],
+ &nmd.ctr.ctr1.array[j]);
+ if (cmp > 0) {
+ /* replace the entry */
+ nmd.ctr.ctr1.array[j] = rmd->ctr.ctr1.array[i];
+ found = true;
+ break;
+ }
+
+ /* we don't want to apply this change so remove the attribute */
+ ldb_msg_remove_element(msg, &msg->elements[i-removed_attrs]);
+ removed_attrs++;
+
+ found = true;
+ break;
+ }
+
+ if (found) continue;
+
+ nmd.ctr.ctr1.array[ni] = rmd->ctr.ctr1.array[i];
+ ni++;
+ }
+
+ /*
+ * finally correct the size of the meta_data array
+ */
+ nmd.ctr.ctr1.count = ni;
+
+ /*
+ * the rdn attribute (the alias for the name attribute),
+ * 'cn' for most objects is the last entry in the meta data array
+ * we have stored
+ *
+ * sort the new meta data array
+ */
+ {
+ struct replPropertyMetaData1 *rdn_p;
+ uint32_t rdn_idx = omd.ctr.ctr1.count - 1;
+
+ rdn_p = &nmd.ctr.ctr1.array[rdn_idx];
+ replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, &rdn_p->attid);
+ }
+
+ /* create the meta data value */
+ ndr_err = ndr_push_struct_blob(&nmd_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ &nmd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ /*
+ * check if some replicated attributes left, otherwise skip the ldb_modify() call
+ */
+ if (msg->num_elements == 0) {
+ ldb_debug(ar->module->ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: skip replace\n",
+ ar->index_current);
+ goto next_object;
+ }
+
+ ldb_debug(ar->module->ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n",
+ ar->index_current, msg->num_elements);
+
+ /*
+ * when we now that we'll modify the record, add the whenChanged, uSNChanged
+ * and replPopertyMetaData attributes
+ */
+ ret = ldb_msg_add_string(msg, "whenChanged", ar->objs->objects[ar->index_current].when_changed);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ ret = samdb_msg_add_uint64(ar->module->ldb, msg, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &nmd_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ replmd_ldb_message_sort(msg, ar->schema);
+
+ /* we want to replace the old values */
+ for (i=0; i < msg->num_elements; i++) {
+ msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ ret = ldb_build_mod_req(&ar->sub.change_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ msg,
+ NULL,
+ ar,
+ replmd_replicated_apply_merge_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.change_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.change_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+next_object:
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replicated_apply_search_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+ bool is_done = false;
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ar->sub.search_msg = talloc_steal(ar->sub.mem_ctx, ares->message);
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+ case LDB_REPLY_EXTENDED:
+ case LDB_REPLY_DONE:
+ is_done = true;
+ }
+
+ talloc_free(ares);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ if (is_done) {
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (ar->sub.search_msg) {
+ return replmd_replicated_apply_merge(ar);
+ }
+ return replmd_replicated_apply_add(ar);
+ }
+#endif
+ return LDB_SUCCESS;
+}
+
+static int replmd_replicated_apply_search(struct replmd_replicated_request *ar)
+{
+ int ret;
+ char *tmp_str;
+ char *filter;
+
+ tmp_str = ldb_binary_encode(ar->sub.mem_ctx, ar->objs->objects[ar->index_current].guid_value);
+ if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ filter = talloc_asprintf(ar->sub.mem_ctx, "(objectGUID=%s)", tmp_str);
+ if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ talloc_free(tmp_str);
+
+ ret = ldb_build_search_req(&ar->sub.search_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ ar->objs->partition_dn,
+ LDB_SCOPE_SUBTREE,
+ filter,
+ NULL,
+ NULL,
+ ar,
+ replmd_replicated_apply_search_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.search_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.search_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS && ar->sub.search_ret != LDB_ERR_NO_SUCH_OBJECT) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (ar->sub.search_msg) {
+ return replmd_replicated_apply_merge(ar);
+ }
+
+ return replmd_replicated_apply_add(ar);
+#endif
+}
+
+static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ if (ar->index_current >= ar->objs->num_objects) {
+ return replmd_replicated_uptodate_vector(ar);
+ }
+#endif
+
+ ar->sub.mem_ctx = talloc_new(ar);
+ if (!ar->sub.mem_ctx) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ return replmd_replicated_apply_search(ar);
+}
+
+static int replmd_replicated_uptodate_modify_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ return replmd_replicated_request_done(ar);
+#else
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_drsuapi_DsReplicaCursor2_compare(const struct drsuapi_DsReplicaCursor2 *c1,
+ const struct drsuapi_DsReplicaCursor2 *c2)
+{
+ return GUID_compare(&c1->source_dsa_invocation_id, &c2->source_dsa_invocation_id);
+}
+
+static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *ar)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replUpToDateVectorBlob ouv;
+ const struct ldb_val *ouv_value;
+ const struct drsuapi_DsReplicaCursor2CtrEx *ruv;
+ struct replUpToDateVectorBlob nuv;
+ struct ldb_val nuv_value;
+ struct ldb_message_element *nuv_el = NULL;
+ const struct GUID *our_invocation_id;
+ struct ldb_message_element *orf_el = NULL;
+ struct repsFromToBlob nrf;
+ struct ldb_val *nrf_value = NULL;
+ struct ldb_message_element *nrf_el = NULL;
+ uint32_t i,j,ni=0;
+ uint64_t seq_num;
+ bool found = false;
+ time_t t = time(NULL);
+ NTTIME now;
+ int ret;
+
+ ruv = ar->objs->uptodateness_vector;
+ ZERO_STRUCT(ouv);
+ ouv.version = 2;
+ ZERO_STRUCT(nuv);
+ nuv.version = 2;
+
+ unix_to_nt_time(&now, t);
+
+ /*
+ * we use the next sequence number for our own highest_usn
+ * because we will do a modify request and this will increment
+ * our highest_usn
+ */
+ ret = ldb_sequence_number(ar->module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /*
+ * first create the new replUpToDateVector
+ */
+ ouv_value = ldb_msg_find_ldb_val(ar->sub.search_msg, "replUpToDateVector");
+ if (ouv_value) {
+ ndr_err = ndr_pull_struct_blob(ouv_value, ar->sub.mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")), &ouv,
+ (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ if (ouv.version != 2) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+ }
+
+ /*
+ * the new uptodateness vector will at least
+ * contain 1 entry, one for the source_dsa
+ *
+ * plus optional values from our old vector and the one from the source_dsa
+ */
+ nuv.ctr.ctr2.count = 1 + ouv.ctr.ctr2.count;
+ if (ruv) nuv.ctr.ctr2.count += ruv->count;
+ nuv.ctr.ctr2.cursors = talloc_array(ar->sub.mem_ctx,
+ struct drsuapi_DsReplicaCursor2,
+ nuv.ctr.ctr2.count);
+ if (!nuv.ctr.ctr2.cursors) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ /* first copy the old vector */
+ for (i=0; i < ouv.ctr.ctr2.count; i++) {
+ nuv.ctr.ctr2.cursors[ni] = ouv.ctr.ctr2.cursors[i];
+ ni++;
+ }
+
+ /* get our invocation_id if we have one already attached to the ldb */
+ our_invocation_id = samdb_ntds_invocation_id(ar->module->ldb);
+
+ /* merge in the source_dsa vector is available */
+ for (i=0; (ruv && i < ruv->count); i++) {
+ found = false;
+
+ if (our_invocation_id &&
+ GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
+ our_invocation_id)) {
+ continue;
+ }
+
+ for (j=0; j < ni; j++) {
+ if (!GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
+ &nuv.ctr.ctr2.cursors[j].source_dsa_invocation_id)) {
+ continue;
+ }
+
+ found = true;
+
+ /*
+ * we update only the highest_usn and not the latest_sync_success time,
+ * because the last success stands for direct replication
+ */
+ if (ruv->cursors[i].highest_usn > nuv.ctr.ctr2.cursors[j].highest_usn) {
+ nuv.ctr.ctr2.cursors[j].highest_usn = ruv->cursors[i].highest_usn;
+ }
+ break;
+ }
+
+ if (found) continue;
+
+ /* if it's not there yet, add it */
+ nuv.ctr.ctr2.cursors[ni] = ruv->cursors[i];
+ ni++;
+ }
+
+ /*
+ * merge in the current highwatermark for the source_dsa
+ */
+ found = false;
+ for (j=0; j < ni; j++) {
+ if (!GUID_equal(&ar->objs->source_dsa->source_dsa_invocation_id,
+ &nuv.ctr.ctr2.cursors[j].source_dsa_invocation_id)) {
+ continue;
+ }
+
+ found = true;
+
+ /*
+ * here we update the highest_usn and last_sync_success time
+ * because we're directly replicating from the source_dsa
+ *
+ * and use the tmp_highest_usn because this is what we have just applied
+ * to our ldb
+ */
+ nuv.ctr.ctr2.cursors[j].highest_usn = ar->objs->source_dsa->highwatermark.tmp_highest_usn;
+ nuv.ctr.ctr2.cursors[j].last_sync_success = now;
+ break;
+ }
+ if (!found) {
+ /*
+ * here we update the highest_usn and last_sync_success time
+ * because we're directly replicating from the source_dsa
+ *
+ * and use the tmp_highest_usn because this is what we have just applied
+ * to our ldb
+ */
+ nuv.ctr.ctr2.cursors[ni].source_dsa_invocation_id= ar->objs->source_dsa->source_dsa_invocation_id;
+ nuv.ctr.ctr2.cursors[ni].highest_usn = ar->objs->source_dsa->highwatermark.tmp_highest_usn;
+ nuv.ctr.ctr2.cursors[ni].last_sync_success = now;
+ ni++;
+ }
+
+ /*
+ * finally correct the size of the cursors array
+ */
+ nuv.ctr.ctr2.count = ni;
+
+ /*
+ * sort the cursors
+ */
+ qsort(nuv.ctr.ctr2.cursors, nuv.ctr.ctr2.count,
+ sizeof(struct drsuapi_DsReplicaCursor2),
+ (comparison_fn_t)replmd_drsuapi_DsReplicaCursor2_compare);
+
+ /*
+ * create the change ldb_message
+ */
+ msg = ldb_msg_new(ar->sub.mem_ctx);
+ if (!msg) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ msg->dn = ar->sub.search_msg->dn;
+
+ ndr_err = ndr_push_struct_blob(&nuv_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ &nuv,
+ (ndr_push_flags_fn_t)ndr_push_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+ ret = ldb_msg_add_value(msg, "replUpToDateVector", &nuv_value, &nuv_el);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ nuv_el->flags = LDB_FLAG_MOD_REPLACE;
+
+ /*
+ * now create the new repsFrom value from the given repsFromTo1 structure
+ */
+ ZERO_STRUCT(nrf);
+ nrf.version = 1;
+ nrf.ctr.ctr1 = *ar->objs->source_dsa;
+ /* and fix some values... */
+ nrf.ctr.ctr1.consecutive_sync_failures = 0;
+ nrf.ctr.ctr1.last_success = now;
+ nrf.ctr.ctr1.last_attempt = now;
+ nrf.ctr.ctr1.result_last_attempt = WERR_OK;
+ nrf.ctr.ctr1.highwatermark.highest_usn = nrf.ctr.ctr1.highwatermark.tmp_highest_usn;
+
+ /*
+ * first see if we already have a repsFrom value for the current source dsa
+ * if so we'll later replace this value
+ */
+ orf_el = ldb_msg_find_element(ar->sub.search_msg, "repsFrom");
+ if (orf_el) {
+ for (i=0; i < orf_el->num_values; i++) {
+ struct repsFromToBlob *trf;
+
+ trf = talloc(ar->sub.mem_ctx, struct repsFromToBlob);
+ if (!trf) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ ndr_err = ndr_pull_struct_blob(&orf_el->values[i], trf, lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")), trf,
+ (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ if (trf->version != 1) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ /*
+ * we compare the source dsa objectGUID not the invocation_id
+ * because we want only one repsFrom value per source dsa
+ * and when the invocation_id of the source dsa has changed we don't need
+ * the old repsFrom with the old invocation_id
+ */
+ if (!GUID_equal(&trf->ctr.ctr1.source_dsa_obj_guid,
+ &ar->objs->source_dsa->source_dsa_obj_guid)) {
+ talloc_free(trf);
+ continue;
+ }
+
+ talloc_free(trf);
+ nrf_value = &orf_el->values[i];
+ break;
+ }
+
+ /*
+ * copy over all old values to the new ldb_message
+ */
+ ret = ldb_msg_add_empty(msg, "repsFrom", 0, &nrf_el);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+ *nrf_el = *orf_el;
+ }
+
+ /*
+ * if we haven't found an old repsFrom value for the current source dsa
+ * we'll add a new value
+ */
+ if (!nrf_value) {
+ struct ldb_val zero_value;
+ ZERO_STRUCT(zero_value);
+ ret = ldb_msg_add_value(msg, "repsFrom", &zero_value, &nrf_el);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ nrf_value = &nrf_el->values[nrf_el->num_values - 1];
+ }
+
+ /* we now fill the value which is already attached to ldb_message */
+ ndr_err = ndr_push_struct_blob(nrf_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ &nrf,
+ (ndr_push_flags_fn_t)ndr_push_repsFromToBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ /*
+ * the ldb_message_element for the attribute, has all the old values and the new one
+ * so we'll replace the whole attribute with all values
+ */
+ nrf_el->flags = LDB_FLAG_MOD_REPLACE;
+
+ /* prepare the ldb_modify() request */
+ ret = ldb_build_mod_req(&ar->sub.change_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ msg,
+ NULL,
+ ar,
+ replmd_replicated_uptodate_modify_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.change_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ return replmd_replicated_request_done(ar);
+#endif
+}
+
+static int replmd_replicated_uptodate_search_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+ bool is_done = false;
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ar->sub.search_msg = talloc_steal(ar->sub.mem_ctx, ares->message);
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+ case LDB_REPLY_EXTENDED:
+ case LDB_REPLY_DONE:
+ is_done = true;
+ }
+
+ talloc_free(ares);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ if (is_done) {
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (!ar->sub.search_msg) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ return replmd_replicated_uptodate_modify(ar);
+ }
+#endif
+ return LDB_SUCCESS;
+}
+
+static int replmd_replicated_uptodate_search(struct replmd_replicated_request *ar)
+{
+ int ret;
+ static const char *attrs[] = {
+ "replUpToDateVector",
+ "repsFrom",
+ NULL
+ };
+
+ ret = ldb_build_search_req(&ar->sub.search_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ ar->objs->partition_dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ar,
+ replmd_replicated_uptodate_search_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.search_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.search_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (!ar->sub.search_msg) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ return replmd_replicated_uptodate_modify(ar);
+#endif
+}
+
+static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar)
+{
+ ar->sub.mem_ctx = talloc_new(ar);
+ if (!ar->sub.mem_ctx) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ return replmd_replicated_uptodate_search(ar);
+}
+
+static int replmd_extended_replicated_objects(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_extended_replicated_objects *objs;
+ struct replmd_replicated_request *ar;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "replmd_extended_replicated_objects\n");
+
+ objs = talloc_get_type(req->op.extended.data, struct dsdb_extended_replicated_objects);
+ if (!objs) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "replmd_extended_replicated_objects: invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ if (objs->version != DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "replmd_extended_replicated_objects: extended data invalid version [%u != %u]\n",
+ objs->version, DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ar = replmd_replicated_init_handle(module, req, objs);
+ if (!ar) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return replmd_replicated_apply_next(ar);
+#else
+ while (ar->index_current < ar->objs->num_objects &&
+ req->handle->state != LDB_ASYNC_DONE) {
+ replmd_replicated_apply_next(ar);
+ }
+
+ if (req->handle->state != LDB_ASYNC_DONE) {
+ replmd_replicated_uptodate_vector(ar);
+ }
+
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_REPLICATED_OBJECTS_OID) == 0) {
+ return replmd_extended_replicated_objects(module, req);
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int replmd_wait_none(struct ldb_handle *handle) {
+ struct replmd_replicated_request *ar;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ar = talloc_get_type(handle->private_data, struct replmd_replicated_request);
+ if (!ar) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* we do only sync calls */
+ if (handle->state != LDB_ASYNC_DONE) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return handle->status;
+}
+
+static int replmd_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = replmd_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int replmd_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return replmd_wait_all(handle);
+ } else {
+ return replmd_wait_none(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_repl_meta_data_module_ops = {
+ .name = "repl_meta_data",
+ .add = replmd_add,
+ .modify = replmd_modify,
+ .extended = replmd_extended,
+ .wait = replmd_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/rootdse.c b/source4/dsdb/samdb/ldb_modules/rootdse.c
new file mode 100644
index 0000000000..ebc90d4cf3
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/rootdse.c
@@ -0,0 +1,441 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ rootDSE ldb module
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Simo Sorce 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "system/time.h"
+#include "dsdb/samdb/samdb.h"
+#include "version.h"
+
+struct private_data {
+ int num_controls;
+ char **controls;
+ int num_partitions;
+ struct ldb_dn **partitions;
+};
+
+/*
+ return 1 if a specific attribute has been requested
+*/
+static int do_attribute(const char * const *attrs, const char *name)
+{
+ return attrs == NULL ||
+ ldb_attr_in_list(attrs, name) ||
+ ldb_attr_in_list(attrs, "*");
+}
+
+static int do_attribute_explicit(const char * const *attrs, const char *name)
+{
+ return attrs != NULL && ldb_attr_in_list(attrs, name);
+}
+
+
+/*
+ add dynamically generated attributes to rootDSE result
+*/
+static int rootdse_add_dynamic(struct ldb_module *module, struct ldb_message *msg, const char * const *attrs)
+{
+ struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
+ char **server_sasl;
+ const struct dsdb_schema *schema;
+
+ schema = dsdb_get_schema(module->ldb);
+
+ msg->dn = ldb_dn_new(msg, module->ldb, NULL);
+
+ /* don't return the distinduishedName, cn and name attributes */
+ ldb_msg_remove_attr(msg, "distinguishedName");
+ ldb_msg_remove_attr(msg, "cn");
+ ldb_msg_remove_attr(msg, "name");
+
+ if (do_attribute(attrs, "currentTime")) {
+ if (ldb_msg_add_steal_string(msg, "currentTime",
+ ldb_timestring(msg, time(NULL))) != 0) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "supportedControl")) {
+ int i;
+ for (i = 0; i < priv->num_controls; i++) {
+ char *control = talloc_strdup(msg, priv->controls[i]);
+ if (!control) {
+ goto failed;
+ }
+ if (ldb_msg_add_steal_string(msg, "supportedControl",
+ control) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ if (do_attribute(attrs, "namingContexts")) {
+ int i;
+ for (i = 0; i < priv->num_partitions; i++) {
+ struct ldb_dn *dn = priv->partitions[i];
+ if (ldb_msg_add_steal_string(msg, "namingContexts",
+ ldb_dn_alloc_linearized(msg, dn)) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ server_sasl = talloc_get_type(ldb_get_opaque(module->ldb, "supportedSASLMechanims"),
+ char *);
+ if (server_sasl && do_attribute(attrs, "supportedSASLMechanisms")) {
+ int i;
+ for (i = 0; server_sasl && server_sasl[i]; i++) {
+ char *sasl_name = talloc_strdup(msg, server_sasl[i]);
+ if (!sasl_name) {
+ goto failed;
+ }
+ if (ldb_msg_add_steal_string(msg, "supportedSASLMechanisms",
+ sasl_name) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ if (do_attribute(attrs, "highestCommittedUSN")) {
+ uint64_t seq_num;
+ int ret = ldb_sequence_number(module->ldb, LDB_SEQ_HIGHEST_SEQ, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (ldb_msg_add_fmt(msg, "highestCommittedUSN",
+ "%llu", (unsigned long long)seq_num) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaAttrCount")) {
+ struct dsdb_attribute *cur;
+ uint32_t n = 0;
+
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ n++;
+ }
+
+ if (ldb_msg_add_fmt(msg, "dsSchemaAttrCount",
+ "%u", n) != 0) {
+ goto failed;
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaClassCount")) {
+ struct dsdb_class *cur;
+ uint32_t n = 0;
+
+ for (cur = schema->classes; cur; cur = cur->next) {
+ n++;
+ }
+
+ if (ldb_msg_add_fmt(msg, "dsSchemaClassCount",
+ "%u", n) != 0) {
+ goto failed;
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaPrefixCount")) {
+ if (ldb_msg_add_fmt(msg, "dsSchemaPrefixCount",
+ "%u", schema->num_prefixes) != 0) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute_explicit(attrs, "validFSMOs")) {
+ const struct dsdb_naming_fsmo *naming_fsmo;
+ const struct dsdb_pdc_fsmo *pdc_fsmo;
+ const char *dn_str;
+
+ if (schema && schema->fsmo.we_are_master) {
+ dn_str = ldb_dn_get_linearized(samdb_schema_dn(module->ldb));
+ if (dn_str && dn_str[0]) {
+ if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ naming_fsmo = talloc_get_type(ldb_get_opaque(module->ldb, "dsdb_naming_fsmo"),
+ struct dsdb_naming_fsmo);
+ if (naming_fsmo && naming_fsmo->we_are_master) {
+ dn_str = ldb_dn_get_linearized(samdb_partitions_dn(module->ldb, msg));
+ if (dn_str && dn_str[0]) {
+ if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ pdc_fsmo = talloc_get_type(ldb_get_opaque(module->ldb, "dsdb_pdc_fsmo"),
+ struct dsdb_pdc_fsmo);
+ if (pdc_fsmo && pdc_fsmo->we_are_master) {
+ dn_str = ldb_dn_get_linearized(samdb_base_dn(module->ldb));
+ if (dn_str && dn_str[0]) {
+ if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) {
+ goto failed;
+ }
+ }
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "vendorVersion")) {
+ if (ldb_msg_add_fmt(msg, "vendorVersion",
+ "%s", SAMBA_VERSION_STRING) != 0) {
+ goto failed;
+ }
+ }
+
+ /* TODO: lots more dynamic attributes should be added here */
+
+ return LDB_SUCCESS;
+
+failed:
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+/*
+ handle search requests
+*/
+
+struct rootdse_context {
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ const char * const * attrs;
+};
+
+static int rootdse_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct rootdse_context *ac;
+
+ ac = talloc_get_type(context, struct rootdse_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ /*
+ * if the client explicit asks for the 'netlogon' attribute
+ * the reply_entry needs to be skipped
+ */
+ if (ac->attrs && ldb_attr_in_list(ac->attrs, "netlogon")) {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ /* for each record returned post-process to add any dynamic
+ attributes that have been asked for */
+ if (rootdse_add_dynamic(ac->module, ares->message, ac->attrs) != LDB_SUCCESS) {
+ goto error;
+ }
+ }
+
+ return ac->up_callback(ldb, ac->up_context, ares);
+
+error:
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+static int rootdse_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct rootdse_context *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ /* see if its for the rootDSE - only a base search on the "" DN qualifies */
+ if (req->op.search.scope != LDB_SCOPE_BASE ||
+ ( ! ldb_dn_is_null(req->op.search.base))) {
+ /* Otherwise, pass down to the rest of the stack */
+ return ldb_next_request(module, req);
+ }
+
+ ac = talloc(req, struct rootdse_context);
+ if (ac == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->module = module;
+ ac->up_context = req->context;
+ ac->up_callback = req->callback;
+ ac->attrs = req->op.search.attrs;
+
+ down_req = talloc_zero(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->operation = req->operation;
+ /* in our db we store the rootDSE with a DN of @ROOTDSE */
+ down_req->op.search.base = ldb_dn_new(down_req, module->ldb, "@ROOTDSE");
+ down_req->op.search.scope = LDB_SCOPE_BASE;
+ down_req->op.search.tree = ldb_parse_tree(down_req, NULL);
+ if (down_req->op.search.base == NULL || down_req->op.search.tree == NULL) {
+ ldb_oom(module->ldb);
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ down_req->op.search.attrs = req->op.search.attrs;
+ down_req->controls = req->controls;
+
+ down_req->context = ac;
+ down_req->callback = rootdse_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req)
+{
+ struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
+ char **list;
+
+ list = talloc_realloc(priv, priv->controls, char *, priv->num_controls + 1);
+ if (!list) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ list[priv->num_controls] = talloc_strdup(list, req->op.reg_control.oid);
+ if (!list[priv->num_controls]) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ priv->num_controls += 1;
+ priv->controls = list;
+
+ return LDB_SUCCESS;
+}
+
+static int rootdse_register_partition(struct ldb_module *module, struct ldb_request *req)
+{
+ struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
+ struct ldb_dn **list;
+
+ list = talloc_realloc(priv, priv->partitions, struct ldb_dn *, priv->num_partitions + 1);
+ if (!list) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ list[priv->num_partitions] = ldb_dn_copy(list, req->op.reg_partition.dn);
+ if (!list[priv->num_partitions]) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ priv->num_partitions += 1;
+ priv->partitions = list;
+
+ return LDB_SUCCESS;
+}
+
+
+static int rootdse_request(struct ldb_module *module, struct ldb_request *req)
+{
+ switch (req->operation) {
+
+ case LDB_REQ_REGISTER_CONTROL:
+ return rootdse_register_control(module, req);
+ case LDB_REQ_REGISTER_PARTITION:
+ return rootdse_register_partition(module, req);
+
+ default:
+ break;
+ }
+ return ldb_next_request(module, req);
+}
+
+static int rootdse_init(struct ldb_module *module)
+{
+ struct private_data *data;
+
+ data = talloc(module, struct private_data);
+ if (data == NULL) {
+ return -1;
+ }
+
+ data->num_controls = 0;
+ data->controls = NULL;
+ data->num_partitions = 0;
+ data->partitions = NULL;
+ module->private_data = data;
+
+ ldb_set_default_dns(module->ldb);
+
+ return ldb_next_init(module);
+}
+
+static int rootdse_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_result *ext_res;
+ int ret;
+ struct ldb_dn *schema_dn;
+ struct ldb_message_element *schemaUpdateNowAttr;
+
+ /*
+ If dn is not "" we should let it pass through
+ */
+ if (!ldb_dn_is_null(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ dn is empty so check for schemaUpdateNow attribute
+ "The type of modification and values specified in the LDAP modify operation do not matter." MSDN
+ */
+ schemaUpdateNowAttr = ldb_msg_find_element(req->op.mod.message, "schemaUpdateNow");
+ if (!schemaUpdateNowAttr) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ schema_dn = samdb_schema_dn(module->ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "rootdse_modify: no schema dn present: (skip ldb_extended call)\n");
+ return ldb_next_request(module, req);
+ }
+
+ ret = ldb_extended(module->ldb, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID, schema_dn, &ext_res);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(ext_res);
+ return ret;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_rootdse_module_ops = {
+ .name = "rootdse",
+ .init_context = rootdse_init,
+ .search = rootdse_search,
+ .request = rootdse_request,
+ .modify = rootdse_modify
+};
diff --git a/source4/dsdb/samdb/ldb_modules/samba3sam.c b/source4/dsdb/samdb/ldb_modules/samba3sam.c
new file mode 100644
index 0000000000..7a123c818f
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samba3sam.c
@@ -0,0 +1,932 @@
+/*
+ ldb database library - Samba3 SAM compatibility backend
+
+ Copyright (C) Jelmer Vernooij 2005
+ Copyright (C) Martin Kuehl <mkhl@samba.org> 2006
+*/
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_private.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/ldb_map/ldb_map.h"
+#include "system/passwd.h"
+
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/ndr_samr.h"
+#include "librpc/ndr/libndr.h"
+#include "libcli/security/security.h"
+#include "libcli/security/proto.h"
+#include "lib/samba3/samba3.h"
+
+/*
+ * sambaSID -> member (dn!)
+ * sambaSIDList -> member (dn!)
+ * sambaDomainName -> name
+ * sambaTrustPassword
+ * sambaUnixIdPool
+ * sambaIdmapEntry
+ * sambaAccountPolicy
+ * sambaSidEntry
+ * sambaAcctFlags -> systemFlags ?
+ * sambaPasswordHistory -> ntPwdHistory*/
+
+/* Not necessary:
+ * sambaConfig
+ * sambaShare
+ * sambaConfigOption
+ * sambaNextGroupRid
+ * sambaNextUserRid
+ * sambaAlgorithmicRidBase
+ */
+
+/* Not in Samba4:
+ * sambaKickoffTime
+ * sambaPwdCanChange
+ * sambaPwdMustChange
+ * sambaHomePath
+ * sambaHomeDrive
+ * sambaLogonScript
+ * sambaProfilePath
+ * sambaUserWorkstations
+ * sambaMungedDial
+ * sambaLogonHours */
+
+/* In Samba4 but not in Samba3:
+*/
+
+/* From a sambaPrimaryGroupSID, generate a primaryGroupID (integer) attribute */
+static struct ldb_message_element *generate_primaryGroupID(struct ldb_module *module, TALLOC_CTX *ctx, const char *local_attr, const struct ldb_message *remote)
+{
+ struct ldb_message_element *el;
+ const char *sid = ldb_msg_find_attr_as_string(remote, "sambaPrimaryGroupSID", NULL);
+ const char *p;
+
+ if (!sid)
+ return NULL;
+
+ p = strrchr(sid, '-');
+ if (!p)
+ return NULL;
+
+ el = talloc_zero(ctx, struct ldb_message_element);
+ el->name = talloc_strdup(ctx, "primaryGroupID");
+ el->num_values = 1;
+ el->values = talloc_array(ctx, struct ldb_val, 1);
+ el->values[0].data = (uint8_t *)talloc_strdup(el->values, p+1);
+ el->values[0].length = strlen((char *)el->values[0].data);
+
+ return el;
+}
+
+static void generate_sambaPrimaryGroupSID(struct ldb_module *module, const char *local_attr, const struct ldb_message *local, struct ldb_message *remote_mp, struct ldb_message *remote_fb)
+{
+ const struct ldb_val *sidval;
+ char *sidstring;
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+
+ /* We need the domain, so we get it from the objectSid that we hope is here... */
+ sidval = ldb_msg_find_ldb_val(local, "objectSid");
+
+ if (!sidval)
+ return; /* Sorry, no SID today.. */
+
+ sid = talloc(remote_mp, struct dom_sid);
+ if (sid == NULL) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob(sidval, sid, NULL, sid, (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(sid);
+ return;
+ }
+
+ if (!ldb_msg_find_ldb_val(local, "primaryGroupID"))
+ return; /* Sorry, no SID today.. */
+
+ sid->num_auths--;
+
+ sidstring = dom_sid_string(remote_mp, sid);
+ talloc_free(sid);
+ ldb_msg_add_fmt(remote_mp, "sambaPrimaryGroupSID", "%s-%d", sidstring, ldb_msg_find_attr_as_uint(local, "primaryGroupID", 0));
+ talloc_free(sidstring);
+}
+
+/* Just copy the old value. */
+static struct ldb_val convert_uid_samaccount(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ ldb_handler_copy(module->ldb, ctx, val, &out);
+
+ return out;
+}
+
+static struct ldb_val lookup_homedir(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING, "Unable to lookup '%s' in passwd", (char *)val->data);
+ return *talloc_zero(ctx, struct ldb_val);
+ }
+
+ retval.data = (uint8_t *)talloc_strdup(ctx, pwd->pw_dir);
+ retval.length = strlen((char *)retval.data);
+
+ return retval;
+}
+
+static struct ldb_val lookup_gid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ return *talloc_zero(ctx, struct ldb_val);
+ }
+
+ retval.data = (uint8_t *)talloc_asprintf(ctx, "%ld", (unsigned long)pwd->pw_gid);
+ retval.length = strlen((char *)retval.data);
+
+ return retval;
+}
+
+static struct ldb_val lookup_uid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ return *talloc_zero(ctx, struct ldb_val);
+ }
+
+ retval.data = (uint8_t *)talloc_asprintf(ctx, "%ld", (unsigned long)pwd->pw_uid);
+ retval.length = strlen((char *)retval.data);
+
+ return retval;
+}
+
+/* Encode a sambaSID to an objectSid. */
+static struct ldb_val encode_sid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+
+ sid = dom_sid_parse_talloc(ctx, (char *)val->data);
+ if (sid == NULL) {
+ return out;
+ }
+
+ ndr_err = ndr_push_struct_blob(&out, ctx,
+ NULL,
+ sid, (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ talloc_free(sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return out;
+ }
+
+ return out;
+}
+
+/* Decode an objectSid to a sambaSID. */
+static struct ldb_val decode_sid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+
+ sid = talloc(ctx, struct dom_sid);
+ if (sid == NULL) {
+ return out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(val, sid, NULL, sid,
+ (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ goto done;
+ }
+
+ out.data = (uint8_t *)dom_sid_string(ctx, sid);
+ if (out.data == NULL) {
+ goto done;
+ }
+ out.length = strlen((const char *)out.data);
+
+done:
+ talloc_free(sid);
+ return out;
+}
+
+/* Convert 16 bytes to 32 hex digits. */
+static struct ldb_val bin2hex(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ struct samr_Password pwd;
+ if (val->length != sizeof(pwd.hash)) {
+ return data_blob(NULL, 0);
+ }
+ memcpy(pwd.hash, val->data, sizeof(pwd.hash));
+ out = data_blob_string_const(smbpasswd_sethexpwd(ctx, &pwd, 0));
+ if (!out.data) {
+ return data_blob(NULL, 0);
+ }
+ return out;
+}
+
+/* Convert 32 hex digits to 16 bytes. */
+static struct ldb_val hex2bin(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ struct samr_Password *pwd;
+ pwd = smbpasswd_gethexpwd(ctx, (const char *)val->data);
+ if (!pwd) {
+ return data_blob(NULL, 0);
+ }
+ out = data_blob_talloc(ctx, pwd->hash, sizeof(pwd->hash));
+ return out;
+}
+
+const struct ldb_map_objectclass samba3_objectclasses[] = {
+ {
+ .local_name = "user",
+ .remote_name = "posixAccount",
+ .base_classes = { "top", NULL },
+ .musts = { "cn", "uid", "uidNumber", "gidNumber", "homeDirectory", NULL },
+ .mays = { "userPassword", "loginShell", "gecos", "description", NULL },
+ },
+ {
+ .local_name = "group",
+ .remote_name = "posixGroup",
+ .base_classes = { "top", NULL },
+ .musts = { "cn", "gidNumber", NULL },
+ .mays = { "userPassword", "memberUid", "description", NULL },
+ },
+ {
+ .local_name = "group",
+ .remote_name = "sambaGroupMapping",
+ .base_classes = { "top", "posixGroup", NULL },
+ .musts = { "gidNumber", "sambaSID", "sambaGroupType", NULL },
+ .mays = { "displayName", "description", "sambaSIDList", NULL },
+ },
+ {
+ .local_name = "user",
+ .remote_name = "sambaSAMAccount",
+ .base_classes = { "top", "posixAccount", NULL },
+ .musts = { "uid", "sambaSID", NULL },
+ .mays = { "cn", "sambaLMPassword", "sambaNTPassword",
+ "sambaPwdLastSet", "sambaLogonTime", "sambaLogoffTime",
+ "sambaKickoffTime", "sambaPwdCanChange", "sambaPwdMustChange",
+ "sambaAcctFlags", "displayName", "sambaHomePath", "sambaHomeDrive",
+ "sambaLogonScript", "sambaProfilePath", "description", "sambaUserWorkstations",
+ "sambaPrimaryGroupSID", "sambaDomainName", "sambaMungedDial",
+ "sambaBadPasswordCount", "sambaBadPasswordTime",
+ "sambaPasswordHistory", "sambaLogonHours", NULL }
+
+ },
+ {
+ .local_name = "domain",
+ .remote_name = "sambaDomain",
+ .base_classes = { "top", NULL },
+ .musts = { "sambaDomainName", "sambaSID", NULL },
+ .mays = { "sambaNextRid", "sambaNextGroupRid", "sambaNextUserRid", "sambaAlgorithmicRidBase", NULL },
+ },
+ { NULL, NULL }
+};
+
+const struct ldb_map_attribute samba3_attributes[] =
+{
+ /* sambaNextRid -> nextRid */
+ {
+ .local_name = "nextRid",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaNextRid",
+ },
+ },
+ },
+
+ /* sambaBadPasswordTime -> badPasswordtime*/
+ {
+ .local_name = "badPasswordTime",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaBadPasswordTime",
+ },
+ },
+ },
+
+ /* sambaLMPassword -> lmPwdHash*/
+ {
+ .local_name = "dBCSPwd",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaLMPassword",
+ .convert_local = bin2hex,
+ .convert_remote = hex2bin,
+ },
+ },
+ },
+
+ /* sambaGroupType -> groupType */
+ {
+ .local_name = "groupType",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaGroupType",
+ },
+ },
+ },
+
+ /* sambaNTPassword -> ntPwdHash*/
+ {
+ .local_name = "ntpwdhash",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaNTPassword",
+ .convert_local = bin2hex,
+ .convert_remote = hex2bin,
+ },
+ },
+ },
+
+ /* sambaPrimaryGroupSID -> primaryGroupID */
+ {
+ .local_name = "primaryGroupID",
+ .type = MAP_GENERATE,
+ .u = {
+ .generate = {
+ .remote_names = { "sambaPrimaryGroupSID", NULL },
+ .generate_local = generate_primaryGroupID,
+ .generate_remote = generate_sambaPrimaryGroupSID,
+ },
+ },
+ },
+
+ /* sambaBadPasswordCount -> badPwdCount */
+ {
+ .local_name = "badPwdCount",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaBadPasswordCount",
+ },
+ },
+ },
+
+ /* sambaLogonTime -> lastLogon*/
+ {
+ .local_name = "lastLogon",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaLogonTime",
+ },
+ },
+ },
+
+ /* sambaLogoffTime -> lastLogoff*/
+ {
+ .local_name = "lastLogoff",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaLogoffTime",
+ },
+ },
+ },
+
+ /* uid -> unixName */
+ {
+ .local_name = "unixName",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "uid",
+ },
+ },
+ },
+
+ /* displayName -> name */
+ {
+ .local_name = "name",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "displayName",
+ },
+ },
+ },
+
+ /* cn */
+ {
+ .local_name = "cn",
+ .type = MAP_KEEP,
+ },
+
+ /* sAMAccountName -> cn */
+ {
+ .local_name = "sAMAccountName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "uid",
+ .convert_remote = convert_uid_samaccount,
+ },
+ },
+ },
+
+ /* objectCategory */
+ {
+ .local_name = "objectCategory",
+ .type = MAP_IGNORE,
+ },
+
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = MAP_IGNORE,
+ },
+
+ /* objectVersion */
+ {
+ .local_name = "objectVersion",
+ .type = MAP_IGNORE,
+ },
+
+ /* codePage */
+ {
+ .local_name = "codePage",
+ .type = MAP_IGNORE,
+ },
+
+ /* dNSHostName */
+ {
+ .local_name = "dNSHostName",
+ .type = MAP_IGNORE,
+ },
+
+
+ /* dnsDomain */
+ {
+ .local_name = "dnsDomain",
+ .type = MAP_IGNORE,
+ },
+
+ /* dnsRoot */
+ {
+ .local_name = "dnsRoot",
+ .type = MAP_IGNORE,
+ },
+
+ /* countryCode */
+ {
+ .local_name = "countryCode",
+ .type = MAP_IGNORE,
+ },
+
+ /* nTMixedDomain */
+ {
+ .local_name = "nTMixedDomain",
+ .type = MAP_IGNORE,
+ },
+
+ /* operatingSystem */
+ {
+ .local_name = "operatingSystem",
+ .type = MAP_IGNORE,
+ },
+
+ /* operatingSystemVersion */
+ {
+ .local_name = "operatingSystemVersion",
+ .type = MAP_IGNORE,
+ },
+
+
+ /* servicePrincipalName */
+ {
+ .local_name = "servicePrincipalName",
+ .type = MAP_IGNORE,
+ },
+
+ /* msDS-Behavior-Version */
+ {
+ .local_name = "msDS-Behavior-Version",
+ .type = MAP_IGNORE,
+ },
+
+ /* msDS-KeyVersionNumber */
+ {
+ .local_name = "msDS-KeyVersionNumber",
+ .type = MAP_IGNORE,
+ },
+
+ /* msDs-masteredBy */
+ {
+ .local_name = "msDs-masteredBy",
+ .type = MAP_IGNORE,
+ },
+
+ /* ou */
+ {
+ .local_name = "ou",
+ .type = MAP_KEEP,
+ },
+
+ /* dc */
+ {
+ .local_name = "dc",
+ .type = MAP_KEEP,
+ },
+
+ /* description */
+ {
+ .local_name = "description",
+ .type = MAP_KEEP,
+ },
+
+ /* sambaSID -> objectSid*/
+ {
+ .local_name = "objectSid",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaSID",
+ .convert_local = decode_sid,
+ .convert_remote = encode_sid,
+ },
+ },
+ },
+
+ /* sambaPwdLastSet -> pwdLastSet */
+ {
+ .local_name = "pwdLastSet",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaPwdLastSet",
+ },
+ },
+ },
+
+ /* accountExpires */
+ {
+ .local_name = "accountExpires",
+ .type = MAP_IGNORE,
+ },
+
+ /* adminCount */
+ {
+ .local_name = "adminCount",
+ .type = MAP_IGNORE,
+ },
+
+ /* canonicalName */
+ {
+ .local_name = "canonicalName",
+ .type = MAP_IGNORE,
+ },
+
+ /* createTimestamp */
+ {
+ .local_name = "createTimestamp",
+ .type = MAP_IGNORE,
+ },
+
+ /* creationTime */
+ {
+ .local_name = "creationTime",
+ .type = MAP_IGNORE,
+ },
+
+ /* dMDLocation */
+ {
+ .local_name = "dMDLocation",
+ .type = MAP_IGNORE,
+ },
+
+ /* fSMORoleOwner */
+ {
+ .local_name = "fSMORoleOwner",
+ .type = MAP_IGNORE,
+ },
+
+ /* forceLogoff */
+ {
+ .local_name = "forceLogoff",
+ .type = MAP_IGNORE,
+ },
+
+ /* instanceType */
+ {
+ .local_name = "instanceType",
+ .type = MAP_IGNORE,
+ },
+
+ /* invocationId */
+ {
+ .local_name = "invocationId",
+ .type = MAP_IGNORE,
+ },
+
+ /* isCriticalSystemObject */
+ {
+ .local_name = "isCriticalSystemObject",
+ .type = MAP_IGNORE,
+ },
+
+ /* localPolicyFlags */
+ {
+ .local_name = "localPolicyFlags",
+ .type = MAP_IGNORE,
+ },
+
+ /* lockOutObservationWindow */
+ {
+ .local_name = "lockOutObservationWindow",
+ .type = MAP_IGNORE,
+ },
+
+ /* lockoutDuration */
+ {
+ .local_name = "lockoutDuration",
+ .type = MAP_IGNORE,
+ },
+
+ /* lockoutThreshold */
+ {
+ .local_name = "lockoutThreshold",
+ .type = MAP_IGNORE,
+ },
+
+ /* logonCount */
+ {
+ .local_name = "logonCount",
+ .type = MAP_IGNORE,
+ },
+
+ /* masteredBy */
+ {
+ .local_name = "masteredBy",
+ .type = MAP_IGNORE,
+ },
+
+ /* maxPwdAge */
+ {
+ .local_name = "maxPwdAge",
+ .type = MAP_IGNORE,
+ },
+
+ /* member */
+ {
+ .local_name = "member",
+ .type = MAP_IGNORE,
+ },
+
+ /* memberOf */
+ {
+ .local_name = "memberOf",
+ .type = MAP_IGNORE,
+ },
+
+ /* minPwdAge */
+ {
+ .local_name = "minPwdAge",
+ .type = MAP_IGNORE,
+ },
+
+ /* minPwdLength */
+ {
+ .local_name = "minPwdLength",
+ .type = MAP_IGNORE,
+ },
+
+ /* modifiedCount */
+ {
+ .local_name = "modifiedCount",
+ .type = MAP_IGNORE,
+ },
+
+ /* modifiedCountAtLastProm */
+ {
+ .local_name = "modifiedCountAtLastProm",
+ .type = MAP_IGNORE,
+ },
+
+ /* modifyTimestamp */
+ {
+ .local_name = "modifyTimestamp",
+ .type = MAP_IGNORE,
+ },
+
+ /* nCName */
+ {
+ .local_name = "nCName",
+ .type = MAP_IGNORE,
+ },
+
+ /* nETBIOSName */
+ {
+ .local_name = "nETBIOSName",
+ .type = MAP_IGNORE,
+ },
+
+ /* oEMInformation */
+ {
+ .local_name = "oEMInformation",
+ .type = MAP_IGNORE,
+ },
+
+ /* privilege */
+ {
+ .local_name = "privilege",
+ .type = MAP_IGNORE,
+ },
+
+ /* pwdHistoryLength */
+ {
+ .local_name = "pwdHistoryLength",
+ .type = MAP_IGNORE,
+ },
+
+ /* pwdProperties */
+ {
+ .local_name = "pwdProperties",
+ .type = MAP_IGNORE,
+ },
+
+ /* rIDAvailablePool */
+ {
+ .local_name = "rIDAvailablePool",
+ .type = MAP_IGNORE,
+ },
+
+ /* revision */
+ {
+ .local_name = "revision",
+ .type = MAP_IGNORE,
+ },
+
+ /* ridManagerReference */
+ {
+ .local_name = "ridManagerReference",
+ .type = MAP_IGNORE,
+ },
+
+ /* sAMAccountType */
+ {
+ .local_name = "sAMAccountType",
+ .type = MAP_IGNORE,
+ },
+
+ /* sPNMappings */
+ {
+ .local_name = "sPNMappings",
+ .type = MAP_IGNORE,
+ },
+
+ /* serverReference */
+ {
+ .local_name = "serverReference",
+ .type = MAP_IGNORE,
+ },
+
+ /* serverState */
+ {
+ .local_name = "serverState",
+ .type = MAP_IGNORE,
+ },
+
+ /* showInAdvancedViewOnly */
+ {
+ .local_name = "showInAdvancedViewOnly",
+ .type = MAP_IGNORE,
+ },
+
+ /* subRefs */
+ {
+ .local_name = "subRefs",
+ .type = MAP_IGNORE,
+ },
+
+ /* systemFlags */
+ {
+ .local_name = "systemFlags",
+ .type = MAP_IGNORE,
+ },
+
+ /* uASCompat */
+ {
+ .local_name = "uASCompat",
+ .type = MAP_IGNORE,
+ },
+
+ /* uSNChanged */
+ {
+ .local_name = "uSNChanged",
+ .type = MAP_IGNORE,
+ },
+
+ /* uSNCreated */
+ {
+ .local_name = "uSNCreated",
+ .type = MAP_IGNORE,
+ },
+
+ /* userPassword */
+ {
+ .local_name = "userPassword",
+ .type = MAP_IGNORE,
+ },
+
+ /* userAccountControl */
+ {
+ .local_name = "userAccountControl",
+ .type = MAP_IGNORE,
+ },
+
+ /* whenChanged */
+ {
+ .local_name = "whenChanged",
+ .type = MAP_IGNORE,
+ },
+
+ /* whenCreated */
+ {
+ .local_name = "whenCreated",
+ .type = MAP_IGNORE,
+ },
+
+ /* uidNumber */
+ {
+ .local_name = "unixName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "uidNumber",
+ .convert_local = lookup_uid,
+ },
+ },
+ },
+
+ /* gidNumber. Perhaps make into generate so we can distinguish between
+ * groups and accounts? */
+ {
+ .local_name = "unixName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "gidNumber",
+ .convert_local = lookup_gid,
+ },
+ },
+ },
+
+ /* homeDirectory */
+ {
+ .local_name = "unixName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "homeDirectory",
+ .convert_local = lookup_homedir,
+ },
+ },
+ },
+ {
+ .local_name = NULL,
+ }
+};
+
+/* the context init function */
+static int samba3sam_init(struct ldb_module *module)
+{
+ int ret;
+
+ ret = ldb_map_init(module, samba3_attributes, samba3_objectclasses, NULL, NULL, "samba3sam");
+ if (ret != LDB_SUCCESS)
+ return ret;
+
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_samba3sam_module_ops = {
+ LDB_MAP_OPS
+ .name = "samba3sam",
+ .init_context = samba3sam_init,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
new file mode 100644
index 0000000000..bd491bd011
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -0,0 +1,825 @@
+/*
+ SAM ldb module
+
+ Copyright (C) Simo Sorce 2004
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+
+ * NOTICE: this module is NOT released under the GNU LGPL license as
+ * other ldb code. This module is release under the GNU GPL v3 or
+ * later license.
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb samldb module
+ *
+ * Description: add embedded user/group creation functionality
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "util/util_ldb.h"
+
+int samldb_notice_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, const struct dom_sid *sid);
+
+static bool samldb_msg_add_sid(struct ldb_module *module, struct ldb_message *msg, const char *name, const struct dom_sid *sid)
+{
+ struct ldb_val v;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(&v, msg, NULL, sid,
+ (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+ return (ldb_msg_add_value(msg, name, &v, NULL) == 0);
+}
+
+/*
+ allocate a new id, attempting to do it atomically
+ return 0 on failure, the id on success
+*/
+static int samldb_set_next_rid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, uint32_t old_id, uint32_t new_id)
+{
+ struct ldb_message msg;
+ int ret;
+ struct ldb_val vals[2];
+ struct ldb_message_element els[2];
+
+ if (new_id == 0) {
+ /* out of IDs ! */
+ ldb_set_errstring(ldb, "Are we out of valid IDs ?\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* we do a delete and add as a single operation. That prevents
+ a race, in case we are not actually on a transaction db */
+ ZERO_STRUCT(msg);
+ msg.dn = ldb_dn_copy(mem_ctx, dn);
+ if (!msg.dn) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg.num_elements = 2;
+ msg.elements = els;
+
+ els[0].num_values = 1;
+ els[0].values = &vals[0];
+ els[0].flags = LDB_FLAG_MOD_DELETE;
+ els[0].name = talloc_strdup(mem_ctx, "nextRid");
+ if (!els[0].name) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ els[1].num_values = 1;
+ els[1].values = &vals[1];
+ els[1].flags = LDB_FLAG_MOD_ADD;
+ els[1].name = els[0].name;
+
+ vals[0].data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", old_id);
+ if (!vals[0].data) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ vals[0].length = strlen((char *)vals[0].data);
+
+ vals[1].data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", new_id);
+ if (!vals[1].data) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ vals[1].length = strlen((char *)vals[1].data);
+
+ ret = ldb_modify(ldb, &msg);
+ return ret;
+}
+
+/*
+ allocate a new id, attempting to do it atomically
+ return 0 on failure, the id on success
+*/
+static int samldb_find_next_rid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, uint32_t *old_rid)
+{
+ const char * const attrs[2] = { "nextRid", NULL };
+ struct ldb_result *res = NULL;
+ int ret;
+ const char *str;
+
+ ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE, "nextRid=*", attrs, &res);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ talloc_free(res);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ str = ldb_msg_find_attr_as_string(res->msgs[0], "nextRid", NULL);
+ if (str == NULL) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute nextRid not found in %s\n",
+ ldb_dn_get_linearized(dn));
+ talloc_free(res);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *old_rid = strtol(str, NULL, 0);
+ talloc_free(res);
+ return LDB_SUCCESS;
+}
+
+static int samldb_allocate_next_rid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, const struct dom_sid *dom_sid,
+ struct dom_sid **new_sid)
+{
+ struct dom_sid *obj_sid;
+ uint32_t old_rid;
+ int ret;
+
+ ret = samldb_find_next_rid(module, mem_ctx, dn, &old_rid);
+ if (ret) {
+ return ret;
+ }
+
+ /* return the new object sid */
+ obj_sid = dom_sid_add_rid(mem_ctx, dom_sid, old_rid);
+
+ *new_sid = dom_sid_add_rid(mem_ctx, dom_sid, old_rid + 1);
+ if (!*new_sid) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samldb_notice_sid(module, mem_ctx, *new_sid);
+ if (ret != 0) {
+ /* gah, there are conflicting sids.
+ * This is a critical situation it means that someone messed up with
+ * the DB and nextRid is not returning free RIDs, report an error
+ * and refuse to create any user until the problem is fixed */
+ ldb_asprintf_errstring(module->ldb,
+ "Critical Error: unconsistent DB, unable to retireve an unique RID to generate a new SID: %s",
+ ldb_errstring(module->ldb));
+ return ret;
+ }
+ return ret;
+}
+
+/* search the domain related to the provided dn
+ allocate a new RID for the domain
+ return the new sid string
+*/
+static int samldb_get_new_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, struct ldb_dn *obj_dn,
+ struct ldb_dn *dom_dn,
+ struct dom_sid **sid)
+{
+ const char * const attrs[2] = { "objectSid", NULL };
+ struct ldb_result *res = NULL;
+ int ret;
+ struct dom_sid *dom_sid;
+
+ /* get the domain component part of the provided dn */
+
+ /* find the domain sid */
+
+ ret = ldb_search(module->ldb, dom_dn, LDB_SCOPE_BASE, "objectSid=*", attrs, &res);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_get_new_sid: error retrieving domain sid from %s: %s!\n",
+ ldb_dn_get_linearized(dom_dn),
+ ldb_errstring(module->ldb));
+ talloc_free(res);
+ return ret;
+ }
+
+ if (res->count != 1) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_get_new_sid: error retrieving domain sid from %s: not found!\n",
+ ldb_dn_get_linearized(dom_dn));
+ talloc_free(res);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ dom_sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid");
+ if (dom_sid == NULL) {
+ ldb_set_errstring(module->ldb, "samldb_get_new_sid: error parsing domain sid!\n");
+ talloc_free(res);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* allocate a new Rid for the domain */
+ ret = samldb_allocate_next_rid(module, mem_ctx, dom_dn, dom_sid, sid);
+ if (ret != 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to increment nextRid of %s: %s\n", ldb_dn_get_linearized(dom_dn), ldb_errstring(module->ldb));
+ talloc_free(res);
+ return ret;
+ }
+
+ talloc_free(res);
+
+ return ret;
+}
+
+/* If we are adding new users/groups, we need to update the nextRid
+ * attribute to be 'above' all incoming users RIDs. This tries to
+ * avoid clashes in future */
+
+int samldb_notice_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, const struct dom_sid *sid)
+{
+ int ret;
+ struct ldb_dn *dom_dn;
+ struct dom_sid *dom_sid;
+ const char *attrs[] = { NULL };
+ struct ldb_result *dom_res;
+ struct ldb_result *res;
+ uint32_t old_rid;
+
+ /* find if this SID already exists */
+ ret = ldb_search_exp_fmt(module->ldb, mem_ctx, &res,
+ NULL, LDB_SCOPE_SUBTREE, attrs,
+ "(objectSid=%s)", ldap_encode_ndr_dom_sid(mem_ctx, sid));
+ if (ret == LDB_SUCCESS) {
+ if (res->count > 0) {
+ talloc_free(res);
+ ldb_asprintf_errstring(module->ldb,
+ "Attempt to add record with SID %s rejected,"
+ " because this SID is already in the database",
+ dom_sid_string(mem_ctx, sid));
+ /* We have a duplicate SID, we must reject the add */
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ talloc_free(res);
+ } else {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_notice_sid: error searching to see if sid %s is in use: %s\n",
+ dom_sid_string(mem_ctx, sid),
+ ldb_errstring(module->ldb));
+ return ret;
+ }
+
+ dom_sid = dom_sid_dup(mem_ctx, sid);
+ if (!dom_sid) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* get the domain component part of the provided SID */
+ dom_sid->num_auths--;
+
+ /* find the domain DN */
+ ret = ldb_search_exp_fmt(module->ldb, mem_ctx, &dom_res,
+ NULL, LDB_SCOPE_SUBTREE, attrs,
+ "(&(objectSid=%s)(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain)))",
+ ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
+ if (ret == LDB_SUCCESS) {
+ if (dom_res->count == 0) {
+ talloc_free(dom_res);
+ /* This isn't an operation on a domain we know about, so nothing to update */
+ return LDB_SUCCESS;
+ }
+
+ if (dom_res->count > 1) {
+ talloc_free(dom_res);
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_notice_sid: error retrieving domain from sid: duplicate (found %d) domain: %s!\n",
+ dom_res->count, dom_sid_string(dom_res, dom_sid));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_notice_sid: error retrieving domain from sid: %s: %s\n",
+ dom_sid_string(dom_res, dom_sid),
+ ldb_errstring(module->ldb));
+ return ret;
+ }
+
+ dom_dn = dom_res->msgs[0]->dn;
+
+ ret = samldb_find_next_rid(module, mem_ctx,
+ dom_dn, &old_rid);
+ if (ret) {
+ talloc_free(dom_res);
+ return ret;
+ }
+
+ if (old_rid <= sid->sub_auths[sid->num_auths - 1]) {
+ ret = samldb_set_next_rid(module->ldb, mem_ctx, dom_dn, old_rid,
+ sid->sub_auths[sid->num_auths - 1] + 1);
+ }
+ talloc_free(dom_res);
+ return ret;
+}
+
+static int samldb_handle_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, struct ldb_message *msg2,
+ struct ldb_dn *parent_dn)
+{
+ int ret;
+
+ struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, msg2, "objectSid");
+ if (sid == NULL) {
+ ret = samldb_get_new_sid(module, msg2, msg2->dn, parent_dn, &sid);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ( ! samldb_msg_add_sid(module, msg2, "objectSid", sid)) {
+ talloc_free(sid);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_free(sid);
+ ret = LDB_SUCCESS;
+ } else {
+ ret = samldb_notice_sid(module, msg2, sid);
+ }
+ return ret;
+}
+
+static int samldb_generate_samAccountName(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dom_dn, char **name)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_result *res;
+ int ret;
+
+ /* Format: $000000-000000000000 */
+
+ do {
+ *name = talloc_asprintf(mem_ctx, "$%.6X-%.6X%.6X", (unsigned int)generate_random(), (unsigned int)generate_random(), (unsigned int)generate_random());
+ /* TODO: Figure out exactly what this is meant to conflict with */
+ ret = ldb_search_exp_fmt(module->ldb,
+ mem_ctx, &res, dom_dn, LDB_SCOPE_SUBTREE, attrs,
+ "samAccountName=%s",
+ ldb_binary_encode_string(mem_ctx, *name));
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb, "samldb: Failure searching to determine if samAccountName %s is unique: %s",
+ *name, ldb_errstring(module->ldb));
+ return ret;
+ }
+
+ if (res->count == 0) {
+ talloc_free(res);
+ /* Great. There are no conflicting users/groups/etc */
+ return LDB_SUCCESS;
+ } else {
+ talloc_free(*name);
+ /* gah, there is a conflicting name, lets move around the loop again... */
+ }
+ } while (1);
+}
+
+static int samldb_fill_group_object(struct ldb_module *module, const struct ldb_message *msg,
+ struct ldb_message **ret_msg)
+{
+ int ret;
+ unsigned int group_type;
+ char *name;
+ struct ldb_message *msg2;
+ struct ldb_dn *dom_dn;
+ const char *rdn_name;
+ TALLOC_CTX *mem_ctx = talloc_new(msg);
+ const char *errstr;
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the new msg */
+ msg2 = ldb_msg_copy(mem_ctx, msg);
+ if (!msg2) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_group_object: ldb_msg_copy failed!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samdb_copy_template(module->ldb, msg2,
+ "group",
+ &errstr);
+ if (ret != 0) {
+
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg2->dn);
+
+ if (strcasecmp(rdn_name, "cn") != 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_group_object: Bad RDN (%s) for group!\n", rdn_name);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = samdb_search_for_parent_domain(module->ldb, mem_ctx, msg2->dn, &dom_dn, &errstr);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_group_object: %s", errstr);
+ return ret;
+ }
+
+ /* Generate a random name, if no samAccountName was supplied */
+ if (ldb_msg_find_element(msg2, "samAccountName") == NULL) {
+ ret = samldb_generate_samAccountName(module, mem_ctx, dom_dn, &name);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ ret = samdb_find_or_add_attribute(module->ldb, msg2, "sAMAccountName", name);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ if (ldb_msg_find_element(msg2, "sAMAccountType") != NULL) {
+ ldb_asprintf_errstring(module->ldb, "sAMAccountType must not be specified");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ group_type = samdb_result_uint(msg2, "groupType", 0);
+ if (group_type == 0) {
+ ldb_asprintf_errstring(module->ldb, "groupType invalid");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ unsigned int account_type = samdb_gtype2atype(group_type);
+ ret = samdb_msg_add_uint(module->ldb, msg2, msg2,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Manage SID allocation, conflicts etc */
+ ret = samldb_handle_sid(module, mem_ctx, msg2, dom_dn);
+
+ if (ret == LDB_SUCCESS) {
+ talloc_steal(msg, msg2);
+ *ret_msg = msg2;
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int samldb_fill_user_or_computer_object(struct ldb_module *module, const struct ldb_message *msg, struct ldb_message **ret_msg)
+{
+ int ret;
+ char *name;
+ struct ldb_message *msg2;
+ struct ldb_dn *dom_dn;
+ const char *rdn_name;
+ TALLOC_CTX *mem_ctx = talloc_new(msg);
+ const char *errstr;
+ unsigned int user_account_control;
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the new msg */
+ msg2 = ldb_msg_copy(mem_ctx, msg);
+ if (!msg2) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_user_or_computer_object: ldb_msg_copy failed!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samdb_copy_template(module->ldb, msg2,
+ "user",
+ &errstr);
+ if (ret) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_user_or_computer_object: Error copying user template: %s\n",
+ errstr);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg2->dn);
+
+ if (strcasecmp(rdn_name, "cn") != 0) {
+ ldb_asprintf_errstring(module->ldb, "Bad RDN (%s=) for user/computer, should be CN=!\n", rdn_name);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = samdb_search_for_parent_domain(module->ldb, mem_ctx, msg2->dn, &dom_dn, &errstr);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_user_or_computer_object: %s", errstr);
+ return ret;
+ }
+
+ if (ldb_msg_find_element(msg2, "samAccountName") == NULL) {
+ ret = samldb_generate_samAccountName(module, mem_ctx, dom_dn, &name);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ ret = samdb_find_or_add_attribute(module->ldb, msg2, "sAMAccountName", name);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ if (ldb_msg_find_element(msg2, "sAMAccountType") != NULL) {
+ ldb_asprintf_errstring(module->ldb, "sAMAccountType must not be specified");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ user_account_control = samdb_result_uint(msg2, "userAccountControl", 0);
+ if (user_account_control == 0) {
+ ldb_asprintf_errstring(module->ldb, "userAccountControl invalid");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ unsigned int account_type = samdb_uf2atype(user_account_control);
+ ret = samdb_msg_add_uint(module->ldb, msg2, msg2,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Manage SID allocation, conflicts etc */
+ ret = samldb_handle_sid(module, mem_ctx, msg2, dom_dn);
+
+ /* TODO: userAccountControl, badPwdCount, codePage, countryCode, badPasswordTime, lastLogoff, lastLogon, pwdLastSet, primaryGroupID, accountExpires, logonCount */
+
+ if (ret == 0) {
+ *ret_msg = msg2;
+ talloc_steal(msg, msg2);
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int samldb_fill_foreignSecurityPrincipal_object(struct ldb_module *module, const struct ldb_message *msg,
+ struct ldb_message **ret_msg)
+{
+ struct ldb_message *msg2;
+ const char *rdn_name;
+ struct dom_sid *dom_sid;
+ struct dom_sid *sid;
+ const char *dom_attrs[] = { "name", NULL };
+ struct ldb_message **dom_msgs;
+ const char *errstr;
+ int ret;
+
+ TALLOC_CTX *mem_ctx = talloc_new(msg);
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the new msg */
+ msg2 = ldb_msg_copy(mem_ctx, msg);
+ if (!msg2) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_foreignSecurityPrincipal_object: ldb_msg_copy failed!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samdb_copy_template(module->ldb, msg2,
+ "ForeignSecurityPrincipal",
+ &errstr);
+ if (ret != 0) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_foreignSecurityPrincipal_object: "
+ "Error copying template: %s",
+ errstr);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg2->dn);
+
+ if (strcasecmp(rdn_name, "cn") != 0) {
+ ldb_asprintf_errstring(module->ldb, "Bad RDN (%s=) for ForeignSecurityPrincipal, should be CN=!", rdn_name);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ sid = samdb_result_dom_sid(msg2, msg, "objectSid");
+ if (!sid) {
+ /* Slightly different for the foreign sids. We don't want
+ * domain SIDs ending up there, it would cause all sorts of
+ * pain */
+
+ sid = dom_sid_parse_talloc(msg2, (const char *)ldb_dn_get_rdn_val(msg2->dn)->data);
+ if (!sid) {
+ ldb_set_errstring(module->ldb, "No valid found SID in ForeignSecurityPrincipal CN!");
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if ( ! samldb_msg_add_sid(module, msg2, "objectSid", sid)) {
+ talloc_free(sid);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ dom_sid = dom_sid_dup(mem_ctx, sid);
+ if (!dom_sid) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* get the domain component part of the provided SID */
+ dom_sid->num_auths--;
+
+ /* find the domain DN */
+
+ ret = gendb_search(module->ldb,
+ mem_ctx, NULL, &dom_msgs, dom_attrs,
+ "(&(objectSid=%s)(objectclass=domain))",
+ ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
+ if (ret >= 1) {
+ /* We don't really like the idea of foreign sids that are not foreign, but it happens */
+ const char *name = samdb_result_string(dom_msgs[0], "name", NULL);
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "NOTE (strange but valid): Adding foreign SID record with SID %s, but this domian (%s) is already in the database",
+ dom_sid_string(mem_ctx, sid), name);
+ } else if (ret == -1) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_foreignSecurityPrincipal_object: error searching for a domain with this sid: %s\n",
+ dom_sid_string(mem_ctx, dom_sid));
+ talloc_free(dom_msgs);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /* This isn't an operation on a domain we know about, so just
+ * check for the SID, looking for duplicates via the common
+ * code */
+ ret = samldb_notice_sid(module, msg2, sid);
+ if (ret == 0) {
+ talloc_steal(msg, msg2);
+ *ret_msg = msg2;
+ }
+
+ return ret;
+}
+
+/* add_record */
+
+/*
+ * FIXME
+ *
+ * Actually this module is not async at all as it does a number of sync searches
+ * in the process. It still to be decided how to deal with it properly so it is
+ * left SYNC for now until we think of a good solution.
+ */
+
+static int samldb_add(struct ldb_module *module, struct ldb_request *req)
+{
+ const struct ldb_message *msg = req->op.add.message;
+ struct ldb_message *msg2 = NULL;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "samldb_add_record\n");
+
+ if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* is user or computer? */
+ if ((samdb_find_attribute(module->ldb, msg, "objectclass", "user") != NULL) ||
+ (samdb_find_attribute(module->ldb, msg, "objectclass", "computer") != NULL)) {
+ /* add all relevant missing objects */
+ ret = samldb_fill_user_or_computer_object(module, msg, &msg2);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ /* is group? add all relevant missing objects */
+ if ( ! msg2 ) {
+ if (samdb_find_attribute(module->ldb, msg, "objectclass", "group") != NULL) {
+ ret = samldb_fill_group_object(module, msg, &msg2);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+
+ /* perhaps a foreignSecurityPrincipal? */
+ if ( ! msg2 ) {
+ if (samdb_find_attribute(module->ldb, msg, "objectclass", "foreignSecurityPrincipal") != NULL) {
+ ret = samldb_fill_foreignSecurityPrincipal_object(module, msg, &msg2);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+
+ if (msg2 == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ down_req->op.add.message = talloc_steal(down_req, msg2);
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+/* modify */
+static int samldb_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_message *msg;
+ struct ldb_message_element *el, *el2;
+ int ret;
+ unsigned int group_type, user_account_control, account_type;
+ if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_msg_find_element(req->op.mod.message, "sAMAccountType") != NULL) {
+ ldb_asprintf_errstring(module->ldb, "sAMAccountType must not be specified");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "groupType");
+ if (el && el->flags & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE) && el->num_values == 1) {
+ req->op.mod.message = msg = ldb_msg_copy_shallow(req, req->op.mod.message);
+
+ group_type = strtoul((const char *)el->values[0].data, NULL, 0);
+ account_type = samdb_gtype2atype(group_type);
+ ret = samdb_msg_add_uint(module->ldb, msg, msg,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el2 = ldb_msg_find_element(msg, "sAMAccountType");
+ el2->flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "userAccountControl");
+ if (el && el->flags & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE) && el->num_values == 1) {
+ req->op.mod.message = msg = ldb_msg_copy_shallow(req, req->op.mod.message);
+
+ user_account_control = strtoul((const char *)el->values[0].data, NULL, 0);
+ account_type = samdb_uf2atype(user_account_control);
+ ret = samdb_msg_add_uint(module->ldb, msg, msg,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el2 = ldb_msg_find_element(msg, "sAMAccountType");
+ el2->flags = LDB_FLAG_MOD_REPLACE;
+ }
+ return ldb_next_request(module, req);
+}
+
+
+static int samldb_init(struct ldb_module *module)
+{
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_samldb_module_ops = {
+ .name = "samldb",
+ .init_context = samldb_init,
+ .add = samldb_add,
+ .modify = samldb_modify
+};
diff --git a/source4/dsdb/samdb/ldb_modules/schema_fsmo.c b/source4/dsdb/samdb/ldb_modules/schema_fsmo.c
new file mode 100644
index 0000000000..968b19c038
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/schema_fsmo.c
@@ -0,0 +1,413 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The module that handles the Schema FSMO Role Owner
+ checkings, it also loads the dsdb_schema.
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/util/dlinklist.h"
+#include "param/param.h"
+
+static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+static int generate_attributeTypes(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+static int generate_dITContentRules(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+
+static const struct {
+ const char *attr;
+ int (*fn)(struct ldb_context *, struct ldb_message *, const struct dsdb_schema *);
+} generated_attrs[] = {
+ {
+ .attr = "objectClasses",
+ .fn = generate_objectClasses
+ },
+ {
+ .attr = "attributeTypes",
+ .fn = generate_attributeTypes
+ },
+ {
+ .attr = "dITContentRules",
+ .fn = generate_dITContentRules
+ }
+};
+
+struct schema_fsmo_private_data {
+ struct ldb_dn *aggregate_dn;
+};
+
+struct schema_fsmo_search_data {
+ struct schema_fsmo_private_data *module_context;
+ struct ldb_request *orig_req;
+};
+
+static int schema_fsmo_init(struct ldb_module *module)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ldb_dn *schema_dn;
+ struct dsdb_schema *schema;
+ char *error_string = NULL;
+ int ret;
+ struct schema_fsmo_private_data *data;
+
+ schema_dn = samdb_schema_dn(module->ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_init: no schema dn present: (skip schema loading)\n");
+ return ldb_next_init(module);
+ }
+
+ data = talloc(module, struct schema_fsmo_private_data);
+ if (data == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Check to see if this is a result on the CN=Aggregate schema */
+ data->aggregate_dn = ldb_dn_copy(data, schema_dn);
+ if (!ldb_dn_add_child_fmt(data->aggregate_dn, "CN=Aggregate")) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ module->private_data = data;
+
+ if (dsdb_get_schema(module->ldb)) {
+ return ldb_next_init(module);
+ }
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_schema_from_schema_dn(mem_ctx, module->ldb,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ schema_dn, &schema, &error_string);
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_init: no schema head present: (skip schema loading)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "schema_fsmo_init: dsdb_schema load failed: %s",
+ error_string);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* dsdb_set_schema() steal schema into the ldb_context */
+ ret = dsdb_set_schema(module->ldb, schema);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "schema_fsmo_init: dsdb_set_schema() failed: %d:%s",
+ ret, ldb_strerror(ret));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+static int schema_fsmo_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_schema *schema;
+ const char *attributeID = NULL;
+ const char *governsID = NULL;
+ const char *oid_attr = NULL;
+ const char *oid = NULL;
+ uint32_t id32;
+ WERROR status;
+
+ schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ if (!schema->fsmo.we_are_master) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "schema_fsmo_add: we are not master: reject request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ attributeID = samdb_result_string(req->op.add.message, "attributeID", NULL);
+ governsID = samdb_result_string(req->op.add.message, "governsID", NULL);
+
+ if (attributeID) {
+ oid_attr = "attributeID";
+ oid = attributeID;
+ } else if (governsID) {
+ oid_attr = "governsID";
+ oid = governsID;
+ }
+
+ if (!oid) {
+ return ldb_next_request(module, req);
+ }
+
+ status = dsdb_map_oid2int(schema, oid, &id32);
+ if (W_ERROR_IS_OK(status)) {
+ return ldb_next_request(module, req);
+ } else if (!W_ERROR_EQUAL(WERR_DS_NO_MSDS_INTID, status)) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "schema_fsmo_add: failed to map %s[%s]: %s\n",
+ oid_attr, oid, win_errstr(status));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ status = dsdb_create_prefix_mapping(module->ldb, schema, oid);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "schema_fsmo_add: failed to create prefix mapping for %s[%s]: %s\n",
+ oid_attr, oid, win_errstr(status));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int schema_fsmo_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_dn *schema_dn;
+ struct dsdb_schema *schema;
+ char *error_string = NULL;
+ int ret;
+ TALLOC_CTX *mem_ctx;
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) != 0) {
+ return ldb_next_request(module, req);
+ }
+
+ schema_dn = samdb_schema_dn(module->ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_extended: no schema dn present: (skip schema loading)\n");
+ return ldb_next_request(module, req);
+ }
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_schema_from_schema_dn(mem_ctx, module->ldb,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ schema_dn, &schema, &error_string);
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_extended: no schema head present: (skip schema loading)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_request(module, req);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "schema_fsmo_extended: dsdb_schema load failed: %s",
+ error_string);
+ talloc_free(mem_ctx);
+ return ldb_next_request(module, req);
+ }
+
+ /* Replace the old schema*/
+ ret = dsdb_set_schema(module->ldb, schema);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "schema_fsmo_extended: dsdb_set_schema() failed: %d:%s",
+ ret, ldb_strerror(ret));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ talloc_free(mem_ctx);
+ return LDB_SUCCESS;
+}
+
+static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_class *class;
+ int ret;
+
+ for (class = schema->classes; class; class = class->next) {
+ ret = ldb_msg_add_string(msg, "objectClasses", schema_class_to_description(msg, class));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+static int generate_attributeTypes(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_attribute *attribute;
+ int ret;
+
+ for (attribute = schema->attributes; attribute; attribute = attribute->next) {
+ ret = ldb_msg_add_string(msg, "attributeTypes", schema_attribute_to_description(msg, attribute));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int generate_dITContentRules(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_class *class;
+ int ret;
+
+ for (class = schema->classes; class; class = class->next) {
+ if (class->auxiliaryClass || class->systemAuxiliaryClass) {
+ char *ditcontentrule = schema_class_to_dITContentRule(msg, class, schema);
+ if (!ditcontentrule) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_steal_string(msg, "dITContentRules", ditcontentrule);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+
+
+/* Add objectClasses, attributeTypes and dITContentRules from the
+ schema object (they are not stored in the database)
+ */
+static int schema_fsmo_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ struct schema_fsmo_search_data *search_data = talloc_get_type(context, struct schema_fsmo_search_data);
+ struct ldb_request *orig_req = search_data->orig_req;
+ TALLOC_CTX *mem_ctx;
+ int i, ret;
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ if (ldb_dn_compare(ares->message->dn, search_data->module_context->aggregate_dn) != 0) {
+ talloc_free(mem_ctx);
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ mem_ctx = talloc_new(ares);
+ if (!mem_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < ARRAY_SIZE(generated_attrs); i++) {
+ if (ldb_attr_in_list(orig_req->op.search.attrs, generated_attrs[i].attr)) {
+ ret = generated_attrs[i].fn(ldb, ares->message, schema);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ talloc_free(mem_ctx);
+ return orig_req->callback(ldb, orig_req->context, ares);
+}
+
+/* search */
+static int schema_fsmo_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int i, ret;
+ struct schema_fsmo_search_data *search_context;
+ struct ldb_request *down_req;
+ struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+
+ if (!schema || !module->private_data) {
+ /* If there is no schema, there is little we can do */
+ return ldb_next_request(module, req);
+ }
+ for (i=0; i < ARRAY_SIZE(generated_attrs); i++) {
+ if (ldb_attr_in_list(req->op.search.attrs, generated_attrs[i].attr)) {
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(generated_attrs)) {
+ /* No request for a generated attr found, nothing to
+ * see here, move along... */
+ return ldb_next_request(module, req);
+ }
+
+ search_context = talloc(req, struct schema_fsmo_search_data);
+ if (!search_context) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ down_req = talloc(req, struct ldb_request);
+ if (!down_req) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+ search_context->orig_req = req;
+ search_context->module_context = talloc_get_type(module->private_data, struct schema_fsmo_private_data);
+ down_req->context = search_context;
+
+ down_req->callback = schema_fsmo_search_callback;
+
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+}
+
+
+_PUBLIC_ const struct ldb_module_ops ldb_schema_fsmo_module_ops = {
+ .name = "schema_fsmo",
+ .init_context = schema_fsmo_init,
+ .add = schema_fsmo_add,
+ .extended = schema_fsmo_extended,
+ .search = schema_fsmo_search
+};
diff --git a/source4/dsdb/samdb/ldb_modules/show_deleted.c b/source4/dsdb/samdb/ldb_modules/show_deleted.c
new file mode 100644
index 0000000000..361cf226dc
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/show_deleted.c
@@ -0,0 +1,201 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005
+ Copyright (C) Stefa Metzmacher <metze@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb deleted objects control module
+ *
+ * Description: this module hides deleted objects, and returns them if the control is there
+ *
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+
+/* search */
+struct show_deleted_search_request {
+
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ bool remove_from_msg;
+};
+
+static int show_deleted_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct show_deleted_search_request *ar;
+
+ ar = talloc_get_type(context, struct show_deleted_search_request);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ bool isDeleted;
+
+ isDeleted = ldb_msg_find_attr_as_bool(ares->message, "isDeleted", false);
+
+ if (isDeleted) {
+ goto skip_deleted;
+ }
+
+ if (ar->remove_from_msg) {
+ ldb_msg_remove_attr(ares->message, "isDeleted");
+ }
+ }
+
+ return ar->up_callback(ldb, ar->up_context, ares);
+
+skip_deleted:
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static int show_deleted_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control;
+ struct ldb_control **saved_controls;
+ struct show_deleted_search_request *ar;
+ struct ldb_request *down_req;
+ char **new_attrs;
+ uint32_t num_attrs = 0;
+ uint32_t i;
+ int ret;
+
+ /* check if there's a show deleted control */
+ control = ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID);
+
+ /* copy the request for modification */
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* copy the request */
+ *down_req = *req;
+
+ /* if a control is there remove if from the modified request */
+ if (control && !save_controls(control, down_req, &saved_controls)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if we had a control, then just go on to the next request as we have nothing to hide */
+ if (control) {
+ goto next_request;
+ }
+
+ ar = talloc(down_req, struct show_deleted_search_request);
+ if (ar == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ar->module = module;
+ ar->up_context = req->context;
+ ar->up_callback = req->callback;
+ ar->remove_from_msg = true;
+
+ /* check if attrs only is specified, in that case check wether we need to modify them */
+ if (down_req->op.search.attrs) {
+ for (i=0; (down_req->op.search.attrs && down_req->op.search.attrs[i]); i++) {
+ num_attrs++;
+ if (strcasecmp(down_req->op.search.attrs[i], "*") == 0) {
+ ar->remove_from_msg = false;
+ } else if (strcasecmp(down_req->op.search.attrs[i], "isDeleted") == 0) {
+ ar->remove_from_msg = false;
+ }
+ }
+ } else {
+ ar->remove_from_msg = false;
+ }
+
+ if (ar->remove_from_msg) {
+ new_attrs = talloc_array(down_req, char *, num_attrs + 2);
+ if (!new_attrs) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i=0; i < num_attrs; i++) {
+ new_attrs[i] = discard_const_p(char, down_req->op.search.attrs[i]);
+ }
+ new_attrs[i] = talloc_strdup(new_attrs, "isDeleted");
+ if (!new_attrs[i]) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ new_attrs[i+1] = NULL;
+ down_req->op.search.attrs = (const char * const *)new_attrs;
+ }
+
+ down_req->context = ar;
+ down_req->callback = show_deleted_search_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+next_request:
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int show_deleted_init(struct ldb_module *module)
+{
+ struct ldb_request *req;
+ int ret;
+
+ req = talloc(module, struct ldb_request);
+ if (req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ req->operation = LDB_REQ_REGISTER_CONTROL;
+ req->op.reg_control.oid = LDB_CONTROL_SHOW_DELETED_OID;
+ req->controls = NULL;
+
+ ret = ldb_request(module->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "show_deleted: Unable to register control with rootdse!\n");
+ talloc_free(req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(req);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_show_deleted_module_ops = {
+ .name = "show_deleted",
+ .search = show_deleted_search,
+ .init_context = show_deleted_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c b/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c
new file mode 100644
index 0000000000..8f92995145
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c
@@ -0,0 +1,716 @@
+/*
+ ldb database module
+
+ LDAP semantics mapping module
+
+ Copyright (C) Jelmer Vernooij 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+
+ 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/>.
+*/
+
+/*
+ This module relies on ldb_map to do all the real work, but performs
+ some of the trivial mappings between AD semantics and that provided
+ by OpenLDAP and similar servers.
+*/
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_private.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/ldb_map/ldb_map.h"
+
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/samdb.h"
+
+static struct ldb_val encode_guid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct GUID guid;
+ NTSTATUS status = GUID_from_string((char *)val->data, &guid);
+ enum ndr_err_code ndr_err;
+ struct ldb_val out = data_blob(NULL, 0);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return out;
+ }
+ ndr_err = ndr_push_struct_blob(&out, ctx, NULL, &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return out;
+ }
+
+ return out;
+}
+
+static struct ldb_val guid_always_string(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct GUID *guid;
+ struct ldb_val out = data_blob(NULL, 0);
+ if (val->length >= 32 && val->data[val->length] == '\0') {
+ ldb_handler_copy(module->ldb, ctx, val, &out);
+ } else {
+ enum ndr_err_code ndr_err;
+
+ guid = talloc(ctx, struct GUID);
+ if (guid == NULL) {
+ return out;
+ }
+ ndr_err = ndr_pull_struct_blob(val, guid, NULL, guid,
+ (ndr_pull_flags_fn_t)ndr_pull_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(guid);
+ return out;
+ }
+ out = data_blob_string_const(GUID_string(ctx, guid));
+ talloc_free(guid);
+ }
+ return out;
+}
+
+static struct ldb_val encode_ns_guid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct GUID guid;
+ NTSTATUS status = NS_GUID_from_string((char *)val->data, &guid);
+ enum ndr_err_code ndr_err;
+ struct ldb_val out = data_blob(NULL, 0);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return out;
+ }
+ ndr_err = ndr_push_struct_blob(&out, ctx, NULL, &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return out;
+ }
+
+ return out;
+}
+
+static struct ldb_val guid_ns_string(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ if (val->length >= 32 && val->data[val->length] == '\0') {
+ struct GUID guid;
+ GUID_from_string((char *)val->data, &guid);
+ out = data_blob_string_const(NS_GUID_string(ctx, &guid));
+ } else {
+ enum ndr_err_code ndr_err;
+ struct GUID *guid_p;
+ guid_p = talloc(ctx, struct GUID);
+ if (guid_p == NULL) {
+ return out;
+ }
+ ndr_err = ndr_pull_struct_blob(val, guid_p, NULL, guid_p,
+ (ndr_pull_flags_fn_t)ndr_pull_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(guid_p);
+ return out;
+ }
+ out = data_blob_string_const(NS_GUID_string(ctx, guid_p));
+ talloc_free(guid_p);
+ }
+ return out;
+}
+
+/* The backend holds binary sids, so just copy them back */
+static struct ldb_val val_copy(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ ldb_handler_copy(module->ldb, ctx, val, &out);
+
+ return out;
+}
+
+/* Ensure we always convert sids into binary, so the backend doesn't have to know about both forms */
+static struct ldb_val sid_always_binary(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(module->ldb, "objectSid");
+
+ if (a->syntax->canonicalise_fn(module->ldb, ctx, val, &out) != LDB_SUCCESS) {
+ return data_blob(NULL, 0);
+ }
+
+ return out;
+}
+
+/* Ensure we always convert objectCategory into a DN */
+static struct ldb_val objectCategory_always_dn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_dn *dn;
+ struct ldb_val out = data_blob(NULL, 0);
+ const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(module->ldb, "objectCategory");
+
+ dn = ldb_dn_from_ldb_val(ctx, module->ldb, val);
+ if (dn && ldb_dn_validate(dn)) {
+ talloc_free(dn);
+ return val_copy(module, ctx, val);
+ }
+ talloc_free(dn);
+
+ if (a->syntax->canonicalise_fn(module->ldb, ctx, val, &out) != LDB_SUCCESS) {
+ return data_blob(NULL, 0);
+ }
+
+ return out;
+}
+
+static struct ldb_val normalise_to_signed32(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ long long int signed_ll = strtoll((const char *)val->data, NULL, 10);
+ if (signed_ll >= 0x80000000LL) {
+ union {
+ int32_t signed_int;
+ uint32_t unsigned_int;
+ } u = {
+ .unsigned_int = strtoul((const char *)val->data, NULL, 10)
+ };
+
+ struct ldb_val out = data_blob_string_const(talloc_asprintf(ctx, "%d", u.signed_int));
+ return out;
+ }
+ return val_copy(module, ctx, val);
+}
+
+static struct ldb_val usn_to_entryCSN(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ unsigned long long usn = strtoull((const char *)val->data, NULL, 10);
+ time_t t = (usn >> 24);
+ out = data_blob_string_const(talloc_asprintf(ctx, "%s#%06x#00#000000", ldb_timestring(ctx, t), (unsigned int)(usn & 0xFFFFFF)));
+ return out;
+}
+
+static unsigned long long entryCSN_to_usn_int(TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ char *entryCSN = talloc_strdup(ctx, (const char *)val->data);
+ char *mod_per_sec;
+ time_t t;
+ unsigned long long usn;
+ char *p;
+ if (!entryCSN) {
+ return 0;
+ }
+ p = strchr(entryCSN, '#');
+ if (!p) {
+ return 0;
+ }
+ p[0] = '\0';
+ p++;
+ mod_per_sec = p;
+
+ p = strchr(p, '#');
+ if (!p) {
+ return 0;
+ }
+ p[0] = '\0';
+ p++;
+
+ usn = strtol(mod_per_sec, NULL, 16);
+
+ t = ldb_string_to_time(entryCSN);
+
+ usn = usn | ((unsigned long long)t <<24);
+ return usn;
+}
+
+static struct ldb_val entryCSN_to_usn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ unsigned long long usn = entryCSN_to_usn_int(ctx, val);
+ out = data_blob_string_const(talloc_asprintf(ctx, "%lld", usn));
+ return out;
+}
+
+static struct ldb_val usn_to_timestamp(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ unsigned long long usn = strtoull((const char *)val->data, NULL, 10);
+ time_t t = (usn >> 24);
+ out = data_blob_string_const(ldb_timestring(ctx, t));
+ return out;
+}
+
+static struct ldb_val timestamp_to_usn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ time_t t;
+ unsigned long long usn;
+
+ t = ldb_string_to_time((const char *)val->data);
+
+ usn = ((unsigned long long)t <<24);
+
+ out = data_blob_string_const(talloc_asprintf(ctx, "%lld", usn));
+ return out;
+}
+
+
+static const struct ldb_map_attribute entryuuid_attributes[] =
+{
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "entryUUID",
+ .convert_local = guid_always_string,
+ .convert_remote = encode_guid,
+ },
+ },
+ },
+ /* invocationId */
+ {
+ .local_name = "invocationId",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "invocationId",
+ .convert_local = guid_always_string,
+ .convert_remote = encode_guid,
+ },
+ },
+ },
+ /* objectSid */
+ {
+ .local_name = "objectSid",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectSid",
+ .convert_local = sid_always_binary,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "name",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4RDN"
+ }
+ }
+ },
+ {
+ .local_name = "whenCreated",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "createTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "whenChanged",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "modifyTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "objectClasses",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4ObjectClasses"
+ }
+ }
+ },
+ {
+ .local_name = "dITContentRules",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4DITContentRules"
+ }
+ }
+ },
+ {
+ .local_name = "attributeTypes",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4AttributeTypes"
+ }
+ }
+ },
+ {
+ .local_name = "objectCategory",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectCategory",
+ .convert_local = objectCategory_always_dn,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "distinguishedName",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "entryDN"
+ }
+ }
+ },
+ {
+ .local_name = "groupType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "groupType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "sAMAccountType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sAMAccountType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "usnChanged",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "entryCSN",
+ .convert_local = usn_to_entryCSN,
+ .convert_remote = entryCSN_to_usn
+ },
+ },
+ },
+ {
+ .local_name = "usnCreated",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "createTimestamp",
+ .convert_local = usn_to_timestamp,
+ .convert_remote = timestamp_to_usn,
+ },
+ },
+ },
+ {
+ .local_name = "*",
+ .type = MAP_KEEP,
+ },
+ {
+ .local_name = NULL,
+ }
+};
+
+/* This objectClass conflicts with builtin classes on OpenLDAP */
+const struct ldb_map_objectclass entryuuid_objectclasses[] =
+{
+ {
+ .local_name = "subSchema",
+ .remote_name = "samba4SubSchema"
+ },
+ {
+ .local_name = NULL
+ }
+};
+
+/* These things do not show up in wildcard searches in OpenLDAP, but
+ * we need them to show up in the AD-like view */
+static const char * const entryuuid_wildcard_attributes[] = {
+ "objectGUID",
+ "whenCreated",
+ "whenChanged",
+ "usnCreated",
+ "usnChanged",
+ "memberOf",
+ NULL
+};
+
+static const struct ldb_map_attribute nsuniqueid_attributes[] =
+{
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "nsuniqueid",
+ .convert_local = guid_ns_string,
+ .convert_remote = encode_ns_guid,
+ },
+ },
+ },
+ /* objectSid */
+ {
+ .local_name = "objectSid",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectSid",
+ .convert_local = sid_always_binary,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "whenCreated",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "createTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "whenChanged",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "modifyTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "objectCategory",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectCategory",
+ .convert_local = objectCategory_always_dn,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "distinguishedName",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "entryDN"
+ }
+ }
+ },
+ {
+ .local_name = "groupType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "groupType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "sAMAccountType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sAMAccountType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "usnChanged",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "modifyTimestamp",
+ .convert_local = usn_to_timestamp,
+ .convert_remote = timestamp_to_usn,
+ },
+ },
+ },
+ {
+ .local_name = "usnCreated",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "createTimestamp",
+ .convert_local = usn_to_timestamp,
+ .convert_remote = timestamp_to_usn,
+ },
+ },
+ },
+ {
+ .local_name = "*",
+ .type = MAP_KEEP,
+ },
+ {
+ .local_name = NULL,
+ }
+};
+
+/* These things do not show up in wildcard searches in OpenLDAP, but
+ * we need them to show up in the AD-like view */
+static const char * const nsuniqueid_wildcard_attributes[] = {
+ "objectGUID",
+ "whenCreated",
+ "whenChanged",
+ "usnCreated",
+ "usnChanged",
+ NULL
+};
+
+/* the context init function */
+static int entryuuid_init(struct ldb_module *module)
+{
+ int ret;
+ ret = ldb_map_init(module, entryuuid_attributes, entryuuid_objectclasses, entryuuid_wildcard_attributes, "samba4Top", NULL);
+ if (ret != LDB_SUCCESS)
+ return ret;
+
+ return ldb_next_init(module);
+}
+
+/* the context init function */
+static int nsuniqueid_init(struct ldb_module *module)
+{
+ int ret;
+ ret = ldb_map_init(module, nsuniqueid_attributes, NULL, nsuniqueid_wildcard_attributes, "extensibleObject", NULL);
+ if (ret != LDB_SUCCESS)
+ return ret;
+
+ return ldb_next_init(module);
+}
+
+static int get_seq(struct ldb_context *ldb, void *context,
+ struct ldb_reply *ares)
+{
+ unsigned long long *seq = (unsigned long long *)context;
+ if (ares->type == LDB_REPLY_ENTRY) {
+ struct ldb_message_element *el = ldb_msg_find_element(ares->message, "contextCSN");
+ if (el) {
+ *seq = entryCSN_to_usn_int(ares, &el->values[0]);
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int entryuuid_sequence_number(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct map_private *map_private;
+ struct entryuuid_private *entryuuid_private;
+ unsigned long long seq = 0;
+ struct ldb_request *search_req;
+
+ const struct ldb_control *partition_ctrl;
+ const struct dsdb_control_current_partition *partition;
+
+ static const char *contextCSN_attr[] = {
+ "contextCSN", NULL
+ };
+
+ map_private = talloc_get_type(module->private_data, struct map_private);
+
+ entryuuid_private = talloc_get_type(map_private->caller_private, struct entryuuid_private);
+
+ /* All this to get the DN of the parition, so we can search the right thing */
+ partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (!partition_ctrl) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "instancetype_add: no current partition control found");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ partition = talloc_get_type(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+ SMB_ASSERT(partition && partition->version == DSDB_CONTROL_CURRENT_PARTITION_VERSION);
+
+ search_req = talloc(req, struct ldb_request);
+ if (search_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Finally, we have it. This saves searching over more
+ * partitions than we expose to the client, such as a cn=samba
+ * configuration partition */
+
+ search_req->operation = LDB_SEARCH;
+ search_req->op.search.base = partition->dn;
+ search_req->op.search.scope = LDB_SCOPE_BASE;
+
+ search_req->op.search.tree = ldb_parse_tree(search_req, "objectClass=*");
+ if (search_req->op.search.tree == NULL) {
+ ldb_set_errstring(module->ldb, "Unable to parse search expression");
+ talloc_free(search_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ search_req->op.search.attrs = contextCSN_attr;
+ search_req->controls = NULL;
+ search_req->context = &seq;
+ search_req->callback = get_seq;
+ ldb_set_timeout(module->ldb, search_req, 0); /* use default timeout */
+
+ ret = ldb_next_request(module, search_req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(search_req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ switch (req->op.seq_num.type) {
+ case LDB_SEQ_HIGHEST_SEQ:
+ req->op.seq_num.seq_num = seq;
+ break;
+ case LDB_SEQ_NEXT:
+ req->op.seq_num.seq_num = seq;
+ req->op.seq_num.seq_num++;
+ break;
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ {
+ req->op.seq_num.seq_num = (seq >> 24);
+ break;
+ }
+ }
+ req->op.seq_num.flags = 0;
+ req->op.seq_num.flags |= LDB_SEQ_TIMESTAMP_SEQUENCE;
+ req->op.seq_num.flags |= LDB_SEQ_GLOBAL_SEQUENCE;
+ return LDB_SUCCESS;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_entryuuid_module_ops = {
+ .name = "entryuuid",
+ .init_context = entryuuid_init,
+ .sequence_number = entryuuid_sequence_number,
+ LDB_MAP_OPS
+};
+
+_PUBLIC_ const struct ldb_module_ops ldb_nsuniqueid_module_ops = {
+ .name = "nsuniqueid",
+ .init_context = nsuniqueid_init,
+ .sequence_number = entryuuid_sequence_number,
+ LDB_MAP_OPS
+};
diff --git a/source4/dsdb/samdb/ldb_modules/subtree_delete.c b/source4/dsdb/samdb/ldb_modules/subtree_delete.c
new file mode 100644
index 0000000000..9c332d2969
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/subtree_delete.c
@@ -0,0 +1,260 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb subtree delete (prevention) module
+ *
+ * Description: Prevent deletion of a subtree in LDB
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "ldb_includes.h"
+
+struct subtree_delete_context {
+ enum sd_step {SD_SEARCH, SD_DO_DEL} step;
+
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *search_req;
+ struct ldb_request *down_req;
+
+ int num_children;
+};
+
+static struct subtree_delete_context *subtree_delete_init_handle(struct ldb_request *req,
+ struct ldb_module *module)
+{
+ struct subtree_delete_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct subtree_delete_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->handle = h;
+ ac->orig_req = req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+static int subtree_delete_check_for_children(struct subtree_delete_context *ac)
+{
+ if (ac->num_children > 0) {
+ ldb_asprintf_errstring(ac->module->ldb, "Cannot delete %s, not a leaf node (has %d children)\n",
+ ldb_dn_get_linearized(ac->orig_req->op.del.dn), ac->num_children);
+ return LDB_ERR_NOT_ALLOWED_ON_NON_LEAF;
+ } else {
+ struct ldb_request *req = talloc(ac, struct ldb_request);
+ if (!req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *req = *ac->orig_req;
+
+ /* Ensure any (io) errors during the search for
+ * children don't propgate back in the error string */
+ ldb_set_errstring(ac->module->ldb, NULL);
+
+ ac->down_req = req;
+ ac->step = SD_DO_DEL;
+ return ldb_next_request(ac->module, req);
+ }
+}
+
+static int subtree_delete_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct subtree_delete_context *ac = talloc_get_type(context, struct subtree_delete_context);
+ TALLOC_CTX *mem_ctx = talloc_new(ac);
+
+ if (!mem_ctx) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* OK, we have one of *many* search results here:
+
+ We should also get the entry we tried to rename. This
+ callback handles this and everything below it.
+ */
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, ac->orig_req->op.del.dn) != 0) {
+ /* And it is an actual entry: now object bitterly that we are not a leaf node */
+ ac->num_children++;
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+/* rename */
+static int subtree_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_request *new_req;
+ struct subtree_delete_context *ac;
+ int ret;
+ if (ldb_dn_is_special(req->op.rename.olddn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for all entires under this entry
+ - Wait for these results to appear
+ - In the callback for each result, count the children (if any)
+ - return an error if there are any
+ */
+
+ ac = subtree_delete_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.del.dn,
+ LDB_SCOPE_SUBTREE,
+ "(objectClass=*)",
+ attrs,
+ req->controls,
+ ac,
+ subtree_delete_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->search_req = new_req;
+ if (req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+
+static int subtree_delete_wait_none(struct ldb_handle *handle) {
+ struct subtree_delete_context *ac;
+ int ret = LDB_ERR_OPERATIONS_ERROR;
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct subtree_delete_context);
+
+ switch (ac->step) {
+ case SD_SEARCH:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS
+ && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ return subtree_delete_check_for_children(ac);
+
+ case SD_DO_DEL:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+ }
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int subtree_delete_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = subtree_delete_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int subtree_delete_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return subtree_delete_wait_all(handle);
+ } else {
+ return subtree_delete_wait_none(handle);
+ }
+}
+
+const struct ldb_module_ops ldb_subtree_delete_module_ops = {
+ .name = "subtree_delete",
+ .del = subtree_delete,
+ .wait = subtree_delete_wait,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/subtree_rename.c b/source4/dsdb/samdb/ldb_modules/subtree_rename.c
new file mode 100644
index 0000000000..fd1388d416
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/subtree_rename.c
@@ -0,0 +1,285 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb subtree rename module
+ *
+ * Description: Rename a subtree in LDB
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "ldb_includes.h"
+
+struct subtree_rename_context {
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ struct ldb_request **down_req;
+ int num_requests;
+ int finished_requests;
+
+ int num_children;
+};
+
+static struct subtree_rename_context *subtree_rename_init_handle(struct ldb_request *req,
+ struct ldb_module *module)
+{
+ struct subtree_rename_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct subtree_rename_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->handle = h;
+ ac->orig_req = req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+
+static int subtree_rename_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct ldb_request *req;
+ struct subtree_rename_context *ac = talloc_get_type(context, struct subtree_rename_context);
+ TALLOC_CTX *mem_ctx = talloc_new(ac);
+
+ if (!mem_ctx) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* OK, we have one of *many* search results here:
+
+ We should also get the entry we tried to rename. This
+ callback handles this and everything below it.
+ */
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, ac->orig_req->op.rename.olddn) != 0) {
+ /* And it is an actual entry: now create a rename from it */
+ int ret;
+
+ struct ldb_dn *newdn = ldb_dn_copy(mem_ctx, ares->message->dn);
+ if (!newdn) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ldb_dn_remove_base_components(newdn, ldb_dn_get_comp_num(ac->orig_req->op.rename.olddn));
+
+ if (!ldb_dn_add_base(newdn, ac->orig_req->op.rename.newdn)) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_rename_req(&req, ldb, mem_ctx,
+ ares->message->dn,
+ newdn,
+ NULL,
+ NULL,
+ NULL);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_set_timeout_from_prev_req(ldb, ac->orig_req, req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(req, newdn);
+
+ talloc_steal(req, ares->message->dn);
+
+ talloc_free(ares);
+
+ } else if (ares->type == LDB_REPLY_DONE) {
+ req = talloc(mem_ctx, struct ldb_request);
+ *req = *ac->orig_req;
+ talloc_free(ares);
+
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->down_req[ac->num_requests] = req;
+ ac->num_requests++;
+
+ return ldb_next_request(ac->module, req);
+
+}
+
+/* rename */
+static int subtree_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_request *new_req;
+ struct subtree_rename_context *ac;
+ int ret;
+ if (ldb_dn_is_special(req->op.rename.olddn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for all entires under this entry
+ - Wait for these results to appear
+ - In the callback for each result, issue a modify request
+ - That will include this rename, we hope
+ - Wait for each modify result
+ - Regain our sainity
+ */
+
+ ac = subtree_rename_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.rename.olddn,
+ LDB_SCOPE_SUBTREE,
+ "(objectClass=*)",
+ attrs,
+ req->controls,
+ ac,
+ subtree_rename_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->down_req[ac->num_requests] = new_req;
+ if (req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->num_requests++;
+ return ldb_next_request(module, new_req);
+}
+
+
+static int subtree_rename_wait_none(struct ldb_handle *handle) {
+ struct subtree_rename_context *ac;
+ int i, ret = LDB_ERR_OPERATIONS_ERROR;
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct subtree_rename_context);
+
+ for (i=0; i < ac->num_requests; i++) {
+ ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req[i]->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ }
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+
+}
+
+static int subtree_rename_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = subtree_rename_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int subtree_rename_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return subtree_rename_wait_all(handle);
+ } else {
+ return subtree_rename_wait_none(handle);
+ }
+}
+
+const struct ldb_module_ops ldb_subtree_rename_module_ops = {
+ .name = "subtree_rename",
+ .rename = subtree_rename,
+ .wait = subtree_rename_wait,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py b/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py
new file mode 100644
index 0000000000..428e6b4d4b
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py
@@ -0,0 +1,1046 @@
+#!/usr/bin/python
+
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2005-2007
+# Copyright (C) Martin Kuehl <mkhl@samba.org> 2006
+#
+# This is a Python port of the original in testprogs/ejs/samba3sam.js
+#
+# 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/>.
+#
+
+"""Tests for the samba3sam LDB module, which maps Samba3 LDAP to AD LDAP."""
+
+import os
+import sys
+import samba
+import ldb
+from ldb import SCOPE_DEFAULT, SCOPE_BASE, SCOPE_SUBTREE
+from samba import Ldb, substitute_var
+from samba.tests import LdbTestCase, TestCaseInTempDir
+
+datadir = os.path.join(os.path.dirname(__file__), "../../../../../testdata/samba3")
+
+class MapBaseTestCase(TestCaseInTempDir):
+ def setup_data(self, obj, ldif):
+ self.assertTrue(ldif is not None)
+ obj.db.add_ldif(substitute_var(ldif, obj.substvars))
+
+ def setup_modules(self, ldb, s3, s4):
+ ldb.add({"dn": "@MAP=samba3sam",
+ "@FROM": s4.basedn,
+ "@TO": "sambaDomainName=TESTS," + s3.basedn})
+
+ ldb.add({"dn": "@MODULES",
+ "@LIST": "rootdse,paged_results,server_sort,extended_dn,asq,samldb,password_hash,operational,objectguid,rdn_name,samba3sam,partition"})
+
+ ldb.add({"dn": "@PARTITION",
+ "partition": [s4.basedn + ":" + s4.url, s3.basedn + ":" + s3.url],
+ "replicateEntries": ["@ATTRIBUTES", "@INDEXLIST"]})
+
+ def setUp(self):
+ super(MapBaseTestCase, self).setUp()
+
+ def make_dn(basedn, rdn):
+ return rdn + ",sambaDomainName=TESTS," + basedn
+
+ def make_s4dn(basedn, rdn):
+ return rdn + "," + basedn
+
+ self.ldbfile = os.path.join(self.tempdir, "test.ldb")
+ self.ldburl = "tdb://" + self.ldbfile
+
+ tempdir = self.tempdir
+ print tempdir
+
+ class Target:
+ """Simple helper class that contains data for a specific SAM connection."""
+ def __init__(self, file, basedn, dn):
+ self.file = os.path.join(tempdir, file)
+ self.url = "tdb://" + self.file
+ self.basedn = basedn
+ self.substvars = {"BASEDN": self.basedn}
+ self.db = Ldb()
+ self._dn = dn
+
+ def dn(self, rdn):
+ return self._dn(rdn, self.basedn)
+
+ def connect(self):
+ return self.db.connect(self.url)
+
+ self.samba4 = Target("samba4.ldb", "dc=vernstok,dc=nl", make_s4dn)
+ self.samba3 = Target("samba3.ldb", "cn=Samba3Sam", make_dn)
+ self.templates = Target("templates.ldb", "cn=templates", None)
+
+ self.samba3.connect()
+ self.templates.connect()
+ self.samba4.connect()
+
+ def tearDown(self):
+ os.unlink(self.ldbfile)
+ os.unlink(self.samba3.file)
+ os.unlink(self.templates.file)
+ os.unlink(self.samba4.file)
+ super(MapBaseTestCase, self).tearDown()
+
+
+class Samba3SamTestCase(MapBaseTestCase):
+ def setUp(self):
+ super(Samba3SamTestCase, self).setUp()
+ ldb = Ldb(self.ldburl)
+ self.setup_data(self.samba3, open(os.path.join(datadir, "samba3.ldif"), 'r').read())
+ self.setup_data(self.templates, open(os.path.join(datadir, "provision_samba3sam_templates.ldif"), 'r').read())
+ ldif = open(os.path.join(datadir, "provision_samba3sam.ldif"), 'r').read()
+ ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
+ self.setup_modules(ldb, self.samba3, self.samba4)
+ self.ldb = Ldb(self.ldburl)
+
+ def test_s3sam_search(self):
+ print "Looking up by non-mapped attribute"
+ msg = self.ldb.search(expression="(cn=Administrator)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["cn"], "Administrator")
+
+ print "Looking up by mapped attribute"
+ msg = self.ldb.search(expression="(name=Backup Operators)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["name"], "Backup Operators")
+
+ print "Looking up by old name of renamed attribute"
+ msg = self.ldb.search(expression="(displayName=Backup Operators)")
+ self.assertEquals(len(msg), 0)
+
+ print "Looking up mapped entry containing SID"
+ msg = self.ldb.search(expression="(cn=Replicator)")
+ self.assertEquals(len(msg), 1)
+ print msg[0].dn
+ self.assertEquals(str(msg[0].dn), "cn=Replicator,ou=Groups,dc=vernstok,dc=nl")
+ self.assertEquals(msg[0]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
+
+ print "Checking mapping of objectClass"
+ oc = set(msg[0]["objectClass"])
+ self.assertTrue(oc is not None)
+ for i in oc:
+ self.assertEquals(oc[i] == "posixGroup" or oc[i], "group")
+
+ print "Looking up by objectClass"
+ msg = self.ldb.search(expression="(|(objectClass=user)(cn=Administrator))")
+ self.assertEquals(len(msg), 2)
+ for i in range(len(msg)):
+ self.assertEquals((str(msg[i].dn), "unixName=Administrator,ou=Users,dc=vernstok,dc=nl") or
+ (str(msg[i].dn) == "unixName=nobody,ou=Users,dc=vernstok,dc=nl"))
+
+
+ def test_s3sam_modify(self):
+ print "Adding a record that will be fallbacked"
+ self.ldb.add({"dn": "cn=Foo",
+ "foo": "bar",
+ "blah": "Blie",
+ "cn": "Foo",
+ "showInAdvancedViewOnly": "TRUE"}
+ )
+
+ print "Checking for existence of record (local)"
+ # TODO: This record must be searched in the local database, which is currently only supported for base searches
+ # msg = ldb.search(expression="(cn=Foo)", ['foo','blah','cn','showInAdvancedViewOnly')]
+ # TODO: Actually, this version should work as well but doesn't...
+ #
+ #
+ msg = self.ldb.search(expression="(cn=Foo)", base="cn=Foo", scope=SCOPE_BASE, attrs=['foo','blah','cn','showInAdvancedViewOnly'])
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["showInAdvancedViewOnly"], "TRUE")
+ self.assertEquals(msg[0]["foo"], "bar")
+ self.assertEquals(msg[0]["blah"], "Blie")
+
+ print "Adding record that will be mapped"
+ self.ldb.add({"dn": "cn=Niemand,cn=Users,dc=vernstok,dc=nl",
+ "objectClass": "user",
+ "unixName": "bin",
+ "sambaUnicodePwd": "geheim",
+ "cn": "Niemand"})
+
+ print "Checking for existence of record (remote)"
+ msg = self.ldb.search(expression="(unixName=bin)",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["sambaUnicodePwd"], "geheim")
+
+ print "Checking for existence of record (local && remote)"
+ msg = self.ldb.search(expression="(&(unixName=bin)(sambaUnicodePwd=geheim))",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ self.assertEquals(len(msg), 1) # TODO: should check with more records
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["unixName"], "bin")
+ self.assertEquals(msg[0]["sambaUnicodePwd"], "geheim")
+
+ print "Checking for existence of record (local || remote)"
+ msg = self.ldb.search(expression="(|(unixName=bin)(sambaUnicodePwd=geheim))",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ print "got " + len(msg) + " replies"
+ self.assertEquals(len(msg), 1) # TODO: should check with more records
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["unixName"] == "bin" or msg[0]["sambaUnicodePwd"], "geheim")
+
+ print "Checking for data in destination database"
+ msg = s3.db.search("(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(msg[0]["sambaSID"], "S-1-5-21-4231626423-2410014848-2360679739-2001")
+ self.assertEquals(msg[0]["displayName"], "Niemand")
+
+ print "Adding attribute..."
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+add: description
+description: Blah
+""")
+
+ print "Checking whether changes are still there..."
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["description"], "Blah")
+
+ print "Modifying attribute..."
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+replace: description
+description: Blie
+""")
+
+ print "Checking whether changes are still there..."
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(msg[0]["description"], "Blie")
+
+ print "Deleting attribute..."
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+delete: description
+""")
+
+ print "Checking whether changes are no longer there..."
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertTrue(not "description" in res[0])
+
+ print "Renaming record..."
+ self.ldb.rename("cn=Niemand,cn=Users,dc=vernstok,dc=nl", "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ print "Checking whether DN has changed..."
+ msg = self.ldb.search(expression="(cn=Niemand2)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(str(msg[0].dn), "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ print "Deleting record..."
+ self.ldb.delete("cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ print "Checking whether record is gone..."
+ msg = self.ldb.search(expression="(cn=Niemand2)")
+ self.assertEquals(len(msg), 0)
+
+
+
+class MapTestCase(MapBaseTestCase):
+ def setUp(self):
+ super(MapTestCase, self).setUp()
+ ldb = Ldb(self.ldburl)
+ self.setup_data(self.templates, open(os.path.join(datadir, "provision_samba3sam_templates.ldif"), 'r').read())
+ ldif = open(os.path.join(datadir, "provision_samba3sam.ldif"), 'r').read()
+ ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
+ self.setup_modules(ldb, self.samba3, self.samba4)
+ self.ldb = Ldb(self.ldburl)
+
+ def test_map_search(self):
+ print "Running search tests on mapped data"
+ ldif = """
+dn: """ + "sambaDomainName=TESTS,""" + self.samba3.basedn + """
+objectclass: sambaDomain
+objectclass: top
+sambaSID: S-1-5-21-4231626423-2410014848-2360679739
+sambaNextRid: 2000
+sambaDomainName: TESTS"""
+ self.samba3.db.add_ldif(substitute_var(ldif, self.samba3.substvars))
+
+ print "Add a set of split records"
+ ldif = """
+dn: """ + self.samba4.dn("cn=X") + """
+objectClass: user
+cn: X
+codePage: x
+revision: x
+dnsHostName: x
+nextRid: y
+lastLogon: x
+description: x
+objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
+primaryGroupID: 1-5-21-4231626423-2410014848-2360679739-512
+
+dn: """ + self.samba4.dn("cn=Y") + """
+objectClass: top
+cn: Y
+codePage: x
+revision: x
+dnsHostName: y
+nextRid: y
+lastLogon: y
+description: x
+
+dn: """ + self.samba4.dn("cn=Z") + """
+objectClass: top
+cn: Z
+codePage: x
+revision: y
+dnsHostName: z
+nextRid: y
+lastLogon: z
+description: y
+"""
+
+ self.ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
+
+ print "Add a set of remote records"
+
+ ldif = """
+dn: """ + self.samba3.dn("cn=A") + """
+objectClass: posixAccount
+cn: A
+sambaNextRid: x
+sambaBadPasswordCount: x
+sambaLogonTime: x
+description: x
+sambaSID: S-1-5-21-4231626423-2410014848-2360679739-552
+sambaPrimaryGroupSID: S-1-5-21-4231626423-2410014848-2360679739-512
+
+dn: """ + self.samba3.dn("cn=B") + """
+objectClass: top
+cn:B
+sambaNextRid: x
+sambaBadPasswordCount: x
+sambaLogonTime: y
+description: x
+
+dn: """ + self.samba3.dn("cn=C") + """
+objectClass: top
+cn: C
+sambaNextRid: x
+sambaBadPasswordCount: y
+sambaLogonTime: z
+description: y
+"""
+ self.samba3.add_ldif(substitute_var(ldif, self.samba3.substvars))
+
+ print "Testing search by DN"
+
+ # Search remote record by local DN
+ dn = self.samba4.dn("cn=A")
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "x")
+
+ # Search remote record by remote DN
+ dn = self.samba3.dn("cn=A")
+ attrs = ["dnsHostName", "lastLogon", "sambaLogonTime"]
+ res = self.samba3.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertTrue(not "lastLogon" in res[0])
+ self.assertEquals(res[0]["sambaLogonTime"], "x")
+
+ # Search split record by local DN
+ dn = self.samba4.dn("cn=X")
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+
+ # Search split record by remote DN
+ dn = self.samba3.dn("cn=X")
+ attrs = ["dnsHostName", "lastLogon", "sambaLogonTime"]
+ res = self.samba3.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertTrue(not "lastLogon" in res[0])
+ self.assertEquals(res[0]["sambaLogonTime"], "x")
+
+ print "Testing search by attribute"
+
+ # Search by ignored attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(revision=x)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(str(res[0].dn)), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(str(res[1].dn)), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by kept attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(description=y)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(str(res[0].dn)), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[0]["dnsHostName"], "z")
+ self.assertEquals(res[0]["lastLogon"], "z")
+ self.assertEquals(str(str(res[1].dn)), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "z")
+
+ # Search by renamed attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(badPwdCount=x)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by converted attribute
+ attrs = ["dnsHostName", "lastLogon", "objectSid"]
+ # TODO:
+ # Using the SID directly in the parse tree leads to conversion
+ # errors, letting the search fail with no results.
+ #res = self.ldb.search("(objectSid=S-1-5-21-4231626423-2410014848-2360679739-552)", scope=SCOPE_DEFAULT, attrs)
+ res = self.ldb.search(expression="(objectSid=*)", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(res[0]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(res[1]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
+
+ # Search by generated attribute
+ # In most cases, this even works when the mapping is missing
+ # a `convert_operator' by enumerating the remote db.
+ attrs = ["dnsHostName", "lastLogon", "primaryGroupID"]
+ res = self.ldb.search(expression="(primaryGroupID=512)", attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(res[0]["primaryGroupID"], "512")
+
+ # TODO: There should actually be two results, A and X. The
+ # primaryGroupID of X seems to get corrupted somewhere, and the
+ # objectSid isn't available during the generation of remote (!) data,
+ # which can be observed with the following search. Also note that Xs
+ # objectSid seems to be fine in the previous search for objectSid... */
+ #res = ldb.search(expression="(primaryGroupID=*)", NULL, ldb. SCOPE_DEFAULT, attrs)
+ #print len(res) + " results found"
+ #for i in range(len(res)):
+ # for (obj in res[i]) {
+ # print obj + ": " + res[i][obj]
+ # }
+ # print "---"
+ #
+
+ # Search by remote name of renamed attribute */
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(sambaBadPasswordCount=*)", attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by objectClass
+ attrs = ["dnsHostName", "lastLogon", "objectClass"]
+ res = self.ldb.search(expression="(objectClass=user)", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertTrue(res[0]["objectClass"] is not None)
+ self.assertEquals(res[0]["objectClass"][0], "user")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertTrue(res[1]["objectClass"] is not None)
+ self.assertEquals(res[1]["objectClass"][0], "user")
+
+ # Prove that the objectClass is actually used for the search
+ res = self.ldb.search(expression="(|(objectClass=user)(badPwdCount=x))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertTrue(res[0]["objectClass"] is not None)
+ for oc in set(res[0]["objectClass"]):
+ self.assertEquals(oc, "user")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertTrue(res[1]["objectClass"] is not None)
+ self.assertEquals(res[1]["objectClass"][0], "user")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "x")
+ self.assertTrue(res[2]["objectClass"] is not None)
+ self.assertEquals(res[2]["objectClass"][0], "user")
+
+ print "Testing search by parse tree"
+
+ # Search by conjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(codePage=x)(revision=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by conjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(lastLogon=x)(description=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by conjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(codePage=x)(description=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by conjunction of local and remote attribute w/o match
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(codePage=x)(nextRid=x))", attrs=attrs)
+ self.assertEquals(len(res), 0)
+ res = self.ldb.search(expression="(&(revision=x)(lastLogon=z))", attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by disjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(revision=x)(dnsHostName=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by disjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(badPwdCount=x)(lastLogon=x))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue("dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertTrue("dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "x")
+
+ # Search by disjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(revision=x)(lastLogon=y))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertTrue("dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[2]["dnsHostName"], "x")
+ self.assertEquals(res[2]["lastLogon"], "x")
+
+ # Search by disjunction of local and remote attribute w/o match
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(codePage=y)(nextRid=z))", attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by negated local attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(revision=x))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(description=x))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[0]["dnsHostName"], "z")
+ self.assertEquals(res[0]["lastLogon"], "z")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "z")
+
+ # Search by negated conjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(&(codePage=x)(revision=x)))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated conjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(&(lastLogon=x)(description=x)))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated conjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(&(codePage=x)(description=x)))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated disjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))", attrs=attrs)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated disjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(|(badPwdCount=x)(lastLogon=x)))", attrs=attrs)
+ self.assertEquals(len(res), 4)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[1]["dnsHostName"], "z")
+ self.assertEquals(res[1]["lastLogon"], "z")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "z")
+
+ # Search by negated disjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(|(revision=x)(lastLogon=y)))", attrs=attrs)
+ self.assertEquals(len(res), 4)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[1]["dnsHostName"], "z")
+ self.assertEquals(res[1]["lastLogon"], "z")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "z")
+
+ print "Search by complex parse tree"
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(&(revision=x)(dnsHostName=x))(!(&(description=x)(nextRid=y)))(badPwdCount=y))", attrs=attrs)
+ self.assertEquals(len(res), 6)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "x")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[3]["dnsHostName"], "z")
+ self.assertEquals(res[3]["lastLogon"], "z")
+ self.assertEquals(str(res[4].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[4])
+ self.assertEquals(res[4]["lastLogon"], "z")
+
+ # Clean up
+ dns = [self.samba4.dn("cn=%s" % n) for n in ["A","B","C","X","Y","Z"]]
+ for dn in dns:
+ self.ldb.delete(dn)
+
+ def test_map_modify_local(self):
+ """Modification of local records."""
+ # Add local record
+ dn = "cn=test,dc=idealx,dc=org"
+ self.ldb.add({"dn": dn,
+ "cn": "test",
+ "foo": "bar",
+ "revision": "1",
+ "description": "test"})
+ # Check it's there
+ attrs = ["foo", "revision", "description"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["foo"], "bar")
+ self.assertEquals(res[0]["revision"], "1")
+ self.assertEquals(res[0]["description"], "test")
+ # Check it's not in the local db
+ res = self.samba4.db.search(expression="(cn=test)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 0)
+ # Check it's not in the remote db
+ res = self.samba3.db.search(expression="(cn=test)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Modify local record
+ ldif = """
+dn: """ + dn + """
+replace: foo
+foo: baz
+replace: description
+description: foo
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in local db
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["foo"], "baz")
+ self.assertEquals(res[0]["revision"], "1")
+ self.assertEquals(res[0]["description"], "foo")
+
+ # Rename local record
+ dn2 = "cn=toast,dc=idealx,dc=org"
+ self.ldb.rename(dn, dn2)
+ # Check in local db
+ res = self.ldb.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["foo"], "baz")
+ self.assertEquals(res[0]["revision"], "1")
+ self.assertEquals(res[0]["description"], "foo")
+
+ # Delete local record
+ self.ldb.delete(dn2)
+ # Check it's gone
+ res = self.ldb.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+
+ def test_map_modify_remote_remote(self):
+ """Modification of remote data of remote records"""
+ # Add remote record
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.samba3.db.add({"dn": dn2,
+ "cn": "test",
+ "description": "foo",
+ "sambaBadPasswordCount": "3",
+ "sambaNextRid": "1001"})
+ # Check it's there
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "3")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["badPwdCount"], "3")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Modify remote data of remote record
+ ldif = """
+dn: """ + dn + """
+replace: description
+description: test
+replace: badPwdCount
+badPwdCount: 4
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+
+ # Rename remote record
+ dn2 = self.samba4.dn("cn=toast")
+ self.ldb.rename(dn, dn2)
+ # Check in mapped db
+ dn = dn2
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ # Check in remote db
+ dn2 = self.samba3.dn("cn=toast")
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+
+ # Delete remote record
+ self.ldb.delete(dn)
+ # Check in mapped db
+ res = self.ldb.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+
+ def test_map_modify_remote_local(self):
+ """Modification of local data of remote records"""
+ # Add remote record (same as before)
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.samba3.db.add({"dn": dn2,
+ "cn": "test",
+ "description": "foo",
+ "sambaBadPasswordCount": "3",
+ "sambaNextRid": "1001"})
+
+ # Modify local data of remote record
+ ldif = """
+dn: """ + dn + """
+add: revision
+revision: 1
+replace: description
+description: test
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["revision", "description"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["revision"], "1")
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertTrue(not "revision" in res[0])
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertEquals(res[0]["revision"], "1")
+
+ # Delete (newly) split record
+ self.ldb.delete(dn)
+
+ def test_map_modify_split(self):
+ """Testing modification of split records"""
+ # Add split record
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.ldb.add({
+ "dn": dn,
+ "cn": "test",
+ "description": "foo",
+ "badPwdCount": "3",
+ "nextRid": "1001",
+ "revision": "1"})
+ # Check it's there
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["badPwdCount"], "3")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ self.assertEquals(res[0]["revision"], "1")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(res[0]["revision"], "1")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "3")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Modify of split record
+ ldif = """
+dn: """ + dn + """
+replace: description
+description: test
+replace: badPwdCount
+badPwdCount: 4
+replace: revision
+revision: 2
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Rename split record
+ dn2 = self.samba4.dn("cn=toast")
+ self.ldb.rename(dn, dn2)
+ # Check in mapped db
+ dn = dn2
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in remote db
+ dn2 = self.samba3.dn("cn=toast")
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Delete split record
+ self.ldb.delete(dn)
+ # Check in mapped db
+ res = self.ldb.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
diff --git a/source4/dsdb/samdb/ldb_modules/update_keytab.c b/source4/dsdb/samdb/ldb_modules/update_keytab.c
new file mode 100644
index 0000000000..b36c2c9b71
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/update_keytab.c
@@ -0,0 +1,211 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb update_keytabs module
+ *
+ * Description: Update keytabs whenever their matching secret record changes
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "system/kerberos.h"
+#include "param/param.h"
+
+struct dn_list {
+ struct cli_credentials *creds;
+ struct dn_list *prev, *next;
+};
+
+struct update_kt_private {
+ struct dn_list *changed_dns;
+};
+
+static int add_modified(struct ldb_module *module, struct ldb_dn *dn, bool delete) {
+ struct update_kt_private *data = talloc_get_type(module->private_data, struct update_kt_private);
+ struct dn_list *item;
+ char *filter;
+ struct ldb_result *res;
+ const char *attrs[] = { NULL };
+ int ret;
+ NTSTATUS status;
+
+ filter = talloc_asprintf(data, "(&(dn=%s)(&(objectClass=kerberosSecret)(privateKeytab=*)))",
+ ldb_dn_get_linearized(dn));
+ if (!filter) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE,
+ filter, attrs, &res);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(filter);
+ return ret;
+ }
+
+ if (res->count != 1) {
+ /* if it's not a kerberosSecret then we don't have anything to update */
+ talloc_free(res);
+ talloc_free(filter);
+ return LDB_SUCCESS;
+ }
+ talloc_free(res);
+
+ item = talloc(data->changed_dns? (void *)data->changed_dns: (void *)data, struct dn_list);
+ if (!item) {
+ talloc_free(filter);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ item->creds = cli_credentials_init(item);
+ if (!item->creds) {
+ DEBUG(1, ("cli_credentials_init failed!"));
+ talloc_free(filter);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ cli_credentials_set_conf(item->creds, ldb_get_opaque(module->ldb, "loadparm"));
+ status = cli_credentials_set_secrets(item->creds, ldb_get_opaque(module->ldb, "EventContext"), ldb_get_opaque(module->ldb, "loadparm"), module->ldb, NULL, filter);
+ talloc_free(filter);
+ if (NT_STATUS_IS_OK(status)) {
+ if (delete) {
+ /* Ensure we don't helpfully keep an old keytab entry */
+ cli_credentials_set_kvno(item->creds, cli_credentials_get_kvno(item->creds)+2);
+ /* Wipe passwords */
+ cli_credentials_set_nt_hash(item->creds, NULL,
+ CRED_SPECIFIED);
+ }
+ DLIST_ADD_END(data->changed_dns, item, struct dn_list *);
+ }
+ return LDB_SUCCESS;
+}
+
+/* add */
+static int update_kt_add(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return add_modified(module, req->op.add.message->dn, false);
+}
+
+/* modify */
+static int update_kt_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return add_modified(module, req->op.mod.message->dn, false);
+}
+
+/* delete */
+static int update_kt_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ /* Before we delete it, record the details */
+ ret = add_modified(module, req->op.del.dn, true);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, req);
+}
+
+/* rename */
+static int update_kt_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return add_modified(module, req->op.rename.newdn, false);
+}
+
+/* end a transaction */
+static int update_kt_end_trans(struct ldb_module *module)
+{
+ struct update_kt_private *data = talloc_get_type(module->private_data, struct update_kt_private);
+
+ struct dn_list *p;
+ for (p=data->changed_dns; p; p = p->next) {
+ int kret;
+ kret = cli_credentials_update_keytab(p->creds, ldb_get_opaque(module->ldb, "EventContext"), ldb_get_opaque(module->ldb, "loadparm"));
+ if (kret != 0) {
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ ldb_asprintf_errstring(module->ldb, "Failed to update keytab: %s", error_message(kret));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ return ldb_next_end_trans(module);
+}
+
+/* end a transaction */
+static int update_kt_del_trans(struct ldb_module *module)
+{
+ struct update_kt_private *data = talloc_get_type(module->private_data, struct update_kt_private);
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+
+ return ldb_next_del_trans(module);
+}
+
+static int update_kt_init(struct ldb_module *module)
+{
+ struct update_kt_private *data;
+
+ data = talloc(module, struct update_kt_private);
+ if (data == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ module->private_data = data;
+ data->changed_dns = NULL;
+
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_update_keytab_module_ops = {
+ .name = "update_keytab",
+ .init_context = update_kt_init,
+ .add = update_kt_add,
+ .modify = update_kt_modify,
+ .rename = update_kt_rename,
+ .del = update_kt_delete,
+ .end_transaction = update_kt_end_trans,
+ .del_transaction = update_kt_del_trans,
+};
diff --git a/source4/dsdb/samdb/samdb.c b/source4/dsdb/samdb/samdb.c
new file mode 100644
index 0000000000..b5b7ddfdc6
--- /dev/null
+++ b/source4/dsdb/samdb/samdb.c
@@ -0,0 +1,296 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ interface functions for the sam database
+
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Volker Lendecke 2004
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+
+ 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 "librpc/gen_ndr/ndr_netlogon.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "lib/events/events.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "libcli/security/security.h"
+#include "libcli/auth/libcli_auth.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "system/time.h"
+#include "system/filesys.h"
+#include "ldb_wrap.h"
+#include "util/util_ldb.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.h"
+#include "param/param.h"
+#include "lib/events/events.h"
+#include "auth/credentials/credentials.h"
+#include "param/secrets.h"
+
+char *samdb_relative_path(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const char *name)
+{
+ const char *base_url =
+ (const char *)ldb_get_opaque(ldb, "ldb_url");
+ char *path, *p, *full_name;
+ if (name == NULL) {
+ return NULL;
+ }
+ if (name[0] == 0 || name[0] == '/' || strstr(name, ":/")) {
+ return talloc_strdup(mem_ctx, name);
+ }
+ path = talloc_strdup(mem_ctx, base_url);
+ if (path == NULL) {
+ return NULL;
+ }
+ if ( (p = strrchr(path, '/')) != NULL) {
+ p[0] = '\0';
+ full_name = talloc_asprintf(mem_ctx, "%s/%s", path, name);
+ } else {
+ full_name = talloc_asprintf(mem_ctx, "./%s", name);
+ }
+ talloc_free(path);
+ return full_name;
+}
+
+struct cli_credentials *samdb_credentials(TALLOC_CTX *mem_ctx,
+ struct event_context *event_ctx,
+ struct loadparm_context *lp_ctx)
+{
+ struct cli_credentials *cred = cli_credentials_init(mem_ctx);
+ if (!cred) {
+ return NULL;
+ }
+ cli_credentials_set_conf(cred, lp_ctx);
+
+ /* We don't want to use krb5 to talk to our samdb - recursion
+ * here would be bad, and this account isn't in the KDC
+ * anyway */
+ cli_credentials_set_kerberos_state(cred, CRED_DONT_USE_KERBEROS);
+
+ if (!NT_STATUS_IS_OK(cli_credentials_set_secrets(cred, event_ctx, lp_ctx, NULL, NULL,
+ SECRETS_LDAP_FILTER))) {
+ /* Perfectly OK - if not against an LDAP backend */
+ return NULL;
+ }
+ return cred;
+}
+
+/*
+ connect to the SAM database
+ return an opaque context pointer on success, or NULL on failure
+ */
+struct ldb_context *samdb_connect(TALLOC_CTX *mem_ctx,
+ struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info *session_info)
+{
+ struct ldb_context *ldb;
+ ldb = ldb_wrap_connect(mem_ctx, ev_ctx, lp_ctx,
+ lp_sam_url(lp_ctx), session_info,
+ samdb_credentials(mem_ctx, ev_ctx, lp_ctx),
+ 0, NULL);
+ if (!ldb) {
+ return NULL;
+ }
+ dsdb_make_schema_global(ldb);
+ return ldb;
+}
+
+/*
+ copy from a template record to a message
+*/
+int samdb_copy_template(struct ldb_context *ldb,
+ struct ldb_message *msg, const char *name,
+ const char **errstring)
+{
+ struct ldb_result *res;
+ struct ldb_message *t;
+ int ret, i, j;
+ struct ldb_context *templates_ldb;
+ char *templates_ldb_path;
+ struct ldb_dn *basedn;
+ struct event_context *event_ctx;
+ struct loadparm_context *lp_ctx;
+
+ templates_ldb = talloc_get_type(ldb_get_opaque(ldb, "templates_ldb"), struct ldb_context);
+
+ if (!templates_ldb) {
+ templates_ldb_path = samdb_relative_path(ldb,
+ msg,
+ "templates.ldb");
+ if (!templates_ldb_path) {
+ *errstring = talloc_asprintf(msg, "samdb_copy_template: ERROR: Failed to contruct path for template db");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ event_ctx = ldb_get_event_context(ldb);
+ lp_ctx = (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm");
+
+ /* FIXME: need to remove this wehn we finally pass the event
+ * context around in ldb */
+ if (event_ctx == NULL) {
+ event_ctx = s4_event_context_init(templates_ldb);
+ }
+
+ templates_ldb = ldb_wrap_connect(ldb, event_ctx, lp_ctx,
+ templates_ldb_path, NULL,
+ NULL, 0, NULL);
+ talloc_free(templates_ldb_path);
+ if (!templates_ldb) {
+ *errstring = talloc_asprintf(msg, "samdb_copy_template: ERROR: Failed to connect to templates db at: %s",
+ templates_ldb_path);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_set_opaque(ldb, "templates_ldb", templates_ldb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ *errstring = NULL;
+
+ basedn = ldb_dn_new(templates_ldb, ldb, "cn=Templates");
+ if (!ldb_dn_add_child_fmt(basedn, "CN=Template%s", name)) {
+ talloc_free(basedn);
+ *errstring = talloc_asprintf(msg, "samdb_copy_template: ERROR: Failed to contruct DN for template '%s'",
+ name);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* pull the template record */
+ ret = ldb_search(templates_ldb, basedn, LDB_SCOPE_BASE, "distinguishedName=*", NULL, &res);
+ talloc_free(basedn);
+ if (ret != LDB_SUCCESS) {
+ *errstring = talloc_steal(msg, ldb_errstring(templates_ldb));
+ return ret;
+ }
+ if (res->count != 1) {
+ *errstring = talloc_asprintf(msg, "samdb_copy_template: ERROR: template '%s' matched %d records, expected 1",
+ name,
+ res->count);
+ talloc_free(res);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ t = res->msgs[0];
+
+ for (i = 0; i < t->num_elements; i++) {
+ struct ldb_message_element *el = &t->elements[i];
+ /* some elements should not be copied from the template */
+ if (ldb_attr_cmp(el->name, "cn") == 0 ||
+ ldb_attr_cmp(el->name, "name") == 0 ||
+ ldb_attr_cmp(el->name, "objectClass") == 0 ||
+ ldb_attr_cmp(el->name, "sAMAccountName") == 0 ||
+ ldb_attr_cmp(el->name, "sAMAccountName") == 0 ||
+ ldb_attr_cmp(el->name, "distinguishedName") == 0 ||
+ ldb_attr_cmp(el->name, "objectGUID") == 0) {
+ continue;
+ }
+ for (j = 0; j < el->num_values; j++) {
+ ret = samdb_find_or_add_attribute(ldb, msg, el->name,
+ (char *)el->values[j].data);
+ if (ret) {
+ *errstring = talloc_asprintf(msg, "Adding attribute %s failed.", el->name);
+ talloc_free(res);
+ return ret;
+ }
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+
+/****************************************************************************
+ Create the SID list for this user.
+****************************************************************************/
+NTSTATUS security_token_create(TALLOC_CTX *mem_ctx,
+ struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ struct dom_sid *user_sid,
+ struct dom_sid *group_sid,
+ int n_groupSIDs,
+ struct dom_sid **groupSIDs,
+ bool is_authenticated,
+ struct security_token **token)
+{
+ struct security_token *ptoken;
+ int i;
+ NTSTATUS status;
+
+ ptoken = security_token_initialise(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(ptoken);
+
+ ptoken->sids = talloc_array(ptoken, struct dom_sid *, n_groupSIDs + 5);
+ NT_STATUS_HAVE_NO_MEMORY(ptoken->sids);
+
+ ptoken->user_sid = talloc_reference(ptoken, user_sid);
+ ptoken->group_sid = talloc_reference(ptoken, group_sid);
+ ptoken->privilege_mask = 0;
+
+ ptoken->sids[0] = ptoken->user_sid;
+ ptoken->sids[1] = ptoken->group_sid;
+
+ /*
+ * Finally add the "standard" SIDs.
+ * The only difference between guest and "anonymous"
+ * is the addition of Authenticated_Users.
+ */
+ ptoken->sids[2] = dom_sid_parse_talloc(ptoken->sids, SID_WORLD);
+ NT_STATUS_HAVE_NO_MEMORY(ptoken->sids[2]);
+ ptoken->sids[3] = dom_sid_parse_talloc(ptoken->sids, SID_NT_NETWORK);
+ NT_STATUS_HAVE_NO_MEMORY(ptoken->sids[3]);
+ ptoken->num_sids = 4;
+
+ if (is_authenticated) {
+ ptoken->sids[4] = dom_sid_parse_talloc(ptoken->sids, SID_NT_AUTHENTICATED_USERS);
+ NT_STATUS_HAVE_NO_MEMORY(ptoken->sids[4]);
+ ptoken->num_sids++;
+ }
+
+ for (i = 0; i < n_groupSIDs; i++) {
+ size_t check_sid_idx;
+ for (check_sid_idx = 1;
+ check_sid_idx < ptoken->num_sids;
+ check_sid_idx++) {
+ if (dom_sid_equal(ptoken->sids[check_sid_idx], groupSIDs[i])) {
+ break;
+ }
+ }
+
+ if (check_sid_idx == ptoken->num_sids) {
+ ptoken->sids[ptoken->num_sids++] = talloc_reference(ptoken->sids, groupSIDs[i]);
+ }
+ }
+
+ /* setup the privilege mask for this token */
+ status = samdb_privilege_setup(ev_ctx, lp_ctx, ptoken);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(ptoken);
+ return status;
+ }
+
+ security_token_debug(10, ptoken);
+
+ *token = ptoken;
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h
new file mode 100644
index 0000000000..f24a75fd8f
--- /dev/null
+++ b/source4/dsdb/samdb/samdb.h
@@ -0,0 +1,100 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ interface functions for the sam database
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+#ifndef __SAMDB_H__
+#define __SAMDB_H__
+
+struct auth_session_info;
+struct dsdb_control_current_partition;
+struct dsdb_extended_replicated_object;
+struct dsdb_extended_replicated_objects;
+struct loadparm_context;
+struct event_context;
+
+#include "librpc/gen_ndr/security.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb-samba/ldif_handlers.h"
+#include "librpc/gen_ndr/samr.h"
+#include "librpc/gen_ndr/drsuapi.h"
+#include "librpc/gen_ndr/drsblobs.h"
+#include "dsdb/schema/schema.h"
+#include "dsdb/samdb/samdb_proto.h"
+#include "dsdb/common/proto.h"
+#include "dsdb/common/flags.h"
+
+#define DSDB_CONTROL_CURRENT_PARTITION_OID "1.3.6.1.4.1.7165.4.3.2"
+struct dsdb_control_current_partition {
+ /*
+ * this is the version of the dsdb_control_current_partition
+ * version 0: initial implementation
+ */
+#define DSDB_CONTROL_CURRENT_PARTITION_VERSION 0
+ uint32_t version;
+
+ struct ldb_dn *dn;
+
+ const char *backend;
+
+ struct ldb_module *module;
+};
+
+#define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1"
+struct dsdb_extended_replicated_object {
+ struct ldb_message *msg;
+ struct ldb_val guid_value;
+ const char *when_changed;
+ struct replPropertyMetaDataBlob *meta_data;
+};
+
+struct dsdb_extended_replicated_objects {
+ /*
+ * this is the version of the dsdb_extended_replicated_objects
+ * version 0: initial implementation
+ */
+#define DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION 0
+ uint32_t version;
+
+ struct ldb_dn *partition_dn;
+
+ const struct repsFromTo1 *source_dsa;
+ const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector;
+
+ uint32_t num_objects;
+ struct dsdb_extended_replicated_object *objects;
+};
+
+struct dsdb_naming_fsmo {
+ bool we_are_master;
+ struct ldb_dn *master_dn;
+};
+
+struct dsdb_pdc_fsmo {
+ bool we_are_master;
+ struct ldb_dn *master_dn;
+};
+
+/*
+ * the schema_dn is passed as struct ldb_dn in
+ * req->op.extended.data
+ */
+#define DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID "1.3.6.1.4.1.7165.4.4.2"
+
+#endif /* __SAMDB_H__ */
diff --git a/source4/dsdb/samdb/samdb_privilege.c b/source4/dsdb/samdb/samdb_privilege.c
new file mode 100644
index 0000000000..688d1ef9de
--- /dev/null
+++ b/source4/dsdb/samdb/samdb_privilege.c
@@ -0,0 +1,121 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ manipulate privilege records in samdb
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "libcli/ldap/ldap_ndr.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth.h"
+#include "libcli/security/security.h"
+#include "util/util_ldb.h"
+#include "param/param.h"
+
+/*
+ add privilege bits for one sid to a security_token
+*/
+static NTSTATUS samdb_privilege_setup_sid(void *samctx, TALLOC_CTX *mem_ctx,
+ struct security_token *token,
+ const struct dom_sid *sid)
+{
+ const char * const attrs[] = { "privilege", NULL };
+ struct ldb_message **res = NULL;
+ struct ldb_message_element *el;
+ int ret, i;
+ char *sidstr;
+
+ sidstr = ldap_encode_ndr_dom_sid(mem_ctx, sid);
+ NT_STATUS_HAVE_NO_MEMORY(sidstr);
+
+ ret = gendb_search(samctx, mem_ctx, NULL, &res, attrs, "objectSid=%s", sidstr);
+ talloc_free(sidstr);
+ if (ret != 1) {
+ /* not an error to not match */
+ return NT_STATUS_OK;
+ }
+
+ el = ldb_msg_find_element(res[0], "privilege");
+ if (el == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ for (i=0;i<el->num_values;i++) {
+ const char *priv_str = (const char *)el->values[i].data;
+ enum sec_privilege privilege = sec_privilege_id(priv_str);
+ if (privilege == -1) {
+ DEBUG(1,("Unknown privilege '%s' in samdb\n",
+ priv_str));
+ continue;
+ }
+ security_token_set_privilege(token, privilege);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ setup the privilege mask for this security token based on our
+ local SAM
+*/
+NTSTATUS samdb_privilege_setup(struct event_context *ev_ctx,
+ struct loadparm_context *lp_ctx, struct security_token *token)
+{
+ void *samctx;
+ TALLOC_CTX *mem_ctx;
+ int i;
+ NTSTATUS status;
+
+ /* Shortcuts to prevent recursion and avoid lookups */
+ if (token->user_sid == NULL) {
+ token->privilege_mask = 0;
+ return NT_STATUS_OK;
+ }
+
+ if (security_token_is_system(token)) {
+ token->privilege_mask = ~0;
+ return NT_STATUS_OK;
+ }
+
+ if (security_token_is_anonymous(token)) {
+ token->privilege_mask = 0;
+ return NT_STATUS_OK;
+ }
+
+ mem_ctx = talloc_new(token);
+ samctx = samdb_connect(mem_ctx, ev_ctx, lp_ctx, system_session(mem_ctx, lp_ctx));
+ if (samctx == NULL) {
+ talloc_free(mem_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ token->privilege_mask = 0;
+
+ for (i=0;i<token->num_sids;i++) {
+ status = samdb_privilege_setup_sid(samctx, mem_ctx,
+ token, token->sids[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(mem_ctx);
+ return status;
+ }
+ }
+
+ talloc_free(mem_ctx);
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/dsdb/schema/schema.h b/source4/dsdb/schema/schema.h
new file mode 100644
index 0000000000..68dc8197cb
--- /dev/null
+++ b/source4/dsdb/schema/schema.h
@@ -0,0 +1,183 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB schema header
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2006
+
+ 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/>.
+
+*/
+
+#ifndef _DSDB_SCHEMA_H
+#define _DSDB_SCHEMA_H
+
+struct dsdb_attribute;
+struct dsdb_class;
+struct dsdb_schema;
+
+struct dsdb_syntax {
+ const char *name;
+ const char *ldap_oid;
+ uint32_t oMSyntax;
+ struct ldb_val oMObjectClass;
+ const char *attributeSyntax_oid;
+ const char *equality;
+ const char *substring;
+ const char *comment;
+ const char *ldb_syntax;
+
+ WERROR (*drsuapi_to_ldb)(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out);
+ WERROR (*ldb_to_drsuapi)(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out);
+};
+
+struct dsdb_attribute {
+ struct dsdb_attribute *prev, *next;
+
+ const char *cn;
+ const char *lDAPDisplayName;
+ const char *attributeID_oid;
+ uint32_t attributeID_id;
+ struct GUID schemaIDGUID;
+ uint32_t mAPIID;
+
+ struct GUID attributeSecurityGUID;
+
+ uint32_t searchFlags;
+ uint32_t systemFlags;
+ bool isMemberOfPartialAttributeSet;
+ uint32_t linkID;
+
+ const char *attributeSyntax_oid;
+ uint32_t attributeSyntax_id;
+ uint32_t oMSyntax;
+ struct ldb_val oMObjectClass;
+
+ bool isSingleValued;
+ uint32_t rangeLower;
+ uint32_t rangeUpper;
+ bool extendedCharsAllowed;
+
+ uint32_t schemaFlagsEx;
+ struct ldb_val msDs_Schema_Extensions;
+
+ bool showInAdvancedViewOnly;
+ const char *adminDisplayName;
+ const char *adminDescription;
+ const char *classDisplayName;
+ bool isEphemeral;
+ bool isDefunct;
+ bool systemOnly;
+
+ /* internal stuff */
+ const struct dsdb_syntax *syntax;
+};
+
+struct dsdb_class {
+ struct dsdb_class *prev, *next;
+
+ const char *cn;
+ const char *lDAPDisplayName;
+ const char *governsID_oid;
+ uint32_t governsID_id;
+ struct GUID schemaIDGUID;
+
+ uint32_t objectClassCategory;
+ const char *rDNAttID;
+ const char *defaultObjectCategory;
+
+ const char *subClassOf;
+
+ const char **systemAuxiliaryClass;
+ const char **systemPossSuperiors;
+ const char **systemMustContain;
+ const char **systemMayContain;
+
+ const char **auxiliaryClass;
+ const char **possSuperiors;
+ const char **mustContain;
+ const char **mayContain;
+ const char **possibleInferiors;
+
+ const char *defaultSecurityDescriptor;
+
+ uint32_t schemaFlagsEx;
+ struct ldb_val msDs_Schema_Extensions;
+
+ bool showInAdvancedViewOnly;
+ const char *adminDisplayName;
+ const char *adminDescription;
+ const char *classDisplayName;
+ bool defaultHidingValue;
+ bool isDefunct;
+ bool systemOnly;
+};
+
+struct dsdb_schema_oid_prefix {
+ uint32_t id;
+ const char *oid;
+ size_t oid_len;
+};
+
+struct dsdb_schema {
+ uint32_t num_prefixes;
+ struct dsdb_schema_oid_prefix *prefixes;
+
+ /*
+ * the last element of the prefix mapping table isn't a oid,
+ * it starts with 0xFF and has 21 bytes and is maybe a schema
+ * version number
+ *
+ * this is the content of the schemaInfo attribute of the
+ * Schema-Partition head object.
+ */
+ const char *schema_info;
+
+ struct dsdb_attribute *attributes;
+ struct dsdb_class *classes;
+
+ struct {
+ bool we_are_master;
+ struct ldb_dn *master_dn;
+ } fsmo;
+
+ struct smb_iconv_convenience *iconv_convenience;
+};
+
+enum dsdb_attr_list_query {
+ DSDB_SCHEMA_ALL_MAY,
+ DSDB_SCHEMA_ALL_MUST,
+ DSDB_SCHEMA_SYS_MAY,
+ DSDB_SCHEMA_SYS_MUST,
+ DSDB_SCHEMA_MAY,
+ DSDB_SCHEMA_MUST,
+ DSDB_SCHEMA_ALL
+};
+
+enum dsdb_schema_convert_target {
+ TARGET_OPENLDAP,
+ TARGET_FEDORA_DS,
+ TARGET_AD_SCHEMA_SUBENTRY
+};
+
+#include "dsdb/schema/proto.h"
+
+#endif /* _DSDB_SCHEMA_H */
diff --git a/source4/dsdb/schema/schema_description.c b/source4/dsdb/schema/schema_description.c
new file mode 100644
index 0000000000..6884c5284e
--- /dev/null
+++ b/source4/dsdb/schema/schema_description.c
@@ -0,0 +1,316 @@
+/*
+ Unix SMB/CIFS mplementation.
+ Print schema info into string format
+
+ Copyright (C) Andrew Bartlett 2006-2008
+
+ 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/samdb/samdb.h"
+
+#define IF_NULL_FAIL_RET(x) do { \
+ if (!x) { \
+ return NULL; \
+ } \
+ } while (0)
+
+
+char *schema_attribute_description(TALLOC_CTX *mem_ctx,
+ enum dsdb_schema_convert_target target,
+ const char *seperator,
+ const char *oid,
+ const char *name,
+ const char *description,
+ const char *equality,
+ const char *substring,
+ const char *syntax,
+ bool single_value, bool operational)
+{
+ char *schema_entry = talloc_asprintf(mem_ctx,
+ "(%s%s%s", seperator, oid, seperator);
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "NAME '%s'%s", name, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+
+ if (description) {
+#if 0
+ /* Need a way to escape ' characters from the description */
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "DESC '%s'%s", description, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+#endif
+ }
+
+ if (equality) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "EQUALITY %s%s", equality, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+ if (substring) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "SUBSTR %s%s", substring, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "SYNTAX %s%s", syntax, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+
+ if (single_value) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "SINGLE-VALUE%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ if (operational) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "NO-USER-MODIFICATION%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ ")");
+ return schema_entry;
+}
+
+char *schema_attribute_to_description(TALLOC_CTX *mem_ctx, const struct dsdb_attribute *attribute)
+{
+ char *schema_description;
+ const struct dsdb_syntax *map = find_syntax_map_by_ad_oid(attribute->attributeSyntax_oid);
+ const char *syntax = map ? map->ldap_oid : attribute->attributeSyntax_oid;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NULL;
+ }
+
+
+ schema_description
+ = schema_attribute_description(mem_ctx,
+ TARGET_AD_SCHEMA_SUBENTRY,
+ " ",
+ attribute->attributeID_oid,
+ attribute->lDAPDisplayName,
+ NULL, NULL, NULL, talloc_asprintf(tmp_ctx, "'%s'", syntax),
+ attribute->isSingleValued,
+ attribute->systemOnly);
+ talloc_free(tmp_ctx);
+ return schema_description;
+}
+
+#define APPEND_ATTRS(attributes) \
+ do { \
+ int k; \
+ for (k=0; attributes && attributes[k]; k++) { \
+ const char *attr_name = attributes[k]; \
+ \
+ schema_entry = talloc_asprintf_append(schema_entry, \
+ "%s ", \
+ attr_name); \
+ IF_NULL_FAIL_RET(schema_entry); \
+ if (attributes[k+1]) { \
+ IF_NULL_FAIL_RET(schema_entry); \
+ if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
+ schema_entry = talloc_asprintf_append(schema_entry, \
+ "$%s ", seperator); \
+ IF_NULL_FAIL_RET(schema_entry); \
+ } else { \
+ schema_entry = talloc_asprintf_append(schema_entry, \
+ "$ "); \
+ } \
+ } \
+ } \
+ } while (0)
+
+
+/* Print a schema class or dITContentRule as a string.
+ *
+ * To print a scheam class, specify objectClassCategory but not auxillary_classes
+ * To print a dITContentRule, specify auxillary_classes but set objectClassCategory == -1
+ *
+ */
+
+char *schema_class_description(TALLOC_CTX *mem_ctx,
+ enum dsdb_schema_convert_target target,
+ const char *seperator,
+ const char *oid,
+ const char *name,
+ const char **auxillary_classes,
+ const char *description,
+ const char *subClassOf,
+ int objectClassCategory,
+ char **must,
+ char **may)
+{
+ char *schema_entry = talloc_asprintf(mem_ctx,
+ "(%s%s%s", seperator, oid, seperator);
+
+ IF_NULL_FAIL_RET(schema_entry);
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "NAME '%s'%s", name, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+
+ if (description) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "DESC '%s'%s", description, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ if (auxillary_classes) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "AUX ( ");
+ IF_NULL_FAIL_RET(schema_entry);
+
+ APPEND_ATTRS(auxillary_classes);
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ ")%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ if (subClassOf && strcasecmp(subClassOf, name) != 0) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "SUP %s%s", subClassOf, seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ switch (objectClassCategory) {
+ case -1:
+ break;
+ /* Dummy case for when used for printing ditContentRules */
+ case 0:
+ /*
+ * NOTE: this is an type 88 class
+ * e.g. 2.5.6.6 NAME 'person'
+ * but w2k3 gives STRUCTURAL here!
+ */
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "STRUCTURAL%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ break;
+ case 1:
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "STRUCTURAL%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ break;
+ case 2:
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "ABSTRACT%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ break;
+ case 3:
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "AUXILIARY%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ break;
+ }
+
+ if (must) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "MUST (%s", target == TARGET_AD_SCHEMA_SUBENTRY ? "" : " ");
+ IF_NULL_FAIL_RET(schema_entry);
+
+ APPEND_ATTRS(must);
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ ")%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ if (may) {
+ schema_entry = talloc_asprintf_append(schema_entry,
+ "MAY (%s", target == TARGET_AD_SCHEMA_SUBENTRY ? "" : " ");
+ IF_NULL_FAIL_RET(schema_entry);
+
+ APPEND_ATTRS(may);
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ ")%s", seperator);
+ IF_NULL_FAIL_RET(schema_entry);
+ }
+
+ schema_entry = talloc_asprintf_append(schema_entry,
+ ")");
+ return schema_entry;
+}
+
+char *schema_class_to_description(TALLOC_CTX *mem_ctx, const struct dsdb_class *class)
+{
+ char *schema_description;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NULL;
+ }
+
+ schema_description
+ = schema_class_description(mem_ctx,
+ TARGET_AD_SCHEMA_SUBENTRY,
+ " ",
+ class->governsID_oid,
+ class->lDAPDisplayName,
+ NULL,
+ NULL,
+ class->subClassOf,
+ class->objectClassCategory,
+ dsdb_attribute_list(tmp_ctx,
+ class, DSDB_SCHEMA_ALL_MUST),
+ dsdb_attribute_list(tmp_ctx,
+ class, DSDB_SCHEMA_ALL_MAY));
+ talloc_free(tmp_ctx);
+ return schema_description;
+}
+char *schema_class_to_dITContentRule(TALLOC_CTX *mem_ctx, const struct dsdb_class *class,
+ const struct dsdb_schema *schema)
+{
+ int i;
+ char *schema_description;
+ char **aux_class_list = NULL;
+ char **attrs;
+ char **must_attr_list = NULL;
+ char **may_attr_list = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ const struct dsdb_class *aux_class;
+ if (!tmp_ctx) {
+ return NULL;
+ }
+
+ aux_class_list = merge_attr_list(tmp_ctx, aux_class_list, class->systemAuxiliaryClass);
+ aux_class_list = merge_attr_list(tmp_ctx, aux_class_list, class->auxiliaryClass);
+
+ for (i=0; aux_class_list && aux_class_list[i]; i++) {
+ aux_class = dsdb_class_by_lDAPDisplayName(schema, aux_class_list[i]);
+
+ attrs = dsdb_attribute_list(mem_ctx, aux_class, DSDB_SCHEMA_ALL_MUST);
+ must_attr_list = merge_attr_list(mem_ctx, must_attr_list, attrs);
+
+ attrs = dsdb_attribute_list(mem_ctx, aux_class, DSDB_SCHEMA_ALL_MAY);
+ may_attr_list = merge_attr_list(mem_ctx, may_attr_list, attrs);
+ }
+
+ schema_description
+ = schema_class_description(mem_ctx,
+ TARGET_AD_SCHEMA_SUBENTRY,
+ " ",
+ class->governsID_oid,
+ class->lDAPDisplayName,
+ (const char **)aux_class_list,
+ NULL,
+ class->subClassOf,
+ -1, must_attr_list, may_attr_list);
+ talloc_free(tmp_ctx);
+ return schema_description;
+}
diff --git a/source4/dsdb/schema/schema_init.c b/source4/dsdb/schema/schema_init.c
new file mode 100644
index 0000000000..3ed7daee59
--- /dev/null
+++ b/source4/dsdb/schema/schema_init.c
@@ -0,0 +1,1411 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB schema header
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2006-2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2008
+
+ 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/samdb/samdb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+
+struct dsdb_schema *dsdb_new_schema(TALLOC_CTX *mem_ctx, struct smb_iconv_convenience *iconv_convenience)
+{
+ struct dsdb_schema *schema = talloc_zero(mem_ctx, struct dsdb_schema);
+ if (!schema) {
+ return NULL;
+ }
+
+ schema->iconv_convenience = iconv_convenience;
+ return schema;
+}
+
+
+WERROR dsdb_load_oid_mappings_drsuapi(struct dsdb_schema *schema, const struct drsuapi_DsReplicaOIDMapping_Ctr *ctr)
+{
+ uint32_t i,j;
+
+ schema->prefixes = talloc_array(schema, struct dsdb_schema_oid_prefix, ctr->num_mappings);
+ W_ERROR_HAVE_NO_MEMORY(schema->prefixes);
+
+ for (i=0, j=0; i < ctr->num_mappings; i++) {
+ if (ctr->mappings[i].oid.oid == NULL) {
+ return WERR_INVALID_PARAM;
+ }
+
+ if (strncasecmp(ctr->mappings[i].oid.oid, "ff", 2) == 0) {
+ if (ctr->mappings[i].id_prefix != 0) {
+ return WERR_INVALID_PARAM;
+ }
+
+ /* the magic value should be in the last array member */
+ if (i != (ctr->num_mappings - 1)) {
+ return WERR_INVALID_PARAM;
+ }
+
+ if (ctr->mappings[i].oid.__ndr_size != 21) {
+ return WERR_INVALID_PARAM;
+ }
+
+ schema->schema_info = talloc_strdup(schema, ctr->mappings[i].oid.oid);
+ W_ERROR_HAVE_NO_MEMORY(schema->schema_info);
+ } else {
+ /* the last array member should contain the magic value not a oid */
+ if (i == (ctr->num_mappings - 1)) {
+ return WERR_INVALID_PARAM;
+ }
+
+ schema->prefixes[j].id = ctr->mappings[i].id_prefix<<16;
+ schema->prefixes[j].oid = talloc_asprintf(schema->prefixes, "%s.",
+ ctr->mappings[i].oid.oid);
+ W_ERROR_HAVE_NO_MEMORY(schema->prefixes[j].oid);
+ schema->prefixes[j].oid_len = strlen(schema->prefixes[j].oid);
+ j++;
+ }
+ }
+
+ schema->num_prefixes = j;
+ return WERR_OK;
+}
+
+WERROR dsdb_load_oid_mappings_ldb(struct dsdb_schema *schema,
+ const struct ldb_val *prefixMap,
+ const struct ldb_val *schemaInfo)
+{
+ WERROR status;
+ enum ndr_err_code ndr_err;
+ struct prefixMapBlob pfm;
+ char *schema_info;
+
+ TALLOC_CTX *mem_ctx = talloc_new(schema);
+ W_ERROR_HAVE_NO_MEMORY(mem_ctx);
+
+ ndr_err = ndr_pull_struct_blob(prefixMap, mem_ctx, schema->iconv_convenience, &pfm, (ndr_pull_flags_fn_t)ndr_pull_prefixMapBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ talloc_free(mem_ctx);
+ return ntstatus_to_werror(nt_status);
+ }
+
+ if (pfm.version != PREFIX_MAP_VERSION_DSDB) {
+ talloc_free(mem_ctx);
+ return WERR_FOOBAR;
+ }
+
+ if (schemaInfo->length != 21 && schemaInfo->data[0] == 0xFF) {
+ talloc_free(mem_ctx);
+ return WERR_FOOBAR;
+ }
+
+ /* append the schema info as last element */
+ pfm.ctr.dsdb.num_mappings++;
+ pfm.ctr.dsdb.mappings = talloc_realloc(mem_ctx, pfm.ctr.dsdb.mappings,
+ struct drsuapi_DsReplicaOIDMapping,
+ pfm.ctr.dsdb.num_mappings);
+ W_ERROR_HAVE_NO_MEMORY(pfm.ctr.dsdb.mappings);
+
+ schema_info = data_blob_hex_string(pfm.ctr.dsdb.mappings, schemaInfo);
+ W_ERROR_HAVE_NO_MEMORY(schema_info);
+
+ pfm.ctr.dsdb.mappings[pfm.ctr.dsdb.num_mappings - 1].id_prefix = 0;
+ pfm.ctr.dsdb.mappings[pfm.ctr.dsdb.num_mappings - 1].oid.__ndr_size = schemaInfo->length;
+ pfm.ctr.dsdb.mappings[pfm.ctr.dsdb.num_mappings - 1].oid.oid = schema_info;
+
+ /* call the drsuapi version */
+ status = dsdb_load_oid_mappings_drsuapi(schema, &pfm.ctr.dsdb);
+ talloc_free(mem_ctx);
+
+ W_ERROR_NOT_OK_RETURN(status);
+
+ return WERR_OK;
+}
+
+WERROR dsdb_get_oid_mappings_drsuapi(const struct dsdb_schema *schema,
+ bool include_schema_info,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaOIDMapping_Ctr **_ctr)
+{
+ struct drsuapi_DsReplicaOIDMapping_Ctr *ctr;
+ uint32_t i;
+
+ ctr = talloc(mem_ctx, struct drsuapi_DsReplicaOIDMapping_Ctr);
+ W_ERROR_HAVE_NO_MEMORY(ctr);
+
+ ctr->num_mappings = schema->num_prefixes;
+ if (include_schema_info) ctr->num_mappings++;
+ ctr->mappings = talloc_array(schema, struct drsuapi_DsReplicaOIDMapping, ctr->num_mappings);
+ W_ERROR_HAVE_NO_MEMORY(ctr->mappings);
+
+ for (i=0; i < schema->num_prefixes; i++) {
+ ctr->mappings[i].id_prefix = schema->prefixes[i].id>>16;
+ ctr->mappings[i].oid.oid = talloc_strndup(ctr->mappings,
+ schema->prefixes[i].oid,
+ schema->prefixes[i].oid_len - 1);
+ W_ERROR_HAVE_NO_MEMORY(ctr->mappings[i].oid.oid);
+ }
+
+ if (include_schema_info) {
+ ctr->mappings[i].id_prefix = 0;
+ ctr->mappings[i].oid.oid = talloc_strdup(ctr->mappings,
+ schema->schema_info);
+ W_ERROR_HAVE_NO_MEMORY(ctr->mappings[i].oid.oid);
+ }
+
+ *_ctr = ctr;
+ return WERR_OK;
+}
+
+WERROR dsdb_get_oid_mappings_ldb(const struct dsdb_schema *schema,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_val *prefixMap,
+ struct ldb_val *schemaInfo)
+{
+ WERROR status;
+ enum ndr_err_code ndr_err;
+ struct drsuapi_DsReplicaOIDMapping_Ctr *ctr;
+ struct prefixMapBlob pfm;
+
+ status = dsdb_get_oid_mappings_drsuapi(schema, false, mem_ctx, &ctr);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ pfm.version = PREFIX_MAP_VERSION_DSDB;
+ pfm.reserved = 0;
+ pfm.ctr.dsdb = *ctr;
+
+ ndr_err = ndr_push_struct_blob(prefixMap, mem_ctx, schema->iconv_convenience, &pfm, (ndr_push_flags_fn_t)ndr_push_prefixMapBlob);
+ talloc_free(ctr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(nt_status);
+ }
+
+ *schemaInfo = strhex_to_data_blob(schema->schema_info);
+ W_ERROR_HAVE_NO_MEMORY(schemaInfo->data);
+ talloc_steal(mem_ctx, schemaInfo->data);
+
+ return WERR_OK;
+}
+
+WERROR dsdb_verify_oid_mappings_drsuapi(const struct dsdb_schema *schema, const struct drsuapi_DsReplicaOIDMapping_Ctr *ctr)
+{
+ uint32_t i,j;
+
+ for (i=0; i < ctr->num_mappings; i++) {
+ if (ctr->mappings[i].oid.oid == NULL) {
+ return WERR_INVALID_PARAM;
+ }
+
+ if (strncasecmp(ctr->mappings[i].oid.oid, "ff", 2) == 0) {
+ if (ctr->mappings[i].id_prefix != 0) {
+ return WERR_INVALID_PARAM;
+ }
+
+ /* the magic value should be in the last array member */
+ if (i != (ctr->num_mappings - 1)) {
+ return WERR_INVALID_PARAM;
+ }
+
+ if (ctr->mappings[i].oid.__ndr_size != 21) {
+ return WERR_INVALID_PARAM;
+ }
+
+ if (strcasecmp(schema->schema_info, ctr->mappings[i].oid.oid) != 0) {
+ return WERR_DS_DRA_SCHEMA_MISMATCH;
+ }
+ } else {
+ /* the last array member should contain the magic value not a oid */
+ if (i == (ctr->num_mappings - 1)) {
+ return WERR_INVALID_PARAM;
+ }
+
+ for (j=0; j < schema->num_prefixes; j++) {
+ size_t oid_len;
+ if (schema->prefixes[j].id != (ctr->mappings[i].id_prefix<<16)) {
+ continue;
+ }
+
+ oid_len = strlen(ctr->mappings[i].oid.oid);
+
+ if (oid_len != (schema->prefixes[j].oid_len - 1)) {
+ return WERR_DS_DRA_SCHEMA_MISMATCH;
+ }
+
+ if (strncmp(ctr->mappings[i].oid.oid, schema->prefixes[j].oid, oid_len) != 0) {
+ return WERR_DS_DRA_SCHEMA_MISMATCH;
+ }
+
+ break;
+ }
+
+ if (j == schema->num_prefixes) {
+ return WERR_DS_DRA_SCHEMA_MISMATCH;
+ }
+ }
+ }
+
+ return WERR_OK;
+}
+
+WERROR dsdb_map_oid2int(const struct dsdb_schema *schema, const char *in, uint32_t *out)
+{
+ return dsdb_find_prefix_for_oid(schema->num_prefixes, schema->prefixes, in, out);
+}
+
+
+WERROR dsdb_map_int2oid(const struct dsdb_schema *schema, uint32_t in, TALLOC_CTX *mem_ctx, const char **out)
+{
+ uint32_t i;
+
+ for (i=0; i < schema->num_prefixes; i++) {
+ const char *val;
+ if (schema->prefixes[i].id != (in & 0xFFFF0000)) {
+ continue;
+ }
+
+ val = talloc_asprintf(mem_ctx, "%s%u",
+ schema->prefixes[i].oid,
+ in & 0xFFFF);
+ W_ERROR_HAVE_NO_MEMORY(val);
+
+ *out = val;
+ return WERR_OK;
+ }
+
+ return WERR_DS_NO_MSDS_INTID;
+}
+
+/*
+ * this function is called from within a ldb transaction from the schema_fsmo module
+ */
+WERROR dsdb_create_prefix_mapping(struct ldb_context *ldb, struct dsdb_schema *schema, const char *full_oid)
+{
+ WERROR status;
+ uint32_t num_prefixes;
+ struct dsdb_schema_oid_prefix *prefixes;
+ TALLOC_CTX *mem_ctx;
+ uint32_t out;
+
+ mem_ctx = talloc_new(ldb);
+ W_ERROR_HAVE_NO_MEMORY(mem_ctx);
+
+ /* Read prefixes from disk*/
+ status = dsdb_read_prefixes_from_ldb( mem_ctx, ldb, &num_prefixes, &prefixes );
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("dsdb_create_prefix_mapping: dsdb_read_prefixes_from_ldb: %s\n",
+ win_errstr(status)));
+ talloc_free(mem_ctx);
+ return status;
+ }
+
+ /* Check if there is a prefix for the oid in the prefixes array*/
+ status = dsdb_find_prefix_for_oid( num_prefixes, prefixes, full_oid, &out );
+ if (W_ERROR_IS_OK(status)) {
+ /* prefix found*/
+ talloc_free(mem_ctx);
+ return status;
+ } else if (!W_ERROR_EQUAL(WERR_DS_NO_MSDS_INTID, status)) {
+ /* error */
+ DEBUG(0,("dsdb_create_prefix_mapping: dsdb_find_prefix_for_oid: %s\n",
+ win_errstr(status)));
+ talloc_free(mem_ctx);
+ return status;
+ }
+
+ /* Create the new mapping for the prefix of full_oid */
+ status = dsdb_prefix_map_update(mem_ctx, &num_prefixes, &prefixes, full_oid);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("dsdb_create_prefix_mapping: dsdb_prefix_map_update: %s\n",
+ win_errstr(status)));
+ talloc_free(mem_ctx);
+ return status;
+ }
+
+ /* Update prefixMap in ldb*/
+ status = dsdb_write_prefixes_to_ldb(mem_ctx, ldb, num_prefixes, prefixes);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("dsdb_create_prefix_mapping: dsdb_write_prefixes_to_ldb: %s\n",
+ win_errstr(status)));
+ talloc_free(mem_ctx);
+ return status;
+ }
+
+ talloc_free(mem_ctx);
+ return status;
+}
+
+WERROR dsdb_prefix_map_update(TALLOC_CTX *mem_ctx, uint32_t *num_prefixes, struct dsdb_schema_oid_prefix **prefixes, const char *oid)
+{
+ uint32_t new_num_prefixes, index_new_prefix, new_entry_id;
+ const char* lastDotOffset;
+ size_t size;
+
+ new_num_prefixes = *num_prefixes + 1;
+ index_new_prefix = *num_prefixes;
+
+ /*
+ * this is the algorithm we use to create new mappings for now
+ *
+ * TODO: find what algorithm windows use
+ */
+ new_entry_id = (*num_prefixes)<<16;
+
+ /* Extract the prefix from the oid*/
+ lastDotOffset = strrchr(oid, '.');
+ if (lastDotOffset == NULL) {
+ DEBUG(0,("dsdb_prefix_map_update: failed to find the last dot\n"));
+ return WERR_NOT_FOUND;
+ }
+
+ /* Calculate the size of the remainig string that should be the prefix of it */
+ size = strlen(oid) - strlen(lastDotOffset);
+ if (size <= 0) {
+ DEBUG(0,("dsdb_prefix_map_update: size of the remaining string invalid\n"));
+ return WERR_FOOBAR;
+ }
+ /* Add one because we need to copy the dot */
+ size += 1;
+
+ /* Create a spot in the prefixMap for one more prefix*/
+ (*prefixes) = talloc_realloc(mem_ctx, *prefixes, struct dsdb_schema_oid_prefix, new_num_prefixes);
+ W_ERROR_HAVE_NO_MEMORY(*prefixes);
+
+ /* Add the new prefix entry*/
+ (*prefixes)[index_new_prefix].id = new_entry_id;
+ (*prefixes)[index_new_prefix].oid = talloc_strndup(mem_ctx, oid, size);
+ (*prefixes)[index_new_prefix].oid_len = strlen((*prefixes)[index_new_prefix].oid);
+
+ /* Increase num_prefixes because new prefix has been added */
+ ++(*num_prefixes);
+
+ return WERR_OK;
+}
+
+WERROR dsdb_find_prefix_for_oid(uint32_t num_prefixes, const struct dsdb_schema_oid_prefix *prefixes, const char *in, uint32_t *out)
+{
+ uint32_t i;
+
+ for (i=0; i < num_prefixes; i++) {
+ const char *val_str;
+ char *end_str;
+ unsigned val;
+
+ if (strncmp(prefixes[i].oid, in, prefixes[i].oid_len) != 0) {
+ continue;
+ }
+
+ val_str = in + prefixes[i].oid_len;
+ end_str = NULL;
+ errno = 0;
+
+ if (val_str[0] == '\0') {
+ return WERR_INVALID_PARAM;
+ }
+
+ /* two '.' chars are invalid */
+ if (val_str[0] == '.') {
+ return WERR_INVALID_PARAM;
+ }
+
+ val = strtoul(val_str, &end_str, 10);
+ if (end_str[0] == '.' && end_str[1] != '\0') {
+ /*
+ * if it's a '.' and not the last char
+ * then maybe an other mapping apply
+ */
+ continue;
+ } else if (end_str[0] != '\0') {
+ return WERR_INVALID_PARAM;
+ } else if (val > 0xFFFF) {
+ return WERR_INVALID_PARAM;
+ }
+
+ *out = prefixes[i].id | val;
+ return WERR_OK;
+ }
+
+ return WERR_DS_NO_MSDS_INTID;
+}
+
+WERROR dsdb_write_prefixes_to_ldb(TALLOC_CTX *mem_ctx, struct ldb_context *ldb,
+ uint32_t num_prefixes,
+ const struct dsdb_schema_oid_prefix *prefixes)
+{
+ struct ldb_message msg;
+ struct ldb_dn *schema_dn;
+ struct ldb_message_element el;
+ struct prefixMapBlob pm;
+ struct ldb_val ndr_blob;
+ enum ndr_err_code ndr_err;
+ uint32_t i;
+ int ret;
+
+ schema_dn = samdb_schema_dn(ldb);
+ if (!schema_dn) {
+ DEBUG(0,("dsdb_write_prefixes_to_ldb: no schema dn present\n"));
+ return WERR_FOOBAR;
+ }
+
+ pm.version = PREFIX_MAP_VERSION_DSDB;
+ pm.ctr.dsdb.num_mappings = num_prefixes;
+ pm.ctr.dsdb.mappings = talloc_array(mem_ctx,
+ struct drsuapi_DsReplicaOIDMapping,
+ pm.ctr.dsdb.num_mappings);
+ if (!pm.ctr.dsdb.mappings) {
+ return WERR_NOMEM;
+ }
+
+ for (i=0; i < num_prefixes; i++) {
+ pm.ctr.dsdb.mappings[i].id_prefix = prefixes[i].id>>16;
+ pm.ctr.dsdb.mappings[i].oid.oid = talloc_strdup(pm.ctr.dsdb.mappings, prefixes[i].oid);
+ }
+
+ ndr_err = ndr_push_struct_blob(&ndr_blob, ldb,
+ lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
+ &pm,
+ (ndr_push_flags_fn_t)ndr_push_prefixMapBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_FOOBAR;
+ }
+
+ el.num_values = 1;
+ el.values = &ndr_blob;
+ el.flags = LDB_FLAG_MOD_REPLACE;
+ el.name = talloc_strdup(mem_ctx, "prefixMap");
+
+ msg.dn = ldb_dn_copy(mem_ctx, schema_dn);
+ msg.num_elements = 1;
+ msg.elements = &el;
+
+ ret = ldb_modify( ldb, &msg );
+ if (ret != 0) {
+ DEBUG(0,("dsdb_write_prefixes_to_ldb: ldb_modify failed\n"));
+ return WERR_FOOBAR;
+ }
+
+ return WERR_OK;
+}
+
+WERROR dsdb_read_prefixes_from_ldb(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, uint32_t* num_prefixes, struct dsdb_schema_oid_prefix **prefixes)
+{
+ struct prefixMapBlob *blob;
+ enum ndr_err_code ndr_err;
+ uint32_t i;
+ const struct ldb_val *prefix_val;
+ struct ldb_dn *schema_dn;
+ struct ldb_result *schema_res;
+ int ret;
+ static const char *schema_attrs[] = {
+ "prefixMap",
+ NULL
+ };
+
+ schema_dn = samdb_schema_dn(ldb);
+ if (!schema_dn) {
+ DEBUG(0,("dsdb_read_prefixes_from_ldb: no schema dn present\n"));
+ return WERR_FOOBAR;
+ }
+
+ ret = ldb_search(ldb, schema_dn, LDB_SCOPE_BASE,NULL, schema_attrs,&schema_res);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(0,("dsdb_read_prefixes_from_ldb: no prefix map present\n"));
+ return WERR_FOOBAR;
+ } else if (ret != LDB_SUCCESS) {
+ DEBUG(0,("dsdb_read_prefixes_from_ldb: failed to search the schema head\n"));
+ return WERR_FOOBAR;
+ }
+
+ prefix_val = ldb_msg_find_ldb_val(schema_res->msgs[0], "prefixMap");
+ if (!prefix_val) {
+ DEBUG(0,("dsdb_read_prefixes_from_ldb: no prefixMap attribute found\n"));
+ return WERR_FOOBAR;
+ }
+
+ blob = talloc(mem_ctx, struct prefixMapBlob);
+ W_ERROR_HAVE_NO_MEMORY(blob);
+
+ ndr_err = ndr_pull_struct_blob(prefix_val, blob,
+ lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
+ blob,
+ (ndr_pull_flags_fn_t)ndr_pull_prefixMapBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,("dsdb_read_prefixes_from_ldb: ndr_pull_struct_blob failed\n"));
+ talloc_free(blob);
+ return WERR_FOOBAR;
+ }
+
+ if (blob->version != PREFIX_MAP_VERSION_DSDB) {
+ DEBUG(0,("dsdb_read_prefixes_from_ldb: blob->version incorect\n"));
+ talloc_free(blob);
+ return WERR_FOOBAR;
+ }
+
+ *num_prefixes = blob->ctr.dsdb.num_mappings;
+ *prefixes = talloc_array(mem_ctx, struct dsdb_schema_oid_prefix, *num_prefixes);
+ if(!(*prefixes)) {
+ talloc_free(blob);
+ return WERR_NOMEM;
+ }
+ for (i=0; i < blob->ctr.dsdb.num_mappings; i++) {
+ char *oid;
+ (*prefixes)[i].id = blob->ctr.dsdb.mappings[i].id_prefix<<16;
+ oid = talloc_strdup(mem_ctx, blob->ctr.dsdb.mappings[i].oid.oid);
+ (*prefixes)[i].oid = talloc_asprintf_append(oid, ".");
+ (*prefixes)[i].oid_len = strlen(blob->ctr.dsdb.mappings[i].oid.oid);
+ }
+
+ talloc_free(blob);
+ return WERR_OK;
+}
+
+#define GET_STRING_LDB(msg, attr, mem_ctx, p, elem, strict) do { \
+ (p)->elem = samdb_result_string(msg, attr, NULL);\
+ if (strict && (p)->elem == NULL) { \
+ d_printf("%s: %s == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } \
+ talloc_steal(mem_ctx, (p)->elem); \
+} while (0)
+
+#define GET_STRING_LIST_LDB(msg, attr, mem_ctx, p, elem, strict) do { \
+ int get_string_list_counter; \
+ struct ldb_message_element *get_string_list_el = ldb_msg_find_element(msg, attr); \
+ if (get_string_list_el == NULL) { \
+ if (strict) { \
+ d_printf("%s: %s == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } else { \
+ (p)->elem = NULL; \
+ break; \
+ } \
+ } \
+ (p)->elem = talloc_array(mem_ctx, const char *, get_string_list_el->num_values + 1); \
+ for (get_string_list_counter=0; \
+ get_string_list_counter < get_string_list_el->num_values; \
+ get_string_list_counter++) { \
+ (p)->elem[get_string_list_counter] = talloc_strndup((p)->elem, \
+ (const char *)get_string_list_el->values[get_string_list_counter].data, \
+ get_string_list_el->values[get_string_list_counter].length); \
+ if (!(p)->elem[get_string_list_counter]) { \
+ d_printf("%s: talloc_strndup failed for %s\n", __location__, attr); \
+ return WERR_NOMEM; \
+ } \
+ (p)->elem[get_string_list_counter+1] = NULL; \
+ } \
+ talloc_steal(mem_ctx, (p)->elem); \
+} while (0)
+
+#define GET_BOOL_LDB(msg, attr, p, elem, strict) do { \
+ const char *str; \
+ str = samdb_result_string(msg, attr, NULL);\
+ if (str == NULL) { \
+ if (strict) { \
+ d_printf("%s: %s == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } else { \
+ (p)->elem = false; \
+ } \
+ } else if (strcasecmp("TRUE", str) == 0) { \
+ (p)->elem = true; \
+ } else if (strcasecmp("FALSE", str) == 0) { \
+ (p)->elem = false; \
+ } else { \
+ d_printf("%s: %s == %s\n", __location__, attr, str); \
+ return WERR_INVALID_PARAM; \
+ } \
+} while (0)
+
+#define GET_UINT32_LDB(msg, attr, p, elem) do { \
+ (p)->elem = samdb_result_uint(msg, attr, 0);\
+} while (0)
+
+#define GET_GUID_LDB(msg, attr, p, elem) do { \
+ (p)->elem = samdb_result_guid(msg, attr);\
+} while (0)
+
+#define GET_BLOB_LDB(msg, attr, mem_ctx, p, elem) do { \
+ const struct ldb_val *_val;\
+ _val = ldb_msg_find_ldb_val(msg, attr);\
+ if (_val) {\
+ (p)->elem = *_val;\
+ talloc_steal(mem_ctx, (p)->elem.data);\
+ } else {\
+ ZERO_STRUCT((p)->elem);\
+ }\
+} while (0)
+
+WERROR dsdb_attribute_from_ldb(const struct dsdb_schema *schema,
+ struct ldb_message *msg,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_attribute *attr)
+{
+ WERROR status;
+
+ GET_STRING_LDB(msg, "cn", mem_ctx, attr, cn, false);
+ GET_STRING_LDB(msg, "lDAPDisplayName", mem_ctx, attr, lDAPDisplayName, true);
+ GET_STRING_LDB(msg, "attributeID", mem_ctx, attr, attributeID_oid, true);
+ if (schema->num_prefixes == 0) {
+ /* set an invalid value */
+ attr->attributeID_id = 0xFFFFFFFF;
+ } else {
+ status = dsdb_map_oid2int(schema, attr->attributeID_oid, &attr->attributeID_id);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("%s: '%s': unable to map attributeID %s: %s\n",
+ __location__, attr->lDAPDisplayName, attr->attributeID_oid,
+ win_errstr(status)));
+ return status;
+ }
+ }
+ GET_GUID_LDB(msg, "schemaIDGUID", attr, schemaIDGUID);
+ GET_UINT32_LDB(msg, "mAPIID", attr, mAPIID);
+
+ GET_GUID_LDB(msg, "attributeSecurityGUID", attr, attributeSecurityGUID);
+
+ GET_UINT32_LDB(msg, "searchFlags", attr, searchFlags);
+ GET_UINT32_LDB(msg, "systemFlags", attr, systemFlags);
+ GET_BOOL_LDB(msg, "isMemberOfPartialAttributeSet", attr, isMemberOfPartialAttributeSet, false);
+ GET_UINT32_LDB(msg, "linkID", attr, linkID);
+
+ GET_STRING_LDB(msg, "attributeSyntax", mem_ctx, attr, attributeSyntax_oid, true);
+ if (schema->num_prefixes == 0) {
+ /* set an invalid value */
+ attr->attributeSyntax_id = 0xFFFFFFFF;
+ } else {
+ status = dsdb_map_oid2int(schema, attr->attributeSyntax_oid, &attr->attributeSyntax_id);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("%s: '%s': unable to map attributeSyntax_ %s: %s\n",
+ __location__, attr->lDAPDisplayName, attr->attributeSyntax_oid,
+ win_errstr(status)));
+ return status;
+ }
+ }
+ GET_UINT32_LDB(msg, "oMSyntax", attr, oMSyntax);
+ GET_BLOB_LDB(msg, "oMObjectClass", mem_ctx, attr, oMObjectClass);
+
+ GET_BOOL_LDB(msg, "isSingleValued", attr, isSingleValued, true);
+ GET_UINT32_LDB(msg, "rangeLower", attr, rangeLower);
+ GET_UINT32_LDB(msg, "rangeUpper", attr, rangeUpper);
+ GET_BOOL_LDB(msg, "extendedCharsAllowed", attr, extendedCharsAllowed, false);
+
+ GET_UINT32_LDB(msg, "schemaFlagsEx", attr, schemaFlagsEx);
+ GET_BLOB_LDB(msg, "msDs-Schema-Extensions", mem_ctx, attr, msDs_Schema_Extensions);
+
+ GET_BOOL_LDB(msg, "showInAdvancedViewOnly", attr, showInAdvancedViewOnly, false);
+ GET_STRING_LDB(msg, "adminDisplayName", mem_ctx, attr, adminDisplayName, false);
+ GET_STRING_LDB(msg, "adminDescription", mem_ctx, attr, adminDescription, false);
+ GET_STRING_LDB(msg, "classDisplayName", mem_ctx, attr, classDisplayName, false);
+ GET_BOOL_LDB(msg, "isEphemeral", attr, isEphemeral, false);
+ GET_BOOL_LDB(msg, "isDefunct", attr, isDefunct, false);
+ GET_BOOL_LDB(msg, "systemOnly", attr, systemOnly, false);
+
+ attr->syntax = dsdb_syntax_for_attribute(attr);
+ if (!attr->syntax) {
+ return WERR_DS_ATT_SCHEMA_REQ_SYNTAX;
+ }
+
+ return WERR_OK;
+}
+
+WERROR dsdb_class_from_ldb(const struct dsdb_schema *schema,
+ struct ldb_message *msg,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_class *obj)
+{
+ WERROR status;
+
+ GET_STRING_LDB(msg, "cn", mem_ctx, obj, cn, false);
+ GET_STRING_LDB(msg, "lDAPDisplayName", mem_ctx, obj, lDAPDisplayName, true);
+ GET_STRING_LDB(msg, "governsID", mem_ctx, obj, governsID_oid, true);
+ if (schema->num_prefixes == 0) {
+ /* set an invalid value */
+ obj->governsID_id = 0xFFFFFFFF;
+ } else {
+ status = dsdb_map_oid2int(schema, obj->governsID_oid, &obj->governsID_id);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("%s: '%s': unable to map governsID %s: %s\n",
+ __location__, obj->lDAPDisplayName, obj->governsID_oid,
+ win_errstr(status)));
+ return status;
+ }
+ }
+ GET_GUID_LDB(msg, "schemaIDGUID", obj, schemaIDGUID);
+
+ GET_UINT32_LDB(msg, "objectClassCategory", obj, objectClassCategory);
+ GET_STRING_LDB(msg, "rDNAttID", mem_ctx, obj, rDNAttID, false);
+ GET_STRING_LDB(msg, "defaultObjectCategory", mem_ctx, obj, defaultObjectCategory, true);
+
+ GET_STRING_LDB(msg, "subClassOf", mem_ctx, obj, subClassOf, true);
+
+ GET_STRING_LIST_LDB(msg, "systemAuxiliaryClass", mem_ctx, obj, systemAuxiliaryClass, false);
+ GET_STRING_LIST_LDB(msg, "auxiliaryClass", mem_ctx, obj, auxiliaryClass, false);
+
+ GET_STRING_LIST_LDB(msg, "systemMustContain", mem_ctx, obj, systemMustContain, false);
+ GET_STRING_LIST_LDB(msg, "systemMayContain", mem_ctx, obj, systemMayContain, false);
+ GET_STRING_LIST_LDB(msg, "mustContain", mem_ctx, obj, mustContain, false);
+ GET_STRING_LIST_LDB(msg, "mayContain", mem_ctx, obj, mayContain, false);
+
+ GET_STRING_LIST_LDB(msg, "systemPossSuperiors", mem_ctx, obj, systemPossSuperiors, false);
+ GET_STRING_LIST_LDB(msg, "possSuperiors", mem_ctx, obj, possSuperiors, false);
+ GET_STRING_LIST_LDB(msg, "possibleInferiors", mem_ctx, obj, possibleInferiors, false);
+
+ GET_STRING_LDB(msg, "defaultSecurityDescriptor", mem_ctx, obj, defaultSecurityDescriptor, false);
+
+ GET_UINT32_LDB(msg, "schemaFlagsEx", obj, schemaFlagsEx);
+ GET_BLOB_LDB(msg, "msDs-Schema-Extensions", mem_ctx, obj, msDs_Schema_Extensions);
+
+ GET_BOOL_LDB(msg, "showInAdvancedViewOnly", obj, showInAdvancedViewOnly, false);
+ GET_STRING_LDB(msg, "adminDisplayName", mem_ctx, obj, adminDisplayName, false);
+ GET_STRING_LDB(msg, "adminDescription", mem_ctx, obj, adminDescription, false);
+ GET_STRING_LDB(msg, "classDisplayName", mem_ctx, obj, classDisplayName, false);
+ GET_BOOL_LDB(msg, "defaultHidingValue", obj, defaultHidingValue, false);
+ GET_BOOL_LDB(msg, "isDefunct", obj, isDefunct, false);
+ GET_BOOL_LDB(msg, "systemOnly", obj, systemOnly, false);
+
+ return WERR_OK;
+}
+
+#define dsdb_oom(error_string, mem_ctx) *error_string = talloc_asprintf(mem_ctx, "dsdb out of memory at %s:%d\n", __FILE__, __LINE__)
+
+int dsdb_schema_from_ldb_results(TALLOC_CTX *mem_ctx, struct ldb_context *ldb,
+ struct smb_iconv_convenience *iconv_convenience,
+ struct ldb_result *schema_res,
+ struct ldb_result *attrs_res, struct ldb_result *objectclass_res,
+ struct dsdb_schema **schema_out,
+ char **error_string)
+{
+ WERROR status;
+ uint32_t i;
+ const struct ldb_val *prefix_val;
+ const struct ldb_val *info_val;
+ struct ldb_val info_val_default;
+ struct dsdb_schema *schema;
+
+ schema = dsdb_new_schema(mem_ctx, iconv_convenience);
+ if (!schema) {
+ dsdb_oom(error_string, mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ prefix_val = ldb_msg_find_ldb_val(schema_res->msgs[0], "prefixMap");
+ if (!prefix_val) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "schema_fsmo_init: no prefixMap attribute found");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ info_val = ldb_msg_find_ldb_val(schema_res->msgs[0], "schemaInfo");
+ if (!info_val) {
+ info_val_default = strhex_to_data_blob("FF0000000000000000000000000000000000000000");
+ if (!info_val_default.data) {
+ dsdb_oom(error_string, mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_steal(mem_ctx, info_val_default.data);
+ info_val = &info_val_default;
+ }
+
+ status = dsdb_load_oid_mappings_ldb(schema, prefix_val, info_val);
+ if (!W_ERROR_IS_OK(status)) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "schema_fsmo_init: failed to load oid mappings: %s",
+ win_errstr(status));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ for (i=0; i < attrs_res->count; i++) {
+ struct dsdb_attribute *sa;
+
+ sa = talloc_zero(schema, struct dsdb_attribute);
+ if (!sa) {
+ dsdb_oom(error_string, mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ status = dsdb_attribute_from_ldb(schema, attrs_res->msgs[i], sa, sa);
+ if (!W_ERROR_IS_OK(status)) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "schema_fsmo_init: failed to load attribute definition: %s:%s",
+ ldb_dn_get_linearized(attrs_res->msgs[i]->dn),
+ win_errstr(status));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ DLIST_ADD_END(schema->attributes, sa, struct dsdb_attribute *);
+ }
+
+ for (i=0; i < objectclass_res->count; i++) {
+ struct dsdb_class *sc;
+
+ sc = talloc_zero(schema, struct dsdb_class);
+ if (!sc) {
+ dsdb_oom(error_string, mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ status = dsdb_class_from_ldb(schema, objectclass_res->msgs[i], sc, sc);
+ if (!W_ERROR_IS_OK(status)) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "schema_fsmo_init: failed to load class definition: %s:%s",
+ ldb_dn_get_linearized(objectclass_res->msgs[i]->dn),
+ win_errstr(status));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ DLIST_ADD_END(schema->classes, sc, struct dsdb_class *);
+ }
+
+ schema->fsmo.master_dn = ldb_msg_find_attr_as_dn(ldb, schema, schema_res->msgs[0], "fSMORoleOwner");
+ if (ldb_dn_compare(samdb_ntds_settings_dn(ldb), schema->fsmo.master_dn) == 0) {
+ schema->fsmo.we_are_master = true;
+ } else {
+ schema->fsmo.we_are_master = false;
+ }
+
+ DEBUG(5, ("schema_fsmo_init: we are master: %s\n",
+ (schema->fsmo.we_are_master?"yes":"no")));
+
+ *schema_out = schema;
+ return LDB_SUCCESS;
+}
+
+/* This recursive load of the objectClasses presumes that they
+ * everything is in a strict subClassOf hirarchy.
+ *
+ * We load this in order so we produce certain outputs (such as the
+ * exported schema for openldap, and sorted objectClass attribute) 'in
+ * order' */
+
+static int fetch_oc_recursive(struct ldb_context *ldb, struct ldb_dn *schemadn,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result *search_from,
+ struct ldb_result *res_list)
+{
+ int i;
+ int ret = 0;
+ for (i=0; i < search_from->count; i++) {
+ struct ldb_result *res;
+ const char *name = ldb_msg_find_attr_as_string(search_from->msgs[i],
+ "lDAPDisplayname", NULL);
+
+ ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
+ schemadn, LDB_SCOPE_SUBTREE, NULL,
+ "(&(&(objectClass=classSchema)(subClassOf=%s))(!(lDAPDisplayName=%s)))",
+ name, name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ res_list->msgs = talloc_realloc(res_list, res_list->msgs,
+ struct ldb_message *, res_list->count + 2);
+ if (!res_list->msgs) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ res_list->msgs[res_list->count] = talloc_move(res_list,
+ &search_from->msgs[i]);
+ res_list->count++;
+ res_list->msgs[res_list->count] = NULL;
+
+ if (res->count > 0) {
+ ret = fetch_oc_recursive(ldb, schemadn, mem_ctx, res, res_list);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static int fetch_objectclass_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **objectclasses_res,
+ char **error_string)
+{
+ TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
+ struct ldb_result *top_res, *ret_res;
+ int ret;
+ if (!local_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Download 'top' */
+ ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
+ "(&(objectClass=classSchema)(lDAPDisplayName=top))",
+ NULL, &top_res);
+ if (ret != LDB_SUCCESS) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "dsdb_schema: failed to search for top classSchema object: %s",
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ talloc_steal(local_ctx, top_res);
+
+ if (top_res->count != 1) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "dsdb_schema: failed to find top classSchema object");
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ ret_res = talloc_zero(local_ctx, struct ldb_result);
+ if (!ret_res) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fetch_oc_recursive(ldb, schemadn, local_ctx, top_res, ret_res);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ *objectclasses_res = talloc_move(mem_ctx, &ret_res);
+ return ret;
+}
+
+int dsdb_schema_from_schema_dn(TALLOC_CTX *mem_ctx, struct ldb_context *ldb,
+ struct smb_iconv_convenience *iconv_convenience,
+ struct ldb_dn *schema_dn,
+ struct dsdb_schema **schema,
+ char **error_string_out)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *error_string;
+ int ret;
+
+ struct ldb_result *schema_res;
+ struct ldb_result *a_res;
+ struct ldb_result *c_res;
+ static const char *schema_attrs[] = {
+ "prefixMap",
+ "schemaInfo",
+ "fSMORoleOwner",
+ NULL
+ };
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ dsdb_oom(error_string_out, mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * setup the prefix mappings and schema info
+ */
+ ret = ldb_search(ldb, schema_dn,
+ LDB_SCOPE_BASE,
+ NULL, schema_attrs,
+ &schema_res);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ return ret;
+ } else if (ret != LDB_SUCCESS) {
+ *error_string_out = talloc_asprintf(mem_ctx,
+ "dsdb_schema: failed to search the schema head: %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ talloc_steal(tmp_ctx, schema_res);
+ if (schema_res->count != 1) {
+ *error_string_out = talloc_asprintf(mem_ctx,
+ "dsdb_schema: [%u] schema heads found on a base search",
+ schema_res->count);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * load the attribute definitions
+ */
+ ret = ldb_search(ldb, schema_dn,
+ LDB_SCOPE_ONELEVEL,
+ "(objectClass=attributeSchema)", NULL,
+ &a_res);
+ if (ret != LDB_SUCCESS) {
+ *error_string_out = talloc_asprintf(mem_ctx,
+ "dsdb_schema: failed to search attributeSchema objects: %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ talloc_steal(tmp_ctx, a_res);
+
+ /*
+ * load the objectClass definitions
+ */
+ ret = fetch_objectclass_schema(ldb, schema_dn, tmp_ctx, &c_res, &error_string);
+ if (ret != LDB_SUCCESS) {
+ *error_string_out = talloc_asprintf(mem_ctx,
+ "Failed to fetch objectClass schema elements: %s", error_string);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_schema_from_ldb_results(tmp_ctx, ldb,
+ lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
+ schema_res, a_res, c_res, schema, &error_string);
+ if (ret != LDB_SUCCESS) {
+ *error_string_out = talloc_asprintf(mem_ctx,
+ "dsdb_schema load failed: %s",
+ error_string);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, *schema);
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+
+static const struct {
+ const char *name;
+ const char *oid;
+} name_mappings[] = {
+ { "cn", "2.5.4.3" },
+ { "name", "1.2.840.113556.1.4.1" },
+ { "lDAPDisplayName", "1.2.840.113556.1.2.460" },
+ { "attributeID", "1.2.840.113556.1.2.30" },
+ { "schemaIDGUID", "1.2.840.113556.1.4.148" },
+ { "mAPIID", "1.2.840.113556.1.2.49" },
+ { "attributeSecurityGUID", "1.2.840.113556.1.4.149" },
+ { "searchFlags", "1.2.840.113556.1.2.334" },
+ { "systemFlags", "1.2.840.113556.1.4.375" },
+ { "isMemberOfPartialAttributeSet", "1.2.840.113556.1.4.639" },
+ { "linkID", "1.2.840.113556.1.2.50" },
+ { "attributeSyntax", "1.2.840.113556.1.2.32" },
+ { "oMSyntax", "1.2.840.113556.1.2.231" },
+ { "oMObjectClass", "1.2.840.113556.1.2.218" },
+ { "isSingleValued", "1.2.840.113556.1.2.33" },
+ { "rangeLower", "1.2.840.113556.1.2.34" },
+ { "rangeUpper", "1.2.840.113556.1.2.35" },
+ { "extendedCharsAllowed", "1.2.840.113556.1.2.380" },
+ { "schemaFlagsEx", "1.2.840.113556.1.4.120" },
+ { "msDs-Schema-Extensions", "1.2.840.113556.1.4.1440" },
+ { "showInAdvancedViewOnly", "1.2.840.113556.1.2.169" },
+ { "adminDisplayName", "1.2.840.113556.1.2.194" },
+ { "adminDescription", "1.2.840.113556.1.2.226" },
+ { "classDisplayName", "1.2.840.113556.1.4.610" },
+ { "isEphemeral", "1.2.840.113556.1.4.1212" },
+ { "isDefunct", "1.2.840.113556.1.4.661" },
+ { "systemOnly", "1.2.840.113556.1.4.170" },
+ { "governsID", "1.2.840.113556.1.2.22" },
+ { "objectClassCategory", "1.2.840.113556.1.2.370" },
+ { "rDNAttID", "1.2.840.113556.1.2.26" },
+ { "defaultObjectCategory", "1.2.840.113556.1.4.783" },
+ { "subClassOf", "1.2.840.113556.1.2.21" },
+ { "systemAuxiliaryClass", "1.2.840.113556.1.4.198" },
+ { "systemPossSuperiors", "1.2.840.113556.1.4.195" },
+ { "systemMustContain", "1.2.840.113556.1.4.197" },
+ { "systemMayContain", "1.2.840.113556.1.4.196" },
+ { "auxiliaryClass", "1.2.840.113556.1.2.351" },
+ { "possSuperiors", "1.2.840.113556.1.2.8" },
+ { "mustContain", "1.2.840.113556.1.2.24" },
+ { "mayContain", "1.2.840.113556.1.2.25" },
+ { "defaultSecurityDescriptor", "1.2.840.113556.1.4.224" },
+ { "defaultHidingValue", "1.2.840.113556.1.4.518" },
+};
+
+static struct drsuapi_DsReplicaAttribute *dsdb_find_object_attr_name(struct dsdb_schema *schema,
+ struct drsuapi_DsReplicaObject *obj,
+ const char *name,
+ uint32_t *idx)
+{
+ WERROR status;
+ uint32_t i, id;
+ const char *oid = NULL;
+
+ for(i=0; i < ARRAY_SIZE(name_mappings); i++) {
+ if (strcmp(name_mappings[i].name, name) != 0) continue;
+
+ oid = name_mappings[i].oid;
+ break;
+ }
+
+ if (!oid) {
+ return NULL;
+ }
+
+ status = dsdb_map_oid2int(schema, oid, &id);
+ if (!W_ERROR_IS_OK(status)) {
+ return NULL;
+ }
+
+ for (i=0; i < obj->attribute_ctr.num_attributes; i++) {
+ if (obj->attribute_ctr.attributes[i].attid != id) continue;
+
+ if (idx) *idx = i;
+ return &obj->attribute_ctr.attributes[i];
+ }
+
+ return NULL;
+}
+
+#define GET_STRING_DS(s, r, attr, mem_ctx, p, elem, strict) do { \
+ struct drsuapi_DsReplicaAttribute *_a; \
+ _a = dsdb_find_object_attr_name(s, r, attr, NULL); \
+ if (strict && !_a) { \
+ d_printf("%s: %s == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (strict && _a->value_ctr.num_values != 1) { \
+ d_printf("%s: %s num_values == %u\n", __location__, attr, \
+ _a->value_ctr.num_values); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (_a && _a->value_ctr.num_values >= 1) { \
+ ssize_t _ret; \
+ _ret = convert_string_talloc(mem_ctx, s->iconv_convenience, CH_UTF16, CH_UNIX, \
+ _a->value_ctr.values[0].blob->data, \
+ _a->value_ctr.values[0].blob->length, \
+ (void **)discard_const(&(p)->elem)); \
+ if (_ret == -1) { \
+ DEBUG(0,("%s: invalid data!\n", attr)); \
+ dump_data(0, \
+ _a->value_ctr.values[0].blob->data, \
+ _a->value_ctr.values[0].blob->length); \
+ return WERR_FOOBAR; \
+ } \
+ } else { \
+ (p)->elem = NULL; \
+ } \
+} while (0)
+
+#define GET_DN_DS(s, r, attr, mem_ctx, p, elem, strict) do { \
+ struct drsuapi_DsReplicaAttribute *_a; \
+ _a = dsdb_find_object_attr_name(s, r, attr, NULL); \
+ if (strict && !_a) { \
+ d_printf("%s: %s == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (strict && _a->value_ctr.num_values != 1) { \
+ d_printf("%s: %s num_values == %u\n", __location__, attr, \
+ _a->value_ctr.num_values); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (strict && !_a->value_ctr.values[0].blob) { \
+ d_printf("%s: %s data == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (_a && _a->value_ctr.num_values >= 1 \
+ && _a->value_ctr.values[0].blob) { \
+ struct drsuapi_DsReplicaObjectIdentifier3 _id3; \
+ enum ndr_err_code _ndr_err; \
+ _ndr_err = ndr_pull_struct_blob_all(_a->value_ctr.values[0].blob, \
+ mem_ctx, s->iconv_convenience, &_id3,\
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);\
+ if (!NDR_ERR_CODE_IS_SUCCESS(_ndr_err)) { \
+ NTSTATUS _nt_status = ndr_map_error2ntstatus(_ndr_err); \
+ return ntstatus_to_werror(_nt_status); \
+ } \
+ (p)->elem = _id3.dn; \
+ } else { \
+ (p)->elem = NULL; \
+ } \
+} while (0)
+
+#define GET_BOOL_DS(s, r, attr, p, elem, strict) do { \
+ struct drsuapi_DsReplicaAttribute *_a; \
+ _a = dsdb_find_object_attr_name(s, r, attr, NULL); \
+ if (strict && !_a) { \
+ d_printf("%s: %s == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (strict && _a->value_ctr.num_values != 1) { \
+ d_printf("%s: %s num_values == %u\n", __location__, attr, \
+ (unsigned int)_a->value_ctr.num_values); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (strict && !_a->value_ctr.values[0].blob) { \
+ d_printf("%s: %s data == NULL\n", __location__, attr); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (strict && _a->value_ctr.values[0].blob->length != 4) { \
+ d_printf("%s: %s length == %u\n", __location__, attr, \
+ (unsigned int)_a->value_ctr.values[0].blob->length); \
+ return WERR_INVALID_PARAM; \
+ } \
+ if (_a && _a->value_ctr.num_values >= 1 \
+ && _a->value_ctr.values[0].blob \
+ && _a->value_ctr.values[0].blob->length == 4) { \
+ (p)->elem = (IVAL(_a->value_ctr.values[0].blob->data,0)?true:false);\
+ } else { \
+ (p)->elem = false; \
+ } \
+} while (0)
+
+#define GET_UINT32_DS(s, r, attr, p, elem) do { \
+ struct drsuapi_DsReplicaAttribute *_a; \
+ _a = dsdb_find_object_attr_name(s, r, attr, NULL); \
+ if (_a && _a->value_ctr.num_values >= 1 \
+ && _a->value_ctr.values[0].blob \
+ && _a->value_ctr.values[0].blob->length == 4) { \
+ (p)->elem = IVAL(_a->value_ctr.values[0].blob->data,0);\
+ } else { \
+ (p)->elem = 0; \
+ } \
+} while (0)
+
+#define GET_GUID_DS(s, r, attr, mem_ctx, p, elem) do { \
+ struct drsuapi_DsReplicaAttribute *_a; \
+ _a = dsdb_find_object_attr_name(s, r, attr, NULL); \
+ if (_a && _a->value_ctr.num_values >= 1 \
+ && _a->value_ctr.values[0].blob \
+ && _a->value_ctr.values[0].blob->length == 16) { \
+ enum ndr_err_code _ndr_err; \
+ _ndr_err = ndr_pull_struct_blob_all(_a->value_ctr.values[0].blob, \
+ mem_ctx, s->iconv_convenience, &(p)->elem, \
+ (ndr_pull_flags_fn_t)ndr_pull_GUID); \
+ if (!NDR_ERR_CODE_IS_SUCCESS(_ndr_err)) { \
+ NTSTATUS _nt_status = ndr_map_error2ntstatus(_ndr_err); \
+ return ntstatus_to_werror(_nt_status); \
+ } \
+ } else { \
+ ZERO_STRUCT((p)->elem);\
+ } \
+} while (0)
+
+#define GET_BLOB_DS(s, r, attr, mem_ctx, p, elem) do { \
+ struct drsuapi_DsReplicaAttribute *_a; \
+ _a = dsdb_find_object_attr_name(s, r, attr, NULL); \
+ if (_a && _a->value_ctr.num_values >= 1 \
+ && _a->value_ctr.values[0].blob) { \
+ (p)->elem = *_a->value_ctr.values[0].blob;\
+ talloc_steal(mem_ctx, (p)->elem.data); \
+ } else { \
+ ZERO_STRUCT((p)->elem);\
+ }\
+} while (0)
+
+WERROR dsdb_attribute_from_drsuapi(struct dsdb_schema *schema,
+ struct drsuapi_DsReplicaObject *r,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_attribute *attr)
+{
+ WERROR status;
+
+ GET_STRING_DS(schema, r, "name", mem_ctx, attr, cn, true);
+ GET_STRING_DS(schema, r, "lDAPDisplayName", mem_ctx, attr, lDAPDisplayName, true);
+ GET_UINT32_DS(schema, r, "attributeID", attr, attributeID_id);
+ status = dsdb_map_int2oid(schema, attr->attributeID_id, mem_ctx, &attr->attributeID_oid);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("%s: '%s': unable to map attributeID 0x%08X: %s\n",
+ __location__, attr->lDAPDisplayName, attr->attributeID_id,
+ win_errstr(status)));
+ return status;
+ }
+ GET_GUID_DS(schema, r, "schemaIDGUID", mem_ctx, attr, schemaIDGUID);
+ GET_UINT32_DS(schema, r, "mAPIID", attr, mAPIID);
+
+ GET_GUID_DS(schema, r, "attributeSecurityGUID", mem_ctx, attr, attributeSecurityGUID);
+
+ GET_UINT32_DS(schema, r, "searchFlags", attr, searchFlags);
+ GET_UINT32_DS(schema, r, "systemFlags", attr, systemFlags);
+ GET_BOOL_DS(schema, r, "isMemberOfPartialAttributeSet", attr, isMemberOfPartialAttributeSet, false);
+ GET_UINT32_DS(schema, r, "linkID", attr, linkID);
+
+ GET_UINT32_DS(schema, r, "attributeSyntax", attr, attributeSyntax_id);
+ status = dsdb_map_int2oid(schema, attr->attributeSyntax_id, mem_ctx, &attr->attributeSyntax_oid);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("%s: '%s': unable to map attributeSyntax 0x%08X: %s\n",
+ __location__, attr->lDAPDisplayName, attr->attributeSyntax_id,
+ win_errstr(status)));
+ return status;
+ }
+ GET_UINT32_DS(schema, r, "oMSyntax", attr, oMSyntax);
+ GET_BLOB_DS(schema, r, "oMObjectClass", mem_ctx, attr, oMObjectClass);
+
+ GET_BOOL_DS(schema, r, "isSingleValued", attr, isSingleValued, true);
+ GET_UINT32_DS(schema, r, "rangeLower", attr, rangeLower);
+ GET_UINT32_DS(schema, r, "rangeUpper", attr, rangeUpper);
+ GET_BOOL_DS(schema, r, "extendedCharsAllowed", attr, extendedCharsAllowed, false);
+
+ GET_UINT32_DS(schema, r, "schemaFlagsEx", attr, schemaFlagsEx);
+ GET_BLOB_DS(schema, r, "msDs-Schema-Extensions", mem_ctx, attr, msDs_Schema_Extensions);
+
+ GET_BOOL_DS(schema, r, "showInAdvancedViewOnly", attr, showInAdvancedViewOnly, false);
+ GET_STRING_DS(schema, r, "adminDisplayName", mem_ctx, attr, adminDisplayName, false);
+ GET_STRING_DS(schema, r, "adminDescription", mem_ctx, attr, adminDescription, false);
+ GET_STRING_DS(schema, r, "classDisplayName", mem_ctx, attr, classDisplayName, false);
+ GET_BOOL_DS(schema, r, "isEphemeral", attr, isEphemeral, false);
+ GET_BOOL_DS(schema, r, "isDefunct", attr, isDefunct, false);
+ GET_BOOL_DS(schema, r, "systemOnly", attr, systemOnly, false);
+
+ attr->syntax = dsdb_syntax_for_attribute(attr);
+ if (!attr->syntax) {
+ return WERR_DS_ATT_SCHEMA_REQ_SYNTAX;
+ }
+
+ return WERR_OK;
+}
+
+WERROR dsdb_class_from_drsuapi(struct dsdb_schema *schema,
+ struct drsuapi_DsReplicaObject *r,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_class *obj)
+{
+ WERROR status;
+
+ GET_STRING_DS(schema, r, "name", mem_ctx, obj, cn, true);
+ GET_STRING_DS(schema, r, "lDAPDisplayName", mem_ctx, obj, lDAPDisplayName, true);
+ GET_UINT32_DS(schema, r, "governsID", obj, governsID_id);
+ status = dsdb_map_int2oid(schema, obj->governsID_id, mem_ctx, &obj->governsID_oid);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0,("%s: '%s': unable to map governsID 0x%08X: %s\n",
+ __location__, obj->lDAPDisplayName, obj->governsID_id,
+ win_errstr(status)));
+ return status;
+ }
+ GET_GUID_DS(schema, r, "schemaIDGUID", mem_ctx, obj, schemaIDGUID);
+
+ GET_UINT32_DS(schema, r, "objectClassCategory", obj, objectClassCategory);
+ GET_STRING_DS(schema, r, "rDNAttID", mem_ctx, obj, rDNAttID, false);
+ GET_DN_DS(schema, r, "defaultObjectCategory", mem_ctx, obj, defaultObjectCategory, true);
+
+ GET_STRING_DS(schema, r, "subClassOf", mem_ctx, obj, subClassOf, true);
+
+ obj->systemAuxiliaryClass = NULL;
+ obj->systemPossSuperiors = NULL;
+ obj->systemMustContain = NULL;
+ obj->systemMayContain = NULL;
+
+ obj->auxiliaryClass = NULL;
+ obj->possSuperiors = NULL;
+ obj->mustContain = NULL;
+ obj->mayContain = NULL;
+
+ obj->possibleInferiors = NULL;
+
+ GET_STRING_DS(schema, r, "defaultSecurityDescriptor", mem_ctx, obj, defaultSecurityDescriptor, false);
+
+ GET_UINT32_DS(schema, r, "schemaFlagsEx", obj, schemaFlagsEx);
+ GET_BLOB_DS(schema, r, "msDs-Schema-Extensions", mem_ctx, obj, msDs_Schema_Extensions);
+
+ GET_BOOL_DS(schema, r, "showInAdvancedViewOnly", obj, showInAdvancedViewOnly, false);
+ GET_STRING_DS(schema, r, "adminDisplayName", mem_ctx, obj, adminDisplayName, false);
+ GET_STRING_DS(schema, r, "adminDescription", mem_ctx, obj, adminDescription, false);
+ GET_STRING_DS(schema, r, "classDisplayName", mem_ctx, obj, classDisplayName, false);
+ GET_BOOL_DS(schema, r, "defaultHidingValue", obj, defaultHidingValue, false);
+ GET_BOOL_DS(schema, r, "isDefunct", obj, isDefunct, false);
+ GET_BOOL_DS(schema, r, "systemOnly", obj, systemOnly, false);
+
+ return WERR_OK;
+}
+
diff --git a/source4/dsdb/schema/schema_query.c b/source4/dsdb/schema/schema_query.c
new file mode 100644
index 0000000000..ca26ffd206
--- /dev/null
+++ b/source4/dsdb/schema/schema_query.c
@@ -0,0 +1,344 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB schema header
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2006-2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2008
+
+ 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/samdb/samdb.h"
+
+const struct dsdb_attribute *dsdb_attribute_by_attributeID_id(const struct dsdb_schema *schema,
+ uint32_t id)
+{
+ struct dsdb_attribute *cur;
+
+ /*
+ * 0xFFFFFFFF is used as value when no mapping table is available,
+ * so don't try to match with it
+ */
+ if (id == 0xFFFFFFFF) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (cur->attributeID_id != id) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_attribute *dsdb_attribute_by_attributeID_oid(const struct dsdb_schema *schema,
+ const char *oid)
+{
+ struct dsdb_attribute *cur;
+
+ if (!oid) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (strcmp(cur->attributeID_oid, oid) != 0) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_attribute *dsdb_attribute_by_lDAPDisplayName(const struct dsdb_schema *schema,
+ const char *name)
+{
+ struct dsdb_attribute *cur;
+
+ if (!name) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (strcasecmp(cur->lDAPDisplayName, name) != 0) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_attribute *dsdb_attribute_by_linkID(const struct dsdb_schema *schema,
+ int linkID)
+{
+ struct dsdb_attribute *cur;
+
+ /* TODO: add binary search */
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (cur->linkID != linkID) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_class *dsdb_class_by_governsID_id(const struct dsdb_schema *schema,
+ uint32_t id)
+{
+ struct dsdb_class *cur;
+
+ /*
+ * 0xFFFFFFFF is used as value when no mapping table is available,
+ * so don't try to match with it
+ */
+ if (id == 0xFFFFFFFF) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->classes; cur; cur = cur->next) {
+ if (cur->governsID_id != id) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_class *dsdb_class_by_governsID_oid(const struct dsdb_schema *schema,
+ const char *oid)
+{
+ struct dsdb_class *cur;
+
+ if (!oid) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->classes; cur; cur = cur->next) {
+ if (strcmp(cur->governsID_oid, oid) != 0) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_class *dsdb_class_by_lDAPDisplayName(const struct dsdb_schema *schema,
+ const char *name)
+{
+ struct dsdb_class *cur;
+
+ if (!name) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->classes; cur; cur = cur->next) {
+ if (strcasecmp(cur->lDAPDisplayName, name) != 0) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const struct dsdb_class *dsdb_class_by_cn(const struct dsdb_schema *schema,
+ const char *cn)
+{
+ struct dsdb_class *cur;
+
+ if (!cn) return NULL;
+
+ /* TODO: add binary search */
+ for (cur = schema->classes; cur; cur = cur->next) {
+ if (strcasecmp(cur->cn, cn) != 0) continue;
+
+ return cur;
+ }
+
+ return NULL;
+}
+
+const char *dsdb_lDAPDisplayName_by_id(const struct dsdb_schema *schema,
+ uint32_t id)
+{
+ const struct dsdb_attribute *a;
+ const struct dsdb_class *c;
+
+ /* TODO: add binary search */
+ a = dsdb_attribute_by_attributeID_id(schema, id);
+ if (a) {
+ return a->lDAPDisplayName;
+ }
+
+ c = dsdb_class_by_governsID_id(schema, id);
+ if (c) {
+ return c->lDAPDisplayName;
+ }
+
+ return NULL;
+}
+
+/**
+ Return a list of linked attributes, in lDAPDisplayName format.
+
+ This may be used to determine if a modification would require
+ backlinks to be updated, for example
+*/
+
+WERROR dsdb_linked_attribute_lDAPDisplayName_list(const struct dsdb_schema *schema, TALLOC_CTX *mem_ctx, const char ***attr_list_ret)
+{
+ const char **attr_list = NULL;
+ struct dsdb_attribute *cur;
+ int i = 0;
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (cur->linkID == 0) continue;
+
+ attr_list = talloc_realloc(mem_ctx, attr_list, const char *, i+2);
+ if (!attr_list) {
+ return WERR_NOMEM;
+ }
+ attr_list[i] = cur->lDAPDisplayName;
+ i++;
+ }
+ attr_list[i] = NULL;
+ *attr_list_ret = attr_list;
+ return WERR_OK;
+}
+
+char **merge_attr_list(TALLOC_CTX *mem_ctx,
+ char **attrs, const char **new_attrs)
+{
+ char **ret_attrs;
+ int i;
+ size_t new_len, orig_len = str_list_length((const char **)attrs);
+ if (!new_attrs) {
+ return attrs;
+ }
+
+ ret_attrs = talloc_realloc(mem_ctx,
+ attrs, char *, orig_len + str_list_length(new_attrs) + 1);
+ if (ret_attrs) {
+ for (i=0; i < str_list_length(new_attrs); i++) {
+ ret_attrs[orig_len + i] = new_attrs[i];
+ }
+ new_len = orig_len + str_list_length(new_attrs);
+
+ ret_attrs[new_len] = NULL;
+ }
+
+ return ret_attrs;
+}
+
+/*
+ Return a merged list of the attributes of exactly one class (not
+ considering subclasses, auxillary classes etc)
+*/
+
+char **dsdb_attribute_list(TALLOC_CTX *mem_ctx, const struct dsdb_class *class, enum dsdb_attr_list_query query)
+{
+ char **attr_list = NULL;
+ switch (query) {
+ case DSDB_SCHEMA_ALL_MAY:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->mayContain);
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->systemMayContain);
+ break;
+
+ case DSDB_SCHEMA_ALL_MUST:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->mustContain);
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->systemMustContain);
+ break;
+
+ case DSDB_SCHEMA_SYS_MAY:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->systemMayContain);
+ break;
+
+ case DSDB_SCHEMA_SYS_MUST:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->systemMustContain);
+ break;
+
+ case DSDB_SCHEMA_MAY:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->mayContain);
+ break;
+
+ case DSDB_SCHEMA_MUST:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->mustContain);
+ break;
+
+ case DSDB_SCHEMA_ALL:
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->mayContain);
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->systemMayContain);
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->mustContain);
+ attr_list = merge_attr_list(mem_ctx, attr_list, class->systemMustContain);
+ break;
+ }
+ return attr_list;
+}
+
+static char **dsdb_full_attribute_list_internal(TALLOC_CTX *mem_ctx,
+ const struct dsdb_schema *schema,
+ const char **class_list,
+ enum dsdb_attr_list_query query)
+{
+ int i;
+ const struct dsdb_class *class;
+
+ char **attr_list = NULL;
+ char **this_class_list;
+ char **recursive_list;
+
+ for (i=0; class_list && class_list[i]; i++) {
+ class = dsdb_class_by_lDAPDisplayName(schema, class_list[i]);
+
+ this_class_list = dsdb_attribute_list(mem_ctx, class, query);
+ attr_list = merge_attr_list(mem_ctx, attr_list, (const char **)this_class_list);
+
+ recursive_list = dsdb_full_attribute_list_internal(mem_ctx, schema,
+ class->systemAuxiliaryClass,
+ query);
+
+ attr_list = merge_attr_list(mem_ctx, attr_list, (const char **)recursive_list);
+
+ recursive_list = dsdb_full_attribute_list_internal(mem_ctx, schema,
+ class->auxiliaryClass,
+ query);
+
+ attr_list = merge_attr_list(mem_ctx, attr_list, (const char **)recursive_list);
+
+ }
+ return attr_list;
+}
+
+char **dsdb_full_attribute_list(TALLOC_CTX *mem_ctx,
+ const struct dsdb_schema *schema,
+ const char **class_list,
+ enum dsdb_attr_list_query query)
+{
+ char **attr_list = dsdb_full_attribute_list_internal(mem_ctx, schema, class_list, query);
+ size_t new_len = str_list_length((const char **)attr_list);
+
+ /* Remove duplicates */
+ if (new_len > 1) {
+ int i;
+ qsort(attr_list, new_len,
+ sizeof(*attr_list),
+ (comparison_fn_t)strcasecmp);
+
+ for (i=1 ; i < new_len; i++) {
+ char **val1 = &attr_list[i-1];
+ char **val2 = &attr_list[i];
+ if (ldb_attr_cmp(*val1, *val2) == 0) {
+ memmove(val1, val2, (new_len - i) * sizeof( *attr_list));
+ new_len--;
+ i--;
+ }
+ }
+ }
+ return attr_list;
+}
diff --git a/source4/dsdb/schema/schema_set.c b/source4/dsdb/schema/schema_set.c
new file mode 100644
index 0000000000..2303b88a05
--- /dev/null
+++ b/source4/dsdb/schema/schema_set.c
@@ -0,0 +1,409 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB schema header
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2006-2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2008
+
+ 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/samdb/samdb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "lib/util/dlinklist.h"
+#include "param/param.h"
+
+
+static int dsdb_schema_set_attributes(struct ldb_context *ldb, struct dsdb_schema *schema, bool write_attributes)
+{
+ int ret = LDB_SUCCESS;
+ struct ldb_result *res;
+ struct ldb_result *res_idx;
+ struct dsdb_attribute *attr;
+ struct ldb_message *mod_msg;
+ TALLOC_CTX *mem_ctx = talloc_new(ldb);
+
+ struct ldb_message *msg;
+ struct ldb_message *msg_idx;
+
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg = ldb_msg_new(mem_ctx);
+ if (!msg) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg_idx = ldb_msg_new(mem_ctx);
+ if (!msg_idx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg->dn = ldb_dn_new(msg, ldb, "@ATTRIBUTES");
+ if (!msg->dn) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg_idx->dn = ldb_dn_new(msg, ldb, "@INDEXLIST");
+ if (!msg_idx->dn) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (attr = schema->attributes; attr; attr = attr->next) {
+ const struct ldb_schema_syntax *s;
+ const char *syntax = attr->syntax->ldb_syntax;
+ if (!syntax) {
+ syntax = attr->syntax->ldap_oid;
+ }
+
+ /* Write out a rough approximation of the schema as an @ATTRIBUTES value, for bootstrapping */
+ if (strcmp(syntax, LDB_SYNTAX_INTEGER) == 0) {
+ ret = ldb_msg_add_string(msg, attr->lDAPDisplayName, "INTEGER");
+ } else if (strcmp(syntax, LDB_SYNTAX_DIRECTORY_STRING) == 0) {
+ ret = ldb_msg_add_string(msg, attr->lDAPDisplayName, "CASE_INSENSITIVE");
+ }
+ if (ret != LDB_SUCCESS) {
+ break;
+ }
+
+ if (attr->searchFlags & SEARCH_FLAG_ATTINDEX) {
+ ret = ldb_msg_add_string(msg_idx, "@IDXATTR", attr->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ break;
+ }
+ }
+
+ if (!attr->syntax) {
+ continue;
+ }
+
+ ret = ldb_schema_attribute_add(ldb, attr->lDAPDisplayName, LDB_ATTR_FLAG_FIXED,
+ syntax);
+ if (ret != LDB_SUCCESS) {
+ s = ldb_samba_syntax_by_name(ldb, attr->syntax->ldap_oid);
+ if (s) {
+ ret = ldb_schema_attribute_add_with_syntax(ldb, attr->lDAPDisplayName, LDB_ATTR_FLAG_FIXED, s);
+ } else {
+ ret = LDB_SUCCESS; /* Nothing to do here */
+ }
+ }
+
+ if (ret != LDB_SUCCESS) {
+ break;
+ }
+ }
+
+ if (!write_attributes || ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+
+ /* Try to avoid churning the attributes too much - we only want to do this if they have changed */
+ ret = ldb_search_exp_fmt(ldb, mem_ctx, &res, msg->dn, LDB_SCOPE_BASE, NULL, "dn=%s", ldb_dn_get_linearized(msg->dn));
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = ldb_add(ldb, msg);
+ } else if (ret != LDB_SUCCESS) {
+ } else if (res->count != 1) {
+ ret = ldb_add(ldb, msg);
+ } else {
+ ret = LDB_SUCCESS;
+ /* Annoyingly added to our search results */
+ ldb_msg_remove_attr(res->msgs[0], "distinguishedName");
+
+ mod_msg = ldb_msg_diff(ldb, res->msgs[0], msg);
+ if (mod_msg->num_elements > 0) {
+ ret = ldb_modify(ldb, mod_msg);
+ }
+ }
+
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ /* We might be on a read-only DB */
+ ret = LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* Now write out the indexs, as found in the schema (if they have changed) */
+
+ ret = ldb_search_exp_fmt(ldb, mem_ctx, &res_idx, msg_idx->dn, LDB_SCOPE_BASE, NULL, "dn=%s", ldb_dn_get_linearized(msg_idx->dn));
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = ldb_add(ldb, msg_idx);
+ } else if (ret != LDB_SUCCESS) {
+ } else if (res->count != 1) {
+ ret = ldb_add(ldb, msg_idx);
+ } else {
+ ret = LDB_SUCCESS;
+ /* Annoyingly added to our search results */
+ ldb_msg_remove_attr(res_idx->msgs[0], "distinguishedName");
+
+ mod_msg = ldb_msg_diff(ldb, res_idx->msgs[0], msg_idx);
+ if (mod_msg->num_elements > 0) {
+ ret = ldb_modify(ldb, mod_msg);
+ }
+ }
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ /* We might be on a read-only DB */
+ ret = LDB_SUCCESS;
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+
+/**
+ * Attach the schema to an opaque pointer on the ldb, so ldb modules
+ * can find it
+ */
+
+int dsdb_set_schema(struct ldb_context *ldb, struct dsdb_schema *schema)
+{
+ int ret;
+
+ ret = ldb_set_opaque(ldb, "dsdb_schema", schema);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Set the new attributes based on the new schema */
+ ret = dsdb_schema_set_attributes(ldb, schema, true);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(ldb, schema);
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Global variable to hold one copy of the schema, used to avoid memory bloat
+ */
+static struct dsdb_schema *global_schema;
+
+/**
+ * Make this ldb use the 'global' schema, setup to avoid having multiple copies in this process
+ */
+int dsdb_set_global_schema(struct ldb_context *ldb)
+{
+ int ret;
+ if (!global_schema) {
+ return LDB_SUCCESS;
+ }
+ ret = ldb_set_opaque(ldb, "dsdb_schema", global_schema);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Set the new attributes based on the new schema */
+ ret = dsdb_schema_set_attributes(ldb, global_schema, false);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Keep a reference to this schema, just incase the global copy is replaced */
+ if (talloc_reference(ldb, global_schema) == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Find the schema object for this ldb
+ */
+
+struct dsdb_schema *dsdb_get_schema(struct ldb_context *ldb)
+{
+ const void *p;
+ struct dsdb_schema *schema;
+
+ /* see if we have a cached copy */
+ p = ldb_get_opaque(ldb, "dsdb_schema");
+ if (!p) {
+ return NULL;
+ }
+
+ schema = talloc_get_type(p, struct dsdb_schema);
+ if (!schema) {
+ return NULL;
+ }
+
+ return schema;
+}
+
+/**
+ * Make the schema found on this ldb the 'global' schema
+ */
+
+void dsdb_make_schema_global(struct ldb_context *ldb)
+{
+ struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ if (!schema) {
+ return;
+ }
+
+ if (global_schema) {
+ talloc_unlink(talloc_autofree_context(), schema);
+ }
+
+ talloc_steal(talloc_autofree_context(), schema);
+ global_schema = schema;
+
+ dsdb_set_global_schema(ldb);
+}
+
+
+/**
+ * Rather than read a schema from the LDB itself, read it from an ldif
+ * file. This allows schema to be loaded and used while adding the
+ * schema itself to the directory.
+ */
+
+WERROR dsdb_attach_schema_from_ldif_file(struct ldb_context *ldb, const char *pf, const char *df)
+{
+ struct ldb_ldif *ldif;
+ struct ldb_message *msg;
+ TALLOC_CTX *mem_ctx;
+ WERROR status;
+ int ret;
+ struct dsdb_schema *schema;
+ const struct ldb_val *prefix_val;
+ const struct ldb_val *info_val;
+ struct ldb_val info_val_default;
+
+ mem_ctx = talloc_new(ldb);
+ if (!mem_ctx) {
+ goto nomem;
+ }
+
+ schema = dsdb_new_schema(mem_ctx, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")));
+
+ schema->fsmo.we_are_master = true;
+ schema->fsmo.master_dn = ldb_dn_new_fmt(schema, ldb, "@PROVISION_SCHEMA_MASTER");
+ if (!schema->fsmo.master_dn) {
+ goto nomem;
+ }
+
+ /*
+ * load the prefixMap attribute from pf
+ */
+ ldif = ldb_ldif_read_string(ldb, &pf);
+ if (!ldif) {
+ status = WERR_INVALID_PARAM;
+ goto failed;
+ }
+ talloc_steal(mem_ctx, ldif);
+
+ msg = ldb_msg_canonicalize(ldb, ldif->msg);
+ if (!msg) {
+ goto nomem;
+ }
+ talloc_steal(mem_ctx, msg);
+ talloc_free(ldif);
+
+ prefix_val = ldb_msg_find_ldb_val(msg, "prefixMap");
+ if (!prefix_val) {
+ status = WERR_INVALID_PARAM;
+ goto failed;
+ }
+
+ info_val = ldb_msg_find_ldb_val(msg, "schemaInfo");
+ if (!info_val) {
+ info_val_default = strhex_to_data_blob("FF0000000000000000000000000000000000000000");
+ if (!info_val_default.data) {
+ goto nomem;
+ }
+ talloc_steal(mem_ctx, info_val_default.data);
+ info_val = &info_val_default;
+ }
+
+ status = dsdb_load_oid_mappings_ldb(schema, prefix_val, info_val);
+ if (!W_ERROR_IS_OK(status)) {
+ goto failed;
+ }
+
+ /*
+ * load the attribute and class definitions outof df
+ */
+ while ((ldif = ldb_ldif_read_string(ldb, &df))) {
+ bool is_sa;
+ bool is_sc;
+
+ talloc_steal(mem_ctx, ldif);
+
+ msg = ldb_msg_canonicalize(ldb, ldif->msg);
+ if (!msg) {
+ goto nomem;
+ }
+
+ talloc_steal(mem_ctx, msg);
+ talloc_free(ldif);
+
+ is_sa = ldb_msg_check_string_attribute(msg, "objectClass", "attributeSchema");
+ is_sc = ldb_msg_check_string_attribute(msg, "objectClass", "classSchema");
+
+ if (is_sa) {
+ struct dsdb_attribute *sa;
+
+ sa = talloc_zero(schema, struct dsdb_attribute);
+ if (!sa) {
+ goto nomem;
+ }
+
+ status = dsdb_attribute_from_ldb(schema, msg, sa, sa);
+ if (!W_ERROR_IS_OK(status)) {
+ goto failed;
+ }
+
+ DLIST_ADD_END(schema->attributes, sa, struct dsdb_attribute *);
+ } else if (is_sc) {
+ struct dsdb_class *sc;
+
+ sc = talloc_zero(schema, struct dsdb_class);
+ if (!sc) {
+ goto nomem;
+ }
+
+ status = dsdb_class_from_ldb(schema, msg, sc, sc);
+ if (!W_ERROR_IS_OK(status)) {
+ goto failed;
+ }
+
+ DLIST_ADD_END(schema->classes, sc, struct dsdb_class *);
+ }
+ }
+
+ ret = dsdb_set_schema(ldb, schema);
+ if (ret != LDB_SUCCESS) {
+ status = WERR_FOOBAR;
+ goto failed;
+ }
+
+ goto done;
+
+nomem:
+ status = WERR_NOMEM;
+failed:
+done:
+ talloc_free(mem_ctx);
+ return status;
+}
diff --git a/source4/dsdb/schema/schema_syntax.c b/source4/dsdb/schema/schema_syntax.c
new file mode 100644
index 0000000000..97cd0020a9
--- /dev/null
+++ b/source4/dsdb/schema/schema_syntax.c
@@ -0,0 +1,1426 @@
+/*
+ Unix SMB/CIFS mplementation.
+ DSDB schema syntaxes
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2006
+ Copyright (C) Simo Sorce 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
+
+ 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/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "lib/ldb/include/ldb.h"
+#include "system/time.h"
+#include "lib/charset/charset.h"
+#include "librpc/ndr/libndr.h"
+#include "param/param.h"
+
+static WERROR dsdb_syntax_FOOBAR_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ str = talloc_asprintf(out->values, "%s: not implemented",
+ attr->syntax->name);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_FOOBAR_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ return WERR_FOOBAR;
+}
+
+static WERROR dsdb_syntax_BOOL_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ uint32_t v;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 4) {
+ return WERR_FOOBAR;
+ }
+
+ v = IVAL(in->value_ctr.values[i].blob->data, 0);
+
+ if (v != 0) {
+ str = talloc_strdup(out->values, "TRUE");
+ W_ERROR_HAVE_NO_MEMORY(str);
+ } else {
+ str = talloc_strdup(out->values, "FALSE");
+ W_ERROR_HAVE_NO_MEMORY(str);
+ }
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_BOOL_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 4);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ if (strcmp("TRUE", (const char *)in->values[i].data) == 0) {
+ SIVAL(blobs[i].data, 0, 0x00000001);
+ } else if (strcmp("FALSE", (const char *)in->values[i].data) == 0) {
+ SIVAL(blobs[i].data, 0, 0x00000000);
+ } else {
+ return WERR_FOOBAR;
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_INT32_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ int32_t v;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 4) {
+ return WERR_FOOBAR;
+ }
+
+ v = IVALS(in->value_ctr.values[i].blob->data, 0);
+
+ str = talloc_asprintf(out->values, "%d", v);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_INT32_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ int32_t v;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 4);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ v = strtol((const char *)in->values[i].data, NULL, 10);
+
+ SIVALS(blobs[i].data, 0, v);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_INT64_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ int64_t v;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 8) {
+ return WERR_FOOBAR;
+ }
+
+ v = BVALS(in->value_ctr.values[i].blob->data, 0);
+
+ str = talloc_asprintf(out->values, "%lld", (long long int)v);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_INT64_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ int64_t v;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 8);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ v = strtoll((const char *)in->values[i].data, NULL, 10);
+
+ SBVALS(blobs[i].data, 0, v);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_NTTIME_UTC_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ NTTIME v;
+ time_t t;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 8) {
+ return WERR_FOOBAR;
+ }
+
+ v = BVAL(in->value_ctr.values[i].blob->data, 0);
+ v *= 10000000;
+ t = nt_time_to_unix(v);
+
+ /*
+ * NOTE: On a w2k3 server you can set a GeneralizedTime string
+ * via LDAP, but you get back an UTCTime string,
+ * but via DRSUAPI you get back the NTTIME_1sec value
+ * that represents the GeneralizedTime value!
+ *
+ * So if we store the UTCTime string in our ldb
+ * we'll loose information!
+ */
+ str = ldb_timestring_utc(out->values, t);
+ W_ERROR_HAVE_NO_MEMORY(str);
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_NTTIME_UTC_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ NTTIME v;
+ time_t t;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 8);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ t = ldb_string_utc_to_time((const char *)in->values[i].data);
+ unix_to_nt_time(&v, t);
+ v /= 10000000;
+
+ SBVAL(blobs[i].data, 0, v);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_NTTIME_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ NTTIME v;
+ time_t t;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 8) {
+ return WERR_FOOBAR;
+ }
+
+ v = BVAL(in->value_ctr.values[i].blob->data, 0);
+ v *= 10000000;
+ t = nt_time_to_unix(v);
+
+ str = ldb_timestring(out->values, t);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_NTTIME_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ NTTIME v;
+ time_t t;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 8);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ t = ldb_string_to_time((const char *)in->values[i].data);
+ unix_to_nt_time(&v, t);
+ v /= 10000000;
+
+ SBVAL(blobs[i].data, 0, v);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_DATA_BLOB_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length == 0) {
+ return WERR_FOOBAR;
+ }
+
+ out->values[i] = data_blob_dup_talloc(out->values,
+ in->value_ctr.values[i].blob);
+ W_ERROR_HAVE_NO_MEMORY(out->values[i].data);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_DATA_BLOB_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_dup_talloc(blobs, &in->values[i]);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR _dsdb_syntax_OID_obj_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ uint32_t v;
+ const struct dsdb_class *c;
+ const char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 4) {
+ return WERR_FOOBAR;
+ }
+
+ v = IVAL(in->value_ctr.values[i].blob->data, 0);
+
+ c = dsdb_class_by_governsID_id(schema, v);
+ if (!c) {
+ return WERR_FOOBAR;
+ }
+
+ str = talloc_strdup(out->values, c->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ /* the values need to be reversed */
+ out->values[out->num_values - (i + 1)] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR _dsdb_syntax_OID_oid_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ uint32_t v;
+ WERROR status;
+ const char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 4) {
+ return WERR_FOOBAR;
+ }
+
+ v = IVAL(in->value_ctr.values[i].blob->data, 0);
+
+ status = dsdb_map_int2oid(schema, v, out->values, &str);
+ W_ERROR_NOT_OK_RETURN(status);
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_OID_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ switch (attr->attributeID_id) {
+ case DRSUAPI_ATTRIBUTE_objectClass:
+ return _dsdb_syntax_OID_obj_drsuapi_to_ldb(schema, attr, in, mem_ctx, out);
+ case DRSUAPI_ATTRIBUTE_governsID:
+ case DRSUAPI_ATTRIBUTE_attributeID:
+ case DRSUAPI_ATTRIBUTE_attributeSyntax:
+ return _dsdb_syntax_OID_oid_drsuapi_to_ldb(schema, attr, in, mem_ctx, out);
+ }
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ uint32_t v;
+ const char *name;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length != 4) {
+ return WERR_FOOBAR;
+ }
+
+ v = IVAL(in->value_ctr.values[i].blob->data, 0);
+
+ name = dsdb_lDAPDisplayName_by_id(schema, v);
+ if (!name) {
+ return WERR_FOOBAR;
+ }
+
+ str = talloc_strdup(out->values, name);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_OID_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ switch (attr->attributeID_id) {
+ case DRSUAPI_ATTRIBUTE_objectClass:
+ case DRSUAPI_ATTRIBUTE_governsID:
+ case DRSUAPI_ATTRIBUTE_attributeID:
+ case DRSUAPI_ATTRIBUTE_attributeSyntax:
+ return dsdb_syntax_FOOBAR_ldb_to_drsuapi(schema, attr, in, mem_ctx, out);
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ uint32_t v;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 4);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ v = strtol((const char *)in->values[i].data, NULL, 10);
+
+ SIVAL(blobs[i].data, 0, v);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_UNICODE_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ ssize_t ret;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length == 0) {
+ return WERR_FOOBAR;
+ }
+
+ ret = convert_string_talloc(out->values, schema->iconv_convenience,
+ CH_UTF16, CH_UNIX,
+ in->value_ctr.values[i].blob->data,
+ in->value_ctr.values[i].blob->length,
+ (void **)&str);
+ if (ret == -1) {
+ return WERR_FOOBAR;
+ }
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_UNICODE_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ ssize_t ret;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ ret = convert_string_talloc(blobs, schema->iconv_convenience, CH_UNIX, CH_UTF16,
+ in->values[i].data,
+ in->values[i].length,
+ (void **)&blobs[i].data);
+ if (ret == -1) {
+ return WERR_FOOBAR;
+ }
+ blobs[i].length = ret;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_DN_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ struct drsuapi_DsReplicaObjectIdentifier3 id3;
+ enum ndr_err_code ndr_err;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length == 0) {
+ return WERR_FOOBAR;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(in->value_ctr.values[i].blob,
+ out->values, schema->iconv_convenience, &id3,
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(status);
+ }
+
+ /* TODO: handle id3.guid and id3.sid */
+ out->values[i] = data_blob_string_const(id3.dn);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_DN_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ struct drsuapi_DsReplicaObjectIdentifier3 id3;
+ enum ndr_err_code ndr_err;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ /* TODO: handle id3.guid and id3.sid */
+ ZERO_STRUCT(id3);
+ id3.dn = (const char *)in->values[i].data;
+
+ ndr_err = ndr_push_struct_blob(&blobs[i], blobs, schema->iconv_convenience, &id3, (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_DN_BINARY_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ struct drsuapi_DsReplicaObjectIdentifier3Binary id3b;
+ char *binary;
+ char *str;
+ enum ndr_err_code ndr_err;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length == 0) {
+ return WERR_FOOBAR;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(in->value_ctr.values[i].blob,
+ out->values, schema->iconv_convenience, &id3b,
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3Binary);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(status);
+ }
+
+ /* TODO: handle id3.guid and id3.sid */
+ binary = data_blob_hex_string(out->values, &id3b.binary);
+ W_ERROR_HAVE_NO_MEMORY(binary);
+
+ str = talloc_asprintf(out->values, "B:%u:%s:%s",
+ (unsigned int)(id3b.binary.length * 2), /* because of 2 hex chars per byte */
+ binary,
+ id3b.dn);
+ W_ERROR_HAVE_NO_MEMORY(str);
+
+ /* TODO: handle id3.guid and id3.sid */
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_DN_BINARY_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ struct drsuapi_DsReplicaObjectIdentifier3Binary id3b;
+ enum ndr_err_code ndr_err;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ /* TODO: handle id3b.guid and id3b.sid, id3.binary */
+ ZERO_STRUCT(id3b);
+ id3b.dn = (const char *)in->values[i].data;
+ id3b.binary = data_blob(NULL, 0);
+
+ ndr_err = ndr_push_struct_blob(&blobs[i], blobs, schema->iconv_convenience, &id3b,
+ (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3Binary);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_PRESENTATION_ADDRESS_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ uint32_t i;
+
+ out->flags = 0;
+ out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName);
+ W_ERROR_HAVE_NO_MEMORY(out->name);
+
+ out->num_values = in->value_ctr.num_values;
+ out->values = talloc_array(mem_ctx, struct ldb_val, out->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->values);
+
+ for (i=0; i < out->num_values; i++) {
+ uint32_t len;
+ ssize_t ret;
+ char *str;
+
+ if (in->value_ctr.values[i].blob == NULL) {
+ return WERR_FOOBAR;
+ }
+
+ if (in->value_ctr.values[i].blob->length < 4) {
+ return WERR_FOOBAR;
+ }
+
+ len = IVAL(in->value_ctr.values[i].blob->data, 0);
+
+ if (len != in->value_ctr.values[i].blob->length) {
+ return WERR_FOOBAR;
+ }
+
+ ret = convert_string_talloc(out->values, schema->iconv_convenience, CH_UTF16, CH_UNIX,
+ in->value_ctr.values[i].blob->data+4,
+ in->value_ctr.values[i].blob->length-4,
+ (void **)&str);
+ if (ret == -1) {
+ return WERR_FOOBAR;
+ }
+
+ out->values[i] = data_blob_string_const(str);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR dsdb_syntax_PRESENTATION_ADDRESS_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ uint32_t i;
+ DATA_BLOB *blobs;
+
+ if (attr->attributeID_id == 0xFFFFFFFF) {
+ return WERR_FOOBAR;
+ }
+
+ out->attid = attr->attributeID_id;
+ out->value_ctr.num_values = in->num_values;
+ out->value_ctr.values = talloc_array(mem_ctx,
+ struct drsuapi_DsAttributeValue,
+ in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(out->value_ctr.values);
+
+ blobs = talloc_array(mem_ctx, DATA_BLOB, in->num_values);
+ W_ERROR_HAVE_NO_MEMORY(blobs);
+
+ for (i=0; i < in->num_values; i++) {
+ uint8_t *data;
+ ssize_t ret;
+
+ out->value_ctr.values[i].blob = &blobs[i];
+
+ ret = convert_string_talloc(blobs, schema->iconv_convenience, CH_UNIX, CH_UTF16,
+ in->values[i].data,
+ in->values[i].length,
+ (void **)&data);
+ if (ret == -1) {
+ return WERR_FOOBAR;
+ }
+
+ blobs[i] = data_blob_talloc(blobs, NULL, 4 + ret);
+ W_ERROR_HAVE_NO_MEMORY(blobs[i].data);
+
+ SIVAL(blobs[i].data, 0, 4 + ret);
+
+ if (ret > 0) {
+ memcpy(blobs[i].data + 4, data, ret);
+ talloc_free(data);
+ }
+ }
+
+ return WERR_OK;
+}
+
+#define OMOBJECTCLASS(val) { .length = sizeof(val) - 1, .data = discard_const_p(uint8_t, val) }
+
+static const struct dsdb_syntax dsdb_syntaxes[] = {
+ {
+ .name = "Boolean",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.7",
+ .oMSyntax = 1,
+ .attributeSyntax_oid = "2.5.5.8",
+ .drsuapi_to_ldb = dsdb_syntax_BOOL_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_BOOL_ldb_to_drsuapi,
+ .equality = "booleanMatch",
+ .comment = "Boolean"
+ },{
+ .name = "Integer",
+ .ldap_oid = LDB_SYNTAX_INTEGER,
+ .oMSyntax = 2,
+ .attributeSyntax_oid = "2.5.5.9",
+ .drsuapi_to_ldb = dsdb_syntax_INT32_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_INT32_ldb_to_drsuapi,
+ .equality = "integerMatch",
+ .comment = "Integer",
+ },{
+ .name = "String(Octet)",
+ .ldap_oid = LDB_SYNTAX_OCTET_STRING,
+ .oMSyntax = 4,
+ .attributeSyntax_oid = "2.5.5.10",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ .equality = "octetStringMatch",
+ .comment = "Octet String",
+ },{
+ .name = "String(Sid)",
+ .ldap_oid = LDB_SYNTAX_OCTET_STRING,
+ .oMSyntax = 4,
+ .attributeSyntax_oid = "2.5.5.17",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ .equality = "octetStringMatch",
+ .comment = "Octet String - Security Identifier (SID)",
+ .ldb_syntax = LDB_SYNTAX_SAMBA_SID
+ },{
+ .name = "String(Object-Identifier)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.38",
+ .oMSyntax = 6,
+ .attributeSyntax_oid = "2.5.5.2",
+ .drsuapi_to_ldb = dsdb_syntax_OID_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_OID_ldb_to_drsuapi,
+ .equality = "caseIgnoreMatch", /* Would use "objectIdentifierMatch" but most are ldap attribute/class names */
+ .comment = "OID String",
+ .ldb_syntax = LDB_SYNTAX_DIRECTORY_STRING
+ },{
+ .name = "Enumeration",
+ .ldap_oid = LDB_SYNTAX_INTEGER,
+ .oMSyntax = 10,
+ .attributeSyntax_oid = "2.5.5.9",
+ .drsuapi_to_ldb = dsdb_syntax_INT32_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_INT32_ldb_to_drsuapi,
+ },{
+ /* not used in w2k3 forest */
+ .name = "String(Numeric)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.36",
+ .oMSyntax = 18,
+ .attributeSyntax_oid = "2.5.5.6",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ .equality = "numericStringMatch",
+ .substring = "numericStringSubstringsMatch",
+ .comment = "Numeric String"
+ },{
+ .name = "String(Printable)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.44",
+ .oMSyntax = 19,
+ .attributeSyntax_oid = "2.5.5.5",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ },{
+ .name = "String(Teletex)",
+ .ldap_oid = "1.2.840.113556.1.4.905",
+ .oMSyntax = 20,
+ .attributeSyntax_oid = "2.5.5.4",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ .equality = "caseIgnoreMatch",
+ .substring = "caseIgnoreSubstringsMatch",
+ .comment = "Case Insensitive String",
+ .ldb_syntax = LDB_SYNTAX_DIRECTORY_STRING,
+ },{
+ .name = "String(IA5)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.26",
+ .oMSyntax = 22,
+ .attributeSyntax_oid = "2.5.5.5",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ .equality = "caseExactIA5Match",
+ .comment = "Printable String"
+ },{
+ .name = "String(UTC-Time)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.53",
+ .oMSyntax = 23,
+ .attributeSyntax_oid = "2.5.5.11",
+ .drsuapi_to_ldb = dsdb_syntax_NTTIME_UTC_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_NTTIME_UTC_ldb_to_drsuapi,
+ .equality = "generalizedTimeMatch",
+ .comment = "UTC Time",
+ },{
+ .name = "String(Generalized-Time)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.24",
+ .oMSyntax = 24,
+ .attributeSyntax_oid = "2.5.5.11",
+ .drsuapi_to_ldb = dsdb_syntax_NTTIME_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_NTTIME_ldb_to_drsuapi,
+ .equality = "generalizedTimeMatch",
+ .comment = "Generalized Time",
+ .ldb_syntax = LDB_SYNTAX_UTC_TIME,
+ },{
+ /* not used in w2k3 schema */
+ .name = "String(Case Sensitive)",
+ .ldap_oid = "1.2.840.113556.1.4.1362",
+ .oMSyntax = 27,
+ .attributeSyntax_oid = "2.5.5.3",
+ .drsuapi_to_ldb = dsdb_syntax_FOOBAR_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_FOOBAR_ldb_to_drsuapi,
+ },{
+ .name = "String(Unicode)",
+ .ldap_oid = LDB_SYNTAX_DIRECTORY_STRING,
+ .oMSyntax = 64,
+ .attributeSyntax_oid = "2.5.5.12",
+ .drsuapi_to_ldb = dsdb_syntax_UNICODE_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_UNICODE_ldb_to_drsuapi,
+ .equality = "caseIgnoreMatch",
+ .substring = "caseIgnoreSubstringsMatch",
+ .comment = "Directory String",
+ },{
+ .name = "Interval/LargeInteger",
+ .ldap_oid = "1.2.840.113556.1.4.906",
+ .oMSyntax = 65,
+ .attributeSyntax_oid = "2.5.5.16",
+ .drsuapi_to_ldb = dsdb_syntax_INT64_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_INT64_ldb_to_drsuapi,
+ .equality = "integerMatch",
+ .comment = "Large Integer",
+ .ldb_syntax = LDB_SYNTAX_INTEGER,
+ },{
+ .name = "String(NT-Sec-Desc)",
+ .ldap_oid = LDB_SYNTAX_SAMBA_SECURITY_DESCRIPTOR,
+ .oMSyntax = 66,
+ .attributeSyntax_oid = "2.5.5.15",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ },{
+ .name = "Object(DS-DN)",
+ .ldap_oid = LDB_SYNTAX_DN,
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x2b\x0c\x02\x87\x73\x1c\x00\x85\x4a"),
+ .attributeSyntax_oid = "2.5.5.1",
+ .drsuapi_to_ldb = dsdb_syntax_DN_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DN_ldb_to_drsuapi,
+ .equality = "distinguishedNameMatch",
+ .comment = "Object(DS-DN) == a DN",
+ },{
+ .name = "Object(DN-Binary)",
+ .ldap_oid = "1.2.840.113556.1.4.903",
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x0b"),
+ .attributeSyntax_oid = "2.5.5.7",
+ .drsuapi_to_ldb = dsdb_syntax_DN_BINARY_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DN_BINARY_ldb_to_drsuapi,
+ .equality = "distinguishedNameMatch",
+ .comment = "OctetString: Binary+DN",
+ .ldb_syntax = LDB_SYNTAX_DN,
+ },{
+ /* not used in w2k3 schema */
+ .name = "Object(OR-Name)",
+ .ldap_oid = "1.2.840.113556.1.4.1221",
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x56\x06\x01\x02\x05\x0b\x1D"),
+ .attributeSyntax_oid = "2.5.5.7",
+ .drsuapi_to_ldb = dsdb_syntax_FOOBAR_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_FOOBAR_ldb_to_drsuapi,
+ },{
+ /*
+ * TODO: verify if DATA_BLOB is correct here...!
+ *
+ * repsFrom and repsTo are the only attributes using
+ * this attribute syntax, but they're not replicated...
+ */
+ .name = "Object(Replica-Link)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.40",
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x06"),
+ .attributeSyntax_oid = "2.5.5.10",
+ .drsuapi_to_ldb = dsdb_syntax_DATA_BLOB_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_DATA_BLOB_ldb_to_drsuapi,
+ },{
+ .name = "Object(Presentation-Address)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.43",
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x2b\x0c\x02\x87\x73\x1c\x00\x85\x5c"),
+ .attributeSyntax_oid = "2.5.5.13",
+ .drsuapi_to_ldb = dsdb_syntax_PRESENTATION_ADDRESS_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_PRESENTATION_ADDRESS_ldb_to_drsuapi,
+ .comment = "Presentation Address"
+ },{
+ /* not used in w2k3 schema */
+ .name = "Object(Access-Point)",
+ .ldap_oid = "1.3.6.1.4.1.1466.115.121.1.2",
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x2b\x0c\x02\x87\x73\x1c\x00\x85\x3e"),
+ .attributeSyntax_oid = "2.5.5.14",
+ .drsuapi_to_ldb = dsdb_syntax_FOOBAR_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_FOOBAR_ldb_to_drsuapi,
+ .equality = "distinguishedNameMatch",
+ .comment = "OctetString: String+DN",
+ .ldb_syntax = LDB_SYNTAX_DN,
+ },{
+ /* not used in w2k3 schema */
+ .name = "Object(DN-String)",
+ .ldap_oid = "1.2.840.113556.1.4.904",
+ .oMSyntax = 127,
+ .oMObjectClass = OMOBJECTCLASS("\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x0c"),
+ .attributeSyntax_oid = "2.5.5.14",
+ .drsuapi_to_ldb = dsdb_syntax_FOOBAR_drsuapi_to_ldb,
+ .ldb_to_drsuapi = dsdb_syntax_FOOBAR_ldb_to_drsuapi,
+ .ldb_syntax = LDB_SYNTAX_DN,
+ }
+};
+
+const struct dsdb_syntax *find_syntax_map_by_ad_oid(const char *ad_oid)
+{
+ int i;
+ for (i=0; dsdb_syntaxes[i].ldap_oid; i++) {
+ if (strcasecmp(ad_oid, dsdb_syntaxes[i].attributeSyntax_oid) == 0) {
+ return &dsdb_syntaxes[i];
+ }
+ }
+ return NULL;
+}
+
+const struct dsdb_syntax *find_syntax_map_by_ad_syntax(int oMSyntax)
+{
+ int i;
+ for (i=0; dsdb_syntaxes[i].ldap_oid; i++) {
+ if (oMSyntax == dsdb_syntaxes[i].oMSyntax) {
+ return &dsdb_syntaxes[i];
+ }
+ }
+ return NULL;
+}
+
+const struct dsdb_syntax *find_syntax_map_by_standard_oid(const char *standard_oid)
+{
+ int i;
+ for (i=0; dsdb_syntaxes[i].ldap_oid; i++) {
+ if (strcasecmp(standard_oid, dsdb_syntaxes[i].ldap_oid) == 0) {
+ return &dsdb_syntaxes[i];
+ }
+ }
+ return NULL;
+}
+const struct dsdb_syntax *dsdb_syntax_for_attribute(const struct dsdb_attribute *attr)
+{
+ uint32_t i;
+
+ for (i=0; i < ARRAY_SIZE(dsdb_syntaxes); i++) {
+ if (attr->oMSyntax != dsdb_syntaxes[i].oMSyntax) continue;
+
+ if (attr->oMObjectClass.length != dsdb_syntaxes[i].oMObjectClass.length) continue;
+
+ if (attr->oMObjectClass.length) {
+ int ret;
+ ret = memcmp(attr->oMObjectClass.data,
+ dsdb_syntaxes[i].oMObjectClass.data,
+ attr->oMObjectClass.length);
+ if (ret != 0) continue;
+ }
+
+ if (strcmp(attr->attributeSyntax_oid, dsdb_syntaxes[i].attributeSyntax_oid) != 0) continue;
+
+ return &dsdb_syntaxes[i];
+ }
+
+ return NULL;
+}
+
+WERROR dsdb_attribute_drsuapi_to_ldb(const struct dsdb_schema *schema,
+ const struct drsuapi_DsReplicaAttribute *in,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *out)
+{
+ const struct dsdb_attribute *sa;
+
+ sa = dsdb_attribute_by_attributeID_id(schema, in->attid);
+ if (!sa) {
+ return WERR_FOOBAR;
+ }
+
+ return sa->syntax->drsuapi_to_ldb(schema, sa, in, mem_ctx, out);
+}
+
+WERROR dsdb_attribute_ldb_to_drsuapi(const struct dsdb_schema *schema,
+ const struct ldb_message_element *in,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaAttribute *out)
+{
+ const struct dsdb_attribute *sa;
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema, in->name);
+ if (!sa) {
+ return WERR_FOOBAR;
+ }
+
+ return sa->syntax->ldb_to_drsuapi(schema, sa, in, mem_ctx, out);
+}