2b4bdc4b714d98c761ab521a93638dbc28fa4625
[metze/samba/wip.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
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
46 def _linked_attribute_compare(la1, la2):
47     """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
48     la1, la1_target = la1
49     la2, la2_target = la2
50
51     # Ascending host object GUID
52     c = cmp(ndr_pack(la1.identifier.guid), ndr_pack(la2.identifier.guid))
53     if c != 0:
54         return c
55
56     # Ascending attribute ID
57     if la1.attid != la2.attid:
58         return -1 if la1.attid < la2.attid else 1
59
60     la1_active = la1.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
61     la2_active = la2.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
62
63     # Ascending 'is present'
64     if la1_active != la2_active:
65         return 1 if la1_active else -1
66
67     # Ascending target object GUID
68     return cmp(ndr_pack(la1_target), ndr_pack(la2_target))
69
70
71 class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
72     """Intended as a semi-black box test case for DsGetNCChanges
73        implementation for extended operations. It should be testing
74        how DsGetNCChanges handles different input params (mostly invalid).
75        Final goal is to make DsGetNCChanges as binary compatible to
76        Windows implementation as possible"""
77
78     def setUp(self):
79         super(DrsReplicaSyncTestCase, self).setUp()
80         self.base_dn = self.ldb_dc1.get_default_basedn()
81         self.ou = "OU=test_getncchanges,%s" % self.base_dn
82         self.ldb_dc1.add({
83             "dn": self.ou,
84             "objectclass": "organizationalUnit"})
85         (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
86         (self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.ldb_dc1)
87
88     def tearDown(self):
89         try:
90             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
91         except ldb.LdbError as (enum, string):
92             if enum == ldb.ERR_NO_SUCH_OBJECT:
93                 pass
94         super(DrsReplicaSyncTestCase, self).tearDown()
95
96     def _determine_fSMORoleOwner(self, fsmo_obj_dn):
97         """Returns (owner, not_owner) pair where:
98              owner: dns name for FSMO owner
99              not_owner: dns name for DC not owning the FSMO"""
100         # collect info to return later
101         fsmo_info_1 = {"dns_name": self.dnsname_dc1,
102                        "invocation_id": self.ldb_dc1.get_invocation_id(),
103                        "ntds_guid": self.ldb_dc1.get_ntds_GUID(),
104                        "server_dn": self.ldb_dc1.get_serverName()}
105         fsmo_info_2 = {"dns_name": self.dnsname_dc2,
106                        "invocation_id": self.ldb_dc2.get_invocation_id(),
107                        "ntds_guid": self.ldb_dc2.get_ntds_GUID(),
108                        "server_dn": self.ldb_dc2.get_serverName()}
109
110         msgs = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=fsmo_info_1["server_dn"], attrs=["serverReference"])
111         fsmo_info_1["server_acct_dn"] = ldb.Dn(self.ldb_dc1, msgs[0]["serverReference"][0])
112         fsmo_info_1["rid_set_dn"] = ldb.Dn(self.ldb_dc1, "CN=RID Set") + fsmo_info_1["server_acct_dn"]
113
114         msgs = self.ldb_dc2.search(scope=ldb.SCOPE_BASE, base=fsmo_info_2["server_dn"], attrs=["serverReference"])
115         fsmo_info_2["server_acct_dn"] = ldb.Dn(self.ldb_dc2, msgs[0]["serverReference"][0])
116         fsmo_info_2["rid_set_dn"] = ldb.Dn(self.ldb_dc2, "CN=RID Set") + fsmo_info_2["server_acct_dn"]
117
118         # determine the owner dc
119         res = self.ldb_dc1.search(fsmo_obj_dn,
120                                   scope=SCOPE_BASE, attrs=["fSMORoleOwner"])
121         assert len(res) == 1, "Only one fSMORoleOwner value expected for %s!"%fsmo_obj_dn
122         fsmo_owner = res[0]["fSMORoleOwner"][0]
123         if fsmo_owner == self.info_dc1["dsServiceName"][0]:
124             return (fsmo_info_1, fsmo_info_2)
125         return (fsmo_info_2, fsmo_info_1)
126
127     def _check_exop_failed(self, ctr6, expected_failure):
128         self.assertEqual(ctr6.extended_ret, expected_failure)
129         #self.assertEqual(ctr6.object_count, 0)
130         #self.assertEqual(ctr6.first_object, None)
131         self.assertEqual(ctr6.more_data, False)
132         self.assertEqual(ctr6.nc_object_count, 0)
133         self.assertEqual(ctr6.nc_linked_attributes_count, 0)
134         self.assertEqual(ctr6.linked_attributes_count, 0)
135         self.assertEqual(ctr6.linked_attributes, [])
136         self.assertEqual(ctr6.drs_error[0], 0)
137
138     def test_do_single_repl(self):
139         """
140         Make sure that DRSU_EXOP_REPL_OBJ never replicates more than
141         one object, even when we use DRS_GET_ANC.
142         """
143
144         ou1 = "OU=get_anc1,%s" % self.ou
145         self.ldb_dc1.add({
146             "dn": ou1,
147             "objectclass": "organizationalUnit"
148             })
149         ou1_id = self._get_identifier(self.ldb_dc1, ou1)
150         ou2 = "OU=get_anc2,%s" % ou1
151         self.ldb_dc1.add({
152             "dn": ou2,
153             "objectclass": "organizationalUnit"
154             })
155         ou2_id = self._get_identifier(self.ldb_dc1, ou2)
156         dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
157         self.ldb_dc1.add({
158             "dn": dc3,
159             "objectclass": "computer",
160             "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
161             })
162         dc3_id = self._get_identifier(self.ldb_dc1, dc3)
163
164         req8 = self._exop_req8(dest_dsa=None,
165                                invocation_id=self.ldb_dc1.get_invocation_id(),
166                                nc_dn_str=ou1,
167                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
168                                replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
169         (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
170         self._check_ctr6(ctr, [ou1])
171
172         # DRSUAPI_DRS_WRIT_REP means that we should only replicate the dn we give (dc3).
173         # DRSUAPI_DRS_GET_ANC means that we should also replicate its ancestors, but
174         # Windows doesn't do this if we use both.
175         req8 = self._exop_req8(dest_dsa=None,
176                                invocation_id=self.ldb_dc1.get_invocation_id(),
177                                nc_dn_str=dc3,
178                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
179                                replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP |
180                                              drsuapi.DRSUAPI_DRS_GET_ANC)
181         (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
182         self._check_ctr6(ctr, [dc3])
183
184         # Even though the ancestor of ou2 (ou1) has changed since last hwm, and we're
185         # sending DRSUAPI_DRS_GET_ANC, the expected response is that it will only try
186         # and replicate the single object still.
187         req8 = self._exop_req8(dest_dsa=None,
188                                invocation_id=self.ldb_dc1.get_invocation_id(),
189                                nc_dn_str=ou2,
190                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
191                                replica_flags=drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
192                                              drsuapi.DRSUAPI_DRS_GET_ANC)
193         (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
194         self._check_ctr6(ctr, [ou2])
195
196     def test_link_utdv_hwm(self):
197         """Test verify the DRS_GET_ANC behavior."""
198
199         ou1 = "OU=get_anc1,%s" % self.ou
200         self.ldb_dc1.add({
201             "dn": ou1,
202             "objectclass": "organizationalUnit"
203             })
204         ou1_id = self._get_identifier(self.ldb_dc1, ou1)
205         ou2 = "OU=get_anc2,%s" % ou1
206         self.ldb_dc1.add({
207             "dn": ou2,
208             "objectclass": "organizationalUnit"
209             })
210         ou2_id = self._get_identifier(self.ldb_dc1, ou2)
211         dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
212         self.ldb_dc1.add({
213             "dn": dc3,
214             "objectclass": "computer",
215             "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
216             })
217         dc3_id = self._get_identifier(self.ldb_dc1, dc3)
218
219         (hwm1, utdv1) = self._check_replication([ou1,ou2,dc3],
220                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
221
222         self._check_replication([ou1,ou2,dc3],
223             drsuapi.DRSUAPI_DRS_WRIT_REP|
224             drsuapi.DRSUAPI_DRS_GET_ANC)
225
226         self._check_replication([dc3],
227             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
228
229         self._check_replication([ou1,ou2,dc3],
230             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
231             drsuapi.DRSUAPI_DRS_GET_ANC)
232
233         m = ldb.Message()
234         m.dn = ldb.Dn(self.ldb_dc1, ou1)
235         m["displayName"] = ldb.MessageElement("OU1", ldb.FLAG_MOD_ADD, "displayName")
236         self.ldb_dc1.modify(m)
237
238         (hwm2, utdv2) = self._check_replication([ou2,dc3,ou1],
239                             drsuapi.DRSUAPI_DRS_WRIT_REP)
240
241         self._check_replication([ou1,ou2,dc3],
242             drsuapi.DRSUAPI_DRS_WRIT_REP|
243             drsuapi.DRSUAPI_DRS_GET_ANC)
244
245         self._check_replication([dc3],
246             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
247
248         self._check_replication([ou1,ou2,dc3],
249             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
250             drsuapi.DRSUAPI_DRS_GET_ANC)
251
252         self._check_replication([ou1],
253             drsuapi.DRSUAPI_DRS_WRIT_REP,
254             highwatermark=hwm1)
255
256         self._check_replication([ou1],
257             drsuapi.DRSUAPI_DRS_WRIT_REP|
258             drsuapi.DRSUAPI_DRS_GET_ANC,
259             highwatermark=hwm1)
260
261         self._check_replication([ou1],
262             drsuapi.DRSUAPI_DRS_WRIT_REP|
263             drsuapi.DRSUAPI_DRS_GET_ANC,
264             uptodateness_vector=utdv1)
265
266         m = ldb.Message()
267         m.dn = ldb.Dn(self.ldb_dc1, ou2)
268         m["displayName"] = ldb.MessageElement("OU2", ldb.FLAG_MOD_ADD, "displayName")
269         self.ldb_dc1.modify(m)
270
271         (hwm3, utdv3) = self._check_replication([dc3,ou1,ou2],
272                             drsuapi.DRSUAPI_DRS_WRIT_REP)
273
274         self._check_replication([ou1,ou2,dc3],
275             drsuapi.DRSUAPI_DRS_WRIT_REP|
276             drsuapi.DRSUAPI_DRS_GET_ANC)
277
278         self._check_replication([dc3],
279             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
280
281         self._check_replication([ou1,ou2,dc3],
282             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
283             drsuapi.DRSUAPI_DRS_GET_ANC)
284
285         self._check_replication([ou1,ou2],
286             drsuapi.DRSUAPI_DRS_WRIT_REP,
287             highwatermark=hwm1)
288
289         self._check_replication([ou1,ou2],
290             drsuapi.DRSUAPI_DRS_WRIT_REP|
291             drsuapi.DRSUAPI_DRS_GET_ANC,
292             highwatermark=hwm1)
293
294         self._check_replication([ou1,ou2],
295             drsuapi.DRSUAPI_DRS_WRIT_REP|
296             drsuapi.DRSUAPI_DRS_GET_ANC,
297             uptodateness_vector=utdv1)
298
299         m = ldb.Message()
300         m.dn = ldb.Dn(self.ldb_dc1, self.ou)
301         m["displayName"] = ldb.MessageElement("OU", ldb.FLAG_MOD_ADD, "displayName")
302         self.ldb_dc1.modify(m)
303
304         (hwm4, utdv4) = self._check_replication([dc3,ou1,ou2,self.ou],
305                             drsuapi.DRSUAPI_DRS_WRIT_REP)
306
307         self._check_replication([self.ou,ou1,ou2,dc3],
308             drsuapi.DRSUAPI_DRS_WRIT_REP|
309             drsuapi.DRSUAPI_DRS_GET_ANC)
310
311         self._check_replication([dc3],
312             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
313
314         self._check_replication([self.ou,ou1,ou2,dc3],
315             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
316             drsuapi.DRSUAPI_DRS_GET_ANC)
317
318         self._check_replication([self.ou,ou2],
319             drsuapi.DRSUAPI_DRS_WRIT_REP|
320             drsuapi.DRSUAPI_DRS_GET_ANC,
321             uptodateness_vector=utdv2)
322
323         cn3 = "CN=get_anc3,%s" % ou2
324         self.ldb_dc1.add({
325             "dn": cn3,
326             "objectclass": "container",
327             })
328         cn3_id = self._get_identifier(self.ldb_dc1, cn3)
329
330         (hwm5, utdv5) = self._check_replication([dc3,ou1,ou2,self.ou,cn3],
331                             drsuapi.DRSUAPI_DRS_WRIT_REP)
332
333         self._check_replication([self.ou,ou1,ou2,dc3,cn3],
334             drsuapi.DRSUAPI_DRS_WRIT_REP|
335             drsuapi.DRSUAPI_DRS_GET_ANC)
336
337         self._check_replication([dc3],
338             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
339
340         self._check_replication([self.ou,ou1,ou2,dc3],
341             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
342             drsuapi.DRSUAPI_DRS_GET_ANC)
343
344         m = ldb.Message()
345         m.dn = ldb.Dn(self.ldb_dc1, ou2)
346         m["managedBy"] = ldb.MessageElement(dc3, ldb.FLAG_MOD_ADD, "managedBy")
347         self.ldb_dc1.modify(m)
348         ou2_managedBy_dc3 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
349                                 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
350                                 ou2_id.guid, dc3_id.guid)
351
352         (hwm6, utdv6) = self._check_replication([dc3,ou1,self.ou,cn3,ou2],
353                             drsuapi.DRSUAPI_DRS_WRIT_REP,
354                             expected_links=[ou2_managedBy_dc3])
355
356         # Can fail against Windows due to equal precedence of dc3, cn3
357         self._check_replication([self.ou,ou1,ou2,dc3,cn3],
358             drsuapi.DRSUAPI_DRS_WRIT_REP|
359             drsuapi.DRSUAPI_DRS_GET_ANC,
360             expected_links=[ou2_managedBy_dc3])
361
362         self._check_replication([dc3],
363             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
364
365         self._check_replication([self.ou,ou1,ou2,dc3],
366             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
367             drsuapi.DRSUAPI_DRS_GET_ANC)
368
369         self._check_replication([],
370                           drsuapi.DRSUAPI_DRS_WRIT_REP,
371                           uptodateness_vector=utdv5,
372                           expected_links=[ou2_managedBy_dc3])
373
374         self._check_replication([],
375                           drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
376                           uptodateness_vector=utdv5)
377
378         self._check_replication([],
379                           drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
380                           uptodateness_vector=utdv5)
381
382         m = ldb.Message()
383         m.dn = ldb.Dn(self.ldb_dc1, dc3)
384         m["managedBy"] = ldb.MessageElement(ou1, ldb.FLAG_MOD_ADD, "managedBy")
385         self.ldb_dc1.modify(m)
386         dc3_managedBy_ou1 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
387                                 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
388                                 dc3_id.guid, ou1_id.guid)
389
390         (hwm7, utdv7) = self._check_replication([ou1,self.ou,cn3,ou2,dc3],
391                             drsuapi.DRSUAPI_DRS_WRIT_REP,
392                             expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
393
394         # Can fail against Windows due to equal precedence of dc3, cn3
395         #self._check_replication([self.ou,ou1,ou2,dc3,cn3],
396         #    drsuapi.DRSUAPI_DRS_WRIT_REP|
397         #    drsuapi.DRSUAPI_DRS_GET_ANC,
398         #    expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
399
400         self._check_replication([dc3],
401             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
402             expected_links=[dc3_managedBy_ou1])
403
404         self._check_replication([dc3],
405             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
406             expected_links=[dc3_managedBy_ou1])
407
408         self._check_replication([self.ou,ou1,ou2,dc3],
409             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
410             drsuapi.DRSUAPI_DRS_GET_ANC,
411             expected_links=[dc3_managedBy_ou1])
412
413         self._check_replication([dc3],
414             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
415             more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
416             expected_links=[dc3_managedBy_ou1])
417
418         m = ldb.Message()
419         m.dn = ldb.Dn(self.ldb_dc1, dc3)
420         m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_REPLACE, "managedBy")
421         self.ldb_dc1.modify(m)
422         dc3_managedBy_ou1.flags &= ~drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
423         dc3_managedBy_ou2 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
424                                 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
425                                 dc3_id.guid, ou2_id.guid)
426
427         (hwm8, utdv8) = self._check_replication([ou1,self.ou,cn3,ou2,dc3],
428                             drsuapi.DRSUAPI_DRS_WRIT_REP,
429                             expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
430
431         # Can fail against Windows due to equal precedence of dc3, cn3
432         #self._check_replication([self.ou,ou1,ou2,dc3,cn3],
433         #    drsuapi.DRSUAPI_DRS_WRIT_REP|
434         #    drsuapi.DRSUAPI_DRS_GET_ANC,
435         #    expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
436
437         self._check_replication([dc3],
438             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
439             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2])
440
441         self._check_replication([self.ou,ou1,ou2,dc3],
442             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
443             drsuapi.DRSUAPI_DRS_GET_ANC,
444             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2])
445
446         self._check_replication([dc3],
447             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
448             more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
449             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2])
450
451         self._check_replication([],
452             drsuapi.DRSUAPI_DRS_WRIT_REP,
453             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
454             highwatermark=hwm7)
455
456         self._check_replication([],
457             drsuapi.DRSUAPI_DRS_WRIT_REP|
458             drsuapi.DRSUAPI_DRS_GET_ANC,
459             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
460             highwatermark=hwm7)
461
462         self._check_replication([],
463             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
464             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
465             highwatermark=hwm7)
466
467         self._check_replication([],
468             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
469             drsuapi.DRSUAPI_DRS_GET_ANC,
470             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
471             highwatermark=hwm7)
472
473         self._check_replication([],
474             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
475             more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
476             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
477             highwatermark=hwm7)
478
479         self._check_replication([],
480             drsuapi.DRSUAPI_DRS_WRIT_REP,
481             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
482             uptodateness_vector=utdv7)
483
484         self._check_replication([],
485             drsuapi.DRSUAPI_DRS_WRIT_REP|
486             drsuapi.DRSUAPI_DRS_GET_ANC,
487             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
488             uptodateness_vector=utdv7)
489
490         self._check_replication([],
491             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
492             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
493             uptodateness_vector=utdv7)
494
495         self._check_replication([],
496             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY|
497             drsuapi.DRSUAPI_DRS_GET_ANC,
498             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
499             uptodateness_vector=utdv7)
500
501         self._check_replication([],
502             drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
503             more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
504             expected_links=[dc3_managedBy_ou1,dc3_managedBy_ou2],
505             uptodateness_vector=utdv7)
506
507     def test_FSMONotOwner(self):
508         """Test role transfer with against DC not owner of the role"""
509         fsmo_dn = self.ldb_dc1.get_schema_basedn()
510         (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
511
512         req8 = self._exop_req8(dest_dsa=fsmo_owner["ntds_guid"],
513                                invocation_id=fsmo_not_owner["invocation_id"],
514                                nc_dn_str=fsmo_dn,
515                                exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
516
517         (drs, drs_handle) = self._ds_bind(fsmo_not_owner["dns_name"])
518         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
519         self.assertEqual(level, 6, "Expected level 6 response!")
520         self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER)
521         self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_not_owner["ntds_guid"]))
522         self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_not_owner["invocation_id"]))
523
524     def test_InvalidDestDSA(self):
525         """Test role transfer with invalid destination DSA guid"""
526         fsmo_dn = self.ldb_dc1.get_schema_basedn()
527         (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
528
529         req8 = self._exop_req8(dest_dsa="9c637462-5b8c-4467-aef2-bdb1f57bc4ef",
530                                invocation_id=fsmo_owner["invocation_id"],
531                                nc_dn_str=fsmo_dn,
532                                exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
533
534         (drs, drs_handle) = self._ds_bind(fsmo_owner["dns_name"])
535         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
536         self.assertEqual(level, 6, "Expected level 6 response!")
537         self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_UNKNOWN_CALLER)
538         self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_owner["ntds_guid"]))
539         self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_owner["invocation_id"]))
540
541 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
542     def setUp(self):
543         super(DrsReplicaPrefixMapTestCase, self).setUp()
544         self.base_dn = self.ldb_dc1.get_default_basedn()
545         self.ou = "ou=pfm_exop,%s" % self.base_dn
546         self.ldb_dc1.add({
547             "dn": self.ou,
548             "objectclass": "organizationalUnit"})
549         self.user = "cn=testuser,%s" % self.ou
550         self.ldb_dc1.add({
551             "dn": self.user,
552             "objectclass": "user"})
553
554     def tearDown(self):
555         super(DrsReplicaPrefixMapTestCase, self).tearDown()
556         try:
557             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
558         except ldb.LdbError as (enum, string):
559             if enum == ldb.ERR_NO_SUCH_OBJECT:
560                 pass
561
562     def test_missing_prefix_map_dsa(self):
563         partial_attribute_set = self.get_partial_attribute_set()
564
565         dc_guid_1 = self.ldb_dc1.get_invocation_id()
566
567         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
568
569         req8 = self._exop_req8(dest_dsa=None,
570                                invocation_id=dc_guid_1,
571                                nc_dn_str=self.user,
572                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
573                                partial_attribute_set=partial_attribute_set)
574
575         try:
576             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
577             self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
578         except RuntimeError:
579             self.fail("Missing prefixmap shouldn't have triggered an error")
580
581     def test_invalid_prefix_map_attid(self):
582         # Request for invalid attid
583         partial_attribute_set = self.get_partial_attribute_set([99999])
584
585         dc_guid_1 = self.ldb_dc1.get_invocation_id()
586         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
587
588         try:
589             pfm = self._samdb_fetch_pfm_and_schi()
590         except KeyError:
591             # On Windows, prefixMap isn't available over LDAP
592             req8 = self._exop_req8(dest_dsa=None,
593                                    invocation_id=dc_guid_1,
594                                    nc_dn_str=self.user,
595                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
596             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
597             pfm = ctr.mapping_ctr
598
599         req8 = self._exop_req8(dest_dsa=None,
600                                invocation_id=dc_guid_1,
601                                nc_dn_str=self.user,
602                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
603                                partial_attribute_set=partial_attribute_set,
604                                mapping_ctr=pfm)
605
606         try:
607             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
608             self.fail("Invalid attid (99999) should have triggered an error")
609         except RuntimeError as (ecode, emsg):
610             self.assertEqual(ecode, 0x000020E2, "Error code should have been "
611                              "WERR_DS_DRA_SCHEMA_MISMATCH")
612
613     def test_secret_prefix_map_attid(self):
614         # Request for a secret attid
615         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
616
617         dc_guid_1 = self.ldb_dc1.get_invocation_id()
618         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
619
620         try:
621             pfm = self._samdb_fetch_pfm_and_schi()
622         except KeyError:
623             # On Windows, prefixMap isn't available over LDAP
624             req8 = self._exop_req8(dest_dsa=None,
625                                    invocation_id=dc_guid_1,
626                                    nc_dn_str=self.user,
627                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
628             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
629             pfm = ctr.mapping_ctr
630
631
632         req8 = self._exop_req8(dest_dsa=None,
633                                invocation_id=dc_guid_1,
634                                nc_dn_str=self.user,
635                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
636                                partial_attribute_set=partial_attribute_set,
637                                mapping_ctr=pfm)
638
639         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
640
641         found = False
642         for attr in ctr.first_object.object.attribute_ctr.attributes:
643             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
644                 found = True
645                 break
646
647         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
648
649         for i, mapping in enumerate(pfm.mappings):
650             # OID: 2.5.4.*
651             # objectClass: 2.5.4.0
652             if mapping.oid.binary_oid == [85, 4]:
653                 idx1 = i
654             # OID: 1.2.840.113556.1.4.*
655             # unicodePwd: 1.2.840.113556.1.4.90
656             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
657                 idx2 = i
658
659         (pfm.mappings[idx1].id_prefix,
660          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
661                                           pfm.mappings[idx1].id_prefix)
662
663         tmp = pfm.mappings
664         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
665         pfm.mappings = tmp
666
667         # 90 for unicodePwd (with new prefix = 0)
668         # 589824, 589827 for objectClass and CN
669         # Use of three ensures sorting is correct
670         partial_attribute_set = self.get_partial_attribute_set([90, 589824, 589827])
671         req8 = self._exop_req8(dest_dsa=None,
672                                invocation_id=dc_guid_1,
673                                nc_dn_str=self.user,
674                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
675                                partial_attribute_set=partial_attribute_set,
676                                mapping_ctr=pfm)
677
678         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
679
680         found = False
681         for attr in ctr.first_object.object.attribute_ctr.attributes:
682             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
683                 found = True
684                 break
685
686         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
687
688     def test_regular_prefix_map_attid(self):
689         # Request for a regular (non-secret) attid
690         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
691
692         dc_guid_1 = self.ldb_dc1.get_invocation_id()
693         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
694
695         try:
696             pfm = self._samdb_fetch_pfm_and_schi()
697         except KeyError:
698             # On Windows, prefixMap isn't available over LDAP
699             req8 = self._exop_req8(dest_dsa=None,
700                                    invocation_id=dc_guid_1,
701                                    nc_dn_str=self.user,
702                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
703             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
704             pfm = ctr.mapping_ctr
705
706
707         req8 = self._exop_req8(dest_dsa=None,
708                                invocation_id=dc_guid_1,
709                                nc_dn_str=self.user,
710                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
711                                partial_attribute_set=partial_attribute_set,
712                                mapping_ctr=pfm)
713
714         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
715
716         found = False
717         for attr in ctr.first_object.object.attribute_ctr.attributes:
718             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
719                 found = True
720                 break
721
722         self.assertTrue(found, "Ensure we get the name attribute back")
723
724         for i, mapping in enumerate(pfm.mappings):
725             # OID: 2.5.4.*
726             # objectClass: 2.5.4.0
727             if mapping.oid.binary_oid == [85, 4]:
728                 idx1 = i
729             # OID: 1.2.840.113556.1.4.*
730             # name: 1.2.840.113556.1.4.1
731             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
732                 idx2 = i
733
734         (pfm.mappings[idx1].id_prefix,
735          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
736                                           pfm.mappings[idx1].id_prefix)
737
738         tmp = pfm.mappings
739         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
740         pfm.mappings = tmp
741
742         # 1 for name (with new prefix = 0)
743         partial_attribute_set = self.get_partial_attribute_set([1])
744         req8 = self._exop_req8(dest_dsa=None,
745                                invocation_id=dc_guid_1,
746                                nc_dn_str=self.user,
747                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
748                                partial_attribute_set=partial_attribute_set,
749                                mapping_ctr=pfm)
750
751         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
752
753         found = False
754         for attr in ctr.first_object.object.attribute_ctr.attributes:
755             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
756                 found = True
757                 break
758
759         self.assertTrue(found, "Ensure we get the name attribute back")
760
761     def test_regular_prefix_map_ex_attid(self):
762         # Request for a regular (non-secret) attid
763         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
764         partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
765
766         dc_guid_1 = self.ldb_dc1.get_invocation_id()
767         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
768
769         try:
770             pfm = self._samdb_fetch_pfm_and_schi()
771         except KeyError:
772             # On Windows, prefixMap isn't available over LDAP
773             req8 = self._exop_req8(dest_dsa=None,
774                                    invocation_id=dc_guid_1,
775                                    nc_dn_str=self.user,
776                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
777             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
778             pfm = ctr.mapping_ctr
779
780
781         req8 = self._exop_req8(dest_dsa=None,
782                                invocation_id=dc_guid_1,
783                                nc_dn_str=self.user,
784                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
785                                partial_attribute_set=partial_attribute_set,
786                                partial_attribute_set_ex=partial_attribute_set_ex,
787                                mapping_ctr=pfm)
788
789         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
790
791         found = False
792         for attr in ctr.first_object.object.attribute_ctr.attributes:
793             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
794                 found = True
795                 break
796
797         self.assertTrue(found, "Ensure we get the name attribute back")
798
799         found = False
800         for attr in ctr.first_object.object.attribute_ctr.attributes:
801             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
802                 found = True
803                 break
804
805         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
806
807         for i, mapping in enumerate(pfm.mappings):
808             # OID: 2.5.4.*
809             # objectClass: 2.5.4.0
810             if mapping.oid.binary_oid == [85, 4]:
811                 idx1 = i
812             # OID: 1.2.840.113556.1.4.*
813             # name: 1.2.840.113556.1.4.1
814             # unicodePwd: 1.2.840.113556.1.4.90
815             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
816                 idx2 = i
817
818         (pfm.mappings[idx1].id_prefix,
819          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
820                                           pfm.mappings[idx1].id_prefix)
821
822         tmp = pfm.mappings
823         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
824         pfm.mappings = tmp
825
826         # 1 for name (with new prefix = 0)
827         partial_attribute_set = self.get_partial_attribute_set([1])
828         # 90 for unicodePwd (with new prefix = 0)
829         # HOWEVER: Windows doesn't seem to respect incoming maps for PartialAttrSetEx
830         partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
831         req8 = self._exop_req8(dest_dsa=None,
832                                invocation_id=dc_guid_1,
833                                nc_dn_str=self.user,
834                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
835                                partial_attribute_set=partial_attribute_set,
836                                partial_attribute_set_ex=partial_attribute_set_ex,
837                                mapping_ctr=pfm)
838
839         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
840
841         found = False
842         for attr in ctr.first_object.object.attribute_ctr.attributes:
843             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
844                 found = True
845                 break
846
847         self.assertTrue(found, "Ensure we get the name attribute back")
848
849         found = False
850         for attr in ctr.first_object.object.attribute_ctr.attributes:
851             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
852                 found = True
853                 break
854
855         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
856
857     def _samdb_fetch_pfm_and_schi(self):
858         """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
859         samdb = self.ldb_dc1
860         res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
861                            attrs=["prefixMap", "schemaInfo"])
862
863         pfm = ndr_unpack(drsblobs.prefixMapBlob,
864                          str(res[0]['prefixMap']))
865
866         schi = drsuapi.DsReplicaOIDMapping()
867         schi.id_prefix = 0
868
869         if 'schemaInfo' in res[0]:
870             schi.oid.length = len(map(ord, str(res[0]['schemaInfo'])))
871             schi.oid.binary_oid = map(ord, str(res[0]['schemaInfo']))
872         else:
873             schema_info = drsblobs.schemaInfoBlob()
874             schema_info.revision = 0
875             schema_info.marker = 0xFF
876             schema_info.invocation_id = misc.GUID(samdb.get_invocation_id())
877             schi.oid.length = len(map(ord, ndr_pack(schema_info)))
878             schi.oid.binary_oid = map(ord, ndr_pack(schema_info))
879
880         pfm.ctr.mappings = pfm.ctr.mappings + [schi]
881         pfm.ctr.num_mappings += 1
882         return pfm.ctr
883
884 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
885     def setUp(self):
886         super(DrsReplicaSyncSortTestCase, self).setUp()
887         self.base_dn = self.ldb_dc1.get_default_basedn()
888         self.ou = "ou=sort_exop,%s" % self.base_dn
889         self.ldb_dc1.add({
890             "dn": self.ou,
891             "objectclass": "organizationalUnit"})
892
893     def tearDown(self):
894         super(DrsReplicaSyncSortTestCase, self).tearDown()
895         # tidyup groups and users
896         try:
897             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
898         except ldb.LdbError as (enum, string):
899             if enum == ldb.ERR_NO_SUCH_OBJECT:
900                 pass
901
902     def add_linked_attribute(self, src, dest, attr='member'):
903         m = ldb.Message()
904         m.dn = ldb.Dn(self.ldb_dc1, src)
905         m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
906         self.ldb_dc1.modify(m)
907
908     def remove_linked_attribute(self, src, dest, attr='member'):
909         m = ldb.Message()
910         m.dn = ldb.Dn(self.ldb_dc1, src)
911         m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
912         self.ldb_dc1.modify(m)
913
914     def test_sort_behaviour_single_object(self):
915         """Testing sorting behaviour on single objects"""
916
917         user1_dn = "cn=test_user1,%s" % self.ou
918         user2_dn = "cn=test_user2,%s" % self.ou
919         user3_dn = "cn=test_user3,%s" % self.ou
920         group_dn = "cn=test_group,%s" % self.ou
921
922         self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
923         self.ldb_dc1.add({"dn": user2_dn, "objectclass": "user"})
924         self.ldb_dc1.add({"dn": user3_dn, "objectclass": "user"})
925         self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
926
927         u1_guid = misc.GUID(self.ldb_dc1.search(base=user1_dn,
928                       attrs=["objectGUID"])[0]['objectGUID'][0])
929         u2_guid = misc.GUID(self.ldb_dc1.search(base=user2_dn,
930                       attrs=["objectGUID"])[0]['objectGUID'][0])
931         u3_guid = misc.GUID(self.ldb_dc1.search(base=user3_dn,
932                       attrs=["objectGUID"])[0]['objectGUID'][0])
933         g_guid = misc.GUID(self.ldb_dc1.search(base=group_dn,
934                      attrs=["objectGUID"])[0]['objectGUID'][0])
935
936         self.add_linked_attribute(group_dn, user1_dn,
937                                   attr='member')
938         self.add_linked_attribute(group_dn, user2_dn,
939                                   attr='member')
940         self.add_linked_attribute(group_dn, user3_dn,
941                                   attr='member')
942         self.add_linked_attribute(group_dn, user1_dn,
943                                   attr='managedby')
944         self.add_linked_attribute(group_dn, user2_dn,
945                                   attr='nonSecurityMember')
946         self.add_linked_attribute(group_dn, user3_dn,
947                                   attr='nonSecurityMember')
948
949         set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
950                                     drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
951                                     g_guid, u3_guid)
952
953         expected_links = set([set_inactive,
954         AbstractLink(drsuapi.DRSUAPI_ATTID_member,
955                      drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
956                      g_guid,
957                      u1_guid),
958         AbstractLink(drsuapi.DRSUAPI_ATTID_member,
959                      drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
960                      g_guid,
961                      u2_guid),
962         AbstractLink(drsuapi.DRSUAPI_ATTID_member,
963                      drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
964                      g_guid,
965                      u3_guid),
966         AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
967                      drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
968                      g_guid,
969                      u1_guid),
970         AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
971                      drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
972                      g_guid,
973                      u2_guid),
974         ])
975
976         dc_guid_1 = self.ldb_dc1.get_invocation_id()
977
978         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
979
980         req8 = self._exop_req8(dest_dsa=None,
981                 invocation_id=dc_guid_1,
982                 nc_dn_str=group_dn,
983                 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
984
985         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
986
987         no_inactive = []
988         for link in ctr.linked_attributes:
989             target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
990                                      link.value.blob).guid
991             no_inactive.append((link, target_guid))
992             self.assertTrue(AbstractLink(link.attid, link.flags,
993                                          link.identifier.guid,
994                                          target_guid) in expected_links)
995
996         no_inactive.sort(cmp=_linked_attribute_compare)
997
998         # assert the two arrays are the same
999         self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1000         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)
1001
1002         self.remove_linked_attribute(group_dn, user3_dn,
1003                                      attr='nonSecurityMember')
1004
1005         # Set the link inactive
1006         expected_links.remove(set_inactive)
1007         set_inactive.flags = 0
1008         expected_links.add(set_inactive)
1009
1010         has_inactive = []
1011         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1012         for link in ctr.linked_attributes:
1013             target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1014                                      link.value.blob).guid
1015             has_inactive.append((link, target_guid))
1016             self.assertTrue(AbstractLink(link.attid, link.flags,
1017                                          link.identifier.guid,
1018                                          target_guid) in expected_links)
1019
1020         has_inactive.sort(cmp=_linked_attribute_compare)
1021
1022         # assert the two arrays are the same
1023         self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1024         self.assertEqual([x[0] for x in has_inactive], ctr.linked_attributes)
1025
1026     def test_sort_behaviour_ncchanges(self):
1027         """Testing sorting behaviour on a group of objects."""
1028         user1_dn = "cn=test_user1,%s" % self.ou
1029         group_dn = "cn=test_group,%s" % self.ou
1030         self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
1031         self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
1032
1033         self.add_linked_attribute(group_dn, user1_dn,
1034                                   attr='member')
1035
1036         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1037
1038         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1039
1040         # Make sure the max objects count is high enough
1041         req8 = self._exop_req8(dest_dsa=None,
1042                                invocation_id=dc_guid_1,
1043                                nc_dn_str=self.base_dn,
1044                                replica_flags=0,
1045                                max_objects=100,
1046                                exop=drsuapi.DRSUAPI_EXOP_NONE)
1047
1048         # Loop until we get linked attributes, or we get to the end.
1049         # Samba sends linked attributes at the end, unlike Windows.
1050         while True:
1051             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1052             if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1053                 break
1054             req8.highwatermark = ctr.new_highwatermark
1055
1056         self.assertTrue(ctr.linked_attributes_count != 0)
1057
1058         no_inactive = []
1059         for link in ctr.linked_attributes:
1060             try:
1061                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1062                                      link.value.blob).guid
1063             except:
1064                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1065                                          link.value.blob).guid
1066             no_inactive.append((link, target_guid))
1067
1068         no_inactive.sort(cmp=_linked_attribute_compare)
1069
1070         # assert the two arrays are the same
1071         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)