diff options
Diffstat (limited to 'source4/dsdb')
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 = ⪙ + + 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); +} |