diff options
Diffstat (limited to '')
-rw-r--r-- | source4/dsdb/common/dsdb_access.c | 183 | ||||
-rw-r--r-- | source4/dsdb/common/dsdb_dn.c | 571 | ||||
-rw-r--r-- | source4/dsdb/common/dsdb_dn.h | 21 | ||||
-rw-r--r-- | source4/dsdb/common/rodc_helper.c | 284 | ||||
-rw-r--r-- | source4/dsdb/common/tests/dsdb.c | 93 | ||||
-rw-r--r-- | source4/dsdb/common/tests/dsdb_dn.c | 374 | ||||
-rw-r--r-- | source4/dsdb/common/util.c | 6796 | ||||
-rw-r--r-- | source4/dsdb/common/util.h | 101 | ||||
-rw-r--r-- | source4/dsdb/common/util_groups.c | 200 | ||||
-rw-r--r-- | source4/dsdb/common/util_links.c | 229 | ||||
-rw-r--r-- | source4/dsdb/common/util_links.h | 48 | ||||
-rw-r--r-- | source4/dsdb/common/util_samr.c | 593 | ||||
-rw-r--r-- | source4/dsdb/common/util_trusts.c | 3443 |
13 files changed, 12936 insertions, 0 deletions
diff --git a/source4/dsdb/common/dsdb_access.c b/source4/dsdb/common/dsdb_access.c new file mode 100644 index 0000000..6edae35 --- /dev/null +++ b/source4/dsdb/common/dsdb_access.c @@ -0,0 +1,183 @@ +/* + ldb database library + + Copyright (C) Nadezhda Ivanova 2010 + + 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: dsdb_access + * + * Description: utility functions for access checking on objects + * + * Authors: Nadezhda Ivanova + */ + +#include "includes.h" +#include "ldb.h" +#include "ldb_module.h" +#include "ldb_errors.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/ldap/ldap_ndr.h" +#include "param/param.h" +#include "auth/auth.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" + +void dsdb_acl_debug(struct security_descriptor *sd, + struct security_token *token, + struct ldb_dn *dn, + bool denied, + int level) +{ + if (denied) { + DEBUG(level, ("Access on %s denied\n", ldb_dn_get_linearized(dn))); + } else { + DEBUG(level, ("Access on %s granted\n", ldb_dn_get_linearized(dn))); + } + + DEBUG(level,("Security context: %s\n", + ndr_print_struct_string(0,(ndr_print_fn_t)ndr_print_security_token,"", token))); + DEBUG(level,("Security descriptor: %s\n", + ndr_print_struct_string(0,(ndr_print_fn_t)ndr_print_security_descriptor,"", sd))); +} + +int dsdb_get_sd_from_ldb_message(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_message *acl_res, + struct security_descriptor **sd) +{ + struct ldb_message_element *sd_element; + enum ndr_err_code ndr_err; + + sd_element = ldb_msg_find_element(acl_res, "nTSecurityDescriptor"); + if (sd_element == NULL) { + return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS, + "nTSecurityDescriptor is missing"); + } + *sd = talloc(mem_ctx, struct security_descriptor); + if(!*sd) { + return ldb_oom(ldb); + } + ndr_err = ndr_pull_struct_blob(&sd_element->values[0], *sd, *sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(*sd); + return ldb_operr(ldb); + } + + return LDB_SUCCESS; +} + +int dsdb_check_access_on_dn_internal(struct ldb_context *ldb, + struct ldb_result *acl_res, + TALLOC_CTX *mem_ctx, + struct security_token *token, + struct ldb_dn *dn, + uint32_t access_mask, + const struct GUID *guid) +{ + struct security_descriptor *sd = NULL; + struct dom_sid *sid = NULL; + struct object_tree *root = NULL; + NTSTATUS status; + uint32_t access_granted; + int ret; + + ret = dsdb_get_sd_from_ldb_message(ldb, mem_ctx, acl_res->msgs[0], &sd); + if (ret != LDB_SUCCESS) { + return ldb_operr(ldb); + } + + sid = samdb_result_dom_sid(mem_ctx, acl_res->msgs[0], "objectSid"); + if (guid) { + if (!insert_in_object_tree(mem_ctx, guid, access_mask, NULL, + &root)) { + TALLOC_FREE(sd); + TALLOC_FREE(sid); + return ldb_operr(ldb); + } + } + status = sec_access_check_ds(sd, token, + access_mask, + &access_granted, + root, + sid); + if (!NT_STATUS_IS_OK(status)) { + dsdb_acl_debug(sd, + token, + dn, + true, + 10); + ldb_asprintf_errstring(ldb, + "dsdb_access: Access check failed on %s", + ldb_dn_get_linearized(dn)); + TALLOC_FREE(sd); + TALLOC_FREE(sid); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + return LDB_SUCCESS; +} + +/* performs an access check from outside the module stack + * given the dn of the object to be checked, the required access + * guid is either the guid of the extended right, or NULL + */ + +int dsdb_check_access_on_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct security_token *token, + uint32_t access_mask, + const char *ext_right) +{ + int ret; + struct GUID guid; + struct ldb_result *acl_res; + static const char *acl_attrs[] = { + "nTSecurityDescriptor", + "objectSid", + NULL + }; + + if (ext_right != NULL) { + NTSTATUS status = GUID_from_string(ext_right, &guid); + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + + /* + * We need AS_SYSTEM in order to get the nTSecurityDescriptor attribute. + * Also the result of this search not controlled by the client + * nor is the result exposed to the client. + */ + ret = dsdb_search_dn(ldb, mem_ctx, &acl_res, dn, acl_attrs, + DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_RECYCLED); + if (ret != LDB_SUCCESS) { + DEBUG(10,("access_check: failed to find object %s\n", ldb_dn_get_linearized(dn))); + return ret; + } + + return dsdb_check_access_on_dn_internal(ldb, acl_res, + mem_ctx, + token, + dn, + access_mask, + ext_right ? &guid : NULL); +} + diff --git a/source4/dsdb/common/dsdb_dn.c b/source4/dsdb/common/dsdb_dn.c new file mode 100644 index 0000000..63a8628 --- /dev/null +++ b/source4/dsdb/common/dsdb_dn.c @@ -0,0 +1,571 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + + Copyright (C) Andrew Tridgell 2009 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009 + + 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 <ldb_module.h> +#include "librpc/ndr/libndr.h" +#include "libcli/security/dom_sid.h" +#include "lib/util/smb_strtox.h" + +enum dsdb_dn_format dsdb_dn_oid_to_format(const char *oid) +{ + if (strcmp(oid, LDB_SYNTAX_DN) == 0) { + return DSDB_NORMAL_DN; + } else if (strcmp(oid, DSDB_SYNTAX_BINARY_DN) == 0) { + return DSDB_BINARY_DN; + } else if (strcmp(oid, DSDB_SYNTAX_STRING_DN) == 0) { + return DSDB_STRING_DN; + } else if (strcmp(oid, DSDB_SYNTAX_OR_NAME) == 0) { + return DSDB_NORMAL_DN; + } else { + return DSDB_INVALID_DN; + } +} + +static struct dsdb_dn *dsdb_dn_construct_internal(TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + DATA_BLOB extra_part, + enum dsdb_dn_format dn_format, + const char *oid) +{ + struct dsdb_dn *dsdb_dn = NULL; + + switch (dn_format) { + case DSDB_BINARY_DN: + case DSDB_STRING_DN: + break; + case DSDB_NORMAL_DN: + if (extra_part.length != 0) { + errno = EINVAL; + return NULL; + } + break; + case DSDB_INVALID_DN: + default: + errno = EINVAL; + return NULL; + } + + dsdb_dn = talloc(mem_ctx, struct dsdb_dn); + if (!dsdb_dn) { + errno = ENOMEM; + return NULL; + } + dsdb_dn->dn = talloc_steal(dsdb_dn, dn); + dsdb_dn->extra_part = extra_part; + dsdb_dn->dn_format = dn_format; + + dsdb_dn->oid = oid; + talloc_steal(dsdb_dn, extra_part.data); + return dsdb_dn; +} + +struct dsdb_dn *dsdb_dn_construct(TALLOC_CTX *mem_ctx, struct ldb_dn *dn, DATA_BLOB extra_part, + const char *oid) +{ + enum dsdb_dn_format dn_format = dsdb_dn_oid_to_format(oid); + return dsdb_dn_construct_internal(mem_ctx, dn, extra_part, dn_format, oid); +} + +struct dsdb_dn *dsdb_dn_parse_trusted(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, + const struct ldb_val *dn_blob, const char *dn_oid) +{ + struct dsdb_dn *dsdb_dn; + struct ldb_dn *dn; + size_t len; + TALLOC_CTX *tmp_ctx; + char *p1; + char *p2; + uint32_t blen; + struct ldb_val bval; + struct ldb_val dval; + char *dn_str; + int error = 0; + + enum dsdb_dn_format dn_format = dsdb_dn_oid_to_format(dn_oid); + + if (dn_blob == NULL || dn_blob->data == NULL || dn_blob->length == 0) { + return NULL; + } + + switch (dn_format) { + case DSDB_INVALID_DN: + return NULL; + case DSDB_NORMAL_DN: + { + dn = ldb_dn_from_ldb_val(mem_ctx, ldb, dn_blob); + if (!dn) { + talloc_free(dn); + return NULL; + } + return dsdb_dn_construct_internal(mem_ctx, dn, data_blob_null, dn_format, dn_oid); + } + case DSDB_BINARY_DN: + if (dn_blob->length < 2 || dn_blob->data[0] != 'B' || dn_blob->data[1] != ':') { + return NULL; + } + break; + case DSDB_STRING_DN: + if (dn_blob->length < 2 || dn_blob->data[0] != 'S' || dn_blob->data[1] != ':') { + return NULL; + } + break; + default: + return NULL; + } + + if (strlen((const char*)dn_blob->data) != dn_blob->length) { + /* The RDN must not contain a character with value 0x0 */ + return NULL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NULL; + } + + len = dn_blob->length - 2; + p1 = talloc_strndup(tmp_ctx, (const char *)dn_blob->data + 2, len); + if (!p1) { + goto failed; + } + + errno = 0; + blen = smb_strtoul(p1, &p2, 10, &error, SMB_STR_STANDARD); + if (error != 0) { + DEBUG(10, (__location__ ": failed\n")); + goto failed; + } + if (p2 == NULL) { + DEBUG(10, (__location__ ": failed\n")); + goto failed; + } + if (p2[0] != ':') { + DEBUG(10, (__location__ ": failed\n")); + goto failed; + } + len -= PTR_DIFF(p2,p1);//??? + p1 = p2+1; + len--; + + if (blen >= len) { + DEBUG(10, (__location__ ": blen=%u len=%u\n", (unsigned)blen, (unsigned)len)); + goto failed; + } + + p2 = p1 + blen; + if (p2[0] != ':') { + DEBUG(10, (__location__ ": %s", p2)); + goto failed; + } + dn_str = p2+1; + + + switch (dn_format) { + case DSDB_BINARY_DN: + if ((blen % 2 != 0)) { + DEBUG(10, (__location__ ": blen=%u - not an even number\n", (unsigned)blen)); + goto failed; + } + + if (blen >= 2) { + bval.length = (blen/2)+1; + bval.data = talloc_size(tmp_ctx, bval.length); + if (bval.data == NULL) { + DEBUG(10, (__location__ ": err\n")); + goto failed; + } + bval.data[bval.length-1] = 0; + + bval.length = strhex_to_str((char *)bval.data, bval.length, + p1, blen); + if (bval.length != (blen / 2)) { + DEBUG(10, (__location__ ": non hexadecimal characters found in binary prefix\n")); + goto failed; + } + } else { + bval = data_blob_null; + } + + break; + case DSDB_STRING_DN: + bval = data_blob(p1, blen); + break; + default: + /* never reached */ + return NULL; + } + + + dval.data = (uint8_t *)dn_str; + dval.length = strlen(dn_str); + + dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &dval); + if (!dn) { + DEBUG(10, (__location__ ": err\n")); + goto failed; + } + + dsdb_dn = dsdb_dn_construct(mem_ctx, dn, bval, dn_oid); + + talloc_free(tmp_ctx); + return dsdb_dn; + +failed: + talloc_free(tmp_ctx); + return NULL; +} + +struct dsdb_dn *dsdb_dn_parse(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, + const struct ldb_val *dn_blob, const char *dn_oid) +{ + struct dsdb_dn *dsdb_dn = dsdb_dn_parse_trusted(mem_ctx, ldb, + dn_blob, dn_oid); + if (dsdb_dn == NULL) { + return NULL; + } + if (ldb_dn_validate(dsdb_dn->dn) == false) { + DEBUG(10, ("could not parse %.*s as a %s DN\n", + (int)dn_blob->length, dn_blob->data, + dn_oid)); + return NULL; + } + return dsdb_dn; +} + +static char *dsdb_dn_get_with_postfix(TALLOC_CTX *mem_ctx, + struct dsdb_dn *dsdb_dn, + const char *postfix) +{ + if (!postfix) { + return NULL; + } + + switch (dsdb_dn->dn_format) { + case DSDB_NORMAL_DN: + { + return talloc_strdup(mem_ctx, postfix); + } + case DSDB_BINARY_DN: + { + char *hexstr = data_blob_hex_string_upper(mem_ctx, &dsdb_dn->extra_part); + + char *p = talloc_asprintf(mem_ctx, "B:%u:%s:%s", (unsigned)(dsdb_dn->extra_part.length*2), hexstr, + postfix); + talloc_free(hexstr); + return p; + } + case DSDB_STRING_DN: + { + return talloc_asprintf(mem_ctx, "S:%u:%*.*s:%s", + (unsigned)(dsdb_dn->extra_part.length), + (int)(dsdb_dn->extra_part.length), + (int)(dsdb_dn->extra_part.length), + (const char *)dsdb_dn->extra_part.data, + postfix); + } + default: + return NULL; + } +} + +char *dsdb_dn_get_linearized(TALLOC_CTX *mem_ctx, + struct dsdb_dn *dsdb_dn) +{ + const char *postfix = ldb_dn_get_linearized(dsdb_dn->dn); + return dsdb_dn_get_with_postfix(mem_ctx, dsdb_dn, postfix); +} + +char *dsdb_dn_get_casefold(TALLOC_CTX *mem_ctx, + struct dsdb_dn *dsdb_dn) +{ + const char *postfix = ldb_dn_get_casefold(dsdb_dn->dn); + return dsdb_dn_get_with_postfix(mem_ctx, dsdb_dn, postfix); +} + +char *dsdb_dn_get_extended_linearized(TALLOC_CTX *mem_ctx, + struct dsdb_dn *dsdb_dn, + int mode) +{ + char *postfix = ldb_dn_get_extended_linearized(mem_ctx, dsdb_dn->dn, mode); + char *ret = dsdb_dn_get_with_postfix(mem_ctx, dsdb_dn, postfix); + talloc_free(postfix); + return ret; +} + +int dsdb_dn_binary_canonicalise(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct dsdb_dn *dsdb_dn = dsdb_dn_parse(mem_ctx, ldb, in, DSDB_SYNTAX_BINARY_DN); + + if (!dsdb_dn) { + return -1; + } + *out = data_blob_string_const(dsdb_dn_get_casefold(mem_ctx, dsdb_dn)); + talloc_free(dsdb_dn); + if (!out->data) { + return -1; + } + return 0; +} + +int dsdb_dn_binary_comparison(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, + const struct ldb_val *v2) +{ + return ldb_any_comparison(ldb, mem_ctx, dsdb_dn_binary_canonicalise, v1, v2); +} + +int dsdb_dn_string_canonicalise(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *in, struct ldb_val *out) +{ + struct dsdb_dn *dsdb_dn = dsdb_dn_parse(mem_ctx, ldb, in, DSDB_SYNTAX_STRING_DN); + + if (!dsdb_dn) { + return -1; + } + *out = data_blob_string_const(dsdb_dn_get_casefold(mem_ctx, dsdb_dn)); + talloc_free(dsdb_dn); + if (!out->data) { + return -1; + } + return 0; +} + +int dsdb_dn_string_comparison(struct ldb_context *ldb, void *mem_ctx, + const struct ldb_val *v1, + const struct ldb_val *v2) +{ + return ldb_any_comparison(ldb, mem_ctx, dsdb_dn_string_canonicalise, v1, v2); +} + +/* + * format a drsuapi_DsReplicaObjectIdentifier naming context as a string for debugging + * + * When forming a DN for DB access you must use drs_ObjectIdentifier_to_dn() + */ +char *drs_ObjectIdentifier_to_debug_string(TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaObjectIdentifier *nc) +{ + char *ret = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!GUID_all_zero(&nc->guid)) { + char *guid = GUID_string(tmp_ctx, &nc->guid); + if (guid) { + ret = talloc_asprintf_append(ret, "<GUID=%s>;", guid); + } + } + if (nc->__ndr_size_sid != 0 && nc->sid.sid_rev_num != 0) { + const char *sid = dom_sid_string(tmp_ctx, &nc->sid); + if (sid) { + ret = talloc_asprintf_append(ret, "<SID=%s>;", sid); + } + } + if (nc->__ndr_size_dn != 0 && nc->dn) { + ret = talloc_asprintf_append(ret, "%s", nc->dn); + } + talloc_free(tmp_ctx); + talloc_steal(mem_ctx, ret); + return ret; +} + +/* + * Safely convert a drsuapi_DsReplicaObjectIdentifier into an LDB DN + * + * We need to have GUID and SID priority and not allow extended + * components in the DN. + * + * We must also totally honour the priority even if the string DN is not valid or able to parse as a DN. + */ +static struct ldb_dn *drs_ObjectIdentifier_to_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct drsuapi_DsReplicaObjectIdentifier *nc) +{ + struct ldb_dn *new_dn = NULL; + + if (!GUID_all_zero(&nc->guid)) { + struct GUID_txt_buf buf; + char *guid = GUID_buf_string(&nc->guid, &buf); + + new_dn = ldb_dn_new_fmt(mem_ctx, + ldb, + "<GUID=%s>", + guid); + if (new_dn == NULL) { + DBG_ERR("Failed to prepare drs_ObjectIdentifier " + "GUID %s into a DN\n", + guid); + return NULL; + } + + return new_dn; + } + + if (nc->__ndr_size_sid != 0 && nc->sid.sid_rev_num != 0) { + struct dom_sid_buf buf; + char *sid = dom_sid_str_buf(&nc->sid, &buf); + + new_dn = ldb_dn_new_fmt(mem_ctx, + ldb, + "<SID=%s>", + sid); + if (new_dn == NULL) { + DBG_ERR("Failed to prepare drs_ObjectIdentifier " + "SID %s into a DN\n", + sid); + return NULL; + } + return new_dn; + } + + if (nc->__ndr_size_dn != 0 && nc->dn) { + int dn_comp_num = 0; + bool new_dn_valid = false; + + new_dn = ldb_dn_new(mem_ctx, ldb, nc->dn); + if (new_dn == NULL) { + /* Set to WARNING as this is user-controlled, don't print the value into the logs */ + DBG_WARNING("Failed to parse string DN in " + "drs_ObjectIdentifier into an LDB DN\n"); + return NULL; + } + + new_dn_valid = ldb_dn_validate(new_dn); + if (!new_dn_valid) { + /* + * Set to WARNING as this is user-controlled, + * but can print the value into the logs as it + * parsed a bit + */ + DBG_WARNING("Failed to validate string DN [%s] in " + "drs_ObjectIdentifier as an LDB DN\n", + ldb_dn_get_linearized(new_dn)); + return NULL; + } + + dn_comp_num = ldb_dn_get_comp_num(new_dn); + if (dn_comp_num <= 0) { + /* + * Set to WARNING as this is user-controlled, + * but can print the value into the logs as it + * parsed a bit + */ + DBG_WARNING("DN [%s] in drs_ObjectIdentifier " + "must have 1 or more components\n", + ldb_dn_get_linearized(new_dn)); + return NULL; + } + + if (ldb_dn_is_special(new_dn)) { + /* + * Set to WARNING as this is user-controlled, + * but can print the value into the logs as it + * parsed a bit + */ + DBG_WARNING("New string DN [%s] in " + "drs_ObjectIdentifier is a " + "special LDB DN\n", + ldb_dn_get_linearized(new_dn)); + return NULL; + } + + /* + * We want this just to be a string DN, extended + * components are manually handled above + */ + if (ldb_dn_has_extended(new_dn)) { + /* + * Set to WARNING as this is user-controlled, + * but can print the value into the logs as it + * parsed a bit + */ + DBG_WARNING("Refusing to parse New string DN [%s] in " + "drs_ObjectIdentifier as an " + "extended LDB DN " + "(GUIDs and SIDs should be in the " + ".guid and .sid IDL elements, " + "not in the string\n", + ldb_dn_get_extended_linearized(mem_ctx, + new_dn, + 1)); + return NULL; + } + return new_dn; + } + + DBG_WARNING("Refusing to parse empty string DN " + "(and no GUID or SID) " + "drs_ObjectIdentifier into a empty " + "(eg RootDSE) LDB DN\n"); + return NULL; +} + +/* + * Safely convert a drsuapi_DsReplicaObjectIdentifier into a validated + * LDB DN of an existing DB entry, and/or find the NC root + * + * We need to have GUID and SID priority and not allow extended + * components in the DN. + * + * We must also totally honour the priority even if the string DN is + * not valid or able to parse as a DN. + * + * Finally, we must return the DN as found in the DB, as otherwise a + * subsequence ldb_dn_compare(dn, nc_root) will fail (as this is based + * on the string components). + */ +int drs_ObjectIdentifier_to_dn_and_nc_root(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct drsuapi_DsReplicaObjectIdentifier *nc, + struct ldb_dn **normalised_dn, + struct ldb_dn **nc_root) +{ + int ret; + struct ldb_dn *new_dn = NULL; + + new_dn = drs_ObjectIdentifier_to_dn(mem_ctx, + ldb, + nc); + if (new_dn == NULL) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + + ret = dsdb_normalise_dn_and_find_nc_root(ldb, + mem_ctx, + new_dn, + normalised_dn, + nc_root); + if (ret != LDB_SUCCESS) { + /* + * dsdb_normalise_dn_and_find_nc_root() sets LDB error + * strings, and the functions it calls do also + */ + DBG_NOTICE("Failed to find DN \"%s\" -> \"%s\" for normalisation: %s (%s)\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, nc), + ldb_dn_get_extended_linearized(mem_ctx, new_dn, 1), + ldb_errstring(ldb), + ldb_strerror(ret)); + } + + TALLOC_FREE(new_dn); + return ret; +} diff --git a/source4/dsdb/common/dsdb_dn.h b/source4/dsdb/common/dsdb_dn.h new file mode 100644 index 0000000..f98e3e7 --- /dev/null +++ b/source4/dsdb/common/dsdb_dn.h @@ -0,0 +1,21 @@ +struct dsdb_dn { + struct ldb_dn *dn; + DATA_BLOB extra_part; + enum dsdb_dn_format dn_format; + const char *oid; +}; + +#define DSDB_SYNTAX_BINARY_DN "1.2.840.113556.1.4.903" +#define DSDB_SYNTAX_STRING_DN "1.2.840.113556.1.4.904" +#define DSDB_SYNTAX_OR_NAME "1.2.840.113556.1.4.1221" +#define DSDB_SYNTAX_ACCESS_POINT "1.3.6.1.4.1.1466.115.121.1.2" + + +/* RMD_FLAGS component in a DN */ +#define DSDB_RMD_FLAG_DELETED 1 +/* + * This is used on a backlink attribute + * if the backlink is not allowed on + * the objectClass + */ +#define DSDB_RMD_FLAG_HIDDEN_BL 2 diff --git a/source4/dsdb/common/rodc_helper.c b/source4/dsdb/common/rodc_helper.c new file mode 100644 index 0000000..b4982ae --- /dev/null +++ b/source4/dsdb/common/rodc_helper.c @@ -0,0 +1,284 @@ +/* + Unix SMB/CIFS implementation. + + common sid helper functions + + Copyright (C) Catalyst.NET Ltd 2017 + + 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 "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "source4/dsdb/samdb/samdb.h" +#include "libcli/security/security.h" + +/* + see if any SIDs in list1 are in list2 + */ +static bool sid_list_match(uint32_t num_sids1, + const struct dom_sid *list1, + uint32_t num_sids2, + const struct dom_sid *list2) +{ + unsigned int i, j; + /* do we ever have enough SIDs here to worry about O(n^2) ? */ + for (i=0; i < num_sids1; i++) { + for (j=0; j < num_sids2; j++) { + if (dom_sid_equal(&list1[i], &list2[j])) { + return true; + } + } + } + return false; +} + +/* + * Return an array of SIDs from a ldb_message given an attribute name assumes + * the SIDs are in NDR form (with primary_sid applied on the start). + */ +static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, + struct ldb_message *msg, + TALLOC_CTX *mem_ctx, + const char *attr, + uint32_t *num_sids, + struct dom_sid **sids, + const struct dom_sid *primary_sid) +{ + struct ldb_message_element *el; + unsigned int i; + + el = ldb_msg_find_element(msg, attr); + if (!el) { + *sids = NULL; + return WERR_OK; + } + + /* Make array long enough for NULL and additional SID */ + (*sids) = talloc_array(mem_ctx, struct dom_sid, + el->num_values + 1); + W_ERROR_HAVE_NO_MEMORY(*sids); + + (*sids)[PRIMARY_USER_SID_INDEX] = *primary_sid; + + for (i = 0; i<el->num_values; i++) { + enum ndr_err_code ndr_err; + struct dom_sid sid = { 0, }; + + ndr_err = ndr_pull_struct_blob_all_noalloc(&el->values[i], &sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INTERNAL_DB_CORRUPTION; + } + /* Primary SID is already in position zero. */ + (*sids)[i+1] = sid; + } + + *num_sids = i+1; + + return WERR_OK; +} + +/* + return an array of SIDs from a ldb_message given an attribute name + assumes the SIDs are in extended DN format + */ +WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, + const struct ldb_message *msg, + TALLOC_CTX *mem_ctx, + const char *attr, + uint32_t *num_sids, + struct dom_sid **sids) +{ + struct ldb_message_element *el; + unsigned int i; + + el = ldb_msg_find_element(msg, attr); + if (!el) { + *sids = NULL; + return WERR_OK; + } + + (*sids) = talloc_array(mem_ctx, struct dom_sid, el->num_values + 1); + W_ERROR_HAVE_NO_MEMORY(*sids); + + for (i=0; i<el->num_values; i++) { + struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]); + NTSTATUS status; + struct dom_sid sid = { 0, }; + + status = dsdb_get_extended_dn_sid(dn, &sid, "SID"); + if (!NT_STATUS_IS_OK(status)) { + return WERR_INTERNAL_DB_CORRUPTION; + } + (*sids)[i] = sid; + } + *num_sids = i; + + return WERR_OK; +} + +WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ctx, + const struct dom_sid *rodc_machine_account_sid, + const struct ldb_message *rodc_msg, + const struct ldb_message *obj_msg, + uint32_t num_token_sids, + const struct dom_sid *token_sids) +{ + uint32_t num_never_reveal_sids, num_reveal_sids; + struct dom_sid *never_reveal_sids, *reveal_sids; + TALLOC_CTX *frame = talloc_stackframe(); + WERROR werr; + uint32_t rodc_uac; + + /* + * We are not allowed to get anyone elses krbtgt secrets (and + * in callers that don't shortcut before this, the RODC should + * not deal with any krbtgt) + */ + if (samdb_result_dn(sam_ctx, frame, + obj_msg, "msDS-KrbTgtLinkBL", NULL)) { + TALLOC_FREE(frame); + DBG_INFO("Denied attempt to replicate to/act as a RODC krbtgt trust account %s using RODC: %s\n", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn)); + return WERR_DS_DRA_SECRETS_DENIED; + } + + if (ldb_msg_find_attr_as_uint(obj_msg, + "userAccountControl", 0) & + UF_INTERDOMAIN_TRUST_ACCOUNT) { + DBG_INFO("Denied attempt to replicate to/act as a inter-domain trust account %s using RODC: %s\n", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn)); + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + /* Be very sure the RODC is really an RODC */ + rodc_uac = ldb_msg_find_attr_as_uint(rodc_msg, + "userAccountControl", + 0); + if ((rodc_uac & UF_PARTIAL_SECRETS_ACCOUNT) + != UF_PARTIAL_SECRETS_ACCOUNT) { + DBG_ERR("Attempt to use an RODC account that is not an RODC: %s\n", + ldb_dn_get_linearized(rodc_msg->dn)); + TALLOC_FREE(frame); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, + frame, "msDS-NeverRevealGroup", + &num_never_reveal_sids, + &never_reveal_sids); + if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("Failed to parse msDS-NeverRevealGroup on %s: %s\n", + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, + frame, "msDS-RevealOnDemandGroup", + &num_reveal_sids, + &reveal_sids); + if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("Failed to parse msDS-RevealOnDemandGroup on %s: %s\n", + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + /* The RODC can replicate and print tickets for itself. */ + if (dom_sid_equal(&token_sids[PRIMARY_USER_SID_INDEX], rodc_machine_account_sid)) { + TALLOC_FREE(frame); + return WERR_OK; + } + + if (never_reveal_sids && + sid_list_match(num_token_sids, + token_sids, + num_never_reveal_sids, + never_reveal_sids)) { + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + if (reveal_sids && + sid_list_match(num_token_sids, + token_sids, + num_reveal_sids, + reveal_sids)) { + TALLOC_FREE(frame); + return WERR_OK; + } + + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + +} + +/* + * This is a wrapper for the above that pulls in the tokenGroups + * rather than relying on the caller providing those + */ +WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, + struct dom_sid *rodc_machine_account_sid, + struct ldb_message *rodc_msg, + struct ldb_message *obj_msg) +{ + TALLOC_CTX *frame = talloc_stackframe(); + WERROR werr; + uint32_t num_token_sids; + struct dom_sid *token_sids; + const struct dom_sid *object_sid = NULL; + + object_sid = samdb_result_dom_sid(frame, + obj_msg, + "objectSid"); + if (object_sid == NULL) { + return WERR_DS_DRA_BAD_DN; + } + + /* + * The SID list needs to include itself as well as the tokenGroups. + * + * TODO determine if sIDHistory is required for this check + */ + werr = samdb_result_sid_array_ndr(sam_ctx, + obj_msg, + frame, "tokenGroups", + &num_token_sids, + &token_sids, + object_sid); + if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { + DBG_ERR("Failed to get tokenGroups on %s to confirm access via RODC %s: %s\n", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); + return WERR_DS_DRA_SECRETS_DENIED; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx, + rodc_machine_account_sid, + rodc_msg, + obj_msg, + num_token_sids, + token_sids); + TALLOC_FREE(frame); + return werr; +} diff --git a/source4/dsdb/common/tests/dsdb.c b/source4/dsdb/common/tests/dsdb.c new file mode 100644 index 0000000..8b20b4d --- /dev/null +++ b/source4/dsdb/common/tests/dsdb.c @@ -0,0 +1,93 @@ +/* + Unix SMB/CIFS implementation. + + Test DSDB search + + Copyright (C) Andrew Bartlet <abartlet@samba.org> 2019 + + 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 "ldb_wrap.h" +#include "param/param.h" +#include "param/loadparm.h" +#include "torture/smbtorture.h" +#include "torture/dsdb_proto.h" +#include "auth/auth.h" + +bool torture_ldb_no_attrs(struct torture_context *torture) +{ + struct ldb_context *ldb; + int ret; + struct ldb_request *req; + struct ldb_result *ctx; + struct ldb_dn *dn; + const char *attrs[] = { NULL }; + + struct auth_session_info *session; + struct dom_sid domain_sid; + const char *path; + + path = lpcfg_private_path(NULL, torture->lp_ctx, "sam.ldb"); + torture_assert(torture, path != NULL, + "Couldn't find sam.ldb. Run with -s $SERVERCONFFILE"); + + domain_sid = global_sid_Builtin; + session = admin_session(NULL, torture->lp_ctx, &domain_sid); + ldb = ldb_wrap_connect(torture, torture->ev, torture->lp_ctx, + path, session, NULL, 0); + torture_assert(torture, ldb, "Failed to connect to LDB target"); + + ctx = talloc_zero(ldb, struct ldb_result); + + dn = ldb_get_default_basedn(ldb); + ldb_dn_add_child_fmt(dn, "cn=users"); + ret = ldb_build_search_req(&req, ldb, ctx, dn, LDB_SCOPE_SUBTREE, + "(objectClass=*)", attrs, NULL, + ctx, ldb_search_default_callback, NULL); + torture_assert(torture, ret == LDB_SUCCESS, + "Failed to build search request"); + ldb_req_mark_untrusted(req); + + ret = ldb_request(ldb, req); + torture_assert(torture, ret == LDB_SUCCESS, ldb_errstring(ldb)); + + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + torture_assert(torture, ret == LDB_SUCCESS, ldb_errstring(ldb)); + + torture_assert(torture, ctx->count > 0, "Users container empty"); + torture_assert_int_equal(torture, ctx->msgs[0]->num_elements, 0, + "Attributes returned for request " + "with empty attribute list"); + + return true; +} + +NTSTATUS torture_dsdb_init(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "dsdb"); + + if (suite == NULL) { + return NT_STATUS_NO_MEMORY; + } + torture_suite_add_simple_test(suite, "no_attrs", torture_ldb_no_attrs); + + suite->description = talloc_strdup(suite, "DSDB tests"); + + torture_register_suite(mem_ctx, suite); + + return NT_STATUS_OK; +} diff --git a/source4/dsdb/common/tests/dsdb_dn.c b/source4/dsdb/common/tests/dsdb_dn.c new file mode 100644 index 0000000..66c7e12 --- /dev/null +++ b/source4/dsdb/common/tests/dsdb_dn.c @@ -0,0 +1,374 @@ +/* + Unix SMB/CIFS implementation. + + Test LDB attribute functions + + Copyright (C) Andrew Bartlet <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 "lib/events/events.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "lib/ldb-samba/ldif_handlers.h" +#include "ldb_wrap.h" +#include "dsdb/samdb/samdb.h" +#include "param/param.h" +#include "torture/smbtorture.h" +#include "torture/local/proto.h" + +#define DSDB_DN_TEST_SID "S-1-5-21-4177067393-1453636373-93818737" + +static bool torture_dsdb_dn_attrs(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx = talloc_new(torture); + struct ldb_context *ldb; + const struct ldb_schema_syntax *syntax; + struct ldb_val dn1, dn2, dn3; + + torture_assert(torture, + ldb = ldb_init(mem_ctx, torture->ev), + "Failed to init ldb"); + + torture_assert_int_equal(torture, + ldb_register_samba_handlers(ldb), LDB_SUCCESS, + "Failed to register Samba handlers"); + + ldb_set_utf8_fns(ldb, NULL, wrap_casefold); + + /* Test DN+Binary behaviour */ + torture_assert(torture, syntax = ldb_samba_syntax_by_name(ldb, DSDB_SYNTAX_BINARY_DN), + "Failed to get DN+Binary schema attribute"); + /* Test compare with different case of HEX string */ + dn1 = data_blob_string_const("B:6:abcdef:dc=samba,dc=org"); + dn2 = data_blob_string_const("B:6:ABCDef:dc=samba,dc=org"); + torture_assert_int_equal(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2), 0, + "Failed to compare different case of binary in DN+Binary"); + torture_assert_int_equal(torture, + syntax->canonicalise_fn(ldb, mem_ctx, &dn1, &dn3), 0, + "Failed to canonicalise DN+Binary"); + torture_assert_data_blob_equal(torture, dn3, data_blob_string_const("B:6:ABCDEF:DC=SAMBA,DC=ORG"), + "Failed to canonicalise DN+Binary"); + /* Test compare with different case of DN */ + dn1 = data_blob_string_const("B:6:abcdef:dc=samba,dc=org"); + dn2 = data_blob_string_const("B:6:abcdef:dc=SAMBa,dc=ORg"); + torture_assert_int_equal(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2), 0, + "Failed to compare different case of DN in DN+Binary"); + + /* Test compare (false) with binary and non-binary prefix */ + dn1 = data_blob_string_const("B:6:abcdef:dc=samba,dc=org"); + dn2 = data_blob_string_const("dc=samba,dc=org"); + torture_assert(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2) != 0, + "compare of binary+dn an dn should have failed"); + + /* Test compare (false) with different binary prefix */ + dn1 = data_blob_string_const("B:6:abcdef:dc=samba,dc=org"); + dn2 = data_blob_string_const("B:4:abcd:dc=samba,dc=org"); + torture_assert(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2) != 0, + "compare of binary+dn an dn should have failed"); + + /* Test DN+String behaviour */ + torture_assert(torture, syntax = ldb_samba_syntax_by_name(ldb, DSDB_SYNTAX_STRING_DN), + "Failed to get DN+String schema attribute"); + + /* Test compare with different case of string */ + dn1 = data_blob_string_const("S:8:hihohiho:dc=samba,dc=org"); + dn2 = data_blob_string_const("S:8:HIHOHIHO:dc=samba,dc=org"); + torture_assert(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2) != 0, + "compare of string+dn an different case of string+dn should have failed"); + + /* Test compare with different case of DN */ + dn1 = data_blob_string_const("S:8:hihohiho:dc=samba,dc=org"); + dn2 = data_blob_string_const("S:8:hihohiho:dc=SAMBA,dc=org"); + torture_assert_int_equal(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2), 0, + "Failed to compare different case of DN in DN+String"); + torture_assert_int_equal(torture, + syntax->canonicalise_fn(ldb, mem_ctx, &dn1, &dn3), 0, + "Failed to canonicalise DN+String"); + torture_assert_data_blob_equal(torture, dn3, data_blob_string_const("S:8:hihohiho:DC=SAMBA,DC=ORG"), + "Failed to canonicalise DN+String"); + + /* Test compare (false) with string and non-string prefix */ + dn1 = data_blob_string_const("S:6:abcdef:dc=samba,dc=org"); + dn2 = data_blob_string_const("dc=samba,dc=org"); + torture_assert(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2) != 0, + "compare of string+dn an dn should have failed"); + + /* Test compare (false) with different string prefix */ + dn1 = data_blob_string_const("S:6:abcdef:dc=samba,dc=org"); + dn2 = data_blob_string_const("S:6:abcXYZ:dc=samba,dc=org"); + torture_assert(torture, + syntax->comparison_fn(ldb, mem_ctx, &dn1, &dn2) != 0, + "compare of string+dn an dn should have failed"); + + talloc_free(mem_ctx); + return true; +} + +static bool torture_dsdb_dn_valid(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx = talloc_new(torture); + struct ldb_context *ldb; + struct ldb_dn *dn; + struct dsdb_dn *dsdb_dn; + + struct ldb_val val; + + DATA_BLOB abcd_blob = data_blob_talloc(mem_ctx, "\xa\xb\xc\xd", 4); + + torture_assert(torture, + ldb = ldb_init(mem_ctx, torture->ev), + "Failed to init ldb"); + + torture_assert_int_equal(torture, + ldb_register_samba_handlers(ldb), LDB_SUCCESS, + "Failed to register Samba handlers"); + + ldb_set_utf8_fns(ldb, NULL, wrap_casefold); + + /* Check behaviour of a normal DN */ + torture_assert(torture, + dn = ldb_dn_new(mem_ctx, ldb, NULL), + "Failed to create a NULL DN"); + torture_assert(torture, + ldb_dn_validate(dn), + "Failed to validate NULL DN"); + torture_assert(torture, + ldb_dn_add_base_fmt(dn, "dc=org"), + "Failed to add base DN"); + torture_assert(torture, + ldb_dn_add_child_fmt(dn, "dc=samba"), + "Failed to add base DN"); + torture_assert_str_equal(torture, ldb_dn_get_linearized(dn), "dc=samba,dc=org", + "linearized DN incorrect"); + torture_assert(torture, dsdb_dn = dsdb_dn_construct(mem_ctx, dn, data_blob_null, LDB_SYNTAX_DN), + "Failed to build dsdb dn"); + torture_assert_str_equal(torture, dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 0), "dc=samba,dc=org", + "extended linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_linearized(mem_ctx, dsdb_dn), "dc=samba,dc=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_casefold(mem_ctx, dsdb_dn), "DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + + + /* Test constructing a binary DN */ + torture_assert(torture, dsdb_dn = dsdb_dn_construct(mem_ctx, dn, abcd_blob, DSDB_SYNTAX_BINARY_DN), + "Failed to build binary dsdb dn"); + torture_assert_str_equal(torture, dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 0), "B:8:0A0B0C0D:dc=samba,dc=org", + "extended linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_linearized(mem_ctx, dsdb_dn), "B:8:0A0B0C0D:dc=samba,dc=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_casefold(mem_ctx, dsdb_dn), "B:8:0A0B0C0D:DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + torture_assert_int_equal(torture, dsdb_dn->extra_part.length, 4, "length of extra-part should be 2"); + + + /* Test constructing a string DN */ + torture_assert(torture, dsdb_dn = dsdb_dn_construct(mem_ctx, dn, data_blob_talloc(mem_ctx, "hello", 5), DSDB_SYNTAX_STRING_DN), + "Failed to build string dsdb dn"); + torture_assert_str_equal(torture, dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 0), "S:5:hello:dc=samba,dc=org", + "extended linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_linearized(mem_ctx, dsdb_dn), "S:5:hello:dc=samba,dc=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_casefold(mem_ctx, dsdb_dn), "S:5:hello:DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + torture_assert_int_equal(torture, dsdb_dn->extra_part.length, 5, "length of extra-part should be 5"); + + + /* Test compose of binary+DN */ + val = data_blob_string_const("B:0::CN=Zer0,DC=SAMBA,DC=org"); + torture_assert(torture, + dsdb_dn = dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN), + "Failed to create a DN with a zero binary part in it"); + torture_assert_int_equal(torture, dsdb_dn->extra_part.length, 0, "length of extra-part should be 0"); + torture_assert_str_equal(torture, dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 0), "B:0::CN=Zer0,DC=SAMBA,DC=org", + "extended linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_linearized(mem_ctx, dsdb_dn), "B:0::CN=Zer0,DC=SAMBA,DC=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_casefold(mem_ctx, dsdb_dn), "B:0::CN=ZER0,DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + + /* Test parse of binary DN */ + val = data_blob_string_const("B:8:abcdabcd:CN=4,DC=Samba,DC=org"); + torture_assert(torture, + dsdb_dn = dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN), + "Failed to create a DN with a binary part in it"); + torture_assert_int_equal(torture, dsdb_dn->extra_part.length, 4, "length of extra-part should be 4"); + + torture_assert_str_equal(torture, dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 0), "B:8:ABCDABCD:CN=4,DC=Samba,DC=org", + "extended linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_linearized(mem_ctx, dsdb_dn), "B:8:ABCDABCD:CN=4,DC=Samba,DC=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_casefold(mem_ctx, dsdb_dn), "B:8:ABCDABCD:CN=4,DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + + /* Test parse of string+DN */ + val = data_blob_string_const("S:8:Goodbye!:CN=S,DC=Samba,DC=org"); + torture_assert(torture, + dsdb_dn = dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_STRING_DN), + "Failed to create a DN with a string part in it"); + torture_assert_int_equal(torture, dsdb_dn->extra_part.length, 8, "length of extra-part should be 8"); + torture_assert_str_equal(torture, dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 0), "S:8:Goodbye!:CN=S,DC=Samba,DC=org", + "extended linearized DN incorrect"); + + /* Test that the linearised DN is the postfix of the lineairsed dsdb_dn */ + torture_assert_str_equal(torture, ldb_dn_get_extended_linearized(mem_ctx, dsdb_dn->dn, 0), "CN=S,DC=Samba,DC=org", + "extended linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_linearized(mem_ctx, dsdb_dn), "S:8:Goodbye!:CN=S,DC=Samba,DC=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, ldb_dn_get_linearized(dsdb_dn->dn), "CN=S,DC=Samba,DC=org", + "linearized DN incorrect"); + torture_assert_str_equal(torture, dsdb_dn_get_casefold(mem_ctx, dsdb_dn), "S:8:Goodbye!:CN=S,DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + + /* Test that the casefold DN is the postfix of the casefolded dsdb_dn */ + torture_assert_str_equal(torture, ldb_dn_get_casefold(dsdb_dn->dn), "CN=S,DC=SAMBA,DC=ORG", + "casefold DN incorrect"); + + talloc_free(mem_ctx); + return true; +} + +static bool torture_dsdb_dn_invalid(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx = talloc_new(torture); + struct ldb_context *ldb; + struct ldb_val val; + + torture_assert(torture, + ldb = ldb_init(mem_ctx, torture->ev), + "Failed to init ldb"); + + torture_assert_int_equal(torture, + ldb_register_samba_handlers(ldb), LDB_SUCCESS, + "Failed to register Samba handlers"); + + ldb_set_utf8_fns(ldb, NULL, wrap_casefold); + + /* Check behaviour of a normal DN */ + val = data_blob_string_const("samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, LDB_SYNTAX_DN) == NULL, + "Should have failed to create a 'normal' invalid DN"); + + /* Test invalid binary DNs */ + val = data_blob_string_const("B:5:AB:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 'binary' DN"); + val = data_blob_string_const("B:5:ABCDEFG:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 'binary' DN"); + val = data_blob_string_const("B:10:AB:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 'binary' DN"); + val = data_blob_string_const("B:4:0xAB:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 0x preifx 'binary' DN"); + val = data_blob_string_const("B:2:0xAB:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 0x preifx 'binary' DN"); + val = data_blob_string_const("B:10:XXXXXXXXXX:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 'binary' DN"); + + val = data_blob_string_const("B:60::dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create an invalid 'binary' DN"); + + /* Test invalid string DNs */ + val = data_blob_string_const("S:5:hi:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_STRING_DN) == NULL, + "Should have Failed to create an invalid 'string' DN"); + val = data_blob_string_const("S:5:hihohiho:dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, + DSDB_SYNTAX_STRING_DN) == NULL, + "Should have Failed to create an invalid 'string' DN"); + + val = data_blob_string_const("<SID=" DSDB_DN_TEST_SID">;dc=samba,dc=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have failed to create an 'extended' DN marked as a binary DN"); + + /* Check DN based on MS-ADTS:3.1.1.5.1.2 Naming Constraints*/ + val = data_blob_string_const("CN=New\nLine,DC=SAMBA,DC=org"); + + /* changed to a warning until we understand the DEL: DNs */ + if (dsdb_dn_parse(mem_ctx, ldb, &val, LDB_SYNTAX_DN) != NULL) { + torture_warning(torture, + "Should have Failed to create a DN with 0xA in it"); + } + + val = data_blob_string_const("B:4:ABAB:CN=New\nLine,DC=SAMBA,DC=org"); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, LDB_SYNTAX_DN) == NULL, + "Should have Failed to create a DN with 0xA in it"); + + val = data_blob_const("CN=Zer\0,DC=SAMBA,DC=org", 23); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, LDB_SYNTAX_DN) == NULL, + "Should have Failed to create a DN with 0x0 in it"); + + val = data_blob_const("B:4:ABAB:CN=Zer\0,DC=SAMBA,DC=org", 23+9); + torture_assert(torture, + dsdb_dn_parse(mem_ctx, ldb, &val, DSDB_SYNTAX_BINARY_DN) == NULL, + "Should have Failed to create a DN with 0x0 in it"); + + return true; +} + +struct torture_suite *torture_dsdb_dn(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "dsdb.dn"); + + if (suite == NULL) { + return NULL; + } + + torture_suite_add_simple_test(suite, "valid", torture_dsdb_dn_valid); + torture_suite_add_simple_test(suite, "invalid", torture_dsdb_dn_invalid); + torture_suite_add_simple_test(suite, "attrs", torture_dsdb_dn_attrs); + + suite->description = talloc_strdup(suite, "DSDB DN tests"); + + return suite; +} diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c new file mode 100644 index 0000000..7030d9c --- /dev/null +++ b/source4/dsdb/common/util.c @@ -0,0 +1,6796 @@ +/* + 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_module.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 "../libds/common/flags.h" +#include "dsdb/common/proto.h" +#include "libcli/ldap/ldap_ndr.h" +#include "param/param.h" +#include "libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "system/locale.h" +#include "system/filesys.h" +#include "lib/util/tsort.h" +#include "dsdb/common/util.h" +#include "lib/socket/socket.h" +#include "librpc/gen_ndr/irpc.h" +#include "libds/common/flag_mapping.h" +#include "lib/util/access.h" +#include "lib/util/sys_rw_data.h" +#include "libcli/util/ntstatus.h" +#include "lib/util/smb_strtox.h" +#include "auth/auth.h" + +#undef strncasecmp +#undef strcasecmp + +/* + * This included to allow us to handle DSDB_FLAG_REPLICATED_UPDATE in + * dsdb_request_add_controls() + */ +#include "dsdb/samdb/ldb_modules/util.h" + +/* default is 30 minutes: -1e7 * 30 * 60 */ +#define DEFAULT_OBSERVATION_WINDOW (-18000000000) + +/* + 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 ldb_msg_find_attr_as_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; +} + +/* + search the sam for a single integer attribute in exactly 1 record +*/ +unsigned int samdb_search_uint(struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + unsigned int 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 ldb_msg_find_attr_as_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 ldb_msg_find_attr_as_int64(res[0], attr_name, default_value); +} + +/* + search the sam for multiple 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] = ldb_msg_find_attr_as_string(res[i], attr_name, NULL); + } + (*strs)[count] = NULL; + + return count; +} + +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) +{ + ssize_t ret; + const struct ldb_val *v; + struct dom_sid *sid; + 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; + } + ret = sid_parse(v->data, v->length, sid); + if (ret == -1) { + talloc_free(sid); + return NULL; + } + return sid; +} + + +/** + * Makes an auth_SidAttr structure from a objectSid in a result set and a + * supplied attribute value. + * + * @param [in] mem_ctx Talloc memory context on which to allocate the auth_SidAttr. + * @param [in] msg The message from which to take the objectSid. + * @param [in] attr The attribute name, usually "objectSid". + * @param [in] attrs SE_GROUP_* flags to go with the SID. + * @returns A pointer to the auth_SidAttr structure, or NULL on failure. + */ +struct auth_SidAttr *samdb_result_dom_sid_attrs(TALLOC_CTX *mem_ctx, const struct ldb_message *msg, + const char *attr, uint32_t attrs) +{ + ssize_t ret; + const struct ldb_val *v; + struct auth_SidAttr *sid; + v = ldb_msg_find_ldb_val(msg, attr); + if (v == NULL) { + return NULL; + } + sid = talloc(mem_ctx, struct auth_SidAttr); + if (sid == NULL) { + return NULL; + } + ret = sid_parse(v->data, v->length, &sid->sid); + if (ret == -1) { + talloc_free(sid); + return NULL; + } + sid->attrs = attrs; + return sid; +} + +/* + pull a dom_sid structure from a objectSid in a result set. +*/ +int samdb_result_dom_sid_buf(const struct ldb_message *msg, + const char *attr, + struct dom_sid *sid) +{ + ssize_t ret; + const struct ldb_val *v = NULL; + v = ldb_msg_find_ldb_val(msg, attr); + if (v == NULL) { + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + ret = sid_parse(v->data, v->length, sid); + if (ret == -1) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; +} + +/* + 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; + struct GUID guid; + NTSTATUS status; + + v = ldb_msg_find_ldb_val(msg, attr); + if (!v) return GUID_zero(); + + status = GUID_from_ndr_blob(v, &guid); + if (!NT_STATUS_IS_OK(status)) { + return GUID_zero(); + } + + 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(const struct ldb_message *msg, const char *attr, + NTTIME default_value) +{ + return ldb_msg_find_attr_as_uint64(msg, attr, default_value); +} + +/* + * Windows stores 0 for lastLogoff. + * But when a MS DC return the lastLogoff (as Logoff Time) + * it returns INT64_MAX, not returning this value in this case + * cause windows 2008 and newer version to fail for SMB requests + */ +NTTIME samdb_result_last_logoff(const struct ldb_message *msg) +{ + NTTIME ret = ldb_msg_find_attr_as_uint64(msg, "lastLogoff",0); + + if (ret == 0) + ret = INT64_MAX; + + return ret; +} + +/* + * Windows uses both 0 and 9223372036854775807 (INT64_MAX) to + * indicate an account doesn't expire. + * + * When Windows initially creates an account, it sets + * accountExpires = 9223372036854775807 (INT64_MAX). 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(const struct ldb_message *msg) +{ + NTTIME ret = ldb_msg_find_attr_as_uint64(msg, "accountExpires", + 0); + + if (ret == 0) + ret = INT64_MAX; + + return ret; +} + +/* + 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, + const struct ldb_message *msg, + const char *attr) +{ + uint64_t attr_time = ldb_msg_find_attr_as_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; +} + +/* + pull a samr_Password structutre from a result set. +*/ +struct samr_Password *samdb_result_hash(TALLOC_CTX *mem_ctx, const 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); + if (hash == NULL) { + return NULL; + } + memcpy(hash->hash, val->data, MIN(val->length, sizeof(hash->hash))); + } + return hash; +} + +/* + pull an array of samr_Password structures from a result set. +*/ +unsigned int samdb_result_hashes(TALLOC_CTX *mem_ctx, const struct ldb_message *msg, + const char *attr, struct samr_Password **hashes) +{ + unsigned int count, i; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + + *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; + } + talloc_keep_secret(*hashes); + + for (i=0;i<count;i++) { + memcpy((*hashes)[i].hash, (i*16)+(char *)val->data, 16); + } + + return count; +} + +NTSTATUS samdb_result_passwords_from_history(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const struct ldb_message *msg, + unsigned int idx, + const struct samr_Password **lm_pwd, + const struct samr_Password **nt_pwd) +{ + struct samr_Password *lmPwdHash, *ntPwdHash; + + if (nt_pwd) { + unsigned int num_nt; + num_nt = samdb_result_hashes(mem_ctx, msg, "ntPwdHistory", &ntPwdHash); + if (num_nt <= idx) { + *nt_pwd = NULL; + } else { + *nt_pwd = &ntPwdHash[idx]; + } + } + 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 (lpcfg_lanman_auth(lp_ctx)) { + unsigned int num_lm; + num_lm = samdb_result_hashes(mem_ctx, msg, "lmPwdHistory", &lmPwdHash); + if (num_lm <= idx) { + *lm_pwd = NULL; + } else { + *lm_pwd = &lmPwdHash[idx]; + } + } else { + *lm_pwd = NULL; + } + } + return NT_STATUS_OK; +} + +NTSTATUS samdb_result_passwords_no_lockout(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const struct ldb_message *msg, + struct samr_Password **nt_pwd) +{ + struct samr_Password *ntPwdHash; + + if (nt_pwd) { + unsigned 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]; + } + } + return NT_STATUS_OK; +} + +NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const struct ldb_message *msg, + struct samr_Password **nt_pwd) +{ + uint16_t acct_flags; + + acct_flags = samdb_result_acct_flags(msg, + "msDS-User-Account-Control-Computed"); + /* Quit if the account was locked out. */ + if (acct_flags & ACB_AUTOLOCK) { + DEBUG(3,("samdb_result_passwords: Account for user %s was locked out.\n", + ldb_dn_get_linearized(msg->dn))); + return NT_STATUS_ACCOUNT_LOCKED_OUT; + } + + return samdb_result_passwords_no_lockout(mem_ctx, lp_ctx, msg, + nt_pwd); +} + +/* + 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 = {}; + size_t units_per_week = 168; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + + if (val) { + units_per_week = val->length * 8; + } + + hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week/8); + if (!hours.bits) { + return hours; + } + hours.units_per_week = units_per_week; + memset(hours.bits, 0xFF, units_per_week/8); + if (val) { + memcpy(hours.bits, val->data, val->length); + } + + return hours; +} + +/* + pull a set of account_flags from a result set. + + Naturally, this requires that userAccountControl and + (if not null) the attributes 'attr' be already + included in msg +*/ +uint32_t samdb_result_acct_flags(const struct ldb_message *msg, const char *attr) +{ + uint32_t userAccountControl = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0); + uint32_t attr_flags = 0; + uint32_t acct_flags = ds_uf2acb(userAccountControl); + if (attr) { + attr_flags = ldb_msg_find_attr_as_uint(msg, attr, UF_ACCOUNTDISABLE); + if (attr_flags == UF_ACCOUNTDISABLE) { + DEBUG(0, ("Attribute %s not found, disabling account %s!\n", attr, + ldb_dn_get_linearized(msg->dn))); + } + acct_flags |= ds_uf2acb(attr_flags); + } + + return acct_flags; +} + +NTSTATUS samdb_result_parameters(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + const char *attr, + struct lsa_BinaryString *s) +{ + int i; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + + ZERO_STRUCTP(s); + + if (!val) { + return NT_STATUS_OK; + } + + if ((val->length % 2) != 0) { + /* + * If the on-disk data is not even in length, we know + * it is corrupt, and can not be safely pushed. We + * would either truncate, send an uninitialised + * byte or send a forced zero byte + */ + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + s->array = talloc_array(mem_ctx, uint16_t, val->length/2); + if (!s->array) { + return NT_STATUS_NO_MEMORY; + } + s->length = s->size = val->length; + + /* The on-disk format is the 'network' format, being UTF16LE (sort of) */ + for (i = 0; i < s->length / 2; i++) { + s->array[i] = SVAL(val->data, i * 2); + } + + return NT_STATUS_OK; +} + +/* Find an attribute, with a particular value */ + +/* The current callers of this function expect a very specific + * behaviour: In particular, objectClass subclass equivalence 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) +{ + unsigned 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; +} + +static int samdb_find_or_add_attribute_ex(struct ldb_context *ldb, + struct ldb_message *msg, + const char *name, + const char *set_value, + unsigned attr_flags, + bool *added) +{ + int ret; + struct ldb_message_element *el; + + SMB_ASSERT(attr_flags != 0); + + el = ldb_msg_find_element(msg, name); + if (el) { + if (added != NULL) { + *added = false; + } + + return LDB_SUCCESS; + } + + ret = ldb_msg_add_empty(msg, name, + attr_flags, + &el); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (set_value != NULL) { + ret = ldb_msg_add_string(msg, name, set_value); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (added != NULL) { + *added = true; + } + return LDB_SUCCESS; +} + +int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value) +{ + return samdb_find_or_add_attribute_ex(ldb, msg, name, set_value, LDB_FLAG_MOD_ADD, NULL); +} + +/* + 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, const struct dom_sid *sid) +{ + struct ldb_val v; + enum ndr_err_code ndr_err; + + ndr_err = ndr_push_struct_blob(&v, mem_ctx, + sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ldb_operr(sam_ldb); + } + 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 + dsdb_replace() to be used everywhere */ + return ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_REPLACE, NULL); +} + +/* + add an add attribute value to a message or enhance an existing attribute + which has the same name and the add flag set. +*/ +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; + struct ldb_val val; + char *v; + unsigned int i; + bool found = false; + int ret; + + v = talloc_strdup(mem_ctx, value); + if (v == NULL) { + return ldb_oom(sam_ldb); + } + + val.data = (uint8_t *) v; + val.length = strlen(v); + + if (val.length == 0) { + /* allow empty strings as non-existent attributes */ + return LDB_SUCCESS; + } + + for (i = 0; i < msg->num_elements; i++) { + el = &msg->elements[i]; + if ((ldb_attr_cmp(el->name, attr_name) == 0) && + (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_ADD)) { + found = true; + break; + } + } + if (!found) { + ret = ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_ADD, + &el); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + ret = ldb_msg_element_add_value(msg->elements, el, &val); + if (ret != LDB_SUCCESS) { + return ldb_oom(sam_ldb); + } + + return LDB_SUCCESS; +} + +/* + add a delete attribute value to a message or enhance an existing attribute + which has the same name and the delete flag set. +*/ +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; + struct ldb_val val; + char *v; + unsigned int i; + bool found = false; + int ret; + + v = talloc_strdup(mem_ctx, value); + if (v == NULL) { + return ldb_oom(sam_ldb); + } + + val.data = (uint8_t *) v; + val.length = strlen(v); + + if (val.length == 0) { + /* allow empty strings as non-existent attributes */ + return LDB_SUCCESS; + } + + for (i = 0; i < msg->num_elements; i++) { + el = &msg->elements[i]; + if ((ldb_attr_cmp(el->name, attr_name) == 0) && + (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE)) { + found = true; + break; + } + } + if (!found) { + ret = ldb_msg_add_empty(msg, attr_name, LDB_FLAG_MOD_DELETE, + &el); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + ret = ldb_msg_element_add_value(msg->elements, el, &val); + if (ret != LDB_SUCCESS) { + return ldb_oom(sam_ldb); + } + + return LDB_SUCCESS; +} + +/* + 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); + if (s == NULL) { + return ldb_oom(sam_ldb); + } + return ldb_msg_add_string(msg, attr_name, s); +} + +int samdb_msg_add_int_flags(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, int v, int flags) +{ + const char *s = talloc_asprintf(mem_ctx, "%d", v); + if (s == NULL) { + return ldb_oom(sam_ldb); + } + return ldb_msg_add_string_flags(msg, attr_name, s, flags); +} + +/* + * Add an unsigned int element to a message + * + * The issue here is that we have not yet first cast to int32_t explicitly, + * before we cast to an signed int to printf() into the %d or cast to a + * int64_t before we then cast to a long long to printf into a %lld. + * + * There are *no* unsigned integers in Active Directory LDAP, even the RID + * allocations and ms-DS-Secondary-KrbTgt-Number are *signed* quantities. + * (See the schema, and the syntax definitions in schema_syntax.c). + * + */ +int samdb_msg_add_uint(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, unsigned int v) +{ + return samdb_msg_add_int(sam_ldb, mem_ctx, msg, attr_name, (int)v); +} + +int samdb_msg_add_uint_flags(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, unsigned int v, int flags) +{ + return samdb_msg_add_int_flags(sam_ldb, mem_ctx, msg, attr_name, (int)v, flags); +} + +/* + 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); + if (s == NULL) { + return ldb_oom(sam_ldb); + } + return ldb_msg_add_string(msg, attr_name, s); +} + +/* + * Add an unsigned int64_t (uint64_t) element to a message + * + * The issue here is that we have not yet first cast to int32_t explicitly, + * before we cast to an signed int to printf() into the %d or cast to a + * int64_t before we then cast to a long long to printf into a %lld. + * + * There are *no* unsigned integers in Active Directory LDAP, even the RID + * allocations and ms-DS-Secondary-KrbTgt-Number are *signed* quantities. + * (See the schema, and the syntax definitions in schema_syntax.c). + * + */ +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) +{ + return samdb_msg_add_int64(sam_ldb, mem_ctx, msg, attr_name, (int64_t)v); +} + +/* + append a int element to a message +*/ +int samdb_msg_append_int(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, int v, int flags) +{ + const char *s = talloc_asprintf(mem_ctx, "%d", v); + if (s == NULL) { + return ldb_oom(sam_ldb); + } + return ldb_msg_append_string(msg, attr_name, s, flags); +} + +/* + * Append an unsigned int element to a message + * + * The issue here is that we have not yet first cast to int32_t explicitly, + * before we cast to an signed int to printf() into the %d or cast to a + * int64_t before we then cast to a long long to printf into a %lld. + * + * There are *no* unsigned integers in Active Directory LDAP, even the RID + * allocations and ms-DS-Secondary-KrbTgt-Number are *signed* quantities. + * (See the schema, and the syntax definitions in schema_syntax.c). + * + */ +int samdb_msg_append_uint(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, unsigned int v, int flags) +{ + return samdb_msg_append_int(sam_ldb, mem_ctx, msg, attr_name, (int)v, flags); +} + +/* + append a (signed) int64_t element to a message +*/ +int samdb_msg_append_int64(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, int64_t v, int flags) +{ + const char *s = talloc_asprintf(mem_ctx, "%lld", (long long)v); + if (s == NULL) { + return ldb_oom(sam_ldb); + } + return ldb_msg_append_string(msg, attr_name, s, flags); +} + +/* + * Append an unsigned int64_t (uint64_t) element to a message + * + * The issue here is that we have not yet first cast to int32_t explicitly, + * before we cast to an signed int to printf() into the %d or cast to a + * int64_t before we then cast to a long long to printf into a %lld. + * + * There are *no* unsigned integers in Active Directory LDAP, even the RID + * allocations and ms-DS-Secondary-KrbTgt-Number are *signed* quantities. + * (See the schema, and the syntax definitions in schema_syntax.c). + * + */ +int samdb_msg_append_uint64(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, uint64_t v, int flags) +{ + return samdb_msg_append_int64(sam_ldb, mem_ctx, msg, attr_name, (int64_t)v, flags); +} + +/* + 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, const struct samr_Password *hash) +{ + struct ldb_val val; + val.data = talloc_memdup(mem_ctx, hash->hash, 16); + if (!val.data) { + return ldb_oom(sam_ldb); + } + 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(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, struct samr_Password *hashes, + unsigned int count) +{ + struct ldb_val val; + unsigned int i; + val.data = talloc_array_size(mem_ctx, 16, count); + val.length = count*16; + if (!val.data) { + return ldb_oom(ldb); + } + 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, ds_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) +{ + int i; + struct ldb_val val; + if ((parameters->length % 2) != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + val.data = talloc_array(mem_ctx, uint8_t, parameters->length); + if (val.data == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + val.length = parameters->length; + for (i = 0; i < parameters->length / 2; i++) { + /* + * The on-disk format needs to be in the 'network' + * format, parameters->array is a uint16_t array of + * length parameters->length / 2 + */ + SSVAL(val.data, i * 2, parameters->array[i]); + } + return ldb_msg_add_steal_value(msg, attr_name, &val); +} + +/* + * Sets an unsigned int element in a message + * + * The issue here is that we have not yet first cast to int32_t explicitly, + * before we cast to an signed int to printf() into the %d or cast to a + * int64_t before we then cast to a long long to printf into a %lld. + * + * There are *no* unsigned integers in Active Directory LDAP, even the RID + * allocations and ms-DS-Secondary-KrbTgt-Number are *signed* quantities. + * (See the schema, and the syntax definitions in schema_syntax.c). + * + */ +int samdb_msg_set_uint(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, + struct ldb_message *msg, const char *attr_name, + unsigned int v) +{ + struct ldb_message_element *el; + + el = ldb_msg_find_element(msg, attr_name); + if (el) { + el->num_values = 0; + } + return samdb_msg_add_uint(sam_ldb, mem_ctx, msg, attr_name, v); +} + +/* + * Handle ldb_request in transaction + */ +int dsdb_autotransaction_request(struct ldb_context *sam_ldb, + struct ldb_request *req) +{ + int ret; + + ret = ldb_transaction_start(sam_ldb); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_request(sam_ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + if (ret == LDB_SUCCESS) { + return ldb_transaction_commit(sam_ldb); + } + ldb_transaction_cancel(sam_ldb); + + return ret; +} + +/* + 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_aggregate_schema_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx) +{ + struct ldb_dn *schema_dn = ldb_get_schema_basedn(sam_ctx); + struct ldb_dn *aggregate_dn; + if (!schema_dn) { + return NULL; + } + + aggregate_dn = ldb_dn_copy(mem_ctx, schema_dn); + if (!aggregate_dn) { + return NULL; + } + if (!ldb_dn_add_child_fmt(aggregate_dn, "CN=Aggregate")) { + return NULL; + } + return aggregate_dn; +} + +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, ldb_get_config_basedn(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_infrastructure_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx) +{ + struct ldb_dn *new_dn; + + new_dn = ldb_dn_copy(mem_ctx, ldb_get_default_basedn(sam_ctx)); + if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Infrastructure")) { + talloc_free(new_dn); + return NULL; + } + return new_dn; +} + +struct ldb_dn *samdb_system_container_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx) +{ + struct ldb_dn *new_dn = NULL; + bool ok; + + new_dn = ldb_dn_copy(mem_ctx, ldb_get_default_basedn(sam_ctx)); + if (new_dn == NULL) { + return NULL; + } + + ok = ldb_dn_add_child_fmt(new_dn, "CN=System"); + if (!ok) { + 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, ldb_get_config_basedn(sam_ctx)); + if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Sites")) { + talloc_free(new_dn); + return NULL; + } + return new_dn; +} + +struct ldb_dn *samdb_extended_rights_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx) +{ + struct ldb_dn *new_dn; + + new_dn = ldb_dn_copy(mem_ctx, ldb_get_config_basedn(sam_ctx)); + if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Extended-Rights")) { + 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: + talloc_free(tmp_ctx); + return NULL; +} + +/* + get domain sid from cache +*/ +const struct dom_sid *samdb_domain_sid_cache_only(struct ldb_context *ldb) +{ + return (struct dom_sid *)ldb_get_opaque(ldb, "cache.domain_sid"); +} + +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; +} + +/* + work out the domain guid for the current open ldb +*/ +const struct GUID *samdb_domain_guid(struct ldb_context *ldb) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct GUID *domain_guid = NULL; + const char *attrs[] = { + "objectGUID", + NULL + }; + struct ldb_result *res = NULL; + int ret; + + /* see if we have a cached copy */ + domain_guid = (struct GUID *)ldb_get_opaque(ldb, "cache.domain_guid"); + if (domain_guid) { + return domain_guid; + } + + 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, "objectGUID=*"); + if (ret != LDB_SUCCESS) { + goto failed; + } + + if (res->count != 1) { + goto failed; + } + + domain_guid = talloc(tmp_ctx, struct GUID); + if (domain_guid == NULL) { + goto failed; + } + *domain_guid = samdb_result_guid(res->msgs[0], "objectGUID"); + + /* cache the domain_sid in the ldb */ + if (ldb_set_opaque(ldb, "cache.domain_guid", domain_guid) != LDB_SUCCESS) { + goto failed; + } + + talloc_steal(ldb, domain_guid); + talloc_free(tmp_ctx); + + return domain_guid; + +failed: + talloc_free(tmp_ctx); + return NULL; +} + +bool samdb_set_ntds_settings_dn(struct ldb_context *ldb, struct ldb_dn *ntds_settings_dn_in) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *ntds_settings_dn_new; + struct ldb_dn *ntds_settings_dn_old; + + /* see if we have a forced copy from provision */ + ntds_settings_dn_old = talloc_get_type(ldb_get_opaque(ldb, + "forced.ntds_settings_dn"), struct ldb_dn); + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + goto failed; + } + + ntds_settings_dn_new = ldb_dn_copy(tmp_ctx, ntds_settings_dn_in); + if (!ntds_settings_dn_new) { + goto failed; + } + + /* set the DN in the ldb to avoid lookups during provision */ + if (ldb_set_opaque(ldb, "forced.ntds_settings_dn", ntds_settings_dn_new) != LDB_SUCCESS) { + goto failed; + } + + talloc_steal(ldb, ntds_settings_dn_new); + talloc_free(tmp_ctx); + talloc_free(ntds_settings_dn_old); + + return true; + +failed: + DEBUG(1,("Failed to set our NTDS Settings DN in the ldb!\n")); + talloc_free(tmp_ctx); + return false; +} + +/* + work out the ntds settings dn for the current open ldb +*/ +struct ldb_dn *samdb_ntds_settings_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) +{ + 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, "forced.ntds_settings_dn"); + if (settings_dn) { + return ldb_dn_copy(mem_ctx, settings_dn); + } + + tmp_ctx = talloc_new(mem_ctx); + 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 != LDB_SUCCESS) { + 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"); + + /* note that we do not cache the DN here, as that would mean + * we could not handle server renames at runtime. Only + * provision sets up forced.ntds_settings_dn */ + + talloc_steal(mem_ctx, 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/objectGUID for the current open ldb +*/ +static const struct GUID *samdb_ntds_GUID(struct ldb_context *ldb, + const char *attribute, + const char *cache_name) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { attribute, NULL }; + int ret; + struct ldb_result *res; + struct GUID *ntds_guid; + struct ldb_dn *ntds_settings_dn = NULL; + const char *errstr = NULL; + + /* see if we have a cached copy */ + ntds_guid = (struct GUID *)ldb_get_opaque(ldb, cache_name); + if (ntds_guid != NULL) { + return ntds_guid; + } + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + goto failed; + } + + ntds_settings_dn = samdb_ntds_settings_dn(ldb, tmp_ctx); + if (ntds_settings_dn == NULL) { + errstr = "samdb_ntds_settings_dn() returned NULL"; + goto failed; + } + + ret = ldb_search(ldb, tmp_ctx, &res, ntds_settings_dn, + LDB_SCOPE_BASE, attrs, NULL); + if (ret) { + errstr = ldb_errstring(ldb); + goto failed; + } + + if (res->count != 1) { + errstr = "incorrect number of results from base search"; + goto failed; + } + + ntds_guid = talloc(tmp_ctx, struct GUID); + if (ntds_guid == NULL) { + goto failed; + } + + *ntds_guid = samdb_result_guid(res->msgs[0], attribute); + + if (GUID_all_zero(ntds_guid)) { + if (ldb_msg_find_ldb_val(res->msgs[0], attribute)) { + errstr = "failed to find the GUID attribute"; + } else { + errstr = "failed to parse the GUID"; + } + goto failed; + } + + /* cache the domain_sid in the ldb */ + if (ldb_set_opaque(ldb, cache_name, ntds_guid) != LDB_SUCCESS) { + errstr = "ldb_set_opaque() failed"; + goto failed; + } + + talloc_steal(ldb, ntds_guid); + talloc_free(tmp_ctx); + + return ntds_guid; + +failed: + DBG_WARNING("Failed to find our own NTDS Settings %s in the ldb: %s!\n", + attribute, errstr); + talloc_free(tmp_ctx); + return NULL; +} + +/* + work out the ntds settings objectGUID for the current open ldb +*/ +const struct GUID *samdb_ntds_objectGUID(struct ldb_context *ldb) +{ + return samdb_ntds_GUID(ldb, "objectGUID", "cache.ntds_guid"); +} + +/* + work out the ntds settings invocationId for the current open ldb +*/ +const struct GUID *samdb_ntds_invocation_id(struct ldb_context *ldb) +{ + return samdb_ntds_GUID(ldb, "invocationId", "cache.invocation_id"); +} + +static bool samdb_set_ntds_GUID(struct ldb_context *ldb, + const struct GUID *ntds_guid_in, + const char *attribute, + const char *cache_name) +{ + 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_name); + + 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_name, 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: + DBG_WARNING("Failed to set our own cached %s in the ldb!\n", + attribute); + talloc_free(tmp_ctx); + return false; +} + +bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in) +{ + return samdb_set_ntds_GUID(ldb, + ntds_guid_in, + "objectGUID", + "cache.ntds_guid"); +} + +bool samdb_set_ntds_invocation_id(struct ldb_context *ldb, const struct GUID *invocation_id_in) +{ + return samdb_set_ntds_GUID(ldb, + invocation_id_in, + "invocationId", + "cache.invocation_id"); +} + +/* + work out the server dn for the current open ldb +*/ +struct ldb_dn *samdb_server_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + struct ldb_dn *dn; + if (!tmp_ctx) { + return NULL; + } + dn = ldb_dn_get_parent(mem_ctx, samdb_ntds_settings_dn(ldb, tmp_ctx)); + talloc_free(tmp_ctx); + return dn; + +} + +/* + 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 *servers_dn; + struct ldb_dn *server_site_dn; + + /* TODO: there must be a saner way to do this!! */ + server_dn = samdb_server_dn(ldb, mem_ctx); + if (!server_dn) return NULL; + + servers_dn = ldb_dn_get_parent(mem_ctx, server_dn); + talloc_free(server_dn); + if (!servers_dn) return NULL; + + server_site_dn = ldb_dn_get_parent(mem_ctx, servers_dn); + talloc_free(servers_dn); + + return server_site_dn; +} + +/* + find the site name from a computers DN record + */ +int samdb_find_site_for_computer(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, struct ldb_dn *computer_dn, + const char **site_name) +{ + int ret; + struct ldb_dn *dn; + const struct ldb_val *rdn_val; + + *site_name = NULL; + + ret = samdb_reference_dn(ldb, mem_ctx, computer_dn, "serverReferenceBL", &dn); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (!ldb_dn_remove_child_components(dn, 2)) { + talloc_free(dn); + return LDB_ERR_INVALID_DN_SYNTAX; + } + + rdn_val = ldb_dn_get_rdn_val(dn); + if (rdn_val == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + (*site_name) = talloc_strndup(mem_ctx, (const char *)rdn_val->data, rdn_val->length); + talloc_free(dn); + if (!*site_name) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; +} + +/* + find the NTDS GUID from a computers DN record + */ +int samdb_find_ntdsguid_for_computer(struct ldb_context *ldb, struct ldb_dn *computer_dn, + struct GUID *ntds_guid) +{ + int ret; + struct ldb_dn *dn; + + *ntds_guid = GUID_zero(); + + ret = samdb_reference_dn(ldb, ldb, computer_dn, "serverReferenceBL", &dn); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (!ldb_dn_add_child_fmt(dn, "CN=NTDS Settings")) { + talloc_free(dn); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_find_guid_by_dn(ldb, dn, ntds_guid); + talloc_free(dn); + return ret; +} + +/* + find a 'reference' DN that points at another object + (eg. serverReference, rIDManagerReference etc) + */ +int samdb_reference_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn *base, + const char *attribute, struct ldb_dn **dn) +{ + const char *attrs[2]; + struct ldb_result *res; + int ret; + + attrs[0] = attribute; + attrs[1] = NULL; + + ret = dsdb_search(ldb, mem_ctx, &res, base, LDB_SCOPE_BASE, attrs, DSDB_SEARCH_ONE_ONLY|DSDB_SEARCH_SHOW_EXTENDED_DN, NULL); + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb, "Cannot find DN %s to get attribute %s for reference dn: %s", + ldb_dn_get_linearized(base), attribute, ldb_errstring(ldb)); + return ret; + } + + *dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, res->msgs[0], attribute); + if (!*dn) { + if (!ldb_msg_find_element(res->msgs[0], attribute)) { + ldb_asprintf_errstring(ldb, "Cannot find attribute %s of %s to calculate reference dn", attribute, + ldb_dn_get_linearized(base)); + } else { + ldb_asprintf_errstring(ldb, "Cannot interpret attribute %s of %s as a dn", attribute, + ldb_dn_get_linearized(base)); + } + talloc_free(res); + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + + talloc_free(res); + return LDB_SUCCESS; +} + +/* + find if a DN (must have GUID component!) is our ntdsDsa + */ +int samdb_dn_is_our_ntdsa(struct ldb_context *ldb, struct ldb_dn *dn, bool *is_ntdsa) +{ + NTSTATUS status; + struct GUID dn_guid; + const struct GUID *our_ntds_guid; + status = dsdb_get_extended_dn_guid(dn, &dn_guid, "GUID"); + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + our_ntds_guid = samdb_ntds_objectGUID(ldb); + if (!our_ntds_guid) { + DEBUG(0, ("Failed to find our NTDS Settings GUID for comparison with %s - %s\n", ldb_dn_get_linearized(dn), ldb_errstring(ldb))); + return LDB_ERR_OPERATIONS_ERROR; + } + + *is_ntdsa = GUID_equal(&dn_guid, our_ntds_guid); + return LDB_SUCCESS; +} + +/* + find a 'reference' DN that points at another object and indicate if it is our ntdsDsa + */ +int samdb_reference_dn_is_our_ntdsa(struct ldb_context *ldb, struct ldb_dn *base, + const char *attribute, bool *is_ntdsa) +{ + int ret; + struct ldb_dn *referenced_dn; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ret = samdb_reference_dn(ldb, tmp_ctx, base, attribute, &referenced_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to find object %s for attribute %s - %s\n", ldb_dn_get_linearized(base), attribute, ldb_errstring(ldb))); + return ret; + } + + ret = samdb_dn_is_our_ntdsa(ldb, referenced_dn, is_ntdsa); + + talloc_free(tmp_ctx); + return ret; +} + +/* + find our machine account via the serverReference attribute in the + server DN + */ +int samdb_server_reference_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn **dn) +{ + struct ldb_dn *server_dn; + int ret; + + server_dn = samdb_server_dn(ldb, mem_ctx); + if (server_dn == NULL) { + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); + } + + ret = samdb_reference_dn(ldb, mem_ctx, server_dn, "serverReference", dn); + talloc_free(server_dn); + + return ret; +} + +/* + find the RID Manager$ DN via the rIDManagerReference attribute in the + base DN + */ +int samdb_rid_manager_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn **dn) +{ + return samdb_reference_dn(ldb, mem_ctx, ldb_get_default_basedn(ldb), + "rIDManagerReference", dn); +} + +/* + find the RID Set DN via the rIDSetReferences attribute in our + machine account DN + */ +int samdb_rid_set_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_dn **dn) +{ + struct ldb_dn *server_ref_dn = NULL; + int ret; + + ret = samdb_server_reference_dn(ldb, mem_ctx, &server_ref_dn); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = samdb_reference_dn(ldb, mem_ctx, server_ref_dn, "rIDSetReferences", dn); + talloc_free(server_ref_dn); + return ret; +} + +const char *samdb_server_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) +{ + const struct ldb_val *val = ldb_dn_get_rdn_val(samdb_server_site_dn(ldb, + mem_ctx)); + + if (val == NULL) { + return NULL; + } + + return (const char *) val->data; +} + +/* + * Finds the client site by using the client's IP address. + * The "subnet_name" returns the name of the subnet if parameter != NULL + * + * Has a Windows-based fallback to provide the only site available, or an empty + * string if there are multiple sites. + */ +const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, + const char *ip_address, char **subnet_name, + bool fallback) +{ + const char *attrs[] = { "cn", "siteObject", NULL }; + struct ldb_dn *sites_container_dn = NULL; + struct ldb_dn *subnets_dn = NULL; + struct ldb_dn *sites_dn = NULL; + struct ldb_result *res = NULL; + const struct ldb_val *val = NULL; + const char *site_name = NULL; + const char *l_subnet_name = NULL; + const char *allow_list[2] = { NULL, NULL }; + unsigned int i, count; + int ret; + + /* + * if we don't have a client ip e.g. ncalrpc + * the server site is the client site + */ + if (ip_address == NULL) { + return samdb_server_site_name(ldb, mem_ctx); + } + + sites_container_dn = samdb_sites_dn(ldb, mem_ctx); + if (sites_container_dn == NULL) { + goto exit; + } + + subnets_dn = ldb_dn_copy(mem_ctx, sites_container_dn); + if ( ! ldb_dn_add_child_fmt(subnets_dn, "CN=Subnets")) { + goto exit; + } + + ret = ldb_search(ldb, mem_ctx, &res, subnets_dn, LDB_SCOPE_ONELEVEL, + attrs, NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + count = 0; + } else if (ret != LDB_SUCCESS) { + goto exit; + } else { + count = res->count; + } + + for (i = 0; i < count; i++) { + l_subnet_name = ldb_msg_find_attr_as_string(res->msgs[i], "cn", + NULL); + + allow_list[0] = l_subnet_name; + + if (allow_access_nolog(NULL, allow_list, "", ip_address)) { + sites_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, + res->msgs[i], + "siteObject"); + if (sites_dn == NULL) { + /* No reference, maybe another subnet matches */ + continue; + } + + /* "val" cannot be NULL here since "sites_dn" != NULL */ + val = ldb_dn_get_rdn_val(sites_dn); + site_name = talloc_strdup(mem_ctx, + (const char *) val->data); + + TALLOC_FREE(sites_dn); + + break; + } + } + + if (site_name == NULL && fallback) { + /* This is the Windows Server fallback rule: when no subnet + * exists and we have only one site available then use it (it + * is for sure the same as our server site). If more sites do + * exist then we don't know which one to use and set the site + * name to "". */ + size_t cnt = 0; + ret = dsdb_domain_count( + ldb, + &cnt, + sites_container_dn, + NULL, + LDB_SCOPE_SUBTREE, + "(objectClass=site)"); + if (ret != LDB_SUCCESS) { + goto exit; + } + if (cnt == 1) { + site_name = samdb_server_site_name(ldb, mem_ctx); + } else { + site_name = talloc_strdup(mem_ctx, ""); + } + l_subnet_name = NULL; + } + + if (subnet_name != NULL) { + *subnet_name = talloc_strdup(mem_ctx, l_subnet_name); + } + +exit: + TALLOC_FREE(sites_container_dn); + TALLOC_FREE(subnets_dn); + TALLOC_FREE(res); + + return site_name; +} + +/* + work out if we are the PDC for the domain of the current open ldb +*/ +bool samdb_is_pdc(struct ldb_context *ldb) +{ + int ret; + bool is_pdc; + + ret = samdb_reference_dn_is_our_ntdsa(ldb, ldb_get_default_basedn(ldb), "fsmoRoleOwner", + &is_pdc); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to find if we are the PDC for this ldb: Searching for fSMORoleOwner in %s failed: %s\n", + ldb_dn_get_linearized(ldb_get_default_basedn(ldb)), + ldb_errstring(ldb))); + return false; + } + + return is_pdc; +} + +/* + 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) +{ + uint32_t options = 0; + if (samdb_ntds_options(ldb, &options) != LDB_SUCCESS) { + return false; + } + return (options & DS_NTDSDSA_OPT_IS_GC) != 0; +} + +/* 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 = LDB_SUCCESS; + const char *attrs[] = { NULL }; + + local_ctx = talloc_new(mem_ctx); + if (local_ctx == NULL) return ldb_oom(ldb); + + 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))"); + 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; + } + /* should never be true with 'ret=LDB_SUCCESS', here to satisfy clang */ + if (res == NULL) { + talloc_free(local_ctx); + return LDB_ERR_OTHER; + } + if (res->count != 1) { + *errstring = talloc_asprintf(mem_ctx, "Invalid dn (%s), not child of a domain object", + ldb_dn_get_linearized(dn)); + DEBUG(0,(__location__ ": %s\n", *errstring)); + 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; +} + +static void pwd_timeout_debug(struct tevent_context *unused1, + struct tevent_timer *unused2, + struct timeval unused3, + void *unused4) +{ + DEBUG(0, ("WARNING: check_password_complexity: password script " + "took more than 1 second to run\n")); +} + + +/* + * Performs checks on a user password (plaintext UNIX format - attribute + * "password"). The remaining parameters have to be extracted from the domain + * object in the AD. + * + * Result codes from "enum samr_ValidationStatus" (consider "samr.idl") + */ +enum samr_ValidationStatus samdb_check_password(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *account_name, + const char *user_principal_name, + const char *full_name, + const DATA_BLOB *utf8_blob, + const uint32_t pwdProperties, + const uint32_t minPwdLength) +{ + const struct loadparm_substitution *lp_sub = + lpcfg_noop_substitution(); + char *password_script = NULL; + const char *utf8_pw = (const char *)utf8_blob->data; + + /* + * This looks strange because it is. + * + * The check for the number of characters in the password + * should clearly not be against the byte length, or else a + * single UTF8 character would count for more than one. + * + * We have chosen to use the number of 16-bit units that the + * password encodes to as the measure of length. This is not + * the same as the number of codepoints, if a password + * contains a character beyond the Basic Multilingual Plane + * (above 65535) it will count for more than one "character". + */ + + size_t password_characters_roughly = strlen_m(utf8_pw); + + /* checks if the "minPwdLength" property is satisfied */ + if (minPwdLength > password_characters_roughly) { + return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT; + } + + /* We might not be asked to check the password complexity */ + if (!(pwdProperties & DOMAIN_PASSWORD_COMPLEX)) { + return SAMR_VALIDATION_STATUS_SUCCESS; + } + + if (password_characters_roughly == 0) { + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + + password_script = lpcfg_check_password_script(lp_ctx, lp_sub, mem_ctx); + if (password_script != NULL && *password_script != '\0') { + int check_ret = 0; + int error = 0; + ssize_t nwritten = 0; + struct tevent_context *event_ctx = NULL; + struct tevent_req *req = NULL; + int cps_stdin = -1; + const char * const cmd[4] = { + "/bin/sh", "-c", + password_script, + NULL + }; + + event_ctx = tevent_context_init(mem_ctx); + if (event_ctx == NULL) { + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + /* Gives a warning after 1 second, terminates after 10 */ + tevent_add_timer(event_ctx, event_ctx, + tevent_timeval_current_ofs(1, 0), + pwd_timeout_debug, NULL); + + check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", account_name, 1); + if (check_ret != 0) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + if (user_principal_name != NULL) { + check_ret = setenv("SAMBA_CPS_USER_PRINCIPAL_NAME", + user_principal_name, 1); + } else { + unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME"); + } + if (check_ret != 0) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + if (full_name != NULL) { + check_ret = setenv("SAMBA_CPS_FULL_NAME", full_name, 1); + } else { + unsetenv("SAMBA_CPS_FULL_NAME"); + } + if (check_ret != 0) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + req = samba_runcmd_send(event_ctx, event_ctx, + tevent_timeval_current_ofs(10, 0), + 100, 100, cmd, NULL); + unsetenv("SAMBA_CPS_ACCOUNT_NAME"); + unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME"); + unsetenv("SAMBA_CPS_FULL_NAME"); + if (req == NULL) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + cps_stdin = samba_runcmd_export_stdin(req); + + nwritten = write_data( + cps_stdin, utf8_blob->data, utf8_blob->length); + if (nwritten == -1) { + close(cps_stdin); + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + close(cps_stdin); + + if (!tevent_req_poll(req, event_ctx)) { + TALLOC_FREE(password_script); + TALLOC_FREE(event_ctx); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + + check_ret = samba_runcmd_recv(req, &error); + TALLOC_FREE(event_ctx); + + if (error == ETIMEDOUT) { + DEBUG(0, ("check_password_complexity: check password script took too long!\n")); + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR; + } + DEBUG(5,("check_password_complexity: check password script (%s) " + "returned [%d]\n", password_script, check_ret)); + + if (check_ret != 0) { + DEBUG(1,("check_password_complexity: " + "check password script said new password is not good " + "enough!\n")); + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + + TALLOC_FREE(password_script); + return SAMR_VALIDATION_STATUS_SUCCESS; + } + + TALLOC_FREE(password_script); + + /* + * Here are the standard AD password quality rules, which we + * run after the script. + */ + + if (!check_password_quality(utf8_pw)) { + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + + return SAMR_VALIDATION_STATUS_SUCCESS; +} + +/* + * Callback for "samdb_set_password" password change + */ +int samdb_set_password_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret; + + if (!ares) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + + if (ares->error != LDB_SUCCESS) { + ret = ares->error; + req->context = talloc_steal(req, + ldb_reply_get_control(ares, DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID)); + talloc_free(ares); + return ldb_request_done(req, ret); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + + req->context = talloc_steal(req, + ldb_reply_get_control(ares, DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID)); + talloc_free(ares); + return ldb_request_done(req, LDB_SUCCESS); +} + +/* + * Sets the user password using plaintext UTF16 (attribute "new_password") or + * LM (attribute "lmNewHash") or NT (attribute "ntNewHash") hash. Also pass + * the old LM and/or NT hash (attributes "lmOldHash"/"ntOldHash") if it is a + * user change or not. The "rejectReason" gives some more information if the + * change failed. + * + * Results: NT_STATUS_OK, NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL, + * NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION, + * NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCOUNT_LOCKED_OUT, NT_STATUS_NO_MEMORY + */ +static NTSTATUS samdb_set_password_internal(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, + struct ldb_dn *user_dn, struct ldb_dn *domain_dn, + const DATA_BLOB *new_password, + const struct samr_Password *ntNewHash, + enum dsdb_password_checked old_password_checked, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **_dominfo, + bool permit_interdomain_trust) +{ + struct ldb_message *msg; + struct ldb_message_element *el; + struct ldb_request *req; + struct dsdb_control_password_change_status *pwd_stat = NULL; + int ret; + bool hash_values = false; + NTSTATUS status = NT_STATUS_OK; + +#define CHECK_RET(x) \ + if (x != LDB_SUCCESS) { \ + talloc_free(msg); \ + return NT_STATUS_NO_MEMORY; \ + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + msg->dn = user_dn; + if ((new_password != NULL) + && ((ntNewHash == NULL))) { + /* we have the password as plaintext UTF16 */ + CHECK_RET(ldb_msg_add_value(msg, "clearTextPassword", + new_password, NULL)); + el = ldb_msg_find_element(msg, "clearTextPassword"); + el->flags = LDB_FLAG_MOD_REPLACE; + } else if ((new_password == NULL) + && ((ntNewHash != NULL))) { + /* we have a password as NT hash */ + if (ntNewHash != NULL) { + CHECK_RET(samdb_msg_add_hash(ldb, mem_ctx, msg, + "unicodePwd", ntNewHash)); + el = ldb_msg_find_element(msg, "unicodePwd"); + el->flags = LDB_FLAG_MOD_REPLACE; + } + hash_values = true; + } else { + /* the password wasn't specified correctly */ + talloc_free(msg); + return NT_STATUS_INVALID_PARAMETER; + } + + /* build modify request */ + ret = ldb_build_mod_req(&req, ldb, mem_ctx, msg, NULL, NULL, + samdb_set_password_callback, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + /* A password change operation */ + if (old_password_checked == DSDB_PASSWORD_CHECKED_AND_CORRECT) { + struct dsdb_control_password_change *change; + + change = talloc(req, struct dsdb_control_password_change); + if (change == NULL) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + change->old_password_checked = old_password_checked; + + ret = ldb_request_add_control(req, + DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID, + true, change); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + } + if (hash_values) { + ret = ldb_request_add_control(req, + DSDB_CONTROL_PASSWORD_HASH_VALUES_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + } + if (permit_interdomain_trust) { + ret = ldb_request_add_control(req, + DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID, + false, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + } + ret = ldb_request_add_control(req, + DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_request(ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + if (req->context != NULL) { + struct ldb_control *control = talloc_get_type_abort(req->context, + struct ldb_control); + pwd_stat = talloc_get_type_abort(control->data, + struct dsdb_control_password_change_status); + talloc_steal(mem_ctx, pwd_stat); + } + + talloc_free(req); + talloc_free(msg); + + /* Sets the domain info (if requested) */ + if (_dominfo != NULL) { + struct samr_DomInfo1 *dominfo; + + dominfo = talloc_zero(mem_ctx, struct samr_DomInfo1); + if (dominfo == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (pwd_stat != NULL) { + dominfo->min_password_length = pwd_stat->domain_data.minPwdLength; + dominfo->password_properties = pwd_stat->domain_data.pwdProperties; + dominfo->password_history_length = pwd_stat->domain_data.pwdHistoryLength; + dominfo->max_password_age = pwd_stat->domain_data.maxPwdAge; + dominfo->min_password_age = pwd_stat->domain_data.minPwdAge; + } + + *_dominfo = dominfo; + } + + if (reject_reason != NULL) { + if (pwd_stat != NULL) { + *reject_reason = pwd_stat->reject_reason; + } else { + *reject_reason = SAM_PWD_CHANGE_NO_ERROR; + } + } + + if (pwd_stat != NULL) { + talloc_free(pwd_stat); + } + + if (ret == LDB_ERR_CONSTRAINT_VIOLATION) { + const char *errmsg = ldb_errstring(ldb); + char *endptr = NULL; + WERROR werr = WERR_GEN_FAILURE; + status = NT_STATUS_UNSUCCESSFUL; + if (errmsg != NULL) { + werr = W_ERROR(strtol(errmsg, &endptr, 16)); + DBG_WARNING("%s\n", errmsg); + } + if (endptr != errmsg) { + if (W_ERROR_EQUAL(werr, WERR_INVALID_PASSWORD)) { + status = NT_STATUS_WRONG_PASSWORD; + } + if (W_ERROR_EQUAL(werr, WERR_PASSWORD_RESTRICTION)) { + status = NT_STATUS_PASSWORD_RESTRICTION; + } + if (W_ERROR_EQUAL(werr, WERR_ACCOUNT_LOCKED_OUT)) { + status = NT_STATUS_ACCOUNT_LOCKED_OUT; + } + } + } else if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* don't let the caller know if an account doesn't exist */ + status = NT_STATUS_WRONG_PASSWORD; + } else if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + status = NT_STATUS_ACCESS_DENIED; + } else if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to set password on %s: %s\n", + ldb_dn_get_linearized(user_dn), + ldb_errstring(ldb))); + status = NT_STATUS_UNSUCCESSFUL; + } + + return status; +} + +NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, + struct ldb_dn *user_dn, struct ldb_dn *domain_dn, + const DATA_BLOB *new_password, + const struct samr_Password *ntNewHash, + enum dsdb_password_checked old_password_checked, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **_dominfo) +{ + return samdb_set_password_internal(ldb, mem_ctx, + user_dn, domain_dn, + new_password, + ntNewHash, + old_password_checked, + reject_reason, _dominfo, + false); /* reject trusts */ +} + +/* + * Sets the user password using plaintext UTF16 (attribute "new_password") or + * LM (attribute "lmNewHash") or NT (attribute "ntNewHash") hash. Also pass + * the old LM and/or NT hash (attributes "lmOldHash"/"ntOldHash") if it is a + * user change or not. The "rejectReason" gives some more information if the + * change failed. + * + * This wrapper function for "samdb_set_password" takes a SID as input rather + * than a user DN. + * + * This call encapsulates a new LDB transaction for changing the password; + * therefore the user hasn't to start a new one. + * + * Results: NT_STATUS_OK, NT_STATUS_INTERNAL_DB_CORRUPTION, + * NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL, + * NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION, + * NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCOUNT_LOCKED_OUT, NT_STATUS_NO_MEMORY + * NT_STATUS_TRANSACTION_ABORTED, NT_STATUS_NO_SUCH_USER + */ +NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + const uint32_t *new_version, /* optional for trusts */ + const DATA_BLOB *new_password, + const struct samr_Password *ntNewHash, + enum dsdb_password_checked old_password_checked, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **_dominfo) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS nt_status; + static const char * const attrs[] = { + "userAccountControl", + "sAMAccountName", + NULL + }; + struct ldb_message *user_msg = NULL; + int ret; + uint32_t uac = 0; + + ret = ldb_transaction_start(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(ldb))); + TALLOC_FREE(frame); + return NT_STATUS_TRANSACTION_ABORTED; + } + + ret = dsdb_search_one(ldb, frame, &user_msg, ldb_get_default_basedn(ldb), + LDB_SCOPE_SUBTREE, attrs, 0, + "(&(objectSid=%s)(objectClass=user))", + ldap_encode_ndr_dom_sid(frame, user_sid)); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + DEBUG(3, ("samdb_set_password_sid: SID[%s] not found in samdb %s - %s, " + "returning NO_SUCH_USER\n", + dom_sid_string(frame, user_sid), + ldb_strerror(ret), ldb_errstring(ldb))); + TALLOC_FREE(frame); + return NT_STATUS_NO_SUCH_USER; + } + + uac = ldb_msg_find_attr_as_uint(user_msg, "userAccountControl", 0); + if (!(uac & UF_ACCOUNT_TYPE_MASK)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: invalid " + "userAccountControl[0x%08X] for SID[%s] DN[%s], " + "returning NO_SUCH_USER\n", + (unsigned)uac, dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_NO_SUCH_USER; + } + + if (uac & UF_INTERDOMAIN_TRUST_ACCOUNT) { + static const char * const tdo_attrs[] = { + "trustAuthIncoming", + "trustDirection", + NULL + }; + struct ldb_message *tdo_msg = NULL; + const char *account_name = NULL; + uint32_t trust_direction; + uint32_t i; + const struct ldb_val *old_val = NULL; + struct trustAuthInOutBlob old_blob = { + .count = 0, + }; + uint32_t old_version = 0; + struct AuthenticationInformation *old_version_a = NULL; + uint32_t _new_version = 0; + struct trustAuthInOutBlob new_blob = { + .count = 0, + }; + struct ldb_val new_val = { + .length = 0, + }; + struct timeval tv = timeval_current(); + NTTIME now = timeval_to_nttime(&tv); + enum ndr_err_code ndr_err; + + if (new_password == NULL && ntNewHash == NULL) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "no new password provided " + "sAMAccountName for SID[%s] DN[%s], " + "returning INVALID_PARAMETER\n", + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (new_password != NULL && ntNewHash != NULL) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "two new passwords provided " + "sAMAccountName for SID[%s] DN[%s], " + "returning INVALID_PARAMETER\n", + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (new_password != NULL && (new_password->length % 2)) { + ldb_transaction_cancel(ldb); + DEBUG(2, ("samdb_set_password_sid: " + "invalid utf16 length (%zu) " + "sAMAccountName for SID[%s] DN[%s], " + "returning WRONG_PASSWORD\n", + new_password->length, + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_WRONG_PASSWORD; + } + + if (new_password != NULL && new_password->length >= 500) { + ldb_transaction_cancel(ldb); + DEBUG(2, ("samdb_set_password_sid: " + "utf16 password too long (%zu) " + "sAMAccountName for SID[%s] DN[%s], " + "returning WRONG_PASSWORD\n", + new_password->length, + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_WRONG_PASSWORD; + } + + account_name = ldb_msg_find_attr_as_string(user_msg, + "sAMAccountName", NULL); + if (account_name == NULL) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: missing " + "sAMAccountName for SID[%s] DN[%s], " + "returning NO_SUCH_USER\n", + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_NO_SUCH_USER; + } + + nt_status = dsdb_trust_search_tdo_by_type(ldb, + SEC_CHAN_DOMAIN, + account_name, + tdo_attrs, + frame, &tdo_msg); + if (!NT_STATUS_IS_OK(nt_status)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: dsdb_trust_search_tdo " + "failed(%s) for sAMAccountName[%s] SID[%s] DN[%s], " + "returning INTERNAL_DB_CORRUPTION\n", + nt_errstr(nt_status), account_name, + dom_sid_string(frame, user_sid), + ldb_dn_get_linearized(user_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + trust_direction = ldb_msg_find_attr_as_int(tdo_msg, + "trustDirection", 0); + if (!(trust_direction & LSA_TRUST_DIRECTION_INBOUND)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: direction[0x%08X] is " + "not inbound for sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "returning INTERNAL_DB_CORRUPTION\n", + (unsigned)trust_direction, + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + old_val = ldb_msg_find_ldb_val(tdo_msg, "trustAuthIncoming"); + if (old_val != NULL) { + ndr_err = ndr_pull_struct_blob(old_val, frame, &old_blob, + (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "failed(%s) to parse " + "trustAuthOutgoing sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "returning INTERNAL_DB_CORRUPTION\n", + ndr_map_error2string(ndr_err), + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn))); + + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + for (i = old_blob.current.count; i > 0; i--) { + struct AuthenticationInformation *a = + &old_blob.current.array[i - 1]; + + switch (a->AuthType) { + case TRUST_AUTH_TYPE_NONE: + if (i == old_blob.current.count) { + /* + * remove TRUST_AUTH_TYPE_NONE at the + * end + */ + old_blob.current.count--; + } + break; + + case TRUST_AUTH_TYPE_VERSION: + old_version_a = a; + old_version = a->AuthInfo.version.version; + break; + + case TRUST_AUTH_TYPE_CLEAR: + break; + + case TRUST_AUTH_TYPE_NT4OWF: + break; + } + } + + if (new_version == NULL) { + _new_version = 0; + new_version = &_new_version; + } + + if (old_version_a != NULL && *new_version != (old_version + 1)) { + old_version_a->LastUpdateTime = now; + old_version_a->AuthType = TRUST_AUTH_TYPE_NONE; + } + + new_blob.count = MAX(old_blob.current.count, 2); + new_blob.current.array = talloc_zero_array(frame, + struct AuthenticationInformation, + new_blob.count); + if (new_blob.current.array == NULL) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + new_blob.previous.array = talloc_zero_array(frame, + struct AuthenticationInformation, + new_blob.count); + if (new_blob.current.array == NULL) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < old_blob.current.count; i++) { + struct AuthenticationInformation *o = + &old_blob.current.array[i]; + struct AuthenticationInformation *p = + &new_blob.previous.array[i]; + + *p = *o; + new_blob.previous.count++; + } + for (; i < new_blob.count; i++) { + struct AuthenticationInformation *pi = + &new_blob.previous.array[i]; + + if (i == 0) { + /* + * new_blob.previous is still empty so + * we'll do new_blob.previous = new_blob.current + * below. + */ + break; + } + + pi->LastUpdateTime = now; + pi->AuthType = TRUST_AUTH_TYPE_NONE; + new_blob.previous.count++; + } + + for (i = 0; i < new_blob.count; i++) { + struct AuthenticationInformation *ci = + &new_blob.current.array[i]; + + ci->LastUpdateTime = now; + switch (i) { + case 0: + if (ntNewHash != NULL) { + ci->AuthType = TRUST_AUTH_TYPE_NT4OWF; + ci->AuthInfo.nt4owf.password = *ntNewHash; + break; + } + + ci->AuthType = TRUST_AUTH_TYPE_CLEAR; + ci->AuthInfo.clear.size = new_password->length; + ci->AuthInfo.clear.password = new_password->data; + break; + case 1: + ci->AuthType = TRUST_AUTH_TYPE_VERSION; + ci->AuthInfo.version.version = *new_version; + break; + default: + ci->AuthType = TRUST_AUTH_TYPE_NONE; + break; + } + + new_blob.current.count++; + } + + if (new_blob.previous.count == 0) { + TALLOC_FREE(new_blob.previous.array); + new_blob.previous = new_blob.current; + } + + ndr_err = ndr_push_struct_blob(&new_val, frame, &new_blob, + (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "failed(%s) to generate " + "trustAuthOutgoing sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "returning UNSUCCESSFUL\n", + ndr_map_error2string(ndr_err), + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn))); + TALLOC_FREE(frame); + return NT_STATUS_UNSUCCESSFUL; + } + + tdo_msg->num_elements = 0; + TALLOC_FREE(tdo_msg->elements); + + ret = ldb_msg_append_value(tdo_msg, "trustAuthIncoming", + &new_val, LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_modify(ldb, tdo_msg); + if (ret != LDB_SUCCESS) { + nt_status = dsdb_ldb_err_to_ntstatus(ret); + ldb_transaction_cancel(ldb); + DEBUG(1, ("samdb_set_password_sid: " + "failed to replace " + "trustAuthOutgoing sAMAccountName[%s] " + "DN[%s] TDO[%s], " + "%s - %s\n", + account_name, + ldb_dn_get_linearized(user_msg->dn), + ldb_dn_get_linearized(tdo_msg->dn), + nt_errstr(nt_status), ldb_errstring(ldb))); + TALLOC_FREE(frame); + return nt_status; + } + } + + nt_status = samdb_set_password_internal(ldb, mem_ctx, + user_msg->dn, NULL, + new_password, + ntNewHash, + old_password_checked, + reject_reason, _dominfo, + true); /* permit trusts */ + if (!NT_STATUS_IS_OK(nt_status)) { + ldb_transaction_cancel(ldb); + TALLOC_FREE(frame); + return nt_status; + } + + ret = ldb_transaction_commit(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to commit transaction to change password on %s: %s\n", + ldb_dn_get_linearized(user_msg->dn), + ldb_errstring(ldb))); + TALLOC_FREE(frame); + return NT_STATUS_TRANSACTION_ABORTED; + } + + TALLOC_FREE(frame); + 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 = NULL; + 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(sidstr); + if (msg == NULL) { + talloc_free(sidstr); + return NT_STATUS_NO_MEMORY; + } + + ret = dsdb_wellknown_dn(sam_ctx, sidstr, + ldb_get_default_basedn(sam_ctx), + DS_GUID_FOREIGNSECURITYPRINCIPALS_CONTAINER, + &basedn); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to find DN for " + "ForeignSecurityPrincipal container - %s\n", ldb_errstring(sam_ctx))); + talloc_free(sidstr); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* add core elements to the ldb_message for the alias */ + msg->dn = basedn; + if ( ! ldb_dn_add_child_fmt(msg->dn, "CN=%s", sidstr)) { + talloc_free(sidstr); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(msg, "objectClass", + "foreignSecurityPrincipal"); + if (ret != LDB_SUCCESS) { + talloc_free(sidstr); + return NT_STATUS_NO_MEMORY; + } + + /* create the alias */ + ret = ldb_add(sam_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to create foreignSecurityPrincipal " + "record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx))); + talloc_free(sidstr); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + *ret_dn = talloc_steal(mem_ctx, msg->dn); + talloc_free(sidstr); + + 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) +{ + unsigned int i; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + const char *binary_encoded; + const char * const *split_realm; + struct ldb_dn *dn; + + if (!tmp_ctx) { + return NULL; + } + + split_realm = (const char * const *)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 (binary_encoded == NULL) { + DEBUG(2, ("Failed to add dc= element to DN %s\n", + ldb_dn_get_linearized(dn))); + talloc_free(tmp_ctx); + return NULL; + } + 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))); + talloc_free(tmp_ctx); + return NULL; + } + talloc_free(tmp_ctx); + return dn; +} + + +/* + Find the DNS equivalent of a DN, in dotted DNS form +*/ +char *samdb_dn_to_dns_domain(TALLOC_CTX *mem_ctx, struct ldb_dn *dn) +{ + int i, num_components = ldb_dn_get_comp_num(dn); + char *dns_name = talloc_strdup(mem_ctx, ""); + if (dns_name == NULL) { + return NULL; + } + + for (i=0; i<num_components; i++) { + const struct ldb_val *v = ldb_dn_get_component_val(dn, i); + char *s; + if (v == NULL) { + talloc_free(dns_name); + return NULL; + } + s = talloc_asprintf_append_buffer(dns_name, "%*.*s.", + (int)v->length, (int)v->length, (char *)v->data); + if (s == NULL) { + talloc_free(dns_name); + return NULL; + } + dns_name = s; + } + + /* remove the last '.' */ + if (dns_name[0] != 0) { + dns_name[strlen(dns_name)-1] = 0; + } + + return dns_name; +} + +/* + Find the DNS _msdcs name for a given NTDS GUID. The resulting DNS + name is based on the forest DNS name +*/ +char *samdb_ntds_msdcs_dns_name(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + const struct GUID *ntds_guid) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + const char *guid_str; + struct ldb_dn *forest_dn; + const char *dnsforest; + char *ret; + + guid_str = GUID_string(tmp_ctx, ntds_guid); + if (guid_str == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + forest_dn = ldb_get_root_basedn(samdb); + if (forest_dn == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + dnsforest = samdb_dn_to_dns_domain(tmp_ctx, forest_dn); + if (dnsforest == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + ret = talloc_asprintf(mem_ctx, "%s._msdcs.%s", guid_str, dnsforest); + talloc_free(tmp_ctx); + return ret; +} + + +/* + 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); + int ret_domain; + + if (escaped_domain == NULL) { + return NULL; + } + + /* find the domain's DN */ + 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 != LDB_SUCCESS) { + 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 != LDB_SUCCESS) { + 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); + +} + + +/* + use a GUID to find a DN + */ +int dsdb_find_dn_by_guid(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct GUID *guid, + uint32_t dsdb_flags, + struct ldb_dn **dn) +{ + int ret; + struct ldb_result *res; + const char *attrs[] = { NULL }; + struct GUID_txt_buf buf; + char *guid_str = GUID_buf_string(guid, &buf); + + ret = dsdb_search(ldb, mem_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs, + DSDB_SEARCH_SEARCH_ALL_PARTITIONS | + DSDB_SEARCH_SHOW_EXTENDED_DN | + DSDB_SEARCH_ONE_ONLY | dsdb_flags, + "objectGUID=%s", guid_str); + if (ret != LDB_SUCCESS) { + return ret; + } + + *dn = talloc_steal(mem_ctx, res->msgs[0]->dn); + talloc_free(res); + + return LDB_SUCCESS; +} + +/* + use a DN to find a GUID with a given attribute name + */ +int dsdb_find_guid_attr_by_dn(struct ldb_context *ldb, + struct ldb_dn *dn, const char *attribute, + struct GUID *guid) +{ + int ret; + struct ldb_result *res = NULL; + const char *attrs[2]; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + + attrs[0] = attribute; + attrs[1] = NULL; + + ret = dsdb_search_dn(ldb, tmp_ctx, &res, dn, attrs, + DSDB_SEARCH_SHOW_DELETED | + DSDB_SEARCH_SHOW_RECYCLED); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + /* satisfy clang */ + if (res == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OTHER; + } + if (res->count < 1) { + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); + } + *guid = samdb_result_guid(res->msgs[0], attribute); + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +/* + use a DN to find a GUID + */ +int dsdb_find_guid_by_dn(struct ldb_context *ldb, + struct ldb_dn *dn, struct GUID *guid) +{ + return dsdb_find_guid_attr_by_dn(ldb, dn, "objectGUID", guid); +} + + + +/* + adds the given GUID to the given ldb_message. This value is added + for the given attr_name (may be either "objectGUID" or "parentGUID"). + This function is used in processing 'add' requests. + */ +int dsdb_msg_add_guid(struct ldb_message *msg, + struct GUID *guid, + const char *attr_name) +{ + int ret; + struct ldb_val v; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_init("dsdb_msg_add_guid"); + + status = GUID_to_ndr_blob(guid, tmp_ctx, &v); + if (!NT_STATUS_IS_OK(status)) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + + ret = ldb_msg_add_steal_value(msg, attr_name, &v); + if (ret != LDB_SUCCESS) { + DEBUG(4,(__location__ ": Failed to add %s to the message\n", + attr_name)); + goto done; + } + + ret = LDB_SUCCESS; + +done: + talloc_free(tmp_ctx); + return ret; + +} + + +/* + use a DN to find a SID + */ +int dsdb_find_sid_by_dn(struct ldb_context *ldb, + struct ldb_dn *dn, struct dom_sid *sid) +{ + int ret; + struct ldb_result *res = NULL; + const char *attrs[] = { "objectSid", NULL }; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + struct dom_sid *s; + + ZERO_STRUCTP(sid); + + ret = dsdb_search_dn(ldb, tmp_ctx, &res, dn, attrs, + DSDB_SEARCH_SHOW_DELETED | + DSDB_SEARCH_SHOW_RECYCLED); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + if (res == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_OTHER; + } + if (res->count < 1) { + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); + } + s = samdb_result_dom_sid(tmp_ctx, res->msgs[0], "objectSid"); + if (s == NULL) { + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); + } + *sid = *s; + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +/* + use a SID to find a DN + */ +int dsdb_find_dn_by_sid(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct dom_sid *sid, struct ldb_dn **dn) +{ + int ret; + struct ldb_result *res; + const char *attrs[] = { NULL }; + char *sid_str = ldap_encode_ndr_dom_sid(mem_ctx, sid); + + if (!sid_str) { + return ldb_operr(ldb); + } + + ret = dsdb_search(ldb, mem_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs, + DSDB_SEARCH_SEARCH_ALL_PARTITIONS | + DSDB_SEARCH_SHOW_EXTENDED_DN | + DSDB_SEARCH_ONE_ONLY, + "objectSid=%s", sid_str); + talloc_free(sid_str); + if (ret != LDB_SUCCESS) { + return ret; + } + + *dn = talloc_steal(mem_ctx, res->msgs[0]->dn); + talloc_free(res); + + return LDB_SUCCESS; +} + +/* + load a repsFromTo blob list for a given partition GUID + attr must be "repsFrom" or "repsTo" + */ +WERROR dsdb_loadreps(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, struct ldb_dn *dn, + const char *attr, struct repsFromToBlob **r, uint32_t *count) +{ + const char *attrs[] = { attr, NULL }; + struct ldb_result *res = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + unsigned int i; + struct ldb_message_element *el; + int ret; + + *r = NULL; + *count = 0; + + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = dsdb_search_dn(sam_ctx, tmp_ctx, &res, dn, attrs, 0); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* partition hasn't been replicated yet */ + talloc_free(tmp_ctx); + return WERR_OK; + } + if (ret != LDB_SUCCESS) { + DEBUG(0,("dsdb_loadreps: failed to read partition object: %s\n", ldb_errstring(sam_ctx))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* satisfy clang */ + if (res == NULL) { + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + el = ldb_msg_find_element(res->msgs[0], attr); + if (el == NULL) { + /* it's OK to be empty */ + talloc_free(tmp_ctx); + return WERR_OK; + } + + *count = el->num_values; + *r = talloc_array(mem_ctx, struct repsFromToBlob, *count); + if (*r == NULL) { + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + for (i=0; i<(*count); i++) { + enum ndr_err_code ndr_err; + ndr_err = ndr_pull_struct_blob(&el->values[i], + mem_ctx, + &(*r)[i], + (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + } + + talloc_free(tmp_ctx); + + return WERR_OK; +} + +/* + save the repsFromTo blob list for a given partition GUID + attr must be "repsFrom" or "repsTo" + */ +WERROR dsdb_savereps(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, struct ldb_dn *dn, + const char *attr, struct repsFromToBlob *r, uint32_t count) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + struct ldb_message *msg; + struct ldb_message_element *el; + unsigned int i; + + if (tmp_ctx == NULL) { + goto failed; + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + goto failed; + } + msg->dn = dn; + if (ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_REPLACE, &el) != LDB_SUCCESS) { + goto failed; + } + + el->values = talloc_array(msg, struct ldb_val, count); + if (!el->values) { + goto failed; + } + + for (i=0; i<count; i++) { + struct ldb_val v; + enum ndr_err_code ndr_err; + + ndr_err = ndr_push_struct_blob(&v, tmp_ctx, + &r[i], + (ndr_push_flags_fn_t)ndr_push_repsFromToBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + goto failed; + } + + el->num_values++; + el->values[i] = v; + } + + if (dsdb_modify(sam_ctx, msg, 0) != LDB_SUCCESS) { + DEBUG(0,("Failed to store %s - %s\n", attr, ldb_errstring(sam_ctx))); + goto failed; + } + + talloc_free(tmp_ctx); + + return WERR_OK; + +failed: + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; +} + + +/* + load the uSNHighest and the uSNUrgent attributes from the @REPLCHANGED + object for a partition + */ +int dsdb_load_partition_usn(struct ldb_context *ldb, struct ldb_dn *dn, + uint64_t *uSN, uint64_t *urgent_uSN) +{ + struct ldb_request *req; + int ret; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + struct dsdb_control_current_partition *p_ctrl; + struct ldb_result *res; + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + res = talloc_zero(tmp_ctx, struct ldb_result); + if (!res) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ret = ldb_build_search_req(&req, ldb, tmp_ctx, + ldb_dn_new(tmp_ctx, ldb, "@REPLCHANGED"), + LDB_SCOPE_BASE, + NULL, NULL, + NULL, + res, ldb_search_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + p_ctrl = talloc(req, struct dsdb_control_current_partition); + if (p_ctrl == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + p_ctrl->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION; + p_ctrl->dn = dn; + + ret = ldb_request_add_control(req, + DSDB_CONTROL_CURRENT_PARTITION_OID, + false, p_ctrl); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + /* Run the new request */ + ret = ldb_request(ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_INVALID_DN_SYNTAX) { + /* it hasn't been created yet, which means + an implicit value of zero */ + *uSN = 0; + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + if (res->count < 1) { + *uSN = 0; + if (urgent_uSN) { + *urgent_uSN = 0; + } + } else { + *uSN = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNHighest", 0); + if (urgent_uSN) { + *urgent_uSN = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNUrgent", 0); + } + } + + talloc_free(tmp_ctx); + + return LDB_SUCCESS; +} + +int 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); +} + +int drsuapi_DsReplicaCursor_compare(const struct drsuapi_DsReplicaCursor *c1, + const struct drsuapi_DsReplicaCursor *c2) +{ + return GUID_compare(&c1->source_dsa_invocation_id, &c2->source_dsa_invocation_id); +} + +/* + * Return the NTDS object for a GUID, confirming it is in the + * configuration partition and a nTDSDSA object + */ +int samdb_get_ntds_obj_by_guid(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + const struct GUID *objectGUID, + const char **attrs, + struct ldb_message **msg) +{ + int ret; + struct ldb_result *res; + struct GUID_txt_buf guid_buf; + char *guid_str = GUID_buf_string(objectGUID, &guid_buf); + struct ldb_dn *config_dn = NULL; + + config_dn = ldb_get_config_basedn(sam_ctx); + if (config_dn == NULL) { + return ldb_operr(sam_ctx); + } + + ret = dsdb_search(sam_ctx, + mem_ctx, + &res, + config_dn, + LDB_SCOPE_SUBTREE, + attrs, + DSDB_SEARCH_ONE_ONLY, + "(&(objectGUID=%s)(objectClass=nTDSDSA))", + guid_str); + if (ret != LDB_SUCCESS) { + return ret; + } + if (msg) { + *msg = talloc_steal(mem_ctx, res->msgs[0]); + } + TALLOC_FREE(res); + return ret; +} + + +/* + see if a computer identified by its objectGUID is a RODC +*/ +int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *objectGUID, bool *is_rodc) +{ + /* 1) find the DN for this servers NTDSDSA object + 2) search for the msDS-isRODC attribute + 3) if not present then not a RODC + 4) if present and TRUE then is a RODC + */ + const char *attrs[] = { "msDS-isRODC", NULL }; + int ret; + struct ldb_message *msg; + TALLOC_CTX *tmp_ctx = talloc_new(sam_ctx); + + if (tmp_ctx == NULL) { + return ldb_oom(sam_ctx); + } + + ret = samdb_get_ntds_obj_by_guid(tmp_ctx, + sam_ctx, + objectGUID, + attrs, &msg); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + *is_rodc = false; + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to find our own NTDS Settings object by objectGUID=%s!\n", + GUID_string(tmp_ctx, objectGUID))); + *is_rodc = false; + talloc_free(tmp_ctx); + return ret; + } + + ret = ldb_msg_find_attr_as_bool(msg, "msDS-isRODC", 0); + *is_rodc = (ret == 1); + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + +/* + see if we are a RODC +*/ +int samdb_rodc(struct ldb_context *sam_ctx, bool *am_rodc) +{ + const struct GUID *objectGUID; + int ret; + bool *cached; + + /* see if we have a cached copy */ + cached = (bool *)ldb_get_opaque(sam_ctx, "cache.am_rodc"); + if (cached) { + *am_rodc = *cached; + return LDB_SUCCESS; + } + + objectGUID = samdb_ntds_objectGUID(sam_ctx); + if (!objectGUID) { + return ldb_operr(sam_ctx); + } + + ret = samdb_is_rodc(sam_ctx, objectGUID, am_rodc); + if (ret != LDB_SUCCESS) { + return ret; + } + + cached = talloc(sam_ctx, bool); + if (cached == NULL) { + return ldb_oom(sam_ctx); + } + *cached = *am_rodc; + + ret = ldb_set_opaque(sam_ctx, "cache.am_rodc", cached); + if (ret != LDB_SUCCESS) { + talloc_free(cached); + return ldb_operr(sam_ctx); + } + + return LDB_SUCCESS; +} + +int samdb_dns_host_name(struct ldb_context *sam_ctx, const char **host_name) +{ + const char *_host_name = NULL; + const char *attrs[] = { "dnsHostName", NULL }; + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct ldb_result *res = NULL; + + _host_name = (const char *)ldb_get_opaque(sam_ctx, "cache.dns_host_name"); + if (_host_name != NULL) { + *host_name = _host_name; + return LDB_SUCCESS; + } + + tmp_ctx = talloc_new(sam_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(sam_ctx); + } + + ret = dsdb_search_dn(sam_ctx, tmp_ctx, &res, NULL, attrs, 0); + + if (res == NULL || res->count != 1 || ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to get rootDSE for dnsHostName: %s\n", + ldb_errstring(sam_ctx))); + TALLOC_FREE(tmp_ctx); + return ret; + } + + _host_name = ldb_msg_find_attr_as_string(res->msgs[0], + "dnsHostName", + NULL); + if (_host_name == NULL) { + DEBUG(0, ("Failed to get dnsHostName from rootDSE\n")); + TALLOC_FREE(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + ret = ldb_set_opaque(sam_ctx, "cache.dns_host_name", + discard_const_p(char *, _host_name)); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(tmp_ctx); + return ldb_operr(sam_ctx); + } + + *host_name = talloc_steal(sam_ctx, _host_name); + + TALLOC_FREE(tmp_ctx); + return LDB_SUCCESS; +} + +bool samdb_set_am_rodc(struct ldb_context *ldb, bool am_rodc) +{ + TALLOC_CTX *tmp_ctx; + bool *cached; + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + goto failed; + } + + cached = talloc(tmp_ctx, bool); + if (!cached) { + goto failed; + } + + *cached = am_rodc; + if (ldb_set_opaque(ldb, "cache.am_rodc", cached) != LDB_SUCCESS) { + goto failed; + } + + talloc_steal(ldb, cached); + talloc_free(tmp_ctx); + return true; + +failed: + DEBUG(1,("Failed to set our own cached am_rodc in the ldb!\n")); + talloc_free(tmp_ctx); + return false; +} + + +/* + * return NTDSSiteSettings options. See MS-ADTS 7.1.1.2.2.1.1 + * flags are DS_NTDSSETTINGS_OPT_* + */ +int samdb_ntds_site_settings_options(struct ldb_context *ldb_ctx, + uint32_t *options) +{ + int rc; + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_dn *site_dn; + const char *attrs[] = { "options", NULL }; + + tmp_ctx = talloc_new(ldb_ctx); + if (tmp_ctx == NULL) + goto failed; + + /* Retrieve the site dn for the ldb that we + * have open. This is our local site. + */ + site_dn = samdb_server_site_dn(ldb_ctx, tmp_ctx); + if (site_dn == NULL) + goto failed; + + /* Perform a one level (child) search from the local + * site distinguished name. We're looking for the + * "options" attribute within the nTDSSiteSettings + * object + */ + rc = ldb_search(ldb_ctx, tmp_ctx, &res, site_dn, + LDB_SCOPE_ONELEVEL, attrs, + "objectClass=nTDSSiteSettings"); + + if (rc != LDB_SUCCESS || res->count != 1) + goto failed; + + *options = ldb_msg_find_attr_as_uint(res->msgs[0], "options", 0); + + talloc_free(tmp_ctx); + + return LDB_SUCCESS; + +failed: + DEBUG(1,("Failed to find our NTDS Site Settings options in ldb!\n")); + talloc_free(tmp_ctx); + return ldb_error(ldb_ctx, LDB_ERR_NO_SUCH_OBJECT, __func__); +} + +/* + return NTDS options flags. See MS-ADTS 7.1.1.2.2.1.2.1.1 + + flags are DS_NTDS_OPTION_* +*/ +int samdb_ntds_options(struct ldb_context *ldb, uint32_t *options) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { "options", NULL }; + int ret; + struct ldb_result *res; + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + goto failed; + } + + ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb, tmp_ctx), LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + goto failed; + } + + if (res->count != 1) { + goto failed; + } + + *options = ldb_msg_find_attr_as_uint(res->msgs[0], "options", 0); + + talloc_free(tmp_ctx); + + return LDB_SUCCESS; + +failed: + DEBUG(1,("Failed to find our own NTDS Settings options in the ldb!\n")); + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); +} + +const char* samdb_ntds_object_category(TALLOC_CTX *tmp_ctx, struct ldb_context *ldb) +{ + const char *attrs[] = { "objectCategory", NULL }; + int ret; + struct ldb_result *res; + + ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb, tmp_ctx), LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + goto failed; + } + + if (res->count != 1) { + goto failed; + } + + return ldb_msg_find_attr_as_string(res->msgs[0], "objectCategory", NULL); + +failed: + DEBUG(1,("Failed to find our own NTDS Settings objectCategory in the ldb!\n")); + return NULL; +} + +/* + * Function which generates a "lDAPDisplayName" attribute from a "CN" one. + * Algorithm implemented according to MS-ADTS 3.1.1.2.3.4 + */ +const char *samdb_cn_to_lDAPDisplayName(TALLOC_CTX *mem_ctx, const char *cn) +{ + char **tokens, *ret; + size_t i; + + tokens = str_list_make(mem_ctx, cn, " -_"); + if (tokens == NULL || tokens[0] == NULL) { + return NULL; + } + + /* "tolower()" and "toupper()" should also work properly on 0x00 */ + tokens[0][0] = tolower(tokens[0][0]); + for (i = 1; tokens[i] != NULL; i++) + tokens[i][0] = toupper(tokens[i][0]); + + ret = talloc_strdup(mem_ctx, tokens[0]); + if (ret == NULL) { + talloc_free(tokens); + return NULL; + } + for (i = 1; tokens[i] != NULL; i++) { + ret = talloc_asprintf_append_buffer(ret, "%s", tokens[i]); + if (ret == NULL) { + talloc_free(tokens); + return NULL; + } + } + + talloc_free(tokens); + + return ret; +} + +/* + * This detects and returns the domain functional level (DS_DOMAIN_FUNCTION_*) + */ +int dsdb_functional_level(struct ldb_context *ldb) +{ + int *domainFunctionality = + talloc_get_type(ldb_get_opaque(ldb, "domainFunctionality"), int); + if (!domainFunctionality) { + /* this is expected during initial provision */ + DEBUG(4,(__location__ ": WARNING: domainFunctionality not setup\n")); + return DS_DOMAIN_FUNCTION_2000; + } + return *domainFunctionality; +} + +/* + * This detects and returns the forest functional level (DS_DOMAIN_FUNCTION_*) + */ +int dsdb_forest_functional_level(struct ldb_context *ldb) +{ + int *forestFunctionality = + talloc_get_type(ldb_get_opaque(ldb, "forestFunctionality"), int); + if (!forestFunctionality) { + DEBUG(0,(__location__ ": WARNING: forestFunctionality not setup\n")); + return DS_DOMAIN_FUNCTION_2000; + } + return *forestFunctionality; +} + +/* + * This detects and returns the DC functional level (DS_DOMAIN_FUNCTION_*) + */ +int dsdb_dc_functional_level(struct ldb_context *ldb) +{ + int *dcFunctionality = + talloc_get_type(ldb_get_opaque(ldb, "domainControllerFunctionality"), int); + if (!dcFunctionality) { + /* this is expected during initial provision */ + DEBUG(4,(__location__ ": WARNING: domainControllerFunctionality not setup\n")); + return DS_DOMAIN_FUNCTION_2008_R2; + } + return *dcFunctionality; +} + +const char *dsdb_dc_operatingSystemVersion(int dc_functional_level) +{ + const char *operatingSystemVersion = NULL; + + /* + * While we are there also update + * operatingSystem and operatingSystemVersion + * as at least operatingSystemVersion is really + * important for some clients/applications (like exchange). + */ + + if (dc_functional_level >= DS_DOMAIN_FUNCTION_2016) { + /* Pretend Windows 2016 */ + operatingSystemVersion = "10.0 (14393)"; + } else if (dc_functional_level >= DS_DOMAIN_FUNCTION_2012_R2) { + /* Pretend Windows 2012 R2 */ + operatingSystemVersion = "6.3 (9600)"; + } else if (dc_functional_level >= DS_DOMAIN_FUNCTION_2012) { + /* Pretend Windows 2012 */ + operatingSystemVersion = "6.2 (9200)"; + } else { + /* Pretend Windows 2008 R2 */ + operatingSystemVersion = "6.1 (7600)"; + } + + return operatingSystemVersion; +} + +int dsdb_check_and_update_fl(struct ldb_context *ldb_ctx, struct loadparm_context *lp_ctx) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int ret; + + int db_dc_functional_level; + int db_domain_functional_level; + int db_forest_functional_level; + int lp_dc_functional_level = lpcfg_ad_dc_functional_level(lp_ctx); + bool am_rodc; + struct ldb_message *msg = NULL; + struct ldb_dn *dc_ntds_settings_dn = NULL; + struct ldb_dn *dc_computer_dn = NULL; + const char *operatingSystem = NULL; + const char *operatingSystemVersion = NULL; + + db_dc_functional_level = dsdb_dc_functional_level(ldb_ctx); + db_domain_functional_level = dsdb_functional_level(ldb_ctx); + db_forest_functional_level = dsdb_forest_functional_level(ldb_ctx); + + if (lp_dc_functional_level < db_domain_functional_level) { + DBG_ERR("Refusing to start as smb.conf 'ad dc functional level' maps to %d, " + "which is less than the domain functional level of %d\n", + lp_dc_functional_level, db_domain_functional_level); + TALLOC_FREE(frame); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (lp_dc_functional_level < db_forest_functional_level) { + DBG_ERR("Refusing to start as smb.conf 'ad dc functional level' maps to %d, " + "which is less than the forest functional level of %d\n", + lp_dc_functional_level, db_forest_functional_level); + TALLOC_FREE(frame); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* Check if we need to update the DB */ + if (db_dc_functional_level == lp_dc_functional_level) { + /* + * Note that this early return means + * we're not updating operatingSystem and + * operatingSystemVersion. + * + * But at least for now that's + * exactly what we want. + */ + TALLOC_FREE(frame); + return LDB_SUCCESS; + } + + /* Confirm we are not an RODC before we try a modify */ + ret = samdb_rodc(ldb_ctx, &am_rodc); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to determine if this server is an RODC\n"); + TALLOC_FREE(frame); + return ret; + } + + if (am_rodc) { + DBG_WARNING("Unable to update DC's msDS-Behavior-Version " + "(from %d to %d) and operatingSystem[Version] " + "as we are an RODC\n", + db_dc_functional_level, lp_dc_functional_level); + TALLOC_FREE(frame); + return LDB_SUCCESS; + } + + dc_ntds_settings_dn = samdb_ntds_settings_dn(ldb_ctx, frame); + + if (dc_ntds_settings_dn == NULL) { + DBG_ERR("Failed to find own NTDS Settings DN\n"); + TALLOC_FREE(frame); + return LDB_ERR_NO_SUCH_OBJECT; + } + + /* Now update our msDS-Behavior-Version */ + + msg = ldb_msg_new(frame); + if (msg == NULL) { + DBG_ERR("Failed to allocate message to update msDS-Behavior-Version\n"); + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->dn = dc_ntds_settings_dn; + + ret = samdb_msg_add_int(ldb_ctx, frame, msg, "msDS-Behavior-Version", lp_dc_functional_level); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to set new msDS-Behavior-Version on message\n"); + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_replace(ldb_ctx, msg, 0); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to update DB with new msDS-Behavior-Version on %s: %s\n", + ldb_dn_get_linearized(dc_ntds_settings_dn), + ldb_errstring(ldb_ctx)); + TALLOC_FREE(frame); + return ret; + } + + /* + * We have to update the opaque because this particular ldb_context + * will not re-read the DB + */ + { + int *val = talloc(ldb_ctx, int); + if (!val) { + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + *val = lp_dc_functional_level; + ret = ldb_set_opaque(ldb_ctx, + "domainControllerFunctionality", val); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to re-set domainControllerFunctionality opaque\n"); + TALLOC_FREE(val); + TALLOC_FREE(frame); + return ret; + } + } + + /* + * While we are there also update + * operatingSystem and operatingSystemVersion + * as at least operatingSystemVersion is really + * important for some clients/applications (like exchange). + */ + + operatingSystem = talloc_asprintf(frame, "Samba-%s", + samba_version_string()); + if (operatingSystem == NULL) { + TALLOC_FREE(frame); + return ldb_oom(ldb_ctx); + } + + operatingSystemVersion = dsdb_dc_operatingSystemVersion(db_dc_functional_level); + + ret = samdb_server_reference_dn(ldb_ctx, frame, &dc_computer_dn); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to get the dc_computer_dn: %s\n", + ldb_errstring(ldb_ctx)); + TALLOC_FREE(frame); + return ret; + } + + msg = ldb_msg_new(frame); + if (msg == NULL) { + DBG_ERR("Failed to allocate message to update msDS-Behavior-Version\n"); + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->dn = dc_computer_dn; + + ret = samdb_msg_add_addval(ldb_ctx, frame, msg, + "operatingSystem", + operatingSystem); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to set new operatingSystem on message\n"); + TALLOC_FREE(frame); + return ldb_operr(ldb_ctx); + } + + ret = samdb_msg_add_addval(ldb_ctx, frame, msg, + "operatingSystemVersion", + operatingSystemVersion); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to set new operatingSystemVersion on message\n"); + TALLOC_FREE(frame); + return ldb_operr(ldb_ctx); + } + + ret = dsdb_replace(ldb_ctx, msg, 0); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to update DB with new operatingSystem[Version] on %s: %s\n", + ldb_dn_get_linearized(dc_computer_dn), + ldb_errstring(ldb_ctx)); + TALLOC_FREE(frame); + return ret; + } + + TALLOC_FREE(frame); + return LDB_SUCCESS; +} + + +/* + set a GUID in an extended DN structure + */ +int dsdb_set_extended_dn_guid(struct ldb_dn *dn, const struct GUID *guid, const char *component_name) +{ + struct ldb_val v; + NTSTATUS status; + int ret; + + status = GUID_to_ndr_blob(guid, dn, &v); + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + ret = ldb_dn_set_extended_component(dn, component_name, &v); + data_blob_free(&v); + return ret; +} + +/* + return a GUID from a extended DN structure + */ +NTSTATUS dsdb_get_extended_dn_guid(struct ldb_dn *dn, struct GUID *guid, const char *component_name) +{ + const struct ldb_val *v; + + v = ldb_dn_get_extended_component(dn, component_name); + if (v == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return GUID_from_ndr_blob(v, guid); +} + +/* + return a uint64_t from a extended DN structure + */ +NTSTATUS dsdb_get_extended_dn_uint64(struct ldb_dn *dn, uint64_t *val, const char *component_name) +{ + const struct ldb_val *v; + int error = 0; + + v = ldb_dn_get_extended_component(dn, component_name); + if (v == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* Just check we don't allow the caller to fill our stack */ + if (v->length >= 64) { + return NT_STATUS_INVALID_PARAMETER; + } else { + char s[v->length+1]; + memcpy(s, v->data, v->length); + s[v->length] = 0; + + *val = smb_strtoull(s, NULL, 0, &error, SMB_STR_STANDARD); + if (error != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + } + return NT_STATUS_OK; +} + +/* + return a NTTIME from a extended DN structure + */ +NTSTATUS dsdb_get_extended_dn_nttime(struct ldb_dn *dn, NTTIME *nttime, const char *component_name) +{ + return dsdb_get_extended_dn_uint64(dn, nttime, component_name); +} + +/* + return a uint32_t from a extended DN structure + */ +NTSTATUS dsdb_get_extended_dn_uint32(struct ldb_dn *dn, uint32_t *val, const char *component_name) +{ + const struct ldb_val *v; + int error = 0; + + v = ldb_dn_get_extended_component(dn, component_name); + if (v == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* Just check we don't allow the caller to fill our stack */ + if (v->length >= 32) { + return NT_STATUS_INVALID_PARAMETER; + } else { + char s[v->length + 1]; + memcpy(s, v->data, v->length); + s[v->length] = 0; + *val = smb_strtoul(s, NULL, 0, &error, SMB_STR_STANDARD); + if (error != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + } + + return NT_STATUS_OK; +} + +/* + return a dom_sid from a extended DN structure + */ +NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const char *component_name) +{ + const struct ldb_val *sid_blob; + enum ndr_err_code ndr_err; + + sid_blob = ldb_dn_get_extended_component(dn, component_name); + if (!sid_blob) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + ndr_err = ndr_pull_struct_blob_all_noalloc(sid_blob, 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); + return status; + } + + return NT_STATUS_OK; +} + + +/* + return RMD_FLAGS directly from a ldb_dn + returns 0 if not found + */ +uint32_t dsdb_dn_rmd_flags(struct ldb_dn *dn) +{ + uint32_t rmd_flags = 0; + NTSTATUS status = dsdb_get_extended_dn_uint32(dn, &rmd_flags, + "RMD_FLAGS"); + if (NT_STATUS_IS_OK(status)) { + return rmd_flags; + } + return 0; +} + +/* + return RMD_FLAGS directly from a ldb_val for a DN + returns 0 if RMD_FLAGS is not found + */ +uint32_t dsdb_dn_val_rmd_flags(const struct ldb_val *val) +{ + const char *p; + uint32_t flags; + char *end; + int error = 0; + + if (val->length < 13) { + return 0; + } + p = memmem(val->data, val->length, "<RMD_FLAGS=", 11); + if (!p) { + return 0; + } + flags = smb_strtoul(p+11, &end, 10, &error, SMB_STR_STANDARD); + if (!end || *end != '>' || error != 0) { + /* it must end in a > */ + return 0; + } + return flags; +} + +/* + return true if a ldb_val containing a DN in storage form is deleted + */ +bool dsdb_dn_is_deleted_val(const struct ldb_val *val) +{ + return (dsdb_dn_val_rmd_flags(val) & DSDB_RMD_FLAG_DELETED) != 0; +} + +/* + return true if a ldb_val containing a DN in storage form is + in the upgraded w2k3 linked attribute format + */ +bool dsdb_dn_is_upgraded_link_val(const struct ldb_val *val) +{ + return memmem(val->data, val->length, "<RMD_VERSION=", 13) != NULL; +} + +/* + return a DN for a wellknown GUID + */ +int dsdb_wellknown_dn(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, + struct ldb_dn *nc_root, const char *wk_guid, + struct ldb_dn **wkguid_dn) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + const char *attrs[] = { NULL }; + int ret; + struct ldb_dn *dn; + struct ldb_result *res = NULL; + + if (tmp_ctx == NULL) { + return ldb_oom(samdb); + } + + /* construct the magic WKGUID DN */ + dn = ldb_dn_new_fmt(tmp_ctx, samdb, "<WKGUID=%s,%s>", + wk_guid, ldb_dn_get_linearized(nc_root)); + if (!wkguid_dn) { + talloc_free(tmp_ctx); + return ldb_operr(samdb); + } + + ret = dsdb_search_dn(samdb, tmp_ctx, &res, dn, attrs, + DSDB_SEARCH_SHOW_DELETED | + DSDB_SEARCH_SHOW_RECYCLED); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + /* fix clang warning */ + if (res == NULL){ + talloc_free(tmp_ctx); + return LDB_ERR_OTHER; + } + + (*wkguid_dn) = talloc_steal(mem_ctx, res->msgs[0]->dn); + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + +static int dsdb_dn_compare_ptrs(struct ldb_dn **dn1, struct ldb_dn **dn2) +{ + return ldb_dn_compare(*dn1, *dn2); +} + +/* + find a NC root given a DN within the NC by reading the rootDSE namingContexts + */ +static int dsdb_find_nc_root_string_based(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct ldb_dn **nc_root) +{ + const char *root_attrs[] = { "namingContexts", NULL }; + TALLOC_CTX *tmp_ctx; + int ret; + struct ldb_message_element *el; + struct ldb_result *root_res; + unsigned int i; + struct ldb_dn **nc_dns; + + tmp_ctx = talloc_new(samdb); + if (tmp_ctx == NULL) { + return ldb_oom(samdb); + } + + ret = ldb_search(samdb, tmp_ctx, &root_res, + ldb_dn_new(tmp_ctx, samdb, ""), LDB_SCOPE_BASE, root_attrs, NULL); + if (ret != LDB_SUCCESS || root_res->count == 0) { + DEBUG(1,("Searching for namingContexts in rootDSE failed: %s\n", ldb_errstring(samdb))); + talloc_free(tmp_ctx); + return ret; + } + + el = ldb_msg_find_element(root_res->msgs[0], "namingContexts"); + if ((el == NULL) || (el->num_values < 3)) { + struct ldb_message *tmp_msg; + + DEBUG(5,("dsdb_find_nc_root: Finding a valid 'namingContexts' element in the RootDSE failed. Using a temporary list.\n")); + + /* This generates a temporary list of NCs in order to let the + * provisioning work. */ + tmp_msg = ldb_msg_new(tmp_ctx); + if (tmp_msg == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(samdb); + } + ret = ldb_msg_add_steal_string(tmp_msg, "namingContexts", + ldb_dn_alloc_linearized(tmp_msg, ldb_get_schema_basedn(samdb))); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + ret = ldb_msg_add_steal_string(tmp_msg, "namingContexts", + ldb_dn_alloc_linearized(tmp_msg, ldb_get_config_basedn(samdb))); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + ret = ldb_msg_add_steal_string(tmp_msg, "namingContexts", + ldb_dn_alloc_linearized(tmp_msg, ldb_get_default_basedn(samdb))); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + el = &tmp_msg->elements[0]; + } + + nc_dns = talloc_array(tmp_ctx, struct ldb_dn *, el->num_values); + if (!nc_dns) { + talloc_free(tmp_ctx); + return ldb_oom(samdb); + } + + for (i=0; i<el->num_values; i++) { + nc_dns[i] = ldb_dn_from_ldb_val(nc_dns, samdb, &el->values[i]); + if (nc_dns[i] == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(samdb); + } + } + + TYPESAFE_QSORT(nc_dns, el->num_values, dsdb_dn_compare_ptrs); + + for (i=0; i<el->num_values; i++) { + if (ldb_dn_compare_base(nc_dns[i], dn) == 0) { + (*nc_root) = talloc_steal(mem_ctx, nc_dns[i]); + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + } + + talloc_free(tmp_ctx); + return ldb_error(samdb, LDB_ERR_NO_SUCH_OBJECT, __func__); +} + +struct dsdb_get_partition_and_dn { + TALLOC_CTX *mem_ctx; + unsigned int count; + struct ldb_dn *dn; + struct ldb_dn *partition_dn; + bool want_partition_dn; +}; + +static int dsdb_get_partition_and_dn(struct ldb_request *req, + struct ldb_reply *ares) +{ + int ret; + struct dsdb_get_partition_and_dn *context = req->context; + struct ldb_control *partition_ctrl = NULL; + struct dsdb_control_current_partition *partition = NULL; + + if (!ares) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS + && ares->error != LDB_ERR_NO_SUCH_OBJECT) { + return ldb_request_done(req, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (context->count != 0) { + return ldb_request_done(req, + LDB_ERR_CONSTRAINT_VIOLATION); + } + context->count++; + + context->dn = talloc_steal(context->mem_ctx, + ares->message->dn); + break; + + case LDB_REPLY_REFERRAL: + talloc_free(ares); + return ldb_request_done(req, LDB_SUCCESS); + + case LDB_REPLY_DONE: + partition_ctrl + = ldb_reply_get_control(ares, + DSDB_CONTROL_CURRENT_PARTITION_OID); + if (!context->want_partition_dn || + partition_ctrl == NULL) { + ret = ares->error; + talloc_free(ares); + + return ldb_request_done(req, ret); + } + + partition + = talloc_get_type_abort(partition_ctrl->data, + struct dsdb_control_current_partition); + context->partition_dn + = ldb_dn_copy(context->mem_ctx, partition->dn); + if (context->partition_dn == NULL) { + return ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = ares->error; + talloc_free(ares); + + return ldb_request_done(req, ret); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +/* + find a NC root given a DN within the NC + */ +int dsdb_normalise_dn_and_find_nc_root(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct ldb_dn **normalised_dn, + struct ldb_dn **nc_root) +{ + TALLOC_CTX *tmp_ctx; + int ret; + struct ldb_request *req; + struct ldb_result *res; + struct ldb_dn *search_dn = dn; + static const char * attrs[] = { NULL }; + bool has_extended = ldb_dn_has_extended(dn); + bool has_normal_components = ldb_dn_get_comp_num(dn) >= 1; + struct dsdb_get_partition_and_dn context = { + .mem_ctx = mem_ctx, + .want_partition_dn = nc_root != NULL + }; + + if (!has_extended && !has_normal_components) { + return ldb_error(samdb, LDB_ERR_NO_SUCH_OBJECT, + "Request for NC root for rootDSE (\"\") denied."); + } + + tmp_ctx = talloc_new(samdb); + if (tmp_ctx == NULL) { + return ldb_oom(samdb); + } + + res = talloc_zero(tmp_ctx, struct ldb_result); + if (res == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(samdb); + } + + if (has_extended && has_normal_components) { + bool minimise_ok; + search_dn = ldb_dn_copy(tmp_ctx, dn); + if (search_dn == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(samdb); + } + minimise_ok = ldb_dn_minimise(search_dn); + if (!minimise_ok) { + talloc_free(tmp_ctx); + return ldb_operr(samdb); + } + } + + ret = ldb_build_search_req(&req, samdb, tmp_ctx, + search_dn, + LDB_SCOPE_BASE, + NULL, + attrs, + NULL, + &context, + dsdb_get_partition_and_dn, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = ldb_request_add_control(req, + DSDB_CONTROL_CURRENT_PARTITION_OID, + false, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = dsdb_request_add_controls(req, + DSDB_SEARCH_SHOW_RECYCLED| + DSDB_SEARCH_SHOW_DELETED| + DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = ldb_request(samdb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + /* + * This could be a new DN, not in the DB, which is OK. If we + * don't need the normalised DN, we can continue. + * + * We may be told the partition it would be in in the search + * reply control, or if not we can do a string-based match. + */ + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + if (normalised_dn != NULL) { + talloc_free(tmp_ctx); + return ret; + } + ret = LDB_SUCCESS; + ldb_reset_err_string(samdb); + } else if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + if (normalised_dn != NULL) { + if (context.count != 1) { + /* No results */ + ldb_asprintf_errstring(samdb, + "Request for NC root for %s failed to return any results.", + ldb_dn_get_linearized(dn)); + talloc_free(tmp_ctx); + return LDB_ERR_NO_SUCH_OBJECT; + } + *normalised_dn = context.dn; + } + + /* + * If the user did not need to find the nc_root, + * we are done + */ + if (nc_root == NULL) { + talloc_free(tmp_ctx); + return ret; + } + + /* + * When we are working locally, both for the case were + * we find the DN, and the case where we fail, we get + * back via controls the partition it was in or should + * have been in, to return to the client + */ + if (context.partition_dn != NULL) { + (*nc_root) = context.partition_dn; + + talloc_free(tmp_ctx); + return ret; + } + + /* + * This is a remote operation, which is a little harder as we + * have a work out the nc_root from the list of NCs. If we did + * at least resolve the DN to a string, get that now, it makes + * the string-based match below possible for a GUID-based + * input over remote LDAP. + */ + if (context.dn) { + dn = context.dn; + } else if (has_extended && !has_normal_components) { + ldb_asprintf_errstring(samdb, + "Cannot determine NC root " + "for a not-found bare extended DN %s.", + ldb_dn_get_extended_linearized(tmp_ctx, dn, 1)); + talloc_free(tmp_ctx); + return LDB_ERR_NO_SUCH_OBJECT; + } + + /* + * Either we are working against a remote LDAP + * server or the object doesn't exist locally. + * + * This means any GUID that was present in the DN + * therefore could not be evaluated, so do a + * string-based match instead. + */ + talloc_free(tmp_ctx); + return dsdb_find_nc_root_string_based(samdb, + mem_ctx, + dn, + nc_root); +} + +/* + find a NC root given a DN within the NC + */ +int dsdb_find_nc_root(struct ldb_context *samdb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct ldb_dn **nc_root) +{ + return dsdb_normalise_dn_and_find_nc_root(samdb, + mem_ctx, + dn, + NULL, + nc_root); +} + +/* + find the deleted objects DN for any object, by looking for the NC + root, then looking up the wellknown GUID + */ +int dsdb_get_deleted_objects_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, struct ldb_dn *obj_dn, + struct ldb_dn **do_dn) +{ + struct ldb_dn *nc_root; + int ret; + + ret = dsdb_find_nc_root(ldb, mem_ctx, obj_dn, &nc_root); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = dsdb_wellknown_dn(ldb, mem_ctx, nc_root, DS_GUID_DELETED_OBJECTS_CONTAINER, do_dn); + talloc_free(nc_root); + return ret; +} + +/* + return the tombstoneLifetime, in days + */ +int dsdb_tombstone_lifetime(struct ldb_context *ldb, uint32_t *lifetime) +{ + struct ldb_dn *dn; + dn = ldb_get_config_basedn(ldb); + if (!dn) { + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); + } + dn = ldb_dn_copy(ldb, dn); + if (!dn) { + return ldb_operr(ldb); + } + /* see MS-ADTS section 7.1.1.2.4.1.1. There doesn't appear to + be a wellknown GUID for this */ + if (!ldb_dn_add_child_fmt(dn, "CN=Directory Service,CN=Windows NT,CN=Services")) { + talloc_free(dn); + return ldb_operr(ldb); + } + + *lifetime = samdb_search_uint(ldb, dn, 180, dn, "tombstoneLifetime", "objectClass=nTDSService"); + talloc_free(dn); + return LDB_SUCCESS; +} + +/* + compare a ldb_val to a string case insensitively + */ +int samdb_ldb_val_case_cmp(const char *s, struct ldb_val *v) +{ + size_t len = strlen(s); + int ret; + if (len > v->length) return 1; + ret = strncasecmp(s, (const char *)v->data, v->length); + if (ret != 0) return ret; + if (v->length > len && v->data[len] != 0) { + return -1; + } + return 0; +} + + +/* + load the UDV for a partition in v2 format + The list is returned sorted, and with our local cursor added + */ +int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaCursor2 **cursors, uint32_t *count) +{ + static const char *attrs[] = { "replUpToDateVector", NULL }; + struct ldb_result *r = NULL; + const struct ldb_val *ouv_value; + unsigned int i; + int ret; + uint64_t highest_usn = 0; + const struct GUID *our_invocation_id; + static const struct timeval tv1970; + NTTIME nt1970 = timeval_to_nttime(&tv1970); + + ret = dsdb_search_dn(samdb, mem_ctx, &r, dn, attrs, DSDB_SEARCH_SHOW_RECYCLED|DSDB_SEARCH_SHOW_DELETED); + if (ret != LDB_SUCCESS) { + return ret; + } + /* fix clang warning */ + if (r == NULL) { + return LDB_ERR_OTHER; + } + ouv_value = ldb_msg_find_ldb_val(r->msgs[0], "replUpToDateVector"); + if (ouv_value) { + enum ndr_err_code ndr_err; + struct replUpToDateVectorBlob ouv; + + ndr_err = ndr_pull_struct_blob(ouv_value, r, &ouv, + (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(r); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + if (ouv.version != 2) { + /* we always store as version 2, and + * replUpToDateVector is not replicated + */ + talloc_free(r); + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + *count = ouv.ctr.ctr2.count; + *cursors = talloc_steal(mem_ctx, ouv.ctr.ctr2.cursors); + } else { + *count = 0; + *cursors = NULL; + } + + talloc_free(r); + + our_invocation_id = samdb_ntds_invocation_id(samdb); + if (!our_invocation_id) { + DEBUG(0,(__location__ ": No invocationID on samdb - %s\n", ldb_errstring(samdb))); + talloc_free(*cursors); + return ldb_operr(samdb); + } + + ret = ldb_sequence_number(samdb, LDB_SEQ_HIGHEST_SEQ, &highest_usn); + if (ret != LDB_SUCCESS) { + /* nothing to add - this can happen after a vampire */ + TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare); + return LDB_SUCCESS; + } + + for (i=0; i<*count; i++) { + if (GUID_equal(our_invocation_id, &(*cursors)[i].source_dsa_invocation_id)) { + (*cursors)[i].highest_usn = highest_usn; + (*cursors)[i].last_sync_success = nt1970; + TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare); + return LDB_SUCCESS; + } + } + + (*cursors) = talloc_realloc(mem_ctx, *cursors, struct drsuapi_DsReplicaCursor2, (*count)+1); + if (! *cursors) { + return ldb_oom(samdb); + } + + (*cursors)[*count].source_dsa_invocation_id = *our_invocation_id; + (*cursors)[*count].highest_usn = highest_usn; + (*cursors)[*count].last_sync_success = nt1970; + (*count)++; + + TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare); + + return LDB_SUCCESS; +} + +/* + load the UDV for a partition in version 1 format + The list is returned sorted, and with our local cursor added + */ +int dsdb_load_udv_v1(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaCursor **cursors, uint32_t *count) +{ + struct drsuapi_DsReplicaCursor2 *v2 = NULL; + uint32_t i; + int ret; + + ret = dsdb_load_udv_v2(samdb, dn, mem_ctx, &v2, count); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (*count == 0) { + talloc_free(v2); + *cursors = NULL; + return LDB_SUCCESS; + } + + *cursors = talloc_array(mem_ctx, struct drsuapi_DsReplicaCursor, *count); + if (*cursors == NULL) { + talloc_free(v2); + return ldb_oom(samdb); + } + + for (i=0; i<*count; i++) { + (*cursors)[i].source_dsa_invocation_id = v2[i].source_dsa_invocation_id; + (*cursors)[i].highest_usn = v2[i].highest_usn; + } + talloc_free(v2); + return LDB_SUCCESS; +} + +/* + add a set of controls to a ldb_request structure based on a set of + flags. See util.h for a list of available flags + */ +int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags) +{ + int ret; + if (dsdb_flags & DSDB_SEARCH_SEARCH_ALL_PARTITIONS) { + struct ldb_search_options_control *options; + /* Using the phantom root control allows us to search all partitions */ + options = talloc(req, struct ldb_search_options_control); + if (options == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + options->search_options = LDB_SEARCH_OPTION_PHANTOM_ROOT; + + ret = ldb_request_add_control(req, + LDB_CONTROL_SEARCH_OPTIONS_OID, + true, options); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_SEARCH_NO_GLOBAL_CATALOG) { + ret = ldb_request_add_control(req, + DSDB_CONTROL_NO_GLOBAL_CATALOG, + false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_SEARCH_SHOW_DELETED) { + ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_DELETED_OID, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_SEARCH_SHOW_RECYCLED) { + ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_RECYCLED_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT) { + ret = ldb_request_add_control(req, DSDB_CONTROL_DN_STORAGE_FORMAT_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_SEARCH_SHOW_EXTENDED_DN) { + struct ldb_extended_dn_control *extended_ctrl = talloc(req, struct ldb_extended_dn_control); + if (!extended_ctrl) { + return LDB_ERR_OPERATIONS_ERROR; + } + extended_ctrl->type = 1; + + ret = ldb_request_add_control(req, LDB_CONTROL_EXTENDED_DN_OID, true, extended_ctrl); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_SEARCH_REVEAL_INTERNALS) { + ret = ldb_request_add_control(req, LDB_CONTROL_REVEAL_INTERNALS, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_MODIFY_RELAX) { + ret = ldb_request_add_control(req, LDB_CONTROL_RELAX_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_MODIFY_PERMISSIVE) { + ret = ldb_request_add_control(req, LDB_CONTROL_PERMISSIVE_MODIFY_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_FLAG_AS_SYSTEM) { + ret = ldb_request_add_control(req, LDB_CONTROL_AS_SYSTEM_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_TREE_DELETE) { + ret = ldb_request_add_control(req, LDB_CONTROL_TREE_DELETE_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_PROVISION) { + ret = ldb_request_add_control(req, LDB_CONTROL_PROVISION_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + /* This is a special control to bypass the password_hash module for use in pdb_samba4 for Samba3 upgrades */ + if (dsdb_flags & DSDB_BYPASS_PASSWORD_HASH) { + ret = ldb_request_add_control(req, DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_PASSWORD_BYPASS_LAST_SET) { + /* + * This must not be critical, as it will only be + * handled (and need to be handled) if the other + * attributes in the request bring password_hash into + * action + */ + ret = ldb_request_add_control(req, DSDB_CONTROL_PASSWORD_BYPASS_LAST_SET_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_REPLMD_VANISH_LINKS) { + ret = ldb_request_add_control(req, DSDB_CONTROL_REPLMD_VANISH_LINKS, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_MODIFY_PARTIAL_REPLICA) { + ret = ldb_request_add_control(req, DSDB_CONTROL_PARTIAL_REPLICA, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_FLAG_REPLICATED_UPDATE) { + ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE) { + ret = ldb_request_add_control(req, DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dsdb_flags & DSDB_MARK_REQ_UNTRUSTED) { + ldb_req_mark_untrusted(req); + } + + return LDB_SUCCESS; +} + +/* + returns true if a control with the specified "oid" exists +*/ +bool dsdb_request_has_control(struct ldb_request *req, const char *oid) +{ + return (ldb_request_get_control(req, oid) != NULL); +} + +/* + an add with a set of controls +*/ +int dsdb_add(struct ldb_context *ldb, const struct ldb_message *message, + uint32_t dsdb_flags) +{ + struct ldb_request *req; + int ret; + + ret = ldb_build_add_req(&req, ldb, ldb, + message, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + if (ret != LDB_SUCCESS) return ret; + + ret = dsdb_request_add_controls(req, dsdb_flags); + if (ret != LDB_SUCCESS) { + talloc_free(req); + return ret; + } + + ret = dsdb_autotransaction_request(ldb, req); + + talloc_free(req); + return ret; +} + +/* + a modify with a set of controls +*/ +int dsdb_modify(struct ldb_context *ldb, const struct ldb_message *message, + uint32_t dsdb_flags) +{ + struct ldb_request *req; + int ret; + + ret = ldb_build_mod_req(&req, ldb, ldb, + message, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + if (ret != LDB_SUCCESS) return ret; + + ret = dsdb_request_add_controls(req, dsdb_flags); + if (ret != LDB_SUCCESS) { + talloc_free(req); + return ret; + } + + ret = dsdb_autotransaction_request(ldb, req); + + talloc_free(req); + return ret; +} + +/* + a delete with a set of flags +*/ +int dsdb_delete(struct ldb_context *ldb, struct ldb_dn *dn, + uint32_t dsdb_flags) +{ + struct ldb_request *req; + int ret; + + ret = ldb_build_del_req(&req, ldb, ldb, + dn, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + if (ret != LDB_SUCCESS) return ret; + + ret = dsdb_request_add_controls(req, dsdb_flags); + if (ret != LDB_SUCCESS) { + talloc_free(req); + return ret; + } + + ret = dsdb_autotransaction_request(ldb, req); + + talloc_free(req); + return ret; +} + +/* + like dsdb_modify() but set all the element flags to + LDB_FLAG_MOD_REPLACE + */ +int dsdb_replace(struct ldb_context *ldb, struct ldb_message *msg, uint32_t dsdb_flags) +{ + unsigned 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; + } + + return dsdb_modify(ldb, msg, dsdb_flags); +} + +const char *dsdb_search_scope_as_string(enum ldb_scope scope) +{ + const char *scope_str; + + switch (scope) { + case LDB_SCOPE_BASE: + scope_str = "BASE"; + break; + case LDB_SCOPE_ONELEVEL: + scope_str = "ONE"; + break; + case LDB_SCOPE_SUBTREE: + scope_str = "SUB"; + break; + default: + scope_str = "<Invalid scope>"; + break; + } + return scope_str; +} + + +/* + search for attrs on one DN, allowing for dsdb_flags controls + */ +int dsdb_search_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + struct ldb_dn *basedn, + const char * const *attrs, + uint32_t dsdb_flags) +{ + int ret; + struct ldb_request *req; + struct ldb_result *res; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + res = talloc_zero(tmp_ctx, struct ldb_result); + if (!res) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ret = ldb_build_search_req(&req, ldb, res, + basedn, + LDB_SCOPE_BASE, + NULL, + attrs, + NULL, + res, + ldb_search_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = dsdb_request_add_controls(req, dsdb_flags); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = ldb_request(ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + talloc_free(req); + if (ret != LDB_SUCCESS) { + DBG_INFO("flags=0x%08x %s -> %s (%s)\n", + dsdb_flags, + basedn?ldb_dn_get_extended_linearized(tmp_ctx, + basedn, + 1):"NULL", + ldb_errstring(ldb), ldb_strerror(ret)); + talloc_free(tmp_ctx); + return ret; + } + + DBG_DEBUG("flags=0x%08x %s -> %d\n", + dsdb_flags, + basedn?ldb_dn_get_extended_linearized(tmp_ctx, + basedn, + 1):"NULL", + res->count); + + *_result = talloc_steal(mem_ctx, res); + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +/* + search for attrs on one DN, by the GUID of the DN, allowing for + dsdb_flags controls + */ +int dsdb_search_by_dn_guid(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + const struct GUID *guid, + const char * const *attrs, + uint32_t dsdb_flags) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + struct ldb_dn *dn; + int ret; + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + dn = ldb_dn_new_fmt(tmp_ctx, ldb, "<GUID=%s>", GUID_string(tmp_ctx, guid)); + if (dn == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ret = dsdb_search_dn(ldb, mem_ctx, _result, dn, attrs, dsdb_flags); + talloc_free(tmp_ctx); + return ret; +} + +/* + general search with dsdb_flags for controls + */ +int dsdb_search(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + struct ldb_dn *basedn, + enum ldb_scope scope, + const char * const *attrs, + uint32_t dsdb_flags, + const char *exp_fmt, ...) _PRINTF_ATTRIBUTE(8, 9) +{ + int ret; + struct ldb_request *req; + struct ldb_result *res; + va_list ap; + char *expression = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + /* cross-partitions searches with a basedn break multi-domain support */ + SMB_ASSERT(basedn == NULL || (dsdb_flags & DSDB_SEARCH_SEARCH_ALL_PARTITIONS) == 0); + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + res = talloc_zero(tmp_ctx, struct ldb_result); + if (!res) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + if (exp_fmt) { + va_start(ap, exp_fmt); + expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap); + va_end(ap); + + if (!expression) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + } + + ret = ldb_build_search_req(&req, ldb, tmp_ctx, + basedn, + scope, + expression, + attrs, + NULL, + res, + ldb_search_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = dsdb_request_add_controls(req, dsdb_flags); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + ldb_reset_err_string(ldb); + return ret; + } + + ret = ldb_request(ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + if (ret != LDB_SUCCESS) { + DBG_INFO("%s flags=0x%08x %s %s -> %s (%s)\n", + dsdb_search_scope_as_string(scope), + dsdb_flags, + basedn?ldb_dn_get_extended_linearized(tmp_ctx, + basedn, + 1):"NULL", + expression?expression:"NULL", + ldb_errstring(ldb), ldb_strerror(ret)); + talloc_free(tmp_ctx); + return ret; + } + + if (dsdb_flags & DSDB_SEARCH_ONE_ONLY) { + if (res->count == 0) { + DBG_INFO("%s SEARCH_ONE_ONLY flags=0x%08x %s %s -> %u results\n", + dsdb_search_scope_as_string(scope), + dsdb_flags, + basedn?ldb_dn_get_extended_linearized(tmp_ctx, + basedn, + 1):"NULL", + expression?expression:"NULL", res->count); + talloc_free(tmp_ctx); + ldb_reset_err_string(ldb); + return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__); + } + if (res->count != 1) { + DBG_INFO("%s SEARCH_ONE_ONLY flags=0x%08x %s %s -> %u (expected 1) results\n", + dsdb_search_scope_as_string(scope), + dsdb_flags, + basedn?ldb_dn_get_extended_linearized(tmp_ctx, + basedn, + 1):"NULL", + expression?expression:"NULL", res->count); + talloc_free(tmp_ctx); + ldb_reset_err_string(ldb); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + } + + *_result = talloc_steal(mem_ctx, res); + + DBG_DEBUG("%s flags=0x%08x %s %s -> %d\n", + dsdb_search_scope_as_string(scope), + dsdb_flags, + basedn?ldb_dn_get_extended_linearized(tmp_ctx, + basedn, + 1):"NULL", + expression?expression:"NULL", + res->count); + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + +/* + general search with dsdb_flags for controls + returns exactly 1 record or an error + */ +int dsdb_search_one(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_message **msg, + struct ldb_dn *basedn, + enum ldb_scope scope, + const char * const *attrs, + uint32_t dsdb_flags, + const char *exp_fmt, ...) _PRINTF_ATTRIBUTE(8, 9) +{ + int ret; + struct ldb_result *res; + va_list ap; + char *expression = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + dsdb_flags |= DSDB_SEARCH_ONE_ONLY; + + res = talloc_zero(tmp_ctx, struct ldb_result); + if (!res) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + if (exp_fmt) { + va_start(ap, exp_fmt); + expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap); + va_end(ap); + + if (!expression) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + ret = dsdb_search(ldb, tmp_ctx, &res, basedn, scope, attrs, + dsdb_flags, "%s", expression); + } else { + ret = dsdb_search(ldb, tmp_ctx, &res, basedn, scope, attrs, + dsdb_flags, NULL); + } + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + *msg = talloc_steal(mem_ctx, res->msgs[0]); + talloc_free(tmp_ctx); + + return LDB_SUCCESS; +} + +/* returns back the forest DNS name */ +const char *samdb_forest_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) +{ + const char *forest_name = ldb_dn_canonical_string(mem_ctx, + ldb_get_root_basedn(ldb)); + char *p; + + if (forest_name == NULL) { + return NULL; + } + + p = strchr(forest_name, '/'); + if (p) { + *p = '\0'; + } + + return forest_name; +} + +/* returns back the default domain DNS name */ +const char *samdb_default_domain_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) +{ + const char *domain_name = ldb_dn_canonical_string(mem_ctx, + ldb_get_default_basedn(ldb)); + char *p; + + if (domain_name == NULL) { + return NULL; + } + + p = strchr(domain_name, '/'); + if (p) { + *p = '\0'; + } + + return domain_name; +} + +/* + validate that an DSA GUID belongs to the specified user sid. + The user SID must be a domain controller account (either RODC or + RWDC) + */ +int dsdb_validate_dsa_guid(struct ldb_context *ldb, + const struct GUID *dsa_guid, + const struct dom_sid *sid) +{ + /* strategy: + - find DN of record with the DSA GUID in the + configuration partition (objectGUID) + - remove "NTDS Settings" component from DN + - do a base search on that DN for serverReference with + extended-dn enabled + - extract objectSid from resulting serverReference + attribute + - check this sid matches the sid argument + */ + struct ldb_dn *config_dn; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + struct ldb_message *msg; + const char *attrs1[] = { NULL }; + const char *attrs2[] = { "serverReference", NULL }; + int ret; + struct ldb_dn *dn, *account_dn; + struct dom_sid sid2; + NTSTATUS status; + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + config_dn = ldb_get_config_basedn(ldb); + + ret = dsdb_search_one(ldb, tmp_ctx, &msg, config_dn, LDB_SCOPE_SUBTREE, + attrs1, 0, "(&(objectGUID=%s)(objectClass=nTDSDSA))", GUID_string(tmp_ctx, dsa_guid)); + if (ret != LDB_SUCCESS) { + DEBUG(1,(__location__ ": Failed to find DSA objectGUID %s for sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + dn = msg->dn; + + if (!ldb_dn_remove_child_components(dn, 1)) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + ret = dsdb_search_one(ldb, tmp_ctx, &msg, dn, LDB_SCOPE_BASE, + attrs2, DSDB_SEARCH_SHOW_EXTENDED_DN, + "(objectClass=server)"); + if (ret != LDB_SUCCESS) { + DEBUG(1,(__location__ ": Failed to find server record for DSA with objectGUID %s, sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + account_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, msg, "serverReference"); + if (account_dn == NULL) { + DEBUG(1,(__location__ ": Failed to find account dn " + "(serverReference) for %s, parent of DSA with " + "objectGUID %s, sid %s\n", + ldb_dn_get_linearized(msg->dn), + GUID_string(tmp_ctx, dsa_guid), + dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + status = dsdb_get_extended_dn_sid(account_dn, &sid2, "SID"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,(__location__ ": Failed to find SID for DSA with objectGUID %s, sid %s\n", + GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid))); + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + if (!dom_sid_equal(sid, &sid2)) { + /* someone is trying to spoof another account */ + DEBUG(0,(__location__ ": Bad DSA objectGUID %s for sid %s - expected sid %s\n", + GUID_string(tmp_ctx, dsa_guid), + dom_sid_string(tmp_ctx, sid), + dom_sid_string(tmp_ctx, &sid2))); + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +static const char * const secret_attributes[] = { + DSDB_SECRET_ATTRIBUTES, + NULL +}; + +/* + check if the attribute belongs to the RODC filtered attribute set + Note that attributes that are in the filtered attribute set are the + ones that _are_ always sent to a RODC +*/ +bool dsdb_attr_in_rodc_fas(const struct dsdb_attribute *sa) +{ + /* they never get secret attributes */ + if (ldb_attr_in_list(secret_attributes, sa->lDAPDisplayName)) { + return false; + } + + /* they do get non-secret critical attributes */ + if (sa->schemaFlagsEx & SCHEMA_FLAG_ATTR_IS_CRITICAL) { + return true; + } + + /* they do get non-secret attributes marked as being in the FAS */ + if (sa->searchFlags & SEARCH_FLAG_RODC_ATTRIBUTE) { + return true; + } + + /* other attributes are denied */ + return false; +} + +/* return fsmo role dn and role owner dn for a particular role*/ +WERROR dsdb_get_fsmo_role_info(TALLOC_CTX *tmp_ctx, + struct ldb_context *ldb, + uint32_t role, + struct ldb_dn **fsmo_role_dn, + struct ldb_dn **role_owner_dn) +{ + int ret; + switch (role) { + case DREPL_NAMING_MASTER: + *fsmo_role_dn = samdb_partitions_dn(ldb, tmp_ctx); + ret = samdb_reference_dn(ldb, tmp_ctx, *fsmo_role_dn, "fSMORoleOwner", role_owner_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find fSMORoleOwner in Naming Master object - %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + break; + case DREPL_INFRASTRUCTURE_MASTER: + *fsmo_role_dn = samdb_infrastructure_dn(ldb, tmp_ctx); + ret = samdb_reference_dn(ldb, tmp_ctx, *fsmo_role_dn, "fSMORoleOwner", role_owner_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find fSMORoleOwner in Schema Master object - %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + break; + case DREPL_RID_MASTER: + ret = samdb_rid_manager_dn(ldb, tmp_ctx, fsmo_role_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ ": Failed to find RID Manager object - %s\n", ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = samdb_reference_dn(ldb, tmp_ctx, *fsmo_role_dn, "fSMORoleOwner", role_owner_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find fSMORoleOwner in RID Manager object - %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + break; + case DREPL_SCHEMA_MASTER: + *fsmo_role_dn = ldb_get_schema_basedn(ldb); + ret = samdb_reference_dn(ldb, tmp_ctx, *fsmo_role_dn, "fSMORoleOwner", role_owner_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find fSMORoleOwner in Schema Master object - %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + break; + case DREPL_PDC_MASTER: + *fsmo_role_dn = ldb_get_default_basedn(ldb); + ret = samdb_reference_dn(ldb, tmp_ctx, *fsmo_role_dn, "fSMORoleOwner", role_owner_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find fSMORoleOwner in Pd Master object - %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + break; + default: + return WERR_DS_DRA_INTERNAL_ERROR; + } + return WERR_OK; +} + +const char *samdb_dn_to_dnshostname(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *server_dn) +{ + int ldb_ret; + struct ldb_result *res = NULL; + const char * const attrs[] = { "dNSHostName", NULL}; + + ldb_ret = ldb_search(ldb, mem_ctx, &res, + server_dn, + LDB_SCOPE_BASE, + attrs, NULL); + if (ldb_ret != LDB_SUCCESS) { + DEBUG(4, ("Failed to find dNSHostName for dn %s, ldb error: %s\n", + ldb_dn_get_linearized(server_dn), ldb_errstring(ldb))); + return NULL; + } + + return ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL); +} + +/* + returns true if an attribute is in the filter, + false otherwise, provided that attribute value is provided with the expression +*/ +bool dsdb_attr_in_parse_tree(struct ldb_parse_tree *tree, + const char *attr) +{ + unsigned int i; + switch (tree->operation) { + case LDB_OP_AND: + case LDB_OP_OR: + for (i=0;i<tree->u.list.num_elements;i++) { + if (dsdb_attr_in_parse_tree(tree->u.list.elements[i], + attr)) + return true; + } + return false; + case LDB_OP_NOT: + return dsdb_attr_in_parse_tree(tree->u.isnot.child, attr); + case LDB_OP_EQUALITY: + if (ldb_attr_cmp(tree->u.equality.attr, attr) == 0) { + return true; + } + return false; + case LDB_OP_GREATER: + case LDB_OP_LESS: + case LDB_OP_APPROX: + if (ldb_attr_cmp(tree->u.comparison.attr, attr) == 0) { + return true; + } + return false; + case LDB_OP_SUBSTRING: + if (ldb_attr_cmp(tree->u.substring.attr, attr) == 0) { + return true; + } + return false; + case LDB_OP_PRESENT: + /* (attrname=*) is not filtered out */ + return false; + case LDB_OP_EXTENDED: + if (tree->u.extended.attr && + ldb_attr_cmp(tree->u.extended.attr, attr) == 0) { + return true; + } + return false; + } + return false; +} + +int dsdb_werror_at(struct ldb_context *ldb, int ldb_ecode, WERROR werr, + const char *location, const char *func, + const char *reason) +{ + if (reason == NULL) { + reason = win_errstr(werr); + } + ldb_asprintf_errstring(ldb, "%08X: %s at %s:%s", + W_ERROR_V(werr), reason, location, func); + return ldb_ecode; +} + +/* + map an ldb error code to an approximate NTSTATUS code + */ +NTSTATUS dsdb_ldb_err_to_ntstatus(int err) +{ + switch (err) { + case LDB_SUCCESS: + return NT_STATUS_OK; + + case LDB_ERR_PROTOCOL_ERROR: + return NT_STATUS_DEVICE_PROTOCOL_ERROR; + + case LDB_ERR_TIME_LIMIT_EXCEEDED: + return NT_STATUS_IO_TIMEOUT; + + case LDB_ERR_SIZE_LIMIT_EXCEEDED: + return NT_STATUS_BUFFER_TOO_SMALL; + + case LDB_ERR_COMPARE_FALSE: + case LDB_ERR_COMPARE_TRUE: + return NT_STATUS_REVISION_MISMATCH; + + case LDB_ERR_AUTH_METHOD_NOT_SUPPORTED: + return NT_STATUS_NOT_SUPPORTED; + + case LDB_ERR_STRONG_AUTH_REQUIRED: + case LDB_ERR_CONFIDENTIALITY_REQUIRED: + case LDB_ERR_SASL_BIND_IN_PROGRESS: + case LDB_ERR_INAPPROPRIATE_AUTHENTICATION: + case LDB_ERR_INVALID_CREDENTIALS: + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + case LDB_ERR_UNWILLING_TO_PERFORM: + return NT_STATUS_ACCESS_DENIED; + + case LDB_ERR_NO_SUCH_OBJECT: + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + + case LDB_ERR_REFERRAL: + case LDB_ERR_NO_SUCH_ATTRIBUTE: + return NT_STATUS_NOT_FOUND; + + case LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION: + return NT_STATUS_NOT_SUPPORTED; + + case LDB_ERR_ADMIN_LIMIT_EXCEEDED: + return NT_STATUS_BUFFER_TOO_SMALL; + + case LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE: + case LDB_ERR_INAPPROPRIATE_MATCHING: + case LDB_ERR_CONSTRAINT_VIOLATION: + case LDB_ERR_INVALID_ATTRIBUTE_SYNTAX: + case LDB_ERR_INVALID_DN_SYNTAX: + case LDB_ERR_NAMING_VIOLATION: + case LDB_ERR_OBJECT_CLASS_VIOLATION: + case LDB_ERR_NOT_ALLOWED_ON_NON_LEAF: + case LDB_ERR_NOT_ALLOWED_ON_RDN: + return NT_STATUS_INVALID_PARAMETER; + + case LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS: + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_ERROR_DS_OBJ_STRING_NAME_EXISTS; + + case LDB_ERR_BUSY: + return NT_STATUS_NETWORK_BUSY; + + case LDB_ERR_ALIAS_PROBLEM: + case LDB_ERR_ALIAS_DEREFERENCING_PROBLEM: + case LDB_ERR_UNAVAILABLE: + case LDB_ERR_LOOP_DETECT: + case LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED: + case LDB_ERR_AFFECTS_MULTIPLE_DSAS: + case LDB_ERR_OTHER: + case LDB_ERR_OPERATIONS_ERROR: + break; + } + return NT_STATUS_UNSUCCESSFUL; +} + + +/* + create a new naming context that will hold a partial replica + */ +int dsdb_create_partial_replica_NC(struct ldb_context *ldb, struct ldb_dn *dn) +{ + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + struct ldb_message *msg; + int ret; + + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + msg->dn = dn; + ret = ldb_msg_add_string(msg, "objectClass", "top"); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + /* [MS-DRSR] implies that we should only add the 'top' + * objectclass, but that would cause lots of problems with our + * objectclass code as top is not structural, so we add + * 'domainDNS' as well to keep things sane. We're expecting + * this new NC to be of objectclass domainDNS after + * replication anyway + */ + ret = ldb_msg_add_string(msg, "objectClass", "domainDNS"); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ret = ldb_msg_add_fmt(msg, "instanceType", "%u", + INSTANCE_TYPE_IS_NC_HEAD| + INSTANCE_TYPE_NC_ABOVE| + INSTANCE_TYPE_UNINSTANT); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ret = dsdb_add(ldb, msg, DSDB_MODIFY_PARTIAL_REPLICA); + if (ret != LDB_SUCCESS && ret != LDB_ERR_ENTRY_ALREADY_EXISTS) { + DEBUG(0,("Failed to create new NC for %s - %s (%s)\n", + ldb_dn_get_linearized(dn), + ldb_errstring(ldb), ldb_strerror(ret))); + talloc_free(tmp_ctx); + return ret; + } + + DEBUG(1,("Created new NC for %s\n", ldb_dn_get_linearized(dn))); + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +/* + * Return the effective badPwdCount + * + * This requires that the user_msg have (if present): + * - badPasswordTime + * - badPwdCount + * + * This also requires that the domain_msg have (if present): + * - lockOutObservationWindow + */ +int dsdb_effective_badPwdCount(const struct ldb_message *user_msg, + int64_t lockOutObservationWindow, + NTTIME now) +{ + int64_t badPasswordTime; + badPasswordTime = ldb_msg_find_attr_as_int64(user_msg, "badPasswordTime", 0); + + if (badPasswordTime - lockOutObservationWindow >= now) { + return ldb_msg_find_attr_as_int(user_msg, "badPwdCount", 0); + } else { + return 0; + } +} + +/* + * Returns a user's PSO, or NULL if none was found + */ +static struct ldb_result *lookup_user_pso(struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + const struct ldb_message *user_msg, + const char * const *attrs) +{ + struct ldb_result *res = NULL; + struct ldb_dn *pso_dn = NULL; + int ret; + + /* if the user has a PSO that applies, then use the PSO's setting */ + pso_dn = ldb_msg_find_attr_as_dn(sam_ldb, mem_ctx, user_msg, + "msDS-ResultantPSO"); + + if (pso_dn != NULL) { + + ret = dsdb_search_dn(sam_ldb, mem_ctx, &res, pso_dn, attrs, 0); + if (ret != LDB_SUCCESS) { + + /* + * log the error. The caller should fallback to using + * the default domain password settings + */ + DBG_ERR("Error retrieving msDS-ResultantPSO %s for %s\n", + ldb_dn_get_linearized(pso_dn), + ldb_dn_get_linearized(user_msg->dn)); + } + talloc_free(pso_dn); + } + return res; +} + +/* + * Return the msDS-LockoutObservationWindow for a user message + * + * This requires that the user_msg have (if present): + * - msDS-ResultantPSO + */ +int64_t samdb_result_msds_LockoutObservationWindow( + struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *domain_dn, + const struct ldb_message *user_msg) +{ + int64_t lockOutObservationWindow; + struct ldb_result *res = NULL; + const char *attrs[] = { "msDS-LockoutObservationWindow", + NULL }; + if (domain_dn == NULL) { + smb_panic("domain dn is NULL"); + } + res = lookup_user_pso(sam_ldb, mem_ctx, user_msg, attrs); + + if (res != NULL) { + lockOutObservationWindow = + ldb_msg_find_attr_as_int64(res->msgs[0], + "msDS-LockoutObservationWindow", + DEFAULT_OBSERVATION_WINDOW); + talloc_free(res); + } else { + + /* no PSO was found, lookup the default domain setting */ + lockOutObservationWindow = + samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn, + "lockOutObservationWindow", NULL); + } + return lockOutObservationWindow; +} + +/* + * Return the effective badPwdCount + * + * This requires that the user_msg have (if present): + * - badPasswordTime + * - badPwdCount + * - msDS-ResultantPSO + */ +int samdb_result_effective_badPwdCount(struct ldb_context *sam_ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *domain_dn, + const struct ldb_message *user_msg) +{ + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + int64_t lockOutObservationWindow = + samdb_result_msds_LockoutObservationWindow( + sam_ldb, mem_ctx, domain_dn, user_msg); + return dsdb_effective_badPwdCount(user_msg, lockOutObservationWindow, now); +} + +/* + * Returns the lockoutThreshold that applies. If a PSO is specified, then that + * setting is used over the domain defaults + */ +static int64_t get_lockout_threshold(struct ldb_message *domain_msg, + struct ldb_message *pso_msg) +{ + if (pso_msg != NULL) { + return ldb_msg_find_attr_as_int(pso_msg, + "msDS-LockoutThreshold", 0); + } else { + return ldb_msg_find_attr_as_int(domain_msg, + "lockoutThreshold", 0); + } +} + +/* + * Returns the lockOutObservationWindow that applies. If a PSO is specified, + * then that setting is used over the domain defaults + */ +static int64_t get_lockout_observation_window(struct ldb_message *domain_msg, + struct ldb_message *pso_msg) +{ + if (pso_msg != NULL) { + return ldb_msg_find_attr_as_int64(pso_msg, + "msDS-LockoutObservationWindow", + DEFAULT_OBSERVATION_WINDOW); + } else { + return ldb_msg_find_attr_as_int64(domain_msg, + "lockOutObservationWindow", + DEFAULT_OBSERVATION_WINDOW); + } +} + +/* + * Prepare an update to the badPwdCount and associated attributes. + * + * This requires that the user_msg have (if present): + * - objectSid + * - badPasswordTime + * - badPwdCount + * + * This also requires that the domain_msg have (if present): + * - pwdProperties + * - lockoutThreshold + * - lockOutObservationWindow + * + * This also requires that the pso_msg have (if present): + * - msDS-LockoutThreshold + * - msDS-LockoutObservationWindow + */ +NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct ldb_message *user_msg, + struct ldb_message *domain_msg, + struct ldb_message *pso_msg, + struct ldb_message **_mod_msg) +{ + int ret, badPwdCount; + unsigned int i; + int64_t lockoutThreshold, lockOutObservationWindow; + struct dom_sid *sid; + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + NTSTATUS status; + uint32_t pwdProperties, rid = 0; + struct ldb_message *mod_msg; + + sid = samdb_result_dom_sid(mem_ctx, user_msg, "objectSid"); + + pwdProperties = ldb_msg_find_attr_as_uint(domain_msg, + "pwdProperties", -1); + if (sid && !(pwdProperties & DOMAIN_PASSWORD_LOCKOUT_ADMINS)) { + status = dom_sid_split_rid(NULL, sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + /* + * This can't happen anyway, but always try + * and update the badPwdCount on failure + */ + rid = 0; + } + } + TALLOC_FREE(sid); + + /* + * Work out if we are doing password lockout on the domain. + * Also, the built in administrator account is exempt: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa375371%28v=vs.85%29.aspx + */ + lockoutThreshold = get_lockout_threshold(domain_msg, pso_msg); + if (lockoutThreshold == 0 || (rid == DOMAIN_RID_ADMINISTRATOR)) { + DEBUG(5, ("Not updating badPwdCount on %s after wrong password\n", + ldb_dn_get_linearized(user_msg->dn))); + return NT_STATUS_OK; + } + + mod_msg = ldb_msg_new(mem_ctx); + if (mod_msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + mod_msg->dn = ldb_dn_copy(mod_msg, user_msg->dn); + if (mod_msg->dn == NULL) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + + lockOutObservationWindow = get_lockout_observation_window(domain_msg, + pso_msg); + + badPwdCount = dsdb_effective_badPwdCount(user_msg, lockOutObservationWindow, now); + + badPwdCount++; + + ret = samdb_msg_add_int(sam_ctx, mod_msg, mod_msg, "badPwdCount", badPwdCount); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + ret = samdb_msg_add_int64(sam_ctx, mod_msg, mod_msg, "badPasswordTime", now); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + + if (badPwdCount >= lockoutThreshold) { + ret = samdb_msg_add_int64(sam_ctx, mod_msg, mod_msg, "lockoutTime", now); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mod_msg); + return NT_STATUS_NO_MEMORY; + } + DEBUGC( DBGC_AUTH, 1, ("Locked out user %s after %d wrong passwords\n", + ldb_dn_get_linearized(user_msg->dn), badPwdCount)); + } else { + DEBUGC( DBGC_AUTH, 5, ("Updated badPwdCount on %s after %d wrong passwords\n", + ldb_dn_get_linearized(user_msg->dn), badPwdCount)); + } + + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ + for (i=0; i< mod_msg->num_elements; i++) { + mod_msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + *_mod_msg = mod_msg; + return NT_STATUS_OK; +} + +/** + * Sets defaults for a User object + * List of default attributes set: + * accountExpires, badPasswordTime, badPwdCount, + * codePage, countryCode, lastLogoff, lastLogon + * logonCount, pwdLastSet + */ +int dsdb_user_obj_set_defaults(struct ldb_context *ldb, + struct ldb_message *usr_obj, + struct ldb_request *req) +{ + size_t i; + int ret; + const struct attribute_values { + const char *name; + const char *value; + const char *add_value; + const char *mod_value; + const char *control; + unsigned add_flags; + unsigned mod_flags; + } map[] = { + { + .name = "accountExpires", + .add_value = "9223372036854775807", + .mod_value = "0", + }, + { + .name = "badPasswordTime", + .value = "0" + }, + { + .name = "badPwdCount", + .value = "0" + }, + { + .name = "codePage", + .value = "0" + }, + { + .name = "countryCode", + .value = "0" + }, + { + .name = "lastLogoff", + .value = "0" + }, + { + .name = "lastLogon", + .value = "0" + }, + { + .name = "logonCount", + .value = "0" + }, + { + .name = "logonHours", + .add_flags = DSDB_FLAG_INTERNAL_FORCE_META_DATA, + }, + { + .name = "pwdLastSet", + .value = "0", + .control = DSDB_CONTROL_PASSWORD_DEFAULT_LAST_SET_OID, + }, + { + .name = "adminCount", + .mod_value = "0", + }, + { + .name = "operatorCount", + .mod_value = "0", + }, + }; + + for (i = 0; i < ARRAY_SIZE(map); i++) { + bool added = false; + const char *value = NULL; + unsigned flags = 0; + + if (req != NULL && req->operation == LDB_ADD) { + value = map[i].add_value; + flags = map[i].add_flags; + } else { + value = map[i].mod_value; + flags = map[i].mod_flags; + } + + if (value == NULL) { + value = map[i].value; + } + + if (value != NULL) { + flags |= LDB_FLAG_MOD_ADD; + } + + if (flags == 0) { + continue; + } + + ret = samdb_find_or_add_attribute_ex(ldb, usr_obj, + map[i].name, + value, flags, + &added); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (req != NULL && added && map[i].control != NULL) { + ret = ldb_request_add_control(req, + map[i].control, + false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + + return LDB_SUCCESS; +} + +/** + * Sets 'sAMAccountType on user object based on userAccountControl. + * This function is used in processing both 'add' and 'modify' requests. + * @param ldb Current ldb_context + * @param usr_obj ldb_message representing User object + * @param user_account_control Value for userAccountControl flags + * @param account_type_p Optional pointer to account_type to return + * @return LDB_SUCCESS or LDB_ERR* code on failure + */ +int dsdb_user_obj_set_account_type(struct ldb_context *ldb, struct ldb_message *usr_obj, + uint32_t user_account_control, uint32_t *account_type_p) +{ + int ret; + uint32_t account_type; + + account_type = ds_uf2atype(user_account_control); + if (account_type == 0) { + ldb_set_errstring(ldb, "dsdb: Unrecognized account type!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + ret = samdb_msg_add_uint_flags(ldb, usr_obj, usr_obj, + "sAMAccountType", + account_type, + LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (account_type_p) { + *account_type_p = account_type; + } + + return LDB_SUCCESS; +} + +/** + * Determine and set primaryGroupID based on userAccountControl value. + * This function is used in processing both 'add' and 'modify' requests. + * @param ldb Current ldb_context + * @param usr_obj ldb_message representing User object + * @param user_account_control Value for userAccountControl flags + * @param group_rid_p Optional pointer to group RID to return + * @return LDB_SUCCESS or LDB_ERR* code on failure + */ +int dsdb_user_obj_set_primary_group_id(struct ldb_context *ldb, struct ldb_message *usr_obj, + uint32_t user_account_control, uint32_t *group_rid_p) +{ + int ret; + uint32_t rid; + + rid = ds_uf2prim_group_rid(user_account_control); + + ret = samdb_msg_add_uint_flags(ldb, usr_obj, usr_obj, + "primaryGroupID", rid, + LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (group_rid_p) { + *group_rid_p = rid; + } + + return LDB_SUCCESS; +} + +/** + * Returns True if the source and target DNs both have the same naming context, + * i.e. they're both in the same partition. + */ +bool dsdb_objects_have_same_nc(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *source_dn, + struct ldb_dn *target_dn) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *source_nc = NULL; + struct ldb_dn *target_nc = NULL; + int ret; + bool same_nc = true; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + ret = dsdb_find_nc_root(ldb, tmp_ctx, source_dn, &source_nc); + /* fix clang warning */ + if (source_nc == NULL) { + ret = LDB_ERR_OTHER; + } + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to find base DN for source %s: %s\n", + ldb_dn_get_linearized(source_dn), ldb_errstring(ldb)); + talloc_free(tmp_ctx); + return true; + } + + ret = dsdb_find_nc_root(ldb, tmp_ctx, target_dn, &target_nc); + /* fix clang warning */ + if (target_nc == NULL) { + ret = LDB_ERR_OTHER; + } + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to find base DN for target %s: %s\n", + ldb_dn_get_linearized(target_dn), ldb_errstring(ldb)); + talloc_free(tmp_ctx); + return true; + } + + same_nc = (ldb_dn_compare(source_nc, target_nc) == 0); + + talloc_free(tmp_ctx); + + return same_nc; +} +/* + * Context for dsdb_count_domain_callback + */ +struct dsdb_count_domain_context { + /* + * Number of matching records + */ + size_t count; + /* + * sid of the domain that the records must belong to. + * if NULL records can belong to any domain. + */ + struct dom_sid *dom_sid; +}; + +/* + * @brief ldb async callback for dsdb_domain_count. + * + * count the number of records in the database matching an LDAP query, + * optionally filtering for domain membership. + * + * @param [in,out] req the ldb request being processed + * req->context contains: + * count The number of matching records + * dom_sid The domain sid, if present records must belong + * to the domain to be counted. + *@param [in,out] ares The query result. + * + * @return an LDB error code + * + */ +static int dsdb_count_domain_callback( + struct ldb_request *req, + struct ldb_reply *ares) +{ + + if (ares == NULL) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + int error = ares->error; + TALLOC_FREE(ares); + return ldb_request_done(req, error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + struct dsdb_count_domain_context *context = NULL; + ssize_t ret; + bool in_domain; + struct dom_sid sid; + const struct ldb_val *v; + + context = req->context; + if (context->dom_sid == NULL) { + context->count++; + break; + } + + v = ldb_msg_find_ldb_val(ares->message, "objectSid"); + if (v == NULL) { + break; + } + + ret = sid_parse(v->data, v->length, &sid); + if (ret == -1) { + break; + } + + in_domain = dom_sid_in_domain(context->dom_sid, &sid); + if (!in_domain) { + break; + } + + context->count++; + break; + } + case LDB_REPLY_REFERRAL: + break; + + case LDB_REPLY_DONE: + TALLOC_FREE(ares); + return ldb_request_done(req, LDB_SUCCESS); + } + + TALLOC_FREE(ares); + + return LDB_SUCCESS; +} + +/* + * @brief Count the number of records matching a query. + * + * Count the number of entries in the database matching the supplied query, + * optionally filtering only those entries belonging to the supplied domain. + * + * @param ldb [in] Current ldb context + * @param count [out] Pointer to the count + * @param base [in] The base dn for the query + * @param dom_sid [in] The domain sid, if non NULL records that are not a member + * of the domain are ignored. + * @param scope [in] Search scope. + * @param exp_fmt [in] format string for the query. + * + * @return LDB_STATUS code. + */ +int PRINTF_ATTRIBUTE(6, 7) dsdb_domain_count( + struct ldb_context *ldb, + size_t *count, + struct ldb_dn *base, + struct dom_sid *dom_sid, + enum ldb_scope scope, + const char *exp_fmt, ...) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_request *req = NULL; + struct dsdb_count_domain_context *context = NULL; + char *expression = NULL; + const char *object_sid[] = {"objectSid", NULL}; + const char *none[] = {NULL}; + va_list ap; + int ret; + + *count = 0; + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + context = talloc_zero(tmp_ctx, struct dsdb_count_domain_context); + if (context == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + context->dom_sid = dom_sid; + + if (exp_fmt) { + va_start(ap, exp_fmt); + expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap); + va_end(ap); + + if (expression == NULL) { + TALLOC_FREE(context); + TALLOC_FREE(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + } + + ret = ldb_build_search_req( + &req, + ldb, + tmp_ctx, + base, + scope, + expression, + (dom_sid == NULL) ? none : object_sid, + NULL, + context, + dsdb_count_domain_callback, + NULL); + ldb_req_set_location(req, "dsdb_domain_count"); + + if (ret != LDB_SUCCESS) goto done; + + ret = ldb_request(ldb, req); + + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + if (ret == LDB_SUCCESS) { + *count = context->count; + } + } + + +done: + TALLOC_FREE(expression); + TALLOC_FREE(req); + TALLOC_FREE(context); + TALLOC_FREE(tmp_ctx); + + return ret; +} + +/* + * Returns 1 if 'sids' contains the Protected Users group SID for the domain, 0 + * if not. Returns a negative value on error. + */ +int dsdb_is_protected_user(struct ldb_context *ldb, + const struct auth_SidAttr *sids, + uint32_t num_sids) +{ + const struct dom_sid *domain_sid = NULL; + struct dom_sid protected_users_sid; + uint32_t i; + + domain_sid = samdb_domain_sid(ldb); + if (domain_sid == NULL) { + return -1; + } + + protected_users_sid = *domain_sid; + if (!sid_append_rid(&protected_users_sid, DOMAIN_RID_PROTECTED_USERS)) { + return -1; + } + + for (i = 0; i < num_sids; ++i) { + if (dom_sid_equal(&protected_users_sid, &sids[i].sid)) { + return 1; + } + } + + return 0; +} diff --git a/source4/dsdb/common/util.h b/source4/dsdb/common/util.h new file mode 100644 index 0000000..63cfd79 --- /dev/null +++ b/source4/dsdb/common/util.h @@ -0,0 +1,101 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009 + + 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_COMMON_UTIL_H__ +#define __DSDB_COMMON_UTIL_H__ + +/* + flags for dsdb_request_add_controls(). For the module functions, + the upper 16 bits are in dsdb/samdb/ldb_modules/util.h +*/ +#define DSDB_SEARCH_SEARCH_ALL_PARTITIONS 0x00001 +#define DSDB_SEARCH_SHOW_DELETED 0x00002 +#define DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT 0x00004 +#define DSDB_SEARCH_REVEAL_INTERNALS 0x00008 +#define DSDB_SEARCH_SHOW_EXTENDED_DN 0x00010 +#define DSDB_MODIFY_RELAX 0x00020 +#define DSDB_MODIFY_PERMISSIVE 0x00040 +#define DSDB_FLAG_AS_SYSTEM 0x00080 +#define DSDB_TREE_DELETE 0x00100 +#define DSDB_SEARCH_ONE_ONLY 0x00200 /* give an error unless 1 record */ +#define DSDB_SEARCH_SHOW_RECYCLED 0x00400 +#define DSDB_PROVISION 0x00800 +#define DSDB_BYPASS_PASSWORD_HASH 0x01000 +#define DSDB_SEARCH_NO_GLOBAL_CATALOG 0x02000 +#define DSDB_MODIFY_PARTIAL_REPLICA 0x04000 +#define DSDB_PASSWORD_BYPASS_LAST_SET 0x08000 +#define DSDB_REPLMD_VANISH_LINKS 0x10000 +#define DSDB_MARK_REQ_UNTRUSTED 0x20000 + +#define DSDB_SECRET_ATTRIBUTES_EX(sep) \ + "pekList" sep \ + "msDS-ExecuteScriptPassword" sep \ + "currentValue" sep \ + "dBCSPwd" sep \ + "initialAuthIncoming" sep \ + "initialAuthOutgoing" sep \ + "lmPwdHistory" sep \ + "ntPwdHistory" sep \ + "priorValue" sep \ + "supplementalCredentials" sep \ + "trustAuthIncoming" sep \ + "trustAuthOutgoing" sep \ + "unicodePwd" sep \ + "clearTextPassword" + +#define DSDB_SECRET_ATTRIBUTES_COMMA , +#define DSDB_SECRET_ATTRIBUTES DSDB_SECRET_ATTRIBUTES_EX(DSDB_SECRET_ATTRIBUTES_COMMA) + +#define DSDB_PASSWORD_ATTRIBUTES \ + "userPassword", \ + "clearTextPassword", \ + "unicodePwd", \ + "dBCSPwd" + +/* + * ldb opaque values used to pass the user session information to ldb modules + */ +#define DSDB_SESSION_INFO "sessionInfo" +#define DSDB_NETWORK_SESSION_INFO "networkSessionInfo" + +struct GUID; + +struct ldb_context; + +int dsdb_werror_at(struct ldb_context *ldb, int ldb_ecode, WERROR werr, + const char *location, const char *func, + const char *reason); + +#define dsdb_module_werror(module, ldb_ecode, werr, reason) \ + dsdb_werror_at(ldb_module_get_ctx(module), ldb_ecode, werr, \ + __location__, __func__, reason) + + +struct dsdb_ldb_dn_list_node { + struct dsdb_ldb_dn_list_node *prev, *next; + + /* the dn of the partition */ + struct ldb_dn *dn; +}; + + + +#endif /* __DSDB_COMMON_UTIL_H__ */ diff --git a/source4/dsdb/common/util_groups.c b/source4/dsdb/common/util_groups.c new file mode 100644 index 0000000..27a8735 --- /dev/null +++ b/source4/dsdb/common/util_groups.c @@ -0,0 +1,200 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010 + Copyright (C) Stefan Metzmacher 2005 + Copyright (C) Matthias Dieter Wallnöfer 2009 + + 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 "auth/auth.h" +#include <ldb.h> +#include "dsdb/samdb/samdb.h" +#include "libcli/security/security.h" +#include "dsdb/common/util.h" + +/* + * This function generates the transitive closure of a given SAM object "dn_val" + * (it basically expands nested memberships). + * If the object isn't located in the "res_sids" structure yet and the + * "only_childs" flag is false, we add it to "res_sids". + * Then we've always to consider the "memberOf" attributes. We invoke the + * function recursively on each of it with the "only_childs" flag set to + * "false". + * The "only_childs" flag is particularly useful if you have a user object and + * want to include all it's groups (referenced with "memberOf") but not itself + * or considering if that object matches the filter. + * + * At the beginning "res_sids" should reference to a NULL pointer. + */ +NTSTATUS dsdb_expand_nested_groups(struct ldb_context *sam_ctx, + struct ldb_val *dn_val, const bool only_childs, const char *filter, + TALLOC_CTX *res_sids_ctx, struct auth_SidAttr **res_sids, + uint32_t *num_res_sids) +{ + static const char * const attrs[] = { "groupType", "memberOf", NULL }; + unsigned int i; + int ret; + struct ldb_dn *dn; + struct dom_sid sid; + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + NTSTATUS status; + const struct ldb_message_element *el; + + if (*res_sids == NULL) { + *num_res_sids = 0; + } + + if (!sam_ctx) { + DEBUG(0, ("No SAM available, cannot determine local groups\n")); + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + tmp_ctx = talloc_new(res_sids_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dn = ldb_dn_from_ldb_val(tmp_ctx, sam_ctx, dn_val); + if (dn == NULL) { + talloc_free(tmp_ctx); + DEBUG(0, (__location__ ": we failed parsing DN %.*s, so we cannot calculate the group token\n", + (int)dn_val->length, dn_val->data)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = dsdb_get_extended_dn_sid(dn, &sid, "SID"); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + /* If we fail finding a SID then this is no error since it could + * be a non SAM object - e.g. a group with object class + * "groupOfNames" */ + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, (__location__ ": when parsing DN '%s' we failed to parse it's SID component, so we cannot calculate the group token: %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, dn, 1), + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (!ldb_dn_minimise(dn)) { + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (only_childs) { + ret = dsdb_search_dn(sam_ctx, tmp_ctx, &res, dn, attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + } else { + ret = dsdb_search(sam_ctx, tmp_ctx, &res, dn, LDB_SCOPE_BASE, + attrs, DSDB_SEARCH_SHOW_EXTENDED_DN, "%s", + filter); + } + + /* + * We have the problem with the caller creating a <SID=S-....> + * DN for ForeignSecurityPrincipals as they also have + * duplicate objects with the SAME SID under CN=Configuration. + * This causes a SID= DN to fail with NO_SUCH_OBJECT on Samba + * and on Windows. So, we allow this to fail, and + * double-check if we can find it with a search in the main + * domain partition. + */ + if (ret == LDB_ERR_NO_SUCH_OBJECT && only_childs) { + char *sid_string = dom_sid_string(tmp_ctx, + &sid); + if (!sid_string) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + ret = dsdb_search(sam_ctx, tmp_ctx, &res, + ldb_get_default_basedn(sam_ctx), + LDB_SCOPE_SUBTREE, + attrs, DSDB_SEARCH_SHOW_EXTENDED_DN, + "(&(objectClass=foreignSecurityPrincipal)(objectSID=%s))", + sid_string); + } + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + if (ret != LDB_SUCCESS) { + DEBUG(1, (__location__ ": dsdb_search for %s failed: %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, dn, 1), + ldb_errstring(sam_ctx))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* We may get back 0 results, if the SID didn't match the filter - such as it wasn't a domain group, for example */ + if (res->count != 1) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + /* We only apply this test once we know the SID matches the filter */ + if (!only_childs) { + unsigned group_type; + uint32_t sid_attrs; + bool already_there; + + sid_attrs = SE_GROUP_DEFAULT_FLAGS; + group_type = ldb_msg_find_attr_as_uint(res->msgs[0], "groupType", 0); + if (group_type & GROUP_TYPE_RESOURCE_GROUP) { + sid_attrs |= SE_GROUP_RESOURCE; + } + + /* This is an O(n^2) linear search */ + already_there = sids_contains_sid_attrs(*res_sids, *num_res_sids, + &sid, sid_attrs); + if (already_there) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + *res_sids = talloc_realloc(res_sids_ctx, *res_sids, + struct auth_SidAttr, *num_res_sids + 1); + if (*res_sids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + (*res_sids)[*num_res_sids].sid = sid; + (*res_sids)[*num_res_sids].attrs = sid_attrs; + + ++(*num_res_sids); + } + + el = ldb_msg_find_element(res->msgs[0], "memberOf"); + + for (i = 0; el && i < el->num_values; i++) { + status = dsdb_expand_nested_groups(sam_ctx, &el->values[i], + false, filter, res_sids_ctx, res_sids, num_res_sids); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} diff --git a/source4/dsdb/common/util_links.c b/source4/dsdb/common/util_links.c new file mode 100644 index 0000000..d41d1f2 --- /dev/null +++ b/source4/dsdb/common/util_links.c @@ -0,0 +1,229 @@ +/* + Unix SMB/CIFS implementation. + + Helpers to search for links in the DB + + Copyright (C) Catalyst.Net Ltd 2017 + + 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/util/binsearch.h" +#include "librpc/gen_ndr/ndr_misc.h" + +/* + * We choose, as the sort order, the same order as is used in DRS replication, + * which is the memcmp() order of the NDR GUID, not that obtained from + * GUID_compare(). + * + * This means that sorted links will be in the same order as a new DC would + * see them. + */ +int ndr_guid_compare(const struct GUID *guid1, const struct GUID *guid2) +{ + uint8_t v1_data[16] = { 0 }; + struct ldb_val v1 = data_blob_const(v1_data, sizeof(v1_data)); + uint8_t v2_data[16]; + struct ldb_val v2 = data_blob_const(v2_data, sizeof(v2_data)); + + /* This can't fail */ + ndr_push_struct_into_fixed_blob(&v1, guid1, + (ndr_push_flags_fn_t)ndr_push_GUID); + /* This can't fail */ + ndr_push_struct_into_fixed_blob(&v2, guid2, + (ndr_push_flags_fn_t)ndr_push_GUID); + return data_blob_cmp(&v1, &v2); +} + + +static int la_guid_compare_with_trusted_dn(struct compare_ctx *ctx, + struct parsed_dn *p) +{ + int cmp = 0; + /* + * This works like a standard compare function in its return values, + * but has an extra trick to deal with errors: zero is returned and + * ctx->err is set to the ldb error code. + * + * That is, if (as is expected in most cases) you get a non-zero + * result, you don't need to check for errors. + * + * We assume the second argument refers to a DN is from the database + * and has a GUID -- but this GUID might not have been parsed out yet. + */ + if (p->dsdb_dn == NULL) { + int ret = really_parse_trusted_dn(ctx->mem_ctx, ctx->ldb, p, + ctx->ldap_oid); + if (ret != LDB_SUCCESS) { + ctx->err = ret; + return 0; + } + } + cmp = ndr_guid_compare(ctx->guid, &p->guid); + if (cmp == 0 && ctx->compare_extra_part) { + if (ctx->partial_extra_part_length != 0) { + /* Allow a prefix match on the blob. */ + return memcmp(ctx->extra_part.data, + p->dsdb_dn->extra_part.data, + MIN(ctx->partial_extra_part_length, + p->dsdb_dn->extra_part.length)); + } else { + return data_blob_cmp(&ctx->extra_part, + &p->dsdb_dn->extra_part); + } + } + + return cmp; +} + +/* When a parsed_dn comes from the database, sometimes it is not really parsed. */ + +int really_parse_trusted_dn(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, + struct parsed_dn *pdn, const char *ldap_oid) +{ + NTSTATUS status; + struct dsdb_dn *dsdb_dn = dsdb_dn_parse_trusted(mem_ctx, ldb, pdn->v, + ldap_oid); + if (dsdb_dn == NULL) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + + status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &pdn->guid, "GUID"); + if (!NT_STATUS_IS_OK(status)) { + return LDB_ERR_OPERATIONS_ERROR; + } + pdn->dsdb_dn = dsdb_dn; + return LDB_SUCCESS; +} + + +int get_parsed_dns_trusted(TALLOC_CTX *mem_ctx, struct ldb_message_element *el, + struct parsed_dn **pdn) +{ + /* Here we get a list of 'struct parsed_dns' without the parsing */ + unsigned int i; + *pdn = talloc_zero_array(mem_ctx, struct parsed_dn, + el->num_values); + if (!*pdn) { + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i = 0; i < el->num_values; i++) { + (*pdn)[i].v = &el->values[i]; + } + + return LDB_SUCCESS; +} + + +int parsed_dn_find(struct ldb_context *ldb, struct parsed_dn *pdn, + unsigned int count, + const struct GUID *guid, + struct ldb_dn *target_dn, + DATA_BLOB extra_part, + size_t partial_extra_part_length, + struct parsed_dn **exact, + struct parsed_dn **next, + const char *ldap_oid, + bool compare_extra_part) +{ + unsigned int i; + struct compare_ctx ctx; + if (pdn == NULL) { + *exact = NULL; + *next = NULL; + return LDB_SUCCESS; + } + + if (unlikely(GUID_all_zero(guid))) { + /* + * When updating a link using DRS, we sometimes get a NULL + * GUID when a forward link has been deleted and its GUID has + * for some reason been forgotten. The best we can do is try + * and match by DN via a linear search. Note that this + * probably only happens in the ADD case, in which we only + * allow modification of link if it is already deleted, so + * this seems very close to an elaborate NO-OP, but we are not + * quite prepared to declare it so. + * + * If the DN is not in our list, we have to add it to the + * beginning of the list, where it would naturally sort. + */ + struct parsed_dn *p; + if (target_dn == NULL) { + /* We don't know the target DN, so we can't search for DN */ + DEBUG(1, ("parsed_dn_find has a NULL GUID for a linked " + "attribute but we don't have a DN to compare " + "it with\n")); + return LDB_ERR_OPERATIONS_ERROR; + } + *exact = NULL; + *next = NULL; + + DEBUG(3, ("parsed_dn_find has a NULL GUID for a link to DN " + "%s; searching through links for it\n", + ldb_dn_get_linearized(target_dn))); + + for (i = 0; i < count; i++) { + int cmp; + p = &pdn[i]; + if (p->dsdb_dn == NULL) { + int ret = really_parse_trusted_dn(pdn, ldb, p, ldap_oid); + if (ret != LDB_SUCCESS) { + return LDB_ERR_OPERATIONS_ERROR; + } + } + + cmp = ldb_dn_compare(p->dsdb_dn->dn, target_dn); + if (cmp == 0) { + *exact = p; + return LDB_SUCCESS; + } + } + /* + * Here we have a null guid which doesn't match any existing + * link. This is a bit unexpected because null guids occur + * when a forward link has been deleted and we are replicating + * that deletion. + * + * The best thing to do is weep into the logs and add the + * offending link to the beginning of the list which is + * at least the correct sort position. + */ + DEBUG(1, ("parsed_dn_find has been given a NULL GUID for a " + "link to unknown DN %s\n", + ldb_dn_get_linearized(target_dn))); + *next = pdn; + return LDB_SUCCESS; + } + + ctx.guid = guid; + ctx.ldb = ldb; + ctx.mem_ctx = pdn; + ctx.ldap_oid = ldap_oid; + ctx.extra_part = extra_part; + ctx.partial_extra_part_length = partial_extra_part_length; + ctx.compare_extra_part = compare_extra_part; + ctx.err = 0; + + BINARY_ARRAY_SEARCH_GTE(pdn, count, &ctx, la_guid_compare_with_trusted_dn, + *exact, *next); + + if (ctx.err != 0) { + return ctx.err; + } + return LDB_SUCCESS; +} diff --git a/source4/dsdb/common/util_links.h b/source4/dsdb/common/util_links.h new file mode 100644 index 0000000..e6dc41b --- /dev/null +++ b/source4/dsdb/common/util_links.h @@ -0,0 +1,48 @@ +/* + Unix SMB/CIFS implementation. + + Helpers to search for links in the DB + + Copyright (C) Catalyst.Net Ltd 2017 + + 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_COMMON_UTIL_LINKS_H__ +#define __DSDB_COMMON_UTIL_LINKS_H__ + +struct compare_ctx { + const struct GUID *guid; + struct ldb_context *ldb; + TALLOC_CTX *mem_ctx; + const char *ldap_oid; + int err; + const struct GUID *invocation_id; + DATA_BLOB extra_part; + size_t partial_extra_part_length; + bool compare_extra_part; +}; + +struct parsed_dn { + struct dsdb_dn *dsdb_dn; + struct GUID guid; + struct ldb_val *v; +}; + + +int get_parsed_dns_trusted(TALLOC_CTX *mem_ctx, + struct ldb_message_element *el, + struct parsed_dn **pdn); + +#endif /* __DSDB_COMMON_UTIL_LINKS_H__ */ diff --git a/source4/dsdb/common/util_samr.c b/source4/dsdb/common/util_samr.c new file mode 100644 index 0000000..0a48fcf --- /dev/null +++ b/source4/dsdb/common/util_samr.c @@ -0,0 +1,593 @@ +/* + Unix SMB/CIFS implementation. + + Helpers to add users and groups to the DB + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Volker Lendecke 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2010 + Copyright (C) Matthias Dieter Wallnöfer 2009 + + 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 "dsdb/common/util.h" +#include "../libds/common/flags.h" +#include "libcli/security/security.h" + +#include "libds/common/flag_mapping.h" + +/* Add a user, SAMR style, including the correct transaction + * semantics. Used by the SAMR server and by pdb_samba4 */ +NTSTATUS dsdb_add_user(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *account_name, + uint32_t acct_flags, + const struct dom_sid *forced_sid, + struct dom_sid **sid, + struct ldb_dn **dn) +{ + const char *name; + struct ldb_message *msg; + int ret; + const char *container, *obj_class=NULL; + char *cn_name; + size_t cn_name_len; + + const char *attrs[] = { + "objectSid", + "userAccountControl", + NULL + }; + + uint32_t user_account_control; + struct ldb_dn *account_dn; + struct dom_sid *account_sid; + + const char *account_name_encoded = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + account_name_encoded = ldb_binary_encode_string(tmp_ctx, account_name); + if (account_name_encoded == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* + * Start a transaction, so we can query and do a subsequent atomic + * modify + */ + + ret = ldb_transaction_start(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to start a transaction for user creation: %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_LOCK_NOT_GRANTED; + } + + /* check if the user already exists */ + name = samdb_search_string(ldb, tmp_ctx, NULL, + "sAMAccountName", + "(&(sAMAccountName=%s)(objectclass=user))", + account_name_encoded); + if (name != NULL) { + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_USER_EXISTS; + } + + cn_name = talloc_strdup(tmp_ctx, account_name); + if (!cn_name) { + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + cn_name_len = strlen(cn_name); + if (cn_name_len < 1) { + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* This must be one of these values *only* */ + if (acct_flags == ACB_NORMAL) { + container = "CN=Users"; + obj_class = "user"; + user_account_control = UF_NORMAL_ACCOUNT; + } else if (acct_flags == ACB_WSTRUST) { + if (cn_name[cn_name_len - 1] != '$') { + ldb_transaction_cancel(ldb); + return NT_STATUS_FOOBAR; + } + cn_name[cn_name_len - 1] = '\0'; + container = "CN=Computers"; + obj_class = "computer"; + user_account_control = UF_WORKSTATION_TRUST_ACCOUNT; + + } else if (acct_flags == ACB_SVRTRUST) { + if (cn_name[cn_name_len - 1] != '$') { + ldb_transaction_cancel(ldb); + return NT_STATUS_FOOBAR; + } + cn_name[cn_name_len - 1] = '\0'; + container = "OU=Domain Controllers"; + obj_class = "computer"; + user_account_control = UF_SERVER_TRUST_ACCOUNT; + } else if (acct_flags == ACB_DOMTRUST) { + DEBUG(3, ("Invalid account flags specified: cannot create domain trusts via this interface (must use LSA CreateTrustedDomain calls\n")); + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } else { + DEBUG(3, ("Invalid account flags specified 0x%08X, must be exactly one of \n" + "ACB_NORMAL (0x%08X) ACB_WSTRUST (0x%08X) or ACB_SVRTRUST (0x%08X)\n", + acct_flags, + ACB_NORMAL, ACB_WSTRUST, ACB_SVRTRUST)); + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + user_account_control |= UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD; + + /* add core elements to the ldb_message for the user */ + msg->dn = ldb_dn_copy(msg, ldb_get_default_basedn(ldb)); + if ( ! ldb_dn_add_child_fmt(msg->dn, "CN=%s,%s", cn_name, container)) { + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_FOOBAR; + } + + ret = ldb_msg_add_string(msg, "sAMAccountName", account_name); + if (ret != LDB_SUCCESS) { + goto failed; + } + ret = ldb_msg_add_string(msg, "objectClass", obj_class); + if (ret != LDB_SUCCESS) { + goto failed; + } + ret = samdb_msg_add_uint(ldb, tmp_ctx, msg, + "userAccountControl", + user_account_control); + if (ret != LDB_SUCCESS) { + goto failed; + } + + /* This is only here for migrations using pdb_samba4, the + * caller and the samldb are responsible for ensuring it makes + * sense */ + if (forced_sid) { + ret = samdb_msg_add_dom_sid(ldb, msg, msg, "objectSID", forced_sid); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + } + + /* create the user */ + ret = ldb_add(ldb, msg); + switch (ret) { + case LDB_SUCCESS: + break; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + ldb_transaction_cancel(ldb); + DEBUG(0,("Failed to create user record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_USER_EXISTS; + case LDB_ERR_UNWILLING_TO_PERFORM: + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + ldb_transaction_cancel(ldb); + DEBUG(0,("Failed to create user record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + default: + ldb_transaction_cancel(ldb); + DEBUG(0,("Failed to create user record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + account_dn = msg->dn; + + /* retrieve the sid and account control bits for the user just created */ + ret = dsdb_search_one(ldb, tmp_ctx, &msg, + account_dn, LDB_SCOPE_BASE, attrs, 0, NULL); + + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(ldb); + DEBUG(0,("Can't locate the account we just created %s: %s\n", + ldb_dn_get_linearized(account_dn), ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid"); + if (account_sid == NULL) { + ldb_transaction_cancel(ldb); + DEBUG(0,("Apparently we failed to get the objectSid of the just created account record %s\n", + ldb_dn_get_linearized(msg->dn))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + ret = ldb_transaction_commit(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to commit transaction to add and modify account record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + *dn = talloc_steal(mem_ctx, account_dn); + if (sid) { + *sid = talloc_steal(mem_ctx, account_sid); + } + talloc_free(tmp_ctx); + return NT_STATUS_OK; + + failed: + ldb_transaction_cancel(ldb); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; +} + +/* + called by samr_CreateDomainGroup and pdb_samba4 +*/ +NTSTATUS dsdb_add_domain_group(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *groupname, + struct dom_sid **sid, + struct ldb_dn **dn) +{ + const char *name; + struct ldb_message *msg; + struct dom_sid *group_sid; + const char *groupname_encoded = NULL; + int ret; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + groupname_encoded = ldb_binary_encode_string(tmp_ctx, groupname); + if (groupname_encoded == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* check if the group already exists */ + name = samdb_search_string(ldb, tmp_ctx, NULL, + "sAMAccountName", + "(&(sAMAccountName=%s)(objectclass=group))", + groupname_encoded); + if (name != NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_GROUP_EXISTS; + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* add core elements to the ldb_message for the user */ + msg->dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(ldb)); + ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=Users", groupname); + if (!msg->dn) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + ldb_msg_add_string(msg, "sAMAccountName", groupname); + ldb_msg_add_string(msg, "objectClass", "group"); + + /* create the group */ + ret = ldb_add(ldb, msg); + switch (ret) { + case LDB_SUCCESS: + break; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + DEBUG(0,("Failed to create group record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_GROUP_EXISTS; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + DEBUG(0,("Failed to create group record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + default: + DEBUG(0,("Failed to create group record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* retrieve the sid for the group just created */ + group_sid = samdb_search_dom_sid(ldb, tmp_ctx, + msg->dn, "objectSid", NULL); + if (group_sid == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + *dn = talloc_steal(mem_ctx, msg->dn); + *sid = talloc_steal(mem_ctx, group_sid); + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_add_domain_alias(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *alias_name, + struct dom_sid **sid, + struct ldb_dn **dn) +{ + const char *name; + struct ldb_message *msg; + struct dom_sid *alias_sid; + const char *alias_name_encoded = NULL; + int ret; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + alias_name_encoded = ldb_binary_encode_string(tmp_ctx, alias_name); + if (alias_name_encoded == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (ldb_transaction_start(ldb) != LDB_SUCCESS) { + DEBUG(0, ("Failed to start transaction in dsdb_add_domain_alias(): %s\n", ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + /* Check if alias already exists */ + name = samdb_search_string(ldb, tmp_ctx, NULL, + "sAMAccountName", + "(sAMAccountName=%s)(objectclass=group))", + alias_name_encoded); + + if (name != NULL) { + talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); + return NT_STATUS_ALIAS_EXISTS; + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); + return NT_STATUS_NO_MEMORY; + } + + /* add core elements to the ldb_message for the alias */ + msg->dn = ldb_dn_copy(mem_ctx, ldb_get_default_basedn(ldb)); + ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=Users", alias_name); + if (!msg->dn) { + talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); + return NT_STATUS_NO_MEMORY; + } + + ldb_msg_add_string(msg, "sAMAccountName", alias_name); + ldb_msg_add_string(msg, "objectClass", "group"); + samdb_msg_add_int(ldb, mem_ctx, msg, "groupType", GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + + /* create the alias */ + ret = ldb_add(ldb, msg); + switch (ret) { + case LDB_SUCCESS: + break; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); + return NT_STATUS_ALIAS_EXISTS; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); + return NT_STATUS_ACCESS_DENIED; + default: + DEBUG(0,("Failed to create alias record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* retrieve the sid for the alias just created */ + alias_sid = samdb_search_dom_sid(ldb, tmp_ctx, + msg->dn, "objectSid", NULL); + + if (ldb_transaction_commit(ldb) != LDB_SUCCESS) { + DEBUG(0, ("Failed to commit transaction in dsdb_add_domain_alias(): %s\n", + ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + *dn = talloc_steal(mem_ctx, msg->dn); + *sid = talloc_steal(mem_ctx, alias_sid); + talloc_free(tmp_ctx); + + + return NT_STATUS_OK; +} + +/* Return the members of this group (which may be a domain group or an alias) */ +NTSTATUS dsdb_enum_group_mem(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, + struct dom_sid **members_out, + unsigned int *pnum_members) +{ + struct ldb_message *msg; + unsigned int i, j; + int ret; + struct dom_sid *members; + struct ldb_message_element *member_el; + const char *attrs[] = { "member", NULL }; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + ret = dsdb_search_one(ldb, tmp_ctx, &msg, dn, LDB_SCOPE_BASE, attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN, NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + talloc_free(tmp_ctx); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + if (ret != LDB_SUCCESS) { + DEBUG(1, ("dsdb_enum_group_mem: dsdb_search for %s failed: %s\n", + ldb_dn_get_linearized(dn), ldb_errstring(ldb))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + member_el = ldb_msg_find_element(msg, "member"); + if (!member_el) { + *members_out = NULL; + *pnum_members = 0; + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + members = talloc_array(mem_ctx, struct dom_sid, member_el->num_values); + if (members == NULL) { + return NT_STATUS_NO_MEMORY; + } + + j = 0; + for (i=0; i <member_el->num_values; i++) { + struct ldb_dn *member_dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, + &member_el->values[i]); + if (!member_dn || !ldb_dn_validate(member_dn)) { + DEBUG(1, ("Could not parse %*.*s as a DN\n", + (int)member_el->values[i].length, + (int)member_el->values[i].length, + (const char *)member_el->values[i].data)); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = dsdb_get_extended_dn_sid(member_dn, &members[j], + "SID"); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + /* If we fail finding a SID then this is no error since + * it could be a non SAM object - e.g. a contact */ + continue; + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("When parsing DN '%s' we failed to parse it's SID component, so we cannot fetch the membership: %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, member_dn, 1), + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + ++j; + } + + *members_out = members; + *pnum_members = j; + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_lookup_rids(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + unsigned int num_rids, + uint32_t *rids, + const char **names, + enum lsa_SidType *lsa_attrs) +{ + const char *attrs[] = { "sAMAccountType", "sAMAccountName", NULL }; + unsigned int i, num_mapped; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + num_mapped = 0; + + for (i=0; i<num_rids; i++) { + struct ldb_message *msg; + struct ldb_dn *dn; + uint32_t attr; + int rc; + + lsa_attrs[i] = SID_NAME_UNKNOWN; + + dn = ldb_dn_new_fmt(tmp_ctx, ldb, "<SID=%s>", + dom_sid_string(tmp_ctx, + dom_sid_add_rid(tmp_ctx, domain_sid, + rids[i]))); + if (dn == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + rc = dsdb_search_one(ldb, tmp_ctx, &msg, dn, LDB_SCOPE_BASE, attrs, 0, "samAccountName=*"); + if (rc == LDB_ERR_NO_SUCH_OBJECT) { + continue; + } else if (rc != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + names[i] = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + if (names[i] == NULL) { + DEBUG(10, ("no samAccountName\n")); + continue; + } + talloc_steal(names, names[i]); + attr = ldb_msg_find_attr_as_uint(msg, "samAccountType", 0); + lsa_attrs[i] = ds_atype_map(attr); + if (lsa_attrs[i] == SID_NAME_UNKNOWN) { + continue; + } + num_mapped += 1; + } + talloc_free(tmp_ctx); + + if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (num_mapped < num_rids) { + return STATUS_SOME_UNMAPPED; + } + return NT_STATUS_OK; +} + diff --git a/source4/dsdb/common/util_trusts.c b/source4/dsdb/common/util_trusts.c new file mode 100644 index 0000000..5003e74 --- /dev/null +++ b/source4/dsdb/common/util_trusts.c @@ -0,0 +1,3443 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2015 + + 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.h" +#include "../lib/util/util_ldb.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "../libds/common/flags.h" +#include "dsdb/common/proto.h" +#include "param/param.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "lib/util/tsort.h" +#include "dsdb/common/util.h" +#include "libds/common/flag_mapping.h" +#include "../lib/util/dlinklist.h" +#include "lib/crypto/md4.h" +#include "libcli/ldap/ldap_ndr.h" + +#undef strcasecmp + +NTSTATUS dsdb_trust_forest_info_from_lsa(TALLOC_CTX *mem_ctx, + const struct lsa_ForestTrustInformation *lfti, + struct ForestTrustInfo **_fti) +{ + struct ForestTrustInfo *fti; + uint32_t i; + + *_fti = NULL; + + fti = talloc_zero(mem_ctx, struct ForestTrustInfo); + if (fti == NULL) { + return NT_STATUS_NO_MEMORY; + } + + fti->version = 1; + fti->count = lfti->count; + fti->records = talloc_zero_array(mem_ctx, + struct ForestTrustInfoRecordArmor, + fti->count); + if (fti->records == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < fti->count; i++) { + const struct lsa_ForestTrustRecord *lftr = lfti->entries[i]; + struct ForestTrustInfoRecord *ftr = &fti->records[i].record; + struct ForestTrustString *str = NULL; + const struct lsa_StringLarge *lstr = NULL; + const struct lsa_ForestTrustDomainInfo *linfo = NULL; + struct ForestTrustDataDomainInfo *info = NULL; + + if (lftr == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_INVALID_PARAMETER; + } + + ftr->flags = lftr->flags; + ftr->timestamp = lftr->time; + ftr->type = (enum ForestTrustInfoRecordType)lftr->type; + + switch (lftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + lstr = &lftr->forest_trust_data.top_level_name; + str = &ftr->data.name; + + str->string = talloc_strdup(mem_ctx, lstr->string); + if (str->string == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_NO_MEMORY; + } + + break; + + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + lstr = &lftr->forest_trust_data.top_level_name_ex; + str = &ftr->data.name; + + str->string = talloc_strdup(mem_ctx, lstr->string); + if (str->string == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_NO_MEMORY; + } + + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + linfo = &lftr->forest_trust_data.domain_info; + info = &ftr->data.info; + + if (linfo->domain_sid == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_INVALID_PARAMETER; + } + info->sid = *linfo->domain_sid; + + lstr = &linfo->dns_domain_name; + str = &info->dns_name; + str->string = talloc_strdup(mem_ctx, lstr->string); + if (str->string == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_NO_MEMORY; + } + + lstr = &linfo->netbios_domain_name; + str = &info->netbios_name; + str->string = talloc_strdup(mem_ctx, lstr->string); + if (str->string == NULL) { + TALLOC_FREE(fti); + return NT_STATUS_NO_MEMORY; + } + + break; + + default: + return NT_STATUS_NOT_SUPPORTED; + } + } + + *_fti = fti; + return NT_STATUS_OK; +} + +static NTSTATUS dsdb_trust_forest_record_to_lsa(TALLOC_CTX *mem_ctx, + const struct ForestTrustInfoRecord *ftr, + struct lsa_ForestTrustRecord **_lftr) +{ + struct lsa_ForestTrustRecord *lftr = NULL; + const struct ForestTrustString *str = NULL; + struct lsa_StringLarge *lstr = NULL; + const struct ForestTrustDataDomainInfo *info = NULL; + struct lsa_ForestTrustDomainInfo *linfo = NULL; + + *_lftr = NULL; + + lftr = talloc_zero(mem_ctx, struct lsa_ForestTrustRecord); + if (lftr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + lftr->flags = ftr->flags; + lftr->time = ftr->timestamp; + lftr->type = (enum lsa_ForestTrustRecordType)ftr->type; + + switch (lftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + lstr = &lftr->forest_trust_data.top_level_name; + str = &ftr->data.name; + + lstr->string = talloc_strdup(mem_ctx, str->string); + if (lstr->string == NULL) { + TALLOC_FREE(lftr); + return NT_STATUS_NO_MEMORY; + } + + break; + + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + lstr = &lftr->forest_trust_data.top_level_name_ex; + str = &ftr->data.name; + + lstr->string = talloc_strdup(mem_ctx, str->string); + if (lstr->string == NULL) { + TALLOC_FREE(lftr); + return NT_STATUS_NO_MEMORY; + } + + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + linfo = &lftr->forest_trust_data.domain_info; + info = &ftr->data.info; + + linfo->domain_sid = dom_sid_dup(lftr, &info->sid); + if (linfo->domain_sid == NULL) { + TALLOC_FREE(lftr); + return NT_STATUS_NO_MEMORY; + } + + lstr = &linfo->dns_domain_name; + str = &info->dns_name; + lstr->string = talloc_strdup(mem_ctx, str->string); + if (lstr->string == NULL) { + TALLOC_FREE(lftr); + return NT_STATUS_NO_MEMORY; + } + + lstr = &linfo->netbios_domain_name; + str = &info->netbios_name; + lstr->string = talloc_strdup(mem_ctx, str->string); + if (lstr->string == NULL) { + TALLOC_FREE(lftr); + return NT_STATUS_NO_MEMORY; + } + + break; + + default: + return NT_STATUS_NOT_SUPPORTED; + } + + *_lftr = lftr; + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_forest_info_to_lsa(TALLOC_CTX *mem_ctx, + const struct ForestTrustInfo *fti, + struct lsa_ForestTrustInformation **_lfti) +{ + struct lsa_ForestTrustInformation *lfti; + uint32_t i; + + *_lfti = NULL; + + if (fti->version != 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + lfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation); + if (lfti == NULL) { + return NT_STATUS_NO_MEMORY; + } + + lfti->count = fti->count; + lfti->entries = talloc_zero_array(mem_ctx, + struct lsa_ForestTrustRecord *, + lfti->count); + if (lfti->entries == NULL) { + TALLOC_FREE(lfti); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < fti->count; i++) { + struct ForestTrustInfoRecord *ftr = &fti->records[i].record; + struct lsa_ForestTrustRecord *lftr = NULL; + NTSTATUS status; + + status = dsdb_trust_forest_record_to_lsa(lfti->entries, ftr, + &lftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(lfti); + return NT_STATUS_NO_MEMORY; + } + lfti->entries[i] = lftr; + } + + *_lfti = lfti; + return NT_STATUS_OK; +} + +static NTSTATUS dsdb_trust_forest_info_add_record(struct lsa_ForestTrustInformation *fti, + const struct lsa_ForestTrustRecord *ftr) +{ + struct lsa_ForestTrustRecord **es = NULL; + struct lsa_ForestTrustRecord *e = NULL; + const struct lsa_StringLarge *dns1 = NULL; + struct lsa_StringLarge *dns2 = NULL; + const struct lsa_ForestTrustDomainInfo *d1 = NULL; + struct lsa_ForestTrustDomainInfo *d2 = NULL; + size_t len = 0; + + es = talloc_realloc(fti, fti->entries, + struct lsa_ForestTrustRecord *, + fti->count + 1); + if (!es) { + return NT_STATUS_NO_MEMORY; + } + fti->entries = es; + + e = talloc_zero(es, struct lsa_ForestTrustRecord); + if (e == NULL) { + return NT_STATUS_NO_MEMORY; + } + + e->type = ftr->type; + e->flags = ftr->flags; + e->time = ftr->time; + + switch (ftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + dns1 = &ftr->forest_trust_data.top_level_name; + dns2 = &e->forest_trust_data.top_level_name; + break; + + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + dns1 = &ftr->forest_trust_data.top_level_name_ex; + dns2 = &e->forest_trust_data.top_level_name_ex; + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + dns1 = &ftr->forest_trust_data.domain_info.dns_domain_name; + dns2 = &e->forest_trust_data.domain_info.dns_domain_name; + d1 = &ftr->forest_trust_data.domain_info; + d2 = &e->forest_trust_data.domain_info; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + if (dns1->string == NULL) { + TALLOC_FREE(e); + return NT_STATUS_INVALID_PARAMETER; + } + + len = strlen(dns1->string); + if (len == 0) { + TALLOC_FREE(e); + return NT_STATUS_INVALID_PARAMETER; + } + + dns2->string = talloc_strdup(e, dns1->string); + if (dns2->string == NULL) { + TALLOC_FREE(e); + return NT_STATUS_NO_MEMORY; + } + + if (d1 != NULL) { + const struct lsa_StringLarge *nb1 = &d1->netbios_domain_name; + struct lsa_StringLarge *nb2 = &d2->netbios_domain_name; + + if (nb1->string == NULL) { + TALLOC_FREE(e); + return NT_STATUS_INVALID_PARAMETER; + } + + len = strlen(nb1->string); + if (len == 0) { + TALLOC_FREE(e); + return NT_STATUS_INVALID_PARAMETER; + } + if (len > 15) { + TALLOC_FREE(e); + return NT_STATUS_INVALID_PARAMETER; + } + + nb2->string = talloc_strdup(e, nb1->string); + if (nb2->string == NULL) { + TALLOC_FREE(e); + return NT_STATUS_NO_MEMORY; + } + + if (d1->domain_sid == NULL) { + TALLOC_FREE(e); + return NT_STATUS_INVALID_PARAMETER; + } + + d2->domain_sid = dom_sid_dup(e, d1->domain_sid); + if (d2->domain_sid == NULL) { + TALLOC_FREE(e); + return NT_STATUS_NO_MEMORY; + } + } + + fti->entries[fti->count++] = e; + return NT_STATUS_OK; +} + +static NTSTATUS dsdb_trust_parse_crossref_info(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + const struct ldb_message *msg, + struct lsa_TrustDomainInfoInfoEx **_tdo) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + const char *dns = NULL; + const char *netbios = NULL; + struct ldb_dn *nc_dn = NULL; + struct dom_sid sid = { + .num_auths = 0, + }; + NTSTATUS status; + + *_tdo = NULL; + tdo = talloc_zero(mem_ctx, struct lsa_TrustDomainInfoInfoEx); + if (tdo == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(frame, tdo); + + dns = ldb_msg_find_attr_as_string(msg, "dnsRoot", NULL); + if (dns == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + tdo->domain_name.string = talloc_strdup(tdo, dns); + if (tdo->domain_name.string == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + netbios = ldb_msg_find_attr_as_string(msg, "nETBIOSName", NULL); + if (netbios == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + tdo->netbios_name.string = talloc_strdup(tdo, netbios); + if (tdo->netbios_name.string == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + nc_dn = samdb_result_dn(sam_ctx, frame, msg, "ncName", NULL); + if (nc_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = dsdb_get_extended_dn_sid(nc_dn, &sid, "SID"); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + tdo->sid = dom_sid_dup(tdo, &sid); + if (tdo->sid == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + tdo->trust_type = LSA_TRUST_TYPE_UPLEVEL; + tdo->trust_direction = LSA_TRUST_DIRECTION_INBOUND | + LSA_TRUST_DIRECTION_OUTBOUND; + tdo->trust_attributes = LSA_TRUST_ATTRIBUTE_WITHIN_FOREST; + + *_tdo = talloc_move(mem_ctx, &tdo); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +static NTSTATUS dsdb_trust_crossref_tdo_info(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct ldb_dn *domain_dn, + const char *extra_filter, + struct lsa_TrustDomainInfoInfoEx **_tdo, + struct lsa_TrustDomainInfoInfoEx **_root_trust_tdo, + struct lsa_TrustDomainInfoInfoEx **_trust_parent_tdo) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + struct lsa_TrustDomainInfoInfoEx *root_trust_tdo = NULL; + struct lsa_TrustDomainInfoInfoEx *trust_parent_tdo = NULL; + struct ldb_dn *partitions_dn = NULL; + const char * const cross_attrs[] = { + "dnsRoot", + "nETBIOSName", + "nCName", + "rootTrust", + "trustParent", + NULL, + }; + struct ldb_result *cross_res = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *root_trust_dn = NULL; + struct ldb_dn *trust_parent_dn = NULL; + NTSTATUS status; + int ret; + + if (extra_filter == NULL) { + extra_filter = ""; + } + + *_tdo = NULL; + if (_root_trust_tdo != NULL) { + *_root_trust_tdo = NULL; + } + if (_trust_parent_tdo != NULL) { + *_trust_parent_tdo = NULL; + } + + partitions_dn = samdb_partitions_dn(sam_ctx, frame); + if (partitions_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = dsdb_search(sam_ctx, partitions_dn, &cross_res, + partitions_dn, LDB_SCOPE_ONELEVEL, + cross_attrs, + DSDB_SEARCH_ONE_ONLY | + DSDB_SEARCH_SHOW_EXTENDED_DN, + "(&" + "(ncName=%s)" + "(objectClass=crossRef)" + "(systemFlags:%s:=%u)" + "%s" + ")", + ldb_dn_get_linearized(domain_dn), + LDB_OID_COMPARATOR_AND, + SYSTEM_FLAG_CR_NTDS_DOMAIN, + extra_filter); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + return dsdb_ldb_err_to_ntstatus(ret); + } + msg = cross_res->msgs[0]; + + status = dsdb_trust_parse_crossref_info(mem_ctx, sam_ctx, msg, &tdo); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + talloc_steal(frame, tdo); + + if (_root_trust_tdo != NULL) { + root_trust_dn = samdb_result_dn(sam_ctx, frame, msg, + "rootTrust", NULL); + } + if (_trust_parent_tdo != NULL) { + trust_parent_dn = samdb_result_dn(sam_ctx, frame, msg, + "trustParent", NULL); + } + + if (root_trust_dn != NULL) { + struct ldb_message *root_trust_msg = NULL; + + ret = dsdb_search_one(sam_ctx, frame, + &root_trust_msg, + root_trust_dn, + LDB_SCOPE_BASE, + cross_attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(objectClass=crossRef)"); + if (ret != LDB_SUCCESS) { + status = dsdb_ldb_err_to_ntstatus(ret); + DEBUG(3, ("Failed to search for %s: %s - %s\n", + ldb_dn_get_linearized(root_trust_dn), + nt_errstr(status), ldb_errstring(sam_ctx))); + TALLOC_FREE(frame); + return status; + } + + status = dsdb_trust_parse_crossref_info(mem_ctx, sam_ctx, + root_trust_msg, + &root_trust_tdo); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + talloc_steal(frame, root_trust_tdo); + } + + if (trust_parent_dn != NULL) { + struct ldb_message *trust_parent_msg = NULL; + + ret = dsdb_search_one(sam_ctx, frame, + &trust_parent_msg, + trust_parent_dn, + LDB_SCOPE_BASE, + cross_attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(objectClass=crossRef)"); + if (ret != LDB_SUCCESS) { + status = dsdb_ldb_err_to_ntstatus(ret); + DEBUG(3, ("Failed to search for %s: %s - %s\n", + ldb_dn_get_linearized(trust_parent_dn), + nt_errstr(status), ldb_errstring(sam_ctx))); + TALLOC_FREE(frame); + return status; + } + + status = dsdb_trust_parse_crossref_info(mem_ctx, sam_ctx, + trust_parent_msg, + &trust_parent_tdo); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + talloc_steal(frame, trust_parent_tdo); + } + + *_tdo = talloc_move(mem_ctx, &tdo); + if (_root_trust_tdo != NULL) { + *_root_trust_tdo = talloc_move(mem_ctx, &root_trust_tdo); + } + if (_trust_parent_tdo != NULL) { + *_trust_parent_tdo = talloc_move(mem_ctx, &trust_parent_tdo); + } + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +#define DNS_CMP_FIRST_IS_CHILD -2 +#define DNS_CMP_FIRST_IS_LESS -1 +#define DNS_CMP_MATCH 0 +#define DNS_CMP_SECOND_IS_LESS 1 +#define DNS_CMP_SECOND_IS_CHILD 2 + +#define DNS_CMP_IS_NO_MATCH(__cmp) \ + ((__cmp == DNS_CMP_FIRST_IS_LESS) || (__cmp == DNS_CMP_SECOND_IS_LESS)) + +/* + * this function assumes names are well formed DNS names. + * it doesn't validate them + * + * It allows strings up to a length of UINT16_MAX - 1 + * with up to UINT8_MAX components. On overflow this + * just returns the result of strcasecmp_m(). + * + * Trailing dots (only one) are ignored. + * + * The DNS names are compared per component, starting from + * the last one. + */ +static int dns_cmp(const char *s1, const char *s2) +{ + size_t l1 = 0; + const char *p1 = NULL; + size_t num_comp1 = 0; + uint16_t comp1[UINT8_MAX] = {0}; + size_t l2 = 0; + const char *p2 = NULL; + size_t num_comp2 = 0; + uint16_t comp2[UINT8_MAX] = {0}; + size_t i; + + if (s1 != NULL) { + l1 = strlen(s1); + } + + if (s2 != NULL) { + l2 = strlen(s2); + } + + /* + * trailing '.' are ignored. + */ + if (l1 > 1 && s1[l1 - 1] == '.') { + l1--; + } + if (l2 > 1 && s2[l2 - 1] == '.') { + l2--; + } + + for (i = 0; i < ARRAY_SIZE(comp1); i++) { + char *p; + + if (i == 0) { + p1 = s1; + + if (l1 == 0 || l1 >= UINT16_MAX) { + /* just use one single component on overflow */ + break; + } + } + + comp1[num_comp1++] = PTR_DIFF(p1, s1); + + p = strchr_m(p1, '.'); + if (p == NULL) { + p1 = NULL; + break; + } + + p1 = p + 1; + } + + if (p1 != NULL) { + /* just use one single component on overflow */ + num_comp1 = 0; + comp1[num_comp1++] = 0; + p1 = NULL; + } + + for (i = 0; i < ARRAY_SIZE(comp2); i++) { + char *p; + + if (i == 0) { + p2 = s2; + + if (l2 == 0 || l2 >= UINT16_MAX) { + /* just use one single component on overflow */ + break; + } + } + + comp2[num_comp2++] = PTR_DIFF(p2, s2); + + p = strchr_m(p2, '.'); + if (p == NULL) { + p2 = NULL; + break; + } + + p2 = p + 1; + } + + if (p2 != NULL) { + /* just use one single component on overflow */ + num_comp2 = 0; + comp2[num_comp2++] = 0; + p2 = NULL; + } + + for (i = 0; i < UINT8_MAX; i++) { + int cmp; + + if (i < num_comp1) { + size_t idx = num_comp1 - (i + 1); + p1 = s1 + comp1[idx]; + } else { + p1 = NULL; + } + + if (i < num_comp2) { + size_t idx = num_comp2 - (i + 1); + p2 = s2 + comp2[idx]; + } else { + p2 = NULL; + } + + if (p1 == NULL && p2 == NULL) { + return DNS_CMP_MATCH; + } + if (p1 != NULL && p2 == NULL) { + return DNS_CMP_FIRST_IS_CHILD; + } + if (p1 == NULL && p2 != NULL) { + return DNS_CMP_SECOND_IS_CHILD; + } + + cmp = strcasecmp_m(p1, p2); + if (cmp < 0) { + return DNS_CMP_FIRST_IS_LESS; + } + if (cmp > 0) { + return DNS_CMP_SECOND_IS_LESS; + } + } + + smb_panic(__location__); + return -1; +} + +static int dsdb_trust_find_tln_match_internal(const struct lsa_ForestTrustInformation *info, + enum lsa_ForestTrustRecordType type, + uint32_t disable_mask, + const char *tln) +{ + uint32_t i; + + for (i = 0; i < info->count; i++) { + struct lsa_ForestTrustRecord *e = info->entries[i]; + struct lsa_StringLarge *t = NULL; + int cmp; + + if (e == NULL) { + continue; + } + + if (e->type != type) { + continue; + } + + if (e->flags & disable_mask) { + continue; + } + + switch (type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + t = &e->forest_trust_data.top_level_name; + break; + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + t = &e->forest_trust_data.top_level_name_ex; + break; + default: + break; + } + + if (t == NULL) { + continue; + } + + cmp = dns_cmp(tln, t->string); + switch (cmp) { + case DNS_CMP_MATCH: + case DNS_CMP_FIRST_IS_CHILD: + return i; + } + } + + return -1; +} + +static bool dsdb_trust_find_tln_match(const struct lsa_ForestTrustInformation *info, + const char *tln) +{ + int m; + + m = dsdb_trust_find_tln_match_internal(info, + LSA_FOREST_TRUST_TOP_LEVEL_NAME, + LSA_TLN_DISABLED_MASK, + tln); + if (m != -1) { + return true; + } + + return false; +} + +static bool dsdb_trust_find_tln_ex_match(const struct lsa_ForestTrustInformation *info, + const char *tln) +{ + int m; + + m = dsdb_trust_find_tln_match_internal(info, + LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX, + 0, + tln); + if (m != -1) { + return true; + } + + return false; +} + +NTSTATUS dsdb_trust_local_tdo_info(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct lsa_TrustDomainInfoInfoEx **_tdo) +{ + struct ldb_dn *domain_dn = NULL; + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + return dsdb_trust_crossref_tdo_info(mem_ctx, sam_ctx, + domain_dn, NULL, + _tdo, NULL, NULL); +} + +NTSTATUS dsdb_trust_xref_tdo_info(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct lsa_TrustDomainInfoInfoEx **_tdo) +{ + /* + * The extra filter makes sure we only find the forest root domain + */ + const char *extra_filter = "(!(|(rootTrust=*)(trustParent=*)))"; + struct ldb_dn *domain_dn = NULL; + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + return dsdb_trust_crossref_tdo_info(mem_ctx, sam_ctx, + domain_dn, extra_filter, + _tdo, NULL, NULL); +} + +static int dsdb_trust_xref_sort_msgs(struct ldb_message **_m1, + struct ldb_message **_m2) +{ + struct ldb_message *m1 = *_m1; + struct ldb_message *m2 = *_m2; + const char *dns1 = NULL; + const char *dns2 = NULL; + int cmp; + struct ldb_message_element *rootTrust1 = NULL; + struct ldb_message_element *trustParent1 = NULL; + struct ldb_message_element *rootTrust2 = NULL; + struct ldb_message_element *trustParent2 = NULL; + + dns1 = ldb_msg_find_attr_as_string(m1, "dnsRoot", NULL); + dns2 = ldb_msg_find_attr_as_string(m2, "dnsRoot", NULL); + + cmp = dns_cmp(dns1, dns2); + switch (cmp) { + case DNS_CMP_FIRST_IS_CHILD: + return -1; + case DNS_CMP_SECOND_IS_CHILD: + return 1; + } + + rootTrust1 = ldb_msg_find_element(m1, "rootTrust"); + trustParent1 = ldb_msg_find_element(m1, "trustParent"); + rootTrust2 = ldb_msg_find_element(m2, "rootTrust"); + trustParent2 = ldb_msg_find_element(m2, "trustParent"); + + if (rootTrust1 == NULL && trustParent1 == NULL) { + /* m1 is the forest root */ + return -1; + } + if (rootTrust2 == NULL && trustParent2 == NULL) { + /* m2 is the forest root */ + return 1; + } + + return cmp; +} + +static int dsdb_trust_xref_sort_vals(struct ldb_val *v1, + struct ldb_val *v2) +{ + const char *dns1 = (const char *)v1->data; + const char *dns2 = (const char *)v2->data; + + return dns_cmp(dns1, dns2); +} + +NTSTATUS dsdb_trust_xref_forest_info(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct lsa_ForestTrustInformation **_info) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct lsa_ForestTrustInformation *info = NULL; + struct ldb_dn *partitions_dn = NULL; + const char * const cross_attrs1[] = { + "uPNSuffixes", + "msDS-SPNSuffixes", + NULL, + }; + struct ldb_result *cross_res1 = NULL; + struct ldb_message_element *upn_el = NULL; + struct ldb_message_element *spn_el = NULL; + struct ldb_message *tln_msg = NULL; + struct ldb_message_element *tln_el = NULL; + const char * const cross_attrs2[] = { + "dnsRoot", + "nETBIOSName", + "nCName", + "rootTrust", + "trustParent", + NULL, + }; + struct ldb_result *cross_res2 = NULL; + int ret; + unsigned int i; + bool restart = false; + + *_info = NULL; + info = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation); + if (info == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(frame, info); + + partitions_dn = samdb_partitions_dn(sam_ctx, frame); + if (partitions_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = dsdb_search_dn(sam_ctx, partitions_dn, &cross_res1, + partitions_dn, cross_attrs1, 0); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = dsdb_search(sam_ctx, partitions_dn, &cross_res2, + partitions_dn, LDB_SCOPE_ONELEVEL, + cross_attrs2, + DSDB_SEARCH_SHOW_EXTENDED_DN, + "(&(objectClass=crossRef)" + "(systemFlags:%s:=%u))", + LDB_OID_COMPARATOR_AND, + SYSTEM_FLAG_CR_NTDS_DOMAIN); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + return dsdb_ldb_err_to_ntstatus(ret); + } + + /* + * Sort the domains as trees, starting with the forest root + */ + TYPESAFE_QSORT(cross_res2->msgs, cross_res2->count, + dsdb_trust_xref_sort_msgs); + + upn_el = ldb_msg_find_element(cross_res1->msgs[0], "uPNSuffixes"); + if (upn_el != NULL) { + upn_el->name = "__tln__"; + } + spn_el = ldb_msg_find_element(cross_res1->msgs[0], "msDS-SPNSuffixes"); + if (spn_el != NULL) { + spn_el->name = "__tln__"; + } + ret = ldb_msg_normalize(sam_ctx, frame, cross_res1->msgs[0], &tln_msg); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + return dsdb_ldb_err_to_ntstatus(ret); + } + tln_el = ldb_msg_find_element(tln_msg, "__tln__"); + if (tln_el != NULL) { + /* + * Sort the domains as trees + */ + TYPESAFE_QSORT(tln_el->values, tln_el->num_values, + dsdb_trust_xref_sort_vals); + } + + for (i=0; i < cross_res2->count; i++) { + struct ldb_message *m = cross_res2->msgs[i]; + const char *dns = NULL; + const char *netbios = NULL; + struct ldb_dn *nc_dn = NULL; + struct dom_sid sid = { + .num_auths = 0, + }; + struct lsa_ForestTrustRecord e = { + .flags = 0, + }; + struct lsa_ForestTrustDomainInfo *d = NULL; + struct lsa_StringLarge *t = NULL; + bool match = false; + NTSTATUS status; + + dns = ldb_msg_find_attr_as_string(m, "dnsRoot", NULL); + if (dns == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + netbios = ldb_msg_find_attr_as_string(m, "nETBIOSName", NULL); + if (netbios == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + nc_dn = samdb_result_dn(sam_ctx, m, m, "ncName", NULL); + if (nc_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = dsdb_get_extended_dn_sid(nc_dn, &sid, "SID"); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + match = dsdb_trust_find_tln_match(info, dns); + if (!match) { + /* + * First the TOP_LEVEL_NAME, if required + */ + e = (struct lsa_ForestTrustRecord) { + .flags = 0, + .type = LSA_FOREST_TRUST_TOP_LEVEL_NAME, + .time = 0, /* so far always 0 in traces. */ + }; + + t = &e.forest_trust_data.top_level_name; + t->string = dns; + + status = dsdb_trust_forest_info_add_record(info, &e); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + /* + * Then the DOMAIN_INFO + */ + e = (struct lsa_ForestTrustRecord) { + .flags = 0, + .type = LSA_FOREST_TRUST_DOMAIN_INFO, + .time = 0, /* so far always 0 in traces. */ + }; + d = &e.forest_trust_data.domain_info; + d->domain_sid = &sid; + d->dns_domain_name.string = dns; + d->netbios_domain_name.string = netbios; + + status = dsdb_trust_forest_info_add_record(info, &e); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + for (i=0; (tln_el != NULL) && i < tln_el->num_values; i++) { + const struct ldb_val *v = &tln_el->values[i]; + const char *dns = (const char *)v->data; + struct lsa_ForestTrustRecord e = { + .flags = 0, + }; + struct lsa_StringLarge *t = NULL; + bool match = false; + NTSTATUS status; + + if (dns == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + match = dsdb_trust_find_tln_match(info, dns); + if (match) { + continue; + } + + /* + * an additional the TOP_LEVEL_NAME + */ + e = (struct lsa_ForestTrustRecord) { + .flags = 0, + .type = LSA_FOREST_TRUST_TOP_LEVEL_NAME, + .time = 0, /* so far always 0 in traces. */ + }; + t = &e.forest_trust_data.top_level_name; + t->string = dns; + + status = dsdb_trust_forest_info_add_record(info, &e); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + for (i=0; i < info->count; restart ? i=0 : i++) { + struct lsa_ForestTrustRecord *tr = info->entries[i]; + const struct lsa_StringLarge *ts = NULL; + uint32_t c; + + restart = false; + + if (tr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + ts = &tr->forest_trust_data.top_level_name; + + for (c = i + 1; c < info->count; c++) { + struct lsa_ForestTrustRecord *cr = info->entries[c]; + const struct lsa_StringLarge *cs = NULL; + uint32_t j; + int cmp; + + if (cr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + cs = &cr->forest_trust_data.top_level_name; + + cmp = dns_cmp(ts->string, cs->string); + if (DNS_CMP_IS_NO_MATCH(cmp)) { + continue; + } + if (cmp != DNS_CMP_FIRST_IS_CHILD) { + /* can't happen ... */ + continue; + } + + ts = NULL; + tr = NULL; + TALLOC_FREE(info->entries[i]); + info->entries[i] = info->entries[c]; + + for (j = c + 1; j < info->count; j++) { + info->entries[j-1] = info->entries[j]; + } + info->count -= 1; + restart = true; + break; + } + } + + *_info = talloc_move(mem_ctx, &info); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_parse_tdo_info(TALLOC_CTX *mem_ctx, + struct ldb_message *m, + struct lsa_TrustDomainInfoInfoEx **_tdo) +{ + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + const char *dns = NULL; + const char *netbios = NULL; + + *_tdo = NULL; + + tdo = talloc_zero(mem_ctx, struct lsa_TrustDomainInfoInfoEx); + if (tdo == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dns = ldb_msg_find_attr_as_string(m, "trustPartner", NULL); + if (dns == NULL) { + TALLOC_FREE(tdo); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + tdo->domain_name.string = talloc_strdup(tdo, dns); + if (tdo->domain_name.string == NULL) { + TALLOC_FREE(tdo); + return NT_STATUS_NO_MEMORY; + } + + netbios = ldb_msg_find_attr_as_string(m, "flatName", NULL); + if (netbios == NULL) { + TALLOC_FREE(tdo); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + tdo->netbios_name.string = talloc_strdup(tdo, netbios); + if (tdo->netbios_name.string == NULL) { + TALLOC_FREE(tdo); + return NT_STATUS_NO_MEMORY; + } + + tdo->sid = samdb_result_dom_sid(tdo, m, "securityIdentifier"); + if (tdo->sid == NULL) { + TALLOC_FREE(tdo); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + tdo->trust_type = ldb_msg_find_attr_as_uint(m, "trustType", 0); + tdo->trust_direction = ldb_msg_find_attr_as_uint(m, "trustDirection", 0); + tdo->trust_attributes = ldb_msg_find_attr_as_uint(m, "trustAttributes", 0); + + *_tdo = tdo; + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_parse_forest_info(TALLOC_CTX *mem_ctx, + struct ldb_message *m, + struct ForestTrustInfo **_fti) +{ + const struct ldb_val *ft_blob = NULL; + struct ForestTrustInfo *fti = NULL; + enum ndr_err_code ndr_err; + + *_fti = NULL; + + ft_blob = ldb_msg_find_ldb_val(m, "msDS-TrustForestTrustInfo"); + if (ft_blob == NULL) { + return NT_STATUS_NOT_FOUND; + } + + fti = talloc_zero(mem_ctx, struct ForestTrustInfo); + if (fti == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* ldb_val is equivalent to DATA_BLOB */ + ndr_err = ndr_pull_struct_blob_all(ft_blob, fti, fti, + (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(fti); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + *_fti = fti; + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_normalize_forest_info_step1(TALLOC_CTX *mem_ctx, + const struct lsa_ForestTrustInformation *gfti, + struct lsa_ForestTrustInformation **_nfti) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct lsa_ForestTrustInformation *nfti; + uint32_t n; + + *_nfti = NULL; + + nfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation); + if (nfti == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(frame, nfti); + + /* + * First we copy every record and remove possible trailing dots + * from dns names. + * + * We also NULL out duplicates. The first one wins and + * we keep 'count' as is. This is required in order to + * provide the correct index for collision records. + */ + for (n = 0; n < gfti->count; n++) { + const struct lsa_ForestTrustRecord *gftr = gfti->entries[n]; + struct lsa_ForestTrustRecord *nftr = NULL; + struct lsa_ForestTrustDomainInfo *ninfo = NULL; + struct lsa_StringLarge *ntln = NULL; + struct lsa_StringLarge *nnb = NULL; + struct dom_sid *nsid = NULL; + NTSTATUS status; + size_t len = 0; + char *p = NULL; + uint32_t c; + + if (gftr == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_trust_forest_info_add_record(nfti, gftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + nftr = nfti->entries[n]; + + switch (nftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + ntln = &nftr->forest_trust_data.top_level_name; + break; + + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + ntln = &nftr->forest_trust_data.top_level_name_ex; + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + ninfo = &nftr->forest_trust_data.domain_info; + ntln = &ninfo->dns_domain_name; + nnb = &ninfo->netbios_domain_name; + nsid = ninfo->domain_sid; + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * We remove one trailing '.' before checking + * for invalid dots. + * + * domain.com. becomes domain.com + * domain.com.. becomes domain.com. + * + * Then the following is invalid: + * + * domain..com + * .domain.com + * domain.com. + */ + len = strlen(ntln->string); + if (len > 1 && ntln->string[len - 1] == '.') { + const char *cp = &ntln->string[len - 1]; + p = discard_const_p(char, cp); + *p= '\0'; + } + if (ntln->string[0] == '.') { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + p = strstr_m(ntln->string, ".."); + if (p != NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + for (c = 0; c < n; c++) { + const struct lsa_ForestTrustRecord *cftr = nfti->entries[c]; + const struct lsa_ForestTrustDomainInfo *cinfo = NULL; + const struct lsa_StringLarge *ctln = NULL; + const struct lsa_StringLarge *cnb = NULL; + const struct dom_sid *csid = NULL; + int cmp; + + if (cftr == NULL) { + continue; + } + + if (cftr->type != nftr->type) { + continue; + } + + switch (cftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + ctln = &cftr->forest_trust_data.top_level_name; + break; + + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + ctln = &cftr->forest_trust_data.top_level_name_ex; + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + cinfo = &cftr->forest_trust_data.domain_info; + ctln = &cinfo->dns_domain_name; + cnb = &cinfo->netbios_domain_name; + csid = cinfo->domain_sid; + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + cmp = dns_cmp(ntln->string, ctln->string); + if (cmp == DNS_CMP_MATCH) { + nftr = NULL; + TALLOC_FREE(nfti->entries[n]); + break; + } + + if (cinfo == NULL) { + continue; + } + + cmp = strcasecmp_m(nnb->string, cnb->string); + if (cmp == 0) { + nftr = NULL; + TALLOC_FREE(nfti->entries[n]); + break; + } + + cmp = dom_sid_compare(nsid, csid); + if (cmp == 0) { + nftr = NULL; + TALLOC_FREE(nfti->entries[n]); + break; + } + } + } + + /* + * Now we check that only true top level names are provided + */ + for (n = 0; n < nfti->count; n++) { + const struct lsa_ForestTrustRecord *nftr = nfti->entries[n]; + const struct lsa_StringLarge *ntln = NULL; + uint32_t c; + + if (nftr == NULL) { + continue; + } + + if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + ntln = &nftr->forest_trust_data.top_level_name; + + for (c = 0; c < nfti->count; c++) { + const struct lsa_ForestTrustRecord *cftr = nfti->entries[c]; + const struct lsa_StringLarge *ctln = NULL; + int cmp; + + if (cftr == NULL) { + continue; + } + + if (cftr == nftr) { + continue; + } + + if (cftr->type != nftr->type) { + continue; + } + + ctln = &cftr->forest_trust_data.top_level_name; + + cmp = dns_cmp(ntln->string, ctln->string); + if (DNS_CMP_IS_NO_MATCH(cmp)) { + continue; + } + + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + } + + /* + * Now we check that only true sub level excludes are provided + */ + for (n = 0; n < nfti->count; n++) { + const struct lsa_ForestTrustRecord *nftr = nfti->entries[n]; + const struct lsa_StringLarge *ntln = NULL; + uint32_t c; + bool found_tln = false; + + if (nftr == NULL) { + continue; + } + + if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX) { + continue; + } + + ntln = &nftr->forest_trust_data.top_level_name; + + for (c = 0; c < nfti->count; c++) { + const struct lsa_ForestTrustRecord *cftr = nfti->entries[c]; + const struct lsa_StringLarge *ctln = NULL; + int cmp; + + if (cftr == NULL) { + continue; + } + + if (cftr == nftr) { + continue; + } + + if (cftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + ctln = &cftr->forest_trust_data.top_level_name; + + cmp = dns_cmp(ntln->string, ctln->string); + if (cmp == DNS_CMP_FIRST_IS_CHILD) { + found_tln = true; + break; + } + } + + if (found_tln) { + continue; + } + + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * Now we check that there's a top level name for each domain + */ + for (n = 0; n < nfti->count; n++) { + const struct lsa_ForestTrustRecord *nftr = nfti->entries[n]; + const struct lsa_ForestTrustDomainInfo *ninfo = NULL; + const struct lsa_StringLarge *ntln = NULL; + uint32_t c; + bool found_tln = false; + + if (nftr == NULL) { + continue; + } + + if (nftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + ninfo = &nftr->forest_trust_data.domain_info; + ntln = &ninfo->dns_domain_name; + + for (c = 0; c < nfti->count; c++) { + const struct lsa_ForestTrustRecord *cftr = nfti->entries[c]; + const struct lsa_StringLarge *ctln = NULL; + int cmp; + + if (cftr == NULL) { + continue; + } + + if (cftr == nftr) { + continue; + } + + if (cftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + ctln = &cftr->forest_trust_data.top_level_name; + + cmp = dns_cmp(ntln->string, ctln->string); + if (cmp == DNS_CMP_MATCH) { + found_tln = true; + break; + } + if (cmp == DNS_CMP_FIRST_IS_CHILD) { + found_tln = true; + break; + } + } + + if (found_tln) { + continue; + } + + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + *_nfti = talloc_move(mem_ctx, &nfti); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_normalize_forest_info_step2(TALLOC_CTX *mem_ctx, + const struct lsa_ForestTrustInformation *gfti, + struct lsa_ForestTrustInformation **_nfti) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct timeval tv = timeval_current(); + NTTIME now = timeval_to_nttime(&tv); + struct lsa_ForestTrustInformation *nfti; + uint32_t g; + + *_nfti = NULL; + + nfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation); + if (nfti == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(frame, nfti); + + /* + * Now we add TOP_LEVEL_NAME[_EX] in reverse order + * followed by LSA_FOREST_TRUST_DOMAIN_INFO in reverse order. + * + * This also removes the possible NULL entries generated in step1. + */ + + for (g = 0; g < gfti->count; g++) { + const struct lsa_ForestTrustRecord *gftr = gfti->entries[gfti->count - (g+1)]; + struct lsa_ForestTrustRecord tftr; + bool skip = false; + NTSTATUS status; + + if (gftr == NULL) { + continue; + } + + switch (gftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + skip = true; + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (skip) { + continue; + } + + /* make a copy in order to update the time. */ + tftr = *gftr; + if (tftr.time == 0) { + tftr.time = now; + } + + status = dsdb_trust_forest_info_add_record(nfti, &tftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + for (g = 0; g < gfti->count; g++) { + const struct lsa_ForestTrustRecord *gftr = gfti->entries[gfti->count - (g+1)]; + struct lsa_ForestTrustRecord tftr; + bool skip = false; + NTSTATUS status; + + if (gftr == NULL) { + continue; + } + + switch (gftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + skip = true; + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (skip) { + continue; + } + + /* make a copy in order to update the time. */ + tftr = *gftr; + if (tftr.time == 0) { + tftr.time = now; + } + + status = dsdb_trust_forest_info_add_record(nfti, &tftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + *_nfti = talloc_move(mem_ctx, &nfti); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +static NTSTATUS dsdb_trust_add_collision( + struct lsa_ForestTrustCollisionInfo *c_info, + enum lsa_ForestTrustCollisionRecordType type, + uint32_t idx, uint32_t flags, + const char *tdo_name) +{ + struct lsa_ForestTrustCollisionRecord **es; + uint32_t i = c_info->count; + + es = talloc_realloc(c_info, c_info->entries, + struct lsa_ForestTrustCollisionRecord *, i + 1); + if (es == NULL) { + return NT_STATUS_NO_MEMORY; + } + c_info->entries = es; + c_info->count = i + 1; + + es[i] = talloc_zero(es, struct lsa_ForestTrustCollisionRecord); + if (es[i] == NULL) { + return NT_STATUS_NO_MEMORY; + } + + es[i]->index = idx; + es[i]->type = type; + es[i]->flags = flags; + es[i]->name.string = talloc_strdup(es[i], tdo_name); + if (es[i]->name.string == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_verify_forest_info(const struct lsa_TrustDomainInfoInfoEx *ref_tdo, + const struct lsa_ForestTrustInformation *ref_fti, + enum lsa_ForestTrustCollisionRecordType collision_type, + struct lsa_ForestTrustCollisionInfo *c_info, + struct lsa_ForestTrustInformation *new_fti) +{ + uint32_t n; + + for (n = 0; n < new_fti->count; n++) { + struct lsa_ForestTrustRecord *nftr = new_fti->entries[n]; + struct lsa_StringLarge *ntln = NULL; + bool ntln_excluded = false; + uint32_t flags = 0; + uint32_t r; + NTSTATUS status; + + if (nftr == NULL) { + continue; + } + + if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + ntln = &nftr->forest_trust_data.top_level_name; + if (ntln->string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + ntln_excluded = dsdb_trust_find_tln_ex_match(ref_fti, + ntln->string); + + /* check if this is already taken and not excluded */ + for (r = 0; r < ref_fti->count; r++) { + const struct lsa_ForestTrustRecord *rftr = + ref_fti->entries[r]; + const struct lsa_StringLarge *rtln = NULL; + int cmp; + + if (rftr == NULL) { + continue; + } + + if (rftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + rtln = &rftr->forest_trust_data.top_level_name; + if (rtln->string == NULL) { + continue; + } + + cmp = dns_cmp(ntln->string, rtln->string); + if (DNS_CMP_IS_NO_MATCH(cmp)) { + continue; + } + if (cmp == DNS_CMP_MATCH) { + /* We need to normalize the string */ + ntln->string = talloc_strdup(nftr, + rtln->string); + if (ntln->string == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (ntln_excluded) { + continue; + } + + if (rftr->flags & LSA_TLN_DISABLED_MASK) { + continue; + } + + if (nftr->flags & LSA_TLN_DISABLED_MASK) { + continue; + } + + if (cmp == DNS_CMP_SECOND_IS_CHILD) { + bool m; + + /* + * If the conflicting tln is a child, check if + * we have an exclusion record for it. + */ + m = dsdb_trust_find_tln_ex_match(new_fti, + rtln->string); + if (m) { + continue; + } + } + + flags |= LSA_TLN_DISABLED_CONFLICT; + } + + if (flags == 0) { + continue; + } + + nftr->flags |= flags; + + status = dsdb_trust_add_collision(c_info, + collision_type, + n, nftr->flags, + ref_tdo->domain_name.string); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + for (n = 0; n < new_fti->count; n++) { + struct lsa_ForestTrustRecord *nftr = new_fti->entries[n]; + struct lsa_ForestTrustDomainInfo *ninfo = NULL; + struct lsa_StringLarge *ntln = NULL; + struct lsa_StringLarge *nnb = NULL; + struct dom_sid *nsid = NULL; + bool ntln_found = false; + uint32_t flags = 0; + uint32_t r; + NTSTATUS status; + + if (nftr == NULL) { + continue; + } + + if (nftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + ninfo = &nftr->forest_trust_data.domain_info; + ntln = &ninfo->dns_domain_name; + if (ntln->string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + nnb = &ninfo->netbios_domain_name; + if (nnb->string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + nsid = ninfo->domain_sid; + if (nsid == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + ntln_found = dsdb_trust_find_tln_match(ref_fti, ntln->string); + + /* check if this is already taken and not excluded */ + for (r = 0; r < ref_fti->count; r++) { + const struct lsa_ForestTrustRecord *rftr = + ref_fti->entries[r]; + const struct lsa_ForestTrustDomainInfo *rinfo = NULL; + const struct lsa_StringLarge *rtln = NULL; + const struct lsa_StringLarge *rnb = NULL; + const struct dom_sid *rsid = NULL; + bool nb_possible = true; + bool sid_possible = true; + int cmp; + + if (rftr == NULL) { + continue; + } + + if (!ntln_found) { + /* + * If the dns name doesn't match any existing + * tln any conflict is ignored, but name + * normalization still happens. + * + * I guess that's a bug in Windows + * (tested with Windows 2012r2). + */ + nb_possible = false; + sid_possible = false; + } + + if (nftr->flags & LSA_SID_DISABLED_MASK) { + sid_possible = false; + } + + if (nftr->flags & LSA_NB_DISABLED_MASK) { + nb_possible = false; + } + + switch (rftr->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + rtln = &rftr->forest_trust_data.top_level_name; + nb_possible = false; + sid_possible = false; + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + rinfo = &rftr->forest_trust_data.domain_info; + rtln = &rinfo->dns_domain_name; + rnb = &rinfo->netbios_domain_name; + rsid = rinfo->domain_sid; + + if (rftr->flags & LSA_SID_DISABLED_MASK) { + sid_possible = false; + } + + if (rftr->flags & LSA_NB_DISABLED_MASK) { + nb_possible = false; + } + break; + + default: + break; + } + + if (rtln == NULL) { + continue; + } + + if (rtln->string == NULL) { + continue; + } + + cmp = dns_cmp(ntln->string, rtln->string); + if (DNS_CMP_IS_NO_MATCH(cmp)) { + nb_possible = false; + sid_possible = false; + } + if (cmp == DNS_CMP_MATCH) { + /* We need to normalize the string */ + ntln->string = talloc_strdup(nftr, + rtln->string); + if (ntln->string == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (rinfo == NULL) { + continue; + } + + if (rsid != NULL) { + cmp = dom_sid_compare(nsid, rsid); + } else { + cmp = -1; + } + if (cmp == 0) { + if (sid_possible) { + flags |= LSA_SID_DISABLED_CONFLICT; + } + } + + if (rnb->string != NULL) { + cmp = strcasecmp_m(nnb->string, rnb->string); + } else { + cmp = -1; + } + if (cmp == 0) { + nnb->string = talloc_strdup(nftr, rnb->string); + if (nnb->string == NULL) { + return NT_STATUS_NO_MEMORY; + } + if (nb_possible) { + flags |= LSA_NB_DISABLED_CONFLICT; + } + } + } + + if (flags == 0) { + continue; + } + + nftr->flags |= flags; + + status = dsdb_trust_add_collision(c_info, + collision_type, + n, nftr->flags, + ref_tdo->domain_name.string); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_merge_forest_info(TALLOC_CTX *mem_ctx, + const struct lsa_TrustDomainInfoInfoEx *tdo, + const struct lsa_ForestTrustInformation *ofti, + const struct lsa_ForestTrustInformation *nfti, + struct lsa_ForestTrustInformation **_mfti) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct lsa_ForestTrustInformation *mfti = NULL; + uint32_t ni; + uint32_t oi; + NTSTATUS status; + int cmp; + + *_mfti = NULL; + mfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation); + if (mfti == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(frame, mfti); + + /* + * First we add all top unique level names. + * + * The one matching the tdo dns name, will be + * added without further checking. All others + * may keep the flags and time values. + */ + for (ni = 0; ni < nfti->count; ni++) { + const struct lsa_ForestTrustRecord *nftr = nfti->entries[ni]; + struct lsa_ForestTrustRecord tftr = { + .flags = 0, + }; + const char *ndns = NULL; + bool ignore_new = false; + bool found_old = false; + uint32_t mi; + + if (nftr == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + ndns = nftr->forest_trust_data.top_level_name.string; + if (ndns == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + cmp = dns_cmp(tdo->domain_name.string, ndns); + if (cmp == DNS_CMP_MATCH) { + status = dsdb_trust_forest_info_add_record(mfti, nftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + for (mi = 0; mi < mfti->count; mi++) { + const struct lsa_ForestTrustRecord *mftr = + mfti->entries[mi]; + const char *mdns = NULL; + + /* + * we just added this above, so we're sure to have a + * valid LSA_FOREST_TRUST_TOP_LEVEL_NAME record + */ + mdns = mftr->forest_trust_data.top_level_name.string; + + cmp = dns_cmp(mdns, ndns); + switch (cmp) { + case DNS_CMP_MATCH: + case DNS_CMP_SECOND_IS_CHILD: + ignore_new = true; + break; + } + + if (ignore_new) { + break; + } + } + + if (ignore_new) { + continue; + } + + /* + * make a temporary copy where we can change time and flags + */ + tftr = *nftr; + + for (oi = 0; oi < ofti->count; oi++) { + const struct lsa_ForestTrustRecord *oftr = + ofti->entries[oi]; + const char *odns = NULL; + + if (oftr == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + if (oftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + odns = oftr->forest_trust_data.top_level_name.string; + if (odns == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + cmp = dns_cmp(odns, ndns); + if (cmp != DNS_CMP_MATCH) { + continue; + } + + found_old = true; + tftr.flags = oftr->flags; + tftr.time = oftr->time; + } + + if (!found_old) { + tftr.flags = LSA_TLN_DISABLED_NEW; + tftr.time = 0; + } + + status = dsdb_trust_forest_info_add_record(mfti, &tftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + /* + * Now we add all unique (based on their SID) domains + * and may keep the flags and time values. + */ + for (ni = 0; ni < nfti->count; ni++) { + const struct lsa_ForestTrustRecord *nftr = nfti->entries[ni]; + struct lsa_ForestTrustRecord tftr = { + .flags = 0, + }; + const struct lsa_ForestTrustDomainInfo *nd = NULL; + const char *ndns = NULL; + const char *nnbt = NULL; + bool ignore_new = false; + bool found_old = false; + uint32_t mi; + + if (nftr == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (nftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + nd = &nftr->forest_trust_data.domain_info; + if (nd->domain_sid == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + ndns = nd->dns_domain_name.string; + if (ndns == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + nnbt = nd->netbios_domain_name.string; + if (nnbt == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + for (mi = 0; mi < mfti->count; mi++) { + const struct lsa_ForestTrustRecord *mftr = + mfti->entries[mi]; + const struct lsa_ForestTrustDomainInfo *md = NULL; + + if (mftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + /* + * we just added this above, so we're sure to have a + * valid LSA_FOREST_TRUST_DOMAIN_INFO record + */ + md = &mftr->forest_trust_data.domain_info; + + cmp = dom_sid_compare(nd->domain_sid, md->domain_sid); + if (cmp == 0) { + ignore_new = true; + break; + } + } + + if (ignore_new) { + continue; + } + + /* + * make a temporary copy where we can change time and flags + */ + tftr = *nftr; + + for (oi = 0; oi < ofti->count; oi++) { + const struct lsa_ForestTrustRecord *oftr = + ofti->entries[oi]; + const struct lsa_ForestTrustDomainInfo *od = NULL; + const char *onbt = NULL; + + if (oftr == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + if (oftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + od = &oftr->forest_trust_data.domain_info; + onbt = od->netbios_domain_name.string; + if (onbt == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + cmp = strcasecmp(onbt, nnbt); + if (cmp != 0) { + continue; + } + + found_old = true; + tftr.flags = oftr->flags; + tftr.time = oftr->time; + } + + if (!found_old) { + tftr.flags = 0; + tftr.time = 0; + } + + status = dsdb_trust_forest_info_add_record(mfti, &tftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + /* + * We keep old domain records disabled by the admin + * if not already in the list. + */ + for (oi = 0; oi < ofti->count; oi++) { + const struct lsa_ForestTrustRecord *oftr = + ofti->entries[oi]; + const struct lsa_ForestTrustDomainInfo *od = NULL; + const char *odns = NULL; + const char *onbt = NULL; + bool ignore_old = true; + uint32_t mi; + + if (oftr == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + if (oftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + od = &oftr->forest_trust_data.domain_info; + odns = od->dns_domain_name.string; + if (odns == NULL) { + /* + * broken record => ignore... + */ + continue; + } + onbt = od->netbios_domain_name.string; + if (onbt == NULL) { + /* + * broken record => ignore... + */ + continue; + } + if (od->domain_sid == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + if (oftr->flags & LSA_NB_DISABLED_ADMIN) { + ignore_old = false; + } else if (oftr->flags & LSA_SID_DISABLED_ADMIN) { + ignore_old = false; + } + + for (mi = 0; mi < mfti->count; mi++) { + const struct lsa_ForestTrustRecord *mftr = + mfti->entries[mi]; + const struct lsa_ForestTrustDomainInfo *md = NULL; + + if (mftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + /* + * we just added this above, so we're sure to have a + * valid LSA_FOREST_TRUST_DOMAIN_INFO record + */ + md = &mftr->forest_trust_data.domain_info; + + cmp = dom_sid_compare(od->domain_sid, md->domain_sid); + if (cmp == 0) { + ignore_old = true; + break; + } + } + + if (ignore_old) { + continue; + } + + status = dsdb_trust_forest_info_add_record(mfti, oftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + /* + * Finally we readd top level exclusions, + * if they still match a top level name. + */ + for (oi = 0; oi < ofti->count; oi++) { + const struct lsa_ForestTrustRecord *oftr = + ofti->entries[oi]; + const char *odns = NULL; + bool ignore_old = false; + uint32_t mi; + + if (oftr == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + if (oftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX) { + continue; + } + + odns = oftr->forest_trust_data.top_level_name_ex.string; + if (odns == NULL) { + /* + * broken record => ignore... + */ + continue; + } + + for (mi = 0; mi < mfti->count; mi++) { + const struct lsa_ForestTrustRecord *mftr = + mfti->entries[mi]; + const char *mdns = NULL; + + if (mftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + /* + * we just added this above, so we're sure to have a + * valid LSA_FOREST_TRUST_TOP_LEVEL_NAME. + */ + mdns = mftr->forest_trust_data.top_level_name.string; + + cmp = dns_cmp(mdns, odns); + switch (cmp) { + case DNS_CMP_MATCH: + case DNS_CMP_SECOND_IS_CHILD: + break; + default: + ignore_old = true; + break; + } + + if (ignore_old) { + break; + } + } + + if (ignore_old) { + continue; + } + + status = dsdb_trust_forest_info_add_record(mfti, oftr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + *_mfti = talloc_move(mem_ctx, &mfti); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_search_tdo(struct ldb_context *sam_ctx, + const char *netbios, const char *dns, + const char * const *attrs, + TALLOC_CTX *mem_ctx, + struct ldb_message **msg) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int ret; + struct ldb_dn *system_dn = NULL; + char *netbios_encoded = NULL; + char *dns_encoded = NULL; + char *filter = NULL; + + *msg = NULL; + + if (netbios == NULL && dns == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + system_dn = samdb_system_container_dn(sam_ctx, frame); + if (system_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + if (netbios != NULL) { + netbios_encoded = ldb_binary_encode_string(frame, netbios); + if (netbios_encoded == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + + if (dns != NULL) { + dns_encoded = ldb_binary_encode_string(frame, dns); + if (dns_encoded == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + + if (netbios != NULL && dns != NULL) { + filter = talloc_asprintf(frame, + "(&(objectClass=trustedDomain)" + "(|(trustPartner=%s)(flatName=%s))" + ")", + dns_encoded, netbios_encoded); + if (filter == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } else if (netbios != NULL) { + filter = talloc_asprintf(frame, + "(&(objectClass=trustedDomain)(flatName=%s))", + netbios_encoded); + if (filter == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } else if (dns != NULL) { + filter = talloc_asprintf(frame, + "(&(objectClass=trustedDomain)(trustPartner=%s))", + dns_encoded); + if (filter == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + + ret = dsdb_search_one(sam_ctx, mem_ctx, msg, + system_dn, + LDB_SCOPE_ONELEVEL, attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "%s", filter); + if (ret != LDB_SUCCESS) { + NTSTATUS status = dsdb_ldb_err_to_ntstatus(ret); + DEBUG(3, ("Failed to search for %s: %s - %s\n", + filter, nt_errstr(status), ldb_errstring(sam_ctx))); + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_search_tdo_by_type(struct ldb_context *sam_ctx, + enum netr_SchannelType type, + const char *name, + const char * const *attrs, + TALLOC_CTX *mem_ctx, + struct ldb_message **msg) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + size_t len; + char trailer = '$'; + bool require_trailer = true; + char *encoded_name = NULL; + const char *netbios = NULL; + const char *dns = NULL; + + if (type != SEC_CHAN_DOMAIN && type != SEC_CHAN_DNS_DOMAIN) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (type == SEC_CHAN_DNS_DOMAIN) { + trailer = '.'; + require_trailer = false; + } + + encoded_name = ldb_binary_encode_string(frame, name); + if (encoded_name == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + len = strlen(encoded_name); + if (len < 2) { + TALLOC_FREE(frame); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (require_trailer && encoded_name[len - 1] != trailer) { + TALLOC_FREE(frame); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + encoded_name[len - 1] = '\0'; + + if (type == SEC_CHAN_DNS_DOMAIN) { + dns = encoded_name; + } else { + netbios = encoded_name; + } + + status = dsdb_trust_search_tdo(sam_ctx, netbios, dns, + attrs, mem_ctx, msg); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_search_tdo_by_sid(struct ldb_context *sam_ctx, + const struct dom_sid *sid, + const char * const *attrs, + TALLOC_CTX *mem_ctx, + struct ldb_message **msg) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int ret; + struct ldb_dn *system_dn = NULL; + char *encoded_sid = NULL; + char *filter = NULL; + + *msg = NULL; + + if (sid == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + encoded_sid = ldap_encode_ndr_dom_sid(frame, sid); + if (encoded_sid == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + system_dn = samdb_system_container_dn(sam_ctx, frame); + if (system_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(frame, + "(&" + "(objectClass=trustedDomain)" + "(securityIdentifier=%s)" + ")", + encoded_sid); + if (filter == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = dsdb_search_one(sam_ctx, mem_ctx, msg, + system_dn, + LDB_SCOPE_ONELEVEL, attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "%s", filter); + if (ret != LDB_SUCCESS) { + NTSTATUS status = dsdb_ldb_err_to_ntstatus(ret); + DEBUG(3, ("Failed to search for %s: %s - %s\n", + filter, nt_errstr(status), ldb_errstring(sam_ctx))); + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_get_incoming_passwords(struct ldb_message *msg, + TALLOC_CTX *mem_ctx, + struct samr_Password **_current, + struct samr_Password **_previous) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct samr_Password __current = { + .hash = {0}, + }; + struct samr_Password __previous = { + .hash = {0}, + }; + struct samr_Password *current = NULL; + struct samr_Password *previous = NULL; + const struct ldb_val *blob = NULL; + enum ndr_err_code ndr_err; + struct trustAuthInOutBlob incoming = { + .count = 0, + }; + uint32_t i; + + if (_current != NULL) { + *_current = NULL; + } + if (_previous != NULL) { + *_previous = NULL; + } + + blob = ldb_msg_find_ldb_val(msg, "trustAuthIncoming"); + if (blob == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_ACCOUNT_DISABLED; + } + + /* ldb_val is equivalent to DATA_BLOB */ + ndr_err = ndr_pull_struct_blob_all(blob, frame, &incoming, + (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + for (i = 0; i < incoming.current.count; i++) { + struct AuthenticationInformation *a = + &incoming.current.array[i]; + + if (current != NULL) { + break; + } + + switch (a->AuthType) { + case TRUST_AUTH_TYPE_NONE: + case TRUST_AUTH_TYPE_VERSION: + break; + case TRUST_AUTH_TYPE_NT4OWF: + current = &a->AuthInfo.nt4owf.password; + break; + case TRUST_AUTH_TYPE_CLEAR: + mdfour(__current.hash, + a->AuthInfo.clear.password, + a->AuthInfo.clear.size); + current = &__current; + break; + } + } + + if (current == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + for (i = 0; i < incoming.previous.count; i++) { + struct AuthenticationInformation *a = + &incoming.previous.array[i]; + + if (previous != NULL) { + break; + } + + switch (a->AuthType) { + case TRUST_AUTH_TYPE_NONE: + case TRUST_AUTH_TYPE_VERSION: + break; + case TRUST_AUTH_TYPE_NT4OWF: + previous = &a->AuthInfo.nt4owf.password; + break; + case TRUST_AUTH_TYPE_CLEAR: + mdfour(__previous.hash, + a->AuthInfo.clear.password, + a->AuthInfo.clear.size); + previous = &__previous; + break; + } + } + + if (previous == NULL) { + previous = current; + } + + if (_current != NULL) { + *_current = talloc_memdup(mem_ctx, current, sizeof(*current)); + if (*_current == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + if (_previous != NULL) { + *_previous = + talloc_memdup(mem_ctx, previous, sizeof(*previous)); + if (*_previous == NULL) { + if (_current != NULL) { + TALLOC_FREE(*_current); + } + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + ZERO_STRUCTP(current); + ZERO_STRUCTP(previous); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS dsdb_trust_search_tdos(struct ldb_context *sam_ctx, + const char *exclude, + const char * const *attrs, + TALLOC_CTX *mem_ctx, + struct ldb_result **res) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int ret; + struct ldb_dn *system_dn = NULL; + const char *filter = NULL; + char *exclude_encoded = NULL; + + *res = NULL; + + system_dn = samdb_system_container_dn(sam_ctx, frame); + if (system_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + if (exclude != NULL) { + exclude_encoded = ldb_binary_encode_string(frame, exclude); + if (exclude_encoded == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(frame, + "(&(objectClass=trustedDomain)" + "(!(|(trustPartner=%s)(flatName=%s)))" + ")", + exclude_encoded, exclude_encoded); + if (filter == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } else { + filter = "(objectClass=trustedDomain)"; + } + + ret = dsdb_search(sam_ctx, mem_ctx, res, + system_dn, + LDB_SCOPE_ONELEVEL, attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "%s", filter); + if (ret != LDB_SUCCESS) { + NTSTATUS status = dsdb_ldb_err_to_ntstatus(ret); + DEBUG(3, ("Failed to search for %s: %s - %s\n", + filter, nt_errstr(status), ldb_errstring(sam_ctx))); + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +struct dsdb_trust_routing_domain; + +struct dsdb_trust_routing_table { + struct dsdb_trust_routing_domain *domains; +}; + +struct dsdb_trust_routing_domain { + struct dsdb_trust_routing_domain *prev, *next; + + struct lsa_TrustDomainInfoInfoEx *tdo; + + struct lsa_ForestTrustDomainInfo di; + + struct lsa_ForestTrustInformation *fti; +}; + +NTSTATUS dsdb_trust_routing_table_load(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct dsdb_trust_routing_table **_table) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct dsdb_trust_routing_table *table; + struct dsdb_trust_routing_domain *d = NULL; + struct ldb_dn *domain_dn = NULL; + struct lsa_TrustDomainInfoInfoEx *root_trust_tdo = NULL; + struct lsa_TrustDomainInfoInfoEx *trust_parent_tdo = NULL; + struct lsa_TrustDomainInfoInfoEx *root_direction_tdo = NULL; + const char * const trusts_attrs[] = { + "securityIdentifier", + "flatName", + "trustPartner", + "trustAttributes", + "trustDirection", + "trustType", + "msDS-TrustForestTrustInfo", + NULL + }; + struct ldb_result *trusts_res = NULL; + unsigned int i; + NTSTATUS status; + + *_table = NULL; + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + table = talloc_zero(mem_ctx, struct dsdb_trust_routing_table); + if (table == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(frame, table); + + d = talloc_zero(table, struct dsdb_trust_routing_domain); + if (d == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = dsdb_trust_crossref_tdo_info(d, sam_ctx, + domain_dn, NULL, + &d->tdo, + &root_trust_tdo, + &trust_parent_tdo); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + /* + * d->tdo should not be NULL of status above is 'NT_STATUS_OK' + * check is needed to satisfy clang static checker + */ + if (d->tdo == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + d->di.domain_sid = d->tdo->sid; + d->di.netbios_domain_name.string = d->tdo->netbios_name.string; + d->di.dns_domain_name.string = d->tdo->domain_name.string; + + if (root_trust_tdo != NULL) { + root_direction_tdo = root_trust_tdo; + } else if (trust_parent_tdo != NULL) { + root_direction_tdo = trust_parent_tdo; + } + + if (root_direction_tdo == NULL) { + /* we're the forest root */ + status = dsdb_trust_xref_forest_info(d, sam_ctx, &d->fti); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + DLIST_ADD(table->domains, d); + + status = dsdb_trust_search_tdos(sam_ctx, NULL, trusts_attrs, + frame, &trusts_res); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + for (i = 0; i < trusts_res->count; i++) { + bool ok; + int cmp; + + d = talloc_zero(table, struct dsdb_trust_routing_domain); + if (d == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = dsdb_trust_parse_tdo_info(d, + trusts_res->msgs[i], + &d->tdo); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + d->di.domain_sid = d->tdo->sid; + d->di.netbios_domain_name.string = d->tdo->netbios_name.string; + d->di.dns_domain_name.string = d->tdo->domain_name.string; + + DLIST_ADD_END(table->domains, d); + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + struct ForestTrustInfo *fti = NULL; + + status = dsdb_trust_parse_forest_info(frame, + trusts_res->msgs[i], + &fti); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + fti = NULL; + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + if (fti == NULL) { + continue; + } + + status = dsdb_trust_forest_info_to_lsa(d, fti, &d->fti); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + continue; + } + + if (!(d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) { + continue; + } + + if (root_direction_tdo == NULL) { + continue; + } + + ok = dom_sid_equal(root_direction_tdo->sid, d->tdo->sid); + if (!ok) { + continue; + } + + cmp = strcasecmp_m(root_direction_tdo->netbios_name.string, + d->tdo->netbios_name.string); + if (cmp != 0) { + continue; + } + + cmp = strcasecmp_m(root_direction_tdo->domain_name.string, + d->tdo->domain_name.string); + if (cmp != 0) { + continue; + } + + /* this our route to the forest root */ + status = dsdb_trust_xref_forest_info(d, sam_ctx, &d->fti); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + *_table = talloc_move(mem_ctx, &table); + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +static void dsdb_trust_update_best_tln( + const struct dsdb_trust_routing_domain **best_d, + const char **best_tln, + const struct dsdb_trust_routing_domain *d, + const char *tln) +{ + int cmp; + + if (*best_tln == NULL) { + *best_tln = tln; + *best_d = d; + return; + } + + cmp = dns_cmp(*best_tln, tln); + if (cmp != DNS_CMP_FIRST_IS_CHILD) { + return; + } + + *best_tln = tln; + *best_d = d; +} + +const struct lsa_TrustDomainInfoInfoEx *dsdb_trust_routing_by_name( + const struct dsdb_trust_routing_table *table, + const char *name) +{ + const struct dsdb_trust_routing_domain *best_d = NULL; + const char *best_tln = NULL; + const struct dsdb_trust_routing_domain *d = NULL; + + if (name == NULL) { + return NULL; + } + + for (d = table->domains; d != NULL; d = d->next) { + bool transitive = false; + bool allow_netbios = false; + bool exclude = false; + uint32_t i; + + if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + /* + * Only uplevel trusts have top level names + */ + continue; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + transitive = true; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + transitive = true; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) { + transitive = false; + } + + if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + transitive = false; + } + + switch (d->tdo->trust_type) { + case LSA_TRUST_TYPE_UPLEVEL: + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY) { + break; + } + allow_netbios = true; + break; + case LSA_TRUST_TYPE_DOWNLEVEL: + allow_netbios = true; + break; + default: + allow_netbios = false; + break; + } + + if (!transitive || d->fti == NULL) { + int cmp; + + if (allow_netbios) { + cmp = dns_cmp(name, d->tdo->netbios_name.string); + if (cmp == DNS_CMP_MATCH) { + /* + * exact match + */ + return d->tdo; + } + } + + cmp = dns_cmp(name, d->tdo->domain_name.string); + if (cmp == DNS_CMP_MATCH) { + /* + * exact match + */ + return d->tdo; + } + if (cmp != DNS_CMP_FIRST_IS_CHILD) { + continue; + } + + if (!transitive) { + continue; + } + + dsdb_trust_update_best_tln(&best_d, &best_tln, d, + d->tdo->domain_name.string); + continue; + } + + exclude = dsdb_trust_find_tln_ex_match(d->fti, name); + if (exclude) { + continue; + } + + for (i = 0; i < d->fti->count; i++ ) { + const struct lsa_ForestTrustRecord *f = d->fti->entries[i]; + const struct lsa_ForestTrustDomainInfo *di = NULL; + const char *fti_nbt = NULL; + int cmp; + + if (!allow_netbios) { + break; + } + + if (f == NULL) { + /* broken record */ + continue; + } + + if (f->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + if (f->flags & LSA_NB_DISABLED_MASK) { + /* + * any flag disables the entry. + */ + continue; + } + + di = &f->forest_trust_data.domain_info; + fti_nbt = di->netbios_domain_name.string; + if (fti_nbt == NULL) { + /* broken record */ + continue; + } + + cmp = dns_cmp(name, fti_nbt); + if (cmp == DNS_CMP_MATCH) { + /* + * exact match + */ + return d->tdo; + } + } + + for (i = 0; i < d->fti->count; i++ ) { + const struct lsa_ForestTrustRecord *f = d->fti->entries[i]; + const union lsa_ForestTrustData *u = NULL; + const char *fti_tln = NULL; + int cmp; + + if (f == NULL) { + /* broken record */ + continue; + } + + if (f->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) { + continue; + } + + if (f->flags & LSA_TLN_DISABLED_MASK) { + /* + * any flag disables the entry. + */ + continue; + } + + u = &f->forest_trust_data; + fti_tln = u->top_level_name.string; + if (fti_tln == NULL) { + continue; + } + + cmp = dns_cmp(name, fti_tln); + switch (cmp) { + case DNS_CMP_MATCH: + case DNS_CMP_FIRST_IS_CHILD: + dsdb_trust_update_best_tln(&best_d, &best_tln, + d, fti_tln); + break; + default: + break; + } + } + } + + if (best_d != NULL) { + return best_d->tdo; + } + + return NULL; +} + +const struct lsa_TrustDomainInfoInfoEx *dsdb_trust_domain_by_sid( + const struct dsdb_trust_routing_table *table, + const struct dom_sid *sid, + const struct lsa_ForestTrustDomainInfo **pdi) +{ + const struct dsdb_trust_routing_domain *d = NULL; + + if (pdi != NULL) { + *pdi = NULL; + } + + if (sid == NULL) { + return NULL; + } + + for (d = table->domains; d != NULL; d = d->next) { + bool transitive = false; + uint32_t i; + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + transitive = true; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + transitive = true; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) { + transitive = false; + } + + if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + transitive = false; + } + + if (!transitive || d->fti == NULL) { + bool match = false; + + match = dom_sid_equal(d->di.domain_sid, sid); + if (match) { + /* + * exact match, it's the domain itself. + */ + if (pdi != NULL) { + *pdi = &d->di; + } + return d->tdo; + } + continue; + } + + for (i = 0; i < d->fti->count; i++ ) { + const struct lsa_ForestTrustRecord *f = d->fti->entries[i]; + const struct lsa_ForestTrustDomainInfo *di = NULL; + const struct dom_sid *fti_sid = NULL; + bool match = false; + + if (f == NULL) { + /* broken record */ + continue; + } + + if (f->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + if (f->flags & LSA_SID_DISABLED_MASK) { + /* + * any flag disables the entry. + */ + continue; + } + + di = &f->forest_trust_data.domain_info; + fti_sid = di->domain_sid; + if (fti_sid == NULL) { + /* broken record */ + continue; + } + + match = dom_sid_equal(fti_sid, sid); + if (match) { + /* + * exact match, it's a domain in the forest. + */ + if (pdi != NULL) { + *pdi = di; + } + return d->tdo; + } + } + } + + return NULL; +} + +const struct lsa_TrustDomainInfoInfoEx *dsdb_trust_domain_by_name( + const struct dsdb_trust_routing_table *table, + const char *name, + const struct lsa_ForestTrustDomainInfo **pdi) +{ + const struct dsdb_trust_routing_domain *d = NULL; + + if (pdi != NULL) { + *pdi = NULL; + } + + if (name == NULL) { + return NULL; + } + + for (d = table->domains; d != NULL; d = d->next) { + bool transitive = false; + uint32_t i; + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + transitive = true; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + transitive = true; + } + + if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) { + transitive = false; + } + + if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + transitive = false; + } + + if (!transitive || d->fti == NULL) { + bool match = false; + + match = strequal_m(d->di.netbios_domain_name.string, + name); + if (match) { + /* + * exact match for netbios name, + * it's the domain itself. + */ + if (pdi != NULL) { + *pdi = &d->di; + } + return d->tdo; + } + match = strequal_m(d->di.dns_domain_name.string, + name); + if (match) { + /* + * exact match for dns name, + * it's the domain itself. + */ + if (pdi != NULL) { + *pdi = &d->di; + } + return d->tdo; + } + continue; + } + + for (i = 0; i < d->fti->count; i++ ) { + const struct lsa_ForestTrustRecord *f = d->fti->entries[i]; + const struct lsa_ForestTrustDomainInfo *di = NULL; + bool match = false; + + if (f == NULL) { + /* broken record */ + continue; + } + + if (f->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + di = &f->forest_trust_data.domain_info; + + if (!(f->flags & LSA_NB_DISABLED_MASK)) { + match = strequal_m(di->netbios_domain_name.string, + name); + if (match) { + /* + * exact match for netbios name, + * it's a domain in the forest. + */ + if (pdi != NULL) { + *pdi = di; + } + return d->tdo; + } + } + + if (!(f->flags & LSA_TLN_DISABLED_MASK)) { + match = strequal_m(di->dns_domain_name.string, + name); + if (match) { + /* + * exact match for dns name, + * it's a domain in the forest. + */ + if (pdi != NULL) { + *pdi = di; + } + return d->tdo; + } + } + } + } + + return NULL; +} |