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