dsdb: Avoid calculating the PSO multiple times
[samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
index ee987d078ed0cb1ba0d5c91ad450c1e42c74ca9f..cdcec1c10463c7ab076c344cde746b48d00a4ace 100644 (file)
@@ -1,6 +1,7 @@
 /*
    ldb database library
 
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
    Copyright (C) Andrew Tridgell 2005
    Copyright (C) Simo Sorce 2006-2008
    Copyright (C) Matthias Dieter Wallnöfer 2009
  */
 
 /*
-  createTimestamp: HIDDEN, searchable, ldaptime, alias for whenCreated
-  modifyTimestamp: HIDDEN, searchable, ldaptime, alias for whenChanged
+  createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
+  modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
 
      for the above two, we do the search as normal, and if
-     createTimestamp or modifyTimestamp is asked for, then do
+     createTimeStamp or modifyTimeStamp is asked for, then do
      additional searches for whenCreated and whenChanged and fill in
      the resulting values
 
@@ -73,8 +74,7 @@
 #include "dsdb/samdb/samdb.h"
 #include "dsdb/samdb/ldb_modules/util.h"
 
-#include "auth/auth.h"
-#include "libcli/security/dom_sid.h"
+#include "libcli/security/security.h"
 
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
@@ -84,11 +84,30 @@ struct operational_data {
        struct ldb_dn *aggregate_dn;
 };
 
+enum search_type {
+       TOKEN_GROUPS,
+       TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
+       TOKEN_GROUPS_NO_GC_ACCEPTABLE,
+
+       /*
+        * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
+        * all account groups in a given domain, excluding built-in groups.
+        * (Used internally for msDS-ResultantPSO support)
+        */
+       ACCOUNT_GROUPS
+};
+
+static int get_pso_for_user(struct ldb_module *module,
+                           struct ldb_message *user_msg,
+                           struct ldb_request *parent,
+                           struct ldb_message **pso_msg);
+
 /*
   construct a canonical name from a message
 */
 static int construct_canonical_name(struct ldb_module *module,
-       struct ldb_message *msg, enum ldb_scope scope)
+                                   struct ldb_message *msg, enum ldb_scope scope,
+                                   struct ldb_request *parent)
 {
        char *canonicalName;
        canonicalName = ldb_dn_canonical_string(msg, msg->dn);
@@ -102,7 +121,8 @@ static int construct_canonical_name(struct ldb_module *module,
   construct a primary group token for groups from a message
 */
 static int construct_primary_group_token(struct ldb_module *module,
-                                        struct ldb_message *msg, enum ldb_scope scope)
+                                        struct ldb_message *msg, enum ldb_scope scope,
+                                        struct ldb_request *parent)
 {
        struct ldb_context *ldb;
        uint32_t primary_group_token;
@@ -115,7 +135,7 @@ static int construct_primary_group_token(struct ldb_module *module,
                        return LDB_SUCCESS;
                }
 
-               return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
+               return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
                        primary_group_token);
        } else {
                return LDB_SUCCESS;
@@ -123,66 +143,162 @@ static int construct_primary_group_token(struct ldb_module *module,
 }
 
 /*
-  construct the token groups for SAM objects from a message
-*/
-static int construct_token_groups(struct ldb_module *module,
-                                 struct ldb_message *msg, enum ldb_scope scope)
+ * Returns the group SIDs for the user in the given LDB message
+ */
+static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+                         struct ldb_message *msg, const char *attribute_string,
+                         enum search_type type, struct dom_sid **groupSIDs,
+                         unsigned int *num_groupSIDs)
 {
-       struct ldb_context *ldb = ldb_module_get_ctx(module);;
-       struct auth_context *auth_context;
-       struct auth_serversupplied_info *server_info;
-       struct auth_session_info *session_info;
-       TALLOC_CTX *tmp_ctx = talloc_new(msg);
-       uint32_t i;
-       int ret;
-
+       const char *filter = NULL;
        NTSTATUS status;
+       struct dom_sid *primary_group_sid;
+       const char *primary_group_string;
+       const char *primary_group_dn;
+       DATA_BLOB primary_group_blob;
+       struct dom_sid *account_sid;
+       const char *account_sid_string;
+       const char *account_sid_dn;
+       DATA_BLOB account_sid_blob;
+       struct dom_sid *domain_sid;
+
+       /* If it's not a user, it won't have a primaryGroupID */
+       if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
+               return LDB_SUCCESS;
+       }
 
-       if (scope != LDB_SCOPE_BASE) {
-               ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
-               return LDB_ERR_OPERATIONS_ERROR;
+       /* Ensure it has an objectSID too */
+       account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
+       if (account_sid == NULL) {
+               return LDB_SUCCESS;
        }
 
-       status = auth_context_create_from_ldb(tmp_ctx, ldb, &auth_context);
-       if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
-               talloc_free(tmp_ctx);
-               return ldb_module_oom(module);
+       status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+               return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
        } else if (!NT_STATUS_IS_OK(status)) {
-               ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, could not create authContext");
-               talloc_free(tmp_ctx);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       status = auth_get_server_info_principal(tmp_ctx, auth_context, NULL, msg->dn, &server_info);
-       if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
-               talloc_free(tmp_ctx);
-               return ldb_module_oom(module);
-       } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
-               /* Not a user, we have no tokenGroups */
-               talloc_free(tmp_ctx);
-               return LDB_SUCCESS;
-       } else if (!NT_STATUS_IS_OK(status)) {
-               talloc_free(tmp_ctx);
-               ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_get_server_info_principal failed: %s", nt_errstr(status));
+       primary_group_sid = dom_sid_add_rid(mem_ctx,
+                                           domain_sid,
+                                           ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
+       if (!primary_group_sid) {
+               return ldb_oom(ldb);
+       }
+
+       /* only return security groups */
+       switch(type) {
+       case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
+               filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
+                                        GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
+               break;
+       case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
+       case TOKEN_GROUPS:
+               filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
+                                        GROUP_TYPE_SECURITY_ENABLED);
+               break;
+
+       /* for RevMembGetAccountGroups, exclude built-in groups */
+       case ACCOUNT_GROUPS:
+               filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))",
+                               GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
+               break;
+       }
+
+       if (!filter) {
+               return ldb_oom(ldb);
+       }
+
+       primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
+       if (!primary_group_string) {
+               return ldb_oom(ldb);
+       }
+
+       primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
+       if (!primary_group_dn) {
+               return ldb_oom(ldb);
+       }
+
+       primary_group_blob = data_blob_string_const(primary_group_dn);
+
+       account_sid_string = dom_sid_string(mem_ctx, account_sid);
+       if (!account_sid_string) {
+               return ldb_oom(ldb);
+       }
+
+       account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
+       if (!account_sid_dn) {
+               return ldb_oom(ldb);
+       }
+
+       account_sid_blob = data_blob_string_const(account_sid_dn);
+
+       status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
+                                          true, /* We don't want to add the object's SID itself,
+                                                   it's not returend in this attribute */
+                                          filter,
+                                          mem_ctx, groupSIDs, num_groupSIDs);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
+                                      attribute_string, account_sid_string,
+                                      nt_errstr(status));
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       status = auth_generate_session_info(tmp_ctx, auth_context, server_info, 0, &session_info);
-       if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
-               talloc_free(tmp_ctx);
-               return ldb_module_oom(module);
-       } else if (!NT_STATUS_IS_OK(status)) {
+       /* Expands the primary group - this function takes in
+        * memberOf-like values, so we fake one up with the
+        * <SID=S-...> format of DN and then let it expand
+        * them, as long as they meet the filter - so only
+        * domain groups, not builtin groups
+        */
+       status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
+                                          mem_ctx, groupSIDs, num_groupSIDs);
+       if (!NT_STATUS_IS_OK(status)) {
+               ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
+                                      attribute_string, account_sid_string,
+                                      nt_errstr(status));
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       return LDB_SUCCESS;
+}
+
+/*
+  construct the token groups for SAM objects from a message
+*/
+static int construct_generic_token_groups(struct ldb_module *module,
+                                         struct ldb_message *msg, enum ldb_scope scope,
+                                         struct ldb_request *parent,
+                                         const char *attribute_string,
+                                         enum search_type type)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       TALLOC_CTX *tmp_ctx = talloc_new(msg);
+       unsigned int i;
+       int ret;
+       struct dom_sid *groupSIDs = NULL;
+       unsigned int num_groupSIDs = 0;
+
+       if (scope != LDB_SCOPE_BASE) {
+               ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       /* calculate the group SIDs for this object */
+       ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
+                            &groupSIDs, &num_groupSIDs);
+
+       if (ret != LDB_SUCCESS) {
                talloc_free(tmp_ctx);
-               ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_generate_session_info failed: %s", nt_errstr(status));
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       /* We start at 1, as the first SID is the user's SID, not included in the tokenGroups */
-       for (i = 1; i < session_info->security_token->num_sids; i++) {
-               ret = samdb_msg_add_dom_sid(ldb, msg, msg,
-                                           "tokenGroups",
-                                           &session_info->security_token->sids[i]);
-               if (ret != LDB_SUCCESS) {
+       /* add these SIDs to the search result */
+       for (i=0; i < num_groupSIDs; i++) {
+               ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
+               if (ret) {
                        talloc_free(tmp_ctx);
                        return ret;
                }
@@ -191,11 +307,46 @@ static int construct_token_groups(struct ldb_module *module,
        return LDB_SUCCESS;
 }
 
+static int construct_token_groups(struct ldb_module *module,
+                                 struct ldb_message *msg, enum ldb_scope scope,
+                                 struct ldb_request *parent)
+{
+       /**
+        * TODO: Add in a limiting domain when we start to support
+        * trusted domains.
+        */
+       return construct_generic_token_groups(module, msg, scope, parent,
+                                             "tokenGroups",
+                                             TOKEN_GROUPS);
+}
+
+static int construct_token_groups_no_gc(struct ldb_module *module,
+                                       struct ldb_message *msg, enum ldb_scope scope,
+                                       struct ldb_request *parent)
+{
+       /**
+        * TODO: Add in a limiting domain when we start to support
+        * trusted domains.
+        */
+       return construct_generic_token_groups(module, msg, scope, parent,
+                                             "tokenGroupsNoGCAcceptable",
+                                             TOKEN_GROUPS);
+}
+
+static int construct_global_universal_token_groups(struct ldb_module *module,
+                                                  struct ldb_message *msg, enum ldb_scope scope,
+                                                  struct ldb_request *parent)
+{
+       return construct_generic_token_groups(module, msg, scope, parent,
+                                             "tokenGroupsGlobalAndUniversal",
+                                             TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
+}
 /*
   construct the parent GUID for an entry from a message
 */
 static int construct_parent_guid(struct ldb_module *module,
-                                struct ldb_message *msg, enum ldb_scope scope)
+                                struct ldb_message *msg, enum ldb_scope scope,
+                                struct ldb_request *parent)
 {
        struct ldb_result *res, *parent_res;
        const struct ldb_val *parent_guid;
@@ -209,7 +360,10 @@ static int construct_parent_guid(struct ldb_module *module,
        /* determine if the object is NC by instance type */
        ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
                                    DSDB_FLAG_NEXT_MODULE |
-                                   DSDB_SEARCH_SHOW_DELETED);
+                                   DSDB_SEARCH_SHOW_RECYCLED, parent);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
 
        instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
                                                 "instanceType", 0);
@@ -224,29 +378,32 @@ static int construct_parent_guid(struct ldb_module *module,
        if (parent_dn == NULL) {
                DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
                                         ldb_dn_get_linearized(msg->dn)));
-               return LDB_SUCCESS;
+               return LDB_ERR_OTHER;
        }
        ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
                                    DSDB_FLAG_NEXT_MODULE |
-                                   DSDB_SEARCH_SHOW_DELETED);
-       talloc_free(parent_dn);
-
+                                   DSDB_SEARCH_SHOW_RECYCLED, parent);
        /* not NC, so the object should have a parent*/
        if (ret == LDB_ERR_NO_SUCH_OBJECT) {
-               DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
-                        ldb_dn_get_linearized(msg->dn)));
-               return ldb_operr(ldb_module_get_ctx(module));
+               ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR, 
+                                talloc_asprintf(msg, "Parent dn %s for %s does not exist",
+                                                ldb_dn_get_linearized(parent_dn),
+                                                ldb_dn_get_linearized(msg->dn)));
+               talloc_free(parent_dn);
+               return ret;
        } else if (ret != LDB_SUCCESS) {
+               talloc_free(parent_dn);
                return ret;
        }
+       talloc_free(parent_dn);
 
        parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
        if (!parent_guid) {
                talloc_free(parent_res);
-               return LDB_SUCCESS;
+               return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
        }
 
-       v = data_blob_dup_talloc(parent_res, parent_guid);
+       v = data_blob_dup_talloc(parent_res, *parent_guid);
        if (!v.data) {
                talloc_free(parent_res);
                return ldb_oom(ldb_module_get_ctx(module));
@@ -256,11 +413,48 @@ static int construct_parent_guid(struct ldb_module *module,
        return ret;
 }
 
+static int construct_modifyTimeStamp(struct ldb_module *module,
+                                       struct ldb_message *msg, enum ldb_scope scope,
+                                       struct ldb_request *parent)
+{
+       struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+       /* We may be being called before the init function has finished */
+       if (!data) {
+               return LDB_SUCCESS;
+       }
+
+       /* Try and set this value up, if possible.  Don't worry if it
+        * fails, we may not have the DB set up yet.
+        */
+       if (!data->aggregate_dn) {
+               data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+       }
+
+       if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
+               /*
+                * If we have the DN for the object with common name = Aggregate and
+                * the request is for this DN then let's do the following:
+                * 1) search the object which changedUSN correspond to the one of the loaded
+                * schema.
+                * 2) Get the whenChanged attribute
+                * 3) Generate the modifyTimestamp out of the whenChanged attribute
+                */
+               const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
+               char *value = ldb_timestring(msg, schema->ts_last_change);
+
+               return ldb_msg_add_string(msg, "modifyTimeStamp", value);
+       }
+       return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
+}
+
 /*
   construct a subSchemaSubEntry
 */
 static int construct_subschema_subentry(struct ldb_module *module,
-                                       struct ldb_message *msg, enum ldb_scope scope)
+                                       struct ldb_message *msg, enum ldb_scope scope,
+                                       struct ldb_request *parent)
 {
        struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
        char *subSchemaSubEntry;
@@ -297,7 +491,7 @@ static int construct_msds_isrodc_with_dn(struct ldb_module *module,
        ldb = ldb_module_get_ctx(module);
        if (!ldb) {
                DEBUG(4, (__location__ ": Failed to get ldb \n"));
-               return ldb_operr(ldb);
+               return LDB_ERR_OPERATIONS_ERROR;
        }
 
        dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
@@ -324,7 +518,8 @@ static int construct_msds_isrodc_with_dn(struct ldb_module *module,
 
 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
                                                struct ldb_message *msg,
-                                               struct ldb_dn *dn)
+                                               struct ldb_dn *dn,
+                                               struct ldb_request *parent)
 {
        struct ldb_dn *server_dn;
        const char *attr_obj_cat[] = { "objectCategory", NULL };
@@ -340,7 +535,7 @@ static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
        }
 
        ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
-                                   DSDB_FLAG_NEXT_MODULE);
+                                   DSDB_FLAG_NEXT_MODULE, parent);
        if (ret == LDB_ERR_NO_SUCH_OBJECT) {
                DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
                                         ldb_dn_get_linearized(server_dn)));
@@ -359,17 +554,16 @@ static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
 }
 
 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
-                                                 struct ldb_message *msg)
+                                                 struct ldb_message *msg,
+                                                 struct ldb_request *parent)
 {
-       struct ldb_context *ldb;
-       const char *attr[] = { "serverReferenceBL", NULL };
-       struct ldb_result *res;
        int ret;
        struct ldb_dn *server_dn;
 
-       ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attr,
-                                   DSDB_FLAG_NEXT_MODULE);
-       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+       ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
+                                      &server_dn, parent);
+       if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+               /* it's OK if we can't find serverReferenceBL attribute */
                DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
                         ldb_dn_get_linearized(msg->dn)));
                return LDB_SUCCESS;
@@ -377,25 +571,15 @@ static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
                return ret;
        }
 
-       ldb = ldb_module_get_ctx(module);
-       if (!ldb) {
-               return LDB_SUCCESS;
-       }
-
-       server_dn = ldb_msg_find_attr_as_dn(ldb, msg, res->msgs[0], "serverReferenceBL");
-       if (!server_dn) {
-               DEBUG(4,(__location__ ": Can't find serverReferenceBL for %s \n",
-                        ldb_dn_get_linearized(res->msgs[0]->dn)));
-               return LDB_SUCCESS;
-       }
-       return construct_msds_isrodc_with_server_dn(module, msg, server_dn);
+       return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
 }
 
 /*
   construct msDS-isRODC attr
 */
 static int construct_msds_isrodc(struct ldb_module *module,
-                                struct ldb_message *msg, enum ldb_scope scope)
+                                struct ldb_message *msg, enum ldb_scope scope,
+                                struct ldb_request *parent)
 {
        struct ldb_message_element * object_class;
        struct ldb_message_element * object_category;
@@ -426,13 +610,13 @@ static int construct_msds_isrodc(struct ldb_module *module,
                         * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
                         * substituting TN for TO.
                         */
-                       return construct_msds_isrodc_with_server_dn(module, msg, msg->dn);
+                       return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
                }
                if (strequal((const char*)object_class->values[i].data, "computer")) {
                        /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
                         * rule for the "TO is a server  object" case, substituting TS for TO.
                         */
-                       return construct_msds_isrodc_with_computer_dn(module, msg);
+                       return construct_msds_isrodc_with_computer_dn(module, msg, parent);
                }
        }
 
@@ -448,21 +632,20 @@ static int construct_msds_isrodc(struct ldb_module *module,
 */
 static int construct_msds_keyversionnumber(struct ldb_module *module,
                                           struct ldb_message *msg,
-                                          enum ldb_scope scope)
+                                          enum ldb_scope scope,
+                                          struct ldb_request *parent)
 {
        uint32_t i;
        enum ndr_err_code ndr_err;
        const struct ldb_val *omd_value;
        struct replPropertyMetaDataBlob *omd;
+       int ret;
 
        omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
        if (!omd_value) {
                /* We can't make up a key version number without meta data */
                return LDB_SUCCESS;
        }
-       if (!omd_value) {
-               return LDB_SUCCESS;
-       }
 
        omd = talloc(msg, struct replPropertyMetaDataBlob);
        if (!omd) {
@@ -485,8 +668,15 @@ static int construct_msds_keyversionnumber(struct ldb_module *module,
                return LDB_SUCCESS;
        }
        for (i=0; i<omd->ctr.ctr1.count; i++) {
-               if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTRIBUTE_unicodePwd) {
-                       ldb_msg_add_fmt(msg, "msDS-KeyVersionNumber", "%u", omd->ctr.ctr1.array[i].version);
+               if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
+                       ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
+                                                msg, msg,
+                                                "msDS-KeyVersionNumber",
+                                                omd->ctr.ctr1.array[i].version);
+                       if (ret != LDB_SUCCESS) {
+                               talloc_free(omd);
+                               return ret;
+                       }
                        break;
                }
        }
@@ -494,13 +684,621 @@ static int construct_msds_keyversionnumber(struct ldb_module *module,
 
 }
 
+#define _UF_TRUST_ACCOUNTS ( \
+       UF_WORKSTATION_TRUST_ACCOUNT | \
+       UF_SERVER_TRUST_ACCOUNT | \
+       UF_INTERDOMAIN_TRUST_ACCOUNT \
+)
+#define _UF_NO_EXPIRY_ACCOUNTS ( \
+       UF_SMARTCARD_REQUIRED | \
+       UF_DONT_EXPIRE_PASSWD | \
+       _UF_TRUST_ACCOUNTS \
+)
+
+
+/*
+ * Returns the Effective-MaximumPasswordAge for a user
+ */
+static int64_t get_user_max_pwd_age(struct ldb_module *module,
+                                   struct ldb_message *user_msg,
+                                   struct ldb_request *parent,
+                                   struct ldb_dn *nc_root)
+{
+       int ret;
+       struct ldb_message *pso = NULL;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+       /* if a PSO applies to the user, use its maxPwdAge */
+       ret = get_pso_for_user(module, user_msg, parent, &pso);
+       if (ret != LDB_SUCCESS) {
+
+               /* log the error, but fallback to the domain default */
+               DBG_ERR("Error retrieving PSO for %s\n",
+                       ldb_dn_get_linearized(user_msg->dn));
+       }
+
+       if (pso != NULL) {
+               return ldb_msg_find_attr_as_int64(pso,
+                                                 "msDS-MaximumPasswordAge", 0);
+       }
+
+       /* otherwise return the default domain value */
+       return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
+}
+
+/*
+  calculate msDS-UserPasswordExpiryTimeComputed
+*/
+static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
+                                               struct ldb_message *msg,
+                                               struct ldb_request *parent,
+                                               struct ldb_dn *domain_dn)
+{
+       int64_t pwdLastSet, maxPwdAge;
+       uint32_t userAccountControl;
+       NTTIME ret;
+
+       userAccountControl = ldb_msg_find_attr_as_uint(msg,
+                                       "userAccountControl",
+                                       0);
+       if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
+               return 0x7FFFFFFFFFFFFFFFULL;
+       }
+
+       pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
+       if (pwdLastSet == 0) {
+               return 0;
+       }
+
+       if (pwdLastSet <= -1) {
+               /*
+                * This can't really happen...
+                */
+               return 0x7FFFFFFFFFFFFFFFULL;
+       }
+
+       if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
+               /*
+                * Somethings wrong with the clock...
+                */
+               return 0x7FFFFFFFFFFFFFFFULL;
+       }
+
+       /*
+        * Note that maxPwdAge is a stored as negative value.
+        *
+        * Possible values are in the range of:
+        *
+        * maxPwdAge: -864000000001
+        * to
+        * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
+        *
+        */
+       maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
+       if (maxPwdAge >= -864000000000) {
+               /*
+                * This is not really possible...
+                */
+               return 0x7FFFFFFFFFFFFFFFULL;
+       }
+
+       if (maxPwdAge == -0x8000000000000000LL) {
+               return 0x7FFFFFFFFFFFFFFFULL;
+       }
+
+       /*
+        * Note we already catched maxPwdAge == -0x8000000000000000ULL
+        * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
+        *
+        * Remember maxPwdAge is a negative number,
+        * so it results in the following.
+        *
+        * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
+        * =
+        * 0xFFFFFFFFFFFFFFFFULL
+        */
+       ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
+       if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
+               return 0x7FFFFFFFFFFFFFFFULL;
+       }
+
+       return ret;
+}
+
+/*
+ * Returns the Effective-LockoutDuration for a user
+ */
+static int64_t get_user_lockout_duration(struct ldb_module *module,
+                                        struct ldb_message *user_msg,
+                                        struct ldb_request *parent,
+                                        struct ldb_dn *nc_root)
+{
+       int ret;
+       struct ldb_message *pso = NULL;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+       /* if a PSO applies to the user, use its lockoutDuration */
+       ret = get_pso_for_user(module, user_msg, parent, &pso);
+       if (ret != LDB_SUCCESS) {
+
+               /* log the error, but fallback to the domain default */
+               DBG_ERR("Error retrieving PSO for %s\n",
+                       ldb_dn_get_linearized(user_msg->dn));
+       }
+
+       if (pso != NULL) {
+               return ldb_msg_find_attr_as_int64(pso,
+                                                 "msDS-LockoutDuration", 0);
+       }
+
+       /* otherwise return the default domain value */
+       return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
+                                 NULL);
+}
+
+/*
+  construct msDS-User-Account-Control-Computed attr
+*/
+static int construct_msds_user_account_control_computed(struct ldb_module *module,
+                                                       struct ldb_message *msg, enum ldb_scope scope,
+                                                       struct ldb_request *parent)
+{
+       uint32_t userAccountControl;
+       uint32_t msDS_User_Account_Control_Computed = 0;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       NTTIME now;
+       struct ldb_dn *nc_root;
+       int ret;
+
+       ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
+       if (ret != 0) {
+               ldb_asprintf_errstring(ldb,
+                                      "Failed to find NC root of DN: %s: %s",
+                                      ldb_dn_get_linearized(msg->dn),
+                                      ldb_errstring(ldb_module_get_ctx(module)));
+               return ret;
+       }
+       if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
+               /* Only calculate this on our default NC */
+               return 0;
+       }
+       /* Test account expire time */
+       unix_to_nt_time(&now, time(NULL));
+
+       userAccountControl = ldb_msg_find_attr_as_uint(msg,
+                                                      "userAccountControl",
+                                                      0);
+       if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
+
+               int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
+               if (lockoutTime != 0) {
+                       int64_t lockoutDuration;
+
+                       lockoutDuration = get_user_lockout_duration(module, msg,
+                                                                   parent,
+                                                                   nc_root);
+
+                       /* zero locks out until the administrator intervenes */
+                       if (lockoutDuration >= 0) {
+                               msDS_User_Account_Control_Computed |= UF_LOCKOUT;
+                       } else if (lockoutTime - lockoutDuration >= now) {
+                               msDS_User_Account_Control_Computed |= UF_LOCKOUT;
+                       }
+               }
+       }
+
+       if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
+               NTTIME must_change_time
+                       = get_msds_user_password_expiry_time_computed(module,
+                                                                     msg,
+                                                                     parent,
+                                                                     nc_root);
+               /* check for expired password */
+               if (must_change_time < now) {
+                       msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
+               }
+       }
+
+       return samdb_msg_add_int64(ldb,
+                                  msg->elements, msg,
+                                  "msDS-User-Account-Control-Computed",
+                                  msDS_User_Account_Control_Computed);
+}
+
+/*
+  construct msDS-UserPasswordExpiryTimeComputed
+*/
+static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
+                                                            struct ldb_message *msg, enum ldb_scope scope,
+                                                            struct ldb_request *parent)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       struct ldb_dn *nc_root;
+       int64_t password_expiry_time;
+       int ret;
+
+       ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
+       if (ret != 0) {
+               ldb_asprintf_errstring(ldb,
+                                      "Failed to find NC root of DN: %s: %s",
+                                      ldb_dn_get_linearized(msg->dn),
+                                      ldb_errstring(ldb));
+               return ret;
+       }
+
+       if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
+               /* Only calculate this on our default NC */
+               return 0;
+       }
+
+       password_expiry_time
+               = get_msds_user_password_expiry_time_computed(module, msg,
+                                                             parent, nc_root);
+
+       return samdb_msg_add_int64(ldb,
+                                  msg->elements, msg,
+                                  "msDS-UserPasswordExpiryTimeComputed",
+                                  password_expiry_time);
+}
+
+/*
+ * Checks whether the msDS-ResultantPSO attribute is supported for a given
+ * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
+ */
+static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
+{
+       int functional_level;
+       uint32_t uac;
+       uint32_t user_rid;
+
+       functional_level = dsdb_functional_level(ldb);
+       if (functional_level < DS_DOMAIN_FUNCTION_2008) {
+               return false;
+       }
+
+       /* msDS-ResultantPSO is only supported for user objects */
+       if (!ldb_match_msg_objectclass(msg, "user")) {
+               return false;
+       }
+
+       /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
+       uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
+       if (!(uac & UF_NORMAL_ACCOUNT)) {
+               return false;
+       }
+
+       /* skip it if it's the special KRBTGT default account */
+       user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
+       if (user_rid == DOMAIN_RID_KRBTGT) {
+               return false;
+       }
+
+       /* ...or if it's a special KRBTGT account for an RODC KDC */
+       if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
+               return false;
+       }
+
+       return true;
+}
+
+/*
+ * Returns the number of PSO objects that exist in the DB
+ */
+static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+                        struct ldb_request *parent, int *pso_count)
+{
+       static const char * const attrs[] = { NULL };
+       int ret;
+       struct ldb_dn *domain_dn = NULL;
+       struct ldb_dn *psc_dn = NULL;
+       struct ldb_result *res = NULL;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+       domain_dn = ldb_get_default_basedn(ldb);
+       psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
+                               "CN=Password Settings Container,CN=System,%s",
+                               ldb_dn_get_linearized(domain_dn));
+       if (psc_dn == NULL) {
+               return ldb_oom(ldb);
+       }
+
+       /* get the number of PSO children */
+       ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
+                                LDB_SCOPE_ONELEVEL, attrs,
+                                DSDB_FLAG_NEXT_MODULE, parent,
+                                "(objectClass=msDS-PasswordSettings)");
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+
+       *pso_count = res->count;
+       talloc_free(res);
+       talloc_free(psc_dn);
+
+       return LDB_SUCCESS;
+}
+
+/*
+ * Compares two PSO objects returned by a search, to work out the better PSO.
+ * The PSO with the lowest precedence is better, otherwise (if the precedence
+ * is equal) the PSO with the lower GUID wins.
+ */
+static int pso_compare(struct ldb_message **m1, struct ldb_message **m2,
+                      TALLOC_CTX *mem_ctx)
+{
+       uint32_t prec1;
+       uint32_t prec2;
+
+       prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
+                                         0xffffffff);
+       prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
+                                         0xffffffff);
+
+       /* if precedence is equal, use the lowest GUID */
+       if (prec1 == prec2) {
+               struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
+               struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
+
+               return ndr_guid_compare(&guid1, &guid2);
+       } else {
+               return prec1 - prec2;
+       }
+}
+
+/*
+ * Search for PSO objects that apply to the object SIDs specified
+ */
+static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+                             struct ldb_request *parent,
+                             struct dom_sid *sid_array, unsigned int num_sids,
+                             struct ldb_result **result)
+{
+       int ret;
+       int i;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       char *sid_filter = NULL;
+       struct ldb_dn *domain_dn = NULL;
+       struct ldb_dn *psc_dn = NULL;
+       const char *attrs[] = {
+               "msDS-PasswordSettingsPrecedence",
+               "objectGUID",
+               "msDS-LockoutDuration",
+               "msDS-MaximumPasswordAge",
+               NULL
+       };
+
+       /* build a query for PSO objects that apply to any of the SIDs given */
+       sid_filter = talloc_strdup(mem_ctx, "");
+
+       for (i = 0; sid_filter && i < num_sids; i++) {
+               char sid_buf[DOM_SID_STR_BUFLEN] = {0,};
+
+               dom_sid_string_buf(&sid_array[i], sid_buf, sizeof(sid_buf));
+
+               sid_filter = talloc_asprintf_append(sid_filter,
+                                                   "(msDS-PSOAppliesTo=<SID=%s>)",
+                                                   sid_buf);
+       }
+
+       if (sid_filter == NULL) {
+               return ldb_oom(ldb);
+       }
+
+       /* only PSOs located in the Password Settings Container are valid */
+       domain_dn = ldb_get_default_basedn(ldb);
+       psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
+                               "CN=Password Settings Container,CN=System,%s",
+                               ldb_dn_get_linearized(domain_dn));
+       if (psc_dn == NULL) {
+               return ldb_oom(ldb);
+       }
+
+       ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
+                                LDB_SCOPE_ONELEVEL, attrs,
+                                DSDB_FLAG_NEXT_MODULE, parent,
+                                "(&(objectClass=msDS-PasswordSettings)(|%s))",
+                                sid_filter);
+       talloc_free(sid_filter);
+       return ret;
+}
+
+/*
+ * Returns the best PSO object that applies to the object SID(s) specified
+ */
+static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+                        struct ldb_request *parent, struct dom_sid *sid_array,
+                        unsigned int num_sids, struct ldb_message **best_pso)
+{
+       struct ldb_result *res = NULL;
+       int ret;
+
+       *best_pso = NULL;
+
+       /* find any PSOs that apply to the SIDs specified */
+       ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
+                                &res);
+       if (ret != LDB_SUCCESS) {
+               DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
+               return ret;
+       }
+
+       /* sort the list so that the best PSO is first */
+       LDB_TYPESAFE_QSORT(res->msgs, res->count, mem_ctx, pso_compare);
+
+       if (res->count > 0) {
+               *best_pso = res->msgs[0];
+       }
+
+       return LDB_SUCCESS;
+}
+
+/*
+ * Determines the Password Settings Object (PSO) that applies to the given user
+ */
+static int get_pso_for_user(struct ldb_module *module,
+                           struct ldb_message *user_msg,
+                           struct ldb_request *parent,
+                            struct ldb_message **pso_msg)
+{
+       bool pso_supported;
+       struct dom_sid *groupSIDs = NULL;
+       unsigned int num_groupSIDs = 0;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       struct ldb_message *best_pso = NULL;
+       struct ldb_dn *pso_dn = NULL;
+       int ret;
+       struct ldb_message_element *el = NULL;
+       TALLOC_CTX *tmp_ctx = NULL;
+       int pso_count = 0;
+       struct ldb_result *res = NULL;
+       static const char *attrs[] = {
+               "msDS-LockoutDuration",
+               "msDS-MaximumPasswordAge",
+               NULL
+       };
+
+       *pso_msg = NULL;
+
+       /* first, check msDS-ResultantPSO is supported for this object */
+       pso_supported = pso_is_supported(ldb, user_msg);
+
+       if (!pso_supported) {
+               return LDB_SUCCESS;
+       }
+
+       tmp_ctx = talloc_new(user_msg);
+
+       /*
+        * Several different constructed attributes try to use the PSO info. If
+        * we've already constructed the msDS-ResultantPSO for this user, we can
+        * just re-use the result, rather than calculating it from scratch again
+        */
+       pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
+                                        "msDS-ResultantPSO");
+
+       if (pso_dn != NULL) {
+               ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
+                                           attrs, DSDB_FLAG_NEXT_MODULE,
+                                           parent);
+               if (ret != LDB_SUCCESS) {
+                       DBG_ERR("Error %d retrieving PSO %s\n", ret,
+                               ldb_dn_get_linearized(pso_dn));
+                       talloc_free(tmp_ctx);
+                       return ret;
+               }
+
+               if (res->count == 1) {
+                       *pso_msg = res->msgs[0];
+                       return LDB_SUCCESS;
+               }
+       }
+
+       /*
+        * if any PSOs apply directly to the user, they are considered first
+        * before we check group membership PSOs
+        */
+       el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
+
+       if (el != NULL && el->num_values > 0) {
+               struct dom_sid *user_sid = NULL;
+
+               /* lookup the best PSO object, based on the user's SID */
+               user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
+
+               ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
+                                   &best_pso);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(tmp_ctx);
+                       return ret;
+               }
+
+               if (best_pso != NULL) {
+                       *pso_msg = best_pso;
+                       return LDB_SUCCESS;
+               }
+       }
+
+       /*
+        * If no valid PSO applies directly to the user, then try its groups.
+        * The group expansion is expensive, so check there are actually
+        * PSOs in the DB first (which is a quick search). Note in the above
+        * cases we could tell that a PSO applied to the user, based on info
+        * already retrieved by the user search.
+        */
+       ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
+       if (ret != LDB_SUCCESS) {
+               DBG_ERR("Error %d determining PSOs in system\n", ret);
+               talloc_free(tmp_ctx);
+               return ret;
+       }
+
+       if (pso_count == 0) {
+               talloc_free(tmp_ctx);
+               return LDB_SUCCESS;
+       }
+
+       /* Work out the SIDs of any account groups the user is a member of */
+       ret = get_group_sids(ldb, tmp_ctx, user_msg,
+                            "msDS-ResultantPSO", ACCOUNT_GROUPS,
+                            &groupSIDs, &num_groupSIDs);
+       if (ret != LDB_SUCCESS) {
+               DBG_ERR("Error %d determining group SIDs for %s\n", ret,
+                       ldb_dn_get_linearized(user_msg->dn));
+               talloc_free(tmp_ctx);
+               return ret;
+       }
+
+       /* lookup the best PSO that applies to any of these groups */
+       ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
+                           num_groupSIDs, &best_pso);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(tmp_ctx);
+               return ret;
+       }
+
+       *pso_msg = best_pso;
+       return LDB_SUCCESS;
+}
+
+/*
+ * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
+ * Settings Object (PSO) that applies to that user.
+ */
+static int construct_resultant_pso(struct ldb_module *module,
+                                   struct ldb_message *msg,
+                                  enum ldb_scope scope,
+                                   struct ldb_request *parent)
+{
+       struct ldb_message *pso = NULL;
+       int ret;
+
+       /* work out the PSO (if any) that applies to this user */
+       ret = get_pso_for_user(module, msg, parent, &pso);
+       if (ret != LDB_SUCCESS) {
+               DBG_ERR("Couldn't determine PSO for %s\n",
+                       ldb_dn_get_linearized(msg->dn));
+               return ret;
+       }
+
+       if (pso != NULL) {
+               DBG_INFO("%s is resultant PSO for user %s\n",
+                        ldb_dn_get_linearized(pso->dn),
+                        ldb_dn_get_linearized(msg->dn));
+               return ldb_msg_add_string(msg, "msDS-ResultantPSO",
+                                         ldb_dn_get_linearized(pso->dn));
+       }
+
+       /* no PSO applies to this user */
+       return LDB_SUCCESS;
+}
+
 struct op_controls_flags {
        bool sd;
        bool bypassoperational;
 };
 
 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
-       if (ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 && controls_flags->bypassoperational) {
+       if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
                return true;
        }
        return false;
@@ -514,31 +1312,93 @@ static const struct {
        const char *attr;
        const char *replace;
 } parse_tree_sub[] = {
-       { "createTimestamp", "whenCreated" },
-       { "modifyTimestamp", "whenChanged" }
+       { "createTimeStamp", "whenCreated" },
+       { "modifyTimeStamp", "whenChanged" }
+};
+
+
+struct op_attributes_replace {
+       const char *attr;
+       const char *replace;
+       const char * const *extra_attrs;
+       int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
+};
+
+/* the 'extra_attrs' required for msDS-ResultantPSO */
+#define RESULTANT_PSO_COMPUTED_ATTRS \
+       "msDS-PSOApplied", \
+       "userAccountControl", \
+       "objectSid", \
+       "msDS-SecondaryKrbTgtNumber", \
+       "primaryGroupID"
+
+/*
+ * any other constructed attributes that want to work out the PSO also need to
+ * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
+ */
+#define PSO_ATTR_DEPENDENCIES \
+       RESULTANT_PSO_COMPUTED_ATTRS, \
+       "objectClass"
+
+static const char *objectSid_attr[] =
+{
+       "objectSid",
+       NULL
+};
+
+
+static const char *objectCategory_attr[] =
+{
+       "objectCategory",
+       NULL
+};
+
+
+static const char *user_account_control_computed_attrs[] =
+{
+       "lockoutTime",
+       "pwdLastSet",
+       PSO_ATTR_DEPENDENCIES,
+       NULL
+};
+
+
+static const char *user_password_expiry_time_computed_attrs[] =
+{
+       "pwdLastSet",
+       PSO_ATTR_DEPENDENCIES,
+       NULL
 };
 
+static const char *resultant_pso_computed_attrs[] =
+{
+       RESULTANT_PSO_COMPUTED_ATTRS,
+       NULL
+};
 
 /*
   a list of attribute names that are hidden, but can be searched for
   using another (non-hidden) name to produce the correct result
 */
-static const struct {
-       const char *attr;
-       const char *replace;
-       const char *extra_attr;
-       int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope);
-} search_sub[] = {
-       { "createTimestamp", "whenCreated", NULL , NULL },
-       { "modifyTimestamp", "whenChanged", NULL , NULL },
+static const struct op_attributes_replace search_sub[] = {
+       { "createTimeStamp", "whenCreated", NULL , NULL },
+       { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
        { "structuralObjectClass", "objectClass", NULL , NULL },
-       { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
-       { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
-       { "tokenGroups", "objectClass", NULL, construct_token_groups },
+       { "canonicalName", NULL, NULL , construct_canonical_name },
+       { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
+       { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
+       { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
+       { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
        { "parentGUID", NULL, NULL, construct_parent_guid },
        { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
-       { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
-       { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
+       { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
+       { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
+       { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
+         construct_msds_user_account_control_computed },
+       { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
+         construct_msds_user_password_expiry_time_computed },
+       { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
+         construct_resultant_pso }
 };
 
 
@@ -555,19 +1415,18 @@ enum op_remove {
 
   Some of these are attributes that were once stored, but are now calculated
 */
-static const struct {
+struct op_attributes_operations {
        const char *attr;
        enum op_remove op;
-} operational_remove[] = {
+};
+
+static const struct op_attributes_operations operational_remove[] = {
        { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
        { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_UNLESS_CONTROL  },
        { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
        { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
-       { "unicodePwd",              OPERATIONAL_REMOVE_UNASKED },
-       { "dBCSPwd",                 OPERATIONAL_REMOVE_UNASKED },
-       { "ntPwdHistory",            OPERATIONAL_REMOVE_UNASKED },
-       { "lmPwdHistory",            OPERATIONAL_REMOVE_UNASKED },
-       { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
+#define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
+       { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
 };
 
 
@@ -582,7 +1441,12 @@ static int operational_search_post_process(struct ldb_module *module,
                                           enum ldb_scope scope,
                                           const char * const *attrs_from_user,
                                           const char * const *attrs_searched_for,
-                                          struct op_controls_flags* controls_flags)
+                                          struct op_controls_flags* controls_flags,
+                                          struct op_attributes_operations *list,
+                                          unsigned int list_size,
+                                          struct op_attributes_replace *list_replace,
+                                          unsigned int list_replace_size,
+                                          struct ldb_request *parent)
 {
        struct ldb_context *ldb;
        unsigned int i, a = 0;
@@ -591,56 +1455,27 @@ static int operational_search_post_process(struct ldb_module *module,
        ldb = ldb_module_get_ctx(module);
 
        /* removed any attrs that should not be shown to the user */
-       for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
-               switch (operational_remove[i].op) {
-               case OPERATIONAL_REMOVE_UNASKED:
-                       if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
-                               continue;
-                       }
-                       if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
-                               continue;
-                       }
-               case OPERATIONAL_REMOVE_ALWAYS:
-                       ldb_msg_remove_attr(msg, operational_remove[i].attr);
-                       break;
-               case OPERATIONAL_REMOVE_UNLESS_CONTROL:
-                       if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
-                               ldb_msg_remove_attr(msg, operational_remove[i].attr);
-                               break;
-                       } else {
-                               continue;
-                       }
-               case OPERATIONAL_SD_FLAGS:
-                       if (controls_flags->sd ||
-                           ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
-                               continue;
-                       }
-                       ldb_msg_remove_attr(msg, operational_remove[i].attr);
-                       break;
-               }
+       for (i=0; i < list_size; i++) {
+               ldb_msg_remove_attr(msg, list[i].attr);
        }
 
-       for (a=0;attrs_from_user && attrs_from_user[a];a++) {
-               if (check_keep_control_for_attribute(controls_flags, attrs_from_user[a])) {
+       for (a=0; a < list_replace_size; a++) {
+               if (check_keep_control_for_attribute(controls_flags,
+                                                    list_replace[a].attr)) {
                        continue;
                }
-               for (i=0;i<ARRAY_SIZE(search_sub);i++) {
-                       if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
-                               continue;
-                       }
 
-                       /* construct the new attribute, using either a supplied
-                          constructor or a simple copy */
-                       constructed_attributes = true;
-                       if (search_sub[i].constructor != NULL) {
-                               if (search_sub[i].constructor(module, msg, scope) != LDB_SUCCESS) {
-                                       goto failed;
-                               }
-                       } else if (ldb_msg_copy_attr(msg,
-                                                    search_sub[i].replace,
-                                                    search_sub[i].attr) != LDB_SUCCESS) {
+               /* construct the new attribute, using either a supplied
+                       constructor or a simple copy */
+               constructed_attributes = true;
+               if (list_replace[a].constructor != NULL) {
+                       if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
                                goto failed;
                        }
+               } else if (ldb_msg_copy_attr(msg,
+                                            list_replace[a].replace,
+                                            list_replace[a].attr) != LDB_SUCCESS) {
+                       goto failed;
                }
        }
 
@@ -649,16 +1484,20 @@ static int operational_search_post_process(struct ldb_module *module,
         * - we aren't requesting all attributes
         */
        if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
-               for (i=0;i<ARRAY_SIZE(search_sub);i++) {
+               for (i=0; i < list_replace_size; i++) {
                        /* remove the added search helper attributes, unless
                         * they were asked for by the user */
-                       if (search_sub[i].replace != NULL && 
-                           !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
-                               ldb_msg_remove_attr(msg, search_sub[i].replace);
+                       if (list_replace[i].replace != NULL &&
+                           !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
+                               ldb_msg_remove_attr(msg, list_replace[i].replace);
                        }
-                       if (search_sub[i].extra_attr != NULL && 
-                           !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
-                               ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
+                       if (list_replace[i].extra_attrs != NULL) {
+                               unsigned int j;
+                               for (j=0; list_replace[i].extra_attrs[j]; j++) {
+                                       if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
+                                               ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
+                                       }
+                               }
                        }
                }
        }
@@ -667,8 +1506,8 @@ static int operational_search_post_process(struct ldb_module *module,
 
 failed:
        ldb_debug_set(ldb, LDB_DEBUG_WARNING,
-                     "operational_search_post_process failed for attribute '%s'",
-                     attrs_from_user[a]);
+                     "operational_search_post_process failed for attribute '%s' - %s",
+                     list_replace[a].attr, ldb_errstring(ldb));
        return -1;
 }
 
@@ -682,6 +1521,10 @@ struct operational_context {
        enum ldb_scope scope;
        const char * const *attrs;
        struct op_controls_flags* controls_flags;
+       struct op_attributes_operations *list_operations;
+       unsigned int list_operations_size;
+       struct op_attributes_replace *attrs_to_replace;
+       unsigned int attrs_to_replace_size;
 };
 
 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
@@ -709,7 +1552,12 @@ static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
                                                      ac->scope,
                                                      ac->attrs,
                                                      req->op.search.attrs,
-                                                     ac->controls_flags);
+                                                     ac->controls_flags,
+                                                     ac->list_operations,
+                                                     ac->list_operations_size,
+                                                     ac->attrs_to_replace,
+                                                     ac->attrs_to_replace_size,
+                                                     req);
                if (ret != 0) {
                        return ldb_module_done(ac->req, NULL, NULL,
                                                LDB_ERR_OPERATIONS_ERROR);
@@ -729,6 +1577,74 @@ static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
        return LDB_SUCCESS;
 }
 
+static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
+                                                             const char* const* attrs,
+                                                             const char* const* searched_attrs,
+                                                             struct op_controls_flags* controls_flags)
+{
+       int idx = 0;
+       int i;
+       struct op_attributes_operations *list = talloc_zero_array(ctx,
+                                                                 struct op_attributes_operations,
+                                                                 ARRAY_SIZE(operational_remove) + 1);
+
+       if (list == NULL) {
+               return NULL;
+       }
+
+       for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
+               switch (operational_remove[i].op) {
+               case OPERATIONAL_REMOVE_UNASKED:
+                       if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
+                               continue;
+                       }
+                       if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
+                               continue;
+                       }
+                       list[idx].attr = operational_remove[i].attr;
+                       list[idx].op = OPERATIONAL_REMOVE_UNASKED;
+                       idx++;
+                       break;
+
+               case OPERATIONAL_REMOVE_ALWAYS:
+                       list[idx].attr = operational_remove[i].attr;
+                       list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
+                       idx++;
+                       break;
+
+               case OPERATIONAL_REMOVE_UNLESS_CONTROL:
+                       if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
+                               list[idx].attr = operational_remove[i].attr;
+                               list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
+                               idx++;
+                       }
+                       break;
+
+               case OPERATIONAL_SD_FLAGS:
+                       if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
+                               continue;
+                       }
+                       if (controls_flags->sd) {
+                               if (attrs == NULL) {
+                                       continue;
+                               }
+                               if (attrs[0] == NULL) {
+                                       continue;
+                               }
+                               if (ldb_attr_in_list(attrs, "*")) {
+                                       continue;
+                               }
+                       }
+                       list[idx].attr = operational_remove[i].attr;
+                       list[idx].op = OPERATIONAL_SD_FLAGS;
+                       idx++;
+                       break;
+               }
+       }
+
+       return list;
+}
+
 static int operational_search(struct ldb_module *module, struct ldb_request *req)
 {
        struct ldb_context *ldb;
@@ -769,28 +1685,48 @@ static int operational_search(struct ldb_module *module, struct ldb_request *req
        ac->controls_flags = talloc(ac, struct op_controls_flags);
        /* remember if the SD_FLAGS_OID was set */
        ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
-       /* remember if the LDB_CONTROL_BYPASSOPERATIONAL_OID */
-       ac->controls_flags->bypassoperational = (ldb_request_get_control(req,
-                                                       LDB_CONTROL_BYPASSOPERATIONAL_OID) != NULL);
+       /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
+       ac->controls_flags->bypassoperational =
+               (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
 
+       ac->attrs_to_replace = NULL;
+       ac->attrs_to_replace_size = 0;
        /* in the list of attributes we are looking for, rename any
           attributes to the alias for any hidden attributes that can
-          be fetched directly using non-hidden names */
+          be fetched directly using non-hidden names.
+          Note that order here can affect performance, e.g. we should process
+          msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
+          latter is also dependent on the PSO information) */
        for (a=0;ac->attrs && ac->attrs[a];a++) {
                if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
                        continue;
                }
                for (i=0;i<ARRAY_SIZE(search_sub);i++) {
-                       if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
-                           search_sub[i].replace) {
 
-                               if (search_sub[i].extra_attr) {
-                                       const char **search_attrs2;
-                                       /* Only adds to the end of the list */
+                       if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
+                               continue;
+                       }
+
+                       ac->attrs_to_replace = talloc_realloc(ac,
+                                                             ac->attrs_to_replace,
+                                                             struct op_attributes_replace,
+                                                             ac->attrs_to_replace_size + 1);
+
+                       ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
+                       ac->attrs_to_replace_size++;
+                       if (!search_sub[i].replace) {
+                               continue;
+                       }
+
+                       if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
+                               unsigned int j;
+                               const char **search_attrs2;
+                               /* Only adds to the end of the list */
+                               for (j = 0; search_sub[i].extra_attrs[j]; j++) {
                                        search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
                                                                               ? search_attrs
                                                                               : ac->attrs, 
-                                                                              search_sub[i].extra_attr);
+                                                                              search_sub[i].extra_attrs[j]);
                                        if (search_attrs2 == NULL) {
                                                return ldb_operr(ldb);
                                        }
@@ -798,19 +1734,28 @@ static int operational_search(struct ldb_module *module, struct ldb_request *req
                                        talloc_free(search_attrs);
                                        search_attrs = search_attrs2;
                                }
+                       }
 
-                               if (!search_attrs) {
-                                       search_attrs = ldb_attr_list_copy(req, ac->attrs);
-                                       if (search_attrs == NULL) {
-                                               return ldb_operr(ldb);
-                                       }
+                       if (!search_attrs) {
+                               search_attrs = ldb_attr_list_copy(req, ac->attrs);
+                               if (search_attrs == NULL) {
+                                       return ldb_operr(ldb);
                                }
-                               /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
-                               search_attrs[a] = search_sub[i].replace;
                        }
+                       /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
+                       search_attrs[a] = search_sub[i].replace;
                }
        }
-
+       ac->list_operations = operation_get_op_list(ac, ac->attrs,
+                                                   search_attrs == NULL?req->op.search.attrs:search_attrs,
+                                                   ac->controls_flags);
+       ac->list_operations_size = 0;
+       i = 0;
+
+       while (ac->list_operations && ac->list_operations[i].attr != NULL) {
+               i++;
+       }
+       ac->list_operations_size = i;
        ret = ldb_build_search_req_ex(&down_req, ldb, ac,
                                        req->op.search.base,
                                        req->op.search.scope,
@@ -820,6 +1765,7 @@ static int operational_search(struct ldb_module *module, struct ldb_request *req
                                        req->controls,
                                        ac, operational_callback,
                                        req);
+       LDB_REQ_SET_LOCATION(down_req);
        if (ret != LDB_SUCCESS) {
                return ldb_operr(ldb);
        }
@@ -832,7 +1778,6 @@ static int operational_init(struct ldb_module *ctx)
 {
        struct operational_data *data;
        int ret;
-       auth_init();
 
        ret = ldb_next_init(ctx);
 
@@ -850,8 +1795,14 @@ static int operational_init(struct ldb_module *ctx)
        return LDB_SUCCESS;
 }
 
-_PUBLIC_ const struct ldb_module_ops ldb_operational_module_ops = {
+static const struct ldb_module_ops ldb_operational_module_ops = {
        .name              = "operational",
        .search            = operational_search,
        .init_context      = operational_init
 };
+
+int ldb_operational_module_init(const char *version)
+{
+       LDB_MODULE_CHECK_VERSION(version);
+       return ldb_register_module(&ldb_operational_module_ops);
+}