lib: Make sid_parse return the parsed length
[samba.git] / source4 / dsdb / common / util.c
index 0bbf402252395b165520f264cdff56c4aab1e0b3..411d1dd22b05d2d55fe674b5f9837b7d7b69cf45 100644 (file)
 #include "lib/socket/socket.h"
 #include "librpc/gen_ndr/irpc.h"
 #include "libds/common/flag_mapping.h"
-#include "../lib/util/util_runcmd.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
@@ -191,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
 */
@@ -351,7 +342,7 @@ uint32_t samdb_result_rid_from_sid(TALLOC_CTX *mem_ctx, const struct ldb_message
 struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_message *msg, 
                                     const char *attr)
 {
-       bool ok;
+       struct sid_parse_ret ret;
        const struct ldb_val *v;
        struct dom_sid *sid;
        v = ldb_msg_find_ldb_val(msg, attr);
@@ -362,8 +353,8 @@ struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_messa
        if (sid == NULL) {
                return NULL;
        }
-       ok = sid_parse(v->data, v->length, sid);
-       if (!ok) {
+       ret = sid_parse(v->data, v->length, sid);
+       if (ret.len == -1) {
                talloc_free(sid);
                return NULL;
        }
@@ -1287,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;
@@ -1375,21 +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) {
-               SMB_ASSERT(!GUID_all_zero(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);
@@ -1397,148 +1446,85 @@ 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, tmp_ctx), LDB_SCOPE_BASE, attrs, NULL);
+       ntds_settings_dn = samdb_ntds_settings_dn(ldb, tmp_ctx);
+       if (ntds_settings_dn == NULL) {
+               errstr = "samdb_ntds_settings_dn() returned NULL";
+               goto failed;
+       }
+
+       ret = ldb_search(ldb, tmp_ctx, &res, ntds_settings_dn,
+                        LDB_SCOPE_BASE, attrs, NULL);
        if (ret) {
+               errstr = ldb_errstring(ldb);
                goto failed;
        }
 
        if (res->count != 1) {
+               errstr = "incorrect number of results from base search";
                goto failed;
        }
 
-       invocation_id = talloc(tmp_ctx, struct GUID);
-       if (!invocation_id) {
+       ntds_guid = talloc(tmp_ctx, struct GUID);
+       if (ntds_guid == NULL) {
                goto failed;
        }
 
-       *invocation_id = samdb_result_guid(res->msgs[0], "invocationId");
-       if (GUID_all_zero(invocation_id)) {
-               if (ldb_msg_find_ldb_val(res->msgs[0], "invocationId")) {
-                       DEBUG(0, ("Failed to find our own NTDS Settings invocationId in the ldb!\n"));  
+       *ntds_guid = samdb_result_guid(res->msgs[0], attribute);
+
+       if (GUID_all_zero(ntds_guid)) {
+               if (ldb_msg_find_ldb_val(res->msgs[0], attribute)) {
+                       errstr = "failed to find the GUID attribute";
                } else {
-                       DEBUG(0, ("Failed to find parse own NTDS Settings invocationId from the ldb!\n"));
+                       errstr = "failed to parse the GUID";
                }
                goto failed;
        }
 
        /* cache the domain_sid in the ldb */
-       if (ldb_set_opaque(ldb, "cache.invocation_id", invocation_id) != 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);
+       talloc_steal(ldb, ntds_guid);
        talloc_free(tmp_ctx);
 
-       return invocation_id;
+       return ntds_guid;
 
 failed:
-       DEBUG(1,("Failed to find our own NTDS Settings 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 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;
-
-       /* 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) {
-               goto failed;
-       }
-
-       SMB_ASSERT(!GUID_all_zero(invocation_id_in));
-       *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) {
-               goto failed;
-       }
-
-       talloc_steal(ldb, invocation_id_new);
-       talloc_free(tmp_ctx);
-       talloc_free(invocation_id_old);
-
-       return true;
-
-failed:
-       DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n"));
-       talloc_free(tmp_ctx);
-       return false;
-}
-
 /*
   work out the ntds settings objectGUID for the current open ldb
 */
 const struct GUID *samdb_ntds_objectGUID(struct ldb_context *ldb)
 {
-       TALLOC_CTX *tmp_ctx;
-       const char *attrs[] = { "objectGUID", NULL };
-       int ret;
-       struct ldb_result *res;
-       struct GUID *ntds_guid;
-
-       /* see if we have a cached copy */
-       ntds_guid = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid");
-       if (ntds_guid) {
-               return ntds_guid;
-       }
-
-       tmp_ctx = talloc_new(ldb);
-       if (tmp_ctx == NULL) {
-               goto failed;
-       }
-
-       ret = ldb_search(ldb, tmp_ctx, &res, samdb_ntds_settings_dn(ldb, tmp_ctx), 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) {
-               goto failed;
-       }
-
-       *ntds_guid = samdb_result_guid(res->msgs[0], "objectGUID");
-
-       /* cache the domain_sid in the ldb */
-       if (ldb_set_opaque(ldb, "cache.ntds_guid", ntds_guid) != LDB_SUCCESS) {
-               goto failed;
-       }
-
-       talloc_steal(ldb, ntds_guid);
-       talloc_free(tmp_ctx);
-
-       return ntds_guid;
+       return samdb_ntds_GUID(ldb, "objectGUID", "cache.ntds_guid");
+}
 
-failed:
-       DEBUG(1,("Failed to find our own NTDS Settings objectGUID in the ldb!\n"));
-       talloc_free(tmp_ctx);
-       return NULL;
+/*
+  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");
 }
 
-bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in)
+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;
        struct GUID *ntds_guid_new;
        struct GUID *ntds_guid_old;
 
        /* see if we have a cached copy */
-       ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, "cache.ntds_guid");
+       ntds_guid_old = (struct GUID *)ldb_get_opaque(ldb, cache_name);
 
        tmp_ctx = talloc_new(ldb);
        if (tmp_ctx == NULL) {
@@ -1553,7 +1539,7 @@ bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_
        *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) {
+       if (ldb_set_opaque(ldb, cache_name, ntds_guid_new) != LDB_SUCCESS) {
                goto failed;
        }
 
@@ -1564,11 +1550,28 @@ bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_
        return true;
 
 failed:
-       DEBUG(1,("Failed to set our own cached invocationId in the ldb!\n"));
+       DBG_WARNING("Failed to set our own cached %s in the ldb!\n",
+                   attribute);
        talloc_free(tmp_ctx);
        return false;
 }
 
+bool samdb_set_ntds_objectGUID(struct ldb_context *ldb, const struct GUID *ntds_guid_in)
+{
+       return samdb_set_ntds_GUID(ldb,
+                                  ntds_guid_in,
+                                  "objectGUID",
+                                  "cache.ntds_guid");
+}
+
+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");
+}
+
 /*
   work out the server dn for the current open ldb
 */
@@ -1818,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
@@ -1841,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,
@@ -1856,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;
        }
@@ -1869,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");
@@ -1883,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 {
@@ -1909,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;
 }
@@ -2013,6 +2029,9 @@ static void pwd_timeout_debug(struct tevent_context *unused1,
  */
 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)
@@ -2041,7 +2060,7 @@ enum samr_ValidationStatus samdb_check_password(TALLOC_CTX *mem_ctx,
                int error = 0;
                struct tevent_context *event_ctx = NULL;
                struct tevent_req *req = NULL;
-               struct samba_runcmd_state *run_cmd = NULL;
+               int cps_stdin = -1;
                const char * const cmd[4] = {
                        "/bin/sh", "-c",
                        password_script,
@@ -2059,18 +2078,58 @@ enum samr_ValidationStatus samdb_check_password(TALLOC_CTX *mem_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);
-               run_cmd = tevent_req_data(req, struct samba_runcmd_state);
-               if (write(run_cmd->fd_stdin, utf8_pw, utf8_len) != utf8_len) {
+               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(run_cmd->fd_stdin);
-               run_cmd->fd_stdin = -1;
+               close(cps_stdin);
+               cps_stdin = -1;
 
                if (!tevent_req_poll(req, event_ctx)) {
                        TALLOC_FREE(password_script);
@@ -2085,17 +2144,16 @@ enum samr_ValidationStatus samdb_check_password(TALLOC_CTX *mem_ctx,
                        DEBUG(0, ("check_password_complexity: check password script took too long!\n"));
                        TALLOC_FREE(password_script);
                        return SAMR_VALIDATION_STATUS_PASSWORD_FILTER_ERROR;
-               } else {
-                       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;
-                       }
+               }
+               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);
@@ -2104,6 +2162,11 @@ enum samr_ValidationStatus samdb_check_password(TALLOC_CTX *mem_ctx,
 
        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;
        }
@@ -2318,7 +2381,7 @@ static NTSTATUS samdb_set_password_internal(struct ldb_context *ldb, TALLOC_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));
@@ -2339,7 +2402,7 @@ static NTSTATUS samdb_set_password_internal(struct ldb_context *ldb, TALLOC_CTX
                status = NT_STATUS_ACCESS_DENIED;
        } else if (ret != LDB_SUCCESS) {
                DEBUG(1, ("Failed to set password on %s: %s\n",
-                         ldb_dn_get_linearized(msg->dn),
+                         ldb_dn_get_linearized(user_dn),
                          ldb_errstring(ldb)));
                status = NT_STATUS_UNSUCCESSFUL;
        }
@@ -2450,12 +2513,18 @@ NTSTATUS samdb_set_password_sid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
                uint32_t trust_direction;
                uint32_t i;
                const struct ldb_val *old_val = NULL;
-               struct trustAuthInOutBlob old_blob = {};
+               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 = {};
-               struct ldb_val new_val = {};
+               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;
@@ -3478,6 +3547,53 @@ int samdb_rodc(struct ldb_context *sam_ctx, bool *am_rodc)
        return LDB_SUCCESS;
 }
 
+int samdb_dns_host_name(struct ldb_context *sam_ctx, const char **host_name)
+{
+       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;
@@ -3714,18 +3830,25 @@ 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;
+       int error = 0;
 
        v = ldb_dn_get_extended_component(dn, component_name);
        if (v == NULL) {
                return NT_STATUS_OBJECT_NAME_NOT_FOUND;
        }
 
-       {
+       /* 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;
 
-               *val = strtoull(s, NULL, 0);
+               *val = strtoull_err(s, NULL, 0, &error);
+               if (error != 0) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
        }
        return NT_STATUS_OK;
 }
@@ -3744,17 +3867,24 @@ 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;
+       int error = 0;
 
        v = ldb_dn_get_extended_component(dn, component_name);
        if (v == NULL) {
                return NT_STATUS_OBJECT_NAME_NOT_FOUND;
        }
 
-       {
+       /* 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(s, NULL, 0);
+               *val = strtoul_err(s, NULL, 0, &error);
+               if (error != 0) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
        }
 
        return NT_STATUS_OK;
@@ -3790,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;
 }
 
 /*
@@ -3808,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;
@@ -3816,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;
        }
@@ -3912,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. */
@@ -4302,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
 */
@@ -5110,12 +5256,12 @@ _PUBLIC_ NTSTATUS NS_GUID_from_string(const char *s, struct GUID *guid)
                return NT_STATUS_INVALID_PARAMETER;
        }
 
-       if (11 == sscanf(s, "%08x-%04x%04x-%02x%02x%02x%02x-%02x%02x%02x%02x",
-                        &time_low, &time_mid, &time_hi_and_version, 
-                        &clock_seq[0], &clock_seq[1],
-                        &node[0], &node[1], &node[2], &node[3], &node[4], &node[5])) {
-               status = NT_STATUS_OK;
-       }
+       status =  parse_guid_string(s,
+                                   &time_low,
+                                   &time_mid,
+                                   &time_hi_and_version,
+                                   clock_seq,
+                                   node);
 
        if (!NT_STATUS_IS_OK(status)) {
                return status;
@@ -5170,13 +5316,47 @@ static int dsdb_effective_badPwdCount(const struct ldb_message *user_msg,
        }
 }
 
+/*
+ * 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,
@@ -5185,11 +5365,64 @@ int samdb_result_effective_badPwdCount(struct ldb_context *sam_ldb,
 {
        struct timeval tv_now = timeval_current();
        NTTIME now = timeval_to_nttime(&tv_now);
-       int64_t lockOutObservationWindow = samdb_search_int64(sam_ldb, mem_ctx, 0, domain_dn,
-                                                             "lockOutObservationWindow", NULL);
+       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.
  *
@@ -5202,11 +5435,16 @@ int samdb_result_effective_badPwdCount(struct ldb_context *sam_ldb,
  *  - 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;
@@ -5239,8 +5477,7 @@ NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx,
         * Also, the built in administrator account is exempt:
         * http://msdn.microsoft.com/en-us/library/windows/desktop/aa375371%28v=vs.85%29.aspx
         */
-       lockoutThreshold = ldb_msg_find_attr_as_int(domain_msg,
-                                                   "lockoutThreshold", 0);
+       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)));
@@ -5257,8 +5494,8 @@ NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx,
                return NT_STATUS_NO_MEMORY;
        }
 
-       lockOutObservationWindow = ldb_msg_find_attr_as_int64(domain_msg,
-                                                             "lockOutObservationWindow", 0);
+       lockOutObservationWindow = get_lockout_observation_window(domain_msg,
+                                                                 pso_msg);
 
        badPwdCount = dsdb_effective_badPwdCount(user_msg, lockOutObservationWindow, now);
 
@@ -5281,10 +5518,10 @@ NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx,
                        TALLOC_FREE(mod_msg);
                        return NT_STATUS_NO_MEMORY;
                }
-               DEBUG(5, ("Locked out user %s after %d wrong passwords\n",
+               DEBUGC( DBGC_AUTH, 1, ("Locked out user %s after %d wrong passwords\n",
                          ldb_dn_get_linearized(user_msg->dn), badPwdCount));
        } else {
-               DEBUG(5, ("Updated badPwdCount on %s after %d wrong passwords\n",
+               DEBUGC( DBGC_AUTH, 5, ("Updated badPwdCount on %s after %d wrong passwords\n",
                          ldb_dn_get_linearized(user_msg->dn), badPwdCount));
        }
 
@@ -5483,3 +5720,222 @@ int dsdb_user_obj_set_primary_group_id(struct ldb_context *ldb, struct ldb_messa
 
        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;
+               struct sid_parse_ret ret;
+               bool 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;
+               }
+
+               ret = sid_parse(v->data, v->length, &sid);
+               if (ret.len == -1) {
+                       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;
+}