From: Sean B. <uni...@gm...> - 2009-03-17 21:25:49
|
Hi, I've been working on implementing an RFC 4533 syncrepl consumer using python-ldap. I can't work out why I can't get the SyncDoneControl that is returned with the LDAP_RES_SEARCH_RESULT through python-ldap. The SyncDoneControl contains the cookie that is needed for stateful/minimal syncrepl refresh only, so losing that control makes the code a lot less useful. It looks like SyncStateControl is never returned to the Python code because Modules/LDAPObject.c l_ldap_result3() calls LDAPmessage_to_python() for LDAP_RES_SEARCH_ENTRY. LDAPmessage_to_python() discards controls in the result. I can live with this. I'm mystified as to why Modules/LDAPObject.c l_ldap_result3() doesn't return the SyncDoneControl with the LDAP_RES_SEARCH_RESULT. I've waded through the source and I'm pretty sure that nothing is deliberately filtering out the control. Here we can see that the control is in the result when l_ldap_result3() calls ldap_parse_result(): Breakpoint 2, ldap_parse_result (ld=0x783710, r=0x730830, errcodep=0x7fff5d8582c4, matcheddnp=0x0, errmsgp=0x0, referralsp=0x7fff5d8582a8, serverctrls=0x7fff5d8582a0, freeit=0) at error.c:261 (gdb) p *r->lm_ber $6 = {ber_opts = {lbo_valid = 2, lbo_options = 1, lbo_debug = 0}, ber_tag = 121, ber_len = 81, ber_usertag = 0, ber_buf = 0x7e3d60 "\002\001\001yL\200\0301.3.6.1.4.1.4203.1.9.1.4\2010¢.\004,csn=20090317201420Z#000000#00#000000,rid=000", ber_ptr = 0x7e3d63 "yL\200\0301.3.6.1.4.1.4203.1.9.1.4\2010¢.\004,csn=20090317201420Z#000000#00#000000,rid=000", ber_end = 0x7e3db1 "", ber_sos = 0x0, ber_rwptr = 0x0, ber_memctx = 0x0} (gdb) p *serverctrls $7 = (LDAPControl **) 0x0 Here is my sample code: class SyncRequestControl(ldap.controls.LDAPControl): # The Sync Request Control is an LDAP Control [RFC4511] where the # controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.1 and the # controlValue, an OCTET STRING, contains a BER-encoded # syncRequestValue. The criticality field is either TRUE or FALSE. # syncRequestValue ::= SEQUENCE { # mode ENUMERATED { # -- 0 unused # refreshOnly (1), # -- 2 reserved # refreshAndPersist (3) # }, # cookie syncCookie OPTIONAL, # reloadHint BOOLEAN DEFAULT FALSE # } # The Sync Request Control is only applicable to the SearchRequest Message. controlType='1.3.6.1.4.1.4203.1.9.1.1' def __init__(self, controlType='1.3.6.1.4.1.4203.1.9.1.1', criticality=False, controlValue=None, encodedControlValue=None): ldap.controls.LDAPControl.__init__(self, self.controlType, criticality, controlValue, encodedControlValue) def encodeControlValue(self, value): # 30 31 Sequence tag (len=0x31) # 0a 01 01 Enumerated (len=0x01) value 0x01 (RefreshOnly) # 04 2C Octet String (len=0x2C) value 'csn=...,rid=000' # 63 73 6e 3d 32 30 30 39 30 33 31 36 31 39 35 35 # 30 39 5a 23 30 30 30 30 30 30 23 30 30 23 30 30 # 30 30 30 30 2c 72 69 64 3d 30 30 30 mode, cookie, reload_hint = value # Enumerated (len=0x01) value 0x01 (RefreshOnly) result_content = struct.pack('BBB', 0x0A, 0x01, mode) # Octet String (optional) if cookie: result_content += struct.pack('BB', 0x04, len(cookie)) result_content += cookie # Boolean (optional) if reload_hint: result_content += struct.pack('BBB', 0x01, 0x01, 0xFF) # Sequence tag result_header = struct.pack('BB', 0x30, len(result_content)) return result_header + result_content ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) ldap.set_option(ldap.OPT_REFERRALS, 0) conn = ldap.initialize(host) sync_req_ctrl=SyncRequestControl(criticality=True, controlValue=(3, cookie, True)) op_id = conn.search_ext(base, ldap.SCOPE_ONELEVEL, attrlist=('dn',), serverctrls=(sync_req_ctrl,), ) # ldap.controls.knownLDAPControls[SyncStateControl.controlType] = SyncStateControl # ldap.controls.knownLDAPControls[SyncDoneControl.controlType] = SyncDoneControl #rtype, rdata, rmsgid, decoded_serverctrl = conn.result3(all=0, timeout=60) rtype, rdata, rmsgid, serverctrls = conn._ldap_call(conn._l.result3,_ldap.RES_ANY,0,60) while rtype: print 'rtype=%s' % rtype print 'rdata=%s' % rdata print 'rmsgid=%s' % rmsgid print 'serverctrls=%s' % serverctrls print '' # rtype, rdata, rmsgid, decoded_serverctrl = conn.result3(all=0, timeout=10) rtype, rdata, rmsgid, serverctrls = conn._ldap_call(conn._l.result3,_ldap.RES_ANY,0,10) -- Thanks, Sean Burford |
From: Sean B. <uni...@gm...> - 2009-03-17 22:53:22
|
Hi, If I switch the control from RefreshPersist(3) to RefreshOnly(1) I get an LDAP_RES_SEARCH_RESULT instead of LDAP_RES_INTERMEDIATE carrying the SyncDoneControl. Strangely enough, the LDAP_RES_SEARCH_RESULT makes it back to my code with an intact control: rtype=101 rdata=[] rmsgid=1 serverctrls=[('1.3.6.1.4.1.4203.1.9.1.3', 0, '01\x04,csn=20090317224602Z#000000#00#000000,rid=000\x01\x01\xff')] So the problem is that the control is lost if it is attached to an LDAP_RES_INTERMEDIATE, but it is not lost when it is attached to an LDAP_RES_SEARCH_RESULT. -- Thanks, Sean Burford |
From: Michael S. <mi...@st...> - 2009-10-13 08:51:20
|
Sean Burford wrote: > > I've been working on implementing an RFC 4533 syncrepl consumer using > python-ldap. I can't work out why I can't get the SyncDoneControl that > is returned with the LDAP_RES_SEARCH_RESULT through python-ldap. Sean, could you please re-try with current python-ldap CVS HEAD. I've checked in a change --------------------------- snip --------------------------- * l_ldap_result3(): controls are now parsed for all response types (SF#2829057) --------------------------- snip --------------------------- Ciao, Michael. |