s4/torture/drs/python: Py2/Py2 fix tab/space also incorrect unicode usage
[samba.git] / source4 / torture / drs / python / drs_base.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Unix SMB/CIFS implementation.
5 # Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2011
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2016
7 # Copyright (C) Catalyst IT Ltd. 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 from __future__ import print_function
24 import sys
25 import time
26 import os
27 import ldb
28
29 sys.path.insert(0, "bin/python")
30 import samba.tests
31 from samba.tests.samba_tool.base import SambaToolCmdTest
32 from samba import dsdb
33 from samba.dcerpc import drsuapi, misc, drsblobs, security
34 from samba.ndr import ndr_unpack, ndr_pack
35 from samba.drs_utils import drs_DsBind
36 from samba import gensec
37 from ldb import (
38     SCOPE_BASE,
39     Message,
40     FLAG_MOD_REPLACE,
41     )
42
43
44 class DrsBaseTestCase(SambaToolCmdTest):
45     """Base class implementation for all DRS python tests.
46        It is intended to provide common initialization and
47        and functionality used by all DRS tests in drs/python
48        test package. For instance, DC1 and DC2 are always used
49        to pass URLs for DCs to test against"""
50
51     def setUp(self):
52         super(DrsBaseTestCase, self).setUp()
53         creds = self.get_credentials()
54         creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
55
56         # connect to DCs
57         url_dc = samba.tests.env_get_var_value("DC1")
58         (self.ldb_dc1, self.info_dc1) = samba.tests.connect_samdb_ex(url_dc,
59                                                                      ldap_only=True)
60         url_dc = samba.tests.env_get_var_value("DC2")
61         (self.ldb_dc2, self.info_dc2) = samba.tests.connect_samdb_ex(url_dc,
62                                                                      ldap_only=True)
63         self.test_ldb_dc = self.ldb_dc1
64
65         # cache some of RootDSE props
66         self.schema_dn = self.info_dc1["schemaNamingContext"][0]
67         self.domain_dn = self.info_dc1["defaultNamingContext"][0]
68         self.config_dn = self.info_dc1["configurationNamingContext"][0]
69         self.forest_level = int(self.info_dc1["forestFunctionality"][0])
70
71         # we will need DCs DNS names for 'samba-tool drs' command
72         self.dnsname_dc1 = self.info_dc1["dnsHostName"][0]
73         self.dnsname_dc2 = self.info_dc2["dnsHostName"][0]
74
75         # for debugging the test code
76         self._debug = False
77
78     def tearDown(self):
79         super(DrsBaseTestCase, self).tearDown()
80
81     def set_test_ldb_dc(self, ldb_dc):
82         """Sets which DC's LDB we perform operations on during the test"""
83         self.test_ldb_dc = ldb_dc
84
85     def _GUID_string(self, guid):
86         return self.test_ldb_dc.schema_format_value("objectGUID", guid)
87
88     def _ldap_schemaUpdateNow(self, sam_db):
89         rec = {"dn": "",
90                "schemaUpdateNow": "1"}
91         m = Message.from_dict(sam_db, rec, FLAG_MOD_REPLACE)
92         sam_db.modify(m)
93
94     def _deleted_objects_dn(self, sam_ldb):
95         wkdn = "<WKGUID=18E2EA80684F11D2B9AA00C04F79F805,%s>" % self.domain_dn
96         res = sam_ldb.search(base=wkdn,
97                              scope=SCOPE_BASE,
98                              controls=["show_deleted:1"])
99         self.assertEquals(len(res), 1)
100         return str(res[0]["dn"])
101
102     def _lost_and_found_dn(self, sam_ldb, nc):
103         wkdn = "<WKGUID=%s,%s>" % (dsdb.DS_GUID_LOSTANDFOUND_CONTAINER, nc)
104         res = sam_ldb.search(base=wkdn,
105                              scope=SCOPE_BASE)
106         self.assertEquals(len(res), 1)
107         return str(res[0]["dn"])
108
109     def _make_obj_name(self, prefix):
110         return prefix + time.strftime("%s", time.gmtime())
111
112     def _samba_tool_cmd_list(self, drs_command):
113         # make command line credentials string
114
115         ccache_name = self.get_creds_ccache_name()
116
117         # Tunnel the command line credentials down to the
118         # subcommand to avoid a new kinit
119         cmdline_auth = "--krb5-ccache=%s" % ccache_name
120
121         # bin/samba-tool drs <drs_command> <cmdline_auth>
122         return ["drs", drs_command, cmdline_auth]
123
124     def _net_drs_replicate(self, DC, fromDC, nc_dn=None, forced=True,
125                            local=False, full_sync=False, single=False):
126         if nc_dn is None:
127             nc_dn = self.domain_dn
128         # make base command line
129         samba_tool_cmdline = self._samba_tool_cmd_list("replicate")
130         # bin/samba-tool drs replicate <Dest_DC_NAME> <Src_DC_NAME> <Naming Context>
131         samba_tool_cmdline += [DC, fromDC, nc_dn]
132
133         if forced:
134             samba_tool_cmdline += ["--sync-forced"]
135         if local:
136             samba_tool_cmdline += ["--local"]
137         if full_sync:
138             samba_tool_cmdline += ["--full-sync"]
139         if single:
140             samba_tool_cmdline += ["--single-object"]
141
142         (result, out, err) = self.runsubcmd(*samba_tool_cmdline)
143         self.assertCmdSuccess(result, out, err)
144         self.assertEquals(err,"","Shouldn't be any error messages")
145
146     def _enable_inbound_repl(self, DC):
147         # make base command line
148         samba_tool_cmd = self._samba_tool_cmd_list("options")
149         # disable replication
150         samba_tool_cmd += [DC, "--dsa-option=-DISABLE_INBOUND_REPL"]
151         (result, out, err) = self.runsubcmd(*samba_tool_cmd)
152         self.assertCmdSuccess(result, out, err)
153         self.assertEquals(err,"","Shouldn't be any error messages")
154
155     def _disable_inbound_repl(self, DC):
156         # make base command line
157         samba_tool_cmd = self._samba_tool_cmd_list("options")
158         # disable replication
159         samba_tool_cmd += [DC, "--dsa-option=+DISABLE_INBOUND_REPL"]
160         (result, out, err) = self.runsubcmd(*samba_tool_cmd)
161         self.assertCmdSuccess(result, out, err)
162         self.assertEquals(err,"","Shouldn't be any error messages")
163
164     def _enable_all_repl(self, DC):
165         self._enable_inbound_repl(DC)
166         # make base command line
167         samba_tool_cmd = self._samba_tool_cmd_list("options")
168         # enable replication
169         samba_tool_cmd += [DC, "--dsa-option=-DISABLE_OUTBOUND_REPL"]
170         (result, out, err) = self.runsubcmd(*samba_tool_cmd)
171         self.assertCmdSuccess(result, out, err)
172         self.assertEquals(err,"","Shouldn't be any error messages")
173
174     def _disable_all_repl(self, DC):
175         self._disable_inbound_repl(DC)
176         # make base command line
177         samba_tool_cmd = self._samba_tool_cmd_list("options")
178         # disable replication
179         samba_tool_cmd += [DC, "--dsa-option=+DISABLE_OUTBOUND_REPL"]
180         (result, out, err) = self.runsubcmd(*samba_tool_cmd)
181         self.assertCmdSuccess(result, out, err)
182         self.assertEquals(err,"","Shouldn't be any error messages")
183
184     def _get_highest_hwm_utdv(self, ldb_conn):
185         res = ldb_conn.search("", scope=ldb.SCOPE_BASE, attrs=["highestCommittedUSN"])
186         hwm = drsuapi.DsReplicaHighWaterMark()
187         hwm.tmp_highest_usn = long(res[0]["highestCommittedUSN"][0])
188         hwm.reserved_usn = 0
189         hwm.highest_usn = hwm.tmp_highest_usn
190
191         utdv = drsuapi.DsReplicaCursorCtrEx()
192         cursors = []
193         c1 = drsuapi.DsReplicaCursor()
194         c1.source_dsa_invocation_id = misc.GUID(ldb_conn.get_invocation_id())
195         c1.highest_usn = hwm.highest_usn
196         cursors.append(c1)
197         utdv.count = len(cursors)
198         utdv.cursors = cursors
199         return (hwm, utdv)
200
201     def _get_identifier(self, ldb_conn, dn):
202         res = ldb_conn.search(dn, scope=ldb.SCOPE_BASE,
203                 attrs=["objectGUID", "objectSid"])
204         id = drsuapi.DsReplicaObjectIdentifier()
205         id.guid = ndr_unpack(misc.GUID, res[0]['objectGUID'][0])
206         if "objectSid" in res[0]:
207             id.sid = ndr_unpack(security.dom_sid, res[0]['objectSid'][0])
208         id.dn = str(res[0].dn)
209         return id
210
211     def _get_ctr6_links(self, ctr6):
212         """
213         Unpacks the linked attributes from a DsGetNCChanges response
214         and returns them as a list.
215         """
216         ctr6_links = []
217         for lidx in range(0, ctr6.linked_attributes_count):
218             l = ctr6.linked_attributes[lidx]
219             try:
220                 target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
221                                     l.value.blob)
222             except:
223                 target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
224                                     l.value.blob)
225             al = AbstractLink(l.attid, l.flags,
226                               l.identifier.guid,
227                               target.guid, target.dn)
228             ctr6_links.append(al)
229
230         return ctr6_links
231
232     def _get_ctr6_object_guids(self, ctr6):
233         """Returns all the object GUIDs in a GetNCChanges response"""
234         guid_list = []
235
236         obj = ctr6.first_object
237         for i in range(0, ctr6.object_count):
238             guid_list.append(str(obj.object.identifier.guid))
239             obj = obj.next_object
240
241         return guid_list
242
243     def _ctr6_debug(self, ctr6):
244         """
245         Displays basic info contained in a DsGetNCChanges response.
246         Having this debug code allows us to see the difference in behaviour
247         between Samba and Windows easier. Turn on the self._debug flag to see it.
248         """
249
250         if self._debug:
251             print("------------ recvd CTR6 -------------")
252
253             next_object = ctr6.first_object
254             for i in range(0, ctr6.object_count):
255                 print("Obj %d: %s %s" %(i, next_object.object.identifier.dn[:25],
256                                         next_object.object.identifier.guid))
257                 next_object = next_object.next_object
258
259             print("Linked Attributes: %d" % ctr6.linked_attributes_count)
260             for lidx in range(0, ctr6.linked_attributes_count):
261                 l = ctr6.linked_attributes[lidx]
262                 try:
263                     target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
264                                         l.value.blob)
265                 except:
266                     target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
267                                         l.value.blob)
268
269                 print("Link Tgt %s... <-- Src %s"
270                       %(target.dn[:25], l.identifier.guid))
271                 state = "Del"
272                 if l.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE:
273                     state = "Act"
274                 print("  v%u %s changed %u" %(l.meta_data.version, state,
275                     l.meta_data.originating_change_time))
276
277             print("HWM:     %d" %(ctr6.new_highwatermark.highest_usn))
278             print("Tmp HWM: %d" %(ctr6.new_highwatermark.tmp_highest_usn))
279             print("More data: %d" %(ctr6.more_data))
280
281     def _get_replication(self, replica_flags,
282                           drs_error=drsuapi.DRSUAPI_EXOP_ERR_NONE, drs=None, drs_handle=None,
283                           highwatermark=None, uptodateness_vector=None,
284                           more_flags=0, max_objects=133, exop=0,
285                           dest_dsa=drsuapi.DRSUAPI_DS_BIND_GUID_W2K3,
286                           source_dsa=None, invocation_id=None, nc_dn_str=None):
287         """
288         Builds a DsGetNCChanges request based on the information provided
289         and returns the response received from the DC.
290         """
291         if source_dsa is None:
292             source_dsa = self.test_ldb_dc.get_ntds_GUID()
293         if invocation_id is None:
294             invocation_id = self.test_ldb_dc.get_invocation_id()
295         if nc_dn_str is None:
296             nc_dn_str = self.test_ldb_dc.domain_dn()
297
298         if highwatermark is None:
299             if self.default_hwm is None:
300                 (highwatermark, _) = self._get_highest_hwm_utdv(self.test_ldb_dc)
301             else:
302                 highwatermark = self.default_hwm
303
304         if drs is None:
305             drs = self.drs
306         if drs_handle is None:
307             drs_handle = self.drs_handle
308
309         req10 = self._getnc_req10(dest_dsa=dest_dsa,
310                                   invocation_id=invocation_id,
311                                   nc_dn_str=nc_dn_str,
312                                   exop=exop,
313                                   max_objects=max_objects,
314                                   replica_flags=replica_flags,
315                                   more_flags=more_flags)
316         req10.highwatermark = highwatermark
317         if uptodateness_vector is not None:
318             uptodateness_vector_v1 = drsuapi.DsReplicaCursorCtrEx()
319             cursors = []
320             for i in xrange(0, uptodateness_vector.count):
321                 c = uptodateness_vector.cursors[i]
322                 c1 = drsuapi.DsReplicaCursor()
323                 c1.source_dsa_invocation_id = c.source_dsa_invocation_id
324                 c1.highest_usn = c.highest_usn
325                 cursors.append(c1)
326             uptodateness_vector_v1.count = len(cursors)
327             uptodateness_vector_v1.cursors = cursors
328             req10.uptodateness_vector = uptodateness_vector_v1
329         (level, ctr) = drs.DsGetNCChanges(drs_handle, 10, req10)
330         self._ctr6_debug(ctr)
331
332         self.assertEqual(level, 6, "expected level 6 response!")
333         self.assertEqual(ctr.source_dsa_guid, misc.GUID(source_dsa))
334         self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(invocation_id))
335         self.assertEqual(ctr.extended_ret, drs_error)
336
337         return ctr
338
339     def _check_replication(self, expected_dns, replica_flags, expected_links=[],
340                            drs_error=drsuapi.DRSUAPI_EXOP_ERR_NONE, drs=None, drs_handle=None,
341                            highwatermark=None, uptodateness_vector=None,
342                            more_flags=0, more_data=False,
343                            dn_ordered=True, links_ordered=True,
344                            max_objects=133, exop=0,
345                            dest_dsa=drsuapi.DRSUAPI_DS_BIND_GUID_W2K3,
346                            source_dsa=None, invocation_id=None, nc_dn_str=None,
347                            nc_object_count=0, nc_linked_attributes_count=0):
348         """
349         Makes sure that replication returns the specific error given.
350         """
351
352         # send a DsGetNCChanges to the DC
353         ctr6 = self._get_replication(replica_flags,
354                                      drs_error, drs, drs_handle,
355                                      highwatermark, uptodateness_vector,
356                                      more_flags, max_objects, exop, dest_dsa,
357                                      source_dsa, invocation_id, nc_dn_str)
358
359         # check the response is what we expect
360         self._check_ctr6(ctr6, expected_dns, expected_links,
361                          nc_object_count=nc_object_count, more_data=more_data,
362                          dn_ordered=dn_ordered)
363         return (ctr6.new_highwatermark, ctr6.uptodateness_vector)
364
365
366     def _get_ctr6_dn_list(self, ctr6):
367         """
368         Returns the DNs contained in a DsGetNCChanges response.
369         """
370         dn_list = []
371         next_object = ctr6.first_object
372         for i in range(0, ctr6.object_count):
373             dn_list.append(next_object.object.identifier.dn)
374             next_object = next_object.next_object
375         self.assertEqual(next_object, None)
376
377         return dn_list
378
379
380     def _check_ctr6(self, ctr6, expected_dns=[], expected_links=[],
381                     dn_ordered=True, links_ordered=True,
382                     more_data=False, nc_object_count=0,
383                     nc_linked_attributes_count=0, drs_error=0):
384         """
385         Check that a ctr6 matches the specified parameters.
386         """
387         self.assertEqual(ctr6.object_count, len(expected_dns))
388         self.assertEqual(ctr6.linked_attributes_count, len(expected_links))
389         self.assertEqual(ctr6.more_data, more_data)
390         self.assertEqual(ctr6.nc_object_count, nc_object_count)
391         self.assertEqual(ctr6.nc_linked_attributes_count, nc_linked_attributes_count)
392         self.assertEqual(ctr6.drs_error[0], drs_error)
393
394         ctr6_dns = self._get_ctr6_dn_list(ctr6)
395
396         i = 0
397         for dn in expected_dns:
398             # Expect them back in the exact same order as specified.
399             if dn_ordered:
400                 self.assertNotEqual(ctr6_dns[i], None)
401                 self.assertEqual(ctr6_dns[i], dn)
402                 i = i + 1
403             # Don't care what order
404             else:
405                 self.assertTrue(dn in ctr6_dns, "Couldn't find DN '%s' anywhere in ctr6 response." % dn)
406
407         # Extract the links from the response
408         ctr6_links = self._get_ctr6_links(ctr6)
409         expected_links.sort()
410
411         lidx = 0
412         for el in expected_links:
413             if links_ordered:
414                 self.assertEqual(el, ctr6_links[lidx])
415                 lidx += 1
416             else:
417                 self.assertTrue(el in ctr6_links, "Couldn't find link '%s' anywhere in ctr6 response." % el)
418
419     def _exop_req8(self, dest_dsa, invocation_id, nc_dn_str, exop,
420                    replica_flags=0, max_objects=0, partial_attribute_set=None,
421                    partial_attribute_set_ex=None, mapping_ctr=None):
422         req8 = drsuapi.DsGetNCChangesRequest8()
423
424         req8.destination_dsa_guid = misc.GUID(dest_dsa) if dest_dsa else misc.GUID()
425         req8.source_dsa_invocation_id = misc.GUID(invocation_id)
426         req8.naming_context = drsuapi.DsReplicaObjectIdentifier()
427         req8.naming_context.dn = str(nc_dn_str)
428         req8.highwatermark = drsuapi.DsReplicaHighWaterMark()
429         req8.highwatermark.tmp_highest_usn = 0
430         req8.highwatermark.reserved_usn = 0
431         req8.highwatermark.highest_usn = 0
432         req8.uptodateness_vector = None
433         req8.replica_flags = replica_flags
434         req8.max_object_count = max_objects
435         req8.max_ndr_size = 402116
436         req8.extended_op = exop
437         req8.fsmo_info = 0
438         req8.partial_attribute_set = partial_attribute_set
439         req8.partial_attribute_set_ex = partial_attribute_set_ex
440         if mapping_ctr:
441             req8.mapping_ctr = mapping_ctr
442         else:
443             req8.mapping_ctr.num_mappings = 0
444             req8.mapping_ctr.mappings = None
445
446         return req8
447
448     def _getnc_req10(self, dest_dsa, invocation_id, nc_dn_str, exop,
449                      replica_flags=0, max_objects=0, partial_attribute_set=None,
450                      partial_attribute_set_ex=None, mapping_ctr=None,
451                      more_flags=0):
452         req10 = drsuapi.DsGetNCChangesRequest10()
453
454         req10.destination_dsa_guid = misc.GUID(dest_dsa) if dest_dsa else misc.GUID()
455         req10.source_dsa_invocation_id = misc.GUID(invocation_id)
456         req10.naming_context = drsuapi.DsReplicaObjectIdentifier()
457         req10.naming_context.dn = str(nc_dn_str)
458         req10.highwatermark = drsuapi.DsReplicaHighWaterMark()
459         req10.highwatermark.tmp_highest_usn = 0
460         req10.highwatermark.reserved_usn = 0
461         req10.highwatermark.highest_usn = 0
462         req10.uptodateness_vector = None
463         req10.replica_flags = replica_flags
464         req10.max_object_count = max_objects
465         req10.max_ndr_size = 402116
466         req10.extended_op = exop
467         req10.fsmo_info = 0
468         req10.partial_attribute_set = partial_attribute_set
469         req10.partial_attribute_set_ex = partial_attribute_set_ex
470         if mapping_ctr:
471             req10.mapping_ctr = mapping_ctr
472         else:
473             req10.mapping_ctr.num_mappings = 0
474             req10.mapping_ctr.mappings = None
475         req10.more_flags = more_flags
476
477         return req10
478
479     def _ds_bind(self, server_name, creds=None):
480         binding_str = "ncacn_ip_tcp:%s[seal]" % server_name
481
482         if creds is None:
483             creds = self.get_credentials()
484         drs = drsuapi.drsuapi(binding_str, self.get_loadparm(), creds)
485         (drs_handle, supported_extensions) = drs_DsBind(drs)
486         return (drs, drs_handle)
487
488     def get_partial_attribute_set(self, attids=[drsuapi.DRSUAPI_ATTID_objectClass]):
489         partial_attribute_set = drsuapi.DsPartialAttributeSet()
490         partial_attribute_set.attids = attids
491         partial_attribute_set.num_attids = len(attids)
492         return partial_attribute_set
493
494
495
496 class AbstractLink:
497     def __init__(self, attid, flags, identifier, targetGUID,
498                  targetDN=""):
499         self.attid = attid
500         self.flags = flags
501         self.identifier = str(identifier)
502         self.selfGUID_blob = ndr_pack(identifier)
503         self.targetGUID = str(targetGUID)
504         self.targetGUID_blob = ndr_pack(targetGUID)
505         self.targetDN = targetDN
506
507     def __repr__(self):
508         return "AbstractLink(0x%08x, 0x%08x, %s, %s)" % (
509                 self.attid, self.flags, self.identifier, self.targetGUID)
510
511     def __internal_cmp__(self, other, verbose=False):
512         """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
513         if not isinstance(other, AbstractLink):
514             if verbose:
515                 print("AbstractLink.__internal_cmp__(%r, %r) => wrong type" % (self, other))
516             return NotImplemented
517
518         c = cmp(self.selfGUID_blob, other.selfGUID_blob)
519         if c != 0:
520             if verbose:
521                 print("AbstractLink.__internal_cmp__(%r, %r) => %d different identifier" % (self, other, c))
522             return c
523
524         c = other.attid - self.attid
525         if c != 0:
526             if verbose:
527                 print("AbstractLink.__internal_cmp__(%r, %r) => %d different attid" % (self, other, c))
528             return c
529
530         self_active = self.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
531         other_active = other.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
532
533         c = self_active - other_active
534         if c != 0:
535             if verbose:
536                 print("AbstractLink.__internal_cmp__(%r, %r) => %d different FLAG_ACTIVE" % (self, other, c))
537             return c
538
539         c = cmp(self.targetGUID_blob, other.targetGUID_blob)
540         if c != 0:
541             if verbose:
542                 print("AbstractLink.__internal_cmp__(%r, %r) => %d different target" % (self, other, c))
543             return c
544
545         c = self.flags - other.flags
546         if c != 0:
547             if verbose:
548                 print("AbstractLink.__internal_cmp__(%r, %r) => %d different flags" % (self, other, c))
549             return c
550
551         return 0
552
553     def __lt__(self, other):
554         c = self.__internal_cmp__(other)
555         if c == NotImplemented:
556             return NotImplemented
557         if c < 0:
558             return True
559         return False
560
561     def __le__(self, other):
562         c = self.__internal_cmp__(other)
563         if c == NotImplemented:
564             return NotImplemented
565         if c <= 0:
566             return True
567         return False
568
569     def __eq__(self, other):
570         c = self.__internal_cmp__(other, verbose=True)
571         if c == NotImplemented:
572             return NotImplemented
573         if c == 0:
574             return True
575         return False
576
577     def __ne__(self, other):
578         c = self.__internal_cmp__(other)
579         if c == NotImplemented:
580             return NotImplemented
581         if c != 0:
582             return True
583         return False
584
585     def __gt__(self, other):
586         c = self.__internal_cmp__(other)
587         if c == NotImplemented:
588             return NotImplemented
589         if c > 0:
590             return True
591         return False
592
593     def __ge__(self, other):
594         c = self.__internal_cmp__(other)
595         if c == NotImplemented:
596             return NotImplemented
597         if c >= 0:
598             return True
599         return False
600
601     def __hash__(self):
602         return hash((self.attid, self.flags, self.identifier, self.targetGUID))