예제 #1
0
    def test_ldif_to_samdb_forced_local_dsa(self):
        for dsa, site in MULTISITE_LDIF_DSAS:
            dburl = os.path.join(self.tempdir, "ldif-to-samba-forced-local-dsa"
                                 "-%s" % dsa)
            samdb = ldif_import_export.ldif_to_samdb(dburl,
                                                     self.lp,
                                                     MULTISITE_LDIF,
                                                     forced_local_dsa=dsa)
            self.assertIsInstance(samdb, SamDB)
            self.assertEqual(samdb.server_site_name(), site)

            res = samdb.search(ldb.Dn(samdb, "CN=NTDS Settings," + dsa),
                               scope=ldb.SCOPE_BASE,
                               attrs=["objectGUID"])

            ntds_guid = misc.GUID(samdb.get_ntds_GUID())
            self.assertEqual(misc.GUID(res[0]["objectGUID"][0]), ntds_guid)

            service_name_res = samdb.search(base="",
                                            scope=ldb.SCOPE_BASE,
                                            attrs=["dsServiceName"])
            dn = ldb.Dn(samdb, service_name_res[0]["dsServiceName"][0])
            self.assertEqual(dn, ldb.Dn(samdb, "CN=NTDS Settings," + dsa))
            self.remove_files(dburl)
예제 #2
0
파일: share.py 프로젝트: Schu23/GSoC-SWAT
    def copy(self, name):
        """ Copy a share with a certain name on the selected backend
        
        FIXME There is no way outside this method to test if the copied share
        exsits.
        
        """
        copied = False

        #
        # FIXME the LDB class does not like this parameter because its type
        # is unicode instead of string so it need to be cast into a string.
        #
        name = str(name).strip()

        try:
            if len(name) == 0:
                raise ShareError(_("You did not specify a Share to copy"), \
                                 "critical")

            if self.share_name_exists(name):
                share = self.__get_share_from_backend(name)
            else:
                raise ShareError(_("The share you selected doesn't exist."))

            copy_name = self._get_available_copy_name(name)

            dn = "CN=" + name + ",CN=Shares"
            copy_dn = "CN=" + copy_name + ",CN=Shares"

            copy_share = ldb.Message(ldb.Dn(self.__shares_db, copy_dn))

            for param, value in share.items():
                if param == "dn":
                    continue

                if param == "name":
                    value = copy_name

                copy_share[param] = ldb.MessageElement(value, \
                                                       ldb.CHANGETYPE_ADD, \
                                                       param)

            self.__shares_db.add(copy_share)
            copied = True

        except ShareError, error:
            self._set_error(error.message, error.type)
예제 #3
0
    def filtered_closure(self, wSet, filter_grouptype):
        res = self.admin_ldb.search(base=self.base_dn, scope=ldb.SCOPE_SUBTREE,
                                    expression="(|(objectclass=user)(objectclass=group))",
                                    attrs=["memberOf"])
        aSet = set()
        aSetR = set()
        vSet = set()
        for obj in res:
            vSet.add(obj.dn.get_casefold())
            if "memberOf" in obj:
                for dn in obj["memberOf"]:
                    first = obj.dn.get_casefold()
                    second = ldb.Dn(self.admin_ldb, dn.decode('utf8')).get_casefold()
                    aSet.add((first, second))
                    aSetR.add((second, first))
                    vSet.add(first)
                    vSet.add(second)

        res = self.admin_ldb.search(base=self.base_dn, scope=ldb.SCOPE_SUBTREE,
                                    expression="(objectclass=user)",
                                    attrs=["primaryGroupID"])
        for obj in res:
            if "primaryGroupID" in obj:
                sid = "%s-%d" % (self.admin_ldb.get_domain_sid(), int(obj["primaryGroupID"][0]))
                res2 = self.admin_ldb.search(base="<SID=%s>" % sid, scope=ldb.SCOPE_BASE,
                                             attrs=[])
                first = obj.dn.get_casefold()
                second = res2[0].dn.get_casefold()

                aSet.add((first, second))
                aSetR.add((second, first))
                vSet.add(first)
                vSet.add(second)

        uSet = set()
        for v in vSet:
            res_group = self.admin_ldb.search(base=v, scope=ldb.SCOPE_BASE,
                                              attrs=["groupType"],
                                              expression="objectClass=group")
            if len(res_group) == 1:
                if hex(int(res_group[0]["groupType"][0]) & 0x00000000FFFFFFFF) == hex(filter_grouptype):
                    uSet.add(v)
            else:
                uSet.add(v)

        closure(uSet, wSet, aSet)
예제 #4
0
파일: asq.py 프로젝트: szaydel/samba
    def test_asq(self):
        """Testing ASQ behaviour.

        ASQ is very strange, it turns a BASE search into a search for
        all the objects pointed to by the specified attribute,
        returning multiple entries!

        """

        msgs = self.ldb.search(base=self.top_dn,
                               scope=ldb.SCOPE_BASE,
                               attrs=["objectGUID", "cn", "member"],
                               controls=["asq:1:member"])

        self.assertEqual(len(msgs), 20)

        for msg in msgs:
            self.assertNotEqual(msg.dn, self.top_dn)
            self.assertIn(msg.dn, self.members2)
            for group in msg["member"]:
                self.assertIn(ldb.Dn(self.ldb, str(group)), self.members)
예제 #5
0
파일: large_ldap.py 프로젝트: szaydel/samba
    def setUp(self):
        super(ManyLDAPTest, self).setUp()
        self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
        self.base_dn = self.ldb.domain_dn()
        self.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05")
        self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn))

        samba.tests.delete_force(self.ldb, self.ou_dn,
                                 controls=['tree_delete:1'])

        self.ldb.add({
            "dn": self.ou_dn,
            "objectclass": "organizationalUnit",
            "ou": self.OU_NAME_MANY})

        for x in range(2000):
            ou_name = self.OU_NAME_MANY + str(x)
            self.ldb.add({
                "dn": "ou=" + ou_name + "," + str(self.ou_dn),
                "objectclass": "organizationalUnit",
                "ou": ou_name})
예제 #6
0
파일: share.py 프로젝트: Schu23/GSoC-SWAT
    def delete(self, name):
        deleted = False

        #
        # FIXME the LDB class does not like this parameter because its type
        # is unicode instead of string so it need to be cast into a string.
        #
        name = str(name).strip()

        try:
            if len(name) == 0:
                raise ShareError(_("You did not specify a Share to remove"))

            if not self.share_name_exists(name):
                raise ShareError(_("The share you selected doesn't exist."))

            dn = "CN=" + name + ",CN=Shares"
            self.__shares_db.delete(ldb.Dn(self.__shares_db, dn))
            deleted = True

        except ShareError, error:
            self._set_error(error.message, error.type)
예제 #7
0
파일: large_ldap.py 프로젝트: szaydel/samba
    def setUp(self):
        super(LargeLDAPTest, self).setUp()
        self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
        self.base_dn = self.ldb.domain_dn()
        self.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-"
        self.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05")
        self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME + "," + str(self.base_dn))

        samba.tests.delete_force(self.ldb, self.ou_dn,
                                 controls=['tree_delete:1'])

        self.ldb.add({
            "dn": self.ou_dn,
            "objectclass": "organizationalUnit",
            "ou": self.OU_NAME})

        for x in range(200):
            user_name = self.USER_NAME + format(x, "03")
            self.ldb.add({
                "dn": "cn=" + user_name + "," + str(self.ou_dn),
                "objectclass": "user",
                "sAMAccountName": user_name,
                "jpegPhoto": b'a' * (2 * 1024 * 1024)})
 def test_samdb_to_ldif_file(self):
     dburl = os.path.join(self.tempdir, "ldap")
     dburl2 = os.path.join(self.tempdir, "ldap_roundtrip")
     ldif_file = os.path.join(self.tempdir, "ldif")
     samdb = ldif_import_export.ldif_to_samdb(dburl, self.lp,
                                              MULTISITE_LDIF)
     self.assertIsInstance(samdb, SamDB)
     ldif_import_export.samdb_to_ldif_file(samdb,
                                           dburl,
                                           lp=self.lp,
                                           creds=None,
                                           ldif_file=ldif_file)
     self.assertGreater(os.path.getsize(ldif_file), 1000,
                        "LDIF should be larger than 1000 bytes")
     samdb = ldif_import_export.ldif_to_samdb(dburl2, self.lp, ldif_file)
     self.assertIsInstance(samdb, SamDB)
     dsa = ("CN=WIN01,CN=Servers,CN=Default-First-Site-Name,CN=Sites,"
            "CN=Configuration,DC=ad,DC=samba,DC=example,DC=com")
     res = samdb.search(ldb.Dn(samdb, "CN=NTDS Settings," + dsa),
                        scope=ldb.SCOPE_BASE,
                        attrs=["objectGUID"])
     self.remove_files(dburl)
     self.remove_files(dburl2)
     self.remove_files(ldif_file)
예제 #9
0
            write_search_result(samdb, f, res)

        # Query rootDSE replicas
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "rootDomainNamingContext",
                 "configurationNamingContext",
                 "schemaNamingContext",
                 "defaultNamingContext",
                 "dsServiceName"]

        sstr = ""
        res = samdb.search(sstr, scope=ldb.SCOPE_BASE,
                           attrs=attrs)

        # Record the rootDSE object as a dn as it
        # would appear in the base ldb file.  We have
        # to save it this way because we are going to
        # be importing as an abbreviated database.
        res[0].dn = ldb.Dn(samdb, "@ROOTDSE")

        # Write rootdse output
        write_search_result(samdb, f, res)

    except ldb.LdbError, (enum, estr):
        raise LdifError("Error processing (%s) : %s" % (sstr, estr))

    f.close()
예제 #10
0
def samdb_to_ldif_file(samdb, dburl, lp, creds, ldif_file):
    """Routine to extract all objects and attributes that are relevent
    to the KCC algorithms from a DC database.

    The point of this function is to allow a programmer/debugger to
    extract an LDIF file with non-security relevent information from
    a DC database.  The LDIF file can then be used to "import" via
    the import_ldif() function this file into a temporary abbreviated
    database.  The KCC algorithm can then run against this abbreviated
    database for debug or test verification that the topology generated
    is computationally the same between different OSes and algorithms.

    :param dburl: LDAP database URL to extract info from
    :param ldif_file: output LDIF file name to create
    """
    try:
        samdb = SamDB(url=dburl,
                      session_info=system_session(),
                      credentials=creds,
                      lp=lp)
    except ldb.LdbError as e:
        (enum, estr) = e.args
        raise LdifError("Unable to open sam database (%s) : %s" %
                        (dburl, estr))

    if os.path.exists(ldif_file):
        raise LdifError("Specify a file (%s) that doesn't already exist." %
                        ldif_file)

    try:
        f = open(ldif_file, "w")
    except IOError as ioerr:
        raise LdifError("Unable to open (%s) : %s" % (ldif_file, str(ioerr)))

    try:
        # Query Partitions
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "objectSid",
            "Enabled", "systemFlags", "dnsRoot", "nCName",
            "msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"
        ]

        sstr = "CN=Partitions,%s" % samdb.get_config_basedn()
        res = samdb.search(base=sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=crossRef)")

        # Write partitions output
        write_search_result(samdb, f, res)

        # Query cross reference container
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "fSMORoleOwner",
            "systemFlags", "msDS-Behavior-Version", "msDS-EnabledFeature"
        ]

        sstr = "CN=Partitions,%s" % samdb.get_config_basedn()
        res = samdb.search(base=sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=crossRefContainer)")

        # Write cross reference container output
        write_search_result(samdb, f, res)

        # Query Sites
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags"
        ]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        sites = samdb.search(base=sstr,
                             scope=ldb.SCOPE_SUBTREE,
                             attrs=attrs,
                             expression="(objectClass=site)")

        # Write sites output
        write_search_result(samdb, f, sites)

        # Query NTDS Site Settings
        for msg in sites:
            sitestr = str(msg.dn)

            attrs = [
                "objectClass", "objectGUID", "cn", "whenChanged",
                "interSiteTopologyGenerator", "interSiteTopologyFailover",
                "schedule", "options"
            ]

            sstr = "CN=NTDS Site Settings,%s" % sitestr
            res = samdb.search(base=sstr, scope=ldb.SCOPE_BASE, attrs=attrs)

            # Write Site Settings output
            write_search_result(samdb, f, res)

        # Naming context list
        nclist = []

        # Query Directory Service Agents
        for msg in sites:
            sstr = str(msg.dn)

            ncattrs = [
                "hasMasterNCs", "msDS-hasMasterNCs", "hasPartialReplicaNCs",
                "msDS-HasDomainNCs", "msDS-hasFullReplicaNCs",
                "msDS-HasInstantiatedNCs"
            ]
            attrs = [
                "objectClass", "objectGUID", "cn", "whenChanged",
                "invocationID", "options", "msDS-isRODC",
                "msDS-Behavior-Version"
            ]

            res = samdb.search(base=sstr,
                               scope=ldb.SCOPE_SUBTREE,
                               attrs=attrs + ncattrs,
                               expression="(objectClass=nTDSDSA)")

            # Spin thru all the DSAs looking for NC replicas
            # and build a list of all possible Naming Contexts
            # for subsequent retrieval below
            for msg in res:
                for k in msg.keys():
                    if k in ncattrs:
                        for value in msg[k]:
                            # Some of these have binary DNs so
                            # use dsdb_Dn to split out relevent parts
                            dsdn = dsdb_Dn(samdb, value)
                            dnstr = str(dsdn.dn)
                            if dnstr not in nclist:
                                nclist.append(dnstr)

            # Write DSA output
            write_search_result(samdb, f, res)

        # Query NTDS Connections
        for msg in sites:
            sstr = str(msg.dn)

            attrs = [
                "objectClass", "objectGUID", "cn", "whenChanged", "options",
                "whenCreated", "enabledConnection", "schedule",
                "transportType", "fromServer", "systemFlags"
            ]

            res = samdb.search(base=sstr,
                               scope=ldb.SCOPE_SUBTREE,
                               attrs=attrs,
                               expression="(objectClass=nTDSConnection)")
            # Write NTDS Connection output
            write_search_result(samdb, f, res)

        # Query Intersite transports
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "options",
            "name", "bridgeheadServerListBL", "transportAddressAttribute"
        ]

        sstr = "CN=Inter-Site Transports,CN=Sites,%s" % \
               samdb.get_config_basedn()
        res = samdb.search(sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=interSiteTransport)")

        # Write inter-site transport output
        write_search_result(samdb, f, res)

        # Query siteLink
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags",
            "options", "schedule", "replInterval", "siteList", "cost"
        ]

        sstr = "CN=Sites,%s" % \
               samdb.get_config_basedn()
        res = samdb.search(sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=siteLink)",
                           controls=['extended_dn:0'])

        # Write siteLink output
        write_search_result(samdb, f, res)

        # Query siteLinkBridge
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "siteLinkList"
        ]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        res = samdb.search(sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=siteLinkBridge)")

        # Write siteLinkBridge output
        write_search_result(samdb, f, res)

        # Query servers containers
        # Needed for samdb.server_site_name()
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags"
        ]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        res = samdb.search(sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=serversContainer)")

        # Write servers container output
        write_search_result(samdb, f, res)

        # Query servers
        # Needed because some transport interfaces refer back to
        # attributes found in the server object.   Also needed
        # so extended-dn will be happy with dsServiceName in rootDSE
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags",
            "dNSHostName", "mailAddress"
        ]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        res = samdb.search(sstr,
                           scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=server)")

        # Write server output
        write_search_result(samdb, f, res)

        # Query Naming Context replicas
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged", "objectSid",
            "fSMORoleOwner", "msDS-Behavior-Version", "repsFrom", "repsTo"
        ]

        for sstr in nclist:
            res = samdb.search(sstr, scope=ldb.SCOPE_BASE, attrs=attrs)

            # Write naming context output
            write_search_result(samdb, f, res)

        # Query rootDSE replicas
        attrs = [
            "objectClass", "objectGUID", "cn", "whenChanged",
            "rootDomainNamingContext", "configurationNamingContext",
            "schemaNamingContext", "defaultNamingContext", "dsServiceName"
        ]

        sstr = ""
        res = samdb.search(sstr, scope=ldb.SCOPE_BASE, attrs=attrs)

        # Record the rootDSE object as a dn as it
        # would appear in the base ldb file.  We have
        # to save it this way because we are going to
        # be importing as an abbreviated database.
        res[0].dn = ldb.Dn(samdb, "@ROOTDSE")

        # Write rootdse output
        write_search_result(samdb, f, res)

    except ldb.LdbError as e1:
        (enum, estr) = e1.args
        raise LdifError("Error processing (%s) : %s" % (sstr, estr))

    f.close()
예제 #11
0
    def test_tokenGroups_manual(self):
        # Manually run the tokenGroups algorithm from MS-ADTS 3.1.1.4.5.19 and MS-DRSR 4.1.8.3
        # and compare the result
        res = self.admin_ldb.search(
            base=self.base_dn,
            scope=ldb.SCOPE_SUBTREE,
            expression="(|(objectclass=user)(objectclass=group))",
            attrs=["memberOf"])
        aSet = set()
        aSetR = set()
        vSet = set()
        for obj in res:
            if "memberOf" in obj:
                for dn in obj["memberOf"]:
                    first = obj.dn.get_casefold()
                    second = ldb.Dn(self.admin_ldb, dn).get_casefold()
                    aSet.add((first, second))
                    aSetR.add((second, first))
                    vSet.add(first)
                    vSet.add(second)

        res = self.admin_ldb.search(base=self.base_dn,
                                    scope=ldb.SCOPE_SUBTREE,
                                    expression="(objectclass=user)",
                                    attrs=["primaryGroupID"])
        for obj in res:
            if "primaryGroupID" in obj:
                sid = "%s-%d" % (self.admin_ldb.get_domain_sid(),
                                 int(obj["primaryGroupID"][0]))
                res2 = self.admin_ldb.search(base="<SID=%s>" % sid,
                                             scope=ldb.SCOPE_BASE,
                                             attrs=[])
                first = obj.dn.get_casefold()
                second = res2[0].dn.get_casefold()

                aSet.add((first, second))
                aSetR.add((second, first))
                vSet.add(first)
                vSet.add(second)

        wSet = set()
        wSet.add(self.test_user_dn.get_casefold())
        closure(vSet, wSet, aSet)
        wSet.remove(self.test_user_dn.get_casefold())

        tokenGroupsSet = set()

        res = self.ldb.search(self.user_sid_dn,
                              scope=ldb.SCOPE_BASE,
                              attrs=["tokenGroups"])
        self.assertEquals(len(res), 1)

        dn_tokengroups = []
        for sid in res[0]['tokenGroups']:
            sid = ndr_unpack(samba.dcerpc.security.dom_sid, sid)
            res3 = self.admin_ldb.search(base="<SID=%s>" % sid,
                                         scope=ldb.SCOPE_BASE,
                                         attrs=[])
            tokenGroupsSet.add(res3[0].dn.get_casefold())

        if len(wSet.difference(tokenGroupsSet)):
            self.fail(msg="additional calculated: %s" %
                      wSet.difference(tokenGroupsSet))

        if len(tokenGroupsSet.difference(wSet)):
            self.fail(msg="additional tokenGroups: %s" %
                      tokenGroupsSet.difference(wSet))
예제 #12
0
파일: share.py 프로젝트: Schu23/GSoC-SWAT
    def store(self, name, is_new=False, old_name=''):
        """ Add/Save share information in LDB. If we are changing a Share's
        name we need to use old_name to specify the old name so the necessary
        renamings may be performed.
        
        Keyword arguments:
        name -- the name of the share to save the information
        is_new -- indicates if the share if new or not
        old_name -- the old share name (in case we are renaming)
        
        Returns:
        Sucess or insucess of the operation
        
        """
        stored = False

        name = str(name).strip()
        old_name = str(old_name).strip()

        if not is_new and len(old_name) == 0:
            old_name = name

        try:
            if len(name) == 0:
                raise ShareError(
                    _("You cannot add a Share with an empty name"))

            if not is_new and len(old_name) == 0:
                raise ShareError(
                    _("You are modifying a Share name but the old name is missing"
                      ))

            if is_new and self.share_name_exists(name):
                raise ShareError(_("A Share with that name already exists"))

            if not is_new and not self.share_name_exists(old_name):
                raise ShareError(
                    _("You are editing a Share that doesn't exist anymore"))

            dn = "CN=" + name + ",CN=Shares"
            old_dn = "CN=" + old_name + ",CN=Shares"

            # Rename the DN element before continuing
            if not is_new and name != old_name:
                self.__shares_db.rename(ldb.Dn(self.__shares_db, old_dn), dn)

            if is_new:
                share = ldb.Message(ldb.Dn(self.__shares_db, dn))
                share["name"] = ldb.MessageElement(name, \
                                                   ldb.CHANGETYPE_ADD, \
                                                   "name")
            else:
                share = self.__get_share_from_backend(name)

            modded_messages = ldb.Message(ldb.Dn(self.__shares_db, dn))

            if not is_new and name != old_name:
                modded_messages["name"] = ldb.MessageElement(name, \
                                                             ldb.FLAG_MOD_REPLACE, \
                                                             "name")

            # Replace existing attribute values
            for param, value in share.items():
                if param == "dn":
                    continue

                if param in self._params:
                    if len(self._params[param]) == 0:
                        self._params[param] = []

                    modded_messages[param] = ldb.MessageElement(self._params[param], \
                                                  ldb.FLAG_MOD_REPLACE, param)
                    del self._params[param]

            # Add the new attributes passed by the form
            for param, value in self._params.items():
                if len(value) > 0:
                    modded_messages[param] = ldb.MessageElement(value, \
                                                                ldb.CHANGETYPE_ADD, \
                                                                param)
            if is_new:
                self.__shares_db.add(share)

            self.__shares_db.modify(modded_messages)
            stored = True

        except ShareError, error:
            self._set_error(error.message, error.type)
예제 #13
0
파일: large_ldap.py 프로젝트: szaydel/samba
    def test_timeout(self):
        policy_dn = ldb.Dn(self.ldb,
                           'CN=Default Query Policy,CN=Query-Policies,'
                           'CN=Directory Service,CN=Windows NT,CN=Services,'
                           f'{self.ldb.get_config_basedn().get_linearized()}')

        # Get the current value of lDAPAdminLimits.
        res = self.ldb.search(base=policy_dn,
                              scope=ldb.SCOPE_BASE,
                              attrs=['lDAPAdminLimits'])
        msg = res[0]
        admin_limits = msg['lDAPAdminLimits']

        # Ensure we restore the previous value of the attribute.
        admin_limits.set_flags(ldb.FLAG_MOD_REPLACE)
        self.addCleanup(self.ldb.modify, msg)

        # Temporarily lower the value of MaxQueryDuration so we can test
        # timeout behaviour.
        timeout = 5
        query_duration = f'MaxQueryDuration={timeout}'.encode()

        admin_limits = [limit for limit in admin_limits
                        if not limit.lower().startswith(b'maxqueryduration=')]
        admin_limits.append(query_duration)

        # Set the new attribute value.
        msg = ldb.Message(policy_dn)
        msg['lDAPAdminLimits'] = ldb.MessageElement(admin_limits,
                                                    ldb.FLAG_MOD_REPLACE,
                                                    'lDAPAdminLimits')
        self.ldb.modify(msg)

        # Use a new connection so that the limits are reloaded.
        samdb = SamDB(url, credentials=creds,
                      session_info=system_session(lp),
                      lp=lp)

        # Create a large search expression that will take a long time to
        # evaluate.
        expression = '(anr=l)' * 10000
        expression = f'(|{expression})'

        # Perform the LDAP search.
        prev = time.time()
        with self.assertRaises(ldb.LdbError) as err:
            samdb.search(base=self.ou_dn,
                         scope=ldb.SCOPE_SUBTREE,
                         expression=expression,
                         attrs=['objectGUID'])
        now = time.time()
        duration = now - prev

        # Ensure that we timed out.
        enum, _ = err.exception.args
        self.assertEqual(ldb.ERR_TIME_LIMIT_EXCEEDED, enum)

        # Ensure that the time spent searching is within the limit we
        # set.  We allow a margin of 100% over as the Samba timeout
        # handling is not very accurate (and does not need to be)
        self.assertLess(timeout - 1, duration)
        self.assertLess(duration, timeout * 2)