diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 0de5cec4..f6ac2d1a 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -314,34 +314,35 @@ def __init__(self, encodedMessage): self.refreshPresent = None self.syncIdSet = None - for attr in ['newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']: - comp = d[0].getComponentByName(attr) - - if comp is not None and comp.hasValue(): - - if attr == 'newcookie': - self.newcookie = str(comp) - return + # Due to the way pyasn1 works, refreshDelete and refreshPresent are both + # valid in the components as they are fully populated defaults. We must + # get the component directly from the message, not by iteration. + attr = d[0].getName() + comp = d[0].getComponent() + + if comp is not None and comp.hasValue(): + if attr == 'newcookie': + self.newcookie = str(comp) + return - val = {} + val = {} - cookie = comp.getComponentByName('cookie') - if cookie.hasValue(): - val['cookie'] = str(cookie) + cookie = comp.getComponentByName('cookie') + if cookie.hasValue(): + val['cookie'] = str(cookie) - if attr.startswith('refresh'): - val['refreshDone'] = bool(comp.getComponentByName('refreshDone')) - elif attr == 'syncIdSet': - uuids = [] - ids = comp.getComponentByName('syncUUIDs') - for i in range(len(ids)): - uuid = UUID(bytes=bytes(ids.getComponentByPosition(i))) - uuids.append(str(uuid)) - val['syncUUIDs'] = uuids - val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes')) + if attr.startswith('refresh'): + val['refreshDone'] = bool(comp.getComponentByName('refreshDone')) + elif attr == 'syncIdSet': + uuids = [] + ids = comp.getComponentByName('syncUUIDs') + for i in range(len(ids)): + uuid = UUID(bytes=bytes(ids.getComponentByPosition(i))) + uuids.append(str(uuid)) + val['syncUUIDs'] = uuids + val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes')) - setattr(self, attr, val) - return + setattr(self, attr, val) class SyncreplConsumer: diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 9398de5b..b8a6ab63 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -10,6 +10,7 @@ import shelve import sys import unittest +import binascii if sys.version_info[0] <= 2: PY2 = True @@ -21,7 +22,7 @@ import ldap from ldap.ldapobject import SimpleLDAPObject -from ldap.syncrepl import SyncreplConsumer +from ldap.syncrepl import SyncreplConsumer, SyncInfoMessage from slapdtest import SlapdObject, SlapdTestCase @@ -445,6 +446,47 @@ def setUp(self): ) self.suffix = self.server.suffix.encode('utf-8') +class DecodeSyncreplProtoTests(unittest.TestCase): + """ + Tests of the ASN.1 decoder for tricky cases or past issues to ensure that + syncrepl messages are handled correctly. + """ + + def test_syncidset_message(self): + """ + A syncrepl server may send a sync info message, with a syncIdSet + of uuids to delete. A regression was found in the original + sync info message implementation due to how the choice was + evaluated, because refreshPresent and refreshDelete were both + able to be fully expressed as defaults, causing the parser + to mistakenly catch a syncIdSet as a refreshPresent/refereshDelete. + + This tests that a syncIdSet request is properly decoded. + + reference: https://tools.ietf.org/html/rfc4533#section-2.5 + """ + + # This is a dump of a syncidset message from wireshark + 389-ds + msg = """ + a36b04526c6461706b64632e6578616d706c652e636f6d3a333839303123636e + 3d6469726563746f7279206d616e616765723a64633d6578616d706c652c6463 + 3d636f6d3a286f626a656374436c6173733d2a2923330101ff311204108dc446 + 01a93611ea8aaff248c5fa5780 + """.replace(' ', '').replace('\n', '') + + msgraw = binascii.unhexlify(msg) + sim = SyncInfoMessage(msgraw) + self.assertEqual(sim.refreshDelete, None) + self.assertEqual(sim.refreshPresent, None) + self.assertEqual(sim.newcookie, None) + self.assertEqual(sim.syncIdSet, + { + 'cookie': 'ldapkdc.example.com:38901#cn=directory manager:dc=example,dc=com:(objectClass=*)#3', + 'syncUUIDs': ['8dc44601-a936-11ea-8aaf-f248c5fa5780'], + 'refreshDeletes': True + } + ) + if __name__ == '__main__': unittest.main()
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: