Source code for ldaptor.protocols.ldap.ldapclient

"""LDAP protocol client"""

from ldaptor.protocols import pureldap, pureber
from ldaptor.protocols.ldap import ldaperrors

from twisted.python import log
from twisted.internet import protocol, defer, ssl, reactor


[docs]class LDAPClientConnectionLostException(ldaperrors.LDAPException):
[docs] def toWire(self): return b"Connection lost"
[docs]class LDAPStartTLSBusyError(ldaperrors.LDAPOperationsError): def __init__(self, onwire, message=None): self.onwire = onwire ldaperrors.LDAPOperationsError.__init__(self, message=message)
[docs] def toWire(self): return b"Cannot STARTTLS while operations on wire: %r" % self.onwire
[docs]class LDAPStartTLSInvalidResponseName(ldaperrors.LDAPException): def __init__(self, responseName): self.responseName = responseName ldaperrors.LDAPException.__init__(self)
[docs] def toWire(self): return b"Invalid responseName in STARTTLS response: %r" % (self.responseName,)
[docs]class LDAPClient(protocol.Protocol): """An LDAP client""" debug = False def __init__(self): self.onwire = {} self.buffer = b"" self.connected = None berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext() ), inherit=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext() ), ) )
[docs] def dataReceived(self, recd): self.buffer += recd while 1: try: o, bytes = pureber.berDecodeObject(self.berdecoder, self.buffer) except pureber.BERExceptionInsufficientData: o, bytes = None, 0 self.buffer = self.buffer[bytes:] if not o: break self.handle(o)
[docs] def connectionMade(self): """TCP connection has opened""" self.connected = 1
[docs] def connectionLost(self, reason=protocol.connectionDone): """Called when TCP connection has been lost""" self.connected = 0 # notify handlers of operations in flight while self.onwire: k, v = self.onwire.popitem() d, _, _, _, _ = v d.errback(reason)
def _send(self, op, controls=None): if not self.connected: raise LDAPClientConnectionLostException() msg = pureldap.LDAPMessage(op, controls=controls) if self.debug: log.msg("C->S %s" % repr(msg)) assert msg.id not in self.onwire return msg
[docs] def send(self, op, controls=None): """ Send an LDAP operation to the server. @param op: the operation to send @type op: LDAPProtocolRequest @param controls: Any controls to be included in the request. @type controls: LDAPControls @return: the response from server @rtype: Deferred LDAPProtocolResponse """ msg = self._send(op, controls=controls) assert op.needs_answer d = defer.Deferred() self.onwire[msg.id] = (d, False, None, None, None) self.transport.write(msg.toWire()) return d
[docs] def send_multiResponse(self, op, handler, *args, **kwargs): """ Send an LDAP operation to the server, expecting one or more responses. If `handler` is provided, it will receive a LDAP response as its first argument. The Deferred returned by this function will never fire. If `handler` is not provided, the Deferred returned by this function will fire with the final LDAP response. @param op: the operation to send @type op: LDAPProtocolRequest @param handler: a callable that will be called for each response. It should return a boolean, whether this was the final response. @param args: positional arguments to pass to handler @param kwargs: keyword arguments to pass to handler @return: the result from the first handler as a deferred that completes when the first response has been received @rtype: Deferred LDAPProtocolResponse """ msg = self._send(op) assert op.needs_answer d = defer.Deferred() self.onwire[msg.id] = (d, False, handler, args, kwargs) self.transport.write(msg.toWire()) return d
[docs] def send_multiResponse_ex(self, op, controls=None, handler=None, *args, **kwargs): """ Send an LDAP operation to the server, expecting one or more responses. If `handler` is provided, it will receive a LDAP response *and* response controls as its first 2 arguments. The Deferred returned by this function will never fire. If `handler` is not provided, the Deferred returned by this function will fire with a tuple of the first LDAP response and any associated response controls. @param op: the operation to send @type op: LDAPProtocolRequest @param controls: LDAP controls to send with the message. @type controls: LDAPControls @param handler: a callable that will be called for each response. It should return a boolean, whether this was the final response. @param args: positional arguments to pass to handler @param kwargs: keyword arguments to pass to handler @return: the result from the last handler as a deferred that completes when the last response has been received @rtype: Deferred LDAPProtocolResponse """ msg = self._send(op, controls=controls) assert op.needs_answer d = defer.Deferred() self.onwire[msg.id] = (d, True, handler, args, kwargs) self.transport.write(msg.toWire()) return d
[docs] def send_noResponse(self, op, controls=None): """ Send an LDAP operation to the server, with no response expected. @param op: the operation to send @type op: LDAPProtocolRequest """ msg = self._send(op, controls=controls) assert not op.needs_answer self.transport.write(msg.toWire())
[docs] def unsolicitedNotification(self, msg): log.msg("Got unsolicited notification: %s" % repr(msg))
[docs] def handle(self, msg): assert isinstance(msg.value, pureldap.LDAPProtocolResponse) if self.debug: log.msg("C<-S %s" % repr(msg)) if msg.id == 0: self.unsolicitedNotification(msg.value) else: d, return_controls, handler, args, kwargs = self.onwire[msg.id] if handler is None: assert (args is None) or (args == ()) assert (kwargs is None) or (kwargs == {}) if return_controls: d.callback((msg.value, msg.controls)) else: d.callback(msg.value) del self.onwire[msg.id] else: assert args is not None assert kwargs is not None # Return true to mark request as fully handled if return_controls: if handler(msg.value, msg.controls, *args, **kwargs): del self.onwire[msg.id] else: if handler(msg.value, *args, **kwargs): del self.onwire[msg.id]
[docs] def bind(self, dn="", auth=""): """ @depreciated: Use e.bind(auth). @todo: Remove this method when there are no callers. """ if not self.connected: raise LDAPClientConnectionLostException() else: r = pureldap.LDAPBindRequest(dn=dn, auth=auth) d = self.send(r) d.addCallback(self._handle_bind_msg) return d
def _handle_bind_msg(self, msg): assert isinstance(msg, pureldap.LDAPBindResponse) assert msg.referral is None # TODO if msg.resultCode != ldaperrors.Success.resultCode: raise ldaperrors.get(msg.resultCode, msg.errorMessage) return (msg.matchedDN, msg.serverSaslCreds)
[docs] def unbind(self): if not self.connected: raise Exception("Not connected (TODO)") # TODO make this a real object r = pureldap.LDAPUnbindRequest() self.send_noResponse(r) self.transport.loseConnection()
def _cbStartTLS(self, msg, ctx): assert isinstance(msg, pureldap.LDAPExtendedResponse) assert msg.referral is None # TODO if msg.resultCode != ldaperrors.Success.resultCode: raise ldaperrors.get(msg.resultCode, msg.errorMessage) if (msg.responseName is not None) and ( msg.responseName != pureldap.LDAPStartTLSResponse.oid ): raise LDAPStartTLSInvalidResponseName(msg.responseName) self.transport.startTLS(ctx) return self
[docs] def startTLS(self, ctx=None): """ Start Transport Layer Security. It is the callers responsibility to make sure other things are not happening at the same time. @todo: server hostname check, see rfc2830 section 3.6. @return: a deferred that will complete when the TLS handshake is complete. """ if ctx is None: ctx = ssl.ClientContextFactory() # we always delay by one event loop iteration to make # sure the previous handler has exited and self.onwire # has been cleaned up d = defer.Deferred() d.addCallback(self._startTLS) reactor.callLater(0, d.callback, ctx) return d
def _startTLS(self, ctx): if not self.connected: raise LDAPClientConnectionLostException() elif self.onwire: raise LDAPStartTLSBusyError(self.onwire) else: op = pureldap.LDAPStartTLSRequest() d = self.send(op) d.addCallback(self._cbStartTLS, ctx) return d