PEP8: fix E128: continuation line under-indented for visual indent
[bbaumbach/samba-autobuild/.git] / source4 / torture / drs / python / getnc_exop.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Tests various schema replication scenarios
5 #
6 # Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2011
7 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2016
8 #
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.
13 #
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.
18 #
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/>.
21 #
22
23 #
24 # Usage:
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"
29 #
30
31 import random
32
33 import drs_base
34 from drs_base import AbstractLink
35
36 import samba.tests
37 import random
38 from samba import werror, WERRORError
39
40 import ldb
41 from ldb import SCOPE_BASE
42
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
48
49 def _linked_attribute_compare(la1, la2):
50     """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
51     la1, la1_target = la1
52     la2, la2_target = la2
53
54     # Ascending host object GUID
55     c = cmp_fn(ndr_pack(la1.identifier.guid), ndr_pack(la2.identifier.guid))
56     if c != 0:
57         return c
58
59     # Ascending attribute ID
60     if la1.attid != la2.attid:
61         return -1 if la1.attid < la2.attid else 1
62
63     la1_active = la1.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
64     la2_active = la2.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
65
66     # Ascending 'is present'
67     if la1_active != la2_active:
68         return 1 if la1_active else -1
69
70     # Ascending target object GUID
71     return cmp_fn(ndr_pack(la1_target), ndr_pack(la2_target))
72
73
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"""
80
81     def setUp(self):
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
85         self.ldb_dc1.add({
86             "dn": self.ou,
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)
90
91     def tearDown(self):
92         try:
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:
97                 pass
98         super(DrsReplicaSyncTestCase, self).tearDown()
99
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()}
113
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"]
117
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"]
121
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)
130
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)
141
142     def test_do_single_repl(self):
143         """
144         Make sure that DRSUAPI_EXOP_REPL_OBJ never replicates more than
145         one object, even when we use DRS_GET_ANC/GET_TGT.
146         """
147
148         ou1 = "OU=get_anc1,%s" % self.ou
149         self.ldb_dc1.add({
150             "dn": ou1,
151             "objectclass": "organizationalUnit"
152         })
153         ou1_id = self._get_identifier(self.ldb_dc1, ou1)
154         ou2 = "OU=get_anc2,%s" % ou1
155         self.ldb_dc1.add({
156             "dn": ou2,
157             "objectclass": "organizationalUnit"
158         })
159         ou2_id = self._get_identifier(self.ldb_dc1, ou2)
160         dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
161         self.ldb_dc1.add({
162             "dn": dc3,
163             "objectclass": "computer",
164             "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
165         })
166         dc3_id = self._get_identifier(self.ldb_dc1, dc3)
167
168         # Add some linked attributes (for checking GET_TGT behaviour)
169         m = ldb.Message()
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)
176
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)
183
184         req = self._getnc_req10(dest_dsa=None,
185                                 invocation_id=self.ldb_dc1.get_invocation_id(),
186                                 nc_dn_str=ou1,
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])
192
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(),
198                                 nc_dn_str=dc3,
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])
205
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(),
211                                 nc_dn_str=ou2,
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])
218
219     def test_do_full_repl_on_ou(self):
220         """
221         Make sure that a full replication on a not-an-nc fails with
222         the right error code
223         """
224
225         non_nc_ou = "OU=not-an-NC,%s" % self.ou
226         self.ldb_dc1.add({
227             "dn": non_nc_ou,
228             "objectclass": "organizationalUnit"
229         })
230         req8 = self._exop_req8(dest_dsa=None,
231                                invocation_id=self.ldb_dc1.get_invocation_id(),
232                                nc_dn_str=non_nc_ou,
233                                exop=drsuapi.DRSUAPI_EXOP_NONE,
234                                replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
235
236         try:
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.assertEquals(enum, werror.WERR_DS_CANT_FIND_EXPECTED_NC)
242
243     def test_link_utdv_hwm(self):
244         """Test verify the DRS_GET_ANC behavior."""
245
246         ou1 = "OU=get_anc1,%s" % self.ou
247         self.ldb_dc1.add({
248             "dn": ou1,
249             "objectclass": "organizationalUnit"
250         })
251         ou1_id = self._get_identifier(self.ldb_dc1, ou1)
252         ou2 = "OU=get_anc2,%s" % ou1
253         self.ldb_dc1.add({
254             "dn": ou2,
255             "objectclass": "organizationalUnit"
256         })
257         ou2_id = self._get_identifier(self.ldb_dc1, ou2)
258         dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
259         self.ldb_dc1.add({
260             "dn": dc3,
261             "objectclass": "computer",
262             "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
263         })
264         dc3_id = self._get_identifier(self.ldb_dc1, dc3)
265
266         (hwm1, utdv1) = self._check_replication([ou1,ou2,dc3],
267                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
268
269         self._check_replication([ou1,ou2,dc3],
270                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
271                                 drsuapi.DRSUAPI_DRS_GET_ANC)
272
273         self._check_replication([dc3],
274                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
275
276         self._check_replication([ou1,ou2,dc3],
277                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
278                                 drsuapi.DRSUAPI_DRS_GET_ANC)
279
280         m = ldb.Message()
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)
284
285         (hwm2, utdv2) = self._check_replication([ou2,dc3,ou1],
286                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
287
288         self._check_replication([ou1,ou2,dc3],
289                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
290                                 drsuapi.DRSUAPI_DRS_GET_ANC)
291
292         self._check_replication([dc3],
293                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
294
295         self._check_replication([ou1,ou2,dc3],
296                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
297                                 drsuapi.DRSUAPI_DRS_GET_ANC)
298
299         self._check_replication([ou1],
300                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
301                                 highwatermark=hwm1)
302
303         self._check_replication([ou1],
304                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
305                                 drsuapi.DRSUAPI_DRS_GET_ANC,
306                                 highwatermark=hwm1)
307
308         self._check_replication([ou1],
309                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
310                                 drsuapi.DRSUAPI_DRS_GET_ANC,
311                                 uptodateness_vector=utdv1)
312
313         m = ldb.Message()
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)
317
318         (hwm3, utdv3) = self._check_replication([dc3,ou1,ou2],
319                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
320
321         self._check_replication([ou1,ou2,dc3],
322                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
323                                 drsuapi.DRSUAPI_DRS_GET_ANC)
324
325         self._check_replication([dc3],
326                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
327
328         self._check_replication([ou1,ou2,dc3],
329                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
330                                 drsuapi.DRSUAPI_DRS_GET_ANC)
331
332         self._check_replication([ou1,ou2],
333                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
334                                 highwatermark=hwm1)
335
336         self._check_replication([ou1,ou2],
337                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
338                                 drsuapi.DRSUAPI_DRS_GET_ANC,
339                                 highwatermark=hwm1)
340
341         self._check_replication([ou1,ou2],
342                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
343                                 drsuapi.DRSUAPI_DRS_GET_ANC,
344                                 uptodateness_vector=utdv1)
345
346         m = ldb.Message()
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)
350
351         (hwm4, utdv4) = self._check_replication([dc3,ou1,ou2,self.ou],
352                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
353
354         self._check_replication([self.ou,ou1,ou2,dc3],
355                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
356                                 drsuapi.DRSUAPI_DRS_GET_ANC)
357
358         self._check_replication([dc3],
359                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
360
361         self._check_replication([self.ou,ou1,ou2,dc3],
362                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
363                                 drsuapi.DRSUAPI_DRS_GET_ANC)
364
365         self._check_replication([self.ou,ou2],
366                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
367                                 drsuapi.DRSUAPI_DRS_GET_ANC,
368                                 uptodateness_vector=utdv2)
369
370         cn3 = "CN=get_anc3,%s" % ou2
371         self.ldb_dc1.add({
372             "dn": cn3,
373             "objectclass": "container",
374         })
375         cn3_id = self._get_identifier(self.ldb_dc1, cn3)
376
377         (hwm5, utdv5) = self._check_replication([dc3,ou1,ou2,self.ou,cn3],
378                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
379
380         self._check_replication([self.ou,ou1,ou2,dc3,cn3],
381                                 drsuapi.DRSUAPI_DRS_WRIT_REP|
382                                 drsuapi.DRSUAPI_DRS_GET_ANC)
383
384         self._check_replication([dc3],
385                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
386
387         self._check_replication([self.ou,ou1,ou2,dc3],
388                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
389                                 drsuapi.DRSUAPI_DRS_GET_ANC)
390
391         m = ldb.Message()
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)
398
399         (hwm6, utdv6) = self._check_replication([dc3,ou1,self.ou,cn3,ou2],
400                                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
401                                                 expected_links=[ou2_managedBy_dc3])
402
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])
408
409         self._check_replication([dc3],
410                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
411
412         self._check_replication([self.ou,ou1,ou2,dc3],
413                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
414                                 drsuapi.DRSUAPI_DRS_GET_ANC)
415
416         self._check_replication([],
417                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
418                                 uptodateness_vector=utdv5,
419                                 expected_links=[ou2_managedBy_dc3])
420
421         self._check_replication([],
422                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
423                                 uptodateness_vector=utdv5)
424
425         self._check_replication([],
426                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
427                                 uptodateness_vector=utdv5)
428
429         m = ldb.Message()
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)
436
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])
440
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])
446
447         self._check_replication([dc3],
448                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
449                                 expected_links=[dc3_managedBy_ou1])
450
451         self._check_replication([dc3],
452                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
453                                 expected_links=[dc3_managedBy_ou1])
454
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])
459
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)
466
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)
470         m = ldb.Message()
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)
478
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])
482
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])
488
489         self._check_replication([dc3],
490                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
491                                 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2])
492
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])
497
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)
504
505         # Use the highwater-mark prior to changing ManagedBy - this should
506         # only return the old/Tombstone and new linked attributes (we already
507         # know all the DNs)
508         self._check_replication([],
509                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
510                                 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
511                                 highwatermark=hwm7)
512
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],
517                                 highwatermark=hwm7)
518
519         self._check_replication([],
520                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
521                                 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
522                                 highwatermark=hwm7)
523
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],
528                                 highwatermark=hwm7)
529
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],
534                                 highwatermark=hwm7)
535
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)
542
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)
548
549         self._check_replication([],
550                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
551                                 expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
552                                 uptodateness_vector=utdv7)
553
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)
559
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)
565
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)
570
571         req8 = self._exop_req8(dest_dsa=fsmo_owner["ntds_guid"],
572                                invocation_id=fsmo_not_owner["invocation_id"],
573                                nc_dn_str=fsmo_dn,
574                                exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
575
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"]))
582
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)
587
588         req8 = self._exop_req8(dest_dsa="9c637462-5b8c-4467-aef2-bdb1f57bc4ef",
589                                invocation_id=fsmo_owner["invocation_id"],
590                                nc_dn_str=fsmo_dn,
591                                exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
592
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"]))
599
600 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
601     def setUp(self):
602         super(DrsReplicaPrefixMapTestCase, self).setUp()
603         self.base_dn = self.ldb_dc1.get_default_basedn()
604         self.ou = "ou=pfm_exop,%s" % self.base_dn
605         self.ldb_dc1.add({
606             "dn": self.ou,
607             "objectclass": "organizationalUnit"})
608         self.user = "cn=testuser,%s" % self.ou
609         self.ldb_dc1.add({
610             "dn": self.user,
611             "objectclass": "user"})
612
613     def tearDown(self):
614         super(DrsReplicaPrefixMapTestCase, self).tearDown()
615         try:
616             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
617         except ldb.LdbError as e2:
618             (enum, string) = e2.args
619             if enum == ldb.ERR_NO_SUCH_OBJECT:
620                 pass
621
622     def test_missing_prefix_map_dsa(self):
623         partial_attribute_set = self.get_partial_attribute_set()
624
625         dc_guid_1 = self.ldb_dc1.get_invocation_id()
626
627         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
628
629         req8 = self._exop_req8(dest_dsa=None,
630                                invocation_id=dc_guid_1,
631                                nc_dn_str=self.user,
632                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
633                                partial_attribute_set=partial_attribute_set)
634
635         try:
636             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
637             self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
638         except RuntimeError:
639             self.fail("Missing prefixmap shouldn't have triggered an error")
640
641     def test_invalid_prefix_map_attid(self):
642         # Request for invalid attid
643         partial_attribute_set = self.get_partial_attribute_set([99999])
644
645         dc_guid_1 = self.ldb_dc1.get_invocation_id()
646         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
647
648         try:
649             pfm = self._samdb_fetch_pfm_and_schi()
650         except KeyError:
651             # On Windows, prefixMap isn't available over LDAP
652             req8 = self._exop_req8(dest_dsa=None,
653                                    invocation_id=dc_guid_1,
654                                    nc_dn_str=self.user,
655                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
656             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
657             pfm = ctr.mapping_ctr
658
659         req8 = self._exop_req8(dest_dsa=None,
660                                invocation_id=dc_guid_1,
661                                nc_dn_str=self.user,
662                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
663                                partial_attribute_set=partial_attribute_set,
664                                mapping_ctr=pfm)
665
666         try:
667             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
668             self.fail("Invalid attid (99999) should have triggered an error")
669         except RuntimeError as e3:
670             (ecode, emsg) = e3.args
671             self.assertEqual(ecode, 0x000020E2, "Error code should have been "
672                              "WERR_DS_DRA_SCHEMA_MISMATCH")
673
674     def test_secret_prefix_map_attid(self):
675         # Request for a secret attid
676         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
677
678         dc_guid_1 = self.ldb_dc1.get_invocation_id()
679         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
680
681         try:
682             pfm = self._samdb_fetch_pfm_and_schi()
683         except KeyError:
684             # On Windows, prefixMap isn't available over LDAP
685             req8 = self._exop_req8(dest_dsa=None,
686                                    invocation_id=dc_guid_1,
687                                    nc_dn_str=self.user,
688                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
689             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
690             pfm = ctr.mapping_ctr
691
692
693         req8 = self._exop_req8(dest_dsa=None,
694                                invocation_id=dc_guid_1,
695                                nc_dn_str=self.user,
696                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
697                                partial_attribute_set=partial_attribute_set,
698                                mapping_ctr=pfm)
699
700         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
701
702         found = False
703         for attr in ctr.first_object.object.attribute_ctr.attributes:
704             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
705                 found = True
706                 break
707
708         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
709
710         for i, mapping in enumerate(pfm.mappings):
711             # OID: 2.5.4.*
712             # objectClass: 2.5.4.0
713             if mapping.oid.binary_oid == [85, 4]:
714                 idx1 = i
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]:
718                 idx2 = i
719
720         (pfm.mappings[idx1].id_prefix,
721          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
722                                           pfm.mappings[idx1].id_prefix)
723
724         tmp = pfm.mappings
725         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
726         pfm.mappings = tmp
727
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,
734                                nc_dn_str=self.user,
735                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
736                                partial_attribute_set=partial_attribute_set,
737                                mapping_ctr=pfm)
738
739         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
740
741         found = False
742         for attr in ctr.first_object.object.attribute_ctr.attributes:
743             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
744                 found = True
745                 break
746
747         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
748
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])
752
753         dc_guid_1 = self.ldb_dc1.get_invocation_id()
754         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
755
756         try:
757             pfm = self._samdb_fetch_pfm_and_schi()
758         except KeyError:
759             # On Windows, prefixMap isn't available over LDAP
760             req8 = self._exop_req8(dest_dsa=None,
761                                    invocation_id=dc_guid_1,
762                                    nc_dn_str=self.user,
763                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
764             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
765             pfm = ctr.mapping_ctr
766
767
768         req8 = self._exop_req8(dest_dsa=None,
769                                invocation_id=dc_guid_1,
770                                nc_dn_str=self.user,
771                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
772                                partial_attribute_set=partial_attribute_set,
773                                mapping_ctr=pfm)
774
775         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
776
777         found = False
778         for attr in ctr.first_object.object.attribute_ctr.attributes:
779             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
780                 found = True
781                 break
782
783         self.assertTrue(found, "Ensure we get the name attribute back")
784
785         for i, mapping in enumerate(pfm.mappings):
786             # OID: 2.5.4.*
787             # objectClass: 2.5.4.0
788             if mapping.oid.binary_oid == [85, 4]:
789                 idx1 = i
790             # OID: 1.2.840.113556.1.4.*
791             # name: 1.2.840.113556.1.4.1
792             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
793                 idx2 = i
794
795         (pfm.mappings[idx1].id_prefix,
796          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
797                                           pfm.mappings[idx1].id_prefix)
798
799         tmp = pfm.mappings
800         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
801         pfm.mappings = tmp
802
803         # 1 for name (with new prefix = 0)
804         partial_attribute_set = self.get_partial_attribute_set([1])
805         req8 = self._exop_req8(dest_dsa=None,
806                                invocation_id=dc_guid_1,
807                                nc_dn_str=self.user,
808                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
809                                partial_attribute_set=partial_attribute_set,
810                                mapping_ctr=pfm)
811
812         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
813
814         found = False
815         for attr in ctr.first_object.object.attribute_ctr.attributes:
816             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
817                 found = True
818                 break
819
820         self.assertTrue(found, "Ensure we get the name attribute back")
821
822     def test_regular_prefix_map_ex_attid(self):
823         # Request for a regular (non-secret) attid
824         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
825         partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
826
827         dc_guid_1 = self.ldb_dc1.get_invocation_id()
828         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
829
830         try:
831             pfm = self._samdb_fetch_pfm_and_schi()
832         except KeyError:
833             # On Windows, prefixMap isn't available over LDAP
834             req8 = self._exop_req8(dest_dsa=None,
835                                    invocation_id=dc_guid_1,
836                                    nc_dn_str=self.user,
837                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
838             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
839             pfm = ctr.mapping_ctr
840
841
842         req8 = self._exop_req8(dest_dsa=None,
843                                invocation_id=dc_guid_1,
844                                nc_dn_str=self.user,
845                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
846                                partial_attribute_set=partial_attribute_set,
847                                partial_attribute_set_ex=partial_attribute_set_ex,
848                                mapping_ctr=pfm)
849
850         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
851
852         found = False
853         for attr in ctr.first_object.object.attribute_ctr.attributes:
854             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
855                 found = True
856                 break
857
858         self.assertTrue(found, "Ensure we get the name attribute back")
859
860         found = False
861         for attr in ctr.first_object.object.attribute_ctr.attributes:
862             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
863                 found = True
864                 break
865
866         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
867
868         for i, mapping in enumerate(pfm.mappings):
869             # OID: 2.5.4.*
870             # objectClass: 2.5.4.0
871             if mapping.oid.binary_oid == [85, 4]:
872                 idx1 = i
873             # OID: 1.2.840.113556.1.4.*
874             # name: 1.2.840.113556.1.4.1
875             # unicodePwd: 1.2.840.113556.1.4.90
876             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
877                 idx2 = i
878
879         (pfm.mappings[idx1].id_prefix,
880          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
881                                           pfm.mappings[idx1].id_prefix)
882
883         tmp = pfm.mappings
884         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
885         pfm.mappings = tmp
886
887         # 1 for name (with new prefix = 0)
888         partial_attribute_set = self.get_partial_attribute_set([1])
889         # 90 for unicodePwd (with new prefix = 0)
890         # HOWEVER: Windows doesn't seem to respect incoming maps for PartialAttrSetEx
891         partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
892         req8 = self._exop_req8(dest_dsa=None,
893                                invocation_id=dc_guid_1,
894                                nc_dn_str=self.user,
895                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
896                                partial_attribute_set=partial_attribute_set,
897                                partial_attribute_set_ex=partial_attribute_set_ex,
898                                mapping_ctr=pfm)
899
900         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
901
902         found = False
903         for attr in ctr.first_object.object.attribute_ctr.attributes:
904             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
905                 found = True
906                 break
907
908         self.assertTrue(found, "Ensure we get the name attribute back")
909
910         found = False
911         for attr in ctr.first_object.object.attribute_ctr.attributes:
912             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
913                 found = True
914                 break
915
916         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
917
918     def _samdb_fetch_pfm_and_schi(self):
919         """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
920         samdb = self.ldb_dc1
921         res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
922                            attrs=["prefixMap", "schemaInfo"])
923
924         pfm = ndr_unpack(drsblobs.prefixMapBlob,
925                          str(res[0]['prefixMap']))
926
927         schi = drsuapi.DsReplicaOIDMapping()
928         schi.id_prefix = 0
929         if 'schemaInfo' in res[0]:
930             binary_oid = [x if isinstance(x, int) else ord(x) for x in res[0]['schemaInfo'][0]]
931             schi.oid.length = len(binary_oid)
932             schi.oid.binary_oid = binary_oid
933         else:
934             schema_info = drsblobs.schemaInfoBlob()
935             schema_info.revision = 0
936             schema_info.marker = 0xFF
937             schema_info.invocation_id = misc.GUID(samdb.get_invocation_id())
938
939             binary_oid = [x if isinstance(x, int) else ord(x) for x in ndr_pack(schema_info)]
940             # you have to set the length before setting binary_oid
941             schi.oid.length = len(binary_oid)
942             schi.oid.binary_oid = binary_oid
943
944         pfm.ctr.mappings = pfm.ctr.mappings + [schi]
945         pfm.ctr.num_mappings += 1
946         return pfm.ctr
947
948 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
949     def setUp(self):
950         super(DrsReplicaSyncSortTestCase, self).setUp()
951         self.base_dn = self.ldb_dc1.get_default_basedn()
952         self.ou = "ou=sort_exop,%s" % self.base_dn
953         self.ldb_dc1.add({
954             "dn": self.ou,
955             "objectclass": "organizationalUnit"})
956
957     def tearDown(self):
958         super(DrsReplicaSyncSortTestCase, self).tearDown()
959         # tidyup groups and users
960         try:
961             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
962         except ldb.LdbError as e4:
963             (enum, string) = e4.args
964             if enum == ldb.ERR_NO_SUCH_OBJECT:
965                 pass
966
967     def add_linked_attribute(self, src, dest, attr='member'):
968         m = ldb.Message()
969         m.dn = ldb.Dn(self.ldb_dc1, src)
970         m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
971         self.ldb_dc1.modify(m)
972
973     def remove_linked_attribute(self, src, dest, attr='member'):
974         m = ldb.Message()
975         m.dn = ldb.Dn(self.ldb_dc1, src)
976         m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
977         self.ldb_dc1.modify(m)
978
979     def test_sort_behaviour_single_object(self):
980         """Testing sorting behaviour on single objects"""
981
982         user1_dn = "cn=test_user1,%s" % self.ou
983         user2_dn = "cn=test_user2,%s" % self.ou
984         user3_dn = "cn=test_user3,%s" % self.ou
985         group_dn = "cn=test_group,%s" % self.ou
986
987         self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
988         self.ldb_dc1.add({"dn": user2_dn, "objectclass": "user"})
989         self.ldb_dc1.add({"dn": user3_dn, "objectclass": "user"})
990         self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
991
992         u1_guid = misc.GUID(self.ldb_dc1.search(base=user1_dn,
993                                                 attrs=["objectGUID"])[0]['objectGUID'][0])
994         u2_guid = misc.GUID(self.ldb_dc1.search(base=user2_dn,
995                                                 attrs=["objectGUID"])[0]['objectGUID'][0])
996         u3_guid = misc.GUID(self.ldb_dc1.search(base=user3_dn,
997                                                 attrs=["objectGUID"])[0]['objectGUID'][0])
998         g_guid = misc.GUID(self.ldb_dc1.search(base=group_dn,
999                                                attrs=["objectGUID"])[0]['objectGUID'][0])
1000
1001         self.add_linked_attribute(group_dn, user1_dn,
1002                                   attr='member')
1003         self.add_linked_attribute(group_dn, user2_dn,
1004                                   attr='member')
1005         self.add_linked_attribute(group_dn, user3_dn,
1006                                   attr='member')
1007         self.add_linked_attribute(group_dn, user1_dn,
1008                                   attr='managedby')
1009         self.add_linked_attribute(group_dn, user2_dn,
1010                                   attr='nonSecurityMember')
1011         self.add_linked_attribute(group_dn, user3_dn,
1012                                   attr='nonSecurityMember')
1013
1014         set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1015                                     drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1016                                     g_guid, u3_guid)
1017
1018         expected_links = set([set_inactive,
1019                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1020                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1021                                            g_guid,
1022                                            u1_guid),
1023                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1024                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1025                                            g_guid,
1026                                            u2_guid),
1027                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1028                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1029                                            g_guid,
1030                                            u3_guid),
1031                               AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
1032                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1033                                            g_guid,
1034                                            u1_guid),
1035                               AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1036                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1037                                            g_guid,
1038                                            u2_guid),
1039         ])
1040
1041         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1042
1043         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1044
1045         req8 = self._exop_req8(dest_dsa=None,
1046                                invocation_id=dc_guid_1,
1047                                nc_dn_str=group_dn,
1048                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
1049
1050         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1051
1052         no_inactive = []
1053         for link in ctr.linked_attributes:
1054             target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1055                                      link.value.blob).guid
1056             no_inactive.append((link, target_guid))
1057             self.assertTrue(AbstractLink(link.attid, link.flags,
1058                                          link.identifier.guid,
1059                                          target_guid) in expected_links)
1060
1061         no_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1062
1063         # assert the two arrays are the same
1064         self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1065         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)
1066
1067         self.remove_linked_attribute(group_dn, user3_dn,
1068                                      attr='nonSecurityMember')
1069
1070         # Set the link inactive
1071         expected_links.remove(set_inactive)
1072         set_inactive.flags = 0
1073         expected_links.add(set_inactive)
1074
1075         has_inactive = []
1076         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1077         for link in ctr.linked_attributes:
1078             target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1079                                      link.value.blob).guid
1080             has_inactive.append((link, target_guid))
1081             self.assertTrue(AbstractLink(link.attid, link.flags,
1082                                          link.identifier.guid,
1083                                          target_guid) in expected_links)
1084
1085         has_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1086
1087         # assert the two arrays are the same
1088         self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1089         self.assertEqual([x[0] for x in has_inactive], ctr.linked_attributes)
1090
1091     def test_sort_behaviour_ncchanges(self):
1092         """Testing sorting behaviour on a group of objects."""
1093         user1_dn = "cn=test_user1,%s" % self.ou
1094         group_dn = "cn=test_group,%s" % self.ou
1095         self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
1096         self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
1097
1098         self.add_linked_attribute(group_dn, user1_dn,
1099                                   attr='member')
1100
1101         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1102
1103         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1104
1105         # Make sure the max objects count is high enough
1106         req8 = self._exop_req8(dest_dsa=None,
1107                                invocation_id=dc_guid_1,
1108                                nc_dn_str=self.base_dn,
1109                                replica_flags=0,
1110                                max_objects=100,
1111                                exop=drsuapi.DRSUAPI_EXOP_NONE)
1112
1113         # Loop until we get linked attributes, or we get to the end.
1114         # Samba sends linked attributes at the end, unlike Windows.
1115         while True:
1116             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1117             if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1118                 break
1119             req8.highwatermark = ctr.new_highwatermark
1120
1121         self.assertTrue(ctr.linked_attributes_count != 0)
1122
1123         no_inactive = []
1124         for link in ctr.linked_attributes:
1125             try:
1126                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1127                                          link.value.blob).guid
1128             except:
1129                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1130                                          link.value.blob).guid
1131             no_inactive.append((link, target_guid))
1132
1133         no_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1134
1135         # assert the two arrays are the same
1136         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)