Skip to content

Syncrepl fails to parse SyncInfoMessage when the message is a syncIdSet #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 25 additions & 24 deletions Lib/ldap/syncrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
44 changes: 43 additions & 1 deletion Tests/t_ldap_syncrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import shelve
import sys
import unittest
import binascii

if sys.version_info[0] <= 2:
PY2 = True
Expand All @@ -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

Expand Down Expand Up @@ -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()