2 # -*- coding: utf-8 -*-
4 # Tests various schema replication scenarios
6 # Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2011
7 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2016
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # export DC1=dc1_dns_name
26 # export DC2=dc2_dns_name
27 # export SUBUNITRUN=$samba4srcdir/scripting/bin/subunitrun
28 # PYTHONPATH="$PYTHONPATH:$samba4srcdir/torture/drs/python" $SUBUNITRUN getnc_exop -U"$DOMAIN/$DC_USERNAME"%"$DC_PASSWORD"
34 from drs_base import AbstractLink
38 from samba import werror, WERRORError
41 from ldb import SCOPE_BASE
43 from samba.dcerpc import drsuapi, misc, drsblobs
44 from samba.drs_utils import drs_DsBind
45 from samba.ndr import ndr_unpack, ndr_pack
46 from samba.compat import cmp_to_key_fn
47 from samba.compat import cmp_fn
50 def _linked_attribute_compare(la1, la2):
51 """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
55 # Ascending host object GUID
56 c = cmp_fn(ndr_pack(la1.identifier.guid), ndr_pack(la2.identifier.guid))
60 # Ascending attribute ID
61 if la1.attid != la2.attid:
62 return -1 if la1.attid < la2.attid else 1
64 la1_active = la1.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
65 la2_active = la2.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
67 # Ascending 'is present'
68 if la1_active != la2_active:
69 return 1 if la1_active else -1
71 # Ascending target object GUID
72 return cmp_fn(ndr_pack(la1_target), ndr_pack(la2_target))
75 class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
76 """Intended as a semi-black box test case for DsGetNCChanges
77 implementation for extended operations. It should be testing
78 how DsGetNCChanges handles different input params (mostly invalid).
79 Final goal is to make DsGetNCChanges as binary compatible to
80 Windows implementation as possible"""
83 super(DrsReplicaSyncTestCase, self).setUp()
84 self.base_dn = self.ldb_dc1.get_default_basedn()
85 self.ou = "OU=test_getncchanges,%s" % self.base_dn
88 "objectclass": "organizationalUnit"})
89 (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
90 (self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.ldb_dc1)
94 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
95 except ldb.LdbError as e:
96 (enum, string) = e.args
97 if enum == ldb.ERR_NO_SUCH_OBJECT:
99 super(DrsReplicaSyncTestCase, self).tearDown()
101 def _determine_fSMORoleOwner(self, fsmo_obj_dn):
102 """Returns (owner, not_owner) pair where:
103 owner: dns name for FSMO owner
104 not_owner: dns name for DC not owning the FSMO"""
105 # collect info to return later
106 fsmo_info_1 = {"dns_name": self.dnsname_dc1,
107 "invocation_id": self.ldb_dc1.get_invocation_id(),
108 "ntds_guid": self.ldb_dc1.get_ntds_GUID(),
109 "server_dn": self.ldb_dc1.get_serverName()}
110 fsmo_info_2 = {"dns_name": self.dnsname_dc2,
111 "invocation_id": self.ldb_dc2.get_invocation_id(),
112 "ntds_guid": self.ldb_dc2.get_ntds_GUID(),
113 "server_dn": self.ldb_dc2.get_serverName()}
115 msgs = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=fsmo_info_1["server_dn"], attrs=["serverReference"])
116 fsmo_info_1["server_acct_dn"] = ldb.Dn(self.ldb_dc1, msgs[0]["serverReference"][0].decode('utf8'))
117 fsmo_info_1["rid_set_dn"] = ldb.Dn(self.ldb_dc1, "CN=RID Set") + fsmo_info_1["server_acct_dn"]
119 msgs = self.ldb_dc2.search(scope=ldb.SCOPE_BASE, base=fsmo_info_2["server_dn"], attrs=["serverReference"])
120 fsmo_info_2["server_acct_dn"] = ldb.Dn(self.ldb_dc2, msgs[0]["serverReference"][0].decode('utf8'))
121 fsmo_info_2["rid_set_dn"] = ldb.Dn(self.ldb_dc2, "CN=RID Set") + fsmo_info_2["server_acct_dn"]
123 # determine the owner dc
124 res = self.ldb_dc1.search(fsmo_obj_dn,
125 scope=SCOPE_BASE, attrs=["fSMORoleOwner"])
126 assert len(res) == 1, "Only one fSMORoleOwner value expected for %s!" %fsmo_obj_dn
127 fsmo_owner = res[0]["fSMORoleOwner"][0]
128 if fsmo_owner == self.info_dc1["dsServiceName"][0]:
129 return (fsmo_info_1, fsmo_info_2)
130 return (fsmo_info_2, fsmo_info_1)
132 def _check_exop_failed(self, ctr6, expected_failure):
133 self.assertEqual(ctr6.extended_ret, expected_failure)
134 #self.assertEqual(ctr6.object_count, 0)
135 #self.assertEqual(ctr6.first_object, None)
136 self.assertEqual(ctr6.more_data, False)
137 self.assertEqual(ctr6.nc_object_count, 0)
138 self.assertEqual(ctr6.nc_linked_attributes_count, 0)
139 self.assertEqual(ctr6.linked_attributes_count, 0)
140 self.assertEqual(ctr6.linked_attributes, [])
141 self.assertEqual(ctr6.drs_error[0], 0)
143 def test_do_single_repl(self):
145 Make sure that DRSUAPI_EXOP_REPL_OBJ never replicates more than
146 one object, even when we use DRS_GET_ANC/GET_TGT.
149 ou1 = "OU=get_anc1,%s" % self.ou
152 "objectclass": "organizationalUnit"
154 ou1_id = self._get_identifier(self.ldb_dc1, ou1)
155 ou2 = "OU=get_anc2,%s" % ou1
158 "objectclass": "organizationalUnit"
160 ou2_id = self._get_identifier(self.ldb_dc1, ou2)
161 dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
164 "objectclass": "computer",
165 "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
167 dc3_id = self._get_identifier(self.ldb_dc1, dc3)
169 # Add some linked attributes (for checking GET_TGT behaviour)
171 m.dn = ldb.Dn(self.ldb_dc2, ou1)
172 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_ADD, "managedBy")
173 self.ldb_dc1.modify(m)
174 ou1_link = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
175 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
176 ou1_id.guid, ou2_id.guid)
178 m.dn = ldb.Dn(self.ldb_dc2, dc3)
179 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_ADD, "managedBy")
180 self.ldb_dc1.modify(m)
181 dc3_link = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
182 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
183 dc3_id.guid, ou2_id.guid)
185 req = self._getnc_req10(dest_dsa=None,
186 invocation_id=self.ldb_dc1.get_invocation_id(),
188 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
189 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP,
190 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
191 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
192 self._check_ctr6(ctr, [ou1], expected_links=[ou1_link])
194 # DRSUAPI_DRS_WRIT_REP means that we should only replicate the dn we give (dc3).
195 # DRSUAPI_DRS_GET_ANC means that we should also replicate its ancestors, but
196 # Windows doesn't do this if we use both.
197 req = self._getnc_req10(dest_dsa=None,
198 invocation_id=self.ldb_dc1.get_invocation_id(),
200 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
201 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP |
202 drsuapi.DRSUAPI_DRS_GET_ANC,
203 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
204 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
205 self._check_ctr6(ctr, [dc3], expected_links=[dc3_link])
207 # Even though the ancestor of ou2 (ou1) has changed since last hwm, and we're
208 # sending DRSUAPI_DRS_GET_ANC, the expected response is that it will only try
209 # and replicate the single object still.
210 req = self._getnc_req10(dest_dsa=None,
211 invocation_id=self.ldb_dc1.get_invocation_id(),
213 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
214 replica_flags=drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
215 drsuapi.DRSUAPI_DRS_GET_ANC,
216 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
217 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
218 self._check_ctr6(ctr, [ou2])
220 def test_do_full_repl_on_ou(self):
222 Make sure that a full replication on a not-an-nc fails with
226 non_nc_ou = "OU=not-an-NC,%s" % self.ou
229 "objectclass": "organizationalUnit"
231 req8 = self._exop_req8(dest_dsa=None,
232 invocation_id=self.ldb_dc1.get_invocation_id(),
234 exop=drsuapi.DRSUAPI_EXOP_NONE,
235 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
238 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
239 self.fail("Expected DsGetNCChanges to fail with WERR_DS_CANT_FIND_EXPECTED_NC")
240 except WERRORError as e1:
241 (enum, estr) = e1.args
242 self.assertEquals(enum, werror.WERR_DS_CANT_FIND_EXPECTED_NC)
244 def test_link_utdv_hwm(self):
245 """Test verify the DRS_GET_ANC behavior."""
247 ou1 = "OU=get_anc1,%s" % self.ou
250 "objectclass": "organizationalUnit"
252 ou1_id = self._get_identifier(self.ldb_dc1, ou1)
253 ou2 = "OU=get_anc2,%s" % ou1
256 "objectclass": "organizationalUnit"
258 ou2_id = self._get_identifier(self.ldb_dc1, ou2)
259 dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
262 "objectclass": "computer",
263 "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
265 dc3_id = self._get_identifier(self.ldb_dc1, dc3)
267 (hwm1, utdv1) = self._check_replication([ou1, ou2, dc3],
268 drsuapi.DRSUAPI_DRS_WRIT_REP)
270 self._check_replication([ou1, ou2, dc3],
271 drsuapi.DRSUAPI_DRS_WRIT_REP |
272 drsuapi.DRSUAPI_DRS_GET_ANC)
274 self._check_replication([dc3],
275 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
277 self._check_replication([ou1, ou2, dc3],
278 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
279 drsuapi.DRSUAPI_DRS_GET_ANC)
282 m.dn = ldb.Dn(self.ldb_dc1, ou1)
283 m["displayName"] = ldb.MessageElement("OU1", ldb.FLAG_MOD_ADD, "displayName")
284 self.ldb_dc1.modify(m)
286 (hwm2, utdv2) = self._check_replication([ou2, dc3, ou1],
287 drsuapi.DRSUAPI_DRS_WRIT_REP)
289 self._check_replication([ou1, ou2, dc3],
290 drsuapi.DRSUAPI_DRS_WRIT_REP |
291 drsuapi.DRSUAPI_DRS_GET_ANC)
293 self._check_replication([dc3],
294 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
296 self._check_replication([ou1, ou2, dc3],
297 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
298 drsuapi.DRSUAPI_DRS_GET_ANC)
300 self._check_replication([ou1],
301 drsuapi.DRSUAPI_DRS_WRIT_REP,
304 self._check_replication([ou1],
305 drsuapi.DRSUAPI_DRS_WRIT_REP |
306 drsuapi.DRSUAPI_DRS_GET_ANC,
309 self._check_replication([ou1],
310 drsuapi.DRSUAPI_DRS_WRIT_REP |
311 drsuapi.DRSUAPI_DRS_GET_ANC,
312 uptodateness_vector=utdv1)
315 m.dn = ldb.Dn(self.ldb_dc1, ou2)
316 m["displayName"] = ldb.MessageElement("OU2", ldb.FLAG_MOD_ADD, "displayName")
317 self.ldb_dc1.modify(m)
319 (hwm3, utdv3) = self._check_replication([dc3, ou1, ou2],
320 drsuapi.DRSUAPI_DRS_WRIT_REP)
322 self._check_replication([ou1, ou2, dc3],
323 drsuapi.DRSUAPI_DRS_WRIT_REP |
324 drsuapi.DRSUAPI_DRS_GET_ANC)
326 self._check_replication([dc3],
327 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
329 self._check_replication([ou1, ou2, dc3],
330 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
331 drsuapi.DRSUAPI_DRS_GET_ANC)
333 self._check_replication([ou1, ou2],
334 drsuapi.DRSUAPI_DRS_WRIT_REP,
337 self._check_replication([ou1, ou2],
338 drsuapi.DRSUAPI_DRS_WRIT_REP |
339 drsuapi.DRSUAPI_DRS_GET_ANC,
342 self._check_replication([ou1, ou2],
343 drsuapi.DRSUAPI_DRS_WRIT_REP |
344 drsuapi.DRSUAPI_DRS_GET_ANC,
345 uptodateness_vector=utdv1)
348 m.dn = ldb.Dn(self.ldb_dc1, self.ou)
349 m["displayName"] = ldb.MessageElement("OU", ldb.FLAG_MOD_ADD, "displayName")
350 self.ldb_dc1.modify(m)
352 (hwm4, utdv4) = self._check_replication([dc3, ou1, ou2, self.ou],
353 drsuapi.DRSUAPI_DRS_WRIT_REP)
355 self._check_replication([self.ou, ou1, ou2, dc3],
356 drsuapi.DRSUAPI_DRS_WRIT_REP |
357 drsuapi.DRSUAPI_DRS_GET_ANC)
359 self._check_replication([dc3],
360 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
362 self._check_replication([self.ou, ou1, ou2, dc3],
363 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
364 drsuapi.DRSUAPI_DRS_GET_ANC)
366 self._check_replication([self.ou, ou2],
367 drsuapi.DRSUAPI_DRS_WRIT_REP |
368 drsuapi.DRSUAPI_DRS_GET_ANC,
369 uptodateness_vector=utdv2)
371 cn3 = "CN=get_anc3,%s" % ou2
374 "objectclass": "container",
376 cn3_id = self._get_identifier(self.ldb_dc1, cn3)
378 (hwm5, utdv5) = self._check_replication([dc3, ou1, ou2, self.ou, cn3],
379 drsuapi.DRSUAPI_DRS_WRIT_REP)
381 self._check_replication([self.ou, ou1, ou2, dc3, cn3],
382 drsuapi.DRSUAPI_DRS_WRIT_REP |
383 drsuapi.DRSUAPI_DRS_GET_ANC)
385 self._check_replication([dc3],
386 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
388 self._check_replication([self.ou, ou1, ou2, dc3],
389 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
390 drsuapi.DRSUAPI_DRS_GET_ANC)
393 m.dn = ldb.Dn(self.ldb_dc1, ou2)
394 m["managedBy"] = ldb.MessageElement(dc3, ldb.FLAG_MOD_ADD, "managedBy")
395 self.ldb_dc1.modify(m)
396 ou2_managedBy_dc3 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
397 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
398 ou2_id.guid, dc3_id.guid)
400 (hwm6, utdv6) = self._check_replication([dc3, ou1, self.ou, cn3, ou2],
401 drsuapi.DRSUAPI_DRS_WRIT_REP,
402 expected_links=[ou2_managedBy_dc3])
404 # Can fail against Windows due to equal precedence of dc3, cn3
405 self._check_replication([self.ou, ou1, ou2, dc3, cn3],
406 drsuapi.DRSUAPI_DRS_WRIT_REP |
407 drsuapi.DRSUAPI_DRS_GET_ANC,
408 expected_links=[ou2_managedBy_dc3])
410 self._check_replication([dc3],
411 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
413 self._check_replication([self.ou, ou1, ou2, dc3],
414 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
415 drsuapi.DRSUAPI_DRS_GET_ANC)
417 self._check_replication([],
418 drsuapi.DRSUAPI_DRS_WRIT_REP,
419 uptodateness_vector=utdv5,
420 expected_links=[ou2_managedBy_dc3])
422 self._check_replication([],
423 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
424 uptodateness_vector=utdv5)
426 self._check_replication([],
427 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
428 uptodateness_vector=utdv5)
431 m.dn = ldb.Dn(self.ldb_dc1, dc3)
432 m["managedBy"] = ldb.MessageElement(ou1, ldb.FLAG_MOD_ADD, "managedBy")
433 self.ldb_dc1.modify(m)
434 dc3_managedBy_ou1 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
435 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
436 dc3_id.guid, ou1_id.guid)
438 (hwm7, utdv7) = self._check_replication([ou1, self.ou, cn3, ou2, dc3],
439 drsuapi.DRSUAPI_DRS_WRIT_REP,
440 expected_links=[ou2_managedBy_dc3, dc3_managedBy_ou1])
442 # Can fail against Windows due to equal precedence of dc3, cn3
443 # self._check_replication([self.ou,ou1,ou2,dc3,cn3],
444 # drsuapi.DRSUAPI_DRS_WRIT_REP|
445 # drsuapi.DRSUAPI_DRS_GET_ANC,
446 # expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
448 self._check_replication([dc3],
449 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
450 expected_links=[dc3_managedBy_ou1])
452 self._check_replication([dc3],
453 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
454 expected_links=[dc3_managedBy_ou1])
456 self._check_replication([self.ou, ou1, ou2, dc3],
457 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
458 drsuapi.DRSUAPI_DRS_GET_ANC,
459 expected_links=[dc3_managedBy_ou1])
461 # GET_TGT seems to override DRS_CRITICAL_ONLY and also returns any
462 # object(s) that relate to the linked attributes (similar to GET_ANC)
463 self._check_replication([ou1, dc3],
464 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
465 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
466 expected_links=[dc3_managedBy_ou1], dn_ordered=False)
468 # Change DC3's managedBy to OU2 instead of OU1
469 # Note that the OU1 managedBy linked attribute will still exist as
470 # a tombstone object (and so will be returned in the replication still)
472 m.dn = ldb.Dn(self.ldb_dc1, dc3)
473 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_REPLACE, "managedBy")
474 self.ldb_dc1.modify(m)
475 dc3_managedBy_ou1.flags &= ~drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
476 dc3_managedBy_ou2 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
477 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
478 dc3_id.guid, ou2_id.guid)
480 (hwm8, utdv8) = self._check_replication([ou1, self.ou, cn3, ou2, dc3],
481 drsuapi.DRSUAPI_DRS_WRIT_REP,
482 expected_links=[ou2_managedBy_dc3, dc3_managedBy_ou1, dc3_managedBy_ou2])
484 # Can fail against Windows due to equal precedence of dc3, cn3
485 # self._check_replication([self.ou,ou1,ou2,dc3,cn3],
486 # drsuapi.DRSUAPI_DRS_WRIT_REP|
487 # drsuapi.DRSUAPI_DRS_GET_ANC,
488 # expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
490 self._check_replication([dc3],
491 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
492 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2])
494 self._check_replication([self.ou, ou1, ou2, dc3],
495 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
496 drsuapi.DRSUAPI_DRS_GET_ANC,
497 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2])
499 # GET_TGT will also return any DNs referenced by the linked attributes
500 # (including the Tombstone attribute)
501 self._check_replication([ou1, ou2, dc3],
502 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
503 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
504 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2], dn_ordered=False)
506 # Use the highwater-mark prior to changing ManagedBy - this should
507 # only return the old/Tombstone and new linked attributes (we already
509 self._check_replication([],
510 drsuapi.DRSUAPI_DRS_WRIT_REP,
511 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
514 self._check_replication([],
515 drsuapi.DRSUAPI_DRS_WRIT_REP |
516 drsuapi.DRSUAPI_DRS_GET_ANC,
517 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
520 self._check_replication([],
521 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
522 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
525 self._check_replication([],
526 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
527 drsuapi.DRSUAPI_DRS_GET_ANC,
528 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
531 self._check_replication([],
532 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
533 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
534 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
537 # Repeat the above set of tests using the uptodateness_vector
538 # instead of the highwater-mark
539 self._check_replication([],
540 drsuapi.DRSUAPI_DRS_WRIT_REP,
541 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
542 uptodateness_vector=utdv7)
544 self._check_replication([],
545 drsuapi.DRSUAPI_DRS_WRIT_REP |
546 drsuapi.DRSUAPI_DRS_GET_ANC,
547 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
548 uptodateness_vector=utdv7)
550 self._check_replication([],
551 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
552 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
553 uptodateness_vector=utdv7)
555 self._check_replication([],
556 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
557 drsuapi.DRSUAPI_DRS_GET_ANC,
558 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
559 uptodateness_vector=utdv7)
561 self._check_replication([],
562 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
563 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
564 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
565 uptodateness_vector=utdv7)
567 def test_FSMONotOwner(self):
568 """Test role transfer with against DC not owner of the role"""
569 fsmo_dn = self.ldb_dc1.get_schema_basedn()
570 (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
572 req8 = self._exop_req8(dest_dsa=fsmo_owner["ntds_guid"],
573 invocation_id=fsmo_not_owner["invocation_id"],
575 exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
577 (drs, drs_handle) = self._ds_bind(fsmo_not_owner["dns_name"])
578 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
579 self.assertEqual(level, 6, "Expected level 6 response!")
580 self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER)
581 self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_not_owner["ntds_guid"]))
582 self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_not_owner["invocation_id"]))
584 def test_InvalidDestDSA(self):
585 """Test role transfer with invalid destination DSA guid"""
586 fsmo_dn = self.ldb_dc1.get_schema_basedn()
587 (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
589 req8 = self._exop_req8(dest_dsa="9c637462-5b8c-4467-aef2-bdb1f57bc4ef",
590 invocation_id=fsmo_owner["invocation_id"],
592 exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
594 (drs, drs_handle) = self._ds_bind(fsmo_owner["dns_name"])
595 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
596 self.assertEqual(level, 6, "Expected level 6 response!")
597 self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_UNKNOWN_CALLER)
598 self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_owner["ntds_guid"]))
599 self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_owner["invocation_id"]))
602 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
604 super(DrsReplicaPrefixMapTestCase, self).setUp()
605 self.base_dn = self.ldb_dc1.get_default_basedn()
606 self.ou = "ou=pfm_exop,%s" % self.base_dn
609 "objectclass": "organizationalUnit"})
610 self.user = "cn=testuser,%s" % self.ou
613 "objectclass": "user"})
616 super(DrsReplicaPrefixMapTestCase, self).tearDown()
618 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
619 except ldb.LdbError as e2:
620 (enum, string) = e2.args
621 if enum == ldb.ERR_NO_SUCH_OBJECT:
624 def test_missing_prefix_map_dsa(self):
625 partial_attribute_set = self.get_partial_attribute_set()
627 dc_guid_1 = self.ldb_dc1.get_invocation_id()
629 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
631 req8 = self._exop_req8(dest_dsa=None,
632 invocation_id=dc_guid_1,
634 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
635 partial_attribute_set=partial_attribute_set)
638 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
639 self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
641 self.fail("Missing prefixmap shouldn't have triggered an error")
643 def test_invalid_prefix_map_attid(self):
644 # Request for invalid attid
645 partial_attribute_set = self.get_partial_attribute_set([99999])
647 dc_guid_1 = self.ldb_dc1.get_invocation_id()
648 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
651 pfm = self._samdb_fetch_pfm_and_schi()
653 # On Windows, prefixMap isn't available over LDAP
654 req8 = self._exop_req8(dest_dsa=None,
655 invocation_id=dc_guid_1,
657 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
658 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
659 pfm = ctr.mapping_ctr
661 req8 = self._exop_req8(dest_dsa=None,
662 invocation_id=dc_guid_1,
664 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
665 partial_attribute_set=partial_attribute_set,
669 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
670 self.fail("Invalid attid (99999) should have triggered an error")
671 except RuntimeError as e3:
672 (ecode, emsg) = e3.args
673 self.assertEqual(ecode, 0x000020E2, "Error code should have been "
674 "WERR_DS_DRA_SCHEMA_MISMATCH")
676 def test_secret_prefix_map_attid(self):
677 # Request for a secret attid
678 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
680 dc_guid_1 = self.ldb_dc1.get_invocation_id()
681 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
684 pfm = self._samdb_fetch_pfm_and_schi()
686 # On Windows, prefixMap isn't available over LDAP
687 req8 = self._exop_req8(dest_dsa=None,
688 invocation_id=dc_guid_1,
690 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
691 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
692 pfm = ctr.mapping_ctr
695 req8 = self._exop_req8(dest_dsa=None,
696 invocation_id=dc_guid_1,
698 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
699 partial_attribute_set=partial_attribute_set,
702 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
705 for attr in ctr.first_object.object.attribute_ctr.attributes:
706 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
710 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
712 for i, mapping in enumerate(pfm.mappings):
714 # objectClass: 2.5.4.0
715 if mapping.oid.binary_oid == [85, 4]:
717 # OID: 1.2.840.113556.1.4.*
718 # unicodePwd: 1.2.840.113556.1.4.90
719 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
722 (pfm.mappings[idx1].id_prefix,
723 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
724 pfm.mappings[idx1].id_prefix)
727 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
730 # 90 for unicodePwd (with new prefix = 0)
731 # 589824, 589827 for objectClass and CN
732 # Use of three ensures sorting is correct
733 partial_attribute_set = self.get_partial_attribute_set([90, 589824, 589827])
734 req8 = self._exop_req8(dest_dsa=None,
735 invocation_id=dc_guid_1,
737 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
738 partial_attribute_set=partial_attribute_set,
741 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
744 for attr in ctr.first_object.object.attribute_ctr.attributes:
745 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
749 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
751 def test_regular_prefix_map_attid(self):
752 # Request for a regular (non-secret) attid
753 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
755 dc_guid_1 = self.ldb_dc1.get_invocation_id()
756 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
759 pfm = self._samdb_fetch_pfm_and_schi()
761 # On Windows, prefixMap isn't available over LDAP
762 req8 = self._exop_req8(dest_dsa=None,
763 invocation_id=dc_guid_1,
765 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
766 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
767 pfm = ctr.mapping_ctr
770 req8 = self._exop_req8(dest_dsa=None,
771 invocation_id=dc_guid_1,
773 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
774 partial_attribute_set=partial_attribute_set,
777 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
780 for attr in ctr.first_object.object.attribute_ctr.attributes:
781 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
785 self.assertTrue(found, "Ensure we get the name attribute back")
787 for i, mapping in enumerate(pfm.mappings):
789 # objectClass: 2.5.4.0
790 if mapping.oid.binary_oid == [85, 4]:
792 # OID: 1.2.840.113556.1.4.*
793 # name: 1.2.840.113556.1.4.1
794 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
797 (pfm.mappings[idx1].id_prefix,
798 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
799 pfm.mappings[idx1].id_prefix)
802 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
805 # 1 for name (with new prefix = 0)
806 partial_attribute_set = self.get_partial_attribute_set([1])
807 req8 = self._exop_req8(dest_dsa=None,
808 invocation_id=dc_guid_1,
810 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
811 partial_attribute_set=partial_attribute_set,
814 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
817 for attr in ctr.first_object.object.attribute_ctr.attributes:
818 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
822 self.assertTrue(found, "Ensure we get the name attribute back")
824 def test_regular_prefix_map_ex_attid(self):
825 # Request for a regular (non-secret) attid
826 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
827 partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
829 dc_guid_1 = self.ldb_dc1.get_invocation_id()
830 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
833 pfm = self._samdb_fetch_pfm_and_schi()
835 # On Windows, prefixMap isn't available over LDAP
836 req8 = self._exop_req8(dest_dsa=None,
837 invocation_id=dc_guid_1,
839 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
840 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
841 pfm = ctr.mapping_ctr
844 req8 = self._exop_req8(dest_dsa=None,
845 invocation_id=dc_guid_1,
847 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
848 partial_attribute_set=partial_attribute_set,
849 partial_attribute_set_ex=partial_attribute_set_ex,
852 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
855 for attr in ctr.first_object.object.attribute_ctr.attributes:
856 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
860 self.assertTrue(found, "Ensure we get the name attribute back")
863 for attr in ctr.first_object.object.attribute_ctr.attributes:
864 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
868 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
870 for i, mapping in enumerate(pfm.mappings):
872 # objectClass: 2.5.4.0
873 if mapping.oid.binary_oid == [85, 4]:
875 # OID: 1.2.840.113556.1.4.*
876 # name: 1.2.840.113556.1.4.1
877 # unicodePwd: 1.2.840.113556.1.4.90
878 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
881 (pfm.mappings[idx1].id_prefix,
882 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
883 pfm.mappings[idx1].id_prefix)
886 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
889 # 1 for name (with new prefix = 0)
890 partial_attribute_set = self.get_partial_attribute_set([1])
891 # 90 for unicodePwd (with new prefix = 0)
892 # HOWEVER: Windows doesn't seem to respect incoming maps for PartialAttrSetEx
893 partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
894 req8 = self._exop_req8(dest_dsa=None,
895 invocation_id=dc_guid_1,
897 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
898 partial_attribute_set=partial_attribute_set,
899 partial_attribute_set_ex=partial_attribute_set_ex,
902 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
905 for attr in ctr.first_object.object.attribute_ctr.attributes:
906 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
910 self.assertTrue(found, "Ensure we get the name attribute back")
913 for attr in ctr.first_object.object.attribute_ctr.attributes:
914 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
918 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
920 def _samdb_fetch_pfm_and_schi(self):
921 """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
923 res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
924 attrs=["prefixMap", "schemaInfo"])
926 pfm = ndr_unpack(drsblobs.prefixMapBlob,
927 str(res[0]['prefixMap']))
929 schi = drsuapi.DsReplicaOIDMapping()
931 if 'schemaInfo' in res[0]:
932 binary_oid = [x if isinstance(x, int) else ord(x) for x in res[0]['schemaInfo'][0]]
933 schi.oid.length = len(binary_oid)
934 schi.oid.binary_oid = binary_oid
936 schema_info = drsblobs.schemaInfoBlob()
937 schema_info.revision = 0
938 schema_info.marker = 0xFF
939 schema_info.invocation_id = misc.GUID(samdb.get_invocation_id())
941 binary_oid = [x if isinstance(x, int) else ord(x) for x in ndr_pack(schema_info)]
942 # you have to set the length before setting binary_oid
943 schi.oid.length = len(binary_oid)
944 schi.oid.binary_oid = binary_oid
946 pfm.ctr.mappings = pfm.ctr.mappings + [schi]
947 pfm.ctr.num_mappings += 1
951 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
953 super(DrsReplicaSyncSortTestCase, self).setUp()
954 self.base_dn = self.ldb_dc1.get_default_basedn()
955 self.ou = "ou=sort_exop,%s" % self.base_dn
958 "objectclass": "organizationalUnit"})
961 super(DrsReplicaSyncSortTestCase, self).tearDown()
962 # tidyup groups and users
964 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
965 except ldb.LdbError as e4:
966 (enum, string) = e4.args
967 if enum == ldb.ERR_NO_SUCH_OBJECT:
970 def add_linked_attribute(self, src, dest, attr='member'):
972 m.dn = ldb.Dn(self.ldb_dc1, src)
973 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
974 self.ldb_dc1.modify(m)
976 def remove_linked_attribute(self, src, dest, attr='member'):
978 m.dn = ldb.Dn(self.ldb_dc1, src)
979 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
980 self.ldb_dc1.modify(m)
982 def test_sort_behaviour_single_object(self):
983 """Testing sorting behaviour on single objects"""
985 user1_dn = "cn=test_user1,%s" % self.ou
986 user2_dn = "cn=test_user2,%s" % self.ou
987 user3_dn = "cn=test_user3,%s" % self.ou
988 group_dn = "cn=test_group,%s" % self.ou
990 self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
991 self.ldb_dc1.add({"dn": user2_dn, "objectclass": "user"})
992 self.ldb_dc1.add({"dn": user3_dn, "objectclass": "user"})
993 self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
995 u1_guid = misc.GUID(self.ldb_dc1.search(base=user1_dn,
996 attrs=["objectGUID"])[0]['objectGUID'][0])
997 u2_guid = misc.GUID(self.ldb_dc1.search(base=user2_dn,
998 attrs=["objectGUID"])[0]['objectGUID'][0])
999 u3_guid = misc.GUID(self.ldb_dc1.search(base=user3_dn,
1000 attrs=["objectGUID"])[0]['objectGUID'][0])
1001 g_guid = misc.GUID(self.ldb_dc1.search(base=group_dn,
1002 attrs=["objectGUID"])[0]['objectGUID'][0])
1004 self.add_linked_attribute(group_dn, user1_dn,
1006 self.add_linked_attribute(group_dn, user2_dn,
1008 self.add_linked_attribute(group_dn, user3_dn,
1010 self.add_linked_attribute(group_dn, user1_dn,
1012 self.add_linked_attribute(group_dn, user2_dn,
1013 attr='nonSecurityMember')
1014 self.add_linked_attribute(group_dn, user3_dn,
1015 attr='nonSecurityMember')
1017 set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1018 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1021 expected_links = set([set_inactive,
1022 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1023 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1026 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1027 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1030 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1031 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1034 AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
1035 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1038 AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1039 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1044 dc_guid_1 = self.ldb_dc1.get_invocation_id()
1046 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1048 req8 = self._exop_req8(dest_dsa=None,
1049 invocation_id=dc_guid_1,
1051 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
1053 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1056 for link in ctr.linked_attributes:
1057 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1058 link.value.blob).guid
1059 no_inactive.append((link, target_guid))
1060 self.assertTrue(AbstractLink(link.attid, link.flags,
1061 link.identifier.guid,
1062 target_guid) in expected_links)
1064 no_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1066 # assert the two arrays are the same
1067 self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1068 self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)
1070 self.remove_linked_attribute(group_dn, user3_dn,
1071 attr='nonSecurityMember')
1073 # Set the link inactive
1074 expected_links.remove(set_inactive)
1075 set_inactive.flags = 0
1076 expected_links.add(set_inactive)
1079 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1080 for link in ctr.linked_attributes:
1081 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1082 link.value.blob).guid
1083 has_inactive.append((link, target_guid))
1084 self.assertTrue(AbstractLink(link.attid, link.flags,
1085 link.identifier.guid,
1086 target_guid) in expected_links)
1088 has_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1090 # assert the two arrays are the same
1091 self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1092 self.assertEqual([x[0] for x in has_inactive], ctr.linked_attributes)
1094 def test_sort_behaviour_ncchanges(self):
1095 """Testing sorting behaviour on a group of objects."""
1096 user1_dn = "cn=test_user1,%s" % self.ou
1097 group_dn = "cn=test_group,%s" % self.ou
1098 self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
1099 self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
1101 self.add_linked_attribute(group_dn, user1_dn,
1104 dc_guid_1 = self.ldb_dc1.get_invocation_id()
1106 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1108 # Make sure the max objects count is high enough
1109 req8 = self._exop_req8(dest_dsa=None,
1110 invocation_id=dc_guid_1,
1111 nc_dn_str=self.base_dn,
1114 exop=drsuapi.DRSUAPI_EXOP_NONE)
1116 # Loop until we get linked attributes, or we get to the end.
1117 # Samba sends linked attributes at the end, unlike Windows.
1119 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1120 if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1122 req8.highwatermark = ctr.new_highwatermark
1124 self.assertTrue(ctr.linked_attributes_count != 0)
1127 for link in ctr.linked_attributes:
1129 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1130 link.value.blob).guid
1132 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1133 link.value.blob).guid
1134 no_inactive.append((link, target_guid))
1136 no_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1138 # assert the two arrays are the same
1139 self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)