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
37 from samba import werror, WERRORError
40 from ldb import SCOPE_BASE
42 from samba.dcerpc import drsuapi, misc, drsblobs
43 from samba.drs_utils import drs_DsBind
44 from samba.ndr import ndr_unpack, ndr_pack
45 from functools import cmp_to_key
46 from samba.common import cmp
49 def _linked_attribute_compare(la1, la2):
50 """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
54 # Ascending host object GUID
55 c = cmp(ndr_pack(la1.identifier.guid), ndr_pack(la2.identifier.guid))
59 # Ascending attribute ID
60 if la1.attid != la2.attid:
61 return -1 if la1.attid < la2.attid else 1
63 la1_active = la1.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
64 la2_active = la2.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
66 # Ascending 'is present'
67 if la1_active != la2_active:
68 return 1 if la1_active else -1
70 # Ascending target object GUID
71 return cmp(ndr_pack(la1_target), ndr_pack(la2_target))
74 class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
75 """Intended as a semi-black box test case for DsGetNCChanges
76 implementation for extended operations. It should be testing
77 how DsGetNCChanges handles different input params (mostly invalid).
78 Final goal is to make DsGetNCChanges as binary compatible to
79 Windows implementation as possible"""
82 super(DrsReplicaSyncTestCase, self).setUp()
83 self.base_dn = self.ldb_dc1.get_default_basedn()
84 self.ou = "OU=test_getncchanges,%s" % self.base_dn
87 "objectclass": "organizationalUnit"})
88 (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
89 (self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.ldb_dc1)
93 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
94 except ldb.LdbError as e:
95 (enum, string) = e.args
96 if enum == ldb.ERR_NO_SUCH_OBJECT:
98 super(DrsReplicaSyncTestCase, self).tearDown()
100 def _determine_fSMORoleOwner(self, fsmo_obj_dn):
101 """Returns (owner, not_owner) pair where:
102 owner: dns name for FSMO owner
103 not_owner: dns name for DC not owning the FSMO"""
104 # collect info to return later
105 fsmo_info_1 = {"dns_name": self.dnsname_dc1,
106 "invocation_id": self.ldb_dc1.get_invocation_id(),
107 "ntds_guid": self.ldb_dc1.get_ntds_GUID(),
108 "server_dn": self.ldb_dc1.get_serverName()}
109 fsmo_info_2 = {"dns_name": self.dnsname_dc2,
110 "invocation_id": self.ldb_dc2.get_invocation_id(),
111 "ntds_guid": self.ldb_dc2.get_ntds_GUID(),
112 "server_dn": self.ldb_dc2.get_serverName()}
114 msgs = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=fsmo_info_1["server_dn"], attrs=["serverReference"])
115 fsmo_info_1["server_acct_dn"] = ldb.Dn(self.ldb_dc1, msgs[0]["serverReference"][0].decode('utf8'))
116 fsmo_info_1["rid_set_dn"] = ldb.Dn(self.ldb_dc1, "CN=RID Set") + fsmo_info_1["server_acct_dn"]
118 msgs = self.ldb_dc2.search(scope=ldb.SCOPE_BASE, base=fsmo_info_2["server_dn"], attrs=["serverReference"])
119 fsmo_info_2["server_acct_dn"] = ldb.Dn(self.ldb_dc2, msgs[0]["serverReference"][0].decode('utf8'))
120 fsmo_info_2["rid_set_dn"] = ldb.Dn(self.ldb_dc2, "CN=RID Set") + fsmo_info_2["server_acct_dn"]
122 # determine the owner dc
123 res = self.ldb_dc1.search(fsmo_obj_dn,
124 scope=SCOPE_BASE, attrs=["fSMORoleOwner"])
125 assert len(res) == 1, "Only one fSMORoleOwner value expected for %s!" % fsmo_obj_dn
126 fsmo_owner = res[0]["fSMORoleOwner"][0]
127 if fsmo_owner == self.info_dc1["dsServiceName"][0]:
128 return (fsmo_info_1, fsmo_info_2)
129 return (fsmo_info_2, fsmo_info_1)
131 def _check_exop_failed(self, ctr6, expected_failure):
132 self.assertEqual(ctr6.extended_ret, expected_failure)
133 #self.assertEqual(ctr6.object_count, 0)
134 #self.assertEqual(ctr6.first_object, None)
135 self.assertEqual(ctr6.more_data, False)
136 self.assertEqual(ctr6.nc_object_count, 0)
137 self.assertEqual(ctr6.nc_linked_attributes_count, 0)
138 self.assertEqual(ctr6.linked_attributes_count, 0)
139 self.assertEqual(ctr6.linked_attributes, [])
140 self.assertEqual(ctr6.drs_error[0], 0)
142 def test_do_single_repl(self):
144 Make sure that DRSUAPI_EXOP_REPL_OBJ never replicates more than
145 one object, even when we use DRS_GET_ANC/GET_TGT.
148 ou1 = "OU=get_anc1,%s" % self.ou
151 "objectclass": "organizationalUnit"
153 ou1_id = self._get_identifier(self.ldb_dc1, ou1)
154 ou2 = "OU=get_anc2,%s" % ou1
157 "objectclass": "organizationalUnit"
159 ou2_id = self._get_identifier(self.ldb_dc1, ou2)
160 dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
163 "objectclass": "computer",
164 "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
166 dc3_id = self._get_identifier(self.ldb_dc1, dc3)
168 # Add some linked attributes (for checking GET_TGT behaviour)
170 m.dn = ldb.Dn(self.ldb_dc2, ou1)
171 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_ADD, "managedBy")
172 self.ldb_dc1.modify(m)
173 ou1_link = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
174 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
175 ou1_id.guid, ou2_id.guid)
177 m.dn = ldb.Dn(self.ldb_dc2, dc3)
178 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_ADD, "managedBy")
179 self.ldb_dc1.modify(m)
180 dc3_link = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
181 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
182 dc3_id.guid, ou2_id.guid)
184 req = self._getnc_req10(dest_dsa=None,
185 invocation_id=self.ldb_dc1.get_invocation_id(),
187 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
188 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP,
189 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
190 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
191 self._check_ctr6(ctr, [ou1], expected_links=[ou1_link])
193 # DRSUAPI_DRS_WRIT_REP means that we should only replicate the dn we give (dc3).
194 # DRSUAPI_DRS_GET_ANC means that we should also replicate its ancestors, but
195 # Windows doesn't do this if we use both.
196 req = self._getnc_req10(dest_dsa=None,
197 invocation_id=self.ldb_dc1.get_invocation_id(),
199 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
200 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP |
201 drsuapi.DRSUAPI_DRS_GET_ANC,
202 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
203 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
204 self._check_ctr6(ctr, [dc3], expected_links=[dc3_link])
206 # Even though the ancestor of ou2 (ou1) has changed since last hwm, and we're
207 # sending DRSUAPI_DRS_GET_ANC, the expected response is that it will only try
208 # and replicate the single object still.
209 req = self._getnc_req10(dest_dsa=None,
210 invocation_id=self.ldb_dc1.get_invocation_id(),
212 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
213 replica_flags=drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
214 drsuapi.DRSUAPI_DRS_GET_ANC,
215 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
216 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
217 self._check_ctr6(ctr, [ou2])
219 def test_do_full_repl_on_ou(self):
221 Make sure that a full replication on a not-an-nc fails with
225 non_nc_ou = "OU=not-an-NC,%s" % self.ou
228 "objectclass": "organizationalUnit"
230 req8 = self._exop_req8(dest_dsa=None,
231 invocation_id=self.ldb_dc1.get_invocation_id(),
233 exop=drsuapi.DRSUAPI_EXOP_NONE,
234 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
237 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
238 self.fail("Expected DsGetNCChanges to fail with WERR_DS_CANT_FIND_EXPECTED_NC")
239 except WERRORError as e1:
240 (enum, estr) = e1.args
241 self.assertEqual(enum, werror.WERR_DS_CANT_FIND_EXPECTED_NC)
243 def test_link_utdv_hwm(self):
244 """Test verify the DRS_GET_ANC behavior."""
246 ou1 = "OU=get_anc1,%s" % self.ou
249 "objectclass": "organizationalUnit"
251 ou1_id = self._get_identifier(self.ldb_dc1, ou1)
252 ou2 = "OU=get_anc2,%s" % ou1
255 "objectclass": "organizationalUnit"
257 ou2_id = self._get_identifier(self.ldb_dc1, ou2)
258 dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
261 "objectclass": "computer",
262 "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
264 dc3_id = self._get_identifier(self.ldb_dc1, dc3)
266 (hwm1, utdv1) = self._check_replication([ou1, ou2, dc3],
267 drsuapi.DRSUAPI_DRS_WRIT_REP)
269 self._check_replication([ou1, ou2, dc3],
270 drsuapi.DRSUAPI_DRS_WRIT_REP |
271 drsuapi.DRSUAPI_DRS_GET_ANC)
273 self._check_replication([dc3],
274 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
276 self._check_replication([ou1, ou2, dc3],
277 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
278 drsuapi.DRSUAPI_DRS_GET_ANC)
281 m.dn = ldb.Dn(self.ldb_dc1, ou1)
282 m["displayName"] = ldb.MessageElement("OU1", ldb.FLAG_MOD_ADD, "displayName")
283 self.ldb_dc1.modify(m)
285 (hwm2, utdv2) = self._check_replication([ou2, dc3, ou1],
286 drsuapi.DRSUAPI_DRS_WRIT_REP)
288 self._check_replication([ou1, ou2, dc3],
289 drsuapi.DRSUAPI_DRS_WRIT_REP |
290 drsuapi.DRSUAPI_DRS_GET_ANC)
292 self._check_replication([dc3],
293 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
295 self._check_replication([ou1, ou2, dc3],
296 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
297 drsuapi.DRSUAPI_DRS_GET_ANC)
299 self._check_replication([ou1],
300 drsuapi.DRSUAPI_DRS_WRIT_REP,
303 self._check_replication([ou1],
304 drsuapi.DRSUAPI_DRS_WRIT_REP |
305 drsuapi.DRSUAPI_DRS_GET_ANC,
308 self._check_replication([ou1],
309 drsuapi.DRSUAPI_DRS_WRIT_REP |
310 drsuapi.DRSUAPI_DRS_GET_ANC,
311 uptodateness_vector=utdv1)
314 m.dn = ldb.Dn(self.ldb_dc1, ou2)
315 m["displayName"] = ldb.MessageElement("OU2", ldb.FLAG_MOD_ADD, "displayName")
316 self.ldb_dc1.modify(m)
318 (hwm3, utdv3) = self._check_replication([dc3, ou1, ou2],
319 drsuapi.DRSUAPI_DRS_WRIT_REP)
321 self._check_replication([ou1, ou2, dc3],
322 drsuapi.DRSUAPI_DRS_WRIT_REP |
323 drsuapi.DRSUAPI_DRS_GET_ANC)
325 self._check_replication([dc3],
326 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
328 self._check_replication([ou1, ou2, dc3],
329 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
330 drsuapi.DRSUAPI_DRS_GET_ANC)
332 self._check_replication([ou1, ou2],
333 drsuapi.DRSUAPI_DRS_WRIT_REP,
336 self._check_replication([ou1, ou2],
337 drsuapi.DRSUAPI_DRS_WRIT_REP |
338 drsuapi.DRSUAPI_DRS_GET_ANC,
341 self._check_replication([ou1, ou2],
342 drsuapi.DRSUAPI_DRS_WRIT_REP |
343 drsuapi.DRSUAPI_DRS_GET_ANC,
344 uptodateness_vector=utdv1)
347 m.dn = ldb.Dn(self.ldb_dc1, self.ou)
348 m["displayName"] = ldb.MessageElement("OU", ldb.FLAG_MOD_ADD, "displayName")
349 self.ldb_dc1.modify(m)
351 (hwm4, utdv4) = self._check_replication([dc3, ou1, ou2, self.ou],
352 drsuapi.DRSUAPI_DRS_WRIT_REP)
354 self._check_replication([self.ou, ou1, ou2, dc3],
355 drsuapi.DRSUAPI_DRS_WRIT_REP |
356 drsuapi.DRSUAPI_DRS_GET_ANC)
358 self._check_replication([dc3],
359 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
361 self._check_replication([self.ou, ou1, ou2, dc3],
362 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
363 drsuapi.DRSUAPI_DRS_GET_ANC)
365 self._check_replication([self.ou, ou2],
366 drsuapi.DRSUAPI_DRS_WRIT_REP |
367 drsuapi.DRSUAPI_DRS_GET_ANC,
368 uptodateness_vector=utdv2)
370 cn3 = "CN=get_anc3,%s" % ou2
373 "objectclass": "container",
375 cn3_id = self._get_identifier(self.ldb_dc1, cn3)
377 (hwm5, utdv5) = self._check_replication([dc3, ou1, ou2, self.ou, cn3],
378 drsuapi.DRSUAPI_DRS_WRIT_REP)
380 self._check_replication([self.ou, ou1, ou2, dc3, cn3],
381 drsuapi.DRSUAPI_DRS_WRIT_REP |
382 drsuapi.DRSUAPI_DRS_GET_ANC)
384 self._check_replication([dc3],
385 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
387 self._check_replication([self.ou, ou1, ou2, dc3],
388 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
389 drsuapi.DRSUAPI_DRS_GET_ANC)
392 m.dn = ldb.Dn(self.ldb_dc1, ou2)
393 m["managedBy"] = ldb.MessageElement(dc3, ldb.FLAG_MOD_ADD, "managedBy")
394 self.ldb_dc1.modify(m)
395 ou2_managedBy_dc3 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
396 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
397 ou2_id.guid, dc3_id.guid)
399 (hwm6, utdv6) = self._check_replication([dc3, ou1, self.ou, cn3, ou2],
400 drsuapi.DRSUAPI_DRS_WRIT_REP,
401 expected_links=[ou2_managedBy_dc3])
403 # Can fail against Windows due to equal precedence of dc3, cn3
404 self._check_replication([self.ou, ou1, ou2, dc3, cn3],
405 drsuapi.DRSUAPI_DRS_WRIT_REP |
406 drsuapi.DRSUAPI_DRS_GET_ANC,
407 expected_links=[ou2_managedBy_dc3])
409 self._check_replication([dc3],
410 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
412 self._check_replication([self.ou, ou1, ou2, dc3],
413 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
414 drsuapi.DRSUAPI_DRS_GET_ANC)
416 self._check_replication([],
417 drsuapi.DRSUAPI_DRS_WRIT_REP,
418 uptodateness_vector=utdv5,
419 expected_links=[ou2_managedBy_dc3])
421 self._check_replication([],
422 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
423 uptodateness_vector=utdv5)
425 self._check_replication([],
426 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
427 uptodateness_vector=utdv5)
430 m.dn = ldb.Dn(self.ldb_dc1, dc3)
431 m["managedBy"] = ldb.MessageElement(ou1, ldb.FLAG_MOD_ADD, "managedBy")
432 self.ldb_dc1.modify(m)
433 dc3_managedBy_ou1 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
434 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
435 dc3_id.guid, ou1_id.guid)
437 (hwm7, utdv7) = self._check_replication([ou1, self.ou, cn3, ou2, dc3],
438 drsuapi.DRSUAPI_DRS_WRIT_REP,
439 expected_links=[ou2_managedBy_dc3, dc3_managedBy_ou1])
441 # Can fail against Windows due to equal precedence of dc3, cn3
442 # self._check_replication([self.ou,ou1,ou2,dc3,cn3],
443 # drsuapi.DRSUAPI_DRS_WRIT_REP|
444 # drsuapi.DRSUAPI_DRS_GET_ANC,
445 # expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
447 self._check_replication([dc3],
448 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
449 expected_links=[dc3_managedBy_ou1])
451 self._check_replication([dc3],
452 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
453 expected_links=[dc3_managedBy_ou1])
455 self._check_replication([self.ou, ou1, ou2, dc3],
456 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
457 drsuapi.DRSUAPI_DRS_GET_ANC,
458 expected_links=[dc3_managedBy_ou1])
460 # GET_TGT seems to override DRS_CRITICAL_ONLY and also returns any
461 # object(s) that relate to the linked attributes (similar to GET_ANC)
462 self._check_replication([ou1, dc3],
463 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
464 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
465 expected_links=[dc3_managedBy_ou1], dn_ordered=False)
467 # Change DC3's managedBy to OU2 instead of OU1
468 # Note that the OU1 managedBy linked attribute will still exist as
469 # a tombstone object (and so will be returned in the replication still)
471 m.dn = ldb.Dn(self.ldb_dc1, dc3)
472 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_REPLACE, "managedBy")
473 self.ldb_dc1.modify(m)
474 dc3_managedBy_ou1.flags &= ~drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
475 dc3_managedBy_ou2 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
476 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
477 dc3_id.guid, ou2_id.guid)
479 (hwm8, utdv8) = self._check_replication([ou1, self.ou, cn3, ou2, dc3],
480 drsuapi.DRSUAPI_DRS_WRIT_REP,
481 expected_links=[ou2_managedBy_dc3, dc3_managedBy_ou1, dc3_managedBy_ou2])
483 # Can fail against Windows due to equal precedence of dc3, cn3
484 # self._check_replication([self.ou,ou1,ou2,dc3,cn3],
485 # drsuapi.DRSUAPI_DRS_WRIT_REP|
486 # drsuapi.DRSUAPI_DRS_GET_ANC,
487 # expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
489 self._check_replication([dc3],
490 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
491 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2])
493 self._check_replication([self.ou, ou1, ou2, dc3],
494 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
495 drsuapi.DRSUAPI_DRS_GET_ANC,
496 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2])
498 # GET_TGT will also return any DNs referenced by the linked attributes
499 # (including the Tombstone attribute)
500 self._check_replication([ou1, ou2, dc3],
501 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
502 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
503 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2], dn_ordered=False)
505 # Use the highwater-mark prior to changing ManagedBy - this should
506 # only return the old/Tombstone and new linked attributes (we already
508 self._check_replication([],
509 drsuapi.DRSUAPI_DRS_WRIT_REP,
510 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
513 self._check_replication([],
514 drsuapi.DRSUAPI_DRS_WRIT_REP |
515 drsuapi.DRSUAPI_DRS_GET_ANC,
516 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
519 self._check_replication([],
520 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
521 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
524 self._check_replication([],
525 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
526 drsuapi.DRSUAPI_DRS_GET_ANC,
527 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
530 self._check_replication([],
531 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
532 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
533 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
536 # Repeat the above set of tests using the uptodateness_vector
537 # instead of the highwater-mark
538 self._check_replication([],
539 drsuapi.DRSUAPI_DRS_WRIT_REP,
540 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
541 uptodateness_vector=utdv7)
543 self._check_replication([],
544 drsuapi.DRSUAPI_DRS_WRIT_REP |
545 drsuapi.DRSUAPI_DRS_GET_ANC,
546 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
547 uptodateness_vector=utdv7)
549 self._check_replication([],
550 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
551 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
552 uptodateness_vector=utdv7)
554 self._check_replication([],
555 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
556 drsuapi.DRSUAPI_DRS_GET_ANC,
557 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
558 uptodateness_vector=utdv7)
560 self._check_replication([],
561 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
562 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
563 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
564 uptodateness_vector=utdv7)
566 def test_FSMONotOwner(self):
567 """Test role transfer with against DC not owner of the role"""
568 fsmo_dn = self.ldb_dc1.get_schema_basedn()
569 (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
571 req8 = self._exop_req8(dest_dsa=fsmo_owner["ntds_guid"],
572 invocation_id=fsmo_not_owner["invocation_id"],
574 exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
576 (drs, drs_handle) = self._ds_bind(fsmo_not_owner["dns_name"])
577 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
578 self.assertEqual(level, 6, "Expected level 6 response!")
579 self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER)
580 self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_not_owner["ntds_guid"]))
581 self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_not_owner["invocation_id"]))
583 def test_InvalidDestDSA(self):
584 """Test role transfer with invalid destination DSA guid"""
585 fsmo_dn = self.ldb_dc1.get_schema_basedn()
586 (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
588 req8 = self._exop_req8(dest_dsa="9c637462-5b8c-4467-aef2-bdb1f57bc4ef",
589 invocation_id=fsmo_owner["invocation_id"],
591 exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
593 (drs, drs_handle) = self._ds_bind(fsmo_owner["dns_name"])
594 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
595 self.assertEqual(level, 6, "Expected level 6 response!")
596 self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_UNKNOWN_CALLER)
597 self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_owner["ntds_guid"]))
598 self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_owner["invocation_id"]))
601 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
603 super(DrsReplicaPrefixMapTestCase, self).setUp()
604 self.base_dn = self.ldb_dc1.get_default_basedn()
605 self.ou = "ou=pfm_exop,%s" % self.base_dn
608 "objectclass": "organizationalUnit"})
609 self.user = "cn=testuser,%s" % self.ou
612 "objectclass": "user"})
615 super(DrsReplicaPrefixMapTestCase, self).tearDown()
617 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
618 except ldb.LdbError as e2:
619 (enum, string) = e2.args
620 if enum == ldb.ERR_NO_SUCH_OBJECT:
623 def test_missing_prefix_map_dsa(self):
624 partial_attribute_set = self.get_partial_attribute_set()
626 dc_guid_1 = self.ldb_dc1.get_invocation_id()
628 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
630 req8 = self._exop_req8(dest_dsa=None,
631 invocation_id=dc_guid_1,
633 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
634 partial_attribute_set=partial_attribute_set)
637 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
638 self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
640 self.fail("Missing prefixmap shouldn't have triggered an error")
642 def test_invalid_prefix_map_attid(self):
643 # Request for invalid attid
644 partial_attribute_set = self.get_partial_attribute_set([99999])
646 dc_guid_1 = self.ldb_dc1.get_invocation_id()
647 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
650 pfm = self._samdb_fetch_pfm_and_schi()
652 # On Windows, prefixMap isn't available over LDAP
653 req8 = self._exop_req8(dest_dsa=None,
654 invocation_id=dc_guid_1,
656 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
657 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
658 pfm = ctr.mapping_ctr
660 req8 = self._exop_req8(dest_dsa=None,
661 invocation_id=dc_guid_1,
663 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
664 partial_attribute_set=partial_attribute_set,
668 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
669 self.fail("Invalid attid (99999) should have triggered an error")
670 except RuntimeError as e3:
671 (ecode, emsg) = e3.args
672 self.assertEqual(ecode, 0x000020E2, "Error code should have been "
673 "WERR_DS_DRA_SCHEMA_MISMATCH")
675 def test_secret_prefix_map_attid(self):
676 # Request for a secret attid
677 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
679 dc_guid_1 = self.ldb_dc1.get_invocation_id()
680 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
683 pfm = self._samdb_fetch_pfm_and_schi()
685 # On Windows, prefixMap isn't available over LDAP
686 req8 = self._exop_req8(dest_dsa=None,
687 invocation_id=dc_guid_1,
689 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
690 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
691 pfm = ctr.mapping_ctr
693 req8 = self._exop_req8(dest_dsa=None,
694 invocation_id=dc_guid_1,
696 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
697 partial_attribute_set=partial_attribute_set,
700 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
703 for attr in ctr.first_object.object.attribute_ctr.attributes:
704 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
708 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
710 for i, mapping in enumerate(pfm.mappings):
712 # objectClass: 2.5.4.0
713 if mapping.oid.binary_oid == [85, 4]:
715 # OID: 1.2.840.113556.1.4.*
716 # unicodePwd: 1.2.840.113556.1.4.90
717 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
720 (pfm.mappings[idx1].id_prefix,
721 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
722 pfm.mappings[idx1].id_prefix)
725 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
728 # 90 for unicodePwd (with new prefix = 0)
729 # 589824, 589827 for objectClass and CN
730 # Use of three ensures sorting is correct
731 partial_attribute_set = self.get_partial_attribute_set([90, 589824, 589827])
732 req8 = self._exop_req8(dest_dsa=None,
733 invocation_id=dc_guid_1,
735 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
736 partial_attribute_set=partial_attribute_set,
739 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
742 for attr in ctr.first_object.object.attribute_ctr.attributes:
743 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
747 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
749 def test_regular_prefix_map_attid(self):
750 # Request for a regular (non-secret) attid
751 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
753 dc_guid_1 = self.ldb_dc1.get_invocation_id()
754 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
757 pfm = self._samdb_fetch_pfm_and_schi()
759 # On Windows, prefixMap isn't available over LDAP
760 req8 = self._exop_req8(dest_dsa=None,
761 invocation_id=dc_guid_1,
763 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
764 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
765 pfm = ctr.mapping_ctr
767 req8 = self._exop_req8(dest_dsa=None,
768 invocation_id=dc_guid_1,
770 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
771 partial_attribute_set=partial_attribute_set,
774 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
777 for attr in ctr.first_object.object.attribute_ctr.attributes:
778 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
782 self.assertTrue(found, "Ensure we get the name attribute back")
784 for i, mapping in enumerate(pfm.mappings):
786 # objectClass: 2.5.4.0
787 if mapping.oid.binary_oid == [85, 4]:
789 # OID: 1.2.840.113556.1.4.*
790 # name: 1.2.840.113556.1.4.1
791 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
794 (pfm.mappings[idx1].id_prefix,
795 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
796 pfm.mappings[idx1].id_prefix)
799 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
802 # 1 for name (with new prefix = 0)
803 partial_attribute_set = self.get_partial_attribute_set([1])
804 req8 = self._exop_req8(dest_dsa=None,
805 invocation_id=dc_guid_1,
807 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
808 partial_attribute_set=partial_attribute_set,
811 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
814 for attr in ctr.first_object.object.attribute_ctr.attributes:
815 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
819 self.assertTrue(found, "Ensure we get the name attribute back")
821 def test_regular_prefix_map_ex_attid(self):
822 # Request for a regular (non-secret) attid
823 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
824 partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
826 dc_guid_1 = self.ldb_dc1.get_invocation_id()
827 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
830 pfm = self._samdb_fetch_pfm_and_schi()
832 # On Windows, prefixMap isn't available over LDAP
833 req8 = self._exop_req8(dest_dsa=None,
834 invocation_id=dc_guid_1,
836 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
837 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
838 pfm = ctr.mapping_ctr
840 req8 = self._exop_req8(dest_dsa=None,
841 invocation_id=dc_guid_1,
843 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
844 partial_attribute_set=partial_attribute_set,
845 partial_attribute_set_ex=partial_attribute_set_ex,
848 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
851 for attr in ctr.first_object.object.attribute_ctr.attributes:
852 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
856 self.assertTrue(found, "Ensure we get the name attribute back")
859 for attr in ctr.first_object.object.attribute_ctr.attributes:
860 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
864 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
866 for i, mapping in enumerate(pfm.mappings):
868 # objectClass: 2.5.4.0
869 if mapping.oid.binary_oid == [85, 4]:
871 # OID: 1.2.840.113556.1.4.*
872 # name: 1.2.840.113556.1.4.1
873 # unicodePwd: 1.2.840.113556.1.4.90
874 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
877 (pfm.mappings[idx1].id_prefix,
878 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
879 pfm.mappings[idx1].id_prefix)
882 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
885 # 1 for name (with new prefix = 0)
886 partial_attribute_set = self.get_partial_attribute_set([1])
887 # 90 for unicodePwd (with new prefix = 0)
888 # HOWEVER: Windows doesn't seem to respect incoming maps for PartialAttrSetEx
889 partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
890 req8 = self._exop_req8(dest_dsa=None,
891 invocation_id=dc_guid_1,
893 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
894 partial_attribute_set=partial_attribute_set,
895 partial_attribute_set_ex=partial_attribute_set_ex,
898 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
901 for attr in ctr.first_object.object.attribute_ctr.attributes:
902 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
906 self.assertTrue(found, "Ensure we get the name attribute back")
909 for attr in ctr.first_object.object.attribute_ctr.attributes:
910 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
914 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
916 def _samdb_fetch_pfm_and_schi(self):
917 """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
919 res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
920 attrs=["prefixMap", "schemaInfo"])
922 pfm = ndr_unpack(drsblobs.prefixMapBlob,
923 res[0]['prefixMap'][0])
925 schi = drsuapi.DsReplicaOIDMapping()
927 if 'schemaInfo' in res[0]:
928 binary_oid = [x if isinstance(x, int) else ord(x) for x in res[0]['schemaInfo'][0]]
929 schi.oid.length = len(binary_oid)
930 schi.oid.binary_oid = binary_oid
932 schema_info = drsblobs.schemaInfoBlob()
933 schema_info.revision = 0
934 schema_info.marker = 0xFF
935 schema_info.invocation_id = misc.GUID(samdb.get_invocation_id())
937 binary_oid = [x if isinstance(x, int) else ord(x) for x in ndr_pack(schema_info)]
938 # you have to set the length before setting binary_oid
939 schi.oid.length = len(binary_oid)
940 schi.oid.binary_oid = binary_oid
942 pfm.ctr.mappings = pfm.ctr.mappings + [schi]
943 pfm.ctr.num_mappings += 1
947 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
949 super(DrsReplicaSyncSortTestCase, self).setUp()
950 self.base_dn = self.ldb_dc1.get_default_basedn()
951 self.ou = "ou=sort_exop,%s" % self.base_dn
954 "objectclass": "organizationalUnit"})
957 super(DrsReplicaSyncSortTestCase, self).tearDown()
958 # tidyup groups and users
960 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
961 except ldb.LdbError as e4:
962 (enum, string) = e4.args
963 if enum == ldb.ERR_NO_SUCH_OBJECT:
966 def add_linked_attribute(self, src, dest, attr='member'):
968 m.dn = ldb.Dn(self.ldb_dc1, src)
969 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
970 self.ldb_dc1.modify(m)
972 def remove_linked_attribute(self, src, dest, attr='member'):
974 m.dn = ldb.Dn(self.ldb_dc1, src)
975 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
976 self.ldb_dc1.modify(m)
978 def test_sort_behaviour_single_object(self):
979 """Testing sorting behaviour on single objects"""
981 user1_dn = "cn=test_user1,%s" % self.ou
982 user2_dn = "cn=test_user2,%s" % self.ou
983 user3_dn = "cn=test_user3,%s" % self.ou
984 group_dn = "cn=test_group,%s" % self.ou
986 self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
987 self.ldb_dc1.add({"dn": user2_dn, "objectclass": "user"})
988 self.ldb_dc1.add({"dn": user3_dn, "objectclass": "user"})
989 self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
991 u1_guid = misc.GUID(self.ldb_dc1.search(base=user1_dn,
992 attrs=["objectGUID"])[0]['objectGUID'][0])
993 u2_guid = misc.GUID(self.ldb_dc1.search(base=user2_dn,
994 attrs=["objectGUID"])[0]['objectGUID'][0])
995 u3_guid = misc.GUID(self.ldb_dc1.search(base=user3_dn,
996 attrs=["objectGUID"])[0]['objectGUID'][0])
997 g_guid = misc.GUID(self.ldb_dc1.search(base=group_dn,
998 attrs=["objectGUID"])[0]['objectGUID'][0])
1000 self.add_linked_attribute(group_dn, user1_dn,
1002 self.add_linked_attribute(group_dn, user2_dn,
1004 self.add_linked_attribute(group_dn, user3_dn,
1006 self.add_linked_attribute(group_dn, user1_dn,
1008 self.add_linked_attribute(group_dn, user2_dn,
1009 attr='nonSecurityMember')
1010 self.add_linked_attribute(group_dn, user3_dn,
1011 attr='nonSecurityMember')
1013 set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1014 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1017 expected_links = set([set_inactive,
1018 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1019 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
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_managedBy,
1031 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1034 AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1035 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1040 dc_guid_1 = self.ldb_dc1.get_invocation_id()
1042 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1044 req8 = self._exop_req8(dest_dsa=None,
1045 invocation_id=dc_guid_1,
1047 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
1049 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1052 for link in ctr.linked_attributes:
1053 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1054 link.value.blob).guid
1055 no_inactive.append((link, target_guid))
1056 self.assertTrue(AbstractLink(link.attid, link.flags,
1057 link.identifier.guid,
1058 target_guid) in expected_links)
1060 no_inactive.sort(key=cmp_to_key(_linked_attribute_compare))
1062 # assert the two arrays are the same
1063 self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1064 self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)
1066 self.remove_linked_attribute(group_dn, user3_dn,
1067 attr='nonSecurityMember')
1069 # Set the link inactive
1070 expected_links.remove(set_inactive)
1071 set_inactive.flags = 0
1072 expected_links.add(set_inactive)
1075 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1076 for link in ctr.linked_attributes:
1077 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1078 link.value.blob).guid
1079 has_inactive.append((link, target_guid))
1080 self.assertTrue(AbstractLink(link.attid, link.flags,
1081 link.identifier.guid,
1082 target_guid) in expected_links)
1084 has_inactive.sort(key=cmp_to_key(_linked_attribute_compare))
1086 # assert the two arrays are the same
1087 self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1088 self.assertEqual([x[0] for x in has_inactive], ctr.linked_attributes)
1090 def test_sort_behaviour_ncchanges(self):
1091 """Testing sorting behaviour on a group of objects."""
1092 user1_dn = "cn=test_user1,%s" % self.ou
1093 group_dn = "cn=test_group,%s" % self.ou
1094 self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
1095 self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
1097 self.add_linked_attribute(group_dn, user1_dn,
1100 dc_guid_1 = self.ldb_dc1.get_invocation_id()
1102 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1104 # Make sure the max objects count is high enough
1105 req8 = self._exop_req8(dest_dsa=None,
1106 invocation_id=dc_guid_1,
1107 nc_dn_str=self.base_dn,
1110 exop=drsuapi.DRSUAPI_EXOP_NONE)
1112 # Loop until we get linked attributes, or we get to the end.
1113 # Samba sends linked attributes at the end, unlike Windows.
1115 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1116 if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1118 req8.highwatermark = ctr.new_highwatermark
1120 self.assertTrue(ctr.linked_attributes_count != 0)
1123 for link in ctr.linked_attributes:
1125 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1126 link.value.blob).guid
1128 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1129 link.value.blob).guid
1130 no_inactive.append((link, target_guid))
1132 no_inactive.sort(key=cmp_to_key(_linked_attribute_compare))
1134 # assert the two arrays are the same
1135 self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)