+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_member_check(struct samldb_ctx *ac)
+{
+ const char * const attrs[] = { "objectSid", NULL };
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_message_element *el;
+ struct ldb_dn *member_dn;
+ struct dom_sid *sid;
+ struct ldb_result *res;
+ struct dom_sid *group_sid;
+ unsigned int i, j;
+ int ret;
+
+ /* Fetch information from the existing object */
+
+ ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, ac->req, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ return ldb_operr(ldb);
+ }
+
+ group_sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid");
+ if (group_sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* We've to walk over all modification entries and consider the "member"
+ * ones. */
+ for (i = 0; i < ac->msg->num_elements; i++) {
+ if (ldb_attr_cmp(ac->msg->elements[i].name, "member") != 0) {
+ continue;
+ }
+
+ el = &ac->msg->elements[i];
+ for (j = 0; j < el->num_values; j++) {
+ struct ldb_result *group_res;
+ const char *group_attrs[] = { "primaryGroupID" , NULL };
+ uint32_t prim_group_rid;
+
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+ /* Deletes will be handled in
+ * repl_meta_data, and deletes not
+ * matching a member will return
+ * LDB_ERR_UNWILLING_TO_PERFORM
+ * there */
+ continue;
+ }
+
+ member_dn = ldb_dn_from_ldb_val(ac, ldb,
+ &el->values[j]);
+ if (!ldb_dn_validate(member_dn)) {
+ return ldb_operr(ldb);
+ }
+
+ /* Denies to add "member"s to groups which are primary
+ * ones for them - in this case return
+ * ERR_ENTRY_ALREADY_EXISTS. */
+
+ ret = dsdb_module_search_dn(ac->module, ac, &group_res,
+ member_dn, group_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* member DN doesn't exist yet */
+ continue;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ prim_group_rid = ldb_msg_find_attr_as_uint(group_res->msgs[0], "primaryGroupID", (uint32_t)-1);
+ if (prim_group_rid == (uint32_t) -1) {
+ /* the member hasn't to be a user account ->
+ * therefore no check needed in this case. */
+ continue;
+ }
+
+ sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb),
+ prim_group_rid);
+ if (sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (dom_sid_equal(group_sid, sid)) {
+ ldb_asprintf_errstring(ldb,
+ "samldb: member %s already set via primaryGroupID %u",
+ ldb_dn_get_linearized(member_dn), prim_group_rid);
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ }
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+/* SAM objects have special rules regarding the "description" attribute on
+ * modify operations. */
+static int samldb_description_check(struct samldb_ctx *ac, bool *modified)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char * const attrs[] = { "objectClass", "description", NULL };
+ struct ldb_result *res;
+ unsigned int i;
+ int ret;
+
+ /* Fetch information from the existing object */
+ ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, ac->req,
+ "(|(objectclass=user)(objectclass=group)(objectclass=samDomain)(objectclass=samServer))");
+ if (ret != LDB_SUCCESS) {
+ /* don't treat it specially ... let normal error codes
+ happen from other places */
+ ldb_reset_err_string(ldb);
+ return LDB_SUCCESS;
+ }
+ if (res->count == 0) {
+ /* we didn't match the filter */
+ talloc_free(res);
+ return LDB_SUCCESS;
+ }
+
+ /* We've to walk over all modification entries and consider the
+ * "description" ones. */
+ for (i = 0; i < ac->msg->num_elements; i++) {
+ if (ldb_attr_cmp(ac->msg->elements[i].name, "description") == 0) {
+ ac->msg->elements[i].flags |= LDB_FLAG_INTERNAL_FORCE_SINGLE_VALUE_CHECK;
+ *modified = true;
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+/* This trigger adapts the "servicePrincipalName" attributes if the
+ * "dNSHostName" and/or "sAMAccountName" attribute change(s) */
+static int samldb_service_principal_names_change(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_message_element *el = NULL, *el2 = NULL;
+ struct ldb_message *msg;
+ const char * const attrs[] = { "servicePrincipalName", NULL };
+ struct ldb_result *res;
+ const char *dns_hostname = NULL, *old_dns_hostname = NULL,
+ *sam_accountname = NULL, *old_sam_accountname = NULL;
+ unsigned int i, j;
+ int ret;
+
+ el = dsdb_get_single_valued_attr(ac->msg, "dNSHostName",
+ ac->req->operation);
+ el2 = dsdb_get_single_valued_attr(ac->msg, "sAMAccountName",
+ ac->req->operation);
+ if ((el == NULL) && (el2 == NULL)) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+
+ /* Create a temporary message for fetching the "dNSHostName" */
+ if (el != NULL) {
+ const char *dns_attrs[] = { "dNSHostName", NULL };
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ dns_hostname = talloc_strdup(ac,
+ ldb_msg_find_attr_as_string(msg, "dNSHostName", NULL));
+ if (dns_hostname == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ talloc_free(msg);
+
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn,
+ dns_attrs, DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret == LDB_SUCCESS) {
+ old_dns_hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
+ }
+ }
+
+ /* Create a temporary message for fetching the "sAMAccountName" */
+ if (el2 != NULL) {
+ char *tempstr, *tempstr2;
+ const char *acct_attrs[] = { "sAMAccountName", NULL };
+
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(msg, el2, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ tempstr = talloc_strdup(ac,
+ ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL));
+ talloc_free(msg);
+
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, acct_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret == LDB_SUCCESS) {
+ tempstr2 = talloc_strdup(ac,
+ ldb_msg_find_attr_as_string(res->msgs[0],
+ "sAMAccountName", NULL));
+ }
+
+
+ /* The "sAMAccountName" needs some additional trimming: we need
+ * to remove the trailing "$"s if they exist. */
+ if ((tempstr != NULL) && (tempstr[0] != '\0') &&
+ (tempstr[strlen(tempstr) - 1] == '$')) {
+ tempstr[strlen(tempstr) - 1] = '\0';
+ }
+ if ((tempstr2 != NULL) && (tempstr2[0] != '\0') &&
+ (tempstr2[strlen(tempstr2) - 1] == '$')) {
+ tempstr2[strlen(tempstr2) - 1] = '\0';
+ }
+ sam_accountname = tempstr;
+ old_sam_accountname = tempstr2;
+ }
+
+ if (old_dns_hostname == NULL) {
+ /* we cannot change when the old name is unknown */
+ dns_hostname = NULL;
+ }
+ if ((old_dns_hostname != NULL) && (dns_hostname != NULL) &&
+ (strcasecmp_m(old_dns_hostname, dns_hostname) == 0)) {
+ /* The "dNSHostName" didn't change */
+ dns_hostname = NULL;
+ }
+
+ if (old_sam_accountname == NULL) {
+ /* we cannot change when the old name is unknown */
+ sam_accountname = NULL;
+ }
+ if ((old_sam_accountname != NULL) && (sam_accountname != NULL) &&
+ (strcasecmp_m(old_sam_accountname, sam_accountname) == 0)) {
+ /* The "sAMAccountName" didn't change */
+ sam_accountname = NULL;
+ }
+
+ if ((dns_hostname == NULL) && (sam_accountname == NULL)) {
+ /* Well, there are information missing (old name(s)) or the
+ * names didn't change. We've nothing to do and can exit here */
+ return LDB_SUCCESS;
+ }
+
+ /* Potential "servicePrincipalName" changes in the same request have to
+ * be handled before the update (Windows behaviour). */
+ el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
+ if (el != NULL) {
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ msg->dn = ac->msg->dn;
+
+ do {
+ ret = ldb_msg_add(msg, el, el->flags);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_msg_remove_element(ac->msg, el);
+
+ el = ldb_msg_find_element(ac->msg,
+ "servicePrincipalName");
+ } while (el != NULL);
+
+ ret = dsdb_module_modify(ac->module, msg,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(msg);
+ }
+
+ /* Fetch the "servicePrincipalName"s if any */
+ ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if ((res->count != 1) || (res->msgs[0]->num_elements > 1)) {
+ return ldb_operr(ldb);
+ }
+
+ if (res->msgs[0]->num_elements == 1) {
+ /*
+ * Yes, we do have "servicePrincipalName"s. First we update them
+ * locally, that means we do always substitute the current
+ * "dNSHostName" with the new one and/or "sAMAccountName"
+ * without "$" with the new one and then we append the
+ * modified "servicePrincipalName"s as a message element
+ * replace to the modification request (Windows behaviour). We
+ * need also to make sure that the values remain case-
+ * insensitively unique.
+ */
+
+ ret = ldb_msg_add_empty(ac->msg, "servicePrincipalName",
+ LDB_FLAG_MOD_REPLACE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i = 0; i < res->msgs[0]->elements[0].num_values; i++) {
+ char *old_str, *new_str, *pos;
+ const char *tok;
+ struct ldb_val *vals;
+ bool found = false;
+
+ old_str = (char *)
+ res->msgs[0]->elements[0].values[i].data;
+
+ new_str = talloc_strdup(ac->msg,
+ strtok_r(old_str, "/", &pos));
+ if (new_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ while ((tok = strtok_r(NULL, "/", &pos)) != NULL) {
+ if ((dns_hostname != NULL) &&
+ (strcasecmp_m(tok, old_dns_hostname) == 0)) {
+ tok = dns_hostname;
+ }
+ if ((sam_accountname != NULL) &&
+ (strcasecmp_m(tok, old_sam_accountname) == 0)) {
+ tok = sam_accountname;
+ }
+
+ new_str = talloc_asprintf(ac->msg, "%s/%s",
+ new_str, tok);
+ if (new_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ }
+
+ /* Uniqueness check */
+ for (j = 0; (!found) && (j < el->num_values); j++) {
+ if (strcasecmp_m((char *)el->values[j].data,
+ new_str) == 0) {
+ found = true;
+ }
+ }
+ if (found) {
+ continue;
+ }
+
+ /*
+ * append the new "servicePrincipalName" -
+ * code derived from ldb_msg_add_value().
+ *
+ * Open coded to make it clear that we must
+ * append to the MOD_REPLACE el created above.
+ */
+ vals = talloc_realloc(ac->msg, el->values,
+ struct ldb_val,
+ el->num_values + 1);
+ if (vals == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ el->values = vals;
+ el->values[el->num_values] = data_blob_string_const(new_str);
+ ++(el->num_values);
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+/* This checks the "fSMORoleOwner" attributes */
+static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char * const no_attrs[] = { NULL };
+ struct ldb_message_element *el;
+ struct ldb_message *tmp_msg;
+ struct ldb_dn *res_dn;
+ struct ldb_result *res;
+ int ret;
+
+ el = dsdb_get_single_valued_attr(ac->msg, "fSMORoleOwner",
+ ac->req->operation);
+ if (el == NULL) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+
+ /* Create a temporary message for fetching the "fSMORoleOwner" */
+ tmp_msg = ldb_msg_new(ac->msg);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(tmp_msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ res_dn = ldb_msg_find_attr_as_dn(ldb, ac, tmp_msg, "fSMORoleOwner");
+ talloc_free(tmp_msg);
+
+ if (res_dn == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!");
+ if (ac->req->operation == LDB_ADD) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ /* Fetched DN has to reference a "nTDSDSA" entry */
+ ret = dsdb_module_search(ac->module, ac, &res, res_dn, LDB_SCOPE_BASE,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED,
+ ac->req, "(objectClass=nTDSDSA)");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ ldb_set_errstring(ldb,
+ "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ talloc_free(res);