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

Source Code for Module ldaptor.protocols.ldap.ldapclient

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