s4-dsdb Implement tokenGroups expansion directly in ldb operational module
authorAndrew Bartlett <abartlet@samba.org>
Tue, 21 Dec 2010 11:34:16 +0000 (22:34 +1100)
committerAndrew Tridgell <tridge@samba.org>
Fri, 14 Jan 2011 05:39:32 +0000 (16:39 +1100)
This removes a silly cross-dependency between the ldb moudle stack and auth/

Andrew Bartlett

source4/dsdb/common/util_groups.c [new file with mode: 0644]
source4/dsdb/samdb/ldb_modules/operational.c
source4/dsdb/wscript_build

diff --git a/source4/dsdb/common/util_groups.c b/source4/dsdb/common/util_groups.c
new file mode 100644 (file)
index 0000000..07d7611
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+   Unix SMB/CIFS implementation.
+   Password and authentication handling
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
+   Copyright (C) Stefan Metzmacher                         2005
+   Copyright (C) Matthias Dieter Wallnöfer                 2009
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth/auth.h"
+#include <ldb.h>
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "dsdb/common/util.h"
+
+/* This function tests if a SID structure "sids" contains the SID "sid" */
+static bool sids_contains_sid(const struct dom_sid **sids,
+                             const unsigned int num_sids,
+                             const struct dom_sid *sid)
+{
+       unsigned int i;
+
+       for (i = 0; i < num_sids; i++) {
+               if (dom_sid_equal(sids[i], sid))
+                       return true;
+       }
+       return false;
+}
+
+/*
+ * This function generates the transitive closure of a given SAM object "dn_val"
+ * (it basically expands nested memberships).
+ * If the object isn't located in the "res_sids" structure yet and the
+ * "only_childs" flag is false, we add it to "res_sids".
+ * Then we've always to consider the "memberOf" attributes. We invoke the
+ * function recursively on each of it with the "only_childs" flag set to
+ * "false".
+ * The "only_childs" flag is particularly useful if you have a user object and
+ * want to include all it's groups (referenced with "memberOf") but not itself
+ * or considering if that object matches the filter.
+ *
+ * At the beginning "res_sids" should reference to a NULL pointer.
+ */
+NTSTATUS dsdb_expand_nested_groups(struct ldb_context *sam_ctx,
+                                  struct ldb_val *dn_val, const bool only_childs, const char *filter,
+                                  TALLOC_CTX *res_sids_ctx, struct dom_sid ***res_sids,
+                                  unsigned int *num_res_sids)
+{
+       const char * const attrs[] = { "memberOf", NULL };
+       unsigned int i;
+       int ret;
+       bool already_there;
+       struct ldb_dn *dn;
+       struct dom_sid sid;
+       TALLOC_CTX *tmp_ctx;
+       struct ldb_result *res;
+       NTSTATUS status;
+       const struct ldb_message_element *el;
+
+       if (*res_sids == NULL) {
+               *num_res_sids = 0;
+       }
+
+       if (!sam_ctx) {
+               DEBUG(0, ("No SAM available, cannot determine local groups\n"));
+               return NT_STATUS_INVALID_SYSTEM_SERVICE;
+       }
+
+       tmp_ctx = talloc_new(res_sids_ctx);
+
+       dn = ldb_dn_from_ldb_val(tmp_ctx, sam_ctx, dn_val);
+       if (dn == NULL) {
+               talloc_free(tmp_ctx);
+               DEBUG(0, (__location__ ": we failed parsing DN %.*s, so we cannot calculate the group token\n",
+                         (int)dn_val->length, dn_val->data));
+               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       }
+
+       status = dsdb_get_extended_dn_sid(dn, &sid, "SID");
+       if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+               /* If we fail finding a SID then this is no error since it could
+                * be a non SAM object - e.g. a group with object class
+                * "groupOfNames" */
+               talloc_free(tmp_ctx);
+               return NT_STATUS_OK;
+       } else if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, (__location__ ": when parsing DN '%s' we failed to parse it's SID component, so we cannot calculate the group token: %s\n",
+                         ldb_dn_get_extended_linearized(tmp_ctx, dn, 1),
+                         nt_errstr(status)));
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       if (only_childs) {
+               ret = dsdb_search_dn(sam_ctx, tmp_ctx, &res, dn, attrs,
+                                    DSDB_SEARCH_SHOW_EXTENDED_DN);
+       } else {
+               /* This is an O(n^2) linear search */
+               already_there = sids_contains_sid((const struct dom_sid**) *res_sids,
+                                                 *num_res_sids, &sid);
+               if (already_there) {
+                       talloc_free(tmp_ctx);
+                       return NT_STATUS_OK;
+               }
+
+               ret = dsdb_search(sam_ctx, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
+                                 attrs, DSDB_SEARCH_SHOW_EXTENDED_DN, "%s",
+                                 filter);
+       }
+
+       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_OK;
+       }
+
+       if (ret != LDB_SUCCESS) {
+               DEBUG(1, (__location__ ": dsdb_search for %s failed: %s\n",
+                         ldb_dn_get_extended_linearized(tmp_ctx, dn, 1),
+                         ldb_errstring(sam_ctx)));
+               talloc_free(tmp_ctx);
+               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       }
+
+       /* We may get back 0 results, if the SID didn't match the filter - such as it wasn't a domain group, for example */
+       if (res->count != 1) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_OK;
+       }
+
+       /* We only apply this test once we know the SID matches the filter */
+       if (!only_childs) {
+               *res_sids = talloc_realloc(res_sids_ctx, *res_sids,
+                       struct dom_sid *, *num_res_sids + 1);
+               NT_STATUS_HAVE_NO_MEMORY_AND_FREE(*res_sids, tmp_ctx);
+               (*res_sids)[*num_res_sids] = dom_sid_dup(*res_sids, &sid);
+               NT_STATUS_HAVE_NO_MEMORY_AND_FREE((*res_sids)[*num_res_sids], tmp_ctx);
+               ++(*num_res_sids);
+       }
+
+       el = ldb_msg_find_element(res->msgs[0], "memberOf");
+
+       for (i = 0; el && i < el->num_values; i++) {
+               status = dsdb_expand_nested_groups(sam_ctx, &el->values[i],
+                                                  false, filter, res_sids_ctx, res_sids, num_res_sids);
+               if (!NT_STATUS_IS_OK(status)) {
+                       talloc_free(tmp_ctx);
+                       return status;
+               }
+       }
+
+       talloc_free(tmp_ctx);
+
+       return NT_STATUS_OK;
+}
index c4c2660f57e08a6a46d1b4f65e6567a4216a8f9e..8604a27b9f0decdfd702e47b060f5ee826aff409 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
@@ -129,60 +130,131 @@ static int construct_token_groups(struct ldb_module *module,
                                  struct ldb_message *msg, enum ldb_scope scope)
 {
        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;
+       unsigned int i;
        int ret;
+       const char *filter;
 
        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 **groupSIDs = NULL;
+       unsigned int num_groupSIDs = 0;
+
+       struct dom_sid *domain_sid;
+
        if (scope != LDB_SCOPE_BASE) {
                ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       status = auth_context_create_from_ldb(tmp_ctx, ldb, &auth_context);
-       if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
+       /* 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_module_oom(module);
+               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)) {
+       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_module_oom(module);
-       } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
-               /* Not a user, we have no tokenGroups */
+               return ldb_oom(ldb);
+       }
+
+       /* Filter out builtin groups from this token.  We will search
+        * for builtin groups later, and not include them in the
+        * tokenGroups (and therefore the PAC or SamLogon validation
+        * info) */
+       filter = talloc_asprintf(tmp_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);
+       if (!filter) {
                talloc_free(tmp_ctx);
-               return LDB_SUCCESS;
-       } else if (!NT_STATUS_IS_OK(status)) {
+               return ldb_oom(ldb);
+       }
+
+       primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
+       if (!primary_group_string) {
                talloc_free(tmp_ctx);
-               ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_get_server_info_principal failed: %s", nt_errstr(status));
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_oom(ldb);
        }
 
-       status = auth_generate_session_info(tmp_ctx, auth_context->lp_ctx, ldb, server_info, 0, &session_info);
-       if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
+       primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
+       if (!primary_group_dn) {
                talloc_free(tmp_ctx);
-               return ldb_module_oom(module);
-       } else if (!NT_STATUS_IS_OK(status)) {
+               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);
-               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) {
+       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;
                }
@@ -542,7 +614,7 @@ static const struct {
        { "structuralObjectClass", NULL, NULL , NULL },
        { "canonicalName", NULL, NULL , construct_canonical_name },
        { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
-       { "tokenGroups", "objectClass", NULL, construct_token_groups },
+       { "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 },
index 59ef1b906278df88470c8e89004222f9c9769ce1..f595f3c26feefb0904b30b3d28f64a4699303aaf 100644 (file)
@@ -13,7 +13,7 @@ bld.SAMBA_LIBRARY('samdb',
 
 
 bld.SAMBA_LIBRARY('samdb-common',
-       source='common/util.c common/util_samr.c common/dsdb_dn.c common/dsdb_access.c ../../libds/common/flag_mapping.c',
+       source='common/util.c common/util_groups.c common/util_samr.c common/dsdb_dn.c common/dsdb_access.c ../../libds/common/flag_mapping.c',
        autoproto='common/proto.h',
        private_library=True,
        deps='ldb NDR_DRSBLOBS UTIL_LDB LIBCLI_AUTH samba-hostconfig samba_socket LIBCLI_LDAP_NDR'