From: Joseph Sutton Date: Mon, 24 May 2021 00:59:59 +0000 (+1200) Subject: dsdb: Add next_free_rid() function to allocate a RID without modifying the database X-Git-Tag: tevent-0.11.0~411 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cc98e03e7a0f2bf7a1ace2950fe6500f53640c1b;p=thirdparty%2Fsamba.git dsdb: Add next_free_rid() function to allocate a RID without modifying the database 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 Reviewed-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- diff --git a/python/samba/samdb.py b/python/samba/samdb.py index 292bee14da3..66e95edff78 100644 --- a/python/samba/samdb.py +++ b/python/samba/samdb.py @@ -1390,6 +1390,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