dsdb: Add next_free_rid() function to allocate a RID without modifying the database
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Mon, 24 May 2021 00:59:59 +0000 (12:59 +1200)
committerKarolin Seeger <kseeger@samba.org>
Tue, 13 Jul 2021 12:31:15 +0000 (12:31 +0000)
If used to generate SIDs for objects, care should be taken, as the
possibility for having duplicate objectSIDs can arise.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14669

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit cc98e03e7a0f2bf7a1ace2950fe6500f53640c1b)

python/samba/samdb.py

index d903babb4062b2d2635aa7418e183b845594fd7c..d13c5e3b7a2e10c50e075f59951d624cf8b1b010 100644 (file)
@@ -1285,6 +1285,111 @@ schemaUpdateNow: 1
         '''return a new RID from the RID Pool on this DSA'''
         return dsdb._dsdb_allocate_rid(self)
 
+    def next_free_rid(self):
+        '''return the next free RID from the RID Pool on this DSA.
+
+        :note: This function is not intended for general use, and care must be
+            taken if it is used to generate objectSIDs. The returned RID is not
+            formally reserved for use, creating the possibility of duplicate
+            objectSIDs.
+        '''
+        rid, _ = self.free_rid_bounds()
+        return rid
+
+    def free_rid_bounds(self):
+        '''return the low and high bounds (inclusive) of RIDs that are
+            available for use in this DSA's current RID pool.
+
+        :note: This function is not intended for general use, and care must be
+            taken if it is used to generate objectSIDs. The returned range of
+            RIDs is not formally reserved for use, creating the possibility of
+            duplicate objectSIDs.
+        '''
+        # Get DN of this server's RID Set
+        server_name_dn = ldb.Dn(self, self.get_serverName())
+        res = self.search(base=server_name_dn,
+                          scope=ldb.SCOPE_BASE,
+                          attrs=["serverReference"])
+        try:
+            server_ref = res[0]["serverReference"]
+        except KeyError:
+            raise ldb.LdbError(
+                ldb.ERR_NO_SUCH_ATTRIBUTE,
+                "No RID Set DN - "
+                "Cannot find attribute serverReference of %s "
+                "to calculate reference dn" % server_name_dn) from None
+        server_ref_dn = ldb.Dn(self, server_ref[0].decode("utf-8"))
+
+        res = self.search(base=server_ref_dn,
+                          scope=ldb.SCOPE_BASE,
+                          attrs=["rIDSetReferences"])
+        try:
+            rid_set_refs = res[0]["rIDSetReferences"]
+        except KeyError:
+            raise ldb.LdbError(
+                ldb.ERR_NO_SUCH_ATTRIBUTE,
+                "No RID Set DN - "
+                "Cannot find attribute rIDSetReferences of %s "
+                "to calculate reference dn" % server_ref_dn) from None
+        rid_set_dn = ldb.Dn(self, rid_set_refs[0].decode("utf-8"))
+
+        # Get the alloc pools and next RID of this RID Set
+        res = self.search(base=rid_set_dn,
+                          scope=ldb.SCOPE_BASE,
+                          attrs=["rIDAllocationPool",
+                                 "rIDPreviousAllocationPool",
+                                 "rIDNextRID"])
+
+        uint32_max = 2**32 - 1
+        uint64_max = 2**64 - 1
+
+        try:
+            alloc_pool = int(res[0]["rIDAllocationPool"][0])
+        except KeyError:
+            alloc_pool = uint64_max
+        if alloc_pool == uint64_max:
+            raise ldb.LdbError(ldb.ERR_OPERATIONS_ERROR,
+                               "Bad RID Set %s" % rid_set_dn)
+
+        try:
+            prev_pool = int(res[0]["rIDPreviousAllocationPool"][0])
+        except KeyError:
+            prev_pool = uint64_max
+        try:
+            next_rid = int(res[0]["rIDNextRID"][0])
+        except KeyError:
+            next_rid = uint32_max
+
+        # If we never used a pool, set up our first pool
+        if prev_pool == uint64_max or next_rid == uint32_max:
+            prev_pool = alloc_pool
+            next_rid = prev_pool & uint32_max
+
+        next_rid += 1
+
+        # Now check if our current pool is still usable
+        prev_pool_lo = prev_pool & uint32_max
+        prev_pool_hi = prev_pool >> 32
+        if next_rid > prev_pool_hi:
+            # We need a new pool, check if we already have a new one
+            # Otherwise we return an error code.
+            if alloc_pool == prev_pool:
+                raise ldb.LdbError(ldb.ERR_OPERATIONS_ERROR,
+                                   "RID pools out of RIDs")
+
+            # Now use the new pool
+            prev_pool = alloc_pool
+            prev_pool_lo = prev_pool & uint32_max
+            prev_pool_hi = prev_pool >> 32
+            next_rid = prev_pool_lo
+
+        if next_rid < prev_pool_lo or next_rid > prev_pool_hi:
+            raise ldb.LdbError(ldb.ERR_OPERATIONS_ERROR,
+                               "Bad RID chosen %d from range %d-%d" %
+                               (next_rid, prev_pool_lo, prev_pool_hi))
+
+        return next_rid, prev_pool_hi
+
     def normalize_dn_in_domain(self, dn):
         '''return a new DN expanded by adding the domain DN