source4 samr: cache samr_EnumDomainUsers results
authorGary Lockyer <gary@catalyst.net.nz>
Thu, 18 Oct 2018 00:54:31 +0000 (13:54 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 20 Nov 2018 21:14:17 +0000 (22:14 +0100)
Add a cache of GUID's that matched the last samr_EnunDomainUsers made on a
domain handle.  The cache is cleared if resume_handle is zero, and when the
final results are returned to the caller.

The existing code repeated the database query for each chunk requested.

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
selftest/knownfail.d/samr [deleted file]
source4/rpc_server/samr/dcesrv_samr.c
source4/rpc_server/samr/dcesrv_samr.h

diff --git a/selftest/knownfail.d/samr b/selftest/knownfail.d/samr
deleted file mode 100644 (file)
index 4e95d3c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-^samba.tests.dcerpc.sam.samba.tests.dcerpc.sam.SamrTests.test_EnumDomainUsers
-^samba.tests.dcerpc.sam.python3.samba.tests.dcerpc.sam.SamrTests.test_EnumDomainUsers
index b485b5a2b628ef896674abcf42a4457d3cdec596..3a5b0146ba709882d6efb7187faa935ec991bd7b 100644 (file)
@@ -1516,12 +1516,18 @@ static NTSTATUS dcesrv_samr_EnumDomainUsers(struct dcesrv_call_state *dce_call,
        struct dcesrv_handle *h;
        struct samr_domain_state *d_state;
        struct ldb_message **res;
-       int i, ldb_cnt;
-       uint32_t first, count;
+       uint32_t i;
+       uint32_t count;
+       uint32_t results;
+       uint32_t max_entries;
+       uint32_t remaining_entries;
+       uint32_t resume_handle;
        struct samr_SamEntry *entries;
        const char * const attrs[] = { "objectSid", "sAMAccountName",
                "userAccountControl", NULL };
+       const char *const cache_attrs[] = {"objectSid", "objectGUID", NULL};
        struct samr_SamArray *sam;
+       struct samr_guid_cache *cache = NULL;
 
        *r->out.resume_handle = 0;
        *r->out.sam = NULL;
@@ -1530,73 +1536,174 @@ static NTSTATUS dcesrv_samr_EnumDomainUsers(struct dcesrv_call_state *dce_call,
        DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN);
 
        d_state = h->data;
+       cache = &d_state->guid_caches[SAMR_ENUM_DOMAIN_USERS_CACHE];
 
-       /* search for all domain users in this domain. This could possibly be
-          cached and resumed on resume_key */
-       ldb_cnt = samdb_search_domain(d_state->sam_ctx, mem_ctx,
-                                     d_state->domain_dn,
-                                     &res, attrs,
-                                     d_state->domain_sid,
-                                     "(objectClass=user)");
-       if (ldb_cnt < 0) {
-               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       /*
+        * If the resume_handle is zero, query the database and cache the
+        * matching GUID's
+        */
+       if (*r->in.resume_handle == 0) {
+               NTSTATUS status;
+               int ldb_cnt;
+               clear_guid_cache(cache);
+               /*
+                * search for all domain users in this domain.
+                */
+               ldb_cnt = samdb_search_domain(d_state->sam_ctx,
+                                             mem_ctx,
+                                             d_state->domain_dn,
+                                             &res,
+                                             cache_attrs,
+                                             d_state->domain_sid,
+                                             "(objectClass=user)");
+               if (ldb_cnt < 0) {
+                       return NT_STATUS_INTERNAL_DB_CORRUPTION;
+               }
+               /*
+                * Sort the results into RID order, while the spec states there
+                * is no order, Windows appears to sort the results by RID and
+                * so it is possible that there are clients that depend on
+                * this ordering
+                */
+               TYPESAFE_QSORT(res, ldb_cnt, compare_msgRid);
+
+               /*
+                * cache the sorted GUID's
+                */
+               status = load_guid_cache(cache, d_state, ldb_cnt, res);
+               TALLOC_FREE(res);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+               cache->handle = 0;
        }
 
-       /* convert to SamEntry format */
-       entries = talloc_array(mem_ctx, struct samr_SamEntry, ldb_cnt);
-       if (!entries) {
-               return NT_STATUS_NO_MEMORY;
+       /*
+        * If the resume handle is out of range we return an empty response
+        * and invalidate the cache.
+        *
+        * From the specification:
+        * Servers SHOULD validate that EnumerationContext is an expected
+        * value for the server's implementation. Windows does NOT validate
+        * the input, though the result of malformed information merely results
+        * in inconsistent output to the client.
+        */
+       if (*r->in.resume_handle >= cache->size) {
+               clear_guid_cache(cache);
+               sam = talloc(mem_ctx, struct samr_SamArray);
+               if (!sam) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+               sam->entries = NULL;
+               sam->count = 0;
+
+               *r->out.sam = sam;
+               *r->out.resume_handle = 0;
+               return NT_STATUS_OK;
        }
 
+       /*
+        * Calculate the number of entries to return limit by max_size.
+        * Note that we use the w2k3 element size value of 54
+        */
+       max_entries = 1 + (r->in.max_size / SAMR_ENUM_USERS_MULTIPLIER);
+       remaining_entries = cache->size - *r->in.resume_handle;
+       results = MIN(remaining_entries, max_entries);
+
+       /*
+        * Process the list of result GUID's.
+        * Read the details of each object and populate the Entries
+        * for the current level.
+        */
        count = 0;
+       resume_handle = *r->in.resume_handle;
+       entries = talloc_array(mem_ctx, struct samr_SamEntry, results);
+       if (entries == NULL) {
+               clear_guid_cache(cache);
+               return NT_STATUS_NO_MEMORY;
+       }
+       for (i = 0; i < results; i++) {
+               struct dom_sid *sid;
+               struct ldb_result *rec;
+               const uint32_t idx = *r->in.resume_handle + i;
+               int ret;
+               const char *name = NULL;
 
-       for (i=0;i<ldb_cnt;i++) {
-               /* Check if a mask has been requested */
-               if (r->in.acct_flags
-                   && ((samdb_result_acct_flags(res[i], NULL) & r->in.acct_flags) == 0)) {
+               resume_handle++;
+               /*
+                * Read an object from disk using the GUID as the key
+                *
+                * If the object can not be read, or it does not have a SID
+                * it is ignored.
+                *
+                * As a consequence of this, if all the remaining GUID's
+                * have been deleted an empty result will be returned.
+                * i.e. even if the previous call returned a non zero
+                * resume_handle it is possible for no results to be returned.
+                *
+                */
+               ret = dsdb_search_by_dn_guid(d_state->sam_ctx,
+                                            mem_ctx,
+                                            &rec,
+                                            &cache->entries[idx],
+                                            attrs,
+                                            0);
+               if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+                       char *guid_str =
+                           GUID_string(mem_ctx, &cache->entries[idx]);
+                       DBG_WARNING("GUID [%s] not found\n", guid_str);
                        continue;
+               } else if (ret != LDB_SUCCESS) {
+                       clear_guid_cache(cache);
+                       return NT_STATUS_INTERNAL_DB_CORRUPTION;
                }
-               entries[count].idx = samdb_result_rid_from_sid(mem_ctx, res[i],
-                                                              "objectSid", 0);
-               entries[count].name.string = ldb_msg_find_attr_as_string(res[i],
-                                                                "sAMAccountName", "");
-               count += 1;
+               sid = samdb_result_dom_sid(mem_ctx, rec->msgs[0], "objectSID");
+               if (sid == NULL) {
+                       char *guid_str =
+                           GUID_string(mem_ctx, &cache->entries[idx]);
+                       DBG_WARNING("objectSID for GUID [%s] not found\n",
+                                   guid_str);
+                       continue;
+               }
+               if (r->in.acct_flags &&
+                   ((samdb_result_acct_flags(rec->msgs[0], NULL) &
+                     r->in.acct_flags) == 0)) {
+                       continue;
+               }
+               entries[count].idx = samdb_result_rid_from_sid(
+                   mem_ctx, rec->msgs[0], "objectSid", 0);
+               name = ldb_msg_find_attr_as_string(
+                   rec->msgs[0], "sAMAccountName", "");
+               entries[count].name.string = talloc_strdup(entries, name);
+               count++;
        }
 
-       /* sort the results by rid */
-       TYPESAFE_QSORT(entries, count, compare_SamEntry);
-
-       /* find the first entry to return */
-       for (first=0;
-            first<count && entries[first].idx <= *r->in.resume_handle;
-            first++) ;
-
-       /* return the rest, limit by max_size. Note that we
-          use the w2k3 element size value of 54 */
-       *r->out.num_entries = count - first;
-       *r->out.num_entries = MIN(*r->out.num_entries,
-                                1+(r->in.max_size/SAMR_ENUM_USERS_MULTIPLIER));
-
        sam = talloc(mem_ctx, struct samr_SamArray);
        if (!sam) {
+               clear_guid_cache(cache);
                return NT_STATUS_NO_MEMORY;
        }
 
-       sam->entries = entries+first;
-       sam->count = *r->out.num_entries;
+       sam->entries = entries;
+       sam->count = count;
 
        *r->out.sam = sam;
+       *r->out.resume_handle = resume_handle;
+       *r->out.num_entries = count;
 
-       if (first == count) {
+       /*
+        * Signal no more results by returning zero resume handle,
+        * the cache is also cleared at this point
+        */
+       if (*r->out.resume_handle >= cache->size) {
+               *r->out.resume_handle = 0;
+               clear_guid_cache(cache);
                return NT_STATUS_OK;
        }
-
-       if (*r->out.num_entries < count - first) {
-               *r->out.resume_handle = entries[first+*r->out.num_entries-1].idx;
-               return STATUS_MORE_ENTRIES;
-       }
-
-       return NT_STATUS_OK;
+       /*
+        * There are more results to be returned.
+        */
+       return STATUS_MORE_ENTRIES;
 }
 
 
index 04866aca757f7f1c49e046dd64ddbbe293bc18a6..d530fc67bd9adc9c0a086224253685d3229eba6c 100644 (file)
@@ -54,6 +54,7 @@ struct samr_guid_cache {
 enum samr_guid_cache_id {
        SAMR_QUERY_DISPLAY_INFO_CACHE,
        SAMR_ENUM_DOMAIN_GROUPS_CACHE,
+       SAMR_ENUM_DOMAIN_USERS_CACHE,
        SAMR_LAST_CACHE
 };