Package ldaptor :: Package protocols :: Package ldap :: Module ldapclient
[hide private]
[frames] | no frames]

Source Code for Module ldaptor.protocols.ldap.ldapclient

  1  # Copyright (C) 2001 Tommi Virtanen 
  2  # 
  3  # This library is free software; you can redistribute it and/or 
  4  # modify it under the terms of version 2.1 of the GNU Lesser General Public 
  5  # License as published by the Free Software Foundation. 
  6  # 
  7  # This library is distributed in the hope that it will be useful, 
  8  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  9  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 10  # Lesser General Public License for more details. 
 11  # 
 12  # You should have received a copy of the GNU Lesser General Public 
 13  # License along with this library; if not, write to the Free Software 
 14  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 15   
 16  """LDAP protocol client""" 
 17   
 18  from ldaptor.protocols import pureldap, pureber 
 19  from ldaptor.protocols.ldap import ldaperrors 
 20   
 21  from twisted.python import log 
 22  from twisted.internet import protocol, defer, ssl, reactor 
 23   
24 -class LDAPClientConnectionLostException(ldaperrors.LDAPException):
25 - def __str__(self):
26 return 'Connection lost'
27
28 -class LDAPStartTLSBusyError(ldaperrors.LDAPOperationsError):
29 - def __init__(self, onwire, message=None):
30 self.onwire = onwire 31 ldaperrors.LDAPOperationsError.__init__(self, message=message)
32
33 - def __str__(self):
34 return 'Cannot STARTTLS while operations on wire: %r' % self.onwire
35
36 -class LDAPClient(protocol.Protocol):
37 """An LDAP client""" 38 debug = False 39
40 - def __init__(self):
41 self.onwire = {} 42 self.buffer = '' 43 self.connected = None
44 45 berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( 46 inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( 47 fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), 48 inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()))) 49
50 - def dataReceived(self, recd):
51 self.buffer += recd 52 while 1: 53 try: 54 o, bytes = pureber.berDecodeObject(self.berdecoder, self.buffer) 55 except pureber.BERExceptionInsufficientData: 56 o, bytes = None, 0 57 self.buffer = self.buffer[bytes:] 58 if not o: 59 break 60 self.handle(o)
61
62 - def connectionMade(self):
63 """TCP connection has opened""" 64 self.connected = 1
65
66 - def connectionLost(self, reason=protocol.connectionDone):
67 """Called when TCP connection has been lost""" 68 self.connected = 0 69 # notify handlers of operations in flight 70 while self.onwire: 71 k, v = self.onwire.popitem() 72 d, _, _, _ = v 73 d.errback(reason)
74
75 - def _send(self, op):
76 if not self.connected: 77 raise LDAPClientConnectionLostException() 78 msg=pureldap.LDAPMessage(op) 79 if self.debug: 80 log.debug('C->S %s' % repr(msg)) 81 assert not self.onwire.has_key(msg.id) 82 return msg
83
84 - def _cbSend(self, msg, d):
85 d.callback(msg) 86 return True
87
88 - def send(self, op):
89 """ 90 Send an LDAP operation to the server. 91 92 @param op: the operation to send 93 94 @type op: LDAPProtocolRequest 95 96 @return: the response from server 97 98 @rtype: Deferred LDAPProtocolResponse 99 """ 100 msg = self._send(op) 101 assert op.needs_answer 102 d = defer.Deferred() 103 self.onwire[msg.id]=(d, None, None, None) 104 self.transport.write(str(msg)) 105 return d
106
107 - def send_multiResponse(self, op, handler, *args, **kwargs):
108 """ 109 Send an LDAP operation to the server, expecting one or more 110 responses. 111 112 @param op: the operation to send 113 114 @type op: LDAPProtocolRequest 115 116 @param handler: a callable that will be called for each 117 response. It should return a boolean, whether this was the 118 final response. 119 120 @param args: positional arguments to pass to handler 121 122 @param kwargs: keyword arguments to pass to handler 123 124 @return: the result from the last handler as a deferred that 125 completes when the last response has been received 126 127 @rtype: Deferred LDAPProtocolResponse 128 """ 129 msg = self._send(op) 130 assert op.needs_answer 131 d = defer.Deferred() 132 self.onwire[msg.id]=(d, handler, args, kwargs) 133 self.transport.write(str(msg)) 134 return d
135
136 - def send_noResponse(self, op):
137 """ 138 Send an LDAP operation to the server, with no response 139 expected. 140 141 @param op: the operation to send 142 @type op: LDAPProtocolRequest 143 """ 144 msg = self._send(op) 145 assert not op.needs_answer 146 self.transport.write(str(msg))
147
148 - def unsolicitedNotification(self, msg):
149 log.msg("Got unsolicited notification: %s" % repr(msg))
150
151 - def handle(self, msg):
152 assert isinstance(msg.value, pureldap.LDAPProtocolResponse) 153 if self.debug: 154 log.debug('C<-S %s' % repr(msg)) 155 156 if msg.id==0: 157 self.unsolicitedNotification(msg.value) 158 else: 159 d, handler, args, kwargs = self.onwire[msg.id] 160 161 if handler is None: 162 assert args is None 163 assert kwargs is None 164 d.callback(msg.value) 165 del self.onwire[msg.id] 166 else: 167 assert args is not None 168 assert kwargs is not None 169 # Return true to mark request as fully handled 170 if handler(msg.value, *args, **kwargs): 171 del self.onwire[msg.id]
172 173 ##Bind
174 - def bind(self, dn='', auth=''):
175 """ 176 @depreciated: Use e.bind(auth). 177 178 @todo: Remove this method when there are no callers. 179 """ 180 if not self.connected: 181 raise LDAPClientConnectionLostException() 182 else: 183 r=pureldap.LDAPBindRequest(dn=dn, auth=auth) 184 d = self.send(r) 185 d.addCallback(self._handle_bind_msg) 186 return d
187
188 - def _handle_bind_msg(self, msg):
189 assert isinstance(msg, pureldap.LDAPBindResponse) 190 assert msg.referral is None #TODO 191 if msg.resultCode!=ldaperrors.Success.resultCode: 192 raise ldaperrors.get(msg.resultCode, msg.errorMessage) 193 return (msg.matchedDN, msg.serverSaslCreds)
194 195 ##Unbind
196 - def unbind(self):
197 if not self.connected: 198 raise Exception("Not connected (TODO)") #TODO make this a real object 199 r=pureldap.LDAPUnbindRequest() 200 self.send_noResponse(r) 201 self.transport.loseConnection()
202
203 - def _cbStartTLS(self, msg, ctx):
204 assert isinstance(msg, pureldap.LDAPExtendedResponse) 205 assert msg.referral is None #TODO 206 if msg.resultCode!=ldaperrors.Success.resultCode: 207 raise ldaperrors.get(msg.resultCode, msg.errorMessage) 208 209 self.transport.startTLS(ctx) 210 return self
211
212 - def startTLS(self, ctx=None):
213 """ 214 Start Transport Layer Security. 215 216 It is the callers responsibility to make sure other things 217 are not happening at the same time. 218 219 @todo: server hostname check, see rfc2830 section 3.6. 220 221 """ 222 if ctx is None: 223 ctx = ssl.ClientContextFactory() 224 # we always delay by one event loop iteration to make 225 # sure the previous handler has exited and self.onwire 226 # has been cleaned up 227 d=defer.Deferred() 228 d.addCallback(self._startTLS) 229 reactor.callLater(0, d.callback, ctx) 230 return d
231
232 - def _startTLS(self, ctx):
233 if not self.connected: 234 raise LDAPClientConnectionLostException 235 elif self.onwire: 236 raise LDAPStartTLSBusyError, self.onwire 237 else: 238 op=pureldap.LDAPStartTLSRequest() 239 d = self.send(op) 240 d.addCallback(self._cbStartTLS, ctx) 241 return d
242