s4:dsdb: cached results of samdb_rodc()
[metze/samba/wip.git] / source4 / dsdb / common / util.c
index 60bcbe484686c18c332688ec774b31f1ca82f696..7c27b4adfb2f1e1557fdc4151b308c02033a1184 100644 (file)
@@ -41,6 +41,7 @@
 #include "lib/util/tsort.h"
 #include "dsdb/common/util.h"
 #include "lib/socket/socket.h"
+#include "dsdb/samdb/ldb_modules/util.h"
 
 /*
   search the sam for the specified attributes in a specific domain, filter on
@@ -721,6 +722,47 @@ struct ldb_message_element *samdb_find_attribute(struct ldb_context *ldb,
        return NULL;
 }
 
+/*
+ * This is intended for use by the "password hash" module since there
+ * password changes can be specified through one message element with the
+ * new password (to set) and another one with the old password (to unset).
+ *
+ * The first which sets a password (new value) can have flags
+ * (LDB_FLAG_MOD_ADD, LDB_FLAG_MOD_REPLACE) but also none (on "add" operations
+ * for entries). The latter (old value) has always specified
+ * LDB_FLAG_MOD_DELETE.
+ *
+ * Returns LDB_ERR_NO_SUCH_ATTRIBUTE if the attribute which should be deleted
+ * doesn't contain only one value (this is the Windows Server behaviour)
+ * otherwise LDB_SUCCESS.
+ */
+int samdb_msg_find_old_and_new_ldb_val(const struct ldb_message *msg,
+                                      const char *name,
+                                      const struct ldb_val **new_val,
+                                      const struct ldb_val **old_val)
+{
+       unsigned int i;
+
+       *new_val = NULL;
+       *old_val = NULL;
+
+       if (msg == NULL) {
+               return LDB_SUCCESS;
+       }
+
+       for (i = 0; i < msg->num_elements; i++) {
+               if (ldb_attr_cmp(msg->elements[i].name, name) == 0) {
+                       if (msg->elements[i].flags == LDB_FLAG_MOD_DELETE) {
+                               *old_val = &msg->elements[i].values[0];
+                       } else {
+                               *new_val = &msg->elements[i].values[0];
+                       }
+               }
+       }
+
+       return LDB_SUCCESS;
+}
+
 int samdb_find_or_add_value(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value)
 {
        if (samdb_find_attribute(ldb, msg, name, set_value) == NULL) {
@@ -1606,7 +1648,7 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
        const struct ldb_val *val;
        const char *site_name = NULL, *l_subnet_name = NULL;
        const char *allow_list[2] = { NULL, NULL };
-       unsigned int i;
+       unsigned int i, count;
        int cnt, ret;
 
        /*
@@ -1631,13 +1673,17 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
 
        ret = ldb_search(ldb, mem_ctx, &res, subnets_dn, LDB_SCOPE_ONELEVEL,
                         attrs, NULL);
-       if (ret != LDB_SUCCESS) {
+       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+               count = 0;
+       } else if (ret != LDB_SUCCESS) {
                talloc_free(sites_container_dn);
                talloc_free(subnets_dn);
                return NULL;
+       } else {
+               count = res->count;
        }
 
-       for (i = 0; i < res->count; i++) {
+       for (i = 0; i < count; i++) {
                l_subnet_name = ldb_msg_find_attr_as_string(res->msgs[i], "cn",
                                                            NULL);
 
@@ -1845,277 +1891,195 @@ enum samr_ValidationStatus samdb_check_password(const DATA_BLOB *password,
        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
  * as parameter if it's a user change or not ("userChange"). The "rejectReason"
  * gives some more informations if the changed failed.
  *
- * The caller should have a LDB transaction wrapping this.
- *
  * Results: NT_STATUS_OK, NT_STATUS_INTERNAL_DB_CORRUPTION,
  *   NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL,
- *   NT_STATUS_PASSWORD_RESTRICTION
+ *   NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION
  */
-NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx,
+NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                            struct ldb_dn *user_dn, struct ldb_dn *domain_dn,
-                           struct ldb_message *mod,
                            const DATA_BLOB *new_password,
-                           struct samr_Password *param_lmNewHash,
-                           struct samr_Password *param_ntNewHash,
+                           struct samr_Password *lmNewHash,
+                           struct samr_Password *ntNewHash,
                            bool user_change,
                            enum samPwdChangeReason *reject_reason,
                            struct samr_DomInfo1 **_dominfo)
 {
-       const char * const user_attrs[] = { "userAccountControl",
-                                           "lmPwdHistory",
-                                           "ntPwdHistory", 
-                                           "dBCSPwd", "unicodePwd", 
-                                           "objectSid", 
-                                           "pwdLastSet", NULL };
-       const char * const domain_attrs[] = { "minPwdLength", "pwdProperties",
-                                             "pwdHistoryLength",
-                                             "maxPwdAge", "minPwdAge", NULL };
-       NTTIME pwdLastSet;
-       uint32_t minPwdLength, pwdProperties, pwdHistoryLength;
-       int64_t maxPwdAge, minPwdAge;
-       uint32_t userAccountControl;
-       struct samr_Password *sambaLMPwdHistory, *sambaNTPwdHistory,
-               *lmPwdHash, *ntPwdHash, *lmNewHash, *ntNewHash;
-       struct samr_Password local_lmNewHash, local_ntNewHash;
-       int sambaLMPwdHistory_len, sambaNTPwdHistory_len;
-       struct dom_sid *domain_sid;
-       struct ldb_message **res;
-       bool restrictions;
-       int count;
-       time_t now = time(NULL);
-       NTTIME now_nt;
-       unsigned int i;
+       struct ldb_message *msg;
+       struct ldb_message_element *el;
+       struct ldb_request *req;
+       struct dsdb_control_password_change_status *pwd_stat = NULL;
+       int ret;
+       NTSTATUS status;
 
-       /* we need to know the time to compute password age */
-       unix_to_nt_time(&now_nt, now);
+#define CHECK_RET(x) \
+       if (x != LDB_SUCCESS) { \
+               talloc_free(msg); \
+               return NT_STATUS_NO_MEMORY; \
+       }
 
-       /* pull all the user parameters */
-       count = gendb_search_dn(ctx, mem_ctx, user_dn, &res, user_attrs);
-       if (count != 1) {
-               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       msg = ldb_msg_new(mem_ctx);
+       if (msg == NULL) {
+               return NT_STATUS_NO_MEMORY;
        }
-       userAccountControl = samdb_result_uint(res[0], "userAccountControl", 0);
-       sambaLMPwdHistory_len = samdb_result_hashes(mem_ctx, res[0],
-                                        "lmPwdHistory", &sambaLMPwdHistory);
-       sambaNTPwdHistory_len = samdb_result_hashes(mem_ctx, res[0],
-                                        "ntPwdHistory", &sambaNTPwdHistory);
-       lmPwdHash = samdb_result_hash(mem_ctx, res[0], "dBCSPwd");
-       ntPwdHash = samdb_result_hash(mem_ctx, res[0], "unicodePwd");
-       pwdLastSet = samdb_result_uint64(res[0], "pwdLastSet", 0);
-
-       /* Copy parameters */
-       lmNewHash = param_lmNewHash;
-       ntNewHash = param_ntNewHash;
-
-       /* Only non-trust accounts have restrictions (possibly this
-        * test is the wrong way around, but I like to be restrictive
-        * if possible */
-       restrictions = !(userAccountControl & (UF_INTERDOMAIN_TRUST_ACCOUNT
-                                              |UF_WORKSTATION_TRUST_ACCOUNT
-                                              |UF_SERVER_TRUST_ACCOUNT)); 
-
-       if (domain_dn != NULL) {
-               /* pull the domain parameters */
-               count = gendb_search_dn(ctx, mem_ctx, domain_dn, &res,
-                                                               domain_attrs);
-               if (count != 1) {
-                       DEBUG(2, ("samdb_set_password: Domain DN %s is invalid, for user %s\n", 
-                                 ldb_dn_get_linearized(domain_dn),
-                                 ldb_dn_get_linearized(user_dn)));
-                       return NT_STATUS_NO_SUCH_DOMAIN;
+       msg->dn = user_dn;
+       if ((new_password != NULL)
+                       && ((lmNewHash == NULL) && (ntNewHash == NULL))) {
+               /* we have the password as plaintext UTF16 */
+               CHECK_RET(samdb_msg_add_value(ldb, mem_ctx, msg,
+                       "clearTextPassword", new_password));
+               el = ldb_msg_find_element(msg, "clearTextPassword");
+               el->flags = LDB_FLAG_MOD_REPLACE;
+       } else if ((new_password == NULL)
+                       && ((lmNewHash != NULL) || (ntNewHash != NULL))) {
+               /* we have a password as LM and/or NT hash */
+               if (lmNewHash != NULL) {
+                       CHECK_RET(samdb_msg_add_hash(ldb, mem_ctx, msg,
+                               "dBCSPwd", lmNewHash));
+                       el = ldb_msg_find_element(msg, "dBCSPwd");
+                       el->flags = LDB_FLAG_MOD_REPLACE;
                }
-       } else {
-               /* work out the domain sid, and pull the domain from there */
-               domain_sid = samdb_result_sid_prefix(mem_ctx, res[0],
-                                                               "objectSid");
-               if (domain_sid == NULL) {
-                       return NT_STATUS_INTERNAL_DB_CORRUPTION;
+               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;
                }
+       } 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;
+        }
 
-               count = gendb_search(ctx, mem_ctx, NULL, &res, domain_attrs, 
-                               "(objectSid=%s)",
-                               ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
-               if (count != 1) {
-                       DEBUG(2, ("samdb_set_password: Could not find domain to match SID: %s, for user %s\n", 
-                                 dom_sid_string(mem_ctx, domain_sid),
-                                 ldb_dn_get_linearized(user_dn)));
-                       return NT_STATUS_NO_SUCH_DOMAIN;
+       if (user_change) {
+               /* a user password change and we've checked already the old
+                * password somewhere else (callers responsability) */
+               ret = ldb_request_add_control(req,
+                                             DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID,
+                                             true, 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_HASH_VALUES_OID,
+                                     true, 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;
+       }
 
-       minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0);
-       pwdProperties = samdb_result_uint(res[0], "pwdProperties", 0);
-       pwdHistoryLength = samdb_result_uint(res[0], "pwdHistoryLength", 0);
-       maxPwdAge = samdb_result_int64(res[0], "maxPwdAge", 0);
-       minPwdAge = samdb_result_int64(res[0], "minPwdAge", 0);
+       ret = dsdb_autotransaction_request(ldb, req);
 
-       if ((userAccountControl & UF_PASSWD_NOTREQD) != 0) {
-               /* see [MS-ADTS] 2.2.15 */
-               minPwdLength = 0;
+       if (req->context != NULL) {
+               pwd_stat = talloc_steal(mem_ctx,
+                                       ((struct ldb_control *)req->context)->data);
        }
 
+       talloc_free(req);
+       talloc_free(msg);
+
+       /* Sets the domain info (if requested) */
        if (_dominfo != NULL) {
                struct samr_DomInfo1 *dominfo;
-               /* on failure we need to fill in the reject reasons */
-               dominfo = talloc(mem_ctx, struct samr_DomInfo1);
+
+               dominfo = talloc_zero(mem_ctx, struct samr_DomInfo1);
                if (dominfo == NULL) {
                        return NT_STATUS_NO_MEMORY;
                }
-               dominfo->min_password_length     = minPwdLength;
-               dominfo->password_properties     = pwdProperties;
-               dominfo->password_history_length = pwdHistoryLength;
-               dominfo->max_password_age        = maxPwdAge;
-               dominfo->min_password_age        = minPwdAge;
-               *_dominfo = dominfo;
-       }
 
-       if ((restrictions != 0) && (new_password != 0)) {
-               char *new_pass;
-
-               /* checks if the "minPwdLength" property is satisfied */
-               if ((restrictions != 0)
-                       && (minPwdLength > utf16_len_n(
-                               new_password->data, new_password->length)/2)) {
-                       if (reject_reason) {
-                               *reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT;
-                       }
-                       return NT_STATUS_PASSWORD_RESTRICTION;
+               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;
                }
 
-               /* Create the NT hash */
-               mdfour(local_ntNewHash.hash, new_password->data,
-                                                       new_password->length);
-
-               ntNewHash = &local_ntNewHash;
-
-               /* Only check complexity if we can convert it at all.  Assuming unconvertable passwords are 'strong' */
-               if (convert_string_talloc_convenience(mem_ctx,
-                         lp_iconv_convenience(ldb_get_opaque(ctx, "loadparm")),
-                         CH_UTF16, CH_UNIX,
-                         new_password->data, new_password->length,
-                         (void **)&new_pass, NULL, false)) {
-
-                       /* checks the password complexity */
-                       if ((restrictions != 0)
-                               && ((pwdProperties
-                                       & DOMAIN_PASSWORD_COMPLEX) != 0)
-                               && (!check_password_quality(new_pass))) {
-                               if (reject_reason) {
-                                       *reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX;
-                               }
-                               return NT_STATUS_PASSWORD_RESTRICTION;
-                       }
-
-                       /* compute the new lm hashes (for checking history - case insenitivly!) */
-                       if (E_deshash(new_pass, local_lmNewHash.hash)) {
-                               lmNewHash = &local_lmNewHash;
-                       }
-               }
+               *_dominfo = dominfo;
        }
 
-       if ((restrictions != 0) && user_change) {
-               /* are all password changes disallowed? */
-               if ((pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) != 0) {
-                       if (reject_reason) {
-                               *reject_reason = SAM_PWD_CHANGE_NO_ERROR;
-                       }
-                       return NT_STATUS_PASSWORD_RESTRICTION;
-               }
-
-               /* can this user change the password? */
-               if ((userAccountControl & UF_PASSWD_CANT_CHANGE) != 0) {
-                       if (reject_reason) {
-                               *reject_reason = SAM_PWD_CHANGE_NO_ERROR;
-                       }
-                       return NT_STATUS_PASSWORD_RESTRICTION;
-               }
-
-               /* Password minimum age: yes, this is a minus. The ages are in negative 100nsec units! */
-               if (pwdLastSet - minPwdAge > now_nt) {
-                       if (reject_reason) {
-                               *reject_reason = SAM_PWD_CHANGE_NO_ERROR;
-                       }
-                       return NT_STATUS_PASSWORD_RESTRICTION;
-               }
-
-               /* check the immediately past password */
-               if (pwdHistoryLength > 0) {
-                       if (lmNewHash && lmPwdHash && memcmp(lmNewHash->hash,
-                                       lmPwdHash->hash, 16) == 0) {
-                               if (reject_reason) {
-                                       *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
-                               }
-                               return NT_STATUS_PASSWORD_RESTRICTION;
-                       }
-                       if (ntNewHash && ntPwdHash && memcmp(ntNewHash->hash,
-                                       ntPwdHash->hash, 16) == 0) {
-                               if (reject_reason) {
-                                       *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
-                               }
-                               return NT_STATUS_PASSWORD_RESTRICTION;
-                       }
-               }
-
-               /* check the password history */
-               sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len,
-                                                       pwdHistoryLength);
-               sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len,
-                                                       pwdHistoryLength);
-
-               for (i=0; lmNewHash && i<sambaLMPwdHistory_len;i++) {
-                       if (memcmp(lmNewHash->hash, sambaLMPwdHistory[i].hash,
-                                       16) == 0) {
-                               if (reject_reason) {
-                                       *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
-                               }
-                               return NT_STATUS_PASSWORD_RESTRICTION;
-                       }
-               }
-               for (i=0; ntNewHash && i<sambaNTPwdHistory_len;i++) {
-                       if (memcmp(ntNewHash->hash, sambaNTPwdHistory[i].hash,
-                                        16) == 0) {
-                               if (reject_reason) {
-                                       *reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
-                               }
-                               return NT_STATUS_PASSWORD_RESTRICTION;
-                       }
+       if (reject_reason != NULL) {
+               if (pwd_stat != NULL) {
+                       *reject_reason = pwd_stat->reject_reason;
+               } else {
+                       *reject_reason = SAM_PWD_CHANGE_NO_ERROR;
                }
        }
 
-#define CHECK_RET(x) do { if (x != 0) return NT_STATUS_NO_MEMORY; } while(0)
+       if (pwd_stat != NULL) {
+               talloc_free(pwd_stat);
+       }
 
-       /* the password is acceptable. Start forming the new fields */
-       if (new_password != NULL) {
-               /* if we know the cleartext UTF16 password, then set it.
-                * Modules in ldb will set all the appropriate
-                * hashes */
-               CHECK_RET(ldb_msg_add_value(mod, "clearTextPassword", new_password, NULL));
+       /* TODO: Error results taken from "password_hash" module. Are they
+          correct? */
+       if (ret == LDB_ERR_UNWILLING_TO_PERFORM) {
+               status = NT_STATUS_WRONG_PASSWORD;
+       } else if (ret == LDB_ERR_CONSTRAINT_VIOLATION) {
+               status = NT_STATUS_PASSWORD_RESTRICTION;
+       } else if (ret != LDB_SUCCESS) {
+               status = NT_STATUS_UNSUCCESSFUL;
        } else {
-               /* we don't have the cleartext, so set what we have of the
-                * hashes */
-
-               if (lmNewHash) {
-                       CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "dBCSPwd", lmNewHash));
-               }
-
-               if (ntNewHash) {
-                       CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "unicodePwd", ntNewHash));
-               }
+               status = NT_STATUS_OK;
        }
 
-       if (reject_reason) {
-               *reject_reason = SAM_PWD_CHANGE_NO_ERROR;
-       }
-       return NT_STATUS_OK;
+       return status;
 }
 
-
 /*
  * Sets the user password using plaintext UTF16 (attribute "new_password") or
  * LM (attribute "lmNewHash") or NT (attribute "ntNewHash") hash. Also pass
@@ -2130,7 +2094,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx,
  *
  * Results: NT_STATUS_OK, NT_STATUS_INTERNAL_DB_CORRUPTION,
  *   NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL,
- *   NT_STATUS_PASSWORD_RESTRICTION
+ *   NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION
  */
 NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                                const struct dom_sid *user_sid,
@@ -2143,7 +2107,6 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
 {
        NTSTATUS nt_status;
        struct ldb_dn *user_dn;
-       struct ldb_message *msg;
        int ret;
 
        ret = ldb_transaction_start(ldb);
@@ -2162,45 +2125,18 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                return NT_STATUS_NO_SUCH_USER;
        }
 
-       msg = ldb_msg_new(mem_ctx);
-       if (msg == NULL) {
-               ldb_transaction_cancel(ldb);
-               talloc_free(user_dn);
-               return NT_STATUS_NO_MEMORY;
-       }
-
-       msg->dn = ldb_dn_copy(msg, user_dn);
-       if (!msg->dn) {
-               ldb_transaction_cancel(ldb);
-               talloc_free(user_dn);
-               talloc_free(msg);
-               return NT_STATUS_NO_MEMORY;
-       }
-
        nt_status = samdb_set_password(ldb, mem_ctx,
                                       user_dn, NULL,
-                                      msg, new_password,
+                                      new_password,
                                       lmNewHash, ntNewHash,
                                       user_change,
                                       reject_reason, _dominfo);
        if (!NT_STATUS_IS_OK(nt_status)) {
                ldb_transaction_cancel(ldb);
                talloc_free(user_dn);
-               talloc_free(msg);
                return nt_status;
        }
 
-       /* modify the samdb record */
-       ret = dsdb_replace(ldb, msg, 0);
-       if (ret != LDB_SUCCESS) {
-               ldb_transaction_cancel(ldb);
-               talloc_free(user_dn);
-               talloc_free(msg);
-               return NT_STATUS_ACCESS_DENIED;
-       }
-
-       talloc_free(msg);
-
        ret = ldb_transaction_commit(ldb);
        if (ret != LDB_SUCCESS) {
                DEBUG(0,("Failed to commit transaction to change password on %s: %s\n",
@@ -2719,7 +2655,7 @@ int drsuapi_DsReplicaCursor_compare(const struct drsuapi_DsReplicaCursor *c1,
 /*
   see if a computer identified by its invocationId is a RODC
 */
-int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *invocationId, bool *is_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
@@ -2739,8 +2675,18 @@ int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *invocationId,
        }
 
        ret = dsdb_search(sam_ctx, tmp_ctx, &res, config_dn, LDB_SCOPE_SUBTREE, attrs,
-                         DSDB_SEARCH_ONE_ONLY, "invocationID=%s", GUID_string(tmp_ctx, invocationId));
+                         DSDB_SEARCH_ONE_ONLY, "objectGUID=%s", GUID_string(tmp_ctx, objectGUID));
+
+       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;
        }
@@ -2758,12 +2704,40 @@ int samdb_is_rodc(struct ldb_context *sam_ctx, const struct GUID *invocationId,
 */
 int samdb_rodc(struct ldb_context *sam_ctx, bool *am_rodc)
 {
-       const struct GUID *invocationId;
-       invocationId = samdb_ntds_invocation_id(sam_ctx);
-       if (!invocationId) {
+       const struct GUID *objectGUID;
+       int ret;
+       bool *cached;
+
+       /* see if we have a cached copy */
+       cached = (bool *)ldb_get_opaque(ldb, "cache.am_rodc");
+       if (cached) {
+               *am_rodc = *cached;
+               return LDB_SUCCESS;
+       }
+
+       objectGUID = samdb_ntds_objectGUID(sam_ctx);
+       if (!objectGUID) {
                return LDB_ERR_OPERATIONS_ERROR;
        }
-       return samdb_is_rodc(sam_ctx, invocationId, am_rodc);
+
+       ret = samdb_is_rodc(sam_ctx, objectGUID, am_rodc);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+
+       cached = talloc(ldb, bool);
+       if (cached == NULL) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       *cached = *am_rodc;
+
+       ret = ldb_set_opaque(ldb, "cache.am_rodc", cached);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(cached);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       return LDB_SUCCESS;
 }
 
 
@@ -3668,18 +3642,18 @@ const char *samdb_forest_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
 }
 
 /*
-   validate that an invocationID belongs to the specified user sid.
+   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_invocation_id(struct ldb_context *ldb,
-                               const struct GUID *invocation_id,
-                               const struct dom_sid *sid)
+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 invocationID in the
-             configuration partition
-            - remote "NTDS Settings" component from DN
+           - 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
@@ -3699,10 +3673,10 @@ int dsdb_validate_invocation_id(struct ldb_context *ldb,
        config_dn = ldb_get_config_basedn(ldb);
 
        ret = dsdb_search_one(ldb, tmp_ctx, &msg, config_dn, LDB_SCOPE_SUBTREE,
-                             attrs1, 0, "(&(invocationID=%s)(objectClass=nTDSDSA))", GUID_string(tmp_ctx, invocation_id));
+                             attrs1, 0, "(&(objectGUID=%s)(objectClass=nTDSDSA))", GUID_string(tmp_ctx, dsa_guid));
        if (ret != LDB_SUCCESS) {
-               DEBUG(1,(__location__ ": Failed to find invocationID %s for sid %s\n",
-                        GUID_string(tmp_ctx, invocation_id), dom_sid_string(tmp_ctx, sid)));
+               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;
        }
@@ -3717,32 +3691,32 @@ int dsdb_validate_invocation_id(struct ldb_context *ldb,
                              attrs2, DSDB_SEARCH_SHOW_EXTENDED_DN,
                              "(objectClass=server)");
        if (ret != LDB_SUCCESS) {
-               DEBUG(1,(__location__ ": Failed to find server record for invocationID %s, sid %s\n",
-                        GUID_string(tmp_ctx, invocation_id), dom_sid_string(tmp_ctx, sid)));
+               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 invocationID %s, sid %s\n",
-                        GUID_string(tmp_ctx, invocation_id), dom_sid_string(tmp_ctx, sid)));
+               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 invocationID %s, sid %s\n",
-                        GUID_string(tmp_ctx, invocation_id), dom_sid_string(tmp_ctx, sid)));
+               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 invocationID invocationID %s for sid %s - expected sid %s\n",
-                        GUID_string(tmp_ctx, invocation_id),
+               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);
@@ -3752,3 +3726,30 @@ int dsdb_validate_invocation_id(struct ldb_context *ldb,
        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;
+}