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