libndr: Avoid assigning duplicate versions to symbols
[amitay/samba.git] / source4 / torture / drs / python / getnc_exop.py
1 #!/usr/bin/env python3
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 from samba import werror, WERRORError
38
39 import ldb
40 from ldb import SCOPE_BASE
41
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
47
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(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(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.assertEqual(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
601 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
602     def setUp(self):
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
606         self.ldb_dc1.add({
607             "dn": self.ou,
608             "objectclass": "organizationalUnit"})
609         self.user = "cn=testuser,%s" % self.ou
610         self.ldb_dc1.add({
611             "dn": self.user,
612             "objectclass": "user"})
613
614     def tearDown(self):
615         super(DrsReplicaPrefixMapTestCase, self).tearDown()
616         try:
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:
621                 pass
622
623     def test_missing_prefix_map_dsa(self):
624         partial_attribute_set = self.get_partial_attribute_set()
625
626         dc_guid_1 = self.ldb_dc1.get_invocation_id()
627
628         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
629
630         req8 = self._exop_req8(dest_dsa=None,
631                                invocation_id=dc_guid_1,
632                                nc_dn_str=self.user,
633                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
634                                partial_attribute_set=partial_attribute_set)
635
636         try:
637             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
638             self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
639         except RuntimeError:
640             self.fail("Missing prefixmap shouldn't have triggered an error")
641
642     def test_invalid_prefix_map_attid(self):
643         # Request for invalid attid
644         partial_attribute_set = self.get_partial_attribute_set([99999])
645
646         dc_guid_1 = self.ldb_dc1.get_invocation_id()
647         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
648
649         try:
650             pfm = self._samdb_fetch_pfm_and_schi()
651         except KeyError:
652             # On Windows, prefixMap isn't available over LDAP
653             req8 = self._exop_req8(dest_dsa=None,
654                                    invocation_id=dc_guid_1,
655                                    nc_dn_str=self.user,
656                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
657             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
658             pfm = ctr.mapping_ctr
659
660         req8 = self._exop_req8(dest_dsa=None,
661                                invocation_id=dc_guid_1,
662                                nc_dn_str=self.user,
663                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
664                                partial_attribute_set=partial_attribute_set,
665                                mapping_ctr=pfm)
666
667         try:
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")
674
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])
678
679         dc_guid_1 = self.ldb_dc1.get_invocation_id()
680         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
681
682         try:
683             pfm = self._samdb_fetch_pfm_and_schi()
684         except KeyError:
685             # On Windows, prefixMap isn't available over LDAP
686             req8 = self._exop_req8(dest_dsa=None,
687                                    invocation_id=dc_guid_1,
688                                    nc_dn_str=self.user,
689                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
690             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
691             pfm = ctr.mapping_ctr
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         req8 = self._exop_req8(dest_dsa=None,
768                                invocation_id=dc_guid_1,
769                                nc_dn_str=self.user,
770                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
771                                partial_attribute_set=partial_attribute_set,
772                                mapping_ctr=pfm)
773
774         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
775
776         found = False
777         for attr in ctr.first_object.object.attribute_ctr.attributes:
778             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
779                 found = True
780                 break
781
782         self.assertTrue(found, "Ensure we get the name attribute back")
783
784         for i, mapping in enumerate(pfm.mappings):
785             # OID: 2.5.4.*
786             # objectClass: 2.5.4.0
787             if mapping.oid.binary_oid == [85, 4]:
788                 idx1 = i
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]:
792                 idx2 = i
793
794         (pfm.mappings[idx1].id_prefix,
795          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
796                                           pfm.mappings[idx1].id_prefix)
797
798         tmp = pfm.mappings
799         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
800         pfm.mappings = tmp
801
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,
806                                nc_dn_str=self.user,
807                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
808                                partial_attribute_set=partial_attribute_set,
809                                mapping_ctr=pfm)
810
811         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
812
813         found = False
814         for attr in ctr.first_object.object.attribute_ctr.attributes:
815             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
816                 found = True
817                 break
818
819         self.assertTrue(found, "Ensure we get the name attribute back")
820
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])
825
826         dc_guid_1 = self.ldb_dc1.get_invocation_id()
827         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
828
829         try:
830             pfm = self._samdb_fetch_pfm_and_schi()
831         except KeyError:
832             # On Windows, prefixMap isn't available over LDAP
833             req8 = self._exop_req8(dest_dsa=None,
834                                    invocation_id=dc_guid_1,
835                                    nc_dn_str=self.user,
836                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
837             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
838             pfm = ctr.mapping_ctr
839
840         req8 = self._exop_req8(dest_dsa=None,
841                                invocation_id=dc_guid_1,
842                                nc_dn_str=self.user,
843                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
844                                partial_attribute_set=partial_attribute_set,
845                                partial_attribute_set_ex=partial_attribute_set_ex,
846                                mapping_ctr=pfm)
847
848         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
849
850         found = False
851         for attr in ctr.first_object.object.attribute_ctr.attributes:
852             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
853                 found = True
854                 break
855
856         self.assertTrue(found, "Ensure we get the name attribute back")
857
858         found = False
859         for attr in ctr.first_object.object.attribute_ctr.attributes:
860             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
861                 found = True
862                 break
863
864         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
865
866         for i, mapping in enumerate(pfm.mappings):
867             # OID: 2.5.4.*
868             # objectClass: 2.5.4.0
869             if mapping.oid.binary_oid == [85, 4]:
870                 idx1 = i
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]:
875                 idx2 = i
876
877         (pfm.mappings[idx1].id_prefix,
878          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
879                                           pfm.mappings[idx1].id_prefix)
880
881         tmp = pfm.mappings
882         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
883         pfm.mappings = tmp
884
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,
892                                nc_dn_str=self.user,
893                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
894                                partial_attribute_set=partial_attribute_set,
895                                partial_attribute_set_ex=partial_attribute_set_ex,
896                                mapping_ctr=pfm)
897
898         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
899
900         found = False
901         for attr in ctr.first_object.object.attribute_ctr.attributes:
902             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
903                 found = True
904                 break
905
906         self.assertTrue(found, "Ensure we get the name attribute back")
907
908         found = False
909         for attr in ctr.first_object.object.attribute_ctr.attributes:
910             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
911                 found = True
912                 break
913
914         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
915
916     def _samdb_fetch_pfm_and_schi(self):
917         """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
918         samdb = self.ldb_dc1
919         res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
920                            attrs=["prefixMap", "schemaInfo"])
921
922         pfm = ndr_unpack(drsblobs.prefixMapBlob,
923                          res[0]['prefixMap'][0])
924
925         schi = drsuapi.DsReplicaOIDMapping()
926         schi.id_prefix = 0
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
931         else:
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())
936
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
941
942         pfm.ctr.mappings = pfm.ctr.mappings + [schi]
943         pfm.ctr.num_mappings += 1
944         return pfm.ctr
945
946
947 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
948     def setUp(self):
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
952         self.ldb_dc1.add({
953             "dn": self.ou,
954             "objectclass": "organizationalUnit"})
955
956     def tearDown(self):
957         super(DrsReplicaSyncSortTestCase, self).tearDown()
958         # tidyup groups and users
959         try:
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:
964                 pass
965
966     def add_linked_attribute(self, src, dest, attr='member'):
967         m = ldb.Message()
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)
971
972     def remove_linked_attribute(self, src, dest, attr='member'):
973         m = ldb.Message()
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)
977
978     def test_sort_behaviour_single_object(self):
979         """Testing sorting behaviour on single objects"""
980
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
985
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"})
990
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])
999
1000         self.add_linked_attribute(group_dn, user1_dn,
1001                                   attr='member')
1002         self.add_linked_attribute(group_dn, user2_dn,
1003                                   attr='member')
1004         self.add_linked_attribute(group_dn, user3_dn,
1005                                   attr='member')
1006         self.add_linked_attribute(group_dn, user1_dn,
1007                                   attr='managedby')
1008         self.add_linked_attribute(group_dn, user2_dn,
1009                                   attr='nonSecurityMember')
1010         self.add_linked_attribute(group_dn, user3_dn,
1011                                   attr='nonSecurityMember')
1012
1013         set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1014                                     drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1015                                     g_guid, u3_guid)
1016
1017         expected_links = set([set_inactive,
1018                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1019                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1020                                            g_guid,
1021                                            u1_guid),
1022                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1023                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1024                                            g_guid,
1025                                            u2_guid),
1026                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1027                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1028                                            g_guid,
1029                                            u3_guid),
1030                               AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
1031                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1032                                            g_guid,
1033                                            u1_guid),
1034                               AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1035                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1036                                            g_guid,
1037                                            u2_guid),
1038                               ])
1039
1040         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1041
1042         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1043
1044         req8 = self._exop_req8(dest_dsa=None,
1045                                invocation_id=dc_guid_1,
1046                                nc_dn_str=group_dn,
1047                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
1048
1049         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1050
1051         no_inactive = []
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)
1059
1060         no_inactive.sort(key=cmp_to_key(_linked_attribute_compare))
1061
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)
1065
1066         self.remove_linked_attribute(group_dn, user3_dn,
1067                                      attr='nonSecurityMember')
1068
1069         # Set the link inactive
1070         expected_links.remove(set_inactive)
1071         set_inactive.flags = 0
1072         expected_links.add(set_inactive)
1073
1074         has_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)
1083
1084         has_inactive.sort(key=cmp_to_key(_linked_attribute_compare))
1085
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)
1089
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"})
1096
1097         self.add_linked_attribute(group_dn, user1_dn,
1098                                   attr='member')
1099
1100         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1101
1102         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1103
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,
1108                                replica_flags=0,
1109                                max_objects=100,
1110                                exop=drsuapi.DRSUAPI_EXOP_NONE)
1111
1112         # Loop until we get linked attributes, or we get to the end.
1113         # Samba sends linked attributes at the end, unlike Windows.
1114         while True:
1115             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1116             if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1117                 break
1118             req8.highwatermark = ctr.new_highwatermark
1119
1120         self.assertTrue(ctr.linked_attributes_count != 0)
1121
1122         no_inactive = []
1123         for link in ctr.linked_attributes:
1124             try:
1125                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1126                                          link.value.blob).guid
1127             except:
1128                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1129                                          link.value.blob).guid
1130             no_inactive.append((link, target_guid))
1131
1132         no_inactive.sort(key=cmp_to_key(_linked_attribute_compare))
1133
1134         # assert the two arrays are the same
1135         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)