diff options
Diffstat (limited to 'source4/dsdb')
53 files changed, 29599 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..dceb41be67 --- /dev/null +++ b/source4/dsdb/common/flag_mapping.c @@ -0,0 +1,145 @@ +/* + 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" +#include "lib/ldb/include/ldb.h" +#include "dsdb/common/proto.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..5c20149384 --- /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 "../lib/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 tevent_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..19eb3433a9 --- /dev/null +++ b/source4/dsdb/common/util.c @@ -0,0 +1,2036 @@ +/* + 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/events.h" +#include "ldb.h" +#include "ldb_errors.h" +#include "../lib/util/util_ldb.h" +#include "../lib/crypto/crypto.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 loadparm_context *lp_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) { + /* Ensure that if we have turned off LM + * authentication, that we never use the LM hash, even + * if we store it */ + if (lp_lanman_auth(lp_ctx)) { + 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]; + } + } else { + *lm_pwd = NULL; + } + } + 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; +} + +struct lsa_BinaryString samdb_result_parameters(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char *attr) +{ + struct lsa_BinaryString s; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + + ZERO_STRUCT(s); + + if (!val) { + return s; + } + + s.array = talloc_array(mem_ctx, uint16_t, val->length/2); + if (!s.array) { + return s; + } + s.length = s.size = val->length/2; + memcpy(s.array, val->data, val->length); + + return s; +} + +/* 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 parameters element to a message +*/ +int samdb_msg_add_parameters(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, struct lsa_BinaryString *parameters) +{ + struct ldb_val val; + val.length = parameters->length * 2; + val.data = (uint8_t *)parameters->array; + 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(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, tmp_ctx, &root_res, ldb_dn_new(tmp_ctx, ldb, ""), LDB_SCOPE_BASE, root_attrs, NULL); + if (ret) { + DEBUG(1,("Searching for dsServiceName in rootDSE failed: %s\n", + ldb_errstring(ldb))); + goto failed; + } + + 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, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL); + if (ret) { + goto failed; + } + + 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, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL); + if (ret) { + goto failed; + } + + 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, tmp_ctx, &dom_res, ldb_get_default_basedn(ldb), LDB_SCOPE_BASE, dom_attrs, NULL); + 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; + } + 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, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL); + if (ret) { + talloc_free(tmp_ctx); + return false; + } + if (res->count != 1) { + talloc_free(tmp_ctx); + return false; + } + + options = ldb_msg_find_attr_as_int(res->msgs[0], "options", 0); + 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, local_ctx, &res, sdn, LDB_SCOPE_BASE, attrs, + "(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain))"); + if (ret == LDB_SUCCESS) { + 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 DATA_BLOB *new_password, + 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_password) { + char *new_pass; + + /* check the various password restrictions */ + if (restrictions && minPwdLength > utf16_len_n(new_password->data, new_password->length) / 2) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_TOO_SHORT; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* Create the NT hash */ + mdfour(local_ntNewHash.hash, new_password->data, new_password->length); + + ntNewHash = &local_ntNewHash; + + /* Only check complexity if we can convert it at all. Assuming unconvertable passwords are 'strong' */ + if (convert_string_talloc_convenience(mem_ctx, lp_iconv_convenience(ldb_get_opaque(ctx, "loadparm")), + CH_UTF16, CH_UNIX, + new_password->data, new_password->length, + (void **)&new_pass, NULL, false)) { + + + /* 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 lm hashes (for checking history - case insenitivly!) */ + if (E_deshash(new_pass, local_lmNewHash.hash)) { + lmNewHash = &local_lmNewHash; + } + + } + } + + if (restrictions && 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_password) { + /* if we know the cleartext UTF16 password, then set it. + * Modules in ldb will set all the appropriate + * hashes */ + CHECK_RET(ldb_msg_add_value(mod, "clearTextPassword", new_password, NULL)); + } 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, "clearTextPassword")); + + 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 DATA_BLOB *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 = (const char **)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(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(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..2ca4e4ca6d --- /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_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 = service +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..c292c6db74 --- /dev/null +++ b/source4/dsdb/repl/drepl_out_helpers.c @@ -0,0 +1,444 @@ +/* + 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->out.level_out = talloc(r, int32_t); + if (composite_nomem(r->out.level_out, c)) return; + r->in.req = talloc(r, union drsuapi_DsGetNCChangesRequest); + if (composite_nomem(r->in.req, c)) return; + r->out.ctr = talloc(r, union drsuapi_DsGetNCChangesCtr); + if (composite_nomem(r->out.ctr, 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_out == 1) { + ctr_level = 1; + ctr1 = &r->out.ctr->ctr1; + } else if (*r->out.level_out == 2 && + r->out.ctr->ctr2.mszip1.ts) { + ctr_level = 1; + ctr1 = &r->out.ctr->ctr2.mszip1.ts->ctr1; + } else if (*r->out.level_out == 6) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr6; + } else if (*r->out.level_out == 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_out == 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..c66c5bbd19 --- /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..f36b735d32 --- /dev/null +++ b/source4/dsdb/repl/drepl_partitions.c @@ -0,0 +1,270 @@ +/* + 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, s, &r, basedn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return WERR_FOOBAR; + } else if (r->count != 1) { + talloc_free(r); + return WERR_FOOBAR; + } + + 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, mem_ctx, &r, p->dn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + return WERR_FOOBAR; + } else if (r->count != 1) { + talloc_free(r); + return WERR_FOOBAR; + } + + 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..b88d2cee1e --- /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 "lib/events/events.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "smbd/service.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 tevent_context *ev, struct tevent_timer *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 tevent_timer *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..152bbec4d5 --- /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_data = 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..a9eea30719 --- /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 tevent_timer *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..560f75da7a --- /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(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..9bcb007358 --- /dev/null +++ b/source4/dsdb/samdb/cracknames.c @@ -0,0 +1,1326 @@ +/* + 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 "../lib/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, tmp_ctx, &res, service_dn, LDB_SCOPE_BASE, + directory_attrs, "(objectClass=nTDSService)"); + + 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; + } + + 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(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 = NULL; + + 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_UNKNOWN: + { + int i; + enum drsuapi_DsNameFormat formats[] = { + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, DRSUAPI_DS_NAME_FORMAT_CANONICAL, + DRSUAPI_DS_NAME_FORMAT_GUID, DRSUAPI_DS_NAME_FORMAT_DISPLAY, + DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX + }; + WERROR werr; + for (i=0; i < ARRAY_SIZE(formats); i++) { + werr = DsCrackNameOneName(sam_ctx, mem_ctx, format_flags, formats[i], format_desired, name, info1); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + if (info1->status != DRSUAPI_DS_NAME_STATUS_NOT_FOUND) { + return werr; + } + } + return werr; + } + + 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 = 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; + } + + 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 = 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; + } + + 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(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(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, + NULL); + 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; + } + + 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(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(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(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 tevent_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 tevent_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_UNKNOWN; + + /* 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; + } else { + return NT_STATUS_NO_SUCH_USER; + } + + 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..a04f5ebfa6 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/anr.c @@ -0,0 +1,370 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007 + Copyright (C) Simo Sorce <idra@samba.org> 2008 + 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_module.h" +#include "dsdb/samdb/samdb.h" + +/** + * Make a and 'and' or 'or' tree from the two supplied elements + */ +static 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_context *ldb; + struct ldb_parse_tree *list; + + ldb = ldb_module_get_ctx(module); + + list = talloc(mem_ctx, struct ldb_parse_tree); + if (list == NULL){ + ldb_oom(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(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 + */ +static 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_context *ldb; + struct ldb_parse_tree *match_tree; + + ldb = ldb_module_get_ctx(module); + + 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(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; + struct ldb_request *req; +}; + +/** + * Given the match for an 'ambigious name resolution' query, create a + * parse tree with an 'or' of all the anr attributes in the schema. + */ + +/** + * Callback function to do the heavy lifting for the parse tree walker + */ +static int anr_replace_value(struct anr_context *ac, + TALLOC_CTX *mem_ctx, + const struct ldb_val *match, + struct ldb_parse_tree **ntree) +{ + struct ldb_parse_tree *tree = NULL; + struct ldb_module *module = ac->module; + struct ldb_parse_tree *match_tree; + struct dsdb_attribute *cur; + const struct dsdb_schema *schema; + struct ldb_context *ldb; + uint8_t *p; + enum ldb_parse_op op; + + ldb = ldb_module_get_ctx(module); + + schema = dsdb_get_schema(ldb); + if (!schema) { + ldb_asprintf_errstring(ldb, "no schema with which to construct anr filter"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->found_anr = true; + + if (match->length > 1 && match->data[0] == '=') { + DATA_BLOB *match2 = talloc(mem_ctx, DATA_BLOB); + *match2 = data_blob_const(match->data+1, match->length - 1); + if (match2 == NULL){ + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + 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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + } 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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + *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, ac, LDB_OP_AND, match_tree_1, match_tree_2); + if (first_split_filter == NULL){ + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + 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, ac, LDB_OP_AND, match_tree_1, match_tree_2); + if (second_split_filter == NULL){ + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + split_filters = make_parse_list(module, mem_ctx, LDB_OP_OR, + first_split_filter, second_split_filter); + if (split_filters == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + 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; + } + } + *ntree = tree; + return LDB_SUCCESS; +} + +/* + replace any occurances of an attribute with a new, generated attribute tree +*/ +static int anr_replace_subtrees(struct anr_context *ac, + struct ldb_parse_tree *tree, + const char *attr, + struct ldb_parse_tree **ntree) +{ + int ret; + int i; + + switch (tree->operation) { + case LDB_OP_AND: + case LDB_OP_OR: + for (i=0;i<tree->u.list.num_elements;i++) { + ret = anr_replace_subtrees(ac, tree->u.list.elements[i], + attr, &tree->u.list.elements[i]); + if (ret != LDB_SUCCESS) { + return ret; + } + *ntree = tree; + } + break; + case LDB_OP_NOT: + ret = anr_replace_subtrees(ac, tree->u.isnot.child, attr, &tree->u.isnot.child); + if (ret != LDB_SUCCESS) { + return ret; + } + *ntree = tree; + break; + case LDB_OP_EQUALITY: + if (ldb_attr_cmp(tree->u.equality.attr, attr) == 0) { + ret = anr_replace_value(ac, tree, &tree->u.equality.value, ntree); + if (ret != LDB_SUCCESS) { + return ret; + } + } + 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) { + ret = anr_replace_value(ac, tree, tree->u.substring.chunks[0], ntree); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + break; + default: + break; + } + + return LDB_SUCCESS; +} + +static int anr_search_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct anr_context *ac; + + ac = talloc_get_type(req->context, struct anr_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + return ldb_module_send_entry(ac->req, ares->message, ares->controls); + + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral(ac->req, ares->referral); + + case LDB_REPLY_DONE: + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); + + } + return LDB_SUCCESS; +} + +/* search */ +static int anr_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_parse_tree *anr_tree; + struct ldb_request *down_req; + struct anr_context *ac; + int ret; + + ldb = ldb_module_get_ctx(module); + + ac = talloc(req, struct anr_context); + if (!ac) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->module = module; + ac->req = req; + ac->found_anr = false; + +#if 0 + printf("oldanr : %s\n", ldb_filter_from_tree (0, req->op.search.tree)); +#endif + + ret = anr_replace_subtrees(ac, req->op.search.tree, "anr", &anr_tree); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->found_anr) { + talloc_free(ac); + return ldb_next_request(module, req); + } + + ret = ldb_build_search_req_ex(&down_req, + ldb, ac, + req->op.search.base, + req->op.search.scope, + anr_tree, + req->op.search.attrs, + req->controls, + ac, anr_search_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + talloc_steal(down_req, anr_tree); + + return ldb_next_request(module, down_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..583d1dcf04 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/config.mk @@ -0,0 +1,325 @@ +################################################ +# Start MODULE ldb_objectguid +[MODULE::ldb_objectguid] +SUBSYSTEM = LIBLDB +PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS LIBNDR +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_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 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 LIBLDB 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 LIBLDB LIBNDR +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_in +[MODULE::ldb_extended_dn_in] +SUBSYSTEM = LIBLDB +PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS +INIT_FUNCTION = LDB_MODULE(extended_dn_in) +# End MODULE ldb_extended_dn_in +################################################ + +ldb_extended_dn_in_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/extended_dn_in.o + +################################################ +# Start MODULE ldb_extended_dn_out +[MODULE::ldb_extended_dn_out] +SUBSYSTEM = LIBLDB +PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS +INIT_FUNCTION = LDB_MODULE(extended_dn_out_ldb),LDB_MODULE(extended_dn_out_dereference) +ENABLE = YES +ALIASES = extended_dn_out_ldb extended_dn_out_dereference +# End MODULE ldb_extended_dn_out +################################################ + +ldb_extended_dn_out_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/extended_dn_out.o + +################################################ +# Start MODULE ldb_extended_dn_store +[MODULE::ldb_extended_dn_store] +SUBSYSTEM = LIBLDB +PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS +INIT_FUNCTION = LDB_MODULE(extended_dn_store) +# End MODULE ldb_extended_dn_store +################################################ + +ldb_extended_dn_store_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/extended_dn_store.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_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_in.c b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c new file mode 100644 index 0000000000..c3db4fa588 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c @@ -0,0 +1,394 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2005-2008 + Copyright (C) Andrew 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 extended dn control module + * + * Description: this module interprets DNs of the form <SID=S-1-2-4456> into normal DNs. + * + * Authors: Simo Sorce + * Andrew Bartlett + */ + +#include "includes.h" +#include "ldb/include/ldb.h" +#include "ldb/include/ldb_errors.h" +#include "ldb/include/ldb_private.h" + +/* search */ +struct extended_search_context { + struct ldb_module *module; + struct ldb_request *req; + struct ldb_dn *basedn; + char *wellknown_object; + int extended_type; +}; + +/* An extra layer of indirection because LDB does not allow the original request to be altered */ + +static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret = LDB_ERR_OPERATIONS_ERROR; + struct extended_search_context *ac; + ac = talloc_get_type(req->context, struct extended_search_context); + + if (ares->error != LDB_SUCCESS) { + ret = ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } else { + switch (ares->type) { + case LDB_REPLY_ENTRY: + + ret = ldb_module_send_entry(ac->req, ares->message, ares->controls); + break; + case LDB_REPLY_REFERRAL: + + ret = ldb_module_send_referral(ac->req, ares->referral); + break; + case LDB_REPLY_DONE: + + ret = ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + break; + } + } + return ret; +} + +static int extended_base_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct extended_search_context *ac; + struct ldb_request *down_req; + struct ldb_message_element *el; + int ret; + size_t i; + size_t wkn_len = 0; + char *valstr = NULL; + const char *found = NULL; + + ac = talloc_get_type(req->context, struct extended_search_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (!ac->wellknown_object) { + ac->basedn = talloc_steal(ac, ares->message->dn); + break; + } + + wkn_len = strlen(ac->wellknown_object); + + el = ldb_msg_find_element(ares->message, "wellKnownObjects"); + if (!el) { + ac->basedn = NULL; + break; + } + + for (i=0; i < el->num_values; i++) { + valstr = talloc_strndup(ac, + (const char *)el->values[i].data, + el->values[i].length); + if (!valstr) { + ldb_oom(ac->module->ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (strncasecmp(valstr, ac->wellknown_object, wkn_len) != 0) { + talloc_free(valstr); + continue; + } + + found = &valstr[wkn_len]; + break; + } + + if (!found) { + break; + } + + ac->basedn = ldb_dn_new(ac, ac->module->ldb, found); + talloc_free(valstr); + if (!ac->basedn) { + ldb_oom(ac->module->ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + break; + + case LDB_REPLY_REFERRAL: + break; + + case LDB_REPLY_DONE: + + if (!ac->basedn) { + const char *str = talloc_asprintf(req, "Base-DN '%s' not found", + ldb_dn_get_linearized(ac->req->op.search.base)); + ldb_set_errstring(ac->module->ldb, str); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_NO_SUCH_OBJECT); + } + + switch (ac->req->operation) { + case LDB_SEARCH: + ret = ldb_build_search_req_ex(&down_req, + ac->module->ldb, ac->req, + ac->basedn, + ac->req->op.search.scope, + ac->req->op.search.tree, + ac->req->op.search.attrs, + ac->req->controls, + ac, extended_final_callback, + ac->req); + break; + case LDB_ADD: + { + struct ldb_message *add_msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message); + if (!add_msg) { + ldb_oom(ac->module->ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + add_msg->dn = ac->basedn; + + ret = ldb_build_add_req(&down_req, + ac->module->ldb, ac->req, + add_msg, + ac->req->controls, + ac, extended_final_callback, + ac->req); + break; + } + case LDB_MODIFY: + { + struct ldb_message *mod_msg = ldb_msg_copy_shallow(ac, ac->req->op.mod.message); + if (!mod_msg) { + ldb_oom(ac->module->ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + mod_msg->dn = ac->basedn; + + ret = ldb_build_mod_req(&down_req, + ac->module->ldb, ac->req, + mod_msg, + ac->req->controls, + ac, extended_final_callback, + ac->req); + break; + } + case LDB_DELETE: + ret = ldb_build_del_req(&down_req, + ac->module->ldb, ac->req, + ac->basedn, + ac->req->controls, + ac, extended_final_callback, + ac->req); + break; + case LDB_RENAME: + ret = ldb_build_rename_req(&down_req, + ac->module->ldb, ac->req, + ac->basedn, + ac->req->op.rename.newdn, + ac->req->controls, + ac, extended_final_callback, + ac->req); + break; + default: + return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return ldb_next_request(ac->module, down_req); + } + talloc_free(ares); + return LDB_SUCCESS; +} + +static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn) +{ + struct extended_search_context *ac; + struct ldb_request *down_req; + int ret; + struct ldb_dn *base_dn = NULL; + enum ldb_scope base_dn_scope = LDB_SCOPE_BASE; + const char *base_dn_filter = NULL; + const char * const *base_dn_attrs = NULL; + char *wellknown_object = NULL; + static const char *no_attr[] = { + NULL + }; + static const char *wkattr[] = { + "wellKnownObjects", + NULL + }; + + if (!ldb_dn_has_extended(dn)) { + /* Move along there isn't anything to see here */ + return ldb_next_request(module, req); + } else { + /* It looks like we need to map the DN */ + const struct ldb_val *sid_val, *guid_val, *wkguid_val; + + sid_val = ldb_dn_get_extended_component(dn, "SID"); + guid_val = ldb_dn_get_extended_component(dn, "GUID"); + wkguid_val = ldb_dn_get_extended_component(dn, "WKGUID"); + + if (sid_val) { + /* TODO: do a search over all partitions */ + base_dn = ldb_get_default_basedn(module->ldb); + base_dn_filter = talloc_asprintf(req, "(objectSid=%s)", + ldb_binary_encode(req, *sid_val)); + if (!base_dn_filter) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + base_dn_scope = LDB_SCOPE_SUBTREE; + base_dn_attrs = no_attr; + + } else if (guid_val) { + + /* TODO: do a search over all partitions */ + base_dn = ldb_get_default_basedn(module->ldb); + base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)", + ldb_binary_encode(req, *guid_val)); + if (!base_dn_filter) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + base_dn_scope = LDB_SCOPE_SUBTREE; + base_dn_attrs = no_attr; + + + } else if (wkguid_val) { + char *wkguid_dup; + char *tail_str; + char *p; + + wkguid_dup = talloc_strndup(req, (char *)wkguid_val->data, wkguid_val->length); + + p = strchr(wkguid_dup, ','); + if (!p) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + + p[0] = '\0'; + p++; + + wellknown_object = talloc_asprintf(req, "B:32:%s:", wkguid_dup); + if (!wellknown_object) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + tail_str = p; + + base_dn = ldb_dn_new(req, module->ldb, tail_str); + talloc_free(wkguid_dup); + if (!base_dn) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + base_dn_filter = talloc_strdup(req, "(objectClass=*)"); + if (!base_dn_filter) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + base_dn_scope = LDB_SCOPE_BASE; + base_dn_attrs = wkattr; + } else { + return LDB_ERR_INVALID_DN_SYNTAX; + } + + ac = talloc_zero(req, struct extended_search_context); + if (ac == NULL) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->module = module; + ac->req = req; + ac->basedn = NULL; /* Filled in if the search finds the DN by SID/GUID etc */ + ac->wellknown_object = wellknown_object; + + /* If the base DN was an extended DN (perhaps a well known + * GUID) then search for that, so we can proceed with the original operation */ + + ret = ldb_build_search_req(&down_req, + module->ldb, ac, + base_dn, + base_dn_scope, + base_dn_filter, + base_dn_attrs, + NULL, + ac, extended_base_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* perform the search */ + return ldb_next_request(module, down_req); + } +} + +static int extended_dn_in_search(struct ldb_module *module, struct ldb_request *req) +{ + return extended_dn_in_fix(module, req, req->op.search.base); +} + +static int extended_dn_in_modify(struct ldb_module *module, struct ldb_request *req) +{ + return extended_dn_in_fix(module, req, req->op.mod.message->dn); +} + +static int extended_dn_in_del(struct ldb_module *module, struct ldb_request *req) +{ + return extended_dn_in_fix(module, req, req->op.del.dn); +} + +static int extended_dn_in_rename(struct ldb_module *module, struct ldb_request *req) +{ + return extended_dn_in_fix(module, req, req->op.rename.olddn); +} + +_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_in_module_ops = { + .name = "extended_dn_in", + .search = extended_dn_in_search, + .modify = extended_dn_in_modify, + .del = extended_dn_in_del, + .rename = extended_dn_in_rename, +}; diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_out.c b/source4/dsdb/samdb/ldb_modules/extended_dn_out.c new file mode 100644 index 0000000000..f8526faf3b --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/extended_dn_out.c @@ -0,0 +1,655 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2005-2008 + Copyright (C) Andrew 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 extended dn control module + * + * Description: this module builds a special dn for returned search + * results, and fixes some other aspects of the result (returned case issues) + * values. + * + * Authors: Simo Sorce + * Andrew Bartlett + */ + +#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 "librpc/ndr/libndr.h" +#include "dsdb/samdb/samdb.h" + +struct extended_dn_out_private { + bool dereference; + bool normalise; + struct dsdb_openldap_dereference_control *dereference_control; +}; + +static bool is_attr_in_list(const char * const * attrs, const char *attr) +{ + int i; + + for (i = 0; attrs[i]; i++) { + if (ldb_attr_cmp(attrs[i], attr) == 0) + return true; + } + + return false; +} + +static char **copy_attrs(void *mem_ctx, const char * const * attrs) +{ + char **nattrs; + int i, num; + + for (num = 0; attrs[num]; num++); + + nattrs = talloc_array(mem_ctx, char *, num + 1); + if (!nattrs) return NULL; + + for(i = 0; i < num; i++) { + nattrs[i] = talloc_strdup(nattrs, attrs[i]); + if (!nattrs[i]) { + talloc_free(nattrs); + return NULL; + } + } + nattrs[i] = NULL; + + return nattrs; +} + +static bool add_attrs(void *mem_ctx, char ***attrs, const char *attr) +{ + char **nattrs; + int num; + + for (num = 0; (*attrs)[num]; num++); + + nattrs = talloc_realloc(mem_ctx, *attrs, char *, num + 2); + if (!nattrs) return false; + + *attrs = nattrs; + + nattrs[num] = talloc_strdup(nattrs, attr); + if (!nattrs[num]) return false; + + nattrs[num + 1] = NULL; + + return true; +} + +/* Fix the DN so that the relative attribute names are in upper case so that the DN: + cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com becomes + CN=Adminstrator,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; +} + +/* Inject the extended DN components, so the DN cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com becomes + <GUID=541203ae-f7d6-47ef-8390-bfcf019f9583>;<SID=S-1-5-21-4177067393-1453636373-93818737-500>;cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com */ + +static int inject_extended_dn_out(struct ldb_reply *ares, + struct ldb_context *ldb, + int type, + bool remove_guid, + bool remove_sid) +{ + int ret; + const DATA_BLOB *guid_blob; + const DATA_BLOB *sid_blob; + + guid_blob = ldb_msg_find_ldb_val(ares->message, "objectGUID"); + sid_blob = ldb_msg_find_ldb_val(ares->message, "objectSID"); + + if (!guid_blob) { + ldb_set_errstring(ldb, "Did not find objectGUID to inject into extended DN"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_dn_set_extended_component(ares->message->dn, "GUID", guid_blob); + if (ret != LDB_SUCCESS) { + return ret; + } + if (sid_blob) { + ret = ldb_dn_set_extended_component(ares->message->dn, "SID", sid_blob); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (remove_guid) { + ldb_msg_remove_attr(ares->message, "objectGUID"); + } + + if (sid_blob && remove_sid) { + ldb_msg_remove_attr(ares->message, "objectSID"); + } + + return LDB_SUCCESS; +} + +static int handle_dereference(struct ldb_dn *dn, + struct dsdb_openldap_dereference_result **dereference_attrs, + const char *attr, const DATA_BLOB *val) +{ + const struct ldb_val *entryUUIDblob, *sid_blob; + struct ldb_message fake_msg; /* easier to use routines that expect an ldb_message */ + int j; + + fake_msg.num_elements = 0; + + /* Look for this attribute in the returned control */ + for (j = 0; dereference_attrs && dereference_attrs[j]; j++) { + struct ldb_val source_dn = data_blob_string_const(dereference_attrs[j]->dereferenced_dn); + if (ldb_attr_cmp(dereference_attrs[j]->source_attribute, attr) == 0 + && data_blob_cmp(&source_dn, val) == 0) { + fake_msg.num_elements = dereference_attrs[j]->num_attributes; + fake_msg.elements = dereference_attrs[j]->attributes; + break; + } + } + if (!fake_msg.num_elements) { + return LDB_SUCCESS; + } + /* Look for an OpenLDAP entryUUID */ + + entryUUIDblob = ldb_msg_find_ldb_val(&fake_msg, "entryUUID"); + if (entryUUIDblob) { + NTSTATUS status; + enum ndr_err_code ndr_err; + + struct ldb_val guid_blob; + struct GUID guid; + + status = GUID_from_data_blob(entryUUIDblob, &guid); + + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + ndr_err = ndr_push_struct_blob(&guid_blob, NULL, NULL, &guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + + ldb_dn_set_extended_component(dn, "GUID", &guid_blob); + } + + sid_blob = ldb_msg_find_ldb_val(&fake_msg, "objectSID"); + + /* Look for the objectSID */ + if (sid_blob) { + ldb_dn_set_extended_component(dn, "SID", sid_blob); + } + return LDB_SUCCESS; +} + +/* search */ +struct extended_search_context { + struct ldb_module *module; + const struct dsdb_schema *schema; + struct ldb_request *req; + bool inject; + bool remove_guid; + bool remove_sid; + int extended_type; +}; + +static int extended_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct extended_search_context *ac; + struct ldb_control *control; + struct dsdb_openldap_dereference_result_control *dereference_control = NULL; + int ret, i, j; + struct ldb_message *msg = ares->message; + struct extended_dn_out_private *p; + + ac = talloc_get_type(req->context, struct extended_search_context); + p = talloc_get_type(ac->module->private_data, struct extended_dn_out_private); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral(ac->req, ares->referral); + + case LDB_REPLY_DONE: + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); + case LDB_REPLY_ENTRY: + break; + } + + if (p && p->normalise) { + ret = fix_dn(ares->message->dn); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + if (ac->inject) { + /* for each record returned post-process to add any derived + attributes that have been asked for */ + ret = inject_extended_dn_out(ares, ac->module->ldb, + ac->extended_type, ac->remove_guid, + ac->remove_sid); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + if ((p && p->normalise) || ac->inject) { + const struct ldb_val *val = ldb_msg_find_ldb_val(ares->message, "distinguishedName"); + if (val) { + ldb_msg_remove_attr(ares->message, "distinguishedName"); + if (ac->inject) { + ret = ldb_msg_add_steal_string(ares->message, "distinguishedName", + ldb_dn_get_extended_linearized(ares->message, ares->message->dn, ac->extended_type)); + } else { + ret = ldb_msg_add_string(ares->message, "distinguishedName", + ldb_dn_get_linearized(ares->message->dn)); + } + if (ret != LDB_SUCCESS) { + ldb_oom(ac->module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + } + } + + if (p && p->dereference) { + control = ldb_reply_get_control(ares, DSDB_OPENLDAP_DEREFERENCE_CONTROL); + + if (control && control->data) { + dereference_control = talloc_get_type(control->data, struct dsdb_openldap_dereference_result_control); + } + } + + /* Walk the retruned elements (but only if we have a schema to interpret the list with) */ + for (i = 0; ac->schema && i < msg->num_elements; i++) { + const struct dsdb_attribute *attribute; + attribute = dsdb_attribute_by_lDAPDisplayName(ac->schema, msg->elements[i].name); + if (!attribute) { + continue; + } + + if (p->normalise) { + /* If we are also in 'normalise' mode, then + * fix the attribute names to be in the + * correct case */ + msg->elements[i].name = talloc_strdup(msg->elements, attribute->lDAPDisplayName); + if (!msg->elements[i].name) { + ldb_oom(ac->module->ldb); + return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + } + + /* distinguishedName has been dealt with above */ + if (ldb_attr_cmp(msg->elements[i].name, "distinguishedName") == 0) { + continue; + } + + /* Look to see if this attributeSyntax is a DN */ + if (strcmp(attribute->attributeSyntax_oid, "2.5.5.1") != 0) { + continue; + } + + for (j = 0; j < msg->elements[i].num_values; j++) { + const char *dn_str; + struct ldb_dn *dn = ldb_dn_from_ldb_val(ac, ac->module->ldb, &msg->elements[i].values[j]); + if (!dn || !ldb_dn_validate(dn)) { + return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_INVALID_DN_SYNTAX); + } + + if (p->normalise) { + ret = fix_dn(dn); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + /* If we are running in dereference mode (such + * as against OpenLDAP) then the DN in the msg + * above does not contain the extended values, + * and we need to look in the dereference + * result */ + + /* Look for this value in the attribute */ + + if (dereference_control) { + ret = handle_dereference(dn, + dereference_control->attributes, + msg->elements[i].name, + &msg->elements[i].values[j]); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + if (!ac->inject) { + dn_str = talloc_steal(msg->elements[i].values, + ldb_dn_get_linearized(dn)); + } else { + dn_str = talloc_steal(msg->elements[i].values, + ldb_dn_get_extended_linearized(msg->elements[i].values, + dn, ac->extended_type)); + } + if (!dn_str) { + ldb_oom(ac->module->ldb); + return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + msg->elements[i].values[j] = data_blob_string_const(dn_str); + talloc_free(dn); + } + } + return ldb_module_send_entry(ac->req, msg, ares->controls); +} + + +static int extended_dn_out_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_control *control; + struct ldb_control *storage_format_control; + struct ldb_extended_dn_control *extended_ctrl = NULL; + struct ldb_control **saved_controls; + struct extended_search_context *ac; + struct ldb_request *down_req; + char **new_attrs; + const char * const *const_attrs; + int ret; + + struct extended_dn_out_private *p = talloc_get_type(module->private_data, struct extended_dn_out_private); + + /* check if there's an extended dn control */ + control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID); + if (control && control->data) { + extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control); + if (!extended_ctrl) { + return LDB_ERR_PROTOCOL_ERROR; + } + } + + /* Look to see if, as we are in 'store DN+GUID+SID' mode, the + * client is after the storage format (to fill in linked + * attributes) */ + storage_format_control = ldb_request_get_control(req, DSDB_CONTROL_DN_STORAGE_FORMAT_OID); + if (!control && storage_format_control && storage_format_control->data) { + extended_ctrl = talloc_get_type(storage_format_control->data, struct ldb_extended_dn_control); + if (!extended_ctrl) { + ldb_set_errstring(module->ldb, "extended_dn_out: extended_ctrl was of the wrong data type"); + return LDB_ERR_PROTOCOL_ERROR; + } + } + + ac = talloc_zero(req, struct extended_search_context); + if (ac == NULL) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->module = module; + ac->schema = dsdb_get_schema(module->ldb); + ac->req = req; + ac->inject = false; + ac->remove_guid = false; + ac->remove_sid = false; + + const_attrs = req->op.search.attrs; + + /* We only need to do special processing if we were asked for + * the extended DN, or we are 'store DN+GUID+SID' + * (!dereference) mode. (This is the normal mode for LDB on + * tdb). */ + if (control || (storage_format_control && p && !p->dereference)) { + ac->inject = true; + if (extended_ctrl) { + ac->extended_type = extended_ctrl->type; + } else { + ac->extended_type = 0; + } + + /* check if attrs only is specified, in that case check wether we need to modify them */ + if (req->op.search.attrs && !is_attr_in_list(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(ac, req->op.search.attrs); + if (new_attrs == NULL) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (ac->remove_guid) { + if (!add_attrs(ac, &new_attrs, "objectGUID")) + return LDB_ERR_OPERATIONS_ERROR; + } + if (ac->remove_sid) { + if (!add_attrs(ac, &new_attrs, "objectSID")) + return LDB_ERR_OPERATIONS_ERROR; + } + const_attrs = (const char * const *)new_attrs; + } + } + } + + ret = ldb_build_search_req_ex(&down_req, + module->ldb, ac, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + const_attrs, + req->controls, + ac, extended_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* Remove extended DN and storage format controls */ + + if (control) { + /* 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; + } + } + + if (storage_format_control) { + /* 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(storage_format_control, down_req, &saved_controls)) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + + /* Add in dereference control, if we were asked to, we are + * using the 'dereference' mode (such as with an OpenLDAP + * backend) and have the control prepared */ + if (control && p && p->dereference && p->dereference_control) { + ret = ldb_request_add_control(down_req, + DSDB_OPENLDAP_DEREFERENCE_CONTROL, + false, p->dereference_control); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + /* perform the search */ + return ldb_next_request(module, down_req); +} + +static int extended_dn_out_ldb_init(struct ldb_module *module) +{ + int ret; + + struct extended_dn_out_private *p = talloc(module, struct extended_dn_out_private); + + module->private_data = p; + + if (!p) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + p->dereference = false; + p->normalise = false; + + ret = ldb_mod_register_control(module, LDB_CONTROL_EXTENDED_DN_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(module->ldb, LDB_DEBUG_ERROR, + "extended_dn_out: Unable to register control with rootdse!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + return ldb_next_init(module); +} + +static int extended_dn_out_dereference_init(struct ldb_module *module) +{ + int ret, i = 0; + struct extended_dn_out_private *p; + struct dsdb_openldap_dereference_control *dereference_control; + struct dsdb_attribute *cur; + + struct dsdb_schema *schema; + + module->private_data = p = talloc_zero(module, struct extended_dn_out_private); + + if (!p) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + p->dereference = true; + + /* At the moment, servers that need dereference also need the + * DN and attribute names to be normalised */ + p->normalise = true; + + ret = ldb_mod_register_control(module, LDB_CONTROL_EXTENDED_DN_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(module->ldb, LDB_DEBUG_ERROR, + "extended_dn_out: Unable to register control with rootdse!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_next_init(module); + + if (ret != LDB_SUCCESS) { + return ret; + } + + schema = dsdb_get_schema(module->ldb); + if (!schema) { + /* No schema on this DB (yet) */ + return LDB_SUCCESS; + } + + p->dereference_control = dereference_control + = talloc_zero(p, struct dsdb_openldap_dereference_control); + + if (!p->dereference_control) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + for (cur = schema->attributes; cur; cur = cur->next) { + static const char *attrs[] = { + "entryUUID", + "objectSID", + NULL + }; + + if (strcmp(cur->syntax->attributeSyntax_oid, "2.5.5.1") != 0) { + continue; + } + dereference_control->dereference + = talloc_realloc(p, dereference_control->dereference, + struct dsdb_openldap_dereference *, i + 2); + if (!dereference_control) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + dereference_control->dereference[i] = talloc(dereference_control->dereference, + struct dsdb_openldap_dereference); + if (!dereference_control->dereference[i]) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + dereference_control->dereference[i]->source_attribute = cur->lDAPDisplayName; + dereference_control->dereference[i]->dereference_attribute = attrs; + i++; + dereference_control->dereference[i] = NULL; + } + return LDB_SUCCESS; +} + +_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_out_ldb_module_ops = { + .name = "extended_dn_out_ldb", + .search = extended_dn_out_search, + .init_context = extended_dn_out_ldb_init, +}; + + +_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_out_dereference_module_ops = { + .name = "extended_dn_out_dereference", + .search = extended_dn_out_search, + .init_context = extended_dn_out_dereference_init, +}; diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_store.c b/source4/dsdb/samdb/ldb_modules/extended_dn_store.c new file mode 100644 index 0000000000..4f4e9d0fd7 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/extended_dn_store.c @@ -0,0 +1,431 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2005-2008 + Copyright (C) Andrew 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 extended dn control module + * + * Description: this module builds a special dn for returned search + * results nad creates the special DN in the backend store for new + * values. + * + * This also has the curious result that we convert <SID=S-1-2-345> + * in an attribute value into a normal DN for the rest of the stack + * to process + * + * Authors: Simo Sorce + * Andrew Bartlett + */ + +#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> + +struct extended_dn_replace_list { + struct extended_dn_replace_list *next; + struct ldb_dn *dn; + TALLOC_CTX *mem_ctx; + struct ldb_val *replace_dn; + struct extended_dn_context *ac; + struct ldb_request *search_req; +}; + + +struct extended_dn_context { + const struct dsdb_schema *schema; + struct ldb_module *module; + struct ldb_request *req; + struct ldb_request *new_req; + + struct extended_dn_replace_list *ops; + struct extended_dn_replace_list *cur; +}; + + +static struct extended_dn_context *extended_dn_context_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct extended_dn_context *ac; + + ac = talloc_zero(req, struct extended_dn_context); + if (ac == NULL) { + ldb_oom(module->ldb); + return NULL; + } + + ac->schema = dsdb_get_schema(module->ldb); + ac->module = module; + ac->req = req; + + return ac; +} + +/* An extra layer of indirection because LDB does not allow the original request to be altered */ + +static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret = LDB_ERR_OPERATIONS_ERROR; + struct extended_dn_context *ac; + ac = talloc_get_type(req->context, struct extended_dn_context); + + if (ares->error != LDB_SUCCESS) { + ret = ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } else { + switch (ares->type) { + case LDB_REPLY_ENTRY: + + ret = ldb_module_send_entry(ac->req, ares->message, ares->controls); + break; + case LDB_REPLY_REFERRAL: + + ret = ldb_module_send_referral(ac->req, ares->referral); + break; + case LDB_REPLY_DONE: + + ret = ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + break; + } + } + return ret; +} + +static int extended_replace_dn(struct ldb_request *req, struct ldb_reply *ares) +{ + struct extended_dn_replace_list *os = talloc_get_type(req->context, + struct extended_dn_replace_list); + + if (!ares) { + return ldb_module_done(os->ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error == LDB_ERR_NO_SUCH_OBJECT) { + /* Don't worry too much about dangling references */ + + ldb_reset_err_string(os->ac->module->ldb); + if (os->next) { + struct extended_dn_replace_list *next; + + next = os->next; + + talloc_free(os); + + os = next; + return ldb_next_request(os->ac->module, next->search_req); + } else { + /* Otherwise, we are done - let's run the + * request now we have swapped the DNs for the + * full versions */ + return ldb_next_request(os->ac->module, os->ac->req); + } + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(os->ac->req, ares->controls, + ares->response, ares->error); + } + + /* Only entries are interesting, and we only want the olddn */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + /* This *must* be the right DN, as this is a base + * search. We can't check, as it could be an extended + * DN, so a module below will resolve it */ + struct ldb_dn *dn = ares->message->dn; + + /* Replace the DN with the extended version of the DN + * (ie, add SID and GUID) */ + *os->replace_dn = data_blob_string_const( + ldb_dn_get_extended_linearized(os->mem_ctx, + dn, 1)); + if (os->replace_dn->data == NULL) { + return ldb_module_done(os->ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + break; + } + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + talloc_free(ares); + + /* Run the next search */ + + if (os->next) { + struct extended_dn_replace_list *next; + + next = os->next; + + talloc_free(os); + + os = next; + return ldb_next_request(os->ac->module, next->search_req); + } else { + /* Otherwise, we are done - let's run the + * request now we have swapped the DNs for the + * full versions */ + return ldb_next_request(os->ac->module, os->ac->new_req); + } + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +/* We have a 'normal' DN in the inbound request. We need to find out + * what the GUID and SID are on the DN it points to, so we can + * construct an extended DN for storage. + * + * This creates a list of DNs to look up, and the plain DN to replace + */ + +static int extended_store_replace(struct extended_dn_context *ac, + TALLOC_CTX *callback_mem_ctx, + struct ldb_val *plain_dn) +{ + int ret; + struct extended_dn_replace_list *os; + static const char *attrs[] = { + "objectSid", + "objectGUID", + NULL + }; + + os = talloc_zero(ac, struct extended_dn_replace_list); + if (!os) { + return LDB_ERR_OPERATIONS_ERROR; + } + + os->ac = ac; + + os->mem_ctx = callback_mem_ctx; + + os->dn = ldb_dn_from_ldb_val(os, ac->module->ldb, plain_dn); + if (!os->dn || !ldb_dn_validate(os->dn)) { + talloc_free(os); + ldb_asprintf_errstring(ac->module->ldb, + "could not parse %.*s as a DN", (int)plain_dn->length, plain_dn->data); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + os->replace_dn = plain_dn; + + /* The search request here might happen to be for an + * 'extended' style DN, such as <GUID=abced...>. The next + * module in the stack will convert this into a normal DN for + * processing */ + ret = ldb_build_search_req(&os->search_req, + ac->module->ldb, os, os->dn, LDB_SCOPE_BASE, NULL, + attrs, NULL, os, extended_replace_dn, + ac->req); + + if (ret != LDB_SUCCESS) { + talloc_free(os); + return ret; + } + + ret = ldb_request_add_control(os->search_req, + DSDB_CONTROL_DN_STORAGE_FORMAT_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(os); + return ret; + } + + if (ac->ops) { + ac->cur->next = os; + } else { + ac->ops = os; + } + ac->cur = os; + + return LDB_SUCCESS; +} + + +/* add */ +static int extended_dn_add(struct ldb_module *module, struct ldb_request *req) +{ + struct extended_dn_context *ac; + int ret; + int i, j; + + if (ldb_dn_is_special(req->op.add.message->dn)) { + /* do not manipulate our control entries */ + return ldb_next_request(module, req); + } + + ac = extended_dn_context_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->schema) { + /* without schema, this doesn't make any sense */ + talloc_free(ac); + return ldb_next_request(module, req); + } + + 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(ac->schema, el->name); + if (!schema_attr) { + continue; + } + + /* We only setup an extended DN GUID on these particular DN objects */ + if (strcmp(schema_attr->attributeSyntax_oid, "2.5.5.1") != 0) { + continue; + } + + /* Before we setup a procedure to modify the incoming message, we must copy it */ + if (!ac->new_req) { + struct ldb_message *msg = ldb_msg_copy(ac, req->op.add.message); + if (!msg) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_add_req(&ac->new_req, module->ldb, ac, msg, req->controls, ac, extended_final_callback, req); + if (ret != LDB_SUCCESS) { + return ret; + } + } + /* Re-calculate el */ + el = &ac->new_req->op.add.message->elements[i]; + for (j = 0; j < el->num_values; j++) { + ret = extended_store_replace(ac, ac->new_req->op.add.message->elements, &el->values[j]); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + /* if DNs were set continue */ + if (ac->ops == NULL) { + talloc_free(ac); + return ldb_next_request(module, req); + } + + /* start with the searches */ + return ldb_next_request(module, ac->ops->search_req); +} + +/* modify */ +static int extended_dn_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 extended_dn_context *ac; + int ret; + + if (ldb_dn_is_special(req->op.mod.message->dn)) { + /* do not manipulate our control entries */ + return ldb_next_request(module, req); + } + + ac = extended_dn_context_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->schema) { + /* without schema, this doesn't make any sense */ + return ldb_next_request(module, req); + } + + for (i=0; i < req->op.mod.message->num_elements; i++) { + const struct ldb_message_element *el = &req->op.mod.message->elements[i]; + const struct dsdb_attribute *schema_attr + = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name); + if (!schema_attr) { + continue; + } + + /* We only setup an extended DN GUID on these particular DN objects */ + if (strcmp(schema_attr->attributeSyntax_oid, "2.5.5.1") != 0) { + continue; + } + + /* Before we setup a procedure to modify the incoming message, we must copy it */ + if (!ac->new_req) { + struct ldb_message *msg = ldb_msg_copy(ac, req->op.mod.message); + if (!msg) { + ldb_oom(module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_mod_req(&ac->new_req, module->ldb, ac, msg, req->controls, ac, extended_final_callback, req); + if (ret != LDB_SUCCESS) { + return ret; + } + } + /* Re-calculate el */ + el = &ac->new_req->op.mod.message->elements[i]; + /* For each value being added, we need to setup the lookups to fill in the extended DN */ + for (j = 0; j < el->num_values; j++) { + struct ldb_dn *dn = ldb_dn_from_ldb_val(ac, module->ldb, &el->values[j]); + if (!dn || !ldb_dn_validate(dn)) { + ldb_asprintf_errstring(module->ldb, + "could not parse attribute %s as a DN", el->name); + return LDB_ERR_INVALID_DN_SYNTAX; + } + if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE) && !ldb_dn_has_extended(dn)) { + /* NO need to figure this DN out, it's going to be deleted anyway */ + continue; + } + ret = extended_store_replace(ac, req->op.mod.message->elements, &el->values[j]); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + /* if DNs were set continue */ + if (ac->ops == NULL) { + talloc_free(ac); + return ldb_next_request(module, req); + } + + /* start with the searches */ + return ldb_next_request(module, ac->ops->search_req); +} + +_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_store_module_ops = { + .name = "extended_dn_store", + .add = extended_dn_add, + .modify = extended_dn_modify, +}; diff --git a/source4/dsdb/samdb/ldb_modules/instancetype.c b/source4/dsdb/samdb/ldb_modules/instancetype.c new file mode 100644 index 0000000000..8d648d6d82 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/instancetype.c @@ -0,0 +1,154 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2004-2008 + 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_module.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/flags.h" + +struct it_context { + struct ldb_module *module; + struct ldb_request *req; +}; + +static int it_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct it_context *ac; + + ac = talloc_get_type(req->context, struct it_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Invalid reply type!"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); +} + +/* add_record: add instancetype attribute */ +static int instancetype_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_request *down_req; + struct ldb_message *msg; + struct it_context *ac; + uint32_t instance_type; + int ret; + const struct ldb_control *partition_ctrl; + const struct dsdb_control_current_partition *partition; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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(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); + + ac = talloc(req, struct it_context); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ac->module = module; + ac->req = req; + + /* we have to copy the message as the caller might have it as a const */ + msg = ldb_msg_copy_shallow(ac, req->op.add.message); + if (msg == NULL) { + ldb_oom(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(ldb)) != 0) { + instance_type |= INSTANCE_TYPE_NC_ABOVE; + } + } + + ret = ldb_msg_add_fmt(msg, "instanceType", "%u", instance_type); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_add_req(&down_req, ldb, ac, + msg, + req->controls, + ac, it_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* go on with the call chain */ + return ldb_next_request(module, down_req); +} + +_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..0b5994bb88 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/kludge_acl.c @@ -0,0 +1,535 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett 2005 + Copyright (C) Simo Sorce 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/>. +*/ + +/* + * 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_module.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 ldb_context *ldb = ldb_module_get_ctx(module); + struct auth_session_info *session_info + = (struct auth_session_info *)ldb_get_opaque(ldb, "sessionInfo"); + return security_session_user_level(session_info); +} + +static const char *user_name(TALLOC_CTX *mem_ctx, struct ldb_module *module) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct auth_session_info *session_info + = (struct auth_session_info *)ldb_get_opaque(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; + struct ldb_request *req; + + enum security_user_level user_type; + bool allowedAttributes; + bool allowedAttributesEffective; + bool allowedChildClasses; + bool allowedChildClassesEffective; + const char * const *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; + const 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, const 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] = (const char *)oc_el->values[i].data; + } + objectclass_list[i] = NULL; + + attr_list = dsdb_full_attribute_list(mem_ctx, schema, 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 LDB_SUCCESS; + +} +/* 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 *sclass; + 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++) { + sclass = dsdb_class_by_lDAPDisplayName(schema, (const char *)oc_el->values[i].data); + if (!sclass) { + /* We don't know this class? what is going on? */ + continue; + } + + for (j=0; sclass->possibleInferiors && sclass->possibleInferiors[j]; j++) { + ldb_msg_add_string(msg, attrName, sclass->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 LDB_SUCCESS; + +} + +/* find all attributes allowed by all these objectClasses */ + +static int kludge_acl_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct kludge_acl_context *ac; + struct kludge_private_data *data; + int i, ret; + + ac = talloc_get_type(req->context, struct kludge_acl_context); + data = talloc_get_type(ldb_module_get_private(ac->module), struct kludge_private_data); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (ac->allowedAttributes) { + ret = kludge_acl_allowedAttributes(ldb, + ares->message, + "allowedAttributes"); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + if (ac->allowedChildClasses) { + ret = kludge_acl_childClasses(ldb, + ares->message, + "allowedChildClasses"); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, 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 ldb_module_done(ac->req, NULL, NULL, ret); + } + } + if (ac->allowedChildClassesEffective) { + ret = kludge_acl_childClasses(ldb, ares->message, + "allowedChildClassesEffective"); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + break; + + case SECURITY_ADMINISTRATOR: + if (ac->allowedAttributesEffective) { + ret = kludge_acl_allowedAttributes(ldb, ares->message, + "allowedAttributesEffective"); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + if (ac->allowedChildClassesEffective) { + ret = kludge_acl_childClasses(ldb, ares->message, + "allowedChildClassesEffective"); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + /* fall through */ + 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) { + + if (!ldb_attr_in_list(ac->attrs, "objectClass") && + !ldb_attr_in_list(ac->attrs, "*")) { + + ldb_msg_remove_attr(ares->message, + "objectClass"); + } + } + + return ldb_module_send_entry(ac->req, ares->message, ares->controls); + + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral(ac->req, ares->referral); + + case LDB_REPLY_DONE: + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); + + } + return LDB_SUCCESS; +} + +static int kludge_acl_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct kludge_acl_context *ac; + struct ldb_request *down_req; + struct kludge_private_data *data; + const char * const *attrs; + int ret, i; + struct ldb_control *sd_control; + struct ldb_control **sd_saved_controls; + + ldb = ldb_module_get_ctx(module); + + ac = talloc(req, struct kludge_acl_context); + if (ac == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + data = talloc_get_type(ldb_module_get_private(module), struct kludge_private_data); + + ac->module = module; + ac->req = req; + ac->user_type = what_is_user(module); + ac->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) { + attrs = ldb_attr_list_copy_add(ac, req->op.search.attrs, "objectClass"); + } else { + attrs = req->op.search.attrs; + } + + /* 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: + /* FIXME: We should copy the tree and keep the original unmodified. */ + /* remove password attributes */ + + if (!data || !data->password_attrs) { + break; + } + for (i = 0; data->password_attrs[i]; i++) { + ldb_parse_tree_attr_replace(req->op.search.tree, + data->password_attrs[i], + "kludgeACLredactedattribute"); + } + } + + ret = ldb_build_search_req_ex(&down_req, + ldb, ac, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + attrs, + req->controls, + ac, kludge_acl_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* check if there's an SD_FLAGS control */ + sd_control = ldb_request_get_control(down_req, LDB_CONTROL_SD_FLAGS_OID); + if (sd_control) { + /* 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(sd_control, down_req, &sd_saved_controls)) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + + /* perform the search */ + return ldb_next_request(module, down_req); +} + +/* ANY change type */ +static int kludge_acl_change(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + 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(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_extended(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + enum security_user_level user_type; + + /* allow everybody to read the sequence number */ + if (strcmp(req->op.extended.oid, + LDB_EXTENDED_SEQUENCE_NUMBER) == 0) { + return ldb_next_request(module, req); + } + + 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(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) +{ + struct ldb_context *ldb; + 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; + + ldb = ldb_module_get_ctx(module); + + data = talloc(module, struct kludge_private_data); + if (data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + data->password_attrs = NULL; + ldb_module_set_private(module, data); + + if (!mem_ctx) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_search(ldb, mem_ctx, &res, + ldb_dn_new(mem_ctx, ldb, "@KLUDGEACL"), + LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + goto done; + } + 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(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; + + ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "partition: Unable to register control with rootdse!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + +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_extended, + .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..4e28c8a149 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/linked_attributes.c @@ -0,0 +1,1105 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007 + Copyright (C) Simo Sorce <idra@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/>. +*/ + +/* + * 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_module.h" +#include "dlinklist.h" +#include "dsdb/samdb/samdb.h" + +struct la_op_store { + struct la_op_store *next; + struct la_op_store *prev; + enum la_op {LA_OP_ADD, LA_OP_DEL} op; + struct ldb_dn *dn; + char *name; + char *value; +}; + +struct replace_context { + struct la_context *ac; + unsigned int num_elements; + struct ldb_message_element *el; +}; + +struct la_context { + const struct dsdb_schema *schema; + struct ldb_module *module; + struct ldb_request *req; + struct ldb_dn *add_dn; + struct ldb_dn *del_dn; + struct replace_context *rc; + struct la_op_store *ops; + struct ldb_extended *op_response; + struct ldb_control **op_controls; +}; + +static struct la_context *linked_attributes_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct la_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct la_context); + if (ac == NULL) { + ldb_oom(ldb); + return NULL; + } + + ac->schema = dsdb_get_schema(ldb); + ac->module = module; + ac->req = req; + + return ac; +} + +/* Common routine to handle reading the attributes and creating a + * series of modify requests */ +static int la_store_op(struct la_context *ac, + enum la_op op, struct ldb_val *dn, + const char *name) +{ + struct ldb_context *ldb; + struct la_op_store *os; + struct ldb_dn *op_dn; + + ldb = ldb_module_get_ctx(ac->module); + + op_dn = ldb_dn_from_ldb_val(ac, ldb, dn); + if (!op_dn) { + ldb_asprintf_errstring(ldb, + "could not parse attribute as a DN"); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + os = talloc_zero(ac, struct la_op_store); + if (!os) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + os->op = op; + + os->dn = talloc_steal(os, op_dn); + + os->name = talloc_strdup(os, name); + if (!os->name) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Do deletes before adds */ + if (op == LA_OP_ADD) { + DLIST_ADD_END(ac->ops, os, struct la_op_store *); + } else { + /* By adding to the head of the list, we do deletes before + * adds when processing a replace */ + DLIST_ADD(ac->ops, os); + } + + return LDB_SUCCESS; +} + +static int la_op_search_callback(struct ldb_request *req, + struct ldb_reply *ares); +static int la_do_mod_request(struct la_context *ac); +static int la_mod_callback(struct ldb_request *req, + struct ldb_reply *ares); +static int la_down_req(struct la_context *ac); + + + +/* add */ +static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + const struct dsdb_attribute *target_attr; + struct la_context *ac; + const char *attr_name; + int ret; + int i, j; + + ldb = ldb_module_get_ctx(module); + + if (ldb_dn_is_special(req->op.add.message->dn)) { + /* do not manipulate our control entries */ + return ldb_next_request(module, req); + } + + ac = linked_attributes_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->schema) { + /* without schema, this doesn't make any sense */ + talloc_free(ac); + return ldb_next_request(module, req); + } + + /* 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(ac->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, now 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(ldb, + "attribute %s must not be modified directly, it is a linked attribute", el->name); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + /* Even link IDs are for the originating attribute */ + target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID + 1); + if (!target_attr) { + /* + * windows 2003 has a broken schema where + * the definition of msDS-IsDomainFor + * is missing (which is supposed to be + * the backlink of the msDS-HasDomainNCs + * attribute + */ + continue; + } + + attr_name = target_attr->lDAPDisplayName; + + for (j = 0; j < el->num_values; j++) { + ret = la_store_op(ac, LA_OP_ADD, + &el->values[j], + attr_name); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + /* if no linked attributes are present continue */ + if (ac->ops == NULL) { + /* nothing to do for this module, proceed */ + talloc_free(ac); + return ldb_next_request(module, req); + } + + /* start with the original request */ + return la_down_req(ac); +} + +/* For a delete or rename, we need to find out what linked attributes + * are currently on this DN, and then deal with them. This is the + * callback to the base search */ + +static int la_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + const struct dsdb_attribute *schema_attr; + const struct dsdb_attribute *target_attr; + struct ldb_message_element *search_el; + struct replace_context *rc; + struct la_context *ac; + const char *attr_name; + int i, j; + int ret = LDB_SUCCESS; + + ac = talloc_get_type(req->context, struct la_context); + ldb = ldb_module_get_ctx(ac->module); + rc = ac->rc; + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + /* Only entries are interesting, and we only want the olddn */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + + if (ldb_dn_compare(ares->message->dn, ac->req->op.mod.message->dn) != 0) { + ldb_asprintf_errstring(ldb, + "linked_attributes: %s is not the DN we were looking for", ldb_dn_get_linearized(ares->message->dn)); + /* Guh? We only asked for this DN */ + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->add_dn = ac->del_dn = talloc_steal(ac, ares->message->dn); + + /* We don't populate 'rc' for ADD - it can't be deleting elements anyway */ + for (i = 0; rc && i < rc->num_elements; i++) { + + schema_attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, rc->el[i].name); + if (!schema_attr) { + ldb_asprintf_errstring(ldb, + "attribute %s is not a valid attribute in schema", + rc->el[i].name); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OBJECT_CLASS_VIOLATION); + } + + search_el = ldb_msg_find_element(ares->message, + rc->el[i].name); + + /* See if this element already exists */ + /* otherwise just ignore as + * the add has already been scheduled */ + if ( ! search_el) { + continue; + } + + target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID + 1); + if (!target_attr) { + /* + * windows 2003 has a broken schema where + * the definition of msDS-IsDomainFor + * is missing (which is supposed to be + * the backlink of the msDS-HasDomainNCs + * attribute + */ + continue; + } + attr_name = target_attr->lDAPDisplayName; + + /* Now we know what was there, we can remove it for the re-add */ + for (j = 0; j < search_el->num_values; j++) { + ret = la_store_op(ac, LA_OP_DEL, + &search_el->values[j], + attr_name); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ac->req, + NULL, NULL, ret); + } + } + } + + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + talloc_free(ares); + + if (ac->req->operation == LDB_ADD) { + /* Start the modifies to the backlinks */ + ret = la_do_mod_request(ac); + + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + ret); + } + } else { + /* Start with the original request */ + ret = la_down_req(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + return LDB_SUCCESS; + } + + talloc_free(ares); + return ret; +} + + +/* 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 */ + + struct ldb_context *ldb; + int i, j; + struct la_context *ac; + struct ldb_request *search_req; + const char **attrs; + + int ret; + + ldb = ldb_module_get_ctx(module); + + 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(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->schema) { + /* without schema, this doesn't make any sense */ + return ldb_next_request(module, req); + } + + ac->rc = talloc_zero(ac, struct replace_context); + if (!ac->rc) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i=0; i < req->op.mod.message->num_elements; i++) { + bool store_el = false; + const char *attr_name; + 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(ac->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, now find out if it is linked */ + if (schema_attr->linkID == 0) { + continue; + } + + if ((schema_attr->linkID & 1) == 1) { + /* Odd is for the target. Illegal to modify */ + ldb_asprintf_errstring(ldb, + "attribute %s must not be modified directly, it is a linked attribute", el->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(ac->schema, schema_attr->linkID + 1); + if (!target_attr) { + /* + * windows 2003 has a broken schema where + * the definition of msDS-IsDomainFor + * is missing (which is supposed to be + * the backlink of the msDS-HasDomainNCs + * attribute + */ + continue; + } + + attr_name = target_attr->lDAPDisplayName; + + switch (el->flags & LDB_FLAG_MOD_MASK) { + case LDB_FLAG_MOD_REPLACE: + /* treat as just a normal add the delete part is handled by the callback */ + store_el = true; + + /* break intentionally missing */ + + case LDB_FLAG_MOD_ADD: + + /* For each value being added, we need to setup the adds */ + for (j = 0; j < el->num_values; j++) { + ret = la_store_op(ac, LA_OP_ADD, + &el->values[j], + attr_name); + if (ret != LDB_SUCCESS) { + return ret; + } + } + break; + + case LDB_FLAG_MOD_DELETE: + + if (el->num_values) { + /* For each value being deleted, we need to setup the delete */ + for (j = 0; j < el->num_values; j++) { + ret = la_store_op(ac, LA_OP_DEL, + &el->values[j], + attr_name); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } else { + /* Flag that there was a DELETE + * without a value specified, so we + * need to look for the old value */ + store_el = true; + } + + break; + } + + if (store_el) { + struct ldb_message_element *search_el; + + search_el = talloc_realloc(ac->rc, ac->rc->el, + struct ldb_message_element, + ac->rc->num_elements +1); + if (!search_el) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ac->rc->el = search_el; + + ac->rc->el[ac->rc->num_elements] = *el; + ac->rc->num_elements++; + } + } + + if (ac->ops || ac->rc->el) { + /* both replace and delete without values are handled in the callback + * after the search on the entry to be modified is performed */ + + attrs = talloc_array(ac->rc, const char *, ac->rc->num_elements + 1); + if (!attrs) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + for (i = 0; ac->rc && i < ac->rc->num_elements; i++) { + attrs[i] = ac->rc->el[i].name; + } + attrs[i] = NULL; + + /* The callback does all the hard work here */ + ret = ldb_build_search_req(&search_req, ldb, ac, + req->op.mod.message->dn, + LDB_SCOPE_BASE, + "(objectClass=*)", attrs, + NULL, + ac, la_mod_search_callback, + req); + + /* We need to figure out our own extended DN, to fill in as the backlink target */ + if (ret == LDB_SUCCESS) { + ret = ldb_request_add_control(search_req, + LDB_CONTROL_EXTENDED_DN_OID, + false, NULL); + } + if (ret == LDB_SUCCESS) { + talloc_steal(search_req, attrs); + + ret = ldb_next_request(module, search_req); + } + + } else { + /* nothing to do for this module, proceed */ + talloc_free(ac); + ret = ldb_next_request(module, req); + } + + return ret; +} + +/* delete, rename */ +static int linked_attributes_del(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_request *search_req; + struct la_context *ac; + const char **attrs; + WERROR werr; + int ret; + + /* 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 + */ + + ldb = ldb_module_get_ctx(module); + + ac = linked_attributes_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->schema) { + /* without schema, this doesn't make any sense */ + return ldb_next_request(module, req); + } + + werr = dsdb_linked_attribute_lDAPDisplayName_list(ac->schema, ac, &attrs); + if (!W_ERROR_IS_OK(werr)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&search_req, ldb, req, + req->op.del.dn, LDB_SCOPE_BASE, + "(objectClass=*)", attrs, + NULL, + ac, la_op_search_callback, + req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + talloc_steal(search_req, attrs); + + return ldb_next_request(module, search_req); +} + +/* delete, rename */ +static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req) +{ + struct la_context *ac; + + /* 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(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!ac->schema) { + /* without schema, this doesn't make any sense */ + return ldb_next_request(module, req); + } + + /* start with the original request */ + return la_down_req(ac); +} + + +static int la_op_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct la_context *ac; + const struct dsdb_attribute *schema_attr; + const struct dsdb_attribute *target_attr; + const struct ldb_message_element *el; + const char *attr_name; + int i, j; + int ret; + + ac = talloc_get_type(req->context, struct la_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + /* Only entries are interesting, and we only want the olddn */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + ret = ldb_dn_compare(ares->message->dn, req->op.search.base); + if (ret != 0) { + /* Guh? We only asked for this DN */ + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->message->num_elements == 0) { + /* only bother at all if there were some + * linked attributes found */ + talloc_free(ares); + return LDB_SUCCESS; + } + + switch (ac->req->operation) { + case LDB_DELETE: + ac->del_dn = talloc_steal(ac, ares->message->dn); + break; + case LDB_RENAME: + ac->add_dn = talloc_steal(ac, ares->message->dn); + ac->del_dn = talloc_steal(ac, ac->req->op.rename.olddn); + break; + default: + talloc_free(ares); + ldb_set_errstring(ldb, + "operations must be delete or rename"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + for (i = 0; i < ares->message->num_elements; i++) { + el = &ares->message->elements[i]; + + schema_attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name); + if (!schema_attr) { + ldb_asprintf_errstring(ldb, + "attribute %s is not a valid attribute" + " in schema", el->name); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OBJECT_CLASS_VIOLATION); + } + + /* Valid attribute, now find out if it is linked */ + if (schema_attr->linkID == 0) { + /* Not a linked attribute, skip */ + continue; + } + + if ((schema_attr->linkID & 1) == 0) { + /* Odd is for the target. */ + target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID + 1); + if (!target_attr) { + continue; + } + attr_name = target_attr->lDAPDisplayName; + } else { + target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID - 1); + if (!target_attr) { + continue; + } + attr_name = target_attr->lDAPDisplayName; + } + for (j = 0; j < el->num_values; j++) { + ret = la_store_op(ac, LA_OP_DEL, + &el->values[j], + attr_name); + + /* for renames, ensure we add it back */ + if (ret == LDB_SUCCESS + && ac->req->operation == LDB_RENAME) { + ret = la_store_op(ac, LA_OP_ADD, + &el->values[j], + attr_name); + } + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ac->req, + NULL, NULL, ret); + } + } + } + + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + talloc_free(ares); + + + switch (ac->req->operation) { + case LDB_DELETE: + /* start the mod requests chain */ + ret = la_down_req(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + break; + case LDB_RENAME: + + ret = la_do_mod_request(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + ret); + } + + return ret; + + default: + talloc_free(ares); + ldb_set_errstring(ldb, + "operations must be delete or rename"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + return LDB_SUCCESS; + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +/* do a linked attributes modify request */ +static int la_do_mod_request(struct la_context *ac) +{ + struct ldb_message_element *ret_el; + struct ldb_request *mod_req; + struct ldb_message *new_msg; + struct ldb_context *ldb; + int ret; + + /* If we have no modifies in the queue, we are done! */ + if (!ac->ops) { + return ldb_module_done(ac->req, ac->op_controls, + ac->op_response, LDB_SUCCESS); + } + + ldb = ldb_module_get_ctx(ac->module); + + /* Create the modify request */ + new_msg = ldb_msg_new(ac); + if (!new_msg) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + new_msg->dn = ac->ops->dn; + + if (ac->ops->op == LA_OP_ADD) { + ret = ldb_msg_add_empty(new_msg, ac->ops->name, + LDB_FLAG_MOD_ADD, &ret_el); + } else { + ret = ldb_msg_add_empty(new_msg, ac->ops->name, + 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->num_values = 1; + if (ac->ops->op == LA_OP_ADD) { + ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->add_dn, 1)); + } else { + ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->del_dn, 1)); + } + +#if 0 + ldb_debug(ldb, LDB_DEBUG_WARNING, + "link on %s %s: %s %s\n", + ldb_dn_get_linearized(new_msg->dn), ret_el->name, + ret_el->values[0].data, ac->ops->op == LA_OP_ADD ? "added" : "deleted"); +#endif + + /* use ac->ops as the mem_ctx so that the request will be freed + * in the callback as soon as completed */ + ret = ldb_build_mod_req(&mod_req, ldb, ac->ops, + new_msg, + NULL, + ac, la_mod_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + talloc_steal(mod_req, new_msg); + + /* Run the new request */ + return ldb_next_request(ac->module, mod_req); +} + +static int la_mod_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct la_context *ac; + struct ldb_context *ldb; + struct la_op_store *os; + + ac = talloc_get_type(req->context, struct la_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "invalid ldb_reply_type in callback"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(ares); + + os = ac->ops; + DLIST_REMOVE(ac->ops, os); + + /* this frees the request too + * DO NOT access 'req' after this point */ + talloc_free(os); + + return la_do_mod_request(ac); +} + +/* Having done the original operation, then try to fix up all the linked attributes for modify and delete */ +static int la_mod_del_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret; + struct la_context *ac; + struct ldb_context *ldb; + + ac = talloc_get_type(req->context, struct la_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "invalid ldb_reply_type in callback"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->op_controls = talloc_steal(ac, ares->controls); + ac->op_response = talloc_steal(ac, ares->response); + + /* If we have modfies to make, this is the time to do them for modify and delete */ + ret = la_do_mod_request(ac); + + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + talloc_free(ares); + + /* la_do_mod_request has already sent the callbacks */ + return LDB_SUCCESS; + +} + +/* Having done the original rename try to fix up all the linked attributes */ +static int la_rename_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret; + struct la_context *ac; + struct ldb_request *search_req; + const char **attrs; + WERROR werr; + struct ldb_context *ldb; + + ac = talloc_get_type(req->context, struct la_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "invalid ldb_reply_type in callback"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + werr = dsdb_linked_attribute_lDAPDisplayName_list(ac->schema, ac, &attrs); + if (!W_ERROR_IS_OK(werr)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&search_req, ldb, req, + ac->req->op.rename.newdn, LDB_SCOPE_BASE, + "(objectClass=*)", attrs, + NULL, + ac, la_op_search_callback, + req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + talloc_steal(search_req, attrs); + + if (ret == LDB_SUCCESS) { + ret = ldb_request_add_control(search_req, + LDB_CONTROL_EXTENDED_DN_OID, + false, NULL); + } + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + ret); + } + + ac->op_controls = talloc_steal(ac, ares->controls); + ac->op_response = talloc_steal(ac, ares->response); + + return ldb_next_request(ac->module, search_req); +} + +/* Having done the original add, then try to fix up all the linked attributes + + This is done after the add so the links can get the extended DNs correctly. + */ +static int la_add_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret; + struct la_context *ac; + struct ldb_context *ldb; + + ac = talloc_get_type(req->context, struct la_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "invalid ldb_reply_type in callback"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (ac->ops) { + struct ldb_request *search_req; + static const char *attrs[] = { NULL }; + + /* The callback does all the hard work here - we need + * the objectGUID and SID of the added record */ + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->req->op.add.message->dn, + LDB_SCOPE_BASE, + "(objectClass=*)", attrs, + NULL, + ac, la_mod_search_callback, + ac->req); + + if (ret == LDB_SUCCESS) { + ret = ldb_request_add_control(search_req, + LDB_CONTROL_EXTENDED_DN_OID, + false, NULL); + } + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + ret); + } + + ac->op_controls = talloc_steal(ac, ares->controls); + ac->op_response = talloc_steal(ac, ares->response); + + return ldb_next_request(ac->module, search_req); + + } else { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } +} + +/* Reconstruct the original request, but pointing at our local callback to finish things off */ +static int la_down_req(struct la_context *ac) +{ + struct ldb_request *down_req; + int ret; + struct ldb_context *ldb; + + ldb = ldb_module_get_ctx(ac->module); + + switch (ac->req->operation) { + case LDB_ADD: + ret = ldb_build_add_req(&down_req, ldb, ac, + ac->req->op.add.message, + ac->req->controls, + ac, la_add_callback, + ac->req); + break; + case LDB_MODIFY: + ret = ldb_build_mod_req(&down_req, ldb, ac, + ac->req->op.mod.message, + ac->req->controls, + ac, la_mod_del_callback, + ac->req); + break; + case LDB_DELETE: + ret = ldb_build_del_req(&down_req, ldb, ac, + ac->req->op.del.dn, + ac->req->controls, + ac, la_mod_del_callback, + ac->req); + break; + case LDB_RENAME: + ret = ldb_build_rename_req(&down_req, ldb, ac, + ac->req->op.rename.olddn, + ac->req->op.rename.newdn, + ac->req->controls, + ac, la_rename_callback, + ac->req); + break; + default: + ret = LDB_ERR_OPERATIONS_ERROR; + } + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, down_req); +} + + +_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_del, + .rename = linked_attributes_rename, +}; 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..58c0f1f0d5 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/local_password.c @@ -0,0 +1,1098 @@ +/* + ldb database module + + Copyright (C) Simo Sorce 2004-2008 + 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_module.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 - perhaps + this should use the @KLUDGE_ACL list of passwordAttribute + */ + +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_reply { + struct lpdb_reply *next; + struct ldb_reply *remote; + struct ldb_dn *local_dn; +}; + +struct lpdb_context { + + struct ldb_module *module; + struct ldb_request *req; + + struct ldb_message *local_message; + + struct lpdb_reply *list; + struct lpdb_reply *current; + struct ldb_reply *remote_done; + struct ldb_reply *remote; + + bool added_objectGUID; + bool added_objectClass; + +}; + +static struct lpdb_context *lpdb_init_context(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct lpdb_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct lpdb_context); + if (ac == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int lpdb_local_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct lpdb_context *ac; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Unexpected reply type"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(ares); + return ldb_module_done(ac->req, + ac->remote_done->controls, + ac->remote_done->response, + ac->remote_done->error); +} + +/***************************************************************************** + * ADD + ****************************************************************************/ + +static int lpdb_add_callback(struct ldb_request *req, + struct ldb_reply *ares); + +static int local_password_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_message *remote_message; + struct ldb_request *remote_req; + struct lpdb_context *ac; + struct GUID objectGUID; + int ret; + int i; + + ldb = ldb_module_get_ctx(module); + ldb_debug(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, 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(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 */ + ac = lpdb_init_context(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + remote_message = ldb_msg_copy_shallow(remote_req, 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]); + } + + /* Find the objectGUID to use as the key */ + objectGUID = samdb_result_guid(ac->req->op.add.message, "objectGUID"); + + ac->local_message = ldb_msg_copy_shallow(ac, req->op.add.message); + if (ac->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 < remote_message->num_elements; i++) { + ldb_msg_remove_attr(ac->local_message, remote_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(req->op.add.message, "objectGUID") == NULL) { + ldb_set_errstring(ldb, + "no objectGUID found in search: " + "local_password module must be " + "onfigured below objectGUID module!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + ac->local_message->dn = ldb_dn_new(ac->local_message, + ldb, LOCAL_BASE); + if ((ac->local_message->dn == NULL) || + ( ! ldb_dn_add_child_fmt(ac->local_message->dn, + PASSWORD_GUID_ATTR "=%s", + GUID_string(ac->local_message, + &objectGUID)))) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_add_req(&remote_req, ldb, ac, + remote_message, + req->controls, + ac, lpdb_add_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, remote_req); +} + +/* Add a record, splitting password attributes from the user's main + * record */ +static int lpdb_add_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct ldb_request *local_req; + struct lpdb_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Unexpected reply type"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->remote_done = talloc_steal(ac, ares); + + ret = ldb_build_add_req(&local_req, ldb, ac, + ac->local_message, + NULL, + ac, lpdb_local_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + ret = ldb_next_request(ac->module, local_req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + return LDB_SUCCESS; +} + +/***************************************************************************** + * MODIFY + ****************************************************************************/ + +static int lpdb_modify_callabck(struct ldb_request *req, + struct ldb_reply *ares); +static int lpdb_mod_search_callback(struct ldb_request *req, + struct ldb_reply *ares); + +static int local_password_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct lpdb_context *ac; + struct ldb_message *remote_message; + struct ldb_request *remote_req; + int ret; + int i; + + ldb = ldb_module_get_ctx(module); + ldb_debug(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, 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 */ + ac = lpdb_init_context(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + remote_message = ldb_msg_copy_shallow(ac, ac->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->local_message = ldb_msg_copy_shallow(ac, ac->req->op.mod.message); + if (ac->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 < remote_message->num_elements;i++) { + ldb_msg_remove_attr(ac->local_message, remote_message->elements[i].name); + } + + ret = ldb_build_mod_req(&remote_req, ldb, ac, + remote_message, + req->controls, + ac, lpdb_modify_callabck, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, remote_req); +} + +/* On a modify, we don't have the objectGUID handy, so we need to + * search our DN for it */ +static int lpdb_modify_callabck(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + static const char * const attrs[] = { "objectGUID", "objectClass", NULL }; + struct ldb_request *search_req; + struct lpdb_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Unexpected reply type"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->remote_done = talloc_steal(ac, ares); + + /* prepare the search operation */ + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->req->op.mod.message->dn, LDB_SCOPE_BASE, + "(objectclass=*)", attrs, + NULL, + ac, lpdb_mod_search_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_next_request(ac->module, search_req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + return LDB_SUCCESS; +} + +/* Called when we search for our own entry. Stores the one entry we + * expect (as it is a base search) on the context pointer */ +static int lpdb_mod_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct ldb_request *local_req; + struct lpdb_context *ac; + struct ldb_dn *local_dn; + struct GUID objectGUID; + int ret = LDB_SUCCESS; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (ac->remote != NULL) { + ldb_set_errstring(ldb, "Too many results"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->remote = talloc_steal(ac, ares); + break; + + case LDB_REPLY_REFERRAL: + + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + /* After we find out the objectGUID for the entry, modify the local + * password database as required */ + + talloc_free(ares); + + /* if it is not an entry of type person this is an error */ + /* TODO: remove this when sambaPassword will be in schema */ + if (ac->remote == NULL) { + ldb_asprintf_errstring(ldb, + "entry just modified (%s) not found!", + ldb_dn_get_linearized(req->op.search.base)); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (!ldb_msg_check_string_attribute(ac->remote->message, + "objectClass", "person")) { + /* Not relevent to us */ + return ldb_module_done(ac->req, + ac->remote_done->controls, + ac->remote_done->response, + ac->remote_done->error); + } + + if (ldb_msg_find_ldb_val(ac->remote->message, + "objectGUID") == NULL) { + ldb_set_errstring(ldb, + "no objectGUID found in search: " + "local_password module must be " + "configured below objectGUID " + "module!\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OBJECT_CLASS_VIOLATION); + } + + objectGUID = samdb_result_guid(ac->remote->message, + "objectGUID"); + + local_dn = ldb_dn_new(ac, ldb, LOCAL_BASE); + if ((local_dn == NULL) || + ( ! ldb_dn_add_child_fmt(local_dn, + PASSWORD_GUID_ATTR "=%s", + GUID_string(ac, &objectGUID)))) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + ac->local_message->dn = local_dn; + + ret = ldb_build_mod_req(&local_req, ldb, ac, + ac->local_message, + NULL, + ac, lpdb_local_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + /* perform the local update */ + ret = ldb_next_request(ac->module, local_req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + return LDB_SUCCESS; +} + +/***************************************************************************** + * DELETE + ****************************************************************************/ + +static int lpdb_delete_callabck(struct ldb_request *req, + struct ldb_reply *ares); +static int lpdb_del_search_callback(struct ldb_request *req, + struct ldb_reply *ares); + +static int local_password_delete(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_request *remote_req; + struct lpdb_context *ac; + int ret; + + ldb = ldb_module_get_ctx(module); + ldb_debug(ldb, LDB_DEBUG_TRACE, "local_password_delete\n"); + + /* do not manipulate our control entries */ + if (ldb_dn_is_special(req->op.mod.message->dn)) { + 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, ldb, LOCAL_BASE), + req->op.del.dn) == 0) { + return ldb_next_request(module, req); + } + + /* From here, we assume we have password attributes to split off */ + ac = lpdb_init_context(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_del_req(&remote_req, ldb, ac, + req->op.del.dn, + req->controls, + ac, lpdb_delete_callabck, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, remote_req); +} + +/* On a modify, we don't have the objectGUID handy, so we need to + * search our DN for it */ +static int lpdb_delete_callabck(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + static const char * const attrs[] = { "objectGUID", "objectClass", NULL }; + struct ldb_request *search_req; + struct lpdb_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Unexpected reply type"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->remote_done = talloc_steal(ac, ares); + + /* prepare the search operation */ + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->req->op.del.dn, LDB_SCOPE_BASE, + "(objectclass=*)", attrs, + NULL, + ac, lpdb_del_search_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_next_request(ac->module, search_req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + return LDB_SUCCESS; +} + +/* Called when we search for our own entry. Stores the one entry we + * expect (as it is a base search) on the context pointer */ +static int lpdb_del_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct ldb_request *local_req; + struct lpdb_context *ac; + struct ldb_dn *local_dn; + struct GUID objectGUID; + int ret = LDB_SUCCESS; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (ac->remote != NULL) { + ldb_set_errstring(ldb, "Too many results"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->remote = talloc_steal(ac, ares); + break; + + case LDB_REPLY_REFERRAL: + + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + /* After we find out the objectGUID for the entry, modify the local + * password database as required */ + + talloc_free(ares); + + /* if it is not an entry of type person this is NOT an error */ + /* TODO: remove this when sambaPassword will be in schema */ + if (ac->remote == NULL) { + return ldb_module_done(ac->req, + ac->remote_done->controls, + ac->remote_done->response, + ac->remote_done->error); + } + if (!ldb_msg_check_string_attribute(ac->remote->message, + "objectClass", "person")) { + /* Not relevent to us */ + return ldb_module_done(ac->req, + ac->remote_done->controls, + ac->remote_done->response, + ac->remote_done->error); + } + + if (ldb_msg_find_ldb_val(ac->remote->message, + "objectGUID") == NULL) { + ldb_set_errstring(ldb, + "no objectGUID found in search: " + "local_password module must be " + "configured below objectGUID " + "module!\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OBJECT_CLASS_VIOLATION); + } + + objectGUID = samdb_result_guid(ac->remote->message, + "objectGUID"); + + local_dn = ldb_dn_new(ac, ldb, LOCAL_BASE); + if ((local_dn == NULL) || + ( ! ldb_dn_add_child_fmt(local_dn, + PASSWORD_GUID_ATTR "=%s", + GUID_string(ac, &objectGUID)))) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_build_del_req(&local_req, ldb, ac, + local_dn, + NULL, + ac, lpdb_local_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + /* perform the local update */ + ret = ldb_next_request(ac->module, local_req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + return LDB_SUCCESS; +} + + +/***************************************************************************** + * SEARCH + ****************************************************************************/ + +static int lpdb_local_search_callback(struct ldb_request *req, + struct ldb_reply *ares); + +static int lpdb_local_search(struct lpdb_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *local_req; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + ret = ldb_build_search_req(&local_req, ldb, ac, + ac->current->local_dn, + LDB_SCOPE_BASE, + "(objectclass=*)", + ac->req->op.search.attrs, + NULL, + ac, lpdb_local_search_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + return ldb_next_request(ac->module, local_req); +} + +static int lpdb_local_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct lpdb_context *ac; + struct ldb_reply *merge; + struct lpdb_reply *lr; + int ret; + int i; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + lr = ac->current; + + /* we are interested only in a single reply (base search) */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + + if (lr->remote == NULL) { + ldb_set_errstring(ldb, + "Too many results for password entry search!"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + merge = lr->remote; + lr->remote = NULL; + + /* steal the local results on the remote results to be + * returned all together */ + talloc_steal(merge, ares->message->elements); + + /* Make sure never to return the internal key attribute */ + ldb_msg_remove_attr(ares->message, PASSWORD_GUID_ATTR); + + for (i=0; i < ares->message->num_elements; i++) { + struct ldb_message_element *el; + + el = ldb_msg_find_element(merge->message, + ares->message->elements[i].name); + if (!el) { + ret = ldb_msg_add_empty(merge->message, + ares->message->elements[i].name, + 0, &el); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ac->req, + NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + *el = ares->message->elements[i]; + } + } + + /* free the rest */ + talloc_free(ares); + + return ldb_module_send_entry(ac->req, merge->message, merge->controls); + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + + talloc_free(ares); + + /* if this entry was not returned yet, return it now */ + if (lr->remote) { + ret = ldb_module_send_entry(ac->req, ac->remote->message, ac->remote->controls); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, + NULL, NULL, ret); + } + lr->remote = NULL; + } + + if (lr->next->remote->type == LDB_REPLY_DONE) { + /* this was the last one */ + return ldb_module_done(ac->req, + lr->next->remote->controls, + lr->next->remote->response, + lr->next->remote->error); + } else { + /* next one */ + ac->current = lr->next; + talloc_free(lr); + + ret = lpdb_local_search(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, + NULL, NULL, ret); + } + } + } + + return LDB_SUCCESS; +} + +/* 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_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct lpdb_context *ac; + struct ldb_dn *local_dn; + struct GUID objectGUID; + struct lpdb_reply *lr; + int ret; + + ac = talloc_get_type(req->context, struct lpdb_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* 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 ldb_module_send_entry(ac->req, ares->message, ares->controls); + } + + if (ldb_msg_find_ldb_val(ares->message, "objectGUID") == NULL) { + ldb_set_errstring(ldb, + "no objectGUID found in search: local_password module must be configured below objectGUID module!\n"); + return ldb_module_done(ac->req, NULL, NULL, + 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"); + } + + local_dn = ldb_dn_new(ac, ldb, LOCAL_BASE); + if ((local_dn == NULL) || + (! ldb_dn_add_child_fmt(local_dn, + PASSWORD_GUID_ATTR "=%s", + GUID_string(ac, &objectGUID)))) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + lr = talloc_zero(ac, struct lpdb_reply); + if (lr == NULL) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + lr->local_dn = talloc_steal(lr, local_dn); + lr->remote = talloc_steal(lr, ares); + + if (ac->list) { + ac->current->next = lr; + } else { + ac->list = lr; + } + ac->current= lr; + + break; + + case LDB_REPLY_REFERRAL: + + return ldb_module_send_referral(ac->req, ares->referral); + + case LDB_REPLY_DONE: + + if (ac->list == NULL) { + /* found nothing */ + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + lr = talloc_zero(ac, struct lpdb_reply); + if (lr == NULL) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + lr->remote = talloc_steal(lr, ares); + + ac->current->next = lr; + + /* rewind current and start local searches */ + ac->current= ac->list; + + ret = lpdb_local_search(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + } + + return LDB_SUCCESS; +} + +/* 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_context *ldb; + struct ldb_request *remote_req; + struct lpdb_context *ac; + int i; + int ret; + const char * const *search_attrs = NULL; + + ldb = ldb_module_get_ctx(module); + ldb_debug(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); + } + + search_attrs = NULL; + + /* If the caller is searching for the local passwords directly, let them pass */ + if (ldb_dn_compare_base(ldb_dn_new(req, 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); + } + } + + ac = lpdb_init_context(module, req); + if (!ac) { + 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 */ + 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(ac, 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(ac, search_attrs, "objectClass"); + ac->added_objectClass = true; + if (!search_attrs) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + } else { + search_attrs = req->op.search.attrs; + } + + ret = ldb_build_search_req_ex(&remote_req, ldb, ac, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + search_attrs, + req->controls, + ac, lpdb_remote_search_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* perform the search */ + return ldb_next_request(module, remote_req); +} + +_PUBLIC_ const struct ldb_module_ops ldb_local_password_module_ops = { + .name = "local_password", + .add = local_password_add, + .modify = local_password_modify, + .del = local_password_delete, + .search = local_password_search +}; 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..607bf054d2 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/naming_fsmo.c @@ -0,0 +1,122 @@ +/* + 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 "ldb_module.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) +{ + struct ldb_context *ldb; + 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 + }; + + ldb = ldb_module_get_ctx(module); + + mem_ctx = talloc_new(module); + if (!mem_ctx) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + naming_dn = samdb_partitions_dn(ldb, mem_ctx); + if (!naming_dn) { + ldb_debug(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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ldb_module_set_private(module, naming_fsmo); + + ret = ldb_search(ldb, mem_ctx, &naming_res, + naming_dn, LDB_SCOPE_BASE, + naming_attrs, NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + ldb_debug(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(ldb, LDB_DEBUG_FATAL, + "naming_fsmo_init: failed to search the cross-ref container: %s: %s", + ldb_strerror(ret), ldb_errstring(ldb)); + talloc_free(mem_ctx); + return ret; + } + if (naming_res->count == 0) { + ldb_debug(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(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(ldb, naming_fsmo, naming_res->msgs[0], "fSMORoleOwner"); + if (ldb_dn_compare(samdb_ntds_settings_dn(ldb), naming_fsmo->master_dn) == 0) { + naming_fsmo->we_are_master = true; + } else { + naming_fsmo->we_are_master = false; + } + + if (ldb_set_opaque(ldb, "dsdb_naming_fsmo", naming_fsmo) != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + talloc_steal(module, naming_fsmo); + + ldb_debug(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/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c new file mode 100644 index 0000000000..898d913965 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/objectclass.c @@ -0,0 +1,1073 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2006-2008 + 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_module.h" +#include "dlinklist.h" +#include "dsdb/samdb/samdb.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 { + + struct ldb_module *module; + struct ldb_request *req; + + struct ldb_reply *search_res; + + int (*step_fn)(struct oc_context *); +}; + +struct class_list { + struct class_list *prev, *next; + const struct dsdb_class *objectclass; +}; + +static struct oc_context *oc_init_context(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct oc_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct oc_context); + if (ac == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int objectclass_do_add(struct oc_context *ac); + +/* 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, + TALLOC_CTX *mem_ctx, + struct ldb_message_element *objectclass_element, + struct class_list **sorted_out) +{ + struct ldb_context *ldb; + int i; + int layer; + struct class_list *sorted = NULL, *parent_class = NULL, + *subclass = NULL, *unsorted = NULL, *current, *poss_subclass, *poss_parent, *new_parent; + + ldb = ldb_module_get_ctx(module); + + /* 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_oom(ldb); + 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(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(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) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + enum ndr_err_code ndr_err; + DATA_BLOB *linear_sd; + struct auth_session_info *session_info + = ldb_get_opaque(ldb, "sessionInfo"); + struct security_descriptor *sd; + const struct dom_sid *domain_sid = samdb_domain_sid(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(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_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct oc_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct oc_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS && + ares->error != LDB_ERR_NO_SUCH_OBJECT) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (ac->search_res != NULL) { + ldb_set_errstring(ldb, "Too many results"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ac->search_res = talloc_steal(ac, ares); + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + talloc_free(ares); + ret = ac->step_fn(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + break; + } + + return LDB_SUCCESS; +} + +static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct oc_context *ac; + + ac = talloc_get_type(req->context, struct oc_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); +} + +/* 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); + /* Add in a very special case for 'clearTextPassword', + * which is used for internal processing only, and is + * not presented in the schema */ + if (!attribute) { + if (strcasecmp(msg->elements[i].name, "clearTextPassword") != 0) { + ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name); + return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE; + } + } else { + msg->elements[i].name = attribute->lDAPDisplayName; + } + } + + return LDB_SUCCESS; +} + +static int objectclass_do_add(struct oc_context *ac); + +static int objectclass_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_request *search_req; + struct oc_context *ac; + struct ldb_dn *parent_dn; + int ret; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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); + } + + /* the objectClass must be specified on add */ + if (ldb_msg_find_element(req->op.add.message, + "objectClass") == NULL) { + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + + ac = oc_init_context(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* If there isn't a parent, just go on to the add processing */ + if (ldb_dn_get_comp_num(ac->req->op.add.message->dn) == 1) { + return objectclass_do_add(ac); + } + + /* get copy of parent DN */ + parent_dn = ldb_dn_get_parent(ac, ac->req->op.add.message->dn); + if (parent_dn == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&search_req, ldb, + ac, parent_dn, LDB_SCOPE_BASE, + "(objectClass=*)", NULL, + NULL, + ac, get_search_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + talloc_steal(search_req, parent_dn); + + ac->step_fn = objectclass_do_add; + + return ldb_next_request(ac->module, search_req); +} + +static int objectclass_do_add(struct oc_context *ac) +{ + struct ldb_context *ldb; + const struct dsdb_schema *schema; + struct ldb_request *add_req; + char *value; + struct ldb_message_element *objectclass_element; + struct ldb_message *msg; + TALLOC_CTX *mem_ctx; + struct class_list *sorted, *current; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + schema = dsdb_get_schema(ldb); + + mem_ctx = talloc_new(ac); + if (mem_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message); + + /* Check we have a valid parent */ + if (ac->search_res == NULL) { + if (ldb_dn_compare(ldb_get_root_basedn(ldb), + msg->dn) == 0) { + /* Allow the tree to be started */ + + /* but don't keep any error string, it's meaningless */ + ldb_set_errstring(ldb, NULL); + } else { + ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, parent does not exist!", + ldb_dn_get_linearized(msg->dn)); + talloc_free(mem_ctx); + 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->req->op.add.message->dn, + ac->search_res->message->dn, + &msg->dn); + + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb, "Could not munge DN %s into normal form", + ldb_dn_get_linearized(ac->req->op.add.message->dn)); + talloc_free(mem_ctx); + return ret; + } + + /* TODO: Check this is a valid child to this parent, + * by reading the allowedChildClasses and + * allowedChildClasssesEffective attributes */ + + } + + if (schema) { + ret = fix_attributes(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, 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) { + value = talloc_strdup(msg, current->objectclass->lDAPDisplayName); + if (value == NULL) { + ldb_oom(ldb); + talloc_free(mem_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_msg_add_string(msg, "objectClass", value); + if (ret != LDB_SUCCESS) { + ldb_set_errstring(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")) { + value = talloc_strdup(msg, current->objectclass->defaultObjectCategory); + if (value == NULL) { + ldb_oom(ldb); + talloc_free(mem_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + ldb_msg_add_string(msg, "objectCategory", value); + } + 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(ldb, msg, msg, "systemFlags", systemFlags); + } + } + } + } + + talloc_free(mem_ctx); + ret = ldb_msg_sanity_check(ldb, msg); + + + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_build_add_req(&add_req, ldb, ac, + msg, + ac->req->controls, + ac, oc_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* perform the add */ + return ldb_next_request(ac->module, add_req); +} + +static int oc_modify_callback(struct ldb_request *req, + struct ldb_reply *ares); +static int objectclass_do_mod(struct oc_context *ac); + +static int objectclass_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_message_element *objectclass_element; + struct ldb_message *msg; + const struct dsdb_schema *schema = dsdb_get_schema(ldb); + struct class_list *sorted, *current; + struct ldb_request *down_req; + struct oc_context *ac; + TALLOC_CTX *mem_ctx; + char *value; + int ret; + + ldb_debug(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"); + + ac = oc_init_context(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* 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) { + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = fix_attributes(ldb, schema, msg); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, oc_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* go on with the call chain */ + return ldb_next_request(module, down_req); + } + + 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: + mem_ctx = talloc_new(ac); + if (mem_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + talloc_free(mem_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = fix_attributes(ldb, schema, msg); + if (ret != LDB_SUCCESS) { + talloc_free(mem_ctx); + return ret; + } + + ret = objectclass_sort(module, schema, mem_ctx, objectclass_element, &sorted); + if (ret != LDB_SUCCESS) { + talloc_free(mem_ctx); + 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) { + /* copy the value as this string is on the schema + * context and we can't rely on it not changing + * before the operation is over */ + value = talloc_strdup(msg, + current->objectclass->lDAPDisplayName); + if (value == NULL) { + ldb_oom(ldb); + talloc_free(mem_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_msg_add_string(msg, "objectClass", value); + if (ret != LDB_SUCCESS) { + ldb_set_errstring(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(ldb, msg); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, oc_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* go on with the call chain */ + return ldb_next_request(module, down_req); + } + + /* 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 + */ + + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = fix_attributes(ldb, schema, msg); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return ret; + } + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, oc_modify_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + static const char * const attrs[] = { "objectClass", NULL }; + struct ldb_request *search_req; + struct oc_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct oc_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->req->op.mod.message->dn, LDB_SCOPE_BASE, + "(objectClass=*)", + attrs, NULL, + ac, get_search_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + ac->step_fn = objectclass_do_mod; + + ret = ldb_next_request(ac->module, search_req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + return LDB_SUCCESS; +} + +static int objectclass_do_mod(struct oc_context *ac) +{ + struct ldb_context *ldb; + const struct dsdb_schema *schema; + struct ldb_request *mod_req; + char *value; + struct ldb_message_element *objectclass_element; + struct ldb_message *msg; + TALLOC_CTX *mem_ctx; + struct class_list *sorted, *current; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + if (ac->search_res == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + schema = dsdb_get_schema(ldb); + + mem_ctx = talloc_new(ac); + if (mem_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* use a new message structure */ + msg = ldb_msg_new(ac); + if (msg == NULL) { + ldb_set_errstring(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->req->op.mod.message->dn; + + ret = objectclass_sort(ac->module, schema, 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(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) { + value = talloc_strdup(msg, current->objectclass->lDAPDisplayName); + if (value == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_msg_add_string(msg, "objectClass", value); + if (ret != LDB_SUCCESS) { + ldb_set_errstring(ldb, "objectclass: could not re-add sorted objectclass to modify msg"); + talloc_free(mem_ctx); + return ret; + } + } + + ret = ldb_msg_sanity_check(ldb, msg); + if (ret != LDB_SUCCESS) { + talloc_free(mem_ctx); + return ret; + } + + ret = ldb_build_mod_req(&mod_req, ldb, ac, + msg, + ac->req->controls, + ac, oc_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + talloc_free(mem_ctx); + return ret; + } + + talloc_free(mem_ctx); + /* perform the modify */ + return ldb_next_request(ac->module, mod_req); +} + +static int objectclass_do_rename(struct oc_context *ac); + +static int objectclass_rename(struct ldb_module *module, struct ldb_request *req) +{ + static const char * const attrs[] = { NULL }; + struct ldb_context *ldb; + struct ldb_request *search_req; + struct oc_context *ac; + struct ldb_dn *parent_dn; + int ret; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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(ldb, "Cannot rename %s to be a child of itself", + ldb_dn_get_linearized(req->op.rename.olddn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + ac = oc_init_context(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + parent_dn = ldb_dn_get_parent(ac, req->op.rename.newdn); + if (parent_dn == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_build_search_req(&search_req, ldb, + ac, parent_dn, LDB_SCOPE_BASE, + "(objectClass=*)", + attrs, NULL, + ac, get_search_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + ac->step_fn = objectclass_do_rename; + + return ldb_next_request(ac->module, search_req); +} + +static int objectclass_do_rename(struct oc_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *rename_req; + struct ldb_dn *fixed_dn; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + /* Check we have a valid parent */ + if (ac->search_res == NULL) { + ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, parent does not exist!", + ldb_dn_get_linearized(ac->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, + ac->req->op.rename.newdn, + ac->search_res->message->dn, + &fixed_dn); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* TODO: Check this is a valid child to this parent, + * by reading the allowedChildClasses and + * allowedChildClasssesEffective attributes */ + + ret = ldb_build_rename_req(&rename_req, ldb, ac, + ac->req->op.rename.olddn, fixed_dn, + ac->req->controls, + ac, oc_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* perform the rename */ + return ldb_next_request(ac->module, rename_req); +} + +_PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = { + .name = "objectclass", + .add = objectclass_add, + .modify = objectclass_modify, + .rename = objectclass_rename, +}; diff --git a/source4/dsdb/samdb/ldb_modules/objectguid.c b/source4/dsdb/samdb/ldb_modules/objectguid.c new file mode 100644 index 0000000000..46ba8ebdb3 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/objectguid.c @@ -0,0 +1,285 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Simo Sorce 2004-2008 + + ** 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_module.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; +} + +struct og_context { + struct ldb_module *module; + struct ldb_request *req; +}; + +static int og_op_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct og_context *ac; + + ac = talloc_get_type(req->context, struct og_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); +} + +/* add_record: add objectGUID attribute */ +static int objectguid_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + 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); + struct og_context *ac; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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); + } + + ac = talloc(req, struct og_context); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ac->module = module; + ac->req = req; + + /* we have to copy the message as the caller might have it as a const */ + msg = ldb_msg_copy_shallow(ac, 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(ldb, "loadparm")), + &guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_msg_add_value(msg, "objectGUID", &v, NULL); + if (ret) { + return ret; + } + + if (add_time_element(msg, "whenCreated", t) != 0 || + add_time_element(msg, "whenChanged", t) != 0) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Get a sequence number from the backend */ + /* FIXME: ldb_sequence_number is a semi-async call, + * make sure this function is split and a callback is used */ + ret = ldb_sequence_number(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) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + + ret = ldb_build_add_req(&down_req, ldb, ac, + msg, + req->controls, + ac, og_op_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* go on with the call chain */ + return ldb_next_request(module, down_req); +} + +/* modify_record: update timestamps */ +static int objectguid_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_request *down_req; + struct ldb_message *msg; + int ret; + time_t t = time(NULL); + uint64_t seq_num; + struct og_context *ac; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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); + } + + ac = talloc(req, struct og_context); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ac->module = module; + ac->req = req; + + /* we have to copy the message as the caller might have it as a const */ + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (add_time_element(msg, "whenChanged", t) != 0) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Get a sequence number from the backend */ + ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num); + if (ret == LDB_SUCCESS) { + if (add_uint64_element(msg, "uSNChanged", seq_num) != 0) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, og_op_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* go on with the call chain */ + return ldb_next_request(module, down_req); +} + +_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..71a1b8e942 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/partition.c @@ -0,0 +1,1345 @@ +/* + 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_private.h" +#include "dsdb/samdb/samdb.h" + +struct partition_private_data { + struct dsdb_control_current_partition **partitions; + struct ldb_dn **replicate; +}; + +struct part_request { + struct ldb_module *module; + struct ldb_request *req; +}; + +struct partition_context { + struct ldb_module *module; + struct ldb_request *req; + bool got_success; + + struct part_request *part_req; + int num_requests; + int finished_requests; +}; + +static struct partition_context *partition_init_ctx(struct ldb_module *module, struct ldb_request *req) +{ + struct partition_context *ac; + + ac = talloc_zero(req, struct partition_context); + if (ac == NULL) { + ldb_set_errstring(module->ldb, "Out of Memory"); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +#define PARTITION_FIND_OP(module, op) do { \ + struct ldb_context *ldbctx = module->ldb; \ + while (module && module->ops->op == NULL) module = module->next; \ + if (module == NULL) { \ + ldb_asprintf_errstring(ldbctx, \ + "Unable to find backend operation for " #op ); \ + return LDB_ERR_OPERATIONS_ERROR; \ + } \ +} while (0) + +/* + * helper functions to call the next module in chain + * */ + +static int partition_request(struct ldb_module *module, struct ldb_request *request) +{ + int ret; + switch (request->operation) { + case LDB_SEARCH: + PARTITION_FIND_OP(module, search); + ret = module->ops->search(module, request); + break; + case LDB_ADD: + PARTITION_FIND_OP(module, add); + ret = module->ops->add(module, request); + break; + case LDB_MODIFY: + PARTITION_FIND_OP(module, modify); + ret = module->ops->modify(module, request); + break; + case LDB_DELETE: + PARTITION_FIND_OP(module, del); + ret = module->ops->del(module, request); + break; + case LDB_RENAME: + PARTITION_FIND_OP(module, rename); + ret = module->ops->rename(module, request); + break; + case LDB_EXTENDED: + PARTITION_FIND_OP(module, extended); + ret = module->ops->extended(module, request); + break; + default: + PARTITION_FIND_OP(module, request); + ret = module->ops->request(module, request); + break; + } + if (ret == LDB_SUCCESS) { + return ret; + } + if (!ldb_errstring(module->ldb)) { + /* Set a default error string, to place the blame somewhere */ + ldb_asprintf_errstring(module->ldb, + "error in module %s: %s (%d)", + module->ops->name, + ldb_strerror(ret), ret); + } + return ret; +} + +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_req_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct partition_context *ac; + struct ldb_module *module; + struct ldb_request *nreq; + int ret; + + ac = talloc_get_type(req->context, struct partition_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (ares->error != LDB_SUCCESS && !ac->got_success) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_REFERRAL: + /* ignore referrals for now */ + break; + + case LDB_REPLY_ENTRY: + if (ac->req->operation != LDB_SEARCH) { + ldb_set_errstring(ac->module->ldb, + "partition_req_callback:" + " Unsupported reply type for this request"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + return ldb_module_send_entry(ac->req, ares->message, ares->controls); + + case LDB_REPLY_DONE: + if (ares->error == LDB_SUCCESS) { + ac->got_success = true; + } + if (ac->req->operation == LDB_EXTENDED) { + /* FIXME: check for ares->response, replmd does not fill it ! */ + if (ares->response) { + if (strcmp(ares->response->oid, LDB_EXTENDED_START_TLS_OID) != 0) { + ldb_set_errstring(ac->module->ldb, + "partition_req_callback:" + " Unknown extended reply, " + "only supports START_TLS"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + } + } + + ac->finished_requests++; + if (ac->finished_requests == ac->num_requests) { + /* this was the last one, call callback */ + return ldb_module_done(ac->req, ares->controls, + ares->response, + ac->got_success?LDB_SUCCESS:ares->error); + } + + /* not the last, now call the next one */ + module = ac->part_req[ac->finished_requests].module; + nreq = ac->part_req[ac->finished_requests].req; + + ret = partition_request(module, nreq); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + break; + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int partition_prep_request(struct partition_context *ac, + struct dsdb_control_current_partition *partition) +{ + int ret; + struct ldb_request *req; + + ac->part_req = talloc_realloc(ac, ac->part_req, + struct part_request, + ac->num_requests + 1); + if (ac->part_req == NULL) { + ldb_oom(ac->module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + switch (ac->req->operation) { + case LDB_SEARCH: + ret = ldb_build_search_req_ex(&req, ac->module->ldb, + ac->part_req, + ac->req->op.search.base, + ac->req->op.search.scope, + ac->req->op.search.tree, + ac->req->op.search.attrs, + ac->req->controls, + ac, partition_req_callback, + ac->req); + break; + case LDB_ADD: + ret = ldb_build_add_req(&req, ac->module->ldb, ac->part_req, + ac->req->op.add.message, + ac->req->controls, + ac, partition_req_callback, + ac->req); + break; + case LDB_MODIFY: + ret = ldb_build_mod_req(&req, ac->module->ldb, ac->part_req, + ac->req->op.mod.message, + ac->req->controls, + ac, partition_req_callback, + ac->req); + break; + case LDB_DELETE: + ret = ldb_build_del_req(&req, ac->module->ldb, ac->part_req, + ac->req->op.del.dn, + ac->req->controls, + ac, partition_req_callback, + ac->req); + break; + case LDB_RENAME: + ret = ldb_build_rename_req(&req, ac->module->ldb, ac->part_req, + ac->req->op.rename.olddn, + ac->req->op.rename.newdn, + ac->req->controls, + ac, partition_req_callback, + ac->req); + break; + case LDB_EXTENDED: + ret = ldb_build_extended_req(&req, ac->module->ldb, + ac->part_req, + ac->req->op.extended.oid, + ac->req->op.extended.data, + ac->req->controls, + ac, partition_req_callback, + ac->req); + break; + default: + ldb_set_errstring(ac->module->ldb, + "Unsupported request type!"); + ret = LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (ret != LDB_SUCCESS) { + return ret; + } + + ac->part_req[ac->num_requests].req = req; + + if (ac->req->controls) { + req->controls = talloc_memdup(req, ac->req->controls, + talloc_get_size(ac->req->controls)); + if (req->controls == NULL) { + ldb_oom(ac->module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + } + + if (partition) { + ac->part_req[ac->num_requests].module = partition->module; + + ret = ldb_request_add_control(req, + DSDB_CONTROL_CURRENT_PARTITION_OID, + false, partition); + if (ret != LDB_SUCCESS) { + return ret; + } + + 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 (ldb_dn_compare_base(partition->dn, + req->op.search.base) != 0) { + req->op.search.base = partition->dn; + } + } + + } else { + /* make sure you put the NEXT module here, or + * partition_request() will simply loop forever on itself */ + ac->part_req[ac->num_requests].module = ac->module->next; + } + + ac->num_requests++; + + return LDB_SUCCESS; +} + +static int partition_call_first(struct partition_context *ac) +{ + return partition_request(ac->part_req[0].module, ac->part_req[0].req); +} + +/** + * 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_prep_request(ac, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + for (i=0; data && data->partitions && data->partitions[i]; i++) { + ret = partition_prep_request(ac, data->partitions[i]); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + /* fire the first one */ + return partition_call_first(ac); +} + +/** + * 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) +{ + struct partition_context *ac; + unsigned i; + int ret; + struct dsdb_control_current_partition *partition; + struct partition_private_data *data = talloc_get_type(module->private_data, + struct partition_private_data); + if (!data || !data->partitions) { + return ldb_next_request(module, req); + } + + 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) { + + ac = partition_init_ctx(module, req); + 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); + } + + ac = partition_init_ctx(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* we need to add a control but we never touch the original request */ + ret = partition_prep_request(ac, partition); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* fire the first one */ + return partition_call_first(ac); +} + +/* 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; + } + + /* + * for now pass down the LDB_CONTROL_SEARCH_OPTIONS_OID control + * down as uncritical to make windows 2008 dcpromo happy. + */ + if (search_control) { + search_control->critical = 0; + } + + /* 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_ctx(module, req); + 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++) { + bool match = false, stop = false; + /* Find all partitions under the search base + + we match if: + + 1) the DN we are looking for exactly matches the partition + or + 2) the DN we are looking for is a parent of the partition and it isn't + a scope base search + or + 3) the DN we are looking for is a child of the partition + */ + if (ldb_dn_compare(data->partitions[i]->dn, req->op.search.base) == 0) { + match = true; + if (req->op.search.scope == LDB_SCOPE_BASE) { + stop = true; + } + } + if (!match && + (ldb_dn_compare_base(req->op.search.base, data->partitions[i]->dn) == 0 && + req->op.search.scope != LDB_SCOPE_BASE)) { + match = true; + } + if (!match && + ldb_dn_compare_base(data->partitions[i]->dn, req->op.search.base) == 0) { + match = true; + stop = true; /* note that this relies on partition ordering */ + } + if (match) { + ret = partition_prep_request(ac, data->partitions[i]); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (stop) break; + } + + /* 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); + } + + /* fire the first one */ + return partition_call_first(ac); + + } 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) +{ + /* 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 (initialization) */ + 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; + } + + 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 (initialization) */ + 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 = data->partitions[i]->module; + PARTITION_FIND_OP(next, start_transaction); + + ret = next->ops->start_transaction(next); + if (ret != LDB_SUCCESS) { + /* Back it out, if it fails on one */ + for (i--; i >= 0; i--) { + next = data->partitions[i]->module; + PARTITION_FIND_OP(next, del_transaction); + + next->ops->del_transaction(next); + } + ldb_next_del_trans(module); + return ret; + } + } + return LDB_SUCCESS; +} + +/* end a transaction */ +static int partition_end_trans(struct ldb_module *module) +{ + int i, ret; + 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 = data->partitions[i]->module; + PARTITION_FIND_OP(next, end_transaction); + + ret = next->ops->end_transaction(next); + if (ret != LDB_SUCCESS) { + /* Back it out, if it fails on one */ + for (i--; i >= 0; i--) { + next = data->partitions[i]->module; + PARTITION_FIND_OP(next, del_transaction); + + next->ops->del_transaction(next); + } + ldb_next_del_trans(module); + return ret; + } + } + + return LDB_SUCCESS; +} + +/* 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 = data->partitions[i]->module; + PARTITION_FIND_OP(next, del_transaction); + + ret = next->ops->del_transaction(next); + if (ret != LDB_SUCCESS) { + ret2 = ret; + } + } + return ret2; +} + + +/* FIXME: This function is still semi-async */ +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); + struct ldb_seqnum_request *seq; + struct ldb_seqnum_result *seqr; + struct ldb_request *treq; + struct ldb_seqnum_request *tseq; + struct ldb_seqnum_result *tseqr; + struct ldb_extended *ext; + struct ldb_result *res; + + seq = talloc_get_type(req->op.extended.data, struct ldb_seqnum_request); + + switch (seq->type) { + case LDB_SEQ_NEXT: + case LDB_SEQ_HIGHEST_SEQ: + res = talloc_zero(req, struct ldb_result); + if (res == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + tseq = talloc_zero(res, struct ldb_seqnum_request); + if (tseq == NULL) { + talloc_free(res); + return LDB_ERR_OPERATIONS_ERROR; + } + tseq->type = seq->type; + + ret = ldb_build_extended_req(&treq, module->ldb, res, + LDB_EXTENDED_SEQUENCE_NUMBER, + tseq, + NULL, + res, + ldb_extended_default_callback, + NULL); + ret = ldb_next_request(module, treq); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(treq->handle, LDB_WAIT_ALL); + } + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + seqr = talloc_get_type(res->extended->data, + struct ldb_seqnum_result); + if (seqr->flags & LDB_SEQ_TIMESTAMP_SEQUENCE) { + timestamp_sequence = seqr->seq_num; + } else { + seq_number += seqr->seq_num; + } + talloc_free(res); + + /* Skip the lot if 'data' isn't here yet (initialistion) */ + for (i=0; data && data->partitions && data->partitions[i]; i++) { + + res = talloc_zero(req, struct ldb_result); + if (res == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + tseq = talloc_zero(res, struct ldb_seqnum_request); + if (tseq == NULL) { + talloc_free(res); + return LDB_ERR_OPERATIONS_ERROR; + } + tseq->type = seq->type; + + ret = ldb_build_extended_req(&treq, module->ldb, res, + LDB_EXTENDED_SEQUENCE_NUMBER, + tseq, + NULL, + res, + ldb_extended_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + ret = ldb_request_add_control(treq, + DSDB_CONTROL_CURRENT_PARTITION_OID, + false, data->partitions[i]); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + ret = partition_request(data->partitions[i]->module, treq); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + ret = ldb_wait(treq->handle, LDB_WAIT_ALL); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + tseqr = talloc_get_type(res->extended->data, + struct ldb_seqnum_result); + if (tseqr->flags & LDB_SEQ_TIMESTAMP_SEQUENCE) { + timestamp_sequence = MAX(timestamp_sequence, + tseqr->seq_num); + } else { + seq_number += tseqr->seq_num; + } + talloc_free(res); + } + /* fall through */ + case LDB_SEQ_HIGHEST_TIMESTAMP: + + res = talloc_zero(req, struct ldb_result); + if (res == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + tseq = talloc_zero(res, struct ldb_seqnum_request); + if (tseq == NULL) { + talloc_free(res); + return LDB_ERR_OPERATIONS_ERROR; + } + tseq->type = LDB_SEQ_HIGHEST_TIMESTAMP; + + ret = ldb_build_extended_req(&treq, module->ldb, res, + LDB_EXTENDED_SEQUENCE_NUMBER, + tseq, + NULL, + res, + ldb_extended_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + ret = ldb_next_request(module, treq); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + ret = ldb_wait(treq->handle, LDB_WAIT_ALL); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + tseqr = talloc_get_type(res->extended->data, + struct ldb_seqnum_result); + timestamp = tseqr->seq_num; + + talloc_free(res); + + /* Skip the lot if 'data' isn't here yet (initialistion) */ + for (i=0; data && data->partitions && data->partitions[i]; i++) { + + res = talloc_zero(req, struct ldb_result); + if (res == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + tseq = talloc_zero(res, struct ldb_seqnum_request); + if (tseq == NULL) { + talloc_free(res); + return LDB_ERR_OPERATIONS_ERROR; + } + tseq->type = LDB_SEQ_HIGHEST_TIMESTAMP; + + ret = ldb_build_extended_req(&treq, module->ldb, res, + LDB_EXTENDED_SEQUENCE_NUMBER, + tseq, + NULL, + res, + ldb_extended_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + ret = ldb_request_add_control(treq, + DSDB_CONTROL_CURRENT_PARTITION_OID, + false, data->partitions[i]); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + ret = partition_request(data->partitions[i]->module, treq); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + ret = ldb_wait(treq->handle, LDB_WAIT_ALL); + if (ret != LDB_SUCCESS) { + talloc_free(res); + return ret; + } + + tseqr = talloc_get_type(res->extended->data, + struct ldb_seqnum_result); + timestamp = MAX(timestamp, tseqr->seq_num); + + talloc_free(res); + } + + break; + } + + ext = talloc_zero(req, struct ldb_extended); + if (!ext) { + return LDB_ERR_OPERATIONS_ERROR; + } + seqr = talloc_zero(ext, struct ldb_seqnum_result); + if (seqr == NULL) { + talloc_free(ext); + return LDB_ERR_OPERATIONS_ERROR; + } + ext->oid = LDB_EXTENDED_SEQUENCE_NUMBER; + ext->data = seqr; + + switch (seq->type) { + case LDB_SEQ_NEXT: + case LDB_SEQ_HIGHEST_SEQ: + + /* Has someone above set a timebase sequence? */ + if (timestamp_sequence) { + seqr->seq_num = (((unsigned long long)timestamp << 24) | (seq_number & 0xFFFFFF)); + } else { + seqr->seq_num = seq_number; + } + + if (timestamp_sequence > seqr->seq_num) { + seqr->seq_num = timestamp_sequence; + seqr->flags |= LDB_SEQ_TIMESTAMP_SEQUENCE; + } + + seqr->flags |= LDB_SEQ_GLOBAL_SEQUENCE; + break; + case LDB_SEQ_HIGHEST_TIMESTAMP: + seqr->seq_num = timestamp; + break; + } + + if (seq->type == LDB_SEQ_NEXT) { + seqr->seq_num++; + } + + /* send request done */ + return ldb_module_done(req, NULL, ext, 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; + 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_ctx(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* we need to add a control but we never touch the original request */ + ret = partition_prep_request(ac, partition); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* fire the first one */ + return partition_call_first(ac); +} + + +/* extended */ +static int partition_extended(struct ldb_module *module, struct ldb_request *req) +{ + struct partition_private_data *data; + struct partition_context *ac; + + data = talloc_get_type(module->private_data, struct partition_private_data); + if (!data || !data->partitions) { + return ldb_next_request(module, req); + } + + if (strcmp(req->op.extended.oid, LDB_EXTENDED_SEQUENCE_NUMBER) == 0) { + return partition_sequence_number(module, req); + } + + 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_ctx(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + return partition_send_all(module, ac, req); +} + +static int partition_sort_compare(const void *v1, const void *v2) +{ + const struct dsdb_control_current_partition *p1; + const struct dsdb_control_current_partition *p2; + + p1 = *((struct dsdb_control_current_partition * const*)v1); + p2 = *((struct dsdb_control_current_partition * const*)v2); + + return ldb_dn_compare(p1->dn, p2->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, mem_ctx, &res, + ldb_dn_new(mem_ctx, module->ldb, "@PARTITION"), + LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(mem_ctx); + return ret; + } + 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 */ + qsort(data->partitions, partition_attributes->num_values, + sizeof(*data->partitions), partition_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; + req->callback = ldb_op_default_callback; + + ldb_set_timeout(module->ldb, req, 0); + + req->handle = ldb_handle_new(req, module->ldb); + if (req->handle == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_request(module->ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + 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; + } + } + } + + ret = ldb_mod_register_control(module, LDB_CONTROL_DOMAIN_SCOPE_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(module->ldb, LDB_DEBUG_ERROR, + "partition: Unable to register control with rootdse!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_mod_register_control(module, LDB_CONTROL_SEARCH_OPTIONS_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(module->ldb, LDB_DEBUG_ERROR, + "partition: Unable to register control with rootdse!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + talloc_free(mem_ctx); + return ldb_next_init(module); +} + +_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, + .start_transaction = partition_start_trans, + .end_transaction = partition_end_trans, + .del_transaction = partition_del_trans, +}; 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..56d4c4fe36 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -0,0 +1,2257 @@ +/* + ldb database module + + Copyright (C) Simo Sorce 2004-2008 + 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_module.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 { + + struct ldb_module *module; + struct ldb_request *req; + + struct ldb_request *dom_req; + struct ldb_reply *dom_res; + + struct ldb_reply *search_res; + + struct dom_sid *domain_sid; + struct domain_data *domain; +}; + +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 struct ldb_val *cleartext_utf8; + const struct ldb_val *cleartext_utf16; + struct ldb_val quoted_utf16; + 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; +}; + +/* Get the NT hash, and fill it in as an entry in the password history, + and specify it into io->g.nt_hash */ + +static int setup_nt_fields(struct setup_password_fields_io *io) +{ + struct ldb_context *ldb; + uint32_t i; + + io->g.nt_hash = io->n.nt_hash; + ldb = ldb_module_get_ctx(io->ac->module); + + 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(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; +} + +/* Get the LANMAN hash, and fill it in as an entry in the password history, + and specify it into io->g.lm_hash */ + +static int setup_lm_fields(struct setup_password_fields_io *io) +{ + struct ldb_context *ldb; + uint32_t i; + + io->g.lm_hash = io->n.lm_hash; + ldb = ldb_module_get_ctx(io->ac->module); + + 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(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) +{ + struct ldb_context *ldb; + krb5_error_code krb5_ret; + Principal *salt_principal; + krb5_salt salt; + krb5_keyblock key; + krb5_data cleartext_data; + + ldb = ldb_module_get_ctx(io->ac->module); + cleartext_data.data = io->n.cleartext_utf8->data; + cleartext_data.length = io->n.cleartext_utf8->length; + + /* 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(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(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(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(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(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(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_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + cleartext_data, + salt, + &key); + if (krb5_ret) { + ldb_asprintf_errstring(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(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_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_AES128_CTS_HMAC_SHA1_96, + cleartext_data, + salt, + &key); + if (krb5_ret) { + ldb_asprintf_errstring(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(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_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_DES_CBC_MD5, + cleartext_data, + salt, + &key); + if (krb5_ret) { + ldb_asprintf_errstring(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(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_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_DES_CBC_CRC, + cleartext_data, + salt, + &key); + if (krb5_ret) { + ldb_asprintf_errstring(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(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 ldb_context *ldb; + 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; + + ldb = ldb_module_get_ctx(io->ac->module); + + /* + * 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(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(io->ac, old_scp->data); + if (!blob.data) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* 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(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(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(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 ldb_context *ldb; + 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; + + ldb = ldb_module_get_ctx(io->ac->module); + + /* + * 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(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(io->ac, old_scp->data); + if (!blob.data) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* 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(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(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(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) +{ + struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module); + 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 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(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(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(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(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(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(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(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); + + 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(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, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length); + MD5Final(pdb->hashes[i].hash, &md5); + } + + return LDB_SUCCESS; +} + +static int setup_supplemental_field(struct setup_password_fields_io *io) +{ + struct ldb_context *ldb; + 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); + + ldb = ldb_module_get_ctx(io->ac->module); + + if (!io->n.cleartext_utf8) { + /* + * 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(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(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(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(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(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(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(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(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(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(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(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(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(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_utf16; + + ndr_err = ndr_push_struct_blob(&pcb_blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(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(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(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(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(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(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(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(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) +{ + struct ldb_context *ldb; + bool ok; + int ret; + size_t converted_pw_len; + + ldb = ldb_module_get_ctx(io->ac->module); + + /* + * refuse the change if someone want to change the cleartext + * and supply his own hashes at the same time... + */ + if ((io->n.cleartext_utf8 || io->n.cleartext_utf16) && (io->n.nt_hash || io->n.lm_hash)) { + ldb_asprintf_errstring(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_utf8 && io->n.cleartext_utf16) { + ldb_asprintf_errstring(ldb, + "setup_password_fields: " + "it's only allowed to set the cleartext password as userPassword or clearTextPasssword, not both at once"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (io->n.cleartext_utf8) { + char **cleartext_utf16_str; + struct ldb_val *cleartext_utf16_blob; + io->n.cleartext_utf16 = cleartext_utf16_blob = talloc(io->ac, struct ldb_val); + if (!io->n.cleartext_utf16) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + if (!convert_string_talloc_convenience(io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + CH_UTF8, CH_UTF16, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length, + (void **)&cleartext_utf16_str, &converted_pw_len, false)) { + ldb_asprintf_errstring(ldb, + "setup_password_fields: " + "failed to generate UTF16 password from cleartext UTF8 password"); + return LDB_ERR_OPERATIONS_ERROR; + } + *cleartext_utf16_blob = data_blob_const(cleartext_utf16_str, converted_pw_len); + } else if (io->n.cleartext_utf16) { + char *cleartext_utf8_str; + struct ldb_val *cleartext_utf8_blob; + io->n.cleartext_utf8 = cleartext_utf8_blob = talloc(io->ac, struct ldb_val); + if (!io->n.cleartext_utf8) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + if (!convert_string_talloc_convenience(io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + CH_UTF16MUNGED, CH_UTF8, io->n.cleartext_utf16->data, io->n.cleartext_utf16->length, + (void **)&cleartext_utf8_str, &converted_pw_len, false)) { + /* We can't bail out entirely, as these unconvertable passwords are frustratingly valid */ + io->n.cleartext_utf8 = NULL; + talloc_free(cleartext_utf8_blob); + } + *cleartext_utf8_blob = data_blob_const(cleartext_utf8_str, converted_pw_len); + } + if (io->n.cleartext_utf16) { + struct samr_Password *nt_hash; + nt_hash = talloc(io->ac, struct samr_Password); + if (!nt_hash) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + io->n.nt_hash = nt_hash; + + /* compute the new nt hash */ + mdfour(nt_hash->hash, io->n.cleartext_utf16->data, io->n.cleartext_utf16->length); + } + + if (io->n.cleartext_utf8) { + struct samr_Password *lm_hash; + char *cleartext_unix; + if (convert_string_talloc_convenience(io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + CH_UTF8, CH_UNIX, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length, + (void **)&cleartext_unix, &converted_pw_len, false)) { + lm_hash = talloc(io->ac, struct samr_Password); + if (!lm_hash) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* compute the new lm hash. */ + ok = E_deshash((char *)cleartext_unix, lm_hash->hash); + if (ok) { + io->n.lm_hash = lm_hash; + } else { + talloc_free(lm_hash->hash); + } + } + + 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 ph_context *ph_init_context(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ph_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct ph_context); + if (ac == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int ph_op_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ph_context *ac; + + ac = talloc_get_type(req->context, struct ph_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); +} + +static int password_hash_add_do_add(struct ph_context *ac); +static int ph_modify_callback(struct ldb_request *req, struct ldb_reply *ares); +static int password_hash_mod_search_self(struct ph_context *ac); +static int ph_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares); +static int password_hash_mod_do_mod(struct ph_context *ac); + +static int get_domain_data_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct domain_data *data; + struct ph_context *ac; + int ret; + char *tmp; + char *p; + + ac = talloc_get_type(req->context, struct ph_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (ac->domain != NULL) { + ldb_set_errstring(ldb, "Too many results"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + data = talloc_zero(ac, struct domain_data); + if (data == NULL) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + data->pwdProperties = samdb_result_uint(ares->message, "pwdProperties", 0); + data->store_cleartext = data->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT; + data->pwdHistoryLength = samdb_result_uint(ares->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(data, ares->message->dn); + if (!tmp) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + /* But it puts a trailing (or just before 'builtin') / on things, so kill that */ + p = strchr(tmp, '/'); + if (p) { + p[0] = '\0'; + } + + data->dns_domain = strlower_talloc(data, tmp); + if (data->dns_domain == NULL) { + ldb_oom(ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + data->realm = strupper_talloc(data, tmp); + if (data->realm == NULL) { + ldb_oom(ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + /* FIXME: NetbIOS name is *always* the first domain component ?? -SSS */ + p = strchr(tmp, '.'); + if (p) { + p[0] = '\0'; + } + data->netbios_domain = strupper_talloc(data, tmp); + if (data->netbios_domain == NULL) { + ldb_oom(ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(tmp); + ac->domain = data; + break; + + case LDB_REPLY_DONE: + + /* call the next step */ + switch (ac->req->operation) { + case LDB_ADD: + ret = password_hash_add_do_add(ac); + break; + + case LDB_MODIFY: + ret = password_hash_mod_do_mod(ac); + break; + + default: + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + } + + 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 */ + struct ldb_context *ldb; + static const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", NULL }; + char *filter; + + ldb = ldb_module_get_ctx(ac->module); + + filter = talloc_asprintf(ac, + "(&(objectSid=%s)(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain)))", + ldap_encode_ndr_dom_sid(ac, ac->domain_sid)); + if (filter == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + return ldb_build_search_req(&ac->dom_req, ldb, ac, + ldb_get_default_basedn(ldb), + LDB_SCOPE_SUBTREE, + filter, attrs, + NULL, + ac, get_domain_data_callback, + ac->req); +} + +static int password_hash_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ph_context *ac; + struct ldb_message_element *sambaAttr; + struct ldb_message_element *clearTextPasswordAttr; + struct ldb_message_element *ntAttr; + struct ldb_message_element *lmAttr; + int ret; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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, ldb, LOCAL_BASE), + req->op.add.message->dn) == 0) { + return ldb_next_request(module, req); + } + + /* nobody must touch these 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"); + clearTextPasswordAttr = ldb_msg_find_element(req->op.mod.message, "clearTextPassword"); + ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd"); + lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd"); + + if ((!sambaAttr) && (!clearTextPasswordAttr) && (!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(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(ldb, "mupltiple values for userPassword not allowed!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if (clearTextPasswordAttr && clearTextPasswordAttr->num_values > 1) { + ldb_set_errstring(ldb, "mupltiple values for clearTextPassword not allowed!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (ntAttr && (ntAttr->num_values > 1)) { + ldb_set_errstring(ldb, "mupltiple values for unicodePwd not allowed!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if (lmAttr && (lmAttr->num_values > 1)) { + ldb_set_errstring(ldb, "mupltiple values for dBCSPwd not allowed!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (sambaAttr && sambaAttr->num_values == 0) { + ldb_set_errstring(ldb, "userPassword must have a value!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (clearTextPasswordAttr && clearTextPasswordAttr->num_values == 0) { + ldb_set_errstring(ldb, "clearTextPassword must have a value!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (ntAttr && (ntAttr->num_values == 0)) { + ldb_set_errstring(ldb, "unicodePwd must have a value!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if (lmAttr && (lmAttr->num_values == 0)) { + ldb_set_errstring(ldb, "dBCSPwd must have a value!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + ac = ph_init_context(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* get user domain data */ + ac->domain_sid = samdb_result_sid_prefix(ac, req->op.add.message, "objectSid"); + if (ac->domain_sid == NULL) { + ldb_debug(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; + } + + return ldb_next_request(module, ac->dom_req); +} + +static int password_hash_add_do_add(struct ph_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *down_req; + struct smb_krb5_context *smb_krb5_context; + struct ldb_message *msg; + struct setup_password_fields_io io; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Some operations below require kerberos contexts */ + if (smb_krb5_init_context(ac, + ldb_get_event_context(ldb), + (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"), + &smb_krb5_context) != 0) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ZERO_STRUCT(io); + io.ac = ac; + io.domain = ac->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_utf8 = ldb_msg_find_ldb_val(msg, "userPassword"); + io.n.cleartext_utf16 = ldb_msg_find_ldb_val(msg, "clearTextPassword"); + 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_utf8) ldb_msg_remove_attr(msg, "userPassword"); + if (io.n.cleartext_utf16) ldb_msg_remove_attr(msg, "clearTextPassword"); + 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(ldb, ac, msg, + "unicodePwd", io.g.nt_hash); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (io.g.lm_hash) { + ret = samdb_msg_add_hash(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(ldb, ac, msg, + "pwdLastSet", + io.g.last_set); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = samdb_msg_add_uint(ldb, ac, msg, + "msDs-KeyVersionNumber", + io.g.kvno); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_build_add_req(&down_req, ldb, ac, + msg, + ac->req->controls, + ac, ph_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, down_req); +} + +static int password_hash_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ph_context *ac; + struct ldb_message_element *sambaAttr; + struct ldb_message_element *clearTextAttr; + struct ldb_message_element *ntAttr; + struct ldb_message_element *lmAttr; + struct ldb_message *msg; + struct ldb_request *down_req; + int ret; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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, 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"); + clearTextAttr = ldb_msg_find_element(req->op.mod.message, "clearTextPassword"); + 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 + * clearTextPassword 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) && (!clearTextAttr) && (!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 (clearTextAttr && (clearTextAttr->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; + } + + ac = ph_init_context(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* use a new message structure so that we can modify it */ + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* - remove any modification to the password from the first commit + * we will make the real modification later */ + if (sambaAttr) ldb_msg_remove_attr(msg, "userPassword"); + if (clearTextAttr) ldb_msg_remove_attr(msg, "clearTextPassword"); + if (ntAttr) ldb_msg_remove_attr(msg, "unicodePwd"); + if (lmAttr) ldb_msg_remove_attr(msg, "dBCSPwd"); + + /* if there was nothing else to be modified skip to next step */ + if (msg->num_elements == 0) { + return password_hash_mod_search_self(ac); + } + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, ph_modify_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +static int ph_modify_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ph_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct ph_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = password_hash_mod_search_self(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int ph_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct ph_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct ph_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + /* we are interested only in the single reply (base search) */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + + if (ac->search_res != NULL) { + ldb_set_errstring(ldb, "Too many results"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + /* if it is not an entry of type person this is an error */ + /* TODO: remove this when sambaPassword 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_module_done(ac->req, NULL, NULL, + LDB_ERR_OBJECT_CLASS_VIOLATION); + } + + ac->search_res = talloc_steal(ac, ares); + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + + /* get object domain sid */ + ac->domain_sid = samdb_result_sid_prefix(ac, + ac->search_res->message, + "objectSid"); + if (ac->domain_sid == NULL) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "can't handle entry without objectSid!\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + /* get user domain data */ + ret = build_domain_data_request(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL,ret); + } + + return ldb_next_request(ac->module, ac->dom_req); + + case LDB_REPLY_REFERRAL: + /*ignore anything else for now */ + break; + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int password_hash_mod_search_self(struct ph_context *ac) +{ + struct ldb_context *ldb; + static const char * const attrs[] = { "userAccountControl", "lmPwdHistory", + "ntPwdHistory", + "objectSid", "msDS-KeyVersionNumber", + "objectClass", "userPrincipalName", + "sAMAccountName", + "dBCSPwd", "unicodePwd", + "supplementalCredentials", + NULL }; + struct ldb_request *search_req; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->req->op.mod.message->dn, + LDB_SCOPE_BASE, + "(objectclass=*)", + attrs, + NULL, + ac, ph_mod_search_callback, + ac->req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, search_req); +} + +static int password_hash_mod_do_mod(struct ph_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *mod_req; + 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; + const struct ldb_val *quoted_utf16; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + /* use a new message structure so that we can modify it */ + msg = ldb_msg_new(ac); + if (msg == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* modify dn */ + msg->dn = ac->req->op.mod.message->dn; + + /* Some operations below require kerberos contexts */ + if (smb_krb5_init_context(ac, + ldb_get_event_context(ldb), + (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"), + &smb_krb5_context) != 0) { + return LDB_ERR_OPERATIONS_ERROR; + } + + orig_msg = discard_const(ac->req->op.mod.message); + searched_msg = ac->search_res->message; + + ZERO_STRUCT(io); + io.ac = ac; + io.domain = ac->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_utf8 = ldb_msg_find_ldb_val(orig_msg, "userPassword"); + io.n.cleartext_utf16 = ldb_msg_find_ldb_val(orig_msg, "clearTextPassword"); + + /* this rather strange looking piece of code is there to + handle a ldap client setting a password remotely using the + unicodePwd ldap field. The syntax is that the password is + in UTF-16LE, with a " at either end. Unfortunately the + unicodePwd field is also used to store the nt hashes + internally in Samba, and is used in the nt hash format on + the wire in DRS replication, so we have a single name for + two distinct values. The code below leaves us with a small + chance (less than 1 in 2^32) of a mixup, if someone manages + to create a MD4 hash which starts and ends in 0x22 0x00, as + that would then be treated as a UTF16 password rather than + a nthash */ + quoted_utf16 = ldb_msg_find_ldb_val(orig_msg, "unicodePwd"); + if (quoted_utf16 && + quoted_utf16->length >= 4 && + quoted_utf16->data[0] == '"' && + quoted_utf16->data[1] == 0 && + quoted_utf16->data[quoted_utf16->length-2] == '"' && + quoted_utf16->data[quoted_utf16->length-1] == 0) { + io.n.quoted_utf16.data = talloc_memdup(orig_msg, quoted_utf16->data+2, quoted_utf16->length-4); + io.n.quoted_utf16.length = quoted_utf16->length-4; + io.n.cleartext_utf16 = &io.n.quoted_utf16; + io.n.nt_hash = NULL; + } else { + 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(ldb, ac, msg, + "unicodePwd", io.g.nt_hash); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (io.g.lm_hash) { + ret = samdb_msg_add_hash(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(ldb, ac, msg, + "pwdLastSet", + io.g.last_set); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = samdb_msg_add_uint(ldb, ac, msg, + "msDs-KeyVersionNumber", + io.g.kvno); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_build_mod_req(&mod_req, ldb, ac, + msg, + ac->req->controls, + ac, ph_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, mod_req); +} + +_PUBLIC_ const struct ldb_module_ops ldb_password_hash_module_ops = { + .name = "password_hash", + .add = password_hash_add, + .modify = password_hash_modify, +}; 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..fefaef4755 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c @@ -0,0 +1,120 @@ +/* + 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 "ldb_module.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) +{ + struct ldb_context *ldb; + 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 + }; + + ldb = ldb_module_get_ctx(module); + + mem_ctx = talloc_new(module); + if (!mem_ctx) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + pdc_dn = samdb_base_dn(ldb); + if (!pdc_dn) { + ldb_debug(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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ldb_module_set_private(module, pdc_fsmo); + + ret = ldb_search(ldb, mem_ctx, &pdc_res, + pdc_dn, LDB_SCOPE_BASE, + pdc_attrs, NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + ldb_debug(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(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; + } + if (pdc_res->count == 0) { + ldb_debug(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(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(ldb, mem_ctx, pdc_res->msgs[0], "fSMORoleOwner"); + if (ldb_dn_compare(samdb_ntds_settings_dn(ldb), pdc_fsmo->master_dn) == 0) { + pdc_fsmo->we_are_master = true; + } else { + pdc_fsmo->we_are_master = false; + } + + if (ldb_set_opaque(ldb, "dsdb_pdc_fsmo", pdc_fsmo) != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + talloc_steal(module, pdc_fsmo); + + ldb_debug(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..72b47c308d --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/proxy.c @@ -0,0 +1,403 @@ +/* + 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_module.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; +}; + +struct proxy_ctx { + struct ldb_module *module; + struct ldb_request *req; + +#ifdef DEBUG_PROXY + int count; +#endif +}; + +/* + load the @PROXYINFO record +*/ +static int load_proxy_info(struct ldb_module *module) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct proxy_data *proxy = talloc_get_type(ldb_module_get_private(module), 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 LDB_SUCCESS; + } + + dn = ldb_dn_new(proxy, ldb, "@PROXYINFO"); + if (dn == NULL) { + goto failed; + } + ret = ldb_search(ldb, proxy, &res, dn, LDB_SCOPE_BASE, NULL, NULL); + talloc_free(dn); + if (ret != LDB_SUCCESS || res->count != 1) { + ldb_debug(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(ldb, LDB_DEBUG_FATAL, "Need url, olddn, newdn, oldstr, newstr, username and password in @PROXYINFO\n"); + goto failed; + } + + proxy->olddn = ldb_dn_new(proxy, ldb, olddn); + if (proxy->olddn == NULL) { + ldb_debug(ldb, LDB_DEBUG_FATAL, "Failed to explode olddn '%s'\n", olddn); + goto failed; + } + + proxy->newdn = ldb_dn_new(proxy, ldb, newdn); + if (proxy->newdn == NULL) { + ldb_debug(ldb, LDB_DEBUG_FATAL, "Failed to explode newdn '%s'\n", newdn); + goto failed; + } + + proxy->upstream = ldb_init(proxy, ldb_get_event_context(ldb)); + if (proxy->upstream == NULL) { + ldb_oom(ldb); + goto failed; + } + + proxy->oldstr = str_list_make(proxy, oldstr, ", "); + if (proxy->oldstr == NULL) { + ldb_oom(ldb); + goto failed; + } + + proxy->newstr = str_list_make(proxy, newstr, ", "); + if (proxy->newstr == NULL) { + ldb_oom(ldb); + goto failed; + } + + /* setup credentials for connection */ + creds = cli_credentials_init(proxy->upstream); + if (creds == NULL) { + ldb_oom(ldb); + goto failed; + } + cli_credentials_guess(creds, ldb_get_opaque(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(ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url); + goto failed; + } + + ldb_debug(ldb, LDB_DEBUG_TRACE, "proxy connected to %s\n", url); + + talloc_free(res); + + return LDB_SUCCESS; + +failed: + talloc_free(res); + talloc_free(proxy->olddn); + talloc_free(proxy->newdn); + talloc_free(proxy->upstream); + proxy->upstream = NULL; + return LDB_ERR_OPERATIONS_ERROR; +} + + +/* + 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 proxy_data *proxy, struct ldb_message *msg, struct ldb_val *v) +{ + 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(TALLOC_CTX *mem_ctx, + struct proxy_data *proxy, + struct ldb_parse_tree *tree) +{ + int i; + char *expression = ldb_filter_from_tree(mem_ctx, 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(mem_ctx, &v, proxy->newstr[i], proxy->oldstr[i]); + return ldb_parse_tree(mem_ctx, (const char *)v.data); + } + return tree; +} + + + +/* + convert a returned record +*/ +static void proxy_convert_record(struct ldb_context *ldb, + struct proxy_data *proxy, + struct ldb_message *msg) +{ + int attr, v; + + /* fix the message DN */ + if (ldb_dn_compare_base(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(proxy, 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(proxy, msg, &msg->elements[attr].values[v]); + } + } +} + +static int proxy_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct proxy_data *proxy; + struct proxy_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct proxy_ctx); + ldb = ldb_module_get_ctx(ac->module); + proxy = talloc_get_type(ldb_module_get_private(module), struct proxy_data); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + /* Only entries are interesting, and we only want the olddn */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + +#ifdef DEBUG_PROXY + ac->count++; +#endif + proxy_convert_record(ldb, proxy, ares->message); + ret = ldb_module_send_entry(ac->req, ares->message, ares->controls); + break; + + case LDB_REPLY_REFERRAL: + + /* ignore remote referrals */ + break; + + case LDB_REPLY_DONE: + +#ifdef DEBUG_PROXY + printf("# record %d\n", ac->count+1); +#endif + + return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS); + } + + talloc_free(ares); + return ret; +} + +/* search */ +static int proxy_search_bytree(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct proxy_ctx *ac; + struct ldb_parse_tree *newtree; + struct proxy_data *proxy = talloc_get_type(ldb_module_get_private(module), struct proxy_data); + struct ldb_request *newreq; + struct ldb_dn *base; + int ret, i; + + ldb = ldb_module_get_ctx(module); + + 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) != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* see if the dn is within olddn */ + if (ldb_dn_compare_base(proxy->newdn, req->op.search.base) != 0) { + goto passthru; + } + + ac = talloc(req, struct proxy_ctx); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->module = module; + ac->req = req; +#ifdef DEBUG_PROXY + ac->count = 0; +#endif + + newtree = proxy_convert_tree(ac, proxy, req->op.search.tree); + + /* convert the basedn of this search */ + base = ldb_dn_copy(ac, req->op.search.base); + if (base == NULL) { + goto failed; + } + ldb_dn_remove_base_components(base, ldb_dn_get_comp_num(proxy->newdn)); + ldb_dn_add_base(base, proxy->olddn); + + ldb_debug(ldb, LDB_DEBUG_FATAL, "proxying: '%s' with dn '%s' \n", + ldb_filter_from_tree(ac, 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(ldb, LDB_DEBUG_FATAL, "attr: '%s'\n", req->op.search.attrs[i]); + } + + ret = ldb_build_search_req_ex(&newreq, ldb, ac, + base, req->op.search.scope, + newtree, req->op.search.attrs, + req->controls, + ac, proxy_search_callback, + req); + + /* FIXME: warning, need a real event system hooked up for this to work properly, + * for now this makes the module *not* ASYNC */ + ret = ldb_request(proxy->upstream, newreq); + if (ret != LDB_SUCCESS) { + ldb_set_errstring(ldb, ldb_errstring(proxy->upstream)); + } + ret = ldb_wait(newreq->handle, LDB_WAIT_ALL); + if (ret != LDB_SUCCESS) { + ldb_set_errstring(ldb, ldb_errstring(proxy->upstream)); + } + return ret; + +failed: + ldb_debug(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..5ce69a26a2 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/ranged_results.c @@ -0,0 +1,252 @@ +/* + 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 "includes.h" +#include "ldb_module.h" + +struct rr_context { + struct ldb_module *module; + struct ldb_request *req; +}; + +static struct rr_context *rr_init_context(struct ldb_module *module, + struct ldb_request *req) +{ + struct rr_context *ac; + + ac = talloc_zero(req, struct rr_context); + if (ac == NULL) { + ldb_set_errstring(ldb_module_get_ctx(module), "Out of Memory"); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int rr_search_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct rr_context *ac; + int i, j; + + ac = talloc_get_type(req->context, struct rr_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type == LDB_REPLY_REFERRAL) { + return ldb_module_send_referral(ac->req, ares->referral); + } + + if (ares->type == LDB_REPLY_DONE) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + /* LDB_REPLY_ENTRY */ + + /* Find those that are range requests from the attribute list */ + for (i = 0; ac->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(ac->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) { + if (sscanf(p, ";range=%u-*", &start) == 1) { + end = (unsigned int)-1; + } else { + continue; + } + } + new_attr = talloc_strndup(ac->req, + ac->req->op.search.attrs[i], + (size_t)(p - ac->req->op.search.attrs[i])); + + if (!new_attr) { + ldb_oom(ldb); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + el = ldb_msg_find_element(ares->message, new_attr); + talloc_free(new_attr); + if (!el) { + continue; + } + 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_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + } + /* If start is greater then where we are 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_module_done(ac->req, NULL, NULL, + 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_module_done(ac->req, NULL, NULL, + 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_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + } + + return ldb_module_send_entry(ac->req, ares->message, ares->controls); +} + +/* search */ +static int rr_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + int i; + unsigned int start, end; + const char **new_attrs = NULL; + bool found_rr = false; + struct ldb_request *down_req; + struct rr_context *ac; + int ret; + + ldb = ldb_module_get_ctx(module); + + /* 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(new_attrs[i], ';'); + if (!p) { + continue; + } + if (strncasecmp(p, ";range=", strlen(";range=")) != 0) { + continue; + } + end = (unsigned int)-1; + if (sscanf(p, ";range=%u-*", &start) != 1) { + if (sscanf(p, ";range=%u-%u", &start, &end) != 2) { + ldb_asprintf_errstring(ldb, + "range request error: " + "range request malformed"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + if (start > end) { + ldb_asprintf_errstring(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, new_attrs[i], + (size_t)(p - new_attrs[i])); + + if (!new_attrs[i]) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + } + + if (found_rr) { + ac = rr_init_context(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req_ex(&down_req, ldb, ac, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + new_attrs, + req->controls, + ac, rr_search_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(module, down_req); + } + + /* No change, just run the original request as if we were never here */ + talloc_free(new_attrs); + 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..41f4e8e7d5 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -0,0 +1,1466 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2004-2008 + 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 "ldb_module.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_request *req; + + const struct dsdb_schema *schema; + + struct dsdb_extended_replicated_objects *objs; + + /* the controls we pass down */ + struct ldb_control **controls; + + uint32_t index_current; + + struct ldb_message *search_msg; +}; + +static struct replmd_replicated_request *replmd_ctx_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct replmd_replicated_request); + if (ac == NULL) { + ldb_oom(ldb); + return NULL; + } + + ac->module = module; + ac->req = req; + return ac; +} + +/* + 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 LDB_SUCCESS; + } + + s = ldb_timestring(msg, t); + if (s == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (ldb_msg_add_string(msg, attr, s) != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + 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 LDB_SUCCESS; +} + +/* + 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 LDB_SUCCESS; + } + + if (ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)v) != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + 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 LDB_SUCCESS; +} + +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_op_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ac; + + ac = talloc_get_type(req->context, struct replmd_replicated_request); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "invalid ldb_reply_type in callback"); + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); +} + +static int replmd_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ac; + 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; + + /* do not manipulate our control entries */ + if (ldb_dn_is_special(req->op.add.message->dn)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + + ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_add\n"); + + schema = dsdb_get_schema(ldb); + if (!schema) { + ldb_debug_set(ldb, LDB_DEBUG_FATAL, + "replmd_modify: no dsdb_schema loaded"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + ac = replmd_ctx_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->schema = schema; + + if (ldb_msg_find_element(req->op.add.message, "objectGUID") != NULL) { + ldb_debug_set(ldb, LDB_DEBUG_ERROR, + "replmd_add: 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(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(ldb); + if (!our_invocation_id) { + ldb_debug_set(ldb, LDB_DEBUG_ERROR, + "replmd_add: unable to find invocationId\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* we have to copy the message as the caller might have it as a const */ + msg = ldb_msg_copy_shallow(ac, req->op.add.message); + if (msg == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* generated times */ + unix_to_nt_time(&now, t); + time_str = ldb_timestring(msg, t); + if (!time_str) { + 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) { + ldb_oom(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) { + ldb_oom(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(ldb, LDB_DEBUG_ERROR, + "replmd_add: attribute '%s' not defined in schema\n", + e->name); + 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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ndr_err = ndr_push_struct_blob(&nmd_value, msg, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &nmd, + (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* + * add the autogenerated values + */ + ret = ldb_msg_add_value(msg, "objectGUID", &guid_value, NULL); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_msg_add_string(msg, "whenChanged", time_str); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNCreated", seq_num); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNChanged", seq_num); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_msg_add_value(msg, "replPropertyMetaData", &nmd_value, NULL); + if (ret != LDB_SUCCESS) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* + * sort the attributes by attid before storing the object + */ + replmd_ldb_message_sort(msg, schema); + + ret = ldb_build_add_req(&down_req, ldb, ac, + msg, + req->controls, + ac, replmd_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* go on with the call chain */ + return ldb_next_request(module, down_req); +} + +static int replmd_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ac; + 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; + + /* do not manipulate our control entries */ + if (ldb_dn_is_special(req->op.mod.message->dn)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + + ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_modify\n"); + + schema = dsdb_get_schema(ldb); + if (!schema) { + ldb_debug_set(ldb, LDB_DEBUG_FATAL, + "replmd_modify: no dsdb_schema loaded"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + ac = replmd_ctx_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->schema = schema; + + /* we have to copy the message as the caller might have it as a const */ + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + talloc_free(ac); + 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) != LDB_SUCCESS) { + talloc_free(ac); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Get a sequence number from the backend */ + ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num); + if (ret == LDB_SUCCESS) { + if (add_uint64_element(msg, "uSNChanged", seq_num) != LDB_SUCCESS) { + talloc_free(ac); + 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 + */ + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, replmd_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + talloc_steal(down_req, msg); + + /* go on with the call chain */ + return ldb_next_request(module, down_req); +} + +static int replmd_replicated_request_error(struct replmd_replicated_request *ar, int ret) +{ + return 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 ret; +} + +static int replmd_replicated_apply_next(struct replmd_replicated_request *ar); + +static int replmd_replicated_apply_add_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ar = talloc_get_type(req->context, + struct replmd_replicated_request); + int ret; + + ldb = ldb_module_get_ctx(ar->module); + + if (!ares) { + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ar->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Invalid reply type\n!"); + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(ares); + ar->index_current++; + + ret = replmd_replicated_apply_next(ar); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ar->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +static int replmd_replicated_apply_add(struct replmd_replicated_request *ar) +{ + struct ldb_context *ldb; + struct ldb_request *change_req; + 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 + */ + + ldb = ldb_module_get_ctx(ar->module); + msg = ar->objs->objects[ar->index_current].msg; + md = ar->objs->objects[ar->index_current].meta_data; + + ret = ldb_sequence_number(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(ldb, msg, msg, "uSNCreated", seq_num); + if (ret != LDB_SUCCESS) { + return replmd_replicated_request_error(ar, ret); + } + + ret = samdb_msg_add_uint64(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(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(&change_req, + ldb, + ar, + msg, + ar->controls, + ar, + replmd_replicated_apply_add_callback, + ar->req); + if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret); + + return ldb_next_request(ar->module, change_req); +} + +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_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ar = talloc_get_type(req->context, + struct replmd_replicated_request); + int ret; + + ldb = ldb_module_get_ctx(ar->module); + + if (!ares) { + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ar->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Invalid reply type\n!"); + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(ares); + ar->index_current++; + + ret = replmd_replicated_apply_next(ar); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ar->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar) +{ + struct ldb_context *ldb; + struct ldb_request *change_req; + 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; + + ldb = ldb_module_get_ctx(ar->module); + 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->search_msg->dn) != 0) { + ldb_debug_set(ldb, LDB_DEBUG_FATAL, "replmd_replicated_apply_merge[%u]: rename not supported", + ar->index_current); + ldb_debug(ldb, LDB_DEBUG_FATAL, "%s => %s\n", + ldb_dn_get_linearized(ar->search_msg->dn), + ldb_dn_get_linearized(msg->dn)); + return replmd_replicated_request_werror(ar, WERR_NOT_SUPPORTED); + } + + ret = ldb_sequence_number(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->search_msg, "replPropertyMetaData"); + if (omd_value) { + ndr_err = ndr_pull_struct_blob(omd_value, ar, + lp_iconv_convenience(ldb_get_opaque(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, + 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(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(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: skip replace\n", + ar->index_current); + + ar->index_current++; + return replmd_replicated_apply_next(ar); + } + + ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n", + ar->index_current, msg->num_elements); + + /* + * when we know 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(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(&change_req, + ldb, + ar, + msg, + ar->controls, + ar, + replmd_replicated_apply_merge_callback, + ar->req); + if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret); + + return ldb_next_request(ar->module, change_req); +} + +static int replmd_replicated_apply_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct replmd_replicated_request *ar = talloc_get_type(req->context, + struct replmd_replicated_request); + int ret; + + if (!ares) { + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS && + ares->error != LDB_ERR_NO_SUCH_OBJECT) { + return ldb_module_done(ar->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + ar->search_msg = talloc_steal(ar, ares->message); + break; + + case LDB_REPLY_REFERRAL: + /* we ignore referrals */ + break; + + case LDB_REPLY_DONE: + if (ar->search_msg != NULL) { + ret = replmd_replicated_apply_merge(ar); + } else { + ret = replmd_replicated_apply_add(ar); + } + if (ret != LDB_SUCCESS) { + return ldb_module_done(ar->req, NULL, NULL, ret); + } + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar); + +static int replmd_replicated_apply_next(struct replmd_replicated_request *ar) +{ + struct ldb_context *ldb; + int ret; + char *tmp_str; + char *filter; + struct ldb_request *search_req; + + if (ar->index_current >= ar->objs->num_objects) { + /* done with it, go to the last op */ + return replmd_replicated_uptodate_vector(ar); + } + + ldb = ldb_module_get_ctx(ar->module); + ar->search_msg = NULL; + + tmp_str = ldb_binary_encode(ar, ar->objs->objects[ar->index_current].guid_value); + if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM); + + filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str); + if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM); + talloc_free(tmp_str); + + ret = ldb_build_search_req(&search_req, + ldb, + ar, + ar->objs->partition_dn, + LDB_SCOPE_SUBTREE, + filter, + NULL, + NULL, + ar, + replmd_replicated_apply_search_callback, + ar->req); + if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret); + + return ldb_next_request(ar->module, search_req); +} + +static int replmd_replicated_uptodate_modify_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct replmd_replicated_request *ar = talloc_get_type(req->context, + struct replmd_replicated_request); + ldb = ldb_module_get_ctx(ar->module); + + if (!ares) { + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ar->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Invalid reply type\n!"); + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(ares); + + return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS); +} + +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) +{ + struct ldb_context *ldb; + struct ldb_request *change_req; + 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; + + ldb = ldb_module_get_ctx(ar->module); + 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(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->search_msg, "replUpToDateVector"); + if (ouv_value) { + ndr_err = ndr_pull_struct_blob(ouv_value, ar, + lp_iconv_convenience(ldb_get_opaque(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, + 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(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); + if (!msg) return replmd_replicated_request_werror(ar, WERR_NOMEM); + msg->dn = ar->search_msg->dn; + + ndr_err = ndr_push_struct_blob(&nuv_value, msg, + lp_iconv_convenience(ldb_get_opaque(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->search_msg, "repsFrom"); + if (orf_el) { + for (i=0; i < orf_el->num_values; i++) { + struct repsFromToBlob *trf; + + trf = talloc(ar, 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(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(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(&change_req, + ldb, + ar, + msg, + ar->controls, + ar, + replmd_replicated_uptodate_modify_callback, + ar->req); + if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret); + + return ldb_next_request(ar->module, change_req); +} + +static int replmd_replicated_uptodate_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct replmd_replicated_request *ar = talloc_get_type(req->context, + struct replmd_replicated_request); + int ret; + + if (!ares) { + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS && + ares->error != LDB_ERR_NO_SUCH_OBJECT) { + return ldb_module_done(ar->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + ar->search_msg = talloc_steal(ar, ares->message); + break; + + case LDB_REPLY_REFERRAL: + /* we ignore referrals */ + break; + + case LDB_REPLY_DONE: + if (ar->search_msg == NULL) { + ret = replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR); + } else { + ret = replmd_replicated_uptodate_modify(ar); + } + if (ret != LDB_SUCCESS) { + return ldb_module_done(ar->req, NULL, NULL, ret); + } + } + + talloc_free(ares); + return LDB_SUCCESS; +} + + +static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar) +{ + struct ldb_context *ldb; + int ret; + static const char *attrs[] = { + "replUpToDateVector", + "repsFrom", + NULL + }; + struct ldb_request *search_req; + + ldb = ldb_module_get_ctx(ar->module); + ar->search_msg = NULL; + + ret = ldb_build_search_req(&search_req, + ldb, + ar, + ar->objs->partition_dn, + LDB_SCOPE_BASE, + "(objectClass=*)", + attrs, + NULL, + ar, + replmd_replicated_uptodate_search_callback, + ar->req); + if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret); + + return ldb_next_request(ar->module, search_req); +} + +static int replmd_extended_replicated_objects(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct dsdb_extended_replicated_objects *objs; + struct replmd_replicated_request *ar; + struct ldb_control **ctrls; + int ret; + + ldb = ldb_module_get_ctx(module); + + ldb_debug(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(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(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_ctx_init(module, req); + if (!ar) + return LDB_ERR_OPERATIONS_ERROR; + + ar->objs = objs; + ar->schema = dsdb_get_schema(ldb); + if (!ar->schema) { + ldb_debug_set(ldb, LDB_DEBUG_FATAL, "replmd_ctx_init: no loaded schema found\n"); + talloc_free(ar); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + ctrls = req->controls; + + if (req->controls) { + req->controls = talloc_memdup(ar, req->controls, + talloc_get_size(req->controls)); + if (!req->controls) return replmd_replicated_request_werror(ar, WERR_NOMEM); + } + + ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + ar->controls = req->controls; + req->controls = ctrls; + + return replmd_replicated_apply_next(ar); +} + +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); +} + +_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, +}; diff --git a/source4/dsdb/samdb/ldb_modules/rootdse.c b/source4/dsdb/samdb/ldb_modules/rootdse.c new file mode 100644 index 0000000000..9ae894d55f --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/rootdse.c @@ -0,0 +1,466 @@ +/* + Unix SMB/CIFS implementation. + + rootDSE ldb module + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Simo Sorce 2005-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 "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 ldb_context *ldb; + struct private_data *priv = talloc_get_type(ldb_module_get_private(module), struct private_data); + char **server_sasl; + const struct dsdb_schema *schema; + + ldb = ldb_module_get_ctx(module); + schema = dsdb_get_schema(ldb); + + msg->dn = ldb_dn_new(msg, 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(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(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(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(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(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(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(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; + struct ldb_request *req; +}; + +static struct rootdse_context *rootdse_init_context(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct rootdse_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct rootdse_context); + if (ac == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int rootdse_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct rootdse_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct rootdse_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* + * if the client explicit asks for the 'netlogon' attribute + * the reply_entry needs to be skipped + */ + if (ac->req->op.search.attrs && + ldb_attr_in_list(ac->req->op.search.attrs, "netlogon")) { + talloc_free(ares); + return LDB_SUCCESS; + } + + /* for each record returned post-process to add any dynamic + attributes that have been asked for */ + ret = rootdse_add_dynamic(ac->module, ares->message, + ac->req->op.search.attrs); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return ldb_module_send_entry(ac->req, ares->message, ares->controls); + + case LDB_REPLY_REFERRAL: + /* should we allow the backend to return referrals in this case + * ?? */ + break; + + case LDB_REPLY_DONE: + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int rootdse_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct rootdse_context *ac; + struct ldb_request *down_req; + int ret; + + ldb = ldb_module_get_ctx(module); + + /* 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 = rootdse_init_context(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* in our db we store the rootDSE with a DN of @ROOTDSE */ + ret = ldb_build_search_req(&down_req, ldb, ac, + ldb_dn_new(ac, ldb, "@ROOTDSE"), + LDB_SCOPE_BASE, + NULL, + req->op.search.attrs, + NULL,/* for now skip the controls from the client */ + ac, rootdse_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req) +{ + struct private_data *priv = talloc_get_type(ldb_module_get_private(module), 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_module_done(req, NULL, NULL, LDB_SUCCESS); +} + +static int rootdse_register_partition(struct ldb_module *module, struct ldb_request *req) +{ + struct private_data *priv = talloc_get_type(ldb_module_get_private(module), 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_module_done(req, NULL, NULL, 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 ldb_context *ldb; + struct private_data *data; + + ldb = ldb_module_get_ctx(module); + + 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; + ldb_module_set_private(module, data); + + ldb_set_default_dns(ldb); + + return ldb_next_init(module); +} + +static int rootdse_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + 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); + } + + ldb = ldb_module_get_ctx(module); + + /* + 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(ldb); + if (!schema_dn) { + ldb_reset_err_string(ldb); + ldb_debug(ldb, LDB_DEBUG_WARNING, + "rootdse_modify: no schema dn present: (skip ldb_extended call)\n"); + return ldb_next_request(module, req); + } + + ret = ldb_extended(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..59cb9de717 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/samba3sam.c @@ -0,0 +1,933 @@ +/* + 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_module.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); + out = ldb_val_dup(ctx, val); + + return out; +} + +static struct ldb_val lookup_homedir(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val) +{ + struct ldb_context *ldb; + struct passwd *pwd; + struct ldb_val retval; + + ldb = ldb_module_get_ctx(module); + + pwd = getpwnam((char *)val->data); + + if (!pwd) { + ldb_debug(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..65e36416f1 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -0,0 +1,1416 @@ +/* + SAM ldb module + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Simo Sorce 2004-2008 + + * 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 "ldb_module.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "../lib/util/util_ldb.h" +#include "ldb_wrap.h" + +struct samldb_ctx; + +typedef int (*samldb_step_fn_t)(struct samldb_ctx *); + +struct samldb_step { + struct samldb_step *next; + samldb_step_fn_t fn; +}; + +struct samldb_ctx { + struct ldb_module *module; + struct ldb_request *req; + + /* the resulting message */ + struct ldb_message *msg; + + /* used to apply templates */ + const char *type; + + /* used to find parent domain */ + struct ldb_dn *check_dn; + struct ldb_dn *domain_dn; + struct dom_sid *domain_sid; + uint32_t next_rid; + + /* generic storage, remember to zero it before use */ + struct ldb_reply *ares; + + /* holds the entry SID */ + struct dom_sid *sid; + + /* all the async steps necessary to complete the operation */ + struct samldb_step *steps; + struct samldb_step *curstep; +}; + +static struct samldb_ctx *samldb_ctx_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct samldb_ctx); + if (ac == NULL) { + ldb_oom(ldb); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int samldb_add_step(struct samldb_ctx *ac, samldb_step_fn_t fn) +{ + struct samldb_step *step; + + step = talloc_zero(ac, struct samldb_step); + if (step == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (ac->steps == NULL) { + ac->steps = step; + ac->curstep = step; + } else { + ac->curstep->next = step; + ac->curstep = step; + } + + step->fn = fn; + + return LDB_SUCCESS; +} + +static int samldb_first_step(struct samldb_ctx *ac) +{ + if (ac->steps == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->curstep = ac->steps; + return ac->curstep->fn(ac); +} + +static int samldb_next_step(struct samldb_ctx *ac) +{ + if (ac->curstep->next) { + ac->curstep = ac->curstep->next; + return ac->curstep->fn(ac); + } + + /* it is an error if the last step does not properly + * return to the upper module by itself */ + return LDB_ERR_OPERATIONS_ERROR; +} + +static int samldb_search_template_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* save entry */ + if (ac->ares != NULL) { + /* one too many! */ + ldb_set_errstring(ldb, + "Invalid number of results while searching " + "for template objects"); + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + + ac->ares = talloc_steal(ac, ares); + ret = LDB_SUCCESS; + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + ret = LDB_SUCCESS; + break; + + case LDB_REPLY_DONE: + + talloc_free(ares); + ret = samldb_next_step(ac); + break; + } + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +static int samldb_search_template(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + struct tevent_context *ev; + struct loadparm_context *lparm_ctx; + struct ldb_context *templates_ldb; + char *templates_ldb_path; + struct ldb_request *req; + struct ldb_dn *basedn; + void *opaque; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + opaque = ldb_get_opaque(ldb, "loadparm"); + lparm_ctx = talloc_get_type(opaque, struct loadparm_context); + if (lparm_ctx == NULL) { + ldb_set_errstring(ldb, + "Unable to find loadparm context\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + opaque = ldb_get_opaque(ldb, "templates_ldb"); + templates_ldb = talloc_get_type(opaque, struct ldb_context); + + /* make sure we have the templates ldb */ + if (!templates_ldb) { + templates_ldb_path = samdb_relative_path(ldb, ac, + "templates.ldb"); + if (!templates_ldb_path) { + ldb_set_errstring(ldb, + "samldb_init_template: ERROR: Failed " + "to contruct path for template db"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ev = ldb_get_event_context(ldb); + + templates_ldb = ldb_wrap_connect(ldb, ev, + lparm_ctx, templates_ldb_path, + NULL, NULL, 0, NULL); + talloc_free(templates_ldb_path); + + if (!templates_ldb) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (!talloc_reference(templates_ldb, ev)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_set_opaque(ldb, + "templates_ldb", templates_ldb); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + /* search template */ + basedn = ldb_dn_new_fmt(ac, templates_ldb, + "cn=Template%s,cn=Templates", ac->type); + if (basedn == NULL) { + ldb_set_errstring(ldb, + "samldb_init_template: ERROR: Failed " + "to contruct DN for template"); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* pull the template record */ + ret = ldb_build_search_req(&req, templates_ldb, ac, + basedn, LDB_SCOPE_BASE, + "(distinguishedName=*)", NULL, + NULL, + ac, samldb_search_template_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + talloc_steal(req, basedn); + ac->ares = NULL; + + return ldb_request(templates_ldb, req); +} + +static int samldb_apply_template(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + struct ldb_message_element *el; + struct ldb_message *msg; + int i, j; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + msg = ac->ares->message; + + for (i = 0; i < msg->num_elements; i++) { + el = &msg->elements[i]; + /* some elements should not be copied */ + 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, ac->msg, el->name, + (char *)el->values[j].data); + if (ret != LDB_SUCCESS) { + ldb_set_errstring(ldb, + "Failed adding template attribute\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + } + } + + return samldb_next_step(ac); +} + +static int samldb_get_parent_domain(struct samldb_ctx *ac); + +static int samldb_get_parent_domain_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + const char *nextRid; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* save entry */ + if (ac->domain_dn != NULL) { + /* one too many! */ + ldb_set_errstring(ldb, + "Invalid number of results while searching " + "for domain object"); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + nextRid = ldb_msg_find_attr_as_string(ares->message, + "nextRid", NULL); + if (nextRid == NULL) { + ldb_asprintf_errstring(ldb, + "while looking for domain above %s attribute nextRid not found in %s\n", + ldb_dn_get_linearized(ac->req->op.add.message->dn), + ldb_dn_get_linearized(ares->message->dn)); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + ac->next_rid = strtol(nextRid, NULL, 0); + + ac->domain_sid = samdb_result_dom_sid(ac, ares->message, + "objectSid"); + if (ac->domain_sid == NULL) { + ldb_set_errstring(ldb, + "error retrieving parent domain domain sid!\n"); + ret = LDB_ERR_CONSTRAINT_VIOLATION; + break; + } + ac->domain_dn = talloc_steal(ac, ares->message->dn); + + talloc_free(ares); + ret = LDB_SUCCESS; + ldb_reset_err_string(ldb); + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + ret = LDB_SUCCESS; + break; + + case LDB_REPLY_DONE: + + talloc_free(ares); + if (ac->domain_dn == NULL) { + /* search again */ + ret = samldb_get_parent_domain(ac); + } else { + /* found, go on */ + ret = samldb_next_step(ac); + } + break; + } + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +/* Find a domain object in the parents of a particular DN. */ +static int samldb_get_parent_domain(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + static const char * const attrs[3] = { "objectSid", "nextRid", NULL }; + struct ldb_request *req; + struct ldb_dn *dn; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + if (ac->check_dn == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + dn = ldb_dn_get_parent(ac, ac->check_dn); + if (dn == NULL) { + ldb_set_errstring(ldb, + "Unable to find parent domain object"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + ac->check_dn = dn; + + ret = ldb_build_search_req(&req, ldb, ac, + dn, LDB_SCOPE_BASE, + "(|(objectClass=domain)" + "(objectClass=builtinDomain)" + "(objectClass=samba4LocalDomain))", + attrs, + NULL, + ac, samldb_get_parent_domain_callback, + ac->req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, req); +} + +static int samldb_generate_samAccountName(struct ldb_message *msg) +{ + char *name; + + /* Format: $000000-000000000000 */ + + name = talloc_asprintf(msg, "$%.6X-%.6X%.6X", + (unsigned int)generate_random(), + (unsigned int)generate_random(), + (unsigned int)generate_random()); + if (name == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + return ldb_msg_add_steal_string(msg, "samAccountName", name); +} + +static int samldb_check_samAccountName_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct samldb_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + /* if we get an entry it means this samAccountName + * already exists */ + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_ENTRY_ALREADY_EXISTS); + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + ret = LDB_SUCCESS; + break; + + case LDB_REPLY_DONE: + + /* not found, go on */ + talloc_free(ares); + ret = samldb_next_step(ac); + break; + } + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +static int samldb_check_samAccountName(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + struct ldb_request *req; + const char *name; + char *filter; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + if (ldb_msg_find_element(ac->msg, "samAccountName") == NULL) { + ret = samldb_generate_samAccountName(ac->msg); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + name = ldb_msg_find_attr_as_string(ac->msg, "samAccountName", NULL); + if (name == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + filter = talloc_asprintf(ac, "samAccountName=%s", name); + if (filter == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&req, ldb, ac, + ac->domain_dn, LDB_SCOPE_SUBTREE, + filter, NULL, + NULL, + ac, samldb_check_samAccountName_callback, + ac->req); + talloc_free(filter); + if (ret != LDB_SUCCESS) { + return ret; + } + ac->ares = NULL; + return ldb_next_request(ac->module, req); +} + +static int samldb_check_samAccountType(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + unsigned int account_type; + unsigned int group_type; + unsigned int uac; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + /* make sure sAMAccountType is not specified */ + if (ldb_msg_find_element(ac->msg, "sAMAccountType") != NULL) { + ldb_asprintf_errstring(ldb, + "sAMAccountType must not be specified"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (strcmp("user", ac->type) == 0) { + uac = samdb_result_uint(ac->msg, "userAccountControl", 0); + if (uac == 0) { + ldb_asprintf_errstring(ldb, + "userAccountControl invalid"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } else { + account_type = samdb_uf2atype(uac); + ret = samdb_msg_add_uint(ldb, + ac->msg, ac->msg, + "sAMAccountType", + account_type); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } else + if (strcmp("group", ac->type) == 0) { + + group_type = samdb_result_uint(ac->msg, "groupType", 0); + if (group_type == 0) { + ldb_asprintf_errstring(ldb, + "groupType invalid"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } else { + account_type = samdb_gtype2atype(group_type); + ret = samdb_msg_add_uint(ldb, + ac->msg, ac->msg, + "sAMAccountType", + account_type); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + return samldb_next_step(ac); +} + +static int samldb_get_sid_domain_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + const char *nextRid; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* save entry */ + if (ac->next_rid != 0) { + /* one too many! */ + ldb_set_errstring(ldb, + "Invalid number of results while searching " + "for domain object"); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + nextRid = ldb_msg_find_attr_as_string(ares->message, + "nextRid", NULL); + if (nextRid == NULL) { + ldb_asprintf_errstring(ldb, + "attribute nextRid not found in %s\n", + ldb_dn_get_linearized(ares->message->dn)); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + ac->next_rid = strtol(nextRid, NULL, 0); + + ac->domain_dn = talloc_steal(ac, ares->message->dn); + + talloc_free(ares); + ret = LDB_SUCCESS; + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + ret = LDB_SUCCESS; + break; + + case LDB_REPLY_DONE: + + if (ac->next_rid == 0) { + ldb_asprintf_errstring(ldb, + "Unable to get nextRid from domain entry\n"); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + /* found, go on */ + ret = samldb_next_step(ac); + break; + } + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +/* Find a domain object in the parents of a particular DN. */ +static int samldb_get_sid_domain(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + static const char * const attrs[2] = { "nextRid", NULL }; + struct ldb_request *req; + char *filter; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + if (ac->sid == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->domain_sid = dom_sid_dup(ac, ac->sid); + if (!ac->domain_sid) { + return LDB_ERR_OPERATIONS_ERROR; + } + /* get the domain component part of the provided SID */ + ac->domain_sid->num_auths--; + + filter = talloc_asprintf(ac, "(&(objectSid=%s)" + "(|(objectClass=domain)" + "(objectClass=builtinDomain)" + "(objectClass=samba4LocalDomain)))", + ldap_encode_ndr_dom_sid(ac, ac->domain_sid)); + if (filter == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&req, ldb, ac, + ldb_get_default_basedn(ldb), + LDB_SCOPE_SUBTREE, + filter, attrs, + NULL, + ac, samldb_get_sid_domain_callback, + ac->req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + ac->next_rid = 0; + return ldb_next_request(ac->module, req); +} + +static bool samldb_msg_add_sid(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); +} + +static int samldb_new_sid(struct samldb_ctx *ac) +{ + + if (ac->domain_sid == NULL || ac->next_rid == 0) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->sid = dom_sid_add_rid(ac, ac->domain_sid, ac->next_rid + 1); + if (ac->sid == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if ( ! samldb_msg_add_sid(ac->msg, "objectSid", ac->sid)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + return samldb_next_step(ac); +} + +static int samldb_check_sid_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct samldb_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + /* if we get an entry it means an object with the + * requested sid exists */ + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_CONSTRAINT_VIOLATION); + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + + /* not found, go on */ + talloc_free(ares); + ret = samldb_next_step(ac); + break; + } + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +static int samldb_check_sid(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + const char *const attrs[2] = { "objectSid", NULL }; + struct ldb_request *req; + char *filter; + int ret; + + if (ac->sid == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ldb = ldb_module_get_ctx(ac->module); + + filter = talloc_asprintf(ac, "(objectSid=%s)", + ldap_encode_ndr_dom_sid(ac, ac->sid)); + if (filter == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&req, ldb, ac, + ldb_get_default_basedn(ldb), + LDB_SCOPE_SUBTREE, + filter, attrs, + NULL, + ac, samldb_check_sid_callback, + ac->req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, req); +} + +static int samldb_notice_sid_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "Invalid reply type!\n"); + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + + ret = samldb_next_step(ac); + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +/* If we are adding new users/groups, we need to update the nextRid + * attribute to be 'above' the new/incoming RID. Attempt to do it + *atomically. */ +static int samldb_notice_sid(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + uint32_t old_id, new_id; + struct ldb_request *req; + struct ldb_message *msg; + struct ldb_message_element *els; + struct ldb_val *vals; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + old_id = ac->next_rid; + new_id = ac->sid->sub_auths[ac->sid->num_auths - 1]; + + if (old_id >= new_id) { + /* no need to update the domain nextRid attribute */ + return samldb_next_step(ac); + } + + /* we do a delete and add as a single operation. That prevents + a race, in case we are not actually on a transaction db */ + msg = talloc_zero(ac, struct ldb_message); + if (msg == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + els = talloc_array(msg, struct ldb_message_element, 2); + if (els == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + vals = talloc_array(msg, struct ldb_val, 2); + if (vals == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + msg->dn = ac->domain_dn; + 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(msg, "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(vals, "%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(vals, "%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_build_mod_req(&req, ldb, ac, + msg, NULL, + ac, samldb_notice_sid_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, req); +} + +static int samldb_add_entry_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + + ac = talloc_get_type(req->context, struct samldb_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, + "Invalid reply type!\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + /* we exit the samldb module here */ + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); +} + +static int samldb_add_entry(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + struct ldb_request *req; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + ret = ldb_build_add_req(&req, ldb, ac, + ac->msg, + ac->req->controls, + ac, samldb_add_entry_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, req); +} + +static int samldb_fill_object(struct samldb_ctx *ac, const char *type) +{ + int ret; + + /* first look for the template */ + ac->type = type; + ret = samldb_add_step(ac, samldb_search_template); + if (ret != LDB_SUCCESS) return ret; + + /* then apply it */ + ret = samldb_add_step(ac, samldb_apply_template); + if (ret != LDB_SUCCESS) return ret; + + /* search for a parent domain objet */ + ac->check_dn = ac->req->op.add.message->dn; + ret = samldb_add_step(ac, samldb_get_parent_domain); + if (ret != LDB_SUCCESS) return ret; + + /* check if we have a valid samAccountName */ + ret = samldb_add_step(ac, samldb_check_samAccountName); + if (ret != LDB_SUCCESS) return ret; + + /* check account_type/group_type */ + ret = samldb_add_step(ac, samldb_check_samAccountType); + if (ret != LDB_SUCCESS) return ret; + + /* check if we have a valid SID */ + ac->sid = samdb_result_dom_sid(ac, ac->msg, "objectSid"); + if ( ! ac->sid) { + ret = samldb_add_step(ac, samldb_new_sid); + if (ret != LDB_SUCCESS) return ret; + } else { + ret = samldb_add_step(ac, samldb_get_sid_domain); + if (ret != LDB_SUCCESS) return ret; + } + + ret = samldb_add_step(ac, samldb_check_sid); + if (ret != LDB_SUCCESS) return ret; + + ret = samldb_add_step(ac, samldb_notice_sid); + if (ret != LDB_SUCCESS) return ret; + + /* finally proceed with adding the entry */ + ret = samldb_add_step(ac, samldb_add_entry); + if (ret != LDB_SUCCESS) return ret; + + return samldb_first_step(ac); + + /* TODO: userAccountControl, badPwdCount, codePage, + * countryCode, badPasswordTime, lastLogoff, lastLogon, + * pwdLastSet, primaryGroupID, accountExpires, logonCount */ + +} + +static int samldb_foreign_notice_sid_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + const char *nextRid; + const char *name; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* save entry */ + if (ac->next_rid != 0) { + /* one too many! */ + ldb_set_errstring(ldb, + "Invalid number of results while searching " + "for domain object"); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + nextRid = ldb_msg_find_attr_as_string(ares->message, + "nextRid", NULL); + if (nextRid == NULL) { + ldb_asprintf_errstring(ldb, + "while looking for forign sid %s attribute nextRid not found in %s\n", + dom_sid_string(ares, ac->sid), ldb_dn_get_linearized(ares->message->dn)); + ret = LDB_ERR_OPERATIONS_ERROR; + break; + } + + ac->next_rid = strtol(nextRid, NULL, 0); + + ac->domain_dn = talloc_steal(ac, ares->message->dn); + + name = samdb_result_string(ares->message, "name", NULL); + ldb_debug(ldb, LDB_DEBUG_TRACE, + "NOTE (strange but valid): Adding foreign SID " + "record with SID %s, but this domain (%s) is " + "not foreign in the database", + dom_sid_string(ares, ac->sid), name); + + talloc_free(ares); + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + + /* if this is a fake foreign SID, notice the SID */ + if (ac->domain_dn) { + ret = samldb_notice_sid(ac); + break; + } + + /* found, go on */ + ret = samldb_next_step(ac); + break; + } + +done: + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +/* Find a domain object in the parents of a particular DN. */ +static int samldb_foreign_notice_sid(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + static const char * const attrs[3] = { "nextRid", "name", NULL }; + struct ldb_request *req; + NTSTATUS status; + char *filter; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + if (ac->sid == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + status = dom_sid_split_rid(ac, ac->sid, &ac->domain_sid, NULL); + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + filter = talloc_asprintf(ac, "(&(objectSid=%s)(objectclass=domain))", + ldap_encode_ndr_dom_sid(ac, ac->domain_sid)); + if (filter == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&req, ldb, ac, + ldb_get_default_basedn(ldb), + LDB_SCOPE_SUBTREE, + filter, attrs, + NULL, + ac, samldb_foreign_notice_sid_callback, + ac->req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + ac->next_rid = 0; + return ldb_next_request(ac->module, req); +} + +static int samldb_fill_foreignSecurityPrincipal_object(struct samldb_ctx *ac) +{ + struct ldb_context *ldb; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + ac->sid = samdb_result_dom_sid(ac->msg, ac->msg, "objectSid"); + if (ac->sid == NULL) { + ac->sid = dom_sid_parse_talloc(ac->msg, + (const char *)ldb_dn_get_rdn_val(ac->msg->dn)->data); + if (!ac->sid) { + ldb_set_errstring(ldb, + "No valid found SID in " + "ForeignSecurityPrincipal CN!"); + talloc_free(ac); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if ( ! samldb_msg_add_sid(ac->msg, "objectSid", ac->sid)) { + talloc_free(ac); + return LDB_ERR_OPERATIONS_ERROR; + } + } + + /* first look for the template */ + ac->type = "foreignSecurityPrincipal"; + ret = samldb_add_step(ac, samldb_search_template); + if (ret != LDB_SUCCESS) return ret; + + /* then apply it */ + ret = samldb_add_step(ac, samldb_apply_template); + if (ret != LDB_SUCCESS) return ret; + + /* check we do not already have this SID */ + ret = samldb_add_step(ac, samldb_check_sid); + if (ret != LDB_SUCCESS) return ret; + + /* check if we need to notice this SID */ + ret = samldb_add_step(ac, samldb_foreign_notice_sid); + if (ret != LDB_SUCCESS) return ret; + + /* finally proceed with adding the entry */ + ret = samldb_add_step(ac, samldb_add_entry); + if (ret != LDB_SUCCESS) return ret; + + return samldb_first_step(ac); +} + +static int samldb_check_rdn(struct ldb_module *module, struct ldb_dn *dn) +{ + struct ldb_context *ldb; + const char *rdn_name; + + ldb = ldb_module_get_ctx(module); + rdn_name = ldb_dn_get_rdn_name(dn); + + if (strcasecmp(rdn_name, "cn") != 0) { + ldb_asprintf_errstring(ldb, + "Bad RDN (%s=) for samldb object, " + "should be CN=!\n", rdn_name); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + return LDB_SUCCESS; +} + +/* add_record */ +static int samldb_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct samldb_ctx *ac; + int ret; + + ldb = ldb_module_get_ctx(module); + ldb_debug(ldb, LDB_DEBUG_TRACE, "samldb_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); + } + + ac = samldb_ctx_init(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* build the new msg */ + ac->msg = ldb_msg_copy(ac, ac->req->op.add.message); + if (!ac->msg) { + talloc_free(ac); + ldb_debug(ldb, LDB_DEBUG_FATAL, + "samldb_add: ldb_msg_copy failed!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (samdb_find_attribute(ldb, ac->msg, + "objectclass", "computer") != NULL) { + + /* make sure the computer object also has the 'user' + * objectclass so it will be handled by the next call */ + ret = samdb_find_or_add_value(ldb, ac->msg, + "objectclass", "user"); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + } + + if (samdb_find_attribute(ldb, ac->msg, + "objectclass", "user") != NULL) { + + ret = samldb_check_rdn(module, ac->req->op.add.message->dn); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + + return samldb_fill_object(ac, "user"); + } + + if (samdb_find_attribute(ldb, ac->msg, + "objectclass", "group") != NULL) { + + ret = samldb_check_rdn(module, ac->req->op.add.message->dn); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + + return samldb_fill_object(ac, "group"); + } + + /* perhaps a foreignSecurityPrincipal? */ + if (samdb_find_attribute(ldb, ac->msg, + "objectclass", + "foreignSecurityPrincipal") != NULL) { + + ret = samldb_check_rdn(module, ac->req->op.add.message->dn); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + + return samldb_fill_foreignSecurityPrincipal_object(ac); + } + + talloc_free(ac); + + /* nothing matched, go on */ + return ldb_next_request(module, req); +} + +/* modify */ +static int samldb_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + 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); + } + + ldb = ldb_module_get_ctx(module); + + if (ldb_msg_find_element(req->op.mod.message, "sAMAccountType") != NULL) { + ldb_asprintf_errstring(ldb, "sAMAccountType must not be specified"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + /* TODO: do not modify original request, create a new one */ + + 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(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(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..edd451255e --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/schema_fsmo.c @@ -0,0 +1,499 @@ +/* + 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 "ldb_module.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 "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 int generate_extendedAttributeInfo(struct ldb_context *ldb, struct ldb_message *msg, + const struct dsdb_schema *schema); +static int generate_extendedClassInfo(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 + }, + { + .attr = "extendedAttributeInfo", + .fn = generate_extendedAttributeInfo + }, + { + .attr = "extendedClassInfo", + .fn = generate_extendedClassInfo + } +}; + +struct schema_fsmo_private_data { + struct ldb_dn *aggregate_dn; +}; + +struct schema_fsmo_search_data { + struct ldb_module *module; + struct ldb_request *req; + + const struct dsdb_schema *schema; +}; + +static int schema_fsmo_init(struct ldb_module *module) +{ + struct ldb_context *ldb; + 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; + + ldb = ldb_module_get_ctx(module); + schema_dn = samdb_schema_dn(ldb); + if (!schema_dn) { + ldb_reset_err_string(ldb); + ldb_debug(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(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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ldb_module_set_private(module, data); + + if (dsdb_get_schema(ldb)) { + return ldb_next_init(module); + } + + mem_ctx = talloc_new(module); + if (!mem_ctx) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_schema_from_schema_dn(mem_ctx, ldb, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + schema_dn, &schema, &error_string); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + ldb_reset_err_string(ldb); + ldb_debug(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(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(ldb, schema); + if (ret != LDB_SUCCESS) { + ldb_debug_set(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 ldb_context *ldb; + 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; + + ldb = ldb_module_get_ctx(module); + + /* special objects should always go through */ + if (ldb_dn_is_special(req->op.add.message->dn)) { + return ldb_next_request(module, req); + } + + /* replicated update should always go through */ + if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) { + return ldb_next_request(module, req); + } + + schema = dsdb_get_schema(ldb); + if (!schema) { + return ldb_next_request(module, req); + } + + if (!schema->fsmo.we_are_master) { + ldb_debug_set(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(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(ldb, schema, oid); + if (!W_ERROR_IS_OK(status)) { + ldb_debug_set(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_context *ldb; + struct ldb_dn *schema_dn; + struct dsdb_schema *schema; + char *error_string = NULL; + int ret; + TALLOC_CTX *mem_ctx; + + ldb = ldb_module_get_ctx(module); + + if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) != 0) { + return ldb_next_request(module, req); + } + + schema_dn = samdb_schema_dn(ldb); + if (!schema_dn) { + ldb_reset_err_string(ldb); + ldb_debug(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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_schema_from_schema_dn(mem_ctx, ldb, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + schema_dn, &schema, &error_string); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + ldb_reset_err_string(ldb); + ldb_debug(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(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(ldb, schema); + if (ret != LDB_SUCCESS) { + ldb_debug_set(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 *sclass; + int ret; + + for (sclass = schema->classes; sclass; sclass = sclass->next) { + ret = ldb_msg_add_string(msg, "objectClasses", schema_class_to_description(msg, sclass)); + 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 *sclass; + int ret; + + for (sclass = schema->classes; sclass; sclass = sclass->next) { + if (sclass->auxiliaryClass || sclass->systemAuxiliaryClass) { + char *ditcontentrule = schema_class_to_dITContentRule(msg, sclass, 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 LDB_SUCCESS; +} + +static int generate_extendedAttributeInfo(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) { + char *val = schema_attribute_to_extendedInfo(msg, attribute); + if (!val) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_msg_add_string(msg, "extendedAttributeInfo", val); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + return LDB_SUCCESS; +} + +static int generate_extendedClassInfo(struct ldb_context *ldb, + struct ldb_message *msg, + const struct dsdb_schema *schema) +{ + const struct dsdb_class *sclass; + int ret; + + for (sclass = schema->classes; sclass; sclass = sclass->next) { + char *val = schema_class_to_extendedInfo(msg, sclass); + if (!val) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_msg_add_string(msg, "extendedClassInfo", val); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + return LDB_SUCCESS; +} + +/* Add objectClasses, attributeTypes and dITContentRules from the + schema object (they are not stored in the database) + */ +static int schema_fsmo_search_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct schema_fsmo_search_data *ac; + struct schema_fsmo_private_data *mc; + int i, ret; + + ac = talloc_get_type(req->context, struct schema_fsmo_search_data); + mc = talloc_get_type(ldb_module_get_private(ac->module), struct schema_fsmo_private_data); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + /* Only entries are interesting, and we handle the case of the parent seperatly */ + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + if (ldb_dn_compare(ares->message->dn, mc->aggregate_dn) != 0) { + return ldb_module_send_entry(ac->req, ares->message, ares->controls); + } + + for (i=0; i < ARRAY_SIZE(generated_attrs); i++) { + if (ldb_attr_in_list(ac->req->op.search.attrs, generated_attrs[i].attr)) { + ret = generated_attrs[i].fn(ldb, ares->message, ac->schema); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + return ldb_module_send_entry(ac->req, ares->message, ares->controls); + + case LDB_REPLY_REFERRAL: + + return ldb_module_send_referral(ac->req, ares->referral); + + case LDB_REPLY_DONE: + + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + return LDB_SUCCESS; +} + +/* search */ +static int schema_fsmo_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + int i, ret; + struct schema_fsmo_search_data *search_context; + struct ldb_request *down_req; + struct dsdb_schema *schema = dsdb_get_schema(ldb); + + if (!schema || !ldb_module_get_private(module)) { + /* 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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + search_context->module = module; + search_context->req = req; + search_context->schema = schema; + + ret = ldb_build_search_req_ex(&down_req, ldb, search_context, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + req->op.search.attrs, + req->controls, + search_context, schema_fsmo_search_callback, + req); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + + return ldb_next_request(module, down_req); +} + + +_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..d619558c21 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/show_deleted.c @@ -0,0 +1,159 @@ +/* + 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 right control is there + * + * Author: Stefan Metzmacher + */ + +#include "includes.h" +#include "ldb/include/ldb_module.h" +#include "dsdb/samdb/samdb.h" + +/* search */ +struct show_deleted_search_request { + + struct ldb_module *module; + struct ldb_request *req; +}; + +static int show_deleted_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct show_deleted_search_request *ar; + + ar = talloc_get_type(req->context, struct show_deleted_search_request); + + if (!ares) { + return ldb_module_done(ar->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ar->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + return ldb_module_send_entry(ar->req, ares->message, ares->controls); + + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral(ar->req, ares->referral); + + case LDB_REPLY_DONE: + return ldb_module_done(ar->req, ares->controls, + ares->response, LDB_SUCCESS); + + } + return LDB_SUCCESS; +} + +static int show_deleted_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct ldb_control *control; + struct ldb_control **saved_controls; + struct show_deleted_search_request *ar; + struct ldb_request *down_req; + char *old_filter; + char *new_filter; + int ret; + + ldb = ldb_module_get_ctx(module); + + ar = talloc_zero(req, struct show_deleted_search_request); + if (ar == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ar->module = module; + ar->req = req; + + /* check if there's a show deleted control */ + control = ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID); + + if ( ! control) { + old_filter = ldb_filter_from_tree(ar, req->op.search.tree); + new_filter = talloc_asprintf(ar, "(&(!(isDeleted=TRUE))%s)", + old_filter); + + ret = ldb_build_search_req(&down_req, ldb, ar, + req->op.search.base, + req->op.search.scope, + new_filter, + req->op.search.attrs, + req->controls, + ar, show_deleted_search_callback, + req); + + } else { + ret = ldb_build_search_req_ex(&down_req, ldb, ar, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + req->op.search.attrs, + req->controls, + ar, show_deleted_search_callback, + req); + } + if (ret != LDB_SUCCESS) { + return ret; + } + + /* 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; + } + + /* perform the search */ + return ldb_next_request(module, down_req); +} + +static int show_deleted_init(struct ldb_module *module) +{ + struct ldb_context *ldb; + int ret; + + ldb = ldb_module_get_ctx(module); + + ret = ldb_mod_register_control(module, LDB_CONTROL_SHOW_DELETED_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "extended_dn: Unable to register control with rootdse!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + 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..948241b094 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c @@ -0,0 +1,719 @@ +/* + 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_module.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" + +struct entryuuid_private { + struct ldb_context *ldb; + struct ldb_dn **base_dns; +}; + +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_data_blob(val, &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 ldb_val out = data_blob(NULL, 0); + struct GUID guid; + NTSTATUS status = GUID_from_data_blob(val, &guid); + if (!NT_STATUS_IS_OK(status)) { + return out; + } + return data_blob_string_const(GUID_string(ctx, &guid)); +} + +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); + struct GUID guid; + NTSTATUS status = GUID_from_data_blob(val, &guid); + if (!NT_STATUS_IS_OK(status)) { + return out; + } + return data_blob_string_const(NS_GUID_string(ctx, &guid)); +} + +/* 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); + out = ldb_val_dup(ctx, val); + + 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_context *ldb = ldb_module_get_ctx(module); + struct ldb_val out = data_blob(NULL, 0); + const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(ldb, "objectSid"); + + if (a->syntax->canonicalise_fn(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_context *ldb = ldb_module_get_ctx(module); + struct ldb_dn *dn; + struct ldb_val out = data_blob(NULL, 0); + const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(ldb, "objectCategory"); + + dn = ldb_dn_from_ldb_val(ctx, 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(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_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + unsigned long long *seq = (unsigned long long *)req->context; + + if (!ares) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_request_done(req, ares->error); + } + + 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]); + } + } + + if (ares->type == LDB_REPLY_DONE) { + return ldb_request_done(req, LDB_SUCCESS); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int entryuuid_sequence_number(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + int ret; + struct map_private *map_private; + struct entryuuid_private *entryuuid_private; + unsigned long long seq_num = 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 + }; + + struct ldb_seqnum_request *seq; + struct ldb_seqnum_result *seqr; + struct ldb_extended *ext; + + ldb = ldb_module_get_ctx(module); + + seq = talloc_get_type(req->op.extended.data, struct ldb_seqnum_request); + + map_private = talloc_get_type(ldb_module_get_private(module), 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(ldb, LDB_DEBUG_FATAL, + "entryuuid_sequence_number: 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); + + ret = ldb_build_search_req(&search_req, ldb, req, + partition->dn, LDB_SCOPE_BASE, + NULL, contextCSN_attr, NULL, + &seq_num, get_seq_callback, + NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + 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; + } + + ext = talloc_zero(req, struct ldb_extended); + if (!ext) { + return LDB_ERR_OPERATIONS_ERROR; + } + seqr = talloc_zero(req, struct ldb_seqnum_result); + if (seqr == NULL) { + talloc_free(ext); + return LDB_ERR_OPERATIONS_ERROR; + } + ext->oid = LDB_EXTENDED_SEQUENCE_NUMBER; + ext->data = seqr; + + switch (seq->type) { + case LDB_SEQ_HIGHEST_SEQ: + seqr->seq_num = seq_num; + break; + case LDB_SEQ_NEXT: + seqr->seq_num = seq_num; + seqr->seq_num++; + break; + case LDB_SEQ_HIGHEST_TIMESTAMP: + { + seqr->seq_num = (seq_num >> 24); + break; + } + } + seqr->flags = 0; + seqr->flags |= LDB_SEQ_TIMESTAMP_SEQUENCE; + seqr->flags |= LDB_SEQ_GLOBAL_SEQUENCE; + + /* send request done */ + return ldb_module_done(req, NULL, ext, LDB_SUCCESS); +} + +static int entryuuid_extended(struct ldb_module *module, struct ldb_request *req) +{ + if (strcmp(req->op.extended.oid, LDB_EXTENDED_SEQUENCE_NUMBER) == 0) { + return entryuuid_sequence_number(module, req); + } + + return ldb_next_request(module, req); +} + +_PUBLIC_ const struct ldb_module_ops ldb_entryuuid_module_ops = { + .name = "entryuuid", + .init_context = entryuuid_init, + .extended = entryuuid_extended, + LDB_MAP_OPS +}; + +_PUBLIC_ const struct ldb_module_ops ldb_nsuniqueid_module_ops = { + .name = "nsuniqueid", + .init_context = nsuniqueid_init, + .extended = entryuuid_extended, + 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..55a24549dd --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/subtree_delete.c @@ -0,0 +1,162 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2007 + Copyright (C) Simo Sorce <idra@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/>. +*/ + +/* + * Name: ldb + * + * Component: ldb subtree delete (prevention) module + * + * Description: Prevent deletion of a subtree in LDB + * + * Author: Andrew Bartlett + */ + +#include "ldb_module.h" + +struct subtree_delete_context { + struct ldb_module *module; + struct ldb_request *req; + + int num_children; +}; + +static struct subtree_delete_context *subdel_ctx_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct subtree_delete_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct subtree_delete_context); + if (ac == NULL) { + ldb_oom(ldb); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int subtree_delete_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct subtree_delete_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct subtree_delete_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + talloc_free(ares); + ac->num_children++; + break; + + case LDB_REPLY_REFERRAL: + + /* ignore */ + talloc_free(ares); + break; + + case LDB_REPLY_DONE: + + if (ac->num_children > 0) { + talloc_free(ares); + ldb_asprintf_errstring(ldb, + "Cannot delete %s, not a leaf node " + "(has %d children)\n", + ldb_dn_get_linearized(ac->req->op.del.dn), + ac->num_children); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_NOT_ALLOWED_ON_NON_LEAF); + } + + /* ok no children, let the original request through */ + ret = ldb_next_request(ac->module, ac->req); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + /* free our own context we are not going to be called back */ + talloc_free(ac); + } + return LDB_SUCCESS; +} + +static int subtree_delete(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + static const char * const attrs[2] = { "distinguishedName", NULL }; + struct ldb_request *search_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); + } + + ldb = ldb_module_get_ctx(module); + + /* 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 = subdel_ctx_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* we do not really need to find all descendents, + * if there is even one single direct child, that's + * enough to bail out */ + ret = ldb_build_search_req(&search_req, ldb, ac, + req->op.del.dn, LDB_SCOPE_ONELEVEL, + "(objectClass=*)", attrs, + req->controls, + ac, subtree_delete_search_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, search_req); +} + +const struct ldb_module_ops ldb_subtree_delete_module_ops = { + .name = "subtree_delete", + .del = subtree_delete, +}; 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..e2f6b1d059 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/subtree_rename.c @@ -0,0 +1,268 @@ +/* + 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_module.h" + +struct subren_msg_store { + struct subren_msg_store *next; + struct ldb_dn *olddn; + struct ldb_dn *newdn; +}; + +struct subtree_rename_context { + struct ldb_module *module; + struct ldb_request *req; + + struct subren_msg_store *list; + struct subren_msg_store *current; +}; + +static struct subtree_rename_context *subren_ctx_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct ldb_context *ldb; + struct subtree_rename_context *ac; + + ldb = ldb_module_get_ctx(module); + + ac = talloc_zero(req, struct subtree_rename_context); + if (ac == NULL) { + ldb_oom(ldb); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +static int subtree_rename_next_request(struct subtree_rename_context *ac); + +static int subtree_rename_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct subtree_rename_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct subtree_rename_context); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Invalid reply type!\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (ac->current == NULL) { + /* this was the last one */ + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); + } + + ret = subtree_rename_next_request(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int subtree_rename_next_request(struct subtree_rename_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *req; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + if (ac->current == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_rename_req(&req, ldb, ac->current, + ac->current->olddn, + ac->current->newdn, + ac->req->controls, + ac, subtree_rename_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + ac->current = ac->current->next; + + return ldb_next_request(ac->module, req); +} + +static int subtree_rename_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct subren_msg_store *store; + struct subtree_rename_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct subtree_rename_context); + + if (!ares || !ac->current) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + if (ldb_dn_compare(ares->message->dn, ac->list->olddn) == 0) { + /* this was already stored by the + * subtree_rename_search() */ + talloc_free(ares); + return LDB_SUCCESS; + } + + store = talloc_zero(ac, struct subren_msg_store); + if (store == NULL) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + ac->current->next = store; + ac->current = store; + + /* the first list element contains the base for the rename */ + store->olddn = talloc_steal(store, ares->message->dn); + store->newdn = ldb_dn_copy(store, store->olddn); + + if ( ! ldb_dn_remove_base_components(store->newdn, + ldb_dn_get_comp_num(ac->list->olddn))) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if ( ! ldb_dn_add_base(store->newdn, ac->list->newdn)) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + /* rewind ac->current */ + ac->current = ac->list; + + /* All dns set up, start with the first one */ + ret = subtree_rename_next_request(ac); + + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + break; + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +/* rename */ +static int subtree_rename(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + static const char *attrs[2] = { "distinguishedName", NULL }; + struct ldb_request *search_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); + } + + ldb = ldb_module_get_ctx(module); + + /* 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 = subren_ctx_init(module, req); + if (!ac) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* add this entry as the first to do */ + ac->current = talloc_zero(ac, struct subren_msg_store); + if (ac->current == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ac->current->olddn = req->op.rename.olddn; + ac->current->newdn = req->op.rename.newdn; + ac->list = ac->current; + + ret = ldb_build_search_req(&search_req, ldb, ac, + req->op.rename.olddn, + LDB_SCOPE_SUBTREE, + "(objectClass=*)", + attrs, + NULL, + ac, + subtree_rename_search_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, search_req); +} + +const struct ldb_module_ops ldb_subtree_rename_module_ops = { + .name = "subtree_rename", + .rename = subtree_rename, +}; 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..75aaeb7366 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py @@ -0,0 +1,1086 @@ +#!/usr/bin/python + +# Unix SMB/CIFS implementation. +# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2005-2008 +# 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 ldb +from ldb import SCOPE_DEFAULT, SCOPE_BASE, SCOPE_SUBTREE +from samba import Ldb, substitute_var +from samba.tests import LdbTestCase, TestCaseInTempDir, cmdline_loadparm +import samba.dcerpc.security +import samba.ndr + +datadir = os.path.join(os.path.dirname(__file__), + "../../../../../testdata/samba3") + +def read_datafile(filename): + return open(os.path.join(datadir, filename), 'r').read() + +def ldb_debug(l, text): + print text + + +class MapBaseTestCase(TestCaseInTempDir): + """Base test case for mapping tests.""" + + 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,asq,samldb,password_hash,operational,objectguid,rdn_name,samba3sam,partition"}) + + ldb.add({"dn": "@PARTITION", + "partition": ["%s:%s" % (s4.basedn, s4.url), + "%s:%s" % (s3.basedn, s3.url)], + "replicateEntries": ["@ATTRIBUTES", "@INDEXLIST"]}) + + def setUp(self): + super(MapBaseTestCase, self).setUp() + + def make_dn(basedn, rdn): + return "%s,sambaDomainName=TESTS,%s" % (rdn, basedn) + + def make_s4dn(basedn, rdn): + return "%s,%s" % (rdn, basedn) + + self.ldbfile = os.path.join(self.tempdir, "test.ldb") + self.ldburl = "tdb://" + self.ldbfile + + tempdir = self.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(lp=cmdline_loadparm) + self._dn = dn + + def dn(self, rdn): + return self._dn(self.basedn, rdn) + + def connect(self): + return self.db.connect(self.url) + + def setup_data(self, path): + self.add_ldif(read_datafile(path)) + + def subst(self, text): + return substitute_var(text, self.substvars) + + def add_ldif(self, ldif): + self.db.add_ldif(self.subst(ldif)) + + def modify_ldif(self, ldif): + self.db.modify_ldif(self.subst(ldif)) + + 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() + + def assertSidEquals(self, text, ndr_sid): + sid_obj1 = samba.ndr.ndr_unpack(samba.dcerpc.security.dom_sid, + str(ndr_sid[0])) + sid_obj2 = samba.dcerpc.security.dom_sid(text) + self.assertEquals(sid_obj1, sid_obj2) + + +class Samba3SamTestCase(MapBaseTestCase): + + def setUp(self): + super(Samba3SamTestCase, self).setUp() + ldb = Ldb(self.ldburl, lp=cmdline_loadparm) + self.samba3.setup_data("samba3.ldif") + self.templates.setup_data("provision_samba3sam_templates.ldif") + ldif = read_datafile("provision_samba3sam.ldif") + ldb.add_ldif(self.samba4.subst(ldif)) + self.setup_modules(ldb, self.samba3, self.samba4) + del ldb + self.ldb = Ldb(self.ldburl, lp=cmdline_loadparm) + + def test_search_non_mapped(self): + """Looking up by non-mapped attribute""" + msg = self.ldb.search(expression="(cn=Administrator)") + self.assertEquals(len(msg), 1) + self.assertEquals(msg[0]["cn"], "Administrator") + + def test_search_non_mapped(self): + """Looking up by mapped attribute""" + msg = self.ldb.search(expression="(name=Backup Operators)") + self.assertEquals(len(msg), 1) + self.assertEquals(str(msg[0]["name"]), "Backup Operators") + + def test_old_name_of_renamed(self): + """Looking up by old name of renamed attribute""" + msg = self.ldb.search(expression="(displayName=Backup Operators)") + self.assertEquals(len(msg), 0) + + def test_mapped_containing_sid(self): + """Looking up mapped entry containing SID""" + msg = self.ldb.search(expression="(cn=Replicator)") + self.assertEquals(len(msg), 1) + self.assertEquals(str(msg[0].dn), + "cn=Replicator,ou=Groups,dc=vernstok,dc=nl") + self.assertTrue("objectSid" in msg[0]) + self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552", + msg[0]["objectSid"]) + oc = set(msg[0]["objectClass"]) + self.assertEquals(oc, set(["group"])) + + def test_search_by_objclass(self): + """Looking up by objectClass""" + msg = self.ldb.search(expression="(|(objectClass=user)(cn=Administrator))") + self.assertEquals(set([str(m.dn) for m in msg]), + set(["unixName=Administrator,ou=Users,dc=vernstok,dc=nl", + "unixName=nobody,ou=Users,dc=vernstok,dc=nl"])) + + def test_s3sam_modify(self): + # Adding a record that will be fallbacked + self.ldb.add({"dn": "cn=Foo", + "foo": "bar", + "blah": "Blie", + "cn": "Foo", + "showInAdvancedViewOnly": "TRUE"} + ) + + # 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(str(msg[0]["showInAdvancedViewOnly"]), "TRUE") + self.assertEquals(str(msg[0]["foo"]), "bar") + self.assertEquals(str(msg[0]["blah"]), "Blie") + + # 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"}) + + # 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(str(msg[0]["cn"]), "Niemand") + self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim") + + # 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(str(msg[0]["cn"]), "Niemand") + self.assertEquals(str(msg[0]["unixName"]), "bin") + self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim") + + # Checking for existence of record (local || remote) + msg = self.ldb.search(expression="(|(unixName=bin)(sambaUnicodePwd=geheim))", + attrs=['unixName','cn','dn', 'sambaUnicodePwd']) + #print "got %d replies" % len(msg) + self.assertEquals(len(msg), 1) # TODO: should check with more records + self.assertEquals(str(msg[0]["cn"]), "Niemand") + self.assertEquals(str(msg[0]["unixName"]), "bin") + self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim") + + # Checking for data in destination database + msg = self.samba3.db.search(expression="(cn=Niemand)") + self.assertTrue(len(msg) >= 1) + self.assertEquals(str(msg[0]["sambaSID"]), + "S-1-5-21-4231626423-2410014848-2360679739-2001") + self.assertEquals(str(msg[0]["displayName"]), "Niemand") + + # Adding attribute... + self.ldb.modify_ldif(""" +dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl +changetype: modify +add: description +description: Blah +""") + + # Checking whether changes are still there... + msg = self.ldb.search(expression="(cn=Niemand)") + self.assertTrue(len(msg) >= 1) + self.assertEquals(str(msg[0]["cn"]), "Niemand") + self.assertEquals(str(msg[0]["description"]), "Blah") + + # Modifying attribute... + self.ldb.modify_ldif(""" +dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl +changetype: modify +replace: description +description: Blie +""") + + # Checking whether changes are still there... + msg = self.ldb.search(expression="(cn=Niemand)") + self.assertTrue(len(msg) >= 1) + self.assertEquals(str(msg[0]["description"]), "Blie") + + # Deleting attribute... + self.ldb.modify_ldif(""" +dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl +changetype: modify +delete: description +""") + + # Checking whether changes are no longer there... + msg = self.ldb.search(expression="(cn=Niemand)") + self.assertTrue(len(msg) >= 1) + self.assertTrue(not "description" in msg[0]) + + # Renaming record... + self.ldb.rename("cn=Niemand,cn=Users,dc=vernstok,dc=nl", + "cn=Niemand2,cn=Users,dc=vernstok,dc=nl") + + # 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") + + # Deleting record... + self.ldb.delete("cn=Niemand2,cn=Users,dc=vernstok,dc=nl") + + # 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, lp=cmdline_loadparm) + self.templates.setup_data("provision_samba3sam_templates.ldif") + ldif = read_datafile("provision_samba3sam.ldif") + ldb.add_ldif(self.samba4.subst(ldif)) + self.setup_modules(ldb, self.samba3, self.samba4) + del ldb + self.ldb = Ldb(self.ldburl, lp=cmdline_loadparm) + + def test_map_search(self): + """Running search tests on mapped data.""" + self.samba3.db.add({ + "dn": "sambaDomainName=TESTS," + self.samba3.basedn, + "objectclass": ["sambaDomain", "top"], + "sambaSID": "S-1-5-21-4231626423-2410014848-2360679739", + "sambaNextRid": "2000", + "sambaDomainName": "TESTS" + }) + + # Add a set of split records + self.ldb.add_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 + +""") + + self.ldb.add({ + "dn": self.samba4.dn("cn=Y"), + "objectClass": "top", + "cn": "Y", + "codePage": "x", + "revision": "x", + "dnsHostName": "y", + "nextRid": "y", + "lastLogon": "y", + "description": "x"}) + + self.ldb.add({ + "dn": self.samba4.dn("cn=Z"), + "objectClass": "top", + "cn": "Z", + "codePage": "x", + "revision": "y", + "dnsHostName": "z", + "nextRid": "y", + "lastLogon": "z", + "description": "y"}) + + # Add a set of remote records + + self.samba3.db.add({ + "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"}) + + self.samba3.db.add({ + "dn": self.samba3.dn("cn=B"), + "objectClass": "top", + "cn": "B", + "sambaNextRid": "x", + "sambaBadPasswordCount": "x", + "sambaLogonTime": "y", + "description": "x"}) + + self.samba3.db.add({ + "dn": self.samba3.dn("cn=C"), + "objectClass": "top", + "cn": "C", + "sambaNextRid": "x", + "sambaBadPasswordCount": "y", + "sambaLogonTime": "z", + "description": "y"}) + + # Testing search by DN + + # Search remote record by local DN + dn = self.samba4.dn("cn=A") + res = self.ldb.search(dn, scope=SCOPE_BASE, + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertTrue(not "dnsHostName" in res[0]) + self.assertEquals(str(res[0]["lastLogon"]), "x") + + # Search remote record by remote DN + dn = self.samba3.dn("cn=A") + res = self.samba3.db.search(dn, scope=SCOPE_BASE, + attrs=["dnsHostName", "lastLogon", "sambaLogonTime"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertTrue(not "dnsHostName" in res[0]) + self.assertTrue(not "lastLogon" in res[0]) + self.assertEquals(str(res[0]["sambaLogonTime"]), "x") + + # Search split record by local DN + dn = self.samba4.dn("cn=X") + res = self.ldb.search(dn, scope=SCOPE_BASE, + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertEquals(str(res[0]["dnsHostName"]), "x") + self.assertEquals(str(res[0]["lastLogon"]), "x") + + # Search split record by remote DN + dn = self.samba3.dn("cn=X") + res = self.samba3.db.search(dn, scope=SCOPE_BASE, + attrs=["dnsHostName", "lastLogon", "sambaLogonTime"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertTrue(not "dnsHostName" in res[0]) + self.assertTrue(not "lastLogon" in res[0]) + self.assertEquals(str(res[0]["sambaLogonTime"]), "x") + + # Testing search by attribute + + # Search by ignored attribute + res = self.ldb.search(expression="(revision=x)", scope=SCOPE_DEFAULT, + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 2) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(res[1]["lastLogon"]), "x") + + # Search by kept attribute + res = self.ldb.search(expression="(description=y)", + scope=SCOPE_DEFAULT, attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 2) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[0]["dnsHostName"]), "z") + self.assertEquals(str(res[0]["lastLogon"]), "z") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "z") + + # Search by renamed attribute + res = self.ldb.search(expression="(badPwdCount=x)", scope=SCOPE_DEFAULT, + attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + + # Search by converted attribute + # 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=*)", base=None, scope=SCOPE_DEFAULT, attrs=["dnsHostName", "lastLogon", "objectSid"]) + self.assertEquals(len(res), 3) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[0]["dnsHostName"]), "x") + self.assertEquals(str(res[0]["lastLogon"]), "x") + self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552", + res[0]["objectSid"]) + self.assertTrue("objectSid" in res[0]) + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552", + res[1]["objectSid"]) + self.assertTrue("objectSid" in res[1]) + + # Search by generated attribute + # In most cases, this even works when the mapping is missing + # a `convert_operator' by enumerating the remote db. + res = self.ldb.search(expression="(primaryGroupID=512)", + attrs=["dnsHostName", "lastLogon", "primaryGroupID"]) + 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(str(res[0]["lastLogon"]), "x") + self.assertEquals(str(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 */ + res = self.ldb.search(expression="(sambaBadPasswordCount=*)", + attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["dnsHostName"]), "x") + self.assertEquals(str(res[0]["lastLogon"]), "x") + self.assertEquals(str(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(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(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(str(res[0]["lastLogon"]), "y") + self.assertEquals(set(res[0]["objectClass"]), set(["top"])) + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(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(str(res[2]["lastLogon"]), "x") + self.assertEquals(res[2]["objectClass"][0], "user") + + # Testing search by parse tree + + # Search by conjunction of local attributes + res = self.ldb.search(expression="(&(codePage=x)(revision=x))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 2) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(res[1]["lastLogon"]), "x") + + # Search by conjunction of remote attributes + res = self.ldb.search(expression="(&(lastLogon=x)(description=x))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 2) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[0]["dnsHostName"]), "x") + self.assertEquals(str(res[0]["lastLogon"]), "x") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + + # Search by conjunction of local and remote attribute + res = self.ldb.search(expression="(&(codePage=x)(description=x))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 2) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(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 + res = self.ldb.search(expression="(|(revision=x)(dnsHostName=x))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 2) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(res[1]["lastLogon"]), "x") + + # Search by disjunction of remote attributes + res = self.ldb.search(expression="(|(badPwdCount=x)(lastLogon=x))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 3) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B")) + self.assertFalse("dnsHostName" in res[0]) + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A")) + self.assertFalse("dnsHostName" in res[2]) + self.assertEquals(str(res[2]["lastLogon"]), "x") + + # Search by disjunction of local and remote attribute + res = self.ldb.search(expression="(|(revision=x)(lastLogon=y))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 3) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B")) + self.assertFalse("dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "y") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[2]["dnsHostName"]), "x") + self.assertEquals(str(res[2]["lastLogon"]), "x") + + # Search by disjunction of local and remote attribute w/o match + res = self.ldb.search(expression="(|(codePage=y)(nextRid=z))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 0) + + # Search by negated local attribute + res = self.ldb.search(expression="(!(revision=x))", + attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[2]["dnsHostName"]), "z") + self.assertEquals(str(res[2]["lastLogon"]), "z") + self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[3]) + self.assertEquals(str(res[3]["lastLogon"]), "z") + + # Search by negated remote attribute + res = self.ldb.search(expression="(!(description=x))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 3) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[0]["dnsHostName"]), "z") + self.assertEquals(str(res[0]["lastLogon"]), "z") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "z") + + # Search by negated conjunction of local attributes + res = self.ldb.search(expression="(!(&(codePage=x)(revision=x)))", + attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[2]["dnsHostName"]), "z") + self.assertEquals(str(res[2]["lastLogon"]), "z") + self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[3]) + self.assertEquals(str(res[3]["lastLogon"]), "z") + + # Search by negated conjunction of remote attributes + res = self.ldb.search(expression="(!(&(lastLogon=x)(description=x)))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 5) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "y") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[2]["dnsHostName"]), "z") + self.assertEquals(str(res[2]["lastLogon"]), "z") + self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[3]) + self.assertEquals(str(res[3]["lastLogon"]), "z") + + # Search by negated conjunction of local and remote attribute + res = self.ldb.search(expression="(!(&(codePage=x)(description=x)))", + attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[2]["dnsHostName"]), "z") + self.assertEquals(str(res[2]["lastLogon"]), "z") + self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[3]) + self.assertEquals(str(res[3]["lastLogon"]), "z") + + # Search by negated disjunction of local attributes + res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B")) + self.assertTrue(not "dnsHostName" in res[0]) + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[1]) + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[2]["dnsHostName"]), "z") + self.assertEquals(str(res[2]["lastLogon"]), "z") + self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[3]) + self.assertEquals(str(res[3]["lastLogon"]), "z") + + # Search by negated disjunction of remote attributes + res = self.ldb.search(expression="(!(|(badPwdCount=x)(lastLogon=x)))", + attrs=["dnsHostName", "lastLogon"]) + self.assertEquals(len(res), 4) + self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y")) + self.assertEquals(str(res[0]["dnsHostName"]), "y") + self.assertEquals(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[1]["dnsHostName"]), "z") + self.assertEquals(str(res[1]["lastLogon"]), "z") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[2]) + self.assertEquals(str(res[2]["lastLogon"]), "z") + + # Search by negated disjunction of local and remote attribute + res = self.ldb.search(expression="(!(|(revision=x)(lastLogon=y)))", + attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["lastLogon"]), "x") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[1]["dnsHostName"]), "z") + self.assertEquals(str(res[1]["lastLogon"]), "z") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[2]) + self.assertEquals(str(res[2]["lastLogon"]), "z") + + # Search by complex parse tree + res = self.ldb.search(expression="(|(&(revision=x)(dnsHostName=x))(!(&(description=x)(nextRid=y)))(badPwdCount=y))", attrs=["dnsHostName", "lastLogon"]) + 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(str(res[0]["lastLogon"]), "y") + self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X")) + self.assertEquals(str(res[1]["dnsHostName"]), "x") + self.assertEquals(str(res[1]["lastLogon"]), "x") + self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A")) + self.assertTrue(not "dnsHostName" in res[2]) + self.assertEquals(str(res[2]["lastLogon"]), "x") + self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z")) + self.assertEquals(str(res[3]["dnsHostName"]), "z") + self.assertEquals(str(res[3]["lastLogon"]), "z") + self.assertEquals(str(res[4].dn), self.samba4.dn("cn=C")) + self.assertTrue(not "dnsHostName" in res[4]) + self.assertEquals(str(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(str(res[0]["foo"]), "bar") + self.assertEquals(str(res[0]["revision"]), "1") + self.assertEquals(str(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(str(res[0]["foo"]), "baz") + self.assertEquals(str(res[0]["revision"]), "1") + self.assertEquals(str(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(str(res[0]["foo"]), "baz") + self.assertEquals(str(res[0]["revision"]), "1") + self.assertEquals(str(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 + res = self.samba3.db.search(dn2, scope=SCOPE_BASE, + attrs=["description", "sambaBadPasswordCount", "sambaNextRid"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn2) + self.assertEquals(str(res[0]["description"]), "foo") + self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "3") + self.assertEquals(str(res[0]["sambaNextRid"]), "1001") + # Check in mapped db + attrs = ["description", "badPwdCount", "nextRid"] + res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs, expression="") + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertEquals(str(res[0]["description"]), "foo") + self.assertEquals(str(res[0]["badPwdCount"]), "3") + self.assertEquals(str(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 + res = self.ldb.search(dn, scope=SCOPE_BASE, + attrs=["description", "badPwdCount", "nextRid"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertEquals(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["badPwdCount"]), "4") + self.assertEquals(str(res[0]["nextRid"]), "1001") + # Check in remote db + res = self.samba3.db.search(dn2, scope=SCOPE_BASE, + attrs=["description", "sambaBadPasswordCount", "sambaNextRid"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn2) + self.assertEquals(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4") + self.assertEquals(str(res[0]["sambaNextRid"]), "1001") + + # Rename remote record + dn2 = self.samba4.dn("cn=toast") + self.ldb.rename(dn, dn2) + # Check in mapped db + dn = dn2 + res = self.ldb.search(dn, scope=SCOPE_BASE, + attrs=["description", "badPwdCount", "nextRid"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn) + self.assertEquals(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["badPwdCount"]), "4") + self.assertEquals(str(res[0]["nextRid"]), "1001") + # Check in remote db + dn2 = self.samba3.dn("cn=toast") + res = self.samba3.db.search(dn2, scope=SCOPE_BASE, + attrs=["description", "sambaBadPasswordCount", "sambaNextRid"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn2) + self.assertEquals(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4") + self.assertEquals(str(res[0]["sambaNextRid"]), "1001") + + # Delete remote record + self.ldb.delete(dn) + # Check in mapped db that it's removed + 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(str(res[0]["description"]), "test") + self.assertEquals(str(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(str(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(str(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(str(res[0]["description"]), "foo") + self.assertEquals(str(res[0]["badPwdCount"]), "3") + self.assertEquals(str(res[0]["nextRid"]), "1001") + self.assertEquals(str(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(str(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(str(res[0]["description"]), "foo") + self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "3") + self.assertEquals(str(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(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["badPwdCount"]), "4") + self.assertEquals(str(res[0]["nextRid"]), "1001") + self.assertEquals(str(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(str(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(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4") + self.assertEquals(str(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(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["badPwdCount"]), "4") + self.assertEquals(str(res[0]["nextRid"]), "1001") + self.assertEquals(str(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(str(res[0]["revision"]), "2") + # Check in remote db + dn2 = self.samba3.dn("cn=toast") + res = self.samba3.db.search(dn2, scope=SCOPE_BASE, + attrs=["description", "sambaBadPasswordCount", "sambaNextRid", + "revision"]) + self.assertEquals(len(res), 1) + self.assertEquals(str(res[0].dn), dn2) + self.assertEquals(str(res[0]["description"]), "test") + self.assertEquals(str(res[0]["sambaBadPasswordCount"]), "4") + self.assertEquals(str(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..f1b6863cdb --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/update_keytab.c @@ -0,0 +1,447 @@ +/* + 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_module.h" +#include "dlinklist.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "system/kerberos.h" + +struct dn_list { + struct cli_credentials *creds; + struct dn_list *prev, *next; +}; + +struct update_kt_private { + struct dn_list *changed_dns; +}; + +struct update_kt_ctx { + struct ldb_module *module; + struct ldb_request *req; + + struct ldb_dn *dn; + bool do_delete; + + struct ldb_reply *op_reply; + bool found; +}; + +static struct update_kt_ctx *update_kt_ctx_init(struct ldb_module *module, + struct ldb_request *req) +{ + struct update_kt_ctx *ac; + + ac = talloc_zero(req, struct update_kt_ctx); + if (ac == NULL) { + ldb_oom(ldb_module_get_ctx(module)); + return NULL; + } + + ac->module = module; + ac->req = req; + + return ac; +} + +/* FIXME: too many semi-async searches here for my taste, direct and indirect as + * cli_credentials_set_secrets() performs a sync ldb search. + * Just hope we are lucky and nothing breaks (using the tdb backend masks a lot + * of async issues). -SSS + */ +static int add_modified(struct ldb_module *module, struct ldb_dn *dn, bool do_delete) { + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct update_kt_private *data = talloc_get_type(ldb_module_get_private(module), 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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_search(ldb, data, &res, + dn, LDB_SCOPE_BASE, attrs, "%s", filter); + 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(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(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + cli_credentials_set_conf(item->creds, ldb_get_opaque(ldb, "loadparm")); + status = cli_credentials_set_secrets(item->creds, ldb_get_event_context(ldb), ldb_get_opaque(ldb, "loadparm"), ldb, NULL, filter); + talloc_free(filter); + if (NT_STATUS_IS_OK(status)) { + if (do_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; +} + +static int ukt_search_modified(struct update_kt_ctx *ac); + +static int update_kt_op_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct update_kt_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct update_kt_ctx); + ldb = ldb_module_get_ctx(ac->module); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + ldb_set_errstring(ldb, "Invalid request type!\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + if (ac->do_delete) { + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); + } + + ac->op_reply = talloc_steal(ac, ares); + + ret = ukt_search_modified(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } + + return LDB_SUCCESS; +} + +static int ukt_del_op(struct update_kt_ctx *ac) +{ + struct ldb_context *ldb; + struct ldb_request *down_req; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + ret = ldb_build_del_req(&down_req, ldb, ac, + ac->dn, + ac->req->controls, + ac, update_kt_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(ac->module, down_req); +} + +static int ukt_search_modified_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct update_kt_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct update_kt_ctx); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + + ac->found = true; + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + if (ac->found) { + /* do the dirty sync job here :/ */ + ret = add_modified(ac->module, ac->dn, ac->do_delete); + } + + if (ac->do_delete) { + ret = ukt_del_op(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, + NULL, NULL, ret); + } + break; + } + + return ldb_module_done(ac->req, ac->op_reply->controls, + ac->op_reply->response, LDB_SUCCESS); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int ukt_search_modified(struct update_kt_ctx *ac) +{ + struct ldb_context *ldb; + static const char * const attrs[] = { "distinguishedName", NULL }; + struct ldb_request *search_req; + int ret; + + ldb = ldb_module_get_ctx(ac->module); + + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->dn, LDB_SCOPE_BASE, + "(&(objectClass=kerberosSecret)" + "(privateKeytab=*))", attrs, + NULL, + ac, ukt_search_modified_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(ac->module, search_req); +} + + +/* add */ +static int update_kt_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct update_kt_ctx *ac; + struct ldb_request *down_req; + int ret; + + ldb = ldb_module_get_ctx(module); + + ac = update_kt_ctx_init(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->dn = req->op.add.message->dn; + + ret = ldb_build_add_req(&down_req, ldb, ac, + req->op.add.message, + req->controls, + ac, update_kt_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +/* modify */ +static int update_kt_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct update_kt_ctx *ac; + struct ldb_request *down_req; + int ret; + + ldb = ldb_module_get_ctx(module); + + ac = update_kt_ctx_init(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->dn = req->op.mod.message->dn; + + ret = ldb_build_mod_req(&down_req, ldb, ac, + req->op.mod.message, + req->controls, + ac, update_kt_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +/* delete */ +static int update_kt_delete(struct ldb_module *module, struct ldb_request *req) +{ + struct update_kt_ctx *ac; + + ac = update_kt_ctx_init(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->dn = req->op.del.dn; + ac->do_delete = true; + + return ukt_search_modified(ac); +} + +/* rename */ +static int update_kt_rename(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct update_kt_ctx *ac; + struct ldb_request *down_req; + int ret; + + ldb = ldb_module_get_ctx(module); + + ac = update_kt_ctx_init(module, req); + if (ac == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->dn = req->op.rename.newdn; + + ret = ldb_build_rename_req(&down_req, ldb, ac, + req->op.rename.olddn, + req->op.rename.newdn, + req->controls, + ac, update_kt_op_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +/* end a transaction */ +static int update_kt_end_trans(struct ldb_module *module) +{ + struct ldb_context *ldb; + struct update_kt_private *data = talloc_get_type(ldb_module_get_private(module), struct update_kt_private); + struct dn_list *p; + + ldb = ldb_module_get_ctx(module); + + for (p=data->changed_dns; p; p = p->next) { + int kret; + kret = cli_credentials_update_keytab(p->creds, ldb_get_event_context(ldb), ldb_get_opaque(ldb, "loadparm")); + if (kret != 0) { + talloc_free(data->changed_dns); + data->changed_dns = NULL; + ldb_asprintf_errstring(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(ldb_module_get_private(module), 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 ldb_context *ldb; + struct update_kt_private *data; + + ldb = ldb_module_get_ctx(module); + + data = talloc(module, struct update_kt_private); + if (data == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + data->changed_dns = NULL; + + ldb_module_set_private(module, data); + + 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..98ca6d03a1 --- /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 "../lib/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 tevent_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 tevent_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 tevent_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, msg, &res, basedn, LDB_SCOPE_BASE, NULL, "distinguishedName=*"); + 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 tevent_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..49dc14d74c --- /dev/null +++ b/source4/dsdb/samdb/samdb.h @@ -0,0 +1,130 @@ +/* + 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 tevent_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_CONTROL_REPLICATED_UPDATE_OID "1.3.6.1.4.1.7165.4.3.3" +/* DSDB_CONTROL_REPLICATED_UPDATE_OID has NULL data */ + +#define DSDB_CONTROL_DN_STORAGE_FORMAT_OID "1.3.6.1.4.1.7165.4.3.4" +/* DSDB_CONTROL_DN_STORAGE_FORMAT_OID has NULL data and behaves very + * much like LDB_CONTROL_EXTENDED_DN_OID when the DB stores an + * extended DN, and otherwise returns normal DNs */ + +#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" + +#define DSDB_OPENLDAP_DEREFERENCE_CONTROL "1.3.6.1.4.1.4203.666.5.16" + +struct dsdb_openldap_dereference { + const char *source_attribute; + const char **dereference_attribute; +}; + +struct dsdb_openldap_dereference_control { + struct dsdb_openldap_dereference **dereference; +}; + +struct dsdb_openldap_dereference_result { + const char *source_attribute; + const char *dereferenced_dn; + int num_attributes; + struct ldb_message_element *attributes; +}; + +struct dsdb_openldap_dereference_result_control { + struct dsdb_openldap_dereference_result **attributes; +}; + +#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..e9c6f4c527 --- /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 "../lib/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 tevent_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..f7d59a7c39 --- /dev/null +++ b/source4/dsdb/schema/schema.h @@ -0,0 +1,185 @@ +/* + 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)(struct ldb_context *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)(struct ldb_context *ldb, + 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..0ff8a72bcd --- /dev/null +++ b/source4/dsdb/schema/schema_description.c @@ -0,0 +1,414 @@ +/* + 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" +#include "librpc/ndr/libndr.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 *equality, + const char *substring, + const char *syntax, + bool single_value, bool operational, + uint32_t *range_lower, + uint32_t *range_upper, + const char *property_guid, + const char *property_set_guid, + bool indexed, bool system_only) +{ + 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 (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); + } + + if (syntax) { + 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); + } + + if (range_lower) { + schema_entry = talloc_asprintf_append(schema_entry, + "RANGE-LOWER '%u'%s", + *range_lower, seperator); + IF_NULL_FAIL_RET(schema_entry); + } + + if (range_upper) { + schema_entry = talloc_asprintf_append(schema_entry, + "RANGE-UPPER '%u'%s", + *range_upper, seperator); + IF_NULL_FAIL_RET(schema_entry); + } + + if (property_guid) { + schema_entry = talloc_asprintf_append(schema_entry, + "PROPERTY-GUID '%s'%s", + property_guid, seperator); + IF_NULL_FAIL_RET(schema_entry); + } + + if (property_set_guid) { + schema_entry = talloc_asprintf_append(schema_entry, + "PROPERTY-SET-GUID '%s'%s", + property_set_guid, seperator); + IF_NULL_FAIL_RET(schema_entry); + } + + if (indexed) { + schema_entry = talloc_asprintf_append(schema_entry, + "INDEXED%s", seperator); + IF_NULL_FAIL_RET(schema_entry); + } + + if (system_only) { + schema_entry = talloc_asprintf_append(schema_entry, + "SYSTEM-ONLY%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 char *syntax = attribute->syntax->ldap_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, talloc_asprintf(tmp_ctx, "'%s'", syntax), + attribute->isSingleValued, + attribute->systemOnly,/* TODO: is this correct? */ + NULL, NULL, NULL, NULL, + false, false); + talloc_free(tmp_ctx); + return schema_description; +} + +char *schema_attribute_to_extendedInfo(TALLOC_CTX *mem_ctx, const struct dsdb_attribute *attribute) +{ + char *schema_description; + 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, + false, false, + attribute->rangeLower, + attribute->rangeUpper, + GUID_hexstring(tmp_ctx, &attribute->schemaIDGUID), + GUID_hexstring(tmp_ctx, &attribute->attributeSecurityGUID), + (attribute->searchFlags & SEARCH_FLAG_ATTINDEX), + 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 *subClassOf, + int objectClassCategory, + const char **must, + const char **may, + const char *schemaHexGUID) +{ + 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 (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); + } + + if (schemaHexGUID) { + schema_entry = talloc_asprintf_append(schema_entry, + "CLASS-GUID '%s'%s", + schemaHexGUID, 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 *sclass) +{ + 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, + " ", + sclass->governsID_oid, + sclass->lDAPDisplayName, + NULL, + sclass->subClassOf, + sclass->objectClassCategory, + dsdb_attribute_list(tmp_ctx, + sclass, DSDB_SCHEMA_ALL_MUST), + dsdb_attribute_list(tmp_ctx, + sclass, DSDB_SCHEMA_ALL_MAY), + NULL); + talloc_free(tmp_ctx); + return schema_description; +} + +char *schema_class_to_dITContentRule(TALLOC_CTX *mem_ctx, const struct dsdb_class *sclass, + const struct dsdb_schema *schema) +{ + int i; + char *schema_description; + const char **aux_class_list = NULL; + const char **attrs; + const char **must_attr_list = NULL; + const 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, sclass->systemAuxiliaryClass); + aux_class_list = merge_attr_list(tmp_ctx, aux_class_list, sclass->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, + " ", + sclass->governsID_oid, + sclass->lDAPDisplayName, + (const char **)aux_class_list, + NULL, /* Must not specify a + * SUP (subclass) in + * ditContentRules + * per MS-ADTS + * 3.1.1.3.1.1.1 */ + -1, must_attr_list, may_attr_list, + NULL); + talloc_free(tmp_ctx); + return schema_description; +} + +char *schema_class_to_extendedInfo(TALLOC_CTX *mem_ctx, const struct dsdb_class *sclass) +{ + char *schema_description = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return NULL; + } + + schema_description + = schema_class_description(mem_ctx, + TARGET_AD_SCHEMA_SUBENTRY, + " ", + sclass->governsID_oid, + sclass->lDAPDisplayName, + NULL, + NULL, /* Must not specify a + * SUP (subclass) in + * ditContentRules + * per MS-ADTS + * 3.1.1.3.1.1.1 */ + -1, NULL, NULL, + GUID_hexstring(tmp_ctx, &sclass->schemaIDGUID)); + 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..a67aecd1e8 --- /dev/null +++ b/source4/dsdb/schema/schema_init.c @@ -0,0 +1,1471 @@ +/* + 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(mem_ctx, schema->schema_info); + W_ERROR_HAVE_NO_MEMORY(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, mem_ctx, &schema_res, schema_dn, LDB_SCOPE_BASE, schema_attrs, NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(0,("dsdb_read_prefixes_from_ldb: no prefix map present\n")); + talloc_free(schema_res); + return WERR_FOOBAR; + } else if (ret != LDB_SUCCESS) { + DEBUG(0,("dsdb_read_prefixes_from_ldb: failed to search the schema head\n")); + talloc_free(schema_res); + 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")); + talloc_free(schema_res); + 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); + talloc_free(schema_res); + return WERR_FOOBAR; + } + + talloc_free(schema_res); + + 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_UINT32_PTR_LDB(msg, attr, p, elem) do { \ + uint64_t _v = samdb_result_uint64(msg, attr, UINT64_MAX);\ + if (_v == UINT64_MAX) { \ + (p)->elem = NULL; \ + } else if (_v > UINT32_MAX) { \ + d_printf("%s: %s == 0x%llX\n", __location__, \ + attr, (unsigned long long)_v); \ + return WERR_INVALID_PARAM; \ + } else { \ + (p)->elem = talloc(mem_ctx, uint32_t); \ + if (!(p)->elem) { \ + d_printf("%s: talloc failed for %s\n", __location__, attr); \ + return WERR_NOMEM; \ + } \ + *(p)->elem = (uint32_t)_v; \ + } \ +} 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_PTR_LDB(msg, "rangeLower", attr, rangeLower); + GET_UINT32_PTR_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(mem_ctx, "FF0000000000000000000000000000000000000000"); + if (!info_val_default.data) { + dsdb_oom(error_string, mem_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + 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(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, local_ctx, &top_res, + schemadn, LDB_SCOPE_SUBTREE, NULL, + "(&(objectClass=classSchema)(lDAPDisplayName=top))"); + 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; + } + + 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, tmp_ctx, &schema_res, + schema_dn, LDB_SCOPE_BASE, schema_attrs, NULL); + 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; + } + 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, tmp_ctx, &a_res, + schema_dn, LDB_SCOPE_ONELEVEL, NULL, + "(objectClass=attributeSchema)"); + 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; + } + + /* + * 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) { \ + size_t _ret; \ + if (!convert_string_talloc_convenience(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), &_ret, false)) { \ + 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_STRING_LIST_DS(s, r, attr, mem_ctx, p, elem, strict) do { \ + int get_string_list_counter; \ + 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; \ + } \ + (p)->elem = _a ? talloc_array(mem_ctx, const char *, _a->value_ctr.num_values + 1) : NULL; \ + for (get_string_list_counter=0; \ + _a && get_string_list_counter < _a->value_ctr.num_values; \ + get_string_list_counter++) { \ + size_t _ret; \ + if (!convert_string_talloc_convenience(mem_ctx, s->iconv_convenience, CH_UTF16, CH_UNIX, \ + _a->value_ctr.values[get_string_list_counter].blob->data, \ + _a->value_ctr.values[get_string_list_counter].blob->length, \ + (void **)discard_const(&(p)->elem[get_string_list_counter]), &_ret, false)) { \ + DEBUG(0,("%s: invalid data!\n", attr)); \ + dump_data(0, \ + _a->value_ctr.values[get_string_list_counter].blob->data, \ + _a->value_ctr.values[get_string_list_counter].blob->length); \ + return WERR_FOOBAR; \ + } \ + (p)->elem[get_string_list_counter+1] = NULL; \ + } \ + talloc_steal(mem_ctx, (p)->elem); \ +} 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_UINT32_PTR_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 = talloc(mem_ctx, uint32_t); \ + if (!(p)->elem) { \ + d_printf("%s: talloc failed for %s\n", __location__, attr); \ + return WERR_NOMEM; \ + } \ + *(p)->elem = IVAL(_a->value_ctr.values[0].blob->data,0);\ + } else { \ + (p)->elem = NULL; \ + } \ +} 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_PTR_DS(schema, r, "rangeLower", attr, rangeLower); + GET_UINT32_PTR_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); + + + GET_STRING_LIST_DS(schema, r, "systemAuxiliaryClass", mem_ctx, obj, systemAuxiliaryClass, false); + GET_STRING_LIST_DS(schema, r, "auxiliaryClass", mem_ctx, obj, auxiliaryClass, false); + + GET_STRING_LIST_DS(schema, r, "systemMustContain", mem_ctx, obj, systemMustContain, false); + GET_STRING_LIST_DS(schema, r, "systemMayContain", mem_ctx, obj, systemMayContain, false); + GET_STRING_LIST_DS(schema, r, "mustContain", mem_ctx, obj, mustContain, false); + GET_STRING_LIST_DS(schema, r, "mayContain", mem_ctx, obj, mayContain, false); + + GET_STRING_LIST_DS(schema, r, "systemPossSuperiors", mem_ctx, obj, systemPossSuperiors, false); + GET_STRING_LIST_DS(schema, r, "possSuperiors", mem_ctx, obj, possSuperiors, false); + GET_STRING_LIST_DS(schema, r, "possibleInferiors", mem_ctx, obj, possibleInferiors, false); + + 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..00de0f8983 --- /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; +} + +const char **merge_attr_list(TALLOC_CTX *mem_ctx, + const char **attrs, const char * const*new_attrs) +{ + const char **ret_attrs; + int i; + size_t new_len, orig_len = str_list_length(attrs); + if (!new_attrs) { + return attrs; + } + + ret_attrs = talloc_realloc(mem_ctx, + attrs, const 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) +*/ + +const char **dsdb_attribute_list(TALLOC_CTX *mem_ctx, const struct dsdb_class *sclass, enum dsdb_attr_list_query query) +{ + const char **attr_list = NULL; + switch (query) { + case DSDB_SCHEMA_ALL_MAY: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->mayContain); + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->systemMayContain); + break; + + case DSDB_SCHEMA_ALL_MUST: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->mustContain); + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->systemMustContain); + break; + + case DSDB_SCHEMA_SYS_MAY: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->systemMayContain); + break; + + case DSDB_SCHEMA_SYS_MUST: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->systemMustContain); + break; + + case DSDB_SCHEMA_MAY: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->mayContain); + break; + + case DSDB_SCHEMA_MUST: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->mustContain); + break; + + case DSDB_SCHEMA_ALL: + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->mayContain); + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->systemMayContain); + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->mustContain); + attr_list = merge_attr_list(mem_ctx, attr_list, sclass->systemMustContain); + break; + } + return attr_list; +} + +static const 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 *sclass; + + const char **attr_list = NULL; + const char **this_class_list; + const char **recursive_list; + + for (i=0; class_list && class_list[i]; i++) { + sclass = dsdb_class_by_lDAPDisplayName(schema, class_list[i]); + + this_class_list = dsdb_attribute_list(mem_ctx, sclass, query); + attr_list = merge_attr_list(mem_ctx, attr_list, this_class_list); + + recursive_list = dsdb_full_attribute_list_internal(mem_ctx, schema, + sclass->systemAuxiliaryClass, + query); + + attr_list = merge_attr_list(mem_ctx, attr_list, recursive_list); + + recursive_list = dsdb_full_attribute_list_internal(mem_ctx, schema, + sclass->auxiliaryClass, + query); + + attr_list = merge_attr_list(mem_ctx, attr_list, recursive_list); + + } + return attr_list; +} + +const char **dsdb_full_attribute_list(TALLOC_CTX *mem_ctx, + const struct dsdb_schema *schema, + const char **class_list, + enum dsdb_attr_list_query query) +{ + const char **attr_list = dsdb_full_attribute_list_internal(mem_ctx, schema, class_list, query); + size_t new_len = str_list_length(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++) { + const char **val1 = &attr_list[i-1]; + const 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..6abd8a8f88 --- /dev/null +++ b/source4/dsdb/schema/schema_set.c @@ -0,0 +1,407 @@ +/* + 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 "dlinklist.h" +#include "dsdb/samdb/samdb.h" +#include "lib/ldb/include/ldb_module.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(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(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(mem_ctx, "FF0000000000000000000000000000000000000000"); + if (!info_val_default.data) { + goto nomem; + } + 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..27c9a6c4a4 --- /dev/null +++ b/source4/dsdb/schema/schema_syntax.c @@ -0,0 +1,1538 @@ +/* + 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 "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "lib/ldb/include/ldb.h" +#include "lib/ldb/include/ldb_errors.h" +#include "system/time.h" +#include "../lib/util/charset/charset.h" +#include "librpc/ndr/libndr.h" + +static WERROR dsdb_syntax_FOOBAR_drsuapi_to_ldb(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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(struct ldb_context *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(struct ldb_context *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(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(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(struct ldb_context *ldb, + 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(ldb, 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(struct ldb_context *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; + } + + if (in->value_ctr.values[i].blob->length == 0) { + return WERR_FOOBAR; + } + + if (!convert_string_talloc_convenience(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, NULL, false)) { + return WERR_FOOBAR; + } + + out->values[i] = data_blob_string_const(str); + } + + return WERR_OK; +} + +static WERROR dsdb_syntax_UNICODE_ldb_to_drsuapi(struct ldb_context *ldb, + 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]; + + if (!convert_string_talloc_convenience(blobs, schema->iconv_convenience, CH_UNIX, CH_UTF16, + in->values[i].data, + in->values[i].length, + (void **)&blobs[i].data, NULL, false)) { + return WERR_FOOBAR; + } + blobs[i].length = ret; + } + + return WERR_OK; +} + +static WERROR dsdb_syntax_DN_drsuapi_to_ldb(struct ldb_context *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; + int ret; + + 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; + DATA_BLOB guid_blob; + struct ldb_dn *dn; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + } + + if (in->value_ctr.values[i].blob == NULL) { + talloc_free(tmp_ctx); + return WERR_FOOBAR; + } + + if (in->value_ctr.values[i].blob->length == 0) { + talloc_free(tmp_ctx); + return WERR_FOOBAR; + } + + + + ndr_err = ndr_pull_struct_blob_all(in->value_ctr.values[i].blob, + tmp_ctx, 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); + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } + + dn = ldb_dn_new(tmp_ctx, ldb, id3.dn); + if (!dn) { + talloc_free(tmp_ctx); + /* If this fails, it must be out of memory, as it does not do much parsing */ + W_ERROR_HAVE_NO_MEMORY(dn); + } + + ndr_err = ndr_push_struct_blob(&guid_blob, tmp_ctx, schema->iconv_convenience, &id3.guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } + + ret = ldb_dn_set_extended_component(dn, "GUID", &guid_blob); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return WERR_FOOBAR; + } + + talloc_free(guid_blob.data); + + if (id3.__ndr_size_sid) { + DATA_BLOB sid_blob; + ndr_err = ndr_push_struct_blob(&sid_blob, tmp_ctx, schema->iconv_convenience, &id3.sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } + + ret = ldb_dn_set_extended_component(dn, "SID", &sid_blob); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return WERR_FOOBAR; + } + } + + out->values[i] = data_blob_string_const(ldb_dn_get_extended_linearized(out->values, dn, 1)); + talloc_free(tmp_ctx); + } + + return WERR_OK; +} + +static WERROR dsdb_syntax_DN_ldb_to_drsuapi(struct ldb_context *ldb, + 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; + const DATA_BLOB *guid_blob, *sid_blob; + struct ldb_dn *dn; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + + out->value_ctr.values[i].blob = &blobs[i]; + + dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &in->values[i]); + + W_ERROR_HAVE_NO_MEMORY(dn); + + guid_blob = ldb_dn_get_extended_component(dn, "GUID"); + + ZERO_STRUCT(id3); + + if (guid_blob) { + ndr_err = ndr_pull_struct_blob_all(guid_blob, + tmp_ctx, schema->iconv_convenience, &id3.guid, + (ndr_pull_flags_fn_t)ndr_pull_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } + } + + sid_blob = ldb_dn_get_extended_component(dn, "SID"); + if (sid_blob) { + + ndr_err = ndr_pull_struct_blob_all(sid_blob, + tmp_ctx, schema->iconv_convenience, &id3.sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } + } + + id3.dn = ldb_dn_get_linearized(dn); + + 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); + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } + talloc_free(tmp_ctx); + } + + return WERR_OK; +} + +static WERROR dsdb_syntax_DN_BINARY_drsuapi_to_ldb(struct ldb_context *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(struct ldb_context *ldb, + 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(struct ldb_context *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; + 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; + } + + if (!convert_string_talloc_convenience(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, NULL, false)) { + return WERR_FOOBAR; + } + + out->values[i] = data_blob_string_const(str); + } + + return WERR_OK; +} + +static WERROR dsdb_syntax_PRESENTATION_ADDRESS_ldb_to_drsuapi(struct ldb_context *ldb, + 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; + size_t ret; + + out->value_ctr.values[i].blob = &blobs[i]; + + if (!convert_string_talloc_convenience(blobs, schema->iconv_convenience, CH_UNIX, CH_UTF16, + in->values[i].data, + in->values[i].length, + (void **)&data, &ret, false)) { + 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 = "octetStringMatch", + .comment = "OctetString: Binary+DN", + .ldb_syntax = LDB_SYNTAX_OCTET_STRING, + },{ + /* 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, + },{ + /* 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_DN_BINARY_drsuapi_to_ldb, + .ldb_to_drsuapi = dsdb_syntax_DN_BINARY_ldb_to_drsuapi, + .equality = "octetStringMatch", + .comment = "OctetString: String+DN", + .ldb_syntax = LDB_SYNTAX_OCTET_STRING, + } +}; + +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(struct ldb_context *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(ldb, schema, sa, in, mem_ctx, out); +} + +WERROR dsdb_attribute_ldb_to_drsuapi(struct ldb_context *ldb, + 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(ldb, schema, sa, in, mem_ctx, out); +} |