+
+/*
+ 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_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_DN_IN_STORAGE_FORMAT) {
+ ret = ldb_request_add_control(req, DSDB_CONTROL_DN_STORAGE_FORMAT_OID, true, 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;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ 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;
+}
+
+/*
+ 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);
+}
+
+
+/*
+ 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 **_res,
+ struct ldb_dn *basedn,
+ const char * const *attrs,
+ uint32_t dsdb_flags)
+{
+ int ret;
+ struct ldb_request *req;
+ struct ldb_result *res;
+
+ res = talloc_zero(mem_ctx, struct ldb_result);
+ if (!res) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ 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(res);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ 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) {
+ talloc_free(res);
+ return ret;
+ }
+
+ *_res = res;
+ return LDB_SUCCESS;
+}
+
+/*
+ general search with dsdb_flags for controls
+ */
+int dsdb_search(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ 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);
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ 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_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ 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);
+ return ret;
+ }
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_SEARCH_ONE_ONLY) {
+ if (res->count == 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ if (res->count != 1) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ *_res = talloc_steal(mem_ctx, res);
+ 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);
+
+ dsdb_flags |= DSDB_SEARCH_ONE_ONLY;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ 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_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ ret = dsdb_search(ldb, tmp_ctx, &res, basedn, scope, attrs,
+ dsdb_flags, "%s", expression);
+ 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;
+}
+
+/*
+ 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;
+
+ 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_ERR_OPERATIONS_ERROR;
+ }
+ dn = msg->dn;
+
+ if (!ldb_dn_remove_child_components(dn, 1)) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ 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_ERR_OPERATIONS_ERROR;
+ }
+
+ 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 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_ERR_OPERATIONS_ERROR;
+ }
+
+ 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_ERR_OPERATIONS_ERROR;
+ }
+
+ 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_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+const char *rodc_fas_list[] = {"ms-PKI-DPAPIMasterKeys",
+ "ms-PKI-AccountCredentials",
+ "ms-PKI-RoamingTimeStamp",
+ "ms-FVE-KeyPackage",
+ "ms-FVE-RecoveryGuid",
+ "ms-FVE-RecoveryInformation",
+ "ms-FVE-RecoveryPassword",
+ "ms-FVE-VolumeGuid",
+ "ms-TPM-OwnerInformation",
+ NULL};
+/*
+ check if the attribute belongs to the RODC filtered attribute set
+*/
+bool dsdb_attr_in_rodc_fas(uint32_t replica_flags, const struct dsdb_attribute *sa)
+{
+ int rodc_filtered_flags = SEARCH_FLAG_RODC_ATTRIBUTE | SEARCH_FLAG_CONFIDENTIAL;
+ bool drs_write_replica = ((replica_flags & DRSUAPI_DRS_WRIT_REP) == 0);
+
+ if (drs_write_replica && (sa->searchFlags & rodc_filtered_flags)) {
+ return true;
+ }
+ if (drs_write_replica && is_attr_in_list(rodc_fas_list, sa->cn)) {
+ return true;
+ }
+ return false;
+}