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
47 def _linked_attribute_compare(la1, la2):
48 """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
52 # Ascending host object GUID
53 c = cmp(ndr_pack(la1.identifier.guid), ndr_pack(la2.identifier.guid))
57 # Ascending attribute ID
58 if la1.attid != la2.attid:
59 return -1 if la1.attid < la2.attid else 1
61 la1_active = la1.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
62 la2_active = la2.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
64 # Ascending 'is present'
65 if la1_active != la2_active:
66 return 1 if la1_active else -1
68 # Ascending target object GUID
69 return cmp(ndr_pack(la1_target), ndr_pack(la2_target))
72 class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
73 """Intended as a semi-black box test case for DsGetNCChanges
74 implementation for extended operations. It should be testing
75 how DsGetNCChanges handles different input params (mostly invalid).
76 Final goal is to make DsGetNCChanges as binary compatible to
77 Windows implementation as possible"""
80 super(DrsReplicaSyncTestCase, self).setUp()
81 self.base_dn = self.ldb_dc1.get_default_basedn()
82 self.ou = "OU=test_getncchanges,%s" % self.base_dn
85 "objectclass": "organizationalUnit"})
86 (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
87 (self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.ldb_dc1)
91 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
92 except ldb.LdbError as (enum, string):
93 if enum == ldb.ERR_NO_SUCH_OBJECT:
95 super(DrsReplicaSyncTestCase, self).tearDown()
97 def _determine_fSMORoleOwner(self, fsmo_obj_dn):
98 """Returns (owner, not_owner) pair where:
99 owner: dns name for FSMO owner
100 not_owner: dns name for DC not owning the FSMO"""
101 # collect info to return later
102 fsmo_info_1 = {"dns_name": self.dnsname_dc1,
103 "invocation_id": self.ldb_dc1.get_invocation_id(),
104 "ntds_guid": self.ldb_dc1.get_ntds_GUID(),
105 "server_dn": self.ldb_dc1.get_serverName()}
106 fsmo_info_2 = {"dns_name": self.dnsname_dc2,
107 "invocation_id": self.ldb_dc2.get_invocation_id(),
108 "ntds_guid": self.ldb_dc2.get_ntds_GUID(),
109 "server_dn": self.ldb_dc2.get_serverName()}
111 msgs = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=fsmo_info_1["server_dn"], attrs=["serverReference"])
112 fsmo_info_1["server_acct_dn"] = ldb.Dn(self.ldb_dc1, msgs[0]["serverReference"][0])
113 fsmo_info_1["rid_set_dn"] = ldb.Dn(self.ldb_dc1, "CN=RID Set") + fsmo_info_1["server_acct_dn"]
115 msgs = self.ldb_dc2.search(scope=ldb.SCOPE_BASE, base=fsmo_info_2["server_dn"], attrs=["serverReference"])
116 fsmo_info_2["server_acct_dn"] = ldb.Dn(self.ldb_dc2, msgs[0]["serverReference"][0])
117 fsmo_info_2["rid_set_dn"] = ldb.Dn(self.ldb_dc2, "CN=RID Set") + fsmo_info_2["server_acct_dn"]
119 # determine the owner dc
120 res = self.ldb_dc1.search(fsmo_obj_dn,
121 scope=SCOPE_BASE, attrs=["fSMORoleOwner"])
122 assert len(res) == 1, "Only one fSMORoleOwner value expected for %s!"%fsmo_obj_dn
123 fsmo_owner = res[0]["fSMORoleOwner"][0]
124 if fsmo_owner == self.info_dc1["dsServiceName"][0]:
125 return (fsmo_info_1, fsmo_info_2)
126 return (fsmo_info_2, fsmo_info_1)
128 def _check_exop_failed(self, ctr6, expected_failure):
129 self.assertEqual(ctr6.extended_ret, expected_failure)
130 #self.assertEqual(ctr6.object_count, 0)
131 #self.assertEqual(ctr6.first_object, None)
132 self.assertEqual(ctr6.more_data, False)
133 self.assertEqual(ctr6.nc_object_count, 0)
134 self.assertEqual(ctr6.nc_linked_attributes_count, 0)
135 self.assertEqual(ctr6.linked_attributes_count, 0)
136 self.assertEqual(ctr6.linked_attributes, [])
137 self.assertEqual(ctr6.drs_error[0], 0)
139 def test_do_single_repl(self):
141 Make sure that DRSU_EXOP_REPL_OBJ never replicates more than
142 one object, even when we use DRS_GET_ANC.
145 ou1 = "OU=get_anc1,%s" % self.ou
148 "objectclass": "organizationalUnit"
150 ou1_id = self._get_identifier(self.ldb_dc1, ou1)
151 ou2 = "OU=get_anc2,%s" % ou1
154 "objectclass": "organizationalUnit"
156 ou2_id = self._get_identifier(self.ldb_dc1, ou2)
157 dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
160 "objectclass": "computer",
161 "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
163 dc3_id = self._get_identifier(self.ldb_dc1, dc3)
165 req8 = self._exop_req8(dest_dsa=None,
166 invocation_id=self.ldb_dc1.get_invocation_id(),
168 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
169 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
170 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
171 self._check_ctr6(ctr, [ou1])
173 # DRSUAPI_DRS_WRIT_REP means that we should only replicate the dn we give (dc3).
174 # DRSUAPI_DRS_GET_ANC means that we should also replicate its ancestors, but
175 # Windows doesn't do this if we use both.
176 req8 = self._exop_req8(dest_dsa=None,
177 invocation_id=self.ldb_dc1.get_invocation_id(),
179 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
180 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP |
181 drsuapi.DRSUAPI_DRS_GET_ANC)
182 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
183 self._check_ctr6(ctr, [dc3])
185 # Even though the ancestor of ou2 (ou1) has changed since last hwm, and we're
186 # sending DRSUAPI_DRS_GET_ANC, the expected response is that it will only try
187 # and replicate the single object still.
188 req8 = self._exop_req8(dest_dsa=None,
189 invocation_id=self.ldb_dc1.get_invocation_id(),
191 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
192 replica_flags=drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
193 drsuapi.DRSUAPI_DRS_GET_ANC)
194 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
195 self._check_ctr6(ctr, [ou2])
197 def test_do_full_repl_on_ou(self):
199 Make sure that a full replication on a not-an-nc fails with
203 non_nc_ou = "OU=not-an-NC,%s" % self.ou
206 "objectclass": "organizationalUnit"
208 req8 = self._exop_req8(dest_dsa=None,
209 invocation_id=self.ldb_dc1.get_invocation_id(),
211 exop=drsuapi.DRSUAPI_EXOP_NONE,
212 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
215 (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
216 self.fail("Expected DsGetNCChanges to fail with WERR_DS_CANT_FIND_EXPECTED_NC")
217 except WERRORError as (enum, estr):
218 self.assertEquals(enum, werror.WERR_DS_CANT_FIND_EXPECTED_NC)
220 def test_link_utdv_hwm(self):
221 """Test verify the DRS_GET_ANC behavior."""
223 ou1 = "OU=get_anc1,%s" % self.ou
226 "objectclass": "organizationalUnit"
228 ou1_id = self._get_identifier(self.ldb_dc1, ou1)
229 ou2 = "OU=get_anc2,%s" % ou1
232 "objectclass": "organizationalUnit"
234 ou2_id = self._get_identifier(self.ldb_dc1, ou2)
235 dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
238 "objectclass": "computer",
239 "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
241 dc3_id = self._get_identifier(self.ldb_dc1, dc3)
243 (hwm1, utdv1) = self._check_replication([ou1,ou2,dc3],
244 drsuapi.DRSUAPI_DRS_WRIT_REP)
246 self._check_replication([ou1,ou2,dc3],
247 drsuapi.DRSUAPI_DRS_WRIT_REP|
248 drsuapi.DRSUAPI_DRS_GET_ANC)
250 self._check_replication([dc3],
251 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
253 self._check_replication([ou1,ou2,dc3],
254 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
255 drsuapi.DRSUAPI_DRS_GET_ANC)
258 m.dn = ldb.Dn(self.ldb_dc1, ou1)
259 m["displayName"] = ldb.MessageElement("OU1", ldb.FLAG_MOD_ADD, "displayName")
260 self.ldb_dc1.modify(m)
262 (hwm2, utdv2) = self._check_replication([ou2,dc3,ou1],
263 drsuapi.DRSUAPI_DRS_WRIT_REP)
265 self._check_replication([ou1,ou2,dc3],
266 drsuapi.DRSUAPI_DRS_WRIT_REP|
267 drsuapi.DRSUAPI_DRS_GET_ANC)
269 self._check_replication([dc3],
270 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
272 self._check_replication([ou1,ou2,dc3],
273 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
274 drsuapi.DRSUAPI_DRS_GET_ANC)
276 self._check_replication([ou1],
277 drsuapi.DRSUAPI_DRS_WRIT_REP,
280 self._check_replication([ou1],
281 drsuapi.DRSUAPI_DRS_WRIT_REP|
282 drsuapi.DRSUAPI_DRS_GET_ANC,
285 self._check_replication([ou1],
286 drsuapi.DRSUAPI_DRS_WRIT_REP|
287 drsuapi.DRSUAPI_DRS_GET_ANC,
288 uptodateness_vector=utdv1)
291 m.dn = ldb.Dn(self.ldb_dc1, ou2)
292 m["displayName"] = ldb.MessageElement("OU2", ldb.FLAG_MOD_ADD, "displayName")
293 self.ldb_dc1.modify(m)
295 (hwm3, utdv3) = self._check_replication([dc3,ou1,ou2],
296 drsuapi.DRSUAPI_DRS_WRIT_REP)
298 self._check_replication([ou1,ou2,dc3],
299 drsuapi.DRSUAPI_DRS_WRIT_REP|
300 drsuapi.DRSUAPI_DRS_GET_ANC)
302 self._check_replication([dc3],
303 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
305 self._check_replication([ou1,ou2,dc3],
306 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
307 drsuapi.DRSUAPI_DRS_GET_ANC)
309 self._check_replication([ou1,ou2],
310 drsuapi.DRSUAPI_DRS_WRIT_REP,
313 self._check_replication([ou1,ou2],
314 drsuapi.DRSUAPI_DRS_WRIT_REP|
315 drsuapi.DRSUAPI_DRS_GET_ANC,
318 self._check_replication([ou1,ou2],
319 drsuapi.DRSUAPI_DRS_WRIT_REP|
320 drsuapi.DRSUAPI_DRS_GET_ANC,
321 uptodateness_vector=utdv1)
324 m.dn = ldb.Dn(self.ldb_dc1, self.ou)
325 m["displayName"] = ldb.MessageElement("OU", ldb.FLAG_MOD_ADD, "displayName")
326 self.ldb_dc1.modify(m)
328 (hwm4, utdv4) = self._check_replication([dc3,ou1,ou2,self.ou],
329 drsuapi.DRSUAPI_DRS_WRIT_REP)
331 self._check_replication([self.ou,ou1,ou2,dc3],
332 drsuapi.DRSUAPI_DRS_WRIT_REP|
333 drsuapi.DRSUAPI_DRS_GET_ANC)
335 self._check_replication([dc3],
336 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
338 self._check_replication([self.ou,ou1,ou2,dc3],
339 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
340 drsuapi.DRSUAPI_DRS_GET_ANC)
342 self._check_replication([self.ou,ou2],
343 drsuapi.DRSUAPI_DRS_WRIT_REP|
344 drsuapi.DRSUAPI_DRS_GET_ANC,
345 uptodateness_vector=utdv2)
347 cn3 = "CN=get_anc3,%s" % ou2
350 "objectclass": "container",
352 cn3_id = self._get_identifier(self.ldb_dc1, cn3)
354 (hwm5, utdv5) = self._check_replication([dc3,ou1,ou2,self.ou,cn3],
355 drsuapi.DRSUAPI_DRS_WRIT_REP)
357 self._check_replication([self.ou,ou1,ou2,dc3,cn3],
358 drsuapi.DRSUAPI_DRS_WRIT_REP|
359 drsuapi.DRSUAPI_DRS_GET_ANC)
361 self._check_replication([dc3],
362 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
364 self._check_replication([self.ou,ou1,ou2,dc3],
365 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
366 drsuapi.DRSUAPI_DRS_GET_ANC)
369 m.dn = ldb.Dn(self.ldb_dc1, ou2)
370 m["managedBy"] = ldb.MessageElement(dc3, ldb.FLAG_MOD_ADD, "managedBy")
371 self.ldb_dc1.modify(m)
372 ou2_managedBy_dc3 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
373 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
374 ou2_id.guid, dc3_id.guid)
376 (hwm6, utdv6) = self._check_replication([dc3,ou1,self.ou,cn3,ou2],
377 drsuapi.DRSUAPI_DRS_WRIT_REP,
378 expected_links=[ou2_managedBy_dc3])
380 # Can fail against Windows due to equal precedence of dc3, cn3
381 self._check_replication([self.ou,ou1,ou2,dc3,cn3],
382 drsuapi.DRSUAPI_DRS_WRIT_REP|
383 drsuapi.DRSUAPI_DRS_GET_ANC,
384 expected_links=[ou2_managedBy_dc3])
386 self._check_replication([dc3],
387 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
389 self._check_replication([self.ou,ou1,ou2,dc3],
390 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
391 drsuapi.DRSUAPI_DRS_GET_ANC)
393 self._check_replication([],
394 drsuapi.DRSUAPI_DRS_WRIT_REP,
395 uptodateness_vector=utdv5,
396 expected_links=[ou2_managedBy_dc3])
398 self._check_replication([],
399 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
400 uptodateness_vector=utdv5)
402 self._check_replication([],
403 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
404 uptodateness_vector=utdv5)
407 m.dn = ldb.Dn(self.ldb_dc1, dc3)
408 m["managedBy"] = ldb.MessageElement(ou1, ldb.FLAG_MOD_ADD, "managedBy")
409 self.ldb_dc1.modify(m)
410 dc3_managedBy_ou1 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
411 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
412 dc3_id.guid, ou1_id.guid)
414 (hwm7, utdv7) = self._check_replication([ou1,self.ou,cn3,ou2,dc3],
415 drsuapi.DRSUAPI_DRS_WRIT_REP,
416 expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
418 # Can fail against Windows due to equal precedence of dc3, cn3
419 #self._check_replication([self.ou,ou1,ou2,dc3,cn3],
420 # drsuapi.DRSUAPI_DRS_WRIT_REP|
421 # drsuapi.DRSUAPI_DRS_GET_ANC,
422 # expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
424 self._check_replication([dc3],
425 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
426 expected_links=[dc3_managedBy_ou1])
428 self._check_replication([dc3],
429 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
430 expected_links=[dc3_managedBy_ou1])
432 self._check_replication([self.ou,ou1,ou2,dc3],
433 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
434 drsuapi.DRSUAPI_DRS_GET_ANC,
435 expected_links=[dc3_managedBy_ou1])
437 # GET_TGT seems to override DRS_CRITICAL_ONLY and also returns any
438 # object(s) that relate to the linked attributes (similar to GET_ANC)
439 self._check_replication([ou1, dc3],
440 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
441 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
442 expected_links=[dc3_managedBy_ou1], dn_ordered=False)
444 # Change DC3's managedBy to OU2 instead of OU1
445 # Note that the OU1 managedBy linked attribute will still exist as
446 # a tombstone object (and so will be returned in the replication still)
448 m.dn = ldb.Dn(self.ldb_dc1, dc3)
449 m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_REPLACE, "managedBy")
450 self.ldb_dc1.modify(m)
451 dc3_managedBy_ou1.flags &= ~drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
452 dc3_managedBy_ou2 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
453 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
454 dc3_id.guid, ou2_id.guid)
456 (hwm8, utdv8) = self._check_replication([ou1,self.ou,cn3,ou2,dc3],
457 drsuapi.DRSUAPI_DRS_WRIT_REP,
458 expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
460 # Can fail against Windows due to equal precedence of dc3, cn3
461 #self._check_replication([self.ou,ou1,ou2,dc3,cn3],
462 # drsuapi.DRSUAPI_DRS_WRIT_REP|
463 # drsuapi.DRSUAPI_DRS_GET_ANC,
464 # expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
466 self._check_replication([dc3],
467 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
468 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2])
470 self._check_replication([self.ou,ou1,ou2,dc3],
471 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
472 drsuapi.DRSUAPI_DRS_GET_ANC,
473 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2])
475 # GET_TGT will also return any DNs referenced by the linked attributes
476 # (including the Tombstone attribute)
477 self._check_replication([ou1, ou2, dc3],
478 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
479 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
480 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2], dn_ordered=False)
482 # Use the highwater-mark prior to changing ManagedBy - this should
483 # only return the old/Tombstone and new linked attributes (we already
485 self._check_replication([],
486 drsuapi.DRSUAPI_DRS_WRIT_REP,
487 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
490 self._check_replication([],
491 drsuapi.DRSUAPI_DRS_WRIT_REP|
492 drsuapi.DRSUAPI_DRS_GET_ANC,
493 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
496 self._check_replication([],
497 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
498 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
501 self._check_replication([],
502 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
503 drsuapi.DRSUAPI_DRS_GET_ANC,
504 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
507 self._check_replication([],
508 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
509 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
510 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
513 # Repeat the above set of tests using the uptodateness_vector
514 # instead of the highwater-mark
515 self._check_replication([],
516 drsuapi.DRSUAPI_DRS_WRIT_REP,
517 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
518 uptodateness_vector=utdv7)
520 self._check_replication([],
521 drsuapi.DRSUAPI_DRS_WRIT_REP|
522 drsuapi.DRSUAPI_DRS_GET_ANC,
523 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
524 uptodateness_vector=utdv7)
526 self._check_replication([],
527 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
528 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
529 uptodateness_vector=utdv7)
531 self._check_replication([],
532 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
533 drsuapi.DRSUAPI_DRS_GET_ANC,
534 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
535 uptodateness_vector=utdv7)
537 self._check_replication([],
538 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
539 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
540 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
541 uptodateness_vector=utdv7)
543 def test_FSMONotOwner(self):
544 """Test role transfer with against DC not owner of the role"""
545 fsmo_dn = self.ldb_dc1.get_schema_basedn()
546 (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
548 req8 = self._exop_req8(dest_dsa=fsmo_owner["ntds_guid"],
549 invocation_id=fsmo_not_owner["invocation_id"],
551 exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
553 (drs, drs_handle) = self._ds_bind(fsmo_not_owner["dns_name"])
554 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
555 self.assertEqual(level, 6, "Expected level 6 response!")
556 self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER)
557 self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_not_owner["ntds_guid"]))
558 self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_not_owner["invocation_id"]))
560 def test_InvalidDestDSA(self):
561 """Test role transfer with invalid destination DSA guid"""
562 fsmo_dn = self.ldb_dc1.get_schema_basedn()
563 (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
565 req8 = self._exop_req8(dest_dsa="9c637462-5b8c-4467-aef2-bdb1f57bc4ef",
566 invocation_id=fsmo_owner["invocation_id"],
568 exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
570 (drs, drs_handle) = self._ds_bind(fsmo_owner["dns_name"])
571 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
572 self.assertEqual(level, 6, "Expected level 6 response!")
573 self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_UNKNOWN_CALLER)
574 self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_owner["ntds_guid"]))
575 self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_owner["invocation_id"]))
577 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
579 super(DrsReplicaPrefixMapTestCase, self).setUp()
580 self.base_dn = self.ldb_dc1.get_default_basedn()
581 self.ou = "ou=pfm_exop,%s" % self.base_dn
584 "objectclass": "organizationalUnit"})
585 self.user = "cn=testuser,%s" % self.ou
588 "objectclass": "user"})
591 super(DrsReplicaPrefixMapTestCase, self).tearDown()
593 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
594 except ldb.LdbError as (enum, string):
595 if enum == ldb.ERR_NO_SUCH_OBJECT:
598 def test_missing_prefix_map_dsa(self):
599 partial_attribute_set = self.get_partial_attribute_set()
601 dc_guid_1 = self.ldb_dc1.get_invocation_id()
603 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
605 req8 = self._exop_req8(dest_dsa=None,
606 invocation_id=dc_guid_1,
608 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
609 partial_attribute_set=partial_attribute_set)
612 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
613 self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
615 self.fail("Missing prefixmap shouldn't have triggered an error")
617 def test_invalid_prefix_map_attid(self):
618 # Request for invalid attid
619 partial_attribute_set = self.get_partial_attribute_set([99999])
621 dc_guid_1 = self.ldb_dc1.get_invocation_id()
622 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
625 pfm = self._samdb_fetch_pfm_and_schi()
627 # On Windows, prefixMap isn't available over LDAP
628 req8 = self._exop_req8(dest_dsa=None,
629 invocation_id=dc_guid_1,
631 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
632 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
633 pfm = ctr.mapping_ctr
635 req8 = self._exop_req8(dest_dsa=None,
636 invocation_id=dc_guid_1,
638 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
639 partial_attribute_set=partial_attribute_set,
643 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
644 self.fail("Invalid attid (99999) should have triggered an error")
645 except RuntimeError as (ecode, emsg):
646 self.assertEqual(ecode, 0x000020E2, "Error code should have been "
647 "WERR_DS_DRA_SCHEMA_MISMATCH")
649 def test_secret_prefix_map_attid(self):
650 # Request for a secret attid
651 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
653 dc_guid_1 = self.ldb_dc1.get_invocation_id()
654 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
657 pfm = self._samdb_fetch_pfm_and_schi()
659 # On Windows, prefixMap isn't available over LDAP
660 req8 = self._exop_req8(dest_dsa=None,
661 invocation_id=dc_guid_1,
663 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
664 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
665 pfm = ctr.mapping_ctr
668 req8 = self._exop_req8(dest_dsa=None,
669 invocation_id=dc_guid_1,
671 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
672 partial_attribute_set=partial_attribute_set,
675 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
678 for attr in ctr.first_object.object.attribute_ctr.attributes:
679 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
683 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
685 for i, mapping in enumerate(pfm.mappings):
687 # objectClass: 2.5.4.0
688 if mapping.oid.binary_oid == [85, 4]:
690 # OID: 1.2.840.113556.1.4.*
691 # unicodePwd: 1.2.840.113556.1.4.90
692 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
695 (pfm.mappings[idx1].id_prefix,
696 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
697 pfm.mappings[idx1].id_prefix)
700 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
703 # 90 for unicodePwd (with new prefix = 0)
704 # 589824, 589827 for objectClass and CN
705 # Use of three ensures sorting is correct
706 partial_attribute_set = self.get_partial_attribute_set([90, 589824, 589827])
707 req8 = self._exop_req8(dest_dsa=None,
708 invocation_id=dc_guid_1,
710 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
711 partial_attribute_set=partial_attribute_set,
714 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
717 for attr in ctr.first_object.object.attribute_ctr.attributes:
718 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
722 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
724 def test_regular_prefix_map_attid(self):
725 # Request for a regular (non-secret) attid
726 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
728 dc_guid_1 = self.ldb_dc1.get_invocation_id()
729 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
732 pfm = self._samdb_fetch_pfm_and_schi()
734 # On Windows, prefixMap isn't available over LDAP
735 req8 = self._exop_req8(dest_dsa=None,
736 invocation_id=dc_guid_1,
738 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
739 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
740 pfm = ctr.mapping_ctr
743 req8 = self._exop_req8(dest_dsa=None,
744 invocation_id=dc_guid_1,
746 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
747 partial_attribute_set=partial_attribute_set,
750 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
753 for attr in ctr.first_object.object.attribute_ctr.attributes:
754 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
758 self.assertTrue(found, "Ensure we get the name attribute back")
760 for i, mapping in enumerate(pfm.mappings):
762 # objectClass: 2.5.4.0
763 if mapping.oid.binary_oid == [85, 4]:
765 # OID: 1.2.840.113556.1.4.*
766 # name: 1.2.840.113556.1.4.1
767 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
770 (pfm.mappings[idx1].id_prefix,
771 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
772 pfm.mappings[idx1].id_prefix)
775 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
778 # 1 for name (with new prefix = 0)
779 partial_attribute_set = self.get_partial_attribute_set([1])
780 req8 = self._exop_req8(dest_dsa=None,
781 invocation_id=dc_guid_1,
783 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
784 partial_attribute_set=partial_attribute_set,
787 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
790 for attr in ctr.first_object.object.attribute_ctr.attributes:
791 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
795 self.assertTrue(found, "Ensure we get the name attribute back")
797 def test_regular_prefix_map_ex_attid(self):
798 # Request for a regular (non-secret) attid
799 partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
800 partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
802 dc_guid_1 = self.ldb_dc1.get_invocation_id()
803 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
806 pfm = self._samdb_fetch_pfm_and_schi()
808 # On Windows, prefixMap isn't available over LDAP
809 req8 = self._exop_req8(dest_dsa=None,
810 invocation_id=dc_guid_1,
812 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
813 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
814 pfm = ctr.mapping_ctr
817 req8 = self._exop_req8(dest_dsa=None,
818 invocation_id=dc_guid_1,
820 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
821 partial_attribute_set=partial_attribute_set,
822 partial_attribute_set_ex=partial_attribute_set_ex,
825 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
828 for attr in ctr.first_object.object.attribute_ctr.attributes:
829 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
833 self.assertTrue(found, "Ensure we get the name attribute back")
836 for attr in ctr.first_object.object.attribute_ctr.attributes:
837 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
841 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
843 for i, mapping in enumerate(pfm.mappings):
845 # objectClass: 2.5.4.0
846 if mapping.oid.binary_oid == [85, 4]:
848 # OID: 1.2.840.113556.1.4.*
849 # name: 1.2.840.113556.1.4.1
850 # unicodePwd: 1.2.840.113556.1.4.90
851 elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
854 (pfm.mappings[idx1].id_prefix,
855 pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
856 pfm.mappings[idx1].id_prefix)
859 tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
862 # 1 for name (with new prefix = 0)
863 partial_attribute_set = self.get_partial_attribute_set([1])
864 # 90 for unicodePwd (with new prefix = 0)
865 # HOWEVER: Windows doesn't seem to respect incoming maps for PartialAttrSetEx
866 partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
867 req8 = self._exop_req8(dest_dsa=None,
868 invocation_id=dc_guid_1,
870 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
871 partial_attribute_set=partial_attribute_set,
872 partial_attribute_set_ex=partial_attribute_set_ex,
875 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
878 for attr in ctr.first_object.object.attribute_ctr.attributes:
879 if attr.attid == drsuapi.DRSUAPI_ATTID_name:
883 self.assertTrue(found, "Ensure we get the name attribute back")
886 for attr in ctr.first_object.object.attribute_ctr.attributes:
887 if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
891 self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
893 def _samdb_fetch_pfm_and_schi(self):
894 """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
896 res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
897 attrs=["prefixMap", "schemaInfo"])
899 pfm = ndr_unpack(drsblobs.prefixMapBlob,
900 str(res[0]['prefixMap']))
902 schi = drsuapi.DsReplicaOIDMapping()
905 if 'schemaInfo' in res[0]:
906 schi.oid.length = len(map(ord, str(res[0]['schemaInfo'])))
907 schi.oid.binary_oid = map(ord, str(res[0]['schemaInfo']))
909 schema_info = drsblobs.schemaInfoBlob()
910 schema_info.revision = 0
911 schema_info.marker = 0xFF
912 schema_info.invocation_id = misc.GUID(samdb.get_invocation_id())
913 schi.oid.length = len(map(ord, ndr_pack(schema_info)))
914 schi.oid.binary_oid = map(ord, ndr_pack(schema_info))
916 pfm.ctr.mappings = pfm.ctr.mappings + [schi]
917 pfm.ctr.num_mappings += 1
920 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
922 super(DrsReplicaSyncSortTestCase, self).setUp()
923 self.base_dn = self.ldb_dc1.get_default_basedn()
924 self.ou = "ou=sort_exop,%s" % self.base_dn
927 "objectclass": "organizationalUnit"})
930 super(DrsReplicaSyncSortTestCase, self).tearDown()
931 # tidyup groups and users
933 self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
934 except ldb.LdbError as (enum, string):
935 if enum == ldb.ERR_NO_SUCH_OBJECT:
938 def add_linked_attribute(self, src, dest, attr='member'):
940 m.dn = ldb.Dn(self.ldb_dc1, src)
941 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
942 self.ldb_dc1.modify(m)
944 def remove_linked_attribute(self, src, dest, attr='member'):
946 m.dn = ldb.Dn(self.ldb_dc1, src)
947 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
948 self.ldb_dc1.modify(m)
950 def test_sort_behaviour_single_object(self):
951 """Testing sorting behaviour on single objects"""
953 user1_dn = "cn=test_user1,%s" % self.ou
954 user2_dn = "cn=test_user2,%s" % self.ou
955 user3_dn = "cn=test_user3,%s" % self.ou
956 group_dn = "cn=test_group,%s" % self.ou
958 self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
959 self.ldb_dc1.add({"dn": user2_dn, "objectclass": "user"})
960 self.ldb_dc1.add({"dn": user3_dn, "objectclass": "user"})
961 self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
963 u1_guid = misc.GUID(self.ldb_dc1.search(base=user1_dn,
964 attrs=["objectGUID"])[0]['objectGUID'][0])
965 u2_guid = misc.GUID(self.ldb_dc1.search(base=user2_dn,
966 attrs=["objectGUID"])[0]['objectGUID'][0])
967 u3_guid = misc.GUID(self.ldb_dc1.search(base=user3_dn,
968 attrs=["objectGUID"])[0]['objectGUID'][0])
969 g_guid = misc.GUID(self.ldb_dc1.search(base=group_dn,
970 attrs=["objectGUID"])[0]['objectGUID'][0])
972 self.add_linked_attribute(group_dn, user1_dn,
974 self.add_linked_attribute(group_dn, user2_dn,
976 self.add_linked_attribute(group_dn, user3_dn,
978 self.add_linked_attribute(group_dn, user1_dn,
980 self.add_linked_attribute(group_dn, user2_dn,
981 attr='nonSecurityMember')
982 self.add_linked_attribute(group_dn, user3_dn,
983 attr='nonSecurityMember')
985 set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
986 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
989 expected_links = set([set_inactive,
990 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
991 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
994 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
995 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
998 AbstractLink(drsuapi.DRSUAPI_ATTID_member,
999 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1002 AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
1003 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1006 AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1007 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1012 dc_guid_1 = self.ldb_dc1.get_invocation_id()
1014 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1016 req8 = self._exop_req8(dest_dsa=None,
1017 invocation_id=dc_guid_1,
1019 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
1021 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1024 for link in ctr.linked_attributes:
1025 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1026 link.value.blob).guid
1027 no_inactive.append((link, target_guid))
1028 self.assertTrue(AbstractLink(link.attid, link.flags,
1029 link.identifier.guid,
1030 target_guid) in expected_links)
1032 no_inactive.sort(cmp=_linked_attribute_compare)
1034 # assert the two arrays are the same
1035 self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1036 self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)
1038 self.remove_linked_attribute(group_dn, user3_dn,
1039 attr='nonSecurityMember')
1041 # Set the link inactive
1042 expected_links.remove(set_inactive)
1043 set_inactive.flags = 0
1044 expected_links.add(set_inactive)
1047 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1048 for link in ctr.linked_attributes:
1049 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1050 link.value.blob).guid
1051 has_inactive.append((link, target_guid))
1052 self.assertTrue(AbstractLink(link.attid, link.flags,
1053 link.identifier.guid,
1054 target_guid) in expected_links)
1056 has_inactive.sort(cmp=_linked_attribute_compare)
1058 # assert the two arrays are the same
1059 self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1060 self.assertEqual([x[0] for x in has_inactive], ctr.linked_attributes)
1062 def test_sort_behaviour_ncchanges(self):
1063 """Testing sorting behaviour on a group of objects."""
1064 user1_dn = "cn=test_user1,%s" % self.ou
1065 group_dn = "cn=test_group,%s" % self.ou
1066 self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
1067 self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
1069 self.add_linked_attribute(group_dn, user1_dn,
1072 dc_guid_1 = self.ldb_dc1.get_invocation_id()
1074 drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1076 # Make sure the max objects count is high enough
1077 req8 = self._exop_req8(dest_dsa=None,
1078 invocation_id=dc_guid_1,
1079 nc_dn_str=self.base_dn,
1082 exop=drsuapi.DRSUAPI_EXOP_NONE)
1084 # Loop until we get linked attributes, or we get to the end.
1085 # Samba sends linked attributes at the end, unlike Windows.
1087 (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1088 if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1090 req8.highwatermark = ctr.new_highwatermark
1092 self.assertTrue(ctr.linked_attributes_count != 0)
1095 for link in ctr.linked_attributes:
1097 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1098 link.value.blob).guid
1100 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1101 link.value.blob).guid
1102 no_inactive.append((link, target_guid))
1104 no_inactive.sort(cmp=_linked_attribute_compare)
1106 # assert the two arrays are the same
1107 self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)