dsdb: Unify samdb_{get,set}_ntds_{objectGUID,invocation_id}
[gd/samba-autobuild/.git] / source4 / dsdb / common / util.c
index cae6bd45b3bcf0cd36aeb9ba4c97d98ae9d69169..7cc9729bc3fef4f479afda47b95248b407959e84 100644 (file)
 #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/util_str_hex.h"
+#include "libcli/util/ntstatus.h"
+
+/*
+ * 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
@@ -189,26 +202,6 @@ struct dom_sid *samdb_search_dom_sid(struct ldb_context *sam_ldb,
        return sid;     
 }
 
-/*
-  return the count of the number of records in the sam matching the query
-*/
-int samdb_search_count(struct ldb_context *sam_ldb,
-                      TALLOC_CTX *mem_ctx,
-                      struct ldb_dn *basedn,
-                      const char *format, ...) _PRINTF_ATTRIBUTE(4,5)
-{
-       va_list ap;
-       const char *attrs[] = { NULL };
-       int ret;
-
-       va_start(ap, format);
-       ret = gendb_search_v(sam_ldb, mem_ctx, basedn, NULL, attrs, format, ap);
-       va_end(ap);
-
-       return ret;
-}
-
-
 /*
   search the sam for a single integer attribute in exactly 1 record
 */
@@ -360,7 +353,7 @@ struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_messa
        if (sid == NULL) {
                return NULL;
        }
-       ok = sid_blob_parse(*v, sid);
+       ok = sid_parse(v->data, v->length, sid);
        if (!ok) {
                talloc_free(sid);
                return NULL;
@@ -475,45 +468,6 @@ NTTIME samdb_result_allow_password_change(struct ldb_context *sam_ldb,
        return attr_time;
 }
 
-/*
-  construct the force_password_change field from the PwdLastSet
-  attribute, the userAccountControl and the domain password settings
-*/
-NTTIME samdb_result_force_password_change(struct ldb_context *sam_ldb, 
-                                         TALLOC_CTX *mem_ctx, 
-                                         struct ldb_dn *domain_dn, 
-                                         struct ldb_message *msg)
-{
-       int64_t attr_time = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
-       uint32_t userAccountControl = ldb_msg_find_attr_as_uint(msg,
-                                                               "userAccountControl",
-                                                               0);
-       int64_t maxPwdAge;
-
-       /* Machine accounts don't expire, and there is a flag for 'no expiry' */
-       if (!(userAccountControl & UF_NORMAL_ACCOUNT)
-           || (userAccountControl & UF_DONT_EXPIRE_PASSWD)) {
-               return 0x7FFFFFFFFFFFFFFFULL;
-       }
-
-       if (attr_time == 0) {
-               return 0;
-       }
-       if (attr_time == -1) {
-               return 0x7FFFFFFFFFFFFFFFULL;
-       }
-
-       maxPwdAge = samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn,
-                                      "maxPwdAge", NULL);
-       if (maxPwdAge == 0) {
-               return 0x7FFFFFFFFFFFFFFFULL;
-       } else {
-               attr_time -= maxPwdAge;
-       }
-
-       return attr_time;
-}
-
 /*
   pull a samr_Password structutre from a result set. 
 */
@@ -558,10 +512,51 @@ unsigned int samdb_result_hashes(TALLOC_CTX *mem_ctx, const struct ldb_message *
        return count;
 }
 
-NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, struct ldb_message *msg,
-                               struct samr_Password **lm_pwd, struct samr_Password **nt_pwd) 
+NTSTATUS samdb_result_passwords_from_history(TALLOC_CTX *mem_ctx,
+                                            struct loadparm_context *lp_ctx,
+                                            struct ldb_message *msg,
+                                            unsigned int idx,
+                                            struct samr_Password **lm_pwd,
+                                            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 **lm_pwd,
+                                          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, "unicodePwd", &ntPwdHash);
@@ -594,6 +589,27 @@ NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct loadparm_context *lp
        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 **lm_pwd,
+                               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,
+                                                lm_pwd, nt_pwd);
+}
+
 /*
   pull a samr_LogonHours structutre from a result set. 
 */
@@ -625,52 +641,63 @@ struct samr_LogonHours samdb_result_logon_hours(TALLOC_CTX *mem_ctx, struct ldb_
 /*
   pull a set of account_flags from a result set. 
 
-  This requires that the attributes: 
-   pwdLastSet
-   userAccountControl
-  be included in 'msg'
+  Naturally, this requires that userAccountControl and
+  (if not null) the attributes 'attr' be already
+  included in msg
 */
-uint32_t samdb_result_acct_flags(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, 
-                                struct ldb_message *msg, struct ldb_dn *domain_dn)
+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);
-       NTTIME must_change_time;
-       NTTIME now;
-
-       must_change_time = samdb_result_force_password_change(sam_ctx, mem_ctx, 
-                                                             domain_dn, msg);
-
-       /* Test account expire time */
-       unix_to_nt_time(&now, time(NULL));
-       /* check for expired password */
-       if (must_change_time < now) {
-               acct_flags |= ACB_PW_EXPIRED;
+       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;
 }
 
-struct lsa_BinaryString samdb_result_parameters(TALLOC_CTX *mem_ctx,
-                                               struct ldb_message *msg,
-                                               const char *attr)
+NTSTATUS samdb_result_parameters(TALLOC_CTX *mem_ctx,
+                                struct ldb_message *msg,
+                                const char *attr,
+                                struct lsa_BinaryString *s)
 {
-       struct lsa_BinaryString s;
+       int i;
        const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr);
 
-       ZERO_STRUCT(s);
+       ZERO_STRUCTP(s);
 
        if (!val) {
-               return s;
+               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 either a un-initilaised
+                * 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;
 
-       s.array = talloc_array(mem_ctx, uint16_t, val->length/2);
-       if (!s.array) {
-               return s;
+       /* 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);
        }
-       s.length = s.size = val->length;
-       memcpy(s.array, val->data, val->length);
 
-       return s;
+       return NT_STATUS_OK;
 }
 
 /* Find an attribute, with a particular value */
@@ -699,16 +726,50 @@ struct ldb_message_element *samdb_find_attribute(struct ldb_context *ldb,
        return NULL;
 }
 
-int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value)
+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;
        }
 
-       return ldb_msg_add_string(msg, name, set_value);
+       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);
 }
 
 /*
@@ -978,55 +1039,26 @@ int samdb_msg_add_logon_hours(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx,
 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;
-       val.length = parameters->length;
-       val.data = (uint8_t *)parameters->array;
-       return ldb_msg_add_value(msg, attr_name, &val, NULL);
-}
-
-/*
-  sets a general value element to a message
-*/
-int samdb_msg_set_value(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
-                       const char *attr_name, const struct ldb_val *val)
-{
-       struct ldb_message_element *el;
-
-       el = ldb_msg_find_element(msg, attr_name);
-       if (el) {
-               el->num_values = 0;
+       if ((parameters->length % 2) != 0) {
+               return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
        }
-       return ldb_msg_add_value(msg, attr_name, val, NULL);
-}
-
-/*
-  set a string element in a message
-*/
-int samdb_msg_set_string(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
-                        const char *attr_name, const char *str)
-{
-       struct ldb_message_element *el;
 
-       el = ldb_msg_find_element(msg, attr_name);
-       if (el) {
-               el->num_values = 0;
+       val.data = talloc_array(mem_ctx, uint8_t, parameters->length);
+       if (val.data == NULL) {
+               return LDB_ERR_OPERATIONS_ERROR;
        }
-       return ldb_msg_add_string(msg, attr_name, str);
-}
-
-/*
- * sets a signed integer in a message
- */
-int samdb_msg_set_int(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx,
-                     struct ldb_message *msg, const char *attr_name, int v)
-{
-       struct ldb_message_element *el;
-
-       el = ldb_msg_find_element(msg, attr_name);
-       if (el) {
-               el->num_values = 0;
+       val.length = parameters->length;
+       for (i = 0; i < parameters->length / 2; i++) {
+               /*
+                * The on-disk format needs to be in the 'network'
+                * format, parmeters->array is a uint16_t array of
+                * length parameters->length / 2
+                */
+               SSVAL(val.data, i * 2, parameters->array[i]);
        }
-       return samdb_msg_add_int(sam_ldb, mem_ctx, msg, attr_name, v);
+       return ldb_msg_add_steal_value(msg, attr_name, &val);
 }
 
 /*
@@ -1057,8 +1089,8 @@ int samdb_msg_set_uint(struct ldb_context *sam_ldb, TALLOC_CTX *mem_ctx,
 /*
  * Handle ldb_request in transaction
  */
-static int dsdb_autotransaction_request(struct ldb_context *sam_ldb,
-                                       struct ldb_request *req)
+int dsdb_autotransaction_request(struct ldb_context *sam_ldb,
+                                struct ldb_request *req)
 {
        int ret;
 
@@ -1246,6 +1278,61 @@ failed:
        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;
@@ -1286,7 +1373,7 @@ failed:
 /*
   work out the ntds settings dn for the current open ldb
 */
-struct ldb_dn *samdb_ntds_settings_dn(struct ldb_context *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 };
@@ -1297,16 +1384,16 @@ struct ldb_dn *samdb_ntds_settings_dn(struct ldb_context *ldb)
        /* see if we have a cached copy */
        settings_dn = (struct ldb_dn *)ldb_get_opaque(ldb, "forced.ntds_settings_dn");
        if (settings_dn) {
-               return settings_dn;
+               return ldb_dn_copy(mem_ctx, settings_dn);
        }
 
-       tmp_ctx = talloc_new(ldb);
+       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) {
+       if (ret != LDB_SUCCESS) {
                DEBUG(1,("Searching for dsServiceName in rootDSE failed: %s\n", 
                         ldb_errstring(ldb)));
                goto failed;
@@ -1322,7 +1409,7 @@ struct ldb_dn *samdb_ntds_settings_dn(struct ldb_context *ldb)
         * we could not handle server renames at runtime. Only
         * provision sets up forced.ntds_settings_dn */
 
-       talloc_steal(ldb, settings_dn);
+       talloc_steal(mem_ctx, settings_dn);
        talloc_free(tmp_ctx);
 
        return settings_dn;
@@ -1334,20 +1421,24 @@ failed:
 }
 
 /*
-  work out the ntds settings invocationId for the current open ldb
+  work out the ntds settings invocationID/objectGUID for the current open ldb
 */
-const struct GUID *samdb_ntds_invocation_id(struct ldb_context *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[] = { "invocationId", NULL };
+       const char *attrs[] = { attribute, NULL };
        int ret;
        struct ldb_result *res;
-       struct GUID *invocation_id;
+       struct GUID *ntds_guid;
+       struct ldb_dn *ntds_settings_dn = NULL;
+       const char *errstr = NULL;
 
        /* see if we have a cached copy */
-       invocation_id = (struct GUID *)ldb_get_opaque(ldb, "cache.invocation_id");
-       if (invocation_id) {
-               return invocation_id;
+       ntds_guid = (struct GUID *)ldb_get_opaque(ldb, cache_name);
+       if (ntds_guid != NULL) {
+               return ntds_guid;
        }
 
        tmp_ctx = talloc_new(ldb);
@@ -1355,167 +1446,130 @@ const struct GUID *samdb_ntds_invocation_id(struct ldb_context *ldb)
                goto failed;
        }
 
-       ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL);
-       if (ret) {
+       ntds_settings_dn = samdb_ntds_settings_dn(ldb, tmp_ctx);
+       if (ntds_settings_dn == NULL) {
+               errstr = "samdb_ntds_settings_dn() returned NULL";
                goto failed;
        }
 
-       if (res->count != 1) {
+       ret = ldb_search(ldb, tmp_ctx, &res, ntds_settings_dn,
+                        LDB_SCOPE_BASE, attrs, NULL);
+       if (ret) {
+               errstr = ldb_errstring(ldb);
                goto failed;
        }
 
-       invocation_id = talloc(tmp_ctx, struct GUID);
-       if (!invocation_id) {
+       if (res->count != 1) {
+               errstr = "incorrect number of results from base search";
                goto failed;
        }
 
-       *invocation_id = samdb_result_guid(res->msgs[0], "invocationId");
-
-       /* cache the domain_sid in the ldb */
-       if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id) != LDB_SUCCESS) {
+       ntds_guid = talloc(tmp_ctx, struct GUID);
+       if (ntds_guid == NULL) {
                goto failed;
        }
 
-       talloc_steal(ldb, invocation_id);
-       talloc_free(tmp_ctx);
-
-       return invocation_id;
-
-failed:
-       DEBUG(1,("Failed to find our own NTDS Settings invocationId in the ldb!\n"));
-       talloc_free(tmp_ctx);
-       return NULL;
-}
-
-bool samdb_set_ntds_invocation_id(struct ldb_context *ldb, const struct GUID *invocation_id_in)
-{
-       TALLOC_CTX *tmp_ctx;
-       struct GUID *invocation_id_new;
-       struct GUID *invocation_id_old;
+       *ntds_guid = samdb_result_guid(res->msgs[0], attribute);
 
-       /* see if we have a cached copy */
-       invocation_id_old = (struct GUID *)ldb_get_opaque(ldb, 
-                                                        "cache.invocation_id");
-
-       tmp_ctx = talloc_new(ldb);
-       if (tmp_ctx == NULL) {
-               goto failed;
-       }
-
-       invocation_id_new = talloc(tmp_ctx, struct GUID);
-       if (!invocation_id_new) {
+       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;
        }
 
-       *invocation_id_new = *invocation_id_in;
-
        /* cache the domain_sid in the ldb */
-       if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id_new) != LDB_SUCCESS) {
+       if (ldb_set_opaque(ldb, cache_name, ntds_guid) != LDB_SUCCESS) {
+               errstr = "ldb_set_opaque() failed";
                goto failed;
        }
 
-       talloc_steal(ldb, invocation_id_new);
+       talloc_steal(ldb, ntds_guid);
        talloc_free(tmp_ctx);
-       talloc_free(invocation_id_old);
 
-       return true;
+       return ntds_guid;
 
 failed:
-       DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n"));
+       DBG_WARNING("Failed to find our own NTDS Settings %s in the ldb: %s!\n",
+                   attribute, errstr);
        talloc_free(tmp_ctx);
-       return false;
+       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;
-       const char *attrs[] = { "objectGUID", NULL };
-       int ret;
-       struct ldb_result *res;
-       struct GUID *ntds_guid;
+       struct GUID *ntds_guid_new;
+       struct GUID *ntds_guid_old;
 
        /* see if we have a cached copy */
-       ntds_guid = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid");
-       if (ntds_guid) {
-               return ntds_guid;
-       }
+       ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, cache_name);
 
        tmp_ctx = talloc_new(ldb);
        if (tmp_ctx == NULL) {
                goto failed;
        }
 
-       ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL);
-       if (ret) {
-               goto failed;
-       }
-
-       if (res->count != 1) {
-               goto failed;
-       }
-
-       ntds_guid = talloc(tmp_ctx, struct GUID);
-       if (!ntds_guid) {
+       ntds_guid_new = talloc(tmp_ctx, struct GUID);
+       if (!ntds_guid_new) {
                goto failed;
        }
 
-       *ntds_guid = samdb_result_guid(res->msgs[0], "objectGUID");
+       *ntds_guid_new = *ntds_guid_in;
 
        /* cache the domain_sid in the ldb */
-       if (ldb_set_opaque(ldb, "cache.ntds_guid", ntds_guid) != LDB_SUCCESS) {
+       if (ldb_set_opaque(ldb, cache_name, ntds_guid_new) != LDB_SUCCESS) {
                goto failed;
        }
 
-       talloc_steal(ldb, ntds_guid);
+       talloc_steal(ldb, ntds_guid_new);
        talloc_free(tmp_ctx);
+       talloc_free(ntds_guid_old);
 
-       return ntds_guid;
+       return true;
 
 failed:
-       DEBUG(1,("Failed to find our own NTDS Settings objectGUID in the ldb!\n"));
+       DBG_WARNING("Failed to set our own cached %s in the ldb!\n",
+                   attribute);
        talloc_free(tmp_ctx);
-       return NULL;
+       return false;
 }
 
 bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in)
 {
-       TALLOC_CTX *tmp_ctx;
-       struct GUID *ntds_guid_new;
-       struct GUID *ntds_guid_old;
+       return samdb_set_ntds_GUID(ldb,
+                                  ntds_guid_in,
+                                  "objectGUID",
+                                  "cache.ntds_guid");
+}
 
-       /* see if we have a cached copy */
-       ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid");
-
-       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.ntds_guid", 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:
-       DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n"));
-       talloc_free(tmp_ctx);
-       return false;
+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");
 }
 
 /*
@@ -1523,7 +1577,15 @@ failed:
 */
 struct ldb_dn *samdb_server_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
 {
-       return ldb_dn_get_parent(mem_ctx, samdb_ntds_settings_dn(ldb));
+       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;
+       
 }
 
 /*
@@ -1625,8 +1687,10 @@ int samdb_reference_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_
        attrs[0] = attribute;
        attrs[1] = NULL;
 
-       ret = dsdb_search(ldb, mem_ctx, &res, base, LDB_SCOPE_BASE, attrs, DSDB_SEARCH_ONE_ONLY, 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;
        }
 
@@ -1647,6 +1711,53 @@ int samdb_reference_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, struct ldb_
        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
@@ -1658,7 +1769,7 @@ int samdb_server_reference_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, stru
 
        server_dn = samdb_server_dn(ldb, mem_ctx);
        if (server_dn == NULL) {
-               return LDB_ERR_NO_SUCH_OBJECT;
+               return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__);
        }
 
        ret = samdb_reference_dn(ldb, mem_ctx, server_dn, "serverReference", dn);
@@ -1710,18 +1821,25 @@ const char *samdb_server_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
 /*
  * 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)
+                                  const char *ip_address, char **subnet_name,
+                                  bool fallback)
 {
        const char *attrs[] = { "cn", "siteObject", NULL };
-       struct ldb_dn *sites_container_dn, *subnets_dn, *sites_dn;
-       struct ldb_result *res;
-       const struct ldb_val *val;
-       const char *site_name = NULL, *l_subnet_name = 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 cnt, ret;
+       int ret;
 
        /*
         * if we don't have a client ip e.g. ncalrpc
@@ -1733,14 +1851,12 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
 
        sites_container_dn = samdb_sites_dn(ldb, mem_ctx);
        if (sites_container_dn == NULL) {
-               return NULL;
+               goto exit;
        }
 
        subnets_dn = ldb_dn_copy(mem_ctx, sites_container_dn);
        if ( ! ldb_dn_add_child_fmt(subnets_dn, "CN=Subnets")) {
-               talloc_free(sites_container_dn);
-               talloc_free(subnets_dn);
-               return NULL;
+               goto exit;
        }
 
        ret = ldb_search(ldb, mem_ctx, &res, subnets_dn, LDB_SCOPE_ONELEVEL,
@@ -1748,9 +1864,7 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
        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;
+               goto exit;
        } else {
                count = res->count;
        }
@@ -1761,7 +1875,7 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
 
                allow_list[0] = l_subnet_name;
 
-               if (socket_allow_access(mem_ctx, NULL, allow_list, "", ip_address)) {
+               if (allow_access_nolog(NULL, allow_list, "", ip_address)) {
                        sites_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx,
                                                           res->msgs[i],
                                                           "siteObject");
@@ -1775,20 +1889,29 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                        site_name = talloc_strdup(mem_ctx,
                                                  (const char *) val->data);
 
-                       talloc_free(sites_dn);
+                       TALLOC_FREE(sites_dn);
 
                        break;
                }
        }
 
-       if (site_name == NULL) {
+       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 "". */
-               cnt = samdb_search_count(ldb, mem_ctx, sites_container_dn,
-                                        "(objectClass=site)");
+               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 {
@@ -1801,9 +1924,10 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                *subnet_name = talloc_strdup(mem_ctx, l_subnet_name);
        }
 
-       talloc_free(sites_container_dn);
-       talloc_free(subnets_dn);
-       talloc_free(res);
+exit:
+       TALLOC_FREE(sites_container_dn);
+       TALLOC_FREE(subnets_dn);
+       TALLOC_FREE(res);
 
        return site_name;
 }
@@ -1813,46 +1937,19 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
 */
 bool samdb_is_pdc(struct ldb_context *ldb)
 {
-       const char *dom_attrs[] = { "fSMORoleOwner", NULL };
        int ret;
-       struct ldb_result *dom_res;
-       TALLOC_CTX *tmp_ctx;
        bool is_pdc;
-       struct ldb_dn *pdc;
-
-       tmp_ctx = talloc_new(ldb);
-       if (tmp_ctx == NULL) {
-               DEBUG(1, ("talloc_new failed in samdb_is_pdc"));
-               return false;
-       }
 
-       ret = ldb_search(ldb, tmp_ctx, &dom_res, ldb_get_default_basedn(ldb), LDB_SCOPE_BASE, dom_attrs, NULL);
+       ret = samdb_reference_dn_is_our_ntdsa(ldb, ldb_get_default_basedn(ldb), "fsmoRoleOwner", 
+                                             &is_pdc);
        if (ret != LDB_SUCCESS) {
-               DEBUG(1,("Searching for fSMORoleOwner in %s failed: %s\n", 
+               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)));
-               goto failed;
-       }
-       if (dom_res->count != 1) {
-               goto failed;
-       }
-
-       pdc = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, dom_res->msgs[0], "fSMORoleOwner");
-
-       if (ldb_dn_compare(samdb_ntds_settings_dn(ldb), pdc) == 0) {
-               is_pdc = true;
-       } else {
-               is_pdc = false;
+               return false;
        }
 
-       talloc_free(tmp_ctx);
-
        return is_pdc;
-
-failed:
-       DEBUG(1,("Failed to find if we are the PDC for this ldb\n"));
-       talloc_free(tmp_ctx);
-       return false;
 }
 
 /*
@@ -1913,6 +2010,15 @@ int samdb_search_for_parent_domain(struct ldb_context *ldb, TALLOC_CTX *mem_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
@@ -1921,19 +2027,149 @@ int samdb_search_for_parent_domain(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
  *
  * Result codes from "enum samr_ValidationStatus" (consider "samr.idl")
  */
-enum samr_ValidationStatus samdb_check_password(const DATA_BLOB *password,
+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 char *utf8_pw = (const char *)utf8_blob->data;
+       size_t utf8_len = strlen_m(utf8_pw);
+       char *password_script = NULL;
+
        /* checks if the "minPwdLength" property is satisfied */
-       if (minPwdLength > password->length)
+       if (minPwdLength > utf8_len) {
                return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT;
+       }
 
        /* checks the password complexity */
-       if (((pwdProperties & DOMAIN_PASSWORD_COMPLEX) != 0)
-                       && (password->data != NULL)
-                       && (!check_password_quality((const char *) password->data)))
+       if (!(pwdProperties & DOMAIN_PASSWORD_COMPLEX)) {
+               return SAMR_VALIDATION_STATUS_SUCCESS;
+       }
+
+       if (utf8_len == 0) {
+               return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH;
+       }
+
+       password_script = lpcfg_check_password_script(lp_ctx, mem_ctx);
+       if (password_script != NULL && *password_script != '\0') {
+               int check_ret = 0;
+               int error = 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);
+
+               if (write(cps_stdin, utf8_pw, utf8_len) != utf8_len) {
+                       close(cps_stdin);
+                       cps_stdin = -1;
+                       TALLOC_FREE(password_script);
+                       TALLOC_FREE(event_ctx);
+                       return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR;
+               }
+
+               close(cps_stdin);
+               cps_stdin = -1;
+
+               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;
 }
@@ -1978,7 +2214,7 @@ int samdb_set_password_callback(struct ldb_request *req, struct ldb_reply *ares)
  * Results: NT_STATUS_OK, NT_STATUS_INVALID_PARAMETER, NT_STATUS_UNSUCCESSFUL,
  *   NT_STATUS_WRONG_PASSWORD, NT_STATUS_PASSWORD_RESTRICTION
  */
-NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+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 *lmNewHash,
@@ -1986,13 +2222,15 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                            const struct samr_Password *lmOldHash,
                            const struct samr_Password *ntOldHash,
                            enum samPwdChangeReason *reject_reason,
-                           struct samr_DomInfo1 **_dominfo)
+                           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) \
@@ -2028,6 +2266,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                        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);
@@ -2065,13 +2304,25 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                        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;
+       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,
@@ -2085,8 +2336,11 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
        ret = dsdb_autotransaction_request(ldb, req);
 
        if (req->context != NULL) {
-               pwd_stat = talloc_steal(mem_ctx,
-                                       ((struct ldb_control *)req->context)->data);
+               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);
@@ -2127,10 +2381,11 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
        if (ret == LDB_ERR_CONSTRAINT_VIOLATION) {
                const char *errmsg = ldb_errstring(ldb);
                char *endptr = NULL;
-               WERROR werr = WERR_GENERAL_FAILURE;
+               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)) {
@@ -2143,13 +2398,37 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
        } 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 *lmNewHash,
+                           const struct samr_Password *ntNewHash,
+                           const struct samr_Password *lmOldHash,
+                           const struct samr_Password *ntOldHash,
+                           enum samPwdChangeReason *reject_reason,
+                           struct samr_DomInfo1 **_dominfo)
+{
+       return samdb_set_password_internal(ldb, mem_ctx,
+                           user_dn, domain_dn,
+                           new_password,
+                           lmNewHash, ntNewHash,
+                           lmOldHash, ntOldHash,
+                           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
@@ -2170,6 +2449,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
  */
 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 *lmNewHash,
                                const struct samr_Password *ntNewHash,
@@ -2178,48 +2458,387 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                                enum samPwdChangeReason *reject_reason,
                                struct samr_DomInfo1 **_dominfo) 
 {
+       TALLOC_CTX *frame = talloc_stackframe();
        NTSTATUS nt_status;
-       struct ldb_dn *user_dn;
+       const char * const user_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;
        }
 
-       user_dn = samdb_search_dn(ldb, mem_ctx, NULL,
-                                 "(&(objectSid=%s)(objectClass=user))", 
-                                 ldap_encode_ndr_dom_sid(mem_ctx, user_sid));
-       if (!user_dn) {
+       ret = dsdb_search_one(ldb, frame, &user_msg, ldb_get_default_basedn(ldb),
+                             LDB_SCOPE_SUBTREE, user_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(3, ("samdb_set_password_sid: SID %s not found in samdb, returning NO_SUCH_USER\n",
-                         dom_sid_string(mem_ctx, user_sid)));
+               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;
        }
 
-       nt_status = samdb_set_password(ldb, mem_ctx,
-                                      user_dn, NULL,
-                                      new_password,
-                                      lmNewHash, ntNewHash,
-                                      lmOldHash, ntOldHash,
-                                      reject_reason, _dominfo);
+       if (uac & UF_INTERDOMAIN_TRUST_ACCOUNT) {
+               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_add_empty(tdo_msg, "trustAuthIncoming",
+                                       LDB_FLAG_MOD_REPLACE, NULL);
+               if (ret != LDB_SUCCESS) {
+                       ldb_transaction_cancel(ldb);
+                       TALLOC_FREE(frame);
+                       return NT_STATUS_NO_MEMORY;
+               }
+               ret = ldb_msg_add_value(tdo_msg, "trustAuthIncoming",
+                                       &new_val, NULL);
+               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,
+                                               lmNewHash, ntNewHash,
+                                               lmOldHash, ntOldHash,
+                                               reject_reason, _dominfo,
+                                               true); /* permit trusts */
        if (!NT_STATUS_IS_OK(nt_status)) {
                ldb_transaction_cancel(ldb);
-               talloc_free(user_dn);
+               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_dn),
+                        ldb_dn_get_linearized(user_msg->dn),
                         ldb_errstring(ldb)));
-               talloc_free(user_dn);
+               TALLOC_FREE(frame);
                return NT_STATUS_TRANSACTION_ABORTED;
        }
 
-       talloc_free(user_dn);
+       TALLOC_FREE(frame);
        return NT_STATUS_OK;
 }
 
@@ -2296,14 +2915,14 @@ struct ldb_dn *samdb_dns_domain_to_dn(struct ldb_context *ldb, TALLOC_CTX *mem_c
        unsigned int i;
        TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
        const char *binary_encoded;
-       const char **split_realm;
+       const char * const *split_realm;
        struct ldb_dn *dn;
 
        if (!tmp_ctx) {
                return NULL;
        }
 
-       split_realm = (const char **)str_list_make(tmp_ctx, dns_domain, ".");
+       split_realm = (const char * const *)str_list_make(tmp_ctx, dns_domain, ".");
        if (!split_realm) {
                talloc_free(tmp_ctx);
                return NULL;
@@ -2458,7 +3077,9 @@ struct ldb_dn *samdb_domain_to_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
  */
 int dsdb_find_dn_by_guid(struct ldb_context *ldb, 
                         TALLOC_CTX *mem_ctx,
-                        const struct GUID *guid, struct ldb_dn **dn)
+                        const struct GUID *guid,
+                        uint32_t dsdb_flags,
+                        struct ldb_dn **dn)
 {
        int ret;
        struct ldb_result *res;
@@ -2472,7 +3093,7 @@ int dsdb_find_dn_by_guid(struct ldb_context *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,
+                         DSDB_SEARCH_ONE_ONLY | dsdb_flags,
                          "objectGUID=%s", guid_str);
        talloc_free(guid_str);
        if (ret != LDB_SUCCESS) {
@@ -2509,7 +3130,7 @@ int dsdb_find_guid_attr_by_dn(struct ldb_context *ldb,
        }
        if (res->count < 1) {
                talloc_free(tmp_ctx);
-               return LDB_ERR_NO_SUCH_OBJECT;
+               return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__);
        }
        *guid = samdb_result_guid(res->msgs[0], attribute);
        talloc_free(tmp_ctx);
@@ -2585,12 +3206,12 @@ int dsdb_find_sid_by_dn(struct ldb_context *ldb,
        }
        if (res->count < 1) {
                talloc_free(tmp_ctx);
-               return LDB_ERR_NO_SUCH_OBJECT;
+               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_ERR_NO_SUCH_OBJECT;
+               return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__);
        }
        *sid = *s;
        talloc_free(tmp_ctx);
@@ -2926,10 +3547,57 @@ int samdb_rodc(struct ldb_context *sam_ctx, bool *am_rodc)
        return LDB_SUCCESS;
 }
 
-bool samdb_set_am_rodc(struct ldb_context *ldb, bool am_rodc)
+int samdb_dns_host_name(struct ldb_context *sam_ctx, const char **host_name)
 {
-       TALLOC_CTX *tmp_ctx;
-       bool *cached;
+       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);
+
+       ret = dsdb_search_dn(sam_ctx, tmp_ctx, &res, NULL, attrs, 0);
+
+       if (res->count != 1 || ret != LDB_SUCCESS) {
+               DEBUG(0, ("Failed to get rootDSE for dnsHostName: %s",
+                         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"));
+               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) {
@@ -3002,7 +3670,7 @@ int samdb_ntds_site_settings_options(struct ldb_context *ldb_ctx,
 failed:
        DEBUG(1,("Failed to find our NTDS Site Settings options in ldb!\n"));
        talloc_free(tmp_ctx);
-       return LDB_ERR_NO_SUCH_OBJECT;
+       return ldb_error(ldb_ctx, LDB_ERR_NO_SUCH_OBJECT, __func__);
 }
 
 /*
@@ -3022,7 +3690,7 @@ int samdb_ntds_options(struct ldb_context *ldb, uint32_t *options)
                goto failed;
        }
 
-       ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL);
+       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;
        }
@@ -3040,7 +3708,7 @@ int samdb_ntds_options(struct ldb_context *ldb, uint32_t *options)
 failed:
        DEBUG(1,("Failed to find our own NTDS Settings options in the ldb!\n"));
        talloc_free(tmp_ctx);
-       return LDB_ERR_NO_SUCH_OBJECT;
+       return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__);
 }
 
 const char* samdb_ntds_object_category(TALLOC_CTX *tmp_ctx, struct ldb_context *ldb)
@@ -3049,7 +3717,7 @@ const char* samdb_ntds_object_category(TALLOC_CTX *tmp_ctx, struct ldb_context *
        int ret;
        struct ldb_result *res;
 
-       ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb), LDB_SCOPE_BASE, attrs, NULL);
+       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;
        }
@@ -3075,16 +3743,17 @@ const char *samdb_cn_to_lDAPDisplayName(TALLOC_CTX *mem_ctx, const char *cn)
        size_t i;
 
        tokens = str_list_make(mem_ctx, cn, " -_");
-       if (tokens == NULL)
+       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; i < str_list_length((const char **)tokens); i++)
+       for (i = 1; tokens[i] != NULL; i++)
                tokens[i][0] = toupper(tokens[i][0]);
 
        ret = talloc_strdup(mem_ctx, tokens[0]);
-       for (i = 1; i < str_list_length((const char **)tokens); i++)
+       for (i = 1; tokens[i] != NULL; i++)
                ret = talloc_asprintf_append_buffer(ret, "%s", tokens[i]);
 
        talloc_free(tokens);
@@ -3161,18 +3830,26 @@ NTSTATUS dsdb_get_extended_dn_guid(struct ldb_dn *dn, struct GUID *guid, const c
 NTSTATUS dsdb_get_extended_dn_uint64(struct ldb_dn *dn, uint64_t *val, const char *component_name)
 {
        const struct ldb_val *v;
-       char *s;
+       int error = 0;
 
        v = ldb_dn_get_extended_component(dn, component_name);
        if (v == NULL) {
                return NT_STATUS_OBJECT_NAME_NOT_FOUND;
        }
-       s = talloc_strndup(dn, (const char *)v->data, v->length);
-       NT_STATUS_HAVE_NO_MEMORY(s);
 
-       *val = strtoull(s, NULL, 0);
+       /* 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;
 
-       talloc_free(s);
+               *val = strtoull_err(s, NULL, 0, &error);
+               if (error != 0) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+       }
        return NT_STATUS_OK;
 }
 
@@ -3190,19 +3867,26 @@ NTSTATUS dsdb_get_extended_dn_nttime(struct ldb_dn *dn, NTTIME *nttime, const ch
 NTSTATUS dsdb_get_extended_dn_uint32(struct ldb_dn *dn, uint32_t *val, const char *component_name)
 {
        const struct ldb_val *v;
-       char *s;
+       int error = 0;
 
        v = ldb_dn_get_extended_component(dn, component_name);
        if (v == NULL) {
                return NT_STATUS_OBJECT_NAME_NOT_FOUND;
        }
 
-       s = talloc_strndup(dn, (const char *)v->data, v->length);
-       NT_STATUS_HAVE_NO_MEMORY(s);
-
-       *val = strtoul(s, NULL, 0);
+       /* 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 = strtoul_err(s, NULL, 0, &error);
+               if (error != 0) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+       }
 
-       talloc_free(s);
        return NT_STATUS_OK;
 }
 
@@ -3212,7 +3896,6 @@ NTSTATUS dsdb_get_extended_dn_uint32(struct ldb_dn *dn, uint32_t *val, const cha
 NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const char *component_name)
 {
        const struct ldb_val *sid_blob;
-       struct TALLOC_CTX *tmp_ctx;
        enum ndr_err_code ndr_err;
 
        sid_blob = ldb_dn_get_extended_component(dn, component_name);
@@ -3220,17 +3903,13 @@ NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const
                return NT_STATUS_OBJECT_NAME_NOT_FOUND;
        }
 
-       tmp_ctx = talloc_new(NULL);
-
-       ndr_err = ndr_pull_struct_blob_all(sid_blob, tmp_ctx, sid,
-                                          (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+       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);
-               talloc_free(tmp_ctx);
                return status;
        }
 
-       talloc_free(tmp_ctx);
        return NT_STATUS_OK;
 }
 
@@ -3241,13 +3920,13 @@ NTSTATUS dsdb_get_extended_dn_sid(struct ldb_dn *dn, struct dom_sid *sid, const
  */
 uint32_t dsdb_dn_rmd_flags(struct ldb_dn *dn)
 {
-       const struct ldb_val *v;
-       char buf[32];
-       v = ldb_dn_get_extended_component(dn, "RMD_FLAGS");
-       if (!v || v->length > sizeof(buf)-1) return 0;
-       strncpy(buf, (const char *)v->data, v->length);
-       buf[v->length] = 0;
-       return strtoul(buf, NULL, 10);
+       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;
 }
 
 /*
@@ -3259,6 +3938,7 @@ 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;
@@ -3267,8 +3947,8 @@ uint32_t dsdb_dn_val_rmd_flags(const struct ldb_val *val)
        if (!p) {
                return 0;
        }
-       flags = strtoul(p+11, &end, 10);
-       if (!end || *end != '>') {
+       flags = strtoul_err(p+11, &end, 10, &error);
+       if (!end || *end != '>' || error != 0) {
                /* it must end in a > */
                return 0;
        }
@@ -3287,7 +3967,7 @@ bool dsdb_dn_is_deleted_val(const struct ldb_val *val)
   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(struct ldb_val *val)
+bool dsdb_dn_is_upgraded_link_val(const struct ldb_val *val)
 {
        return memmem(val->data, val->length, "<RMD_VERSION=", 13) != NULL;
 }
@@ -3353,7 +4033,7 @@ int dsdb_find_nc_root(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, struct ldb
 
        ret = ldb_search(samdb, tmp_ctx, &root_res,
                         ldb_dn_new(tmp_ctx, samdb, ""), LDB_SCOPE_BASE, root_attrs, NULL);
-       if (ret != LDB_SUCCESS) {
+       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;
@@ -3363,7 +4043,7 @@ int dsdb_find_nc_root(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, struct ldb
        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."));
+               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. */
@@ -3418,7 +4098,7 @@ int dsdb_find_nc_root(struct ldb_context *samdb, TALLOC_CTX *mem_ctx, struct ldb
        }
 
        talloc_free(tmp_ctx);
-       return LDB_ERR_NO_SUCH_OBJECT;
+       return ldb_error(samdb, LDB_ERR_NO_SUCH_OBJECT, __func__);
 }
 
 
@@ -3451,7 +4131,7 @@ 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_ERR_NO_SUCH_OBJECT;
+               return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__);
        }
        dn = ldb_dn_copy(ldb, dn);
        if (!dn) {
@@ -3498,11 +4178,12 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m
        const struct ldb_val *ouv_value;
        unsigned int i;
        int ret;
-       uint64_t highest_usn;
+       uint64_t highest_usn = 0;
        const struct GUID *our_invocation_id;
-       struct timeval now = timeval_current();
+       static const struct timeval tv1970;
+       NTTIME nt1970 = timeval_to_nttime(&tv1970);
 
-       ret = ldb_search(samdb, mem_ctx, &r, dn, LDB_SCOPE_BASE, attrs, NULL);
+       ret = dsdb_search_dn(samdb, mem_ctx, &r, dn, attrs, DSDB_SEARCH_SHOW_RECYCLED|DSDB_SEARCH_SHOW_DELETED);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -3541,7 +4222,7 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m
                return ldb_operr(samdb);
        }
 
-       ret = dsdb_load_partition_usn(samdb, dn, &highest_usn, NULL);
+       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);
@@ -3551,7 +4232,7 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m
        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 = timeval_to_nttime(&now);
+                       (*cursors)[i].last_sync_success = nt1970;
                        TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare);
                        return LDB_SUCCESS;
                }
@@ -3564,7 +4245,7 @@ int dsdb_load_udv_v2(struct ldb_context *samdb, struct ldb_dn *dn, TALLOC_CTX *m
 
        (*cursors)[*count].source_dsa_invocation_id = *our_invocation_id;
        (*cursors)[*count].highest_usn = highest_usn;
-       (*cursors)[*count].last_sync_success = timeval_to_nttime(&now);
+       (*cursors)[*count].last_sync_success = nt1970;
        (*count)++;
 
        TYPESAFE_QSORT(*cursors, *count, drsuapi_DsReplicaCursor2_compare);
@@ -3725,6 +4406,26 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags)
                }
        }
 
+       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) {
@@ -3732,9 +4433,24 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags)
                }
        }
 
+       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;
+               }
+       }
+
        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
 */
@@ -3847,7 +4563,7 @@ int dsdb_replace(struct ldb_context *ldb, struct ldb_message *msg, uint32_t dsdb
  */
 int dsdb_search_dn(struct ldb_context *ldb,
                   TALLOC_CTX *mem_ctx,
-                  struct ldb_result **_res,
+                  struct ldb_result **_result,
                   struct ldb_dn *basedn,
                   const char * const *attrs,
                   uint32_t dsdb_flags)
@@ -3892,7 +4608,7 @@ int dsdb_search_dn(struct ldb_context *ldb,
                return ret;
        }
 
-       *_res = res;
+       *_result = res;
        return LDB_SUCCESS;
 }
 
@@ -3902,7 +4618,7 @@ int dsdb_search_dn(struct ldb_context *ldb,
  */
 int dsdb_search_by_dn_guid(struct ldb_context *ldb,
                           TALLOC_CTX *mem_ctx,
-                          struct ldb_result **_res,
+                          struct ldb_result **_result,
                           const struct GUID *guid,
                           const char * const *attrs,
                           uint32_t dsdb_flags)
@@ -3917,7 +4633,7 @@ int dsdb_search_by_dn_guid(struct ldb_context *ldb,
                return ldb_oom(ldb);
        }
 
-       ret = dsdb_search_dn(ldb, mem_ctx, _res, dn, attrs, dsdb_flags);
+       ret = dsdb_search_dn(ldb, mem_ctx, _result, dn, attrs, dsdb_flags);
        talloc_free(tmp_ctx);
        return ret;
 }
@@ -3927,7 +4643,7 @@ int dsdb_search_by_dn_guid(struct ldb_context *ldb,
  */
 int dsdb_search(struct ldb_context *ldb,
                TALLOC_CTX *mem_ctx,
-               struct ldb_result **_res,
+               struct ldb_result **_result,
                struct ldb_dn *basedn,
                enum ldb_scope scope,
                const char * const *attrs,
@@ -3996,7 +4712,7 @@ int dsdb_search(struct ldb_context *ldb,
                if (res->count == 0) {
                        talloc_free(tmp_ctx);
                        ldb_reset_err_string(ldb);
-                       return LDB_ERR_NO_SUCH_OBJECT;
+                       return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, __func__);
                }
                if (res->count != 1) {
                        talloc_free(tmp_ctx);
@@ -4005,7 +4721,7 @@ int dsdb_search(struct ldb_context *ldb,
                }
        }
 
-       *_res = talloc_steal(mem_ctx, res);
+       *_result = talloc_steal(mem_ctx, res);
        talloc_free(tmp_ctx);
 
        return LDB_SUCCESS;
@@ -4162,8 +4878,12 @@ int dsdb_validate_dsa_guid(struct ldb_context *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 for DSA with objectGUID %s, sid %s\n",
-                        GUID_string(tmp_ctx, dsa_guid), dom_sid_string(tmp_ctx, sid)));
+               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);
        }
@@ -4370,6 +5090,17 @@ bool is_attr_in_list(const char * const * attrs, const char *attr)
        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
@@ -4508,3 +5239,702 @@ int dsdb_create_partial_replica_NC(struct ldb_context *ldb,  struct ldb_dn *dn)
        talloc_free(tmp_ctx);
        return LDB_SUCCESS;
 }
+
+/**
+  build a GUID from a string
+*/
+_PUBLIC_ NTSTATUS NS_GUID_from_string(const char *s, struct GUID *guid)
+{
+       NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+       uint32_t time_low;
+       uint32_t time_mid, time_hi_and_version;
+       uint32_t clock_seq[2];
+       uint32_t node[6];
+       int i;
+
+       if (s == NULL) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       status =  parse_guid_string(s,
+                                   &time_low,
+                                   &time_mid,
+                                   &time_hi_and_version,
+                                   clock_seq,
+                                   node);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       guid->time_low = time_low;
+       guid->time_mid = time_mid;
+       guid->time_hi_and_version = time_hi_and_version;
+       guid->clock_seq[0] = clock_seq[0];
+       guid->clock_seq[1] = clock_seq[1];
+       for (i=0;i<6;i++) {
+               guid->node[i] = node[i];
+       }
+
+       return NT_STATUS_OK;
+}
+
+_PUBLIC_ char *NS_GUID_string(TALLOC_CTX *mem_ctx, const struct GUID *guid)
+{
+       return talloc_asprintf(mem_ctx, 
+                              "%08x-%04x%04x-%02x%02x%02x%02x-%02x%02x%02x%02x",
+                              guid->time_low, guid->time_mid,
+                              guid->time_hi_and_version,
+                              guid->clock_seq[0],
+                              guid->clock_seq[1],
+                              guid->node[0], guid->node[1],
+                              guid->node[2], guid->node[3],
+                              guid->node[4], guid->node[5]);
+}
+
+/*
+ * 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
+ */
+static 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",
+                               ldb_dn_get_linearized(pso_dn),
+                               ldb_dn_get_linearized(user_msg->dn));
+               }
+               talloc_free(pso_dn);
+       }
+       return res;
+}
+
+/*
+ * 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;
+       struct ldb_result *res = NULL;
+       const char *attrs[] = { "msDS-LockoutObservationWindow",
+                               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 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 i, ret, badPwdCount;
+       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)
+{
+       int i, 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
+ * @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;
+       struct ldb_message_element *el;
+
+       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(ldb, usr_obj, usr_obj,
+                                "sAMAccountType",
+                                account_type);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+       el = ldb_msg_find_element(usr_obj, "sAMAccountType");
+       el->flags = LDB_FLAG_MOD_REPLACE;
+
+       if (account_type_p) {
+               *account_type_p = account_type;
+       }
+
+       return LDB_SUCCESS;
+}
+
+/**
+ * Determine and set primaryGroupID based on userAccountControl value
+ * @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;
+       struct ldb_message_element *el;
+
+       rid = ds_uf2prim_group_rid(user_account_control);
+
+       ret = samdb_msg_add_uint(ldb, usr_obj, usr_obj,
+                                "primaryGroupID", rid);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+       el = ldb_msg_find_element(usr_obj, "primaryGroupID");
+       el->flags = LDB_FLAG_MOD_REPLACE;
+
+       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;
+       struct ldb_dn *target_nc;
+       int ret;
+       bool same_nc = true;
+
+       tmp_ctx = talloc_new(mem_ctx);
+
+       ret = dsdb_find_nc_root(ldb, tmp_ctx, source_dn, &source_nc);
+       if (ret != LDB_SUCCESS) {
+               DBG_ERR("Failed to find base DN for source %s\n",
+                       ldb_dn_get_linearized(source_dn));
+               talloc_free(tmp_ctx);
+               return true;
+       }
+
+       ret = dsdb_find_nc_root(ldb, tmp_ctx, target_dn, &target_nc);
+       if (ret != LDB_SUCCESS) {
+               DBG_ERR("Failed to find base DN for target %s\n",
+                       ldb_dn_get_linearized(target_dn));
+               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 aysnc 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;
+               bool ok, 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;
+               }
+
+               ok = sid_parse(v->data, v->length, &sid);
+               if (!ok) {
+                       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 quey
+ * @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 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);
+
+       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;
+}