s4-dsdb: pass parent request to dsdb_module_*() functions
[samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
index ccfddbe56edf98530856d2a18d2687b709dfd56e..ae61089198ff41eb4579056da7eb53851a4b981d 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
 */
 
 #include "includes.h"
-#include "ldb_includes.h"
-#include "ldb_module.h"
+#include <ldb.h>
+#include <ldb_module.h>
 
 #include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
 #include "param/param.h"
 #include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#include "auth/auth.h"
+#include "libcli/security/security.h"
 
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
 #endif
 
+struct operational_data {
+       struct ldb_dn *aggregate_dn;
+};
+
 /*
   construct a canonical name from a message
 */
 static int construct_canonical_name(struct ldb_module *module,
-       struct ldb_message *msg)
+                                   struct ldb_message *msg, enum ldb_scope scope,
+                                   struct ldb_request *parent)
 {
        char *canonicalName;
        canonicalName = ldb_dn_canonical_string(msg, msg->dn);
        if (canonicalName == NULL) {
-               return -1;
+               return ldb_operr(ldb_module_get_ctx(module));
        }
        return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
 }
@@ -93,53 +104,495 @@ 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)
+                                        struct ldb_message *msg, enum ldb_scope scope,
+                                        struct ldb_request *parent)
 {
        struct ldb_context *ldb;
        uint32_t primary_group_token;
-
+       
        ldb = ldb_module_get_ctx(module);
-
-       if (samdb_search_count(ldb, ldb, msg->dn, "(objectclass=group)") == 1) {
+       if (ldb_match_msg_objectclass(msg, "group") == 1) {
                primary_group_token
-                       = samdb_result_rid_from_sid(ldb, msg, "objectSid", 0);
-               return samdb_msg_add_int(ldb, ldb, msg, "primaryGroupToken",
+                       = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
+               if (primary_group_token == 0) {
+                       return LDB_SUCCESS;
+               }
+
+               return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
                        primary_group_token);
        } else {
                return LDB_SUCCESS;
        }
 }
 
-static int construct_parent_guid(struct ldb_module *module,
-               struct ldb_message *msg)
+/*
+  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,
+                                 struct ldb_request *parent)
 {
-       struct ldb_context *ldb;
-       struct GUID parent_guid;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);;
+       TALLOC_CTX *tmp_ctx = talloc_new(msg);
+       unsigned int i;
        int ret;
+       const char *filter;
 
-       ldb = ldb_module_get_ctx(module);
+       NTSTATUS status;
 
-       ret = dsdb_find_parentguid_by_dn(ldb, msg->dn, &parent_guid);
+       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 **groupSIDs = NULL;
+       unsigned int num_groupSIDs = 0;
 
-       if (ret != LDB_SUCCESS){
+       struct dom_sid *domain_sid;
 
-               /* if there is no parentGUID for this object, then return */
-               if (ret == LDB_ERR_NO_SUCH_OBJECT){
-                       return LDB_SUCCESS;
-               }else{
+       if (scope != LDB_SCOPE_BASE) {
+               ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       /* If it's not a user, it won't have a primaryGroupID */
+       if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
+               talloc_free(tmp_ctx);
+               return LDB_SUCCESS;
+       }
+
+       /* Ensure it has an objectSID too */
+       account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
+       if (account_sid == NULL) {
+               talloc_free(tmp_ctx);
+               return LDB_SUCCESS;
+       }
+
+       status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+               talloc_free(tmp_ctx);
+               return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+       } else if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(tmp_ctx);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       primary_group_sid = dom_sid_add_rid(tmp_ctx,
+                                           domain_sid,
+                                           ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
+       if (!primary_group_sid) {
+               talloc_free(tmp_ctx);
+               return ldb_oom(ldb);
+       }
+
+       /* only return security groups */
+       filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
+                                GROUP_TYPE_SECURITY_ENABLED);
+       if (!filter) {
+               talloc_free(tmp_ctx);
+               return ldb_oom(ldb);
+       }
+
+       primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
+       if (!primary_group_string) {
+               talloc_free(tmp_ctx);
+               return ldb_oom(ldb);
+       }
+
+       primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
+       if (!primary_group_dn) {
+               talloc_free(tmp_ctx);
+               return ldb_oom(ldb);
+       }
+
+       primary_group_blob = data_blob_string_const(primary_group_dn);
+
+       account_sid_string = dom_sid_string(tmp_ctx, account_sid);
+       if (!account_sid_string) {
+               talloc_free(tmp_ctx);
+               return ldb_oom(ldb);
+       }
+
+       account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
+       if (!account_sid_dn) {
+               talloc_free(tmp_ctx);
+               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,
+                                          tmp_ctx, &groupSIDs, &num_groupSIDs);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
+                                      account_sid_string, nt_errstr(status));
+               talloc_free(tmp_ctx);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       /* 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,
+                                          tmp_ctx, &groupSIDs, &num_groupSIDs);
+       if (!NT_STATUS_IS_OK(status)) {
+               ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
+                                      account_sid_string, nt_errstr(status));
+               talloc_free(tmp_ctx);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       for (i=0; i < num_groupSIDs; i++) {
+               ret = samdb_msg_add_dom_sid(ldb, msg, msg, "tokenGroups", groupSIDs[i]);
+               if (ret) {
+                       talloc_free(tmp_ctx);
                        return ret;
                }
+       }
+
+       return LDB_SUCCESS;
+}
+
+/*
+  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_request *parent)
+{
+       struct ldb_result *res, *parent_res;
+       const struct ldb_val *parent_guid;
+       const char *attrs[] = { "instanceType", NULL };
+       const char *attrs2[] = { "objectGUID", NULL };
+       uint32_t instanceType;
+       int ret;
+       struct ldb_dn *parent_dn;
+       struct ldb_val v;
+
+       /* 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_RECYCLED, parent);
+
+       instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
+                                                "instanceType", 0);
+       talloc_free(res);
+       if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
+               DEBUG(4,(__location__ ": Object %s is NC\n",
+                        ldb_dn_get_linearized(msg->dn)));
+               return LDB_SUCCESS;
+       }
+       parent_dn = ldb_dn_get_parent(msg, msg->dn);
 
+       if (parent_dn == NULL) {
+               DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
+                                        ldb_dn_get_linearized(msg->dn)));
+               return LDB_SUCCESS;
+       }
+       ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
+                                   DSDB_FLAG_NEXT_MODULE |
+                                   DSDB_SEARCH_SHOW_RECYCLED, parent);
+       talloc_free(parent_dn);
+
+       /* 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));
+       } else if (ret != LDB_SUCCESS) {
+               return ret;
        }
 
-       ret = dsdb_msg_add_guid(msg, &parent_guid, "parentGUID");
+       parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
+       if (!parent_guid) {
+               talloc_free(parent_res);
+               return LDB_SUCCESS;
+       }
 
+       v = data_blob_dup_talloc(parent_res, parent_guid);
+       if (!v.data) {
+               talloc_free(parent_res);
+               return ldb_oom(ldb_module_get_ctx(module));
+       }
+       ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
+       talloc_free(parent_res);
        return ret;
+}
+
+/*
+  construct a subSchemaSubEntry
+*/
+static int construct_subschema_subentry(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);
+       char *subSchemaSubEntry;
+
+       /* 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, and it's not
+        * really vital anyway */
+       if (!data->aggregate_dn) {
+               struct ldb_context *ldb = ldb_module_get_ctx(module);
+               data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+       }
+
+       if (data->aggregate_dn) {
+               subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
+               return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
+       }
+       return LDB_SUCCESS;
+}
+
+
+static int construct_msds_isrodc_with_dn(struct ldb_module *module,
+                                        struct ldb_message *msg,
+                                        struct ldb_message_element *object_category)
+{
+       struct ldb_context *ldb;
+       struct ldb_dn *dn;
+       const struct ldb_val *val;
+
+       ldb = ldb_module_get_ctx(module);
+       if (!ldb) {
+               DEBUG(4, (__location__ ": Failed to get ldb \n"));
+               return ldb_operr(ldb);
+       }
+
+       dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
+       if (!dn) {
+               DEBUG(4, (__location__ ": Failed to create dn from %s \n",
+                         (const char *)object_category->values[0].data));
+               return ldb_operr(ldb);
+       }
+
+       val = ldb_dn_get_rdn_val(dn);
+       if (!val) {
+               DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
+                         ldb_dn_get_linearized(dn)));
+               return ldb_operr(ldb);
+       }
+
+       if (strequal((const char *)val->data, "NTDS-DSA")) {
+               ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
+       } else {
+               ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
+       }
+       return LDB_SUCCESS;
+}
+
+static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
+                                               struct ldb_message *msg,
+                                               struct ldb_dn *dn,
+                                               struct ldb_request *parent)
+{
+       struct ldb_dn *server_dn;
+       const char *attr_obj_cat[] = { "objectCategory", NULL };
+       struct ldb_result *res;
+       struct ldb_message_element *object_category;
+       int ret;
+
+       server_dn = ldb_dn_copy(msg, dn);
+       if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
+               DEBUG(4, (__location__ ": Failed to add child to %s \n",
+                         ldb_dn_get_linearized(server_dn)));
+               return ldb_operr(ldb_module_get_ctx(module));
+       }
+
+       ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
+                                   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)));
+               return LDB_SUCCESS;
+       } else if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+
+       object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
+       if (!object_category) {
+               DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
+                        ldb_dn_get_linearized(res->msgs[0]->dn)));
+               return LDB_SUCCESS;
+       }
+       return construct_msds_isrodc_with_dn(module, msg, object_category);
+}
+
+static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
+                                                 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, parent);
+       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+               DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
+                        ldb_dn_get_linearized(msg->dn)));
+               return LDB_SUCCESS;
+       } else if (ret != LDB_SUCCESS) {
+               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, parent);
+}
+
+/*
+  construct msDS-isRODC attr
+*/
+static int construct_msds_isrodc(struct ldb_module *module,
+                                struct ldb_message *msg, enum ldb_scope scope,
+                                struct ldb_request *parent)
+{
+       struct ldb_message_element * object_class;
+       struct ldb_message_element * object_category;
+       unsigned int i;
+
+       object_class = ldb_msg_find_element(msg, "objectClass");
+       if (!object_class) {
+               DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
+                        ldb_dn_get_linearized(msg->dn)));
+               return ldb_operr(ldb_module_get_ctx(module));
+       }
+
+       for (i=0; i<object_class->num_values; i++) {
+               if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
+                       /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
+                        * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
+                        */
+                       object_category = ldb_msg_find_element(msg, "objectCategory");
+                       if (!object_category) {
+                               DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
+                                        ldb_dn_get_linearized(msg->dn)));
+                               return LDB_SUCCESS;
+                       }
+                       return construct_msds_isrodc_with_dn(module, msg, object_category);
+               }
+               if (strequal((const char*)object_class->values[i].data, "server")) {
+                       /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
+                        * 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, 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, parent);
+               }
+       }
+
+       return LDB_SUCCESS;
 }
 
 
+/*
+  construct msDS-keyVersionNumber attr
+
+  TODO:  Make this based on the 'win2k' DS huristics bit...
+
+*/
+static int construct_msds_keyversionnumber(struct ldb_module *module,
+                                          struct ldb_message *msg,
+                                          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) {
+               ldb_module_oom(module);
+               return LDB_SUCCESS;
+       }
+
+       ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
+                                      (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
+                        ldb_dn_get_linearized(msg->dn)));
+               return ldb_operr(ldb_module_get_ctx(module));
+       }
+
+       if (omd->version != 1) {
+               DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
+                        omd->version, ldb_dn_get_linearized(msg->dn)));
+               talloc_free(omd);
+               return LDB_SUCCESS;
+       }
+       for (i=0; i<omd->ctr.ctr1.count; i++) {
+               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;
+               }
+       }
+       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) {
+               return true;
+       }
+       return false;
+}
+
 /*
   a list of attribute names that should be substituted in the parse
   tree before the search is done
@@ -160,16 +613,48 @@ static const struct {
 static const struct {
        const char *attr;
        const char *replace;
-       int (*constructor)(struct ldb_module *, struct ldb_message *);
+       const char *extra_attr;
+       int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
 } search_sub[] = {
-       { "createTimestamp", "whenCreated", NULL },
-       { "modifyTimestamp", "whenChanged", NULL },
-       { "structuralObjectClass", "objectClass", NULL },
-       { "canonicalName", "distinguishedName", construct_canonical_name },
-       { "primaryGroupToken", "objectSid", construct_primary_group_token },
-       { "parentGUID", NULL, construct_parent_guid }
+       { "createTimestamp", "whenCreated", NULL , NULL },
+       { "modifyTimestamp", "whenChanged", NULL , NULL },
+       { "structuralObjectClass", NULL, NULL , NULL },
+       { "canonicalName", NULL, NULL , construct_canonical_name },
+       { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
+       { "tokenGroups", "primaryGroupID", "objectSid", construct_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 }
+};
+
+
+enum op_remove {
+       OPERATIONAL_REMOVE_ALWAYS, /* remove always */
+       OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
+       OPERATIONAL_SD_FLAGS,      /* show if SD_FLAGS_OID set, or asked for */
+       OPERATIONAL_REMOVE_UNLESS_CONTROL        /* remove always unless an adhoc control has been specified */
+};
+
+/*
+  a list of attributes that may need to be removed from the
+  underlying db return
+
+  Some of these are attributes that were once stored, but are now calculated
+*/
+static const struct {
+       const char *attr;
+       enum op_remove op;
+} operational_remove[] = {
+       { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
+       { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_UNLESS_CONTROL  },
+       { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
+       { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
+#define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
+       { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
 };
 
+
 /*
   post process a search result record. For any search_sub[] attributes that were
   asked for, we need to call the appropriate copy routine to copy the result
@@ -178,40 +663,88 @@ static const struct {
 */
 static int operational_search_post_process(struct ldb_module *module,
                                           struct ldb_message *msg,
-                                          const char * const *attrs)
+                                          enum ldb_scope scope,
+                                          const char * const *attrs_from_user,
+                                          const char * const *attrs_searched_for,
+                                          struct op_controls_flags* controls_flags,
+                                          struct ldb_request *parent)
 {
        struct ldb_context *ldb;
-       int i, a=0;
+       unsigned int i, a = 0;
+       bool constructed_attributes = false;
 
        ldb = ldb_module_get_ctx(module);
 
-       for (a=0;attrs && attrs[a];a++) {
+       /* 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 (a=0;attrs_from_user && attrs_from_user[a];a++) {
+               if (check_keep_control_for_attribute(controls_flags, attrs_from_user[a])) {
+                       continue;
+               }
                for (i=0;i<ARRAY_SIZE(search_sub);i++) {
-                       if (ldb_attr_cmp(attrs[a], search_sub[i].attr) != 0) {
+                       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 */
-                       if (search_sub[i].constructor) {
-                               if (search_sub[i].constructor(module, msg) != 0) {
+                       constructed_attributes = true;
+                       if (search_sub[i].constructor != NULL) {
+                               if (search_sub[i].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
                                        goto failed;
                                }
                        } else if (ldb_msg_copy_attr(msg,
-                                                    search_sub[i].replace,
-                                                    search_sub[i].attr) != 0) {
+                                                    search_sub[i].attr,
+                                                    search_sub[i].replace) != LDB_SUCCESS) {
                                goto failed;
                        }
+               }
+       }
 
-                       /* remove the added search attribute, unless it was
-                          asked for by the user */
-                       if (search_sub[i].replace == NULL ||
-                           ldb_attr_in_list(attrs, search_sub[i].replace) ||
-                           ldb_attr_in_list(attrs, "*")) {
-                               continue;
+       /* Deletion of the search helper attributes are needed if:
+        * - we generated constructed attributes and
+        * - 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++) {
+                       /* 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 (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);
                        }
-
-                       ldb_msg_remove_attr(msg, search_sub[i].replace);
                }
        }
 
@@ -219,12 +752,11 @@ 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[a]);
+                     "operational_search_post_process failed for attribute '%s' - %s",
+                     attrs_from_user[a], ldb_errstring(ldb));
        return -1;
 }
 
-
 /*
   hook search operations
 */
@@ -232,8 +764,9 @@ failed:
 struct operational_context {
        struct ldb_module *module;
        struct ldb_request *req;
-
+       enum ldb_scope scope;
        const char * const *attrs;
+       struct op_controls_flags* controls_flags;
 };
 
 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
@@ -257,8 +790,11 @@ static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
                /* for each record returned post-process to add any derived
                   attributes that have been asked for */
                ret = operational_search_post_process(ac->module,
-                                                       ares->message,
-                                                       ac->attrs);
+                                                     ares->message,
+                                                     ac->scope,
+                                                     ac->attrs,
+                                                     req->op.search.attrs,
+                                                     ac->controls_flags, req);
                if (ret != 0) {
                        return ldb_module_done(ac->req, NULL, NULL,
                                                LDB_ERR_OPERATIONS_ERROR);
@@ -266,8 +802,7 @@ static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
                return ldb_module_send_entry(ac->req, ares->message, ares->controls);
 
        case LDB_REPLY_REFERRAL:
-               /* ignore referrals */
-               break;
+               return ldb_module_send_referral(ac->req, ares->referral);
 
        case LDB_REPLY_DONE:
 
@@ -285,18 +820,24 @@ static int operational_search(struct ldb_module *module, struct ldb_request *req
        struct operational_context *ac;
        struct ldb_request *down_req;
        const char **search_attrs = NULL;
-       int i, a;
+       unsigned int i, a;
        int ret;
 
+       /* There are no operational attributes on special DNs */
+       if (ldb_dn_is_special(req->op.search.base)) {
+               return ldb_next_request(module, req);
+       }
+
        ldb = ldb_module_get_ctx(module);
 
        ac = talloc(req, struct operational_context);
        if (ac == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_oom(ldb);
        }
 
        ac->module = module;
        ac->req = req;
+       ac->scope = req->op.search.scope;
        ac->attrs = req->op.search.attrs;
 
        /*  FIXME: We must copy the tree and keep the original
@@ -310,19 +851,46 @@ static int operational_search(struct ldb_module *module, struct ldb_request *req
                                            parse_tree_sub[i].replace);
        }
 
+       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_BYPASS_OPERATIONAL_OID */
+       ac->controls_flags->bypassoperational =
+               (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
+
        /* 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 */
        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 */
+                                       search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
+                                                                              ? search_attrs
+                                                                              : ac->attrs, 
+                                                                              search_sub[i].extra_attr);
+                                       if (search_attrs2 == NULL) {
+                                               return ldb_operr(ldb);
+                                       }
+                                       /* may be NULL, talloc_free() doesn't mind */
+                                       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_ERR_OPERATIONS_ERROR;
+                                               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;
                        }
                }
@@ -337,8 +905,9 @@ 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_ERR_OPERATIONS_ERROR;
+               return ldb_operr(ldb);
        }
 
        /* perform the search */
@@ -347,17 +916,34 @@ static int operational_search(struct ldb_module *module, struct ldb_request *req
 
 static int operational_init(struct ldb_module *ctx)
 {
-       int ret = 0;
+       struct operational_data *data;
+       int ret;
+       auth_init();
 
-       if (ret != 0) {
+       ret = ldb_next_init(ctx);
+
+       if (ret != LDB_SUCCESS) {
                return ret;
        }
 
-       return ldb_next_init(ctx);
+       data = talloc_zero(ctx, struct operational_data);
+       if (!data) {
+               return ldb_module_oom(ctx);
+       }
+
+       ldb_module_set_private(ctx, data);
+
+       return LDB_SUCCESS;
 }
 
-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);
+}