+ struct ldb_result *acl_res;
+ struct ldb_result *netbios_res;
+ struct ldb_message_element *el;
+ struct ldb_dn *partitions_dn = samdb_partitions_dn(ldb, tmp_ctx);
+ uint32_t userAccountControl;
+ const char *samAccountName;
+ const char *dnsHostName;
+ const char *netbios_name;
+ struct GUID ntds;
+ char *ntds_guid = NULL;
+
+ static const char *acl_attrs[] = {
+ "samAccountName",
+ "dnsHostName",
+ "userAccountControl",
+ NULL
+ };
+ static const char *netbios_attrs[] = {
+ "nETBIOSName",
+ NULL
+ };
+
+ /* if we have wp, we can do whatever we like */
+ if (acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr) == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module),
+ GUID_DRS_VALIDATE_SPN,
+ SEC_ADS_SELF_WRITE,
+ sid);
+
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx,
+ &acl_res, req->op.mod.message->dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ userAccountControl = ldb_msg_find_attr_as_uint(acl_res->msgs[0], "userAccountControl", 0);
+ dnsHostName = ldb_msg_find_attr_as_string(acl_res->msgs[0], "dnsHostName", NULL);
+ samAccountName = ldb_msg_find_attr_as_string(acl_res->msgs[0], "samAccountName", NULL);
+
+ ret = dsdb_module_search(module, tmp_ctx,
+ &netbios_res, partitions_dn,
+ LDB_SCOPE_ONELEVEL,
+ netbios_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ req,
+ "(ncName=%s)",
+ ldb_dn_get_linearized(ldb_get_default_basedn(ldb)));
+
+ netbios_name = ldb_msg_find_attr_as_string(netbios_res->msgs[0], "nETBIOSName", NULL);
+
+ el = ldb_msg_find_element(req->op.mod.message, "servicePrincipalName");
+ if (!el) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "Error finding element for servicePrincipalName.");
+ }
+
+ /* NTDSDSA objectGuid of object we are checking SPN for */
+ if (userAccountControl & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) {
+ ret = dsdb_module_find_ntdsguid_for_computer(module, tmp_ctx,
+ req->op.mod.message->dn, &ntds, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find NTDSDSA objectGuid for %s: %s",
+ ldb_dn_get_linearized(req->op.mod.message->dn),
+ ldb_strerror(ret));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ntds_guid = GUID_string(tmp_ctx, &ntds);
+ }
+
+ for (i=0; i < el->num_values; i++) {
+ ret = acl_validate_spn_value(tmp_ctx,
+ ldb,
+ (char *)el->values[i].data,
+ userAccountControl,
+ samAccountName,
+ dnsHostName,
+ netbios_name,
+ ntds_guid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int acl_add(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_dn *parent = ldb_dn_get_parent(req, req->op.add.message->dn);
+ struct ldb_context *ldb;
+ const struct dsdb_schema *schema;
+ struct ldb_message_element *oc_el;
+ const struct GUID *guid;
+ struct ldb_dn *nc_root;
+ struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
+
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ if (dsdb_module_am_system(module) || as_system) {
+ return ldb_next_request(module, req);
+ }
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* Creating an NC. There is probably something we should do here,
+ * but we will establish that later */
+
+ ret = dsdb_find_nc_root(ldb, req, req->op.add.message->dn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (ldb_dn_compare(nc_root, req->op.add.message->dn) == 0) {
+ talloc_free(nc_root);
+ return ldb_next_request(module, req);
+ }
+ talloc_free(nc_root);
+
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return ldb_operr(ldb);
+ }
+
+ oc_el = ldb_msg_find_element(req->op.add.message, "objectClass");
+ if (!oc_el || oc_el->num_values == 0) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl: unable to find objectClass on %s\n",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ return ldb_module_done(req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ guid = class_schemaid_guid_by_lDAPDisplayName(schema,
+ (char *)oc_el->values[oc_el->num_values-1].data);
+ ret = dsdb_module_check_access_on_dn(module, req, parent, SEC_ADS_CREATE_CHILD, guid, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, req);
+}
+
+/* ckecks if modifications are allowed on "Member" attribute */
+static int acl_check_self_membership(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct GUID *oc_guid,
+ const struct dsdb_attribute *attr)
+{
+ int ret;
+ unsigned int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_dn *user_dn;
+ struct ldb_message_element *member_el;
+ /* if we have wp, we can do whatever we like */
+ if (acl_check_access_on_attribute(module,
+ mem_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr) == LDB_SUCCESS) {
+ return LDB_SUCCESS;
+ }
+ /* if we are adding/deleting ourselves, check for self membership */
+ ret = dsdb_find_dn_by_sid(ldb, mem_ctx,
+ &acl_user_token(module)->sids[PRIMARY_USER_SID_INDEX],
+ &user_dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ member_el = ldb_msg_find_element(req->op.mod.message, "member");
+ if (!member_el) {
+ return ldb_operr(ldb);
+ }
+ /* user can only remove oneself */
+ if (member_el->num_values == 0) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ for (i = 0; i < member_el->num_values; i++) {
+ if (strcasecmp((const char *)member_el->values[i].data,
+ ldb_dn_get_extended_linearized(mem_ctx, user_dn, 1)) != 0) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ }
+ ret = acl_check_extended_right(mem_ctx, sd, acl_user_token(module),
+ GUID_DRS_SELF_MEMBERSHIP,
+ SEC_ADS_SELF_WRITE,
+ sid);
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ }
+ return ret;
+}
+
+static int acl_check_password_rights(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct GUID *oc_guid,
+ bool userPassword)
+{
+ int ret = LDB_SUCCESS;
+ unsigned int del_attr_cnt = 0, add_attr_cnt = 0, rep_attr_cnt = 0;
+ struct ldb_message_element *el;
+ struct ldb_message *msg;
+ const char *passwordAttrs[] = { "userPassword", "clearTextPassword",
+ "unicodePwd", "dBCSPwd", NULL }, **l;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ msg = ldb_msg_copy_shallow(tmp_ctx, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+ for (l = passwordAttrs; *l != NULL; l++) {
+ if ((!userPassword) && (ldb_attr_cmp(*l, "userPassword") == 0)) {
+ continue;
+ }
+
+ while ((el = ldb_msg_find_element(msg, *l)) != NULL) {
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+ ++del_attr_cnt;
+ }
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_ADD) {
+ ++add_attr_cnt;
+ }
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) {
+ ++rep_attr_cnt;
+ }
+ ldb_msg_remove_element(msg, el);
+ }
+ }
+
+ /* single deletes will be handled by the "password_hash" LDB module
+ * later in the stack, so we let it though here */
+ if ((del_attr_cnt > 0) && (add_attr_cnt == 0) && (rep_attr_cnt == 0)) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ if (ldb_request_get_control(req,
+ DSDB_CONTROL_PASSWORD_CHANGE_OID) != NULL) {
+ /* The "DSDB_CONTROL_PASSWORD_CHANGE_OID" control means that we
+ * have a user password change and not a set as the message
+ * looks like. In it's value blob it contains the NT and/or LM
+ * hash of the old password specified by the user.
+ * This control is used by the SAMR and "kpasswd" password
+ * change mechanisms. */
+ ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module),
+ GUID_DRS_USER_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ }
+ else if (rep_attr_cnt > 0 || (add_attr_cnt != del_attr_cnt)) {
+ ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module),
+ GUID_DRS_FORCE_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ }
+ else if (add_attr_cnt == 1 && del_attr_cnt == 1) {
+ ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module),
+ GUID_DRS_USER_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ /* Very strange, but we get constraint violation in this case */
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static const struct GUID *get_oc_guid_from_message(const struct dsdb_schema *schema,
+ struct ldb_message *msg)
+{
+ struct ldb_message_element *oc_el;
+ const struct dsdb_class *object_class;
+
+ oc_el = ldb_msg_find_element(msg, "objectClass");
+ if (!oc_el) {
+ return NULL;
+ }
+
+ object_class = dsdb_get_last_structural_class(schema, oc_el);
+ if (object_class == NULL) {
+ return NULL;
+ }
+
+ return &object_class->schemaIDGUID;
+}
+
+
+static int acl_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema;
+ unsigned int i;