diff --git a/src/ldapserver/__init__.py b/src/ldapserver/__init__.py index 1cec6c213e5c01e8505c8ad297c1612ec57510b3..9ac0ff51a0ffd2893e3dbbc288b5175827cb26f1 100644 --- a/src/ldapserver/__init__.py +++ b/src/ldapserver/__init__.py @@ -1,5 +1,5 @@ from . import ldap from . import dn +from . import exceptions -from .exceptions import * from .server import BaseLDAPRequestHandler, SimpleLDAPRequestHandler diff --git a/src/ldapserver/server.py b/src/ldapserver/server.py index 0e92de53990420da81f15b839ca3f7df4c61b459..101b849f3c5993977275189844da83b7a4106900 100644 --- a/src/ldapserver/server.py +++ b/src/ldapserver/server.py @@ -3,29 +3,25 @@ import hashlib import secrets import socket import ssl -from socketserver import BaseRequestHandler +import socketserver -from .ldap import * -from .asn1 import IncompleteBERError -from .directory import RootDSE, Subschema, BaseDirectory -from .schema import CORE_SUBSCHEMA -from .exceptions import * +from . import asn1, exceptions, ldap, schema, directory def decode_msg(shallowmsg): try: return shallowmsg.decode()[0] except: traceback.print_exc() - raise LDAPProtocolError() + raise exceptions.LDAPProtocolError() def reject_critical_controls(controls=None, supported_oids=[]): for control in controls or []: if not control.criticality: continue if control.controlType not in supported_oids: - raise LDAPUnavailableCriticalExtension() + raise exceptions.LDAPUnavailableCriticalExtension() -class BaseLDAPRequestHandler(BaseRequestHandler): +class BaseLDAPRequestHandler(socketserver.BaseRequestHandler): def setup(self): super().setup() self.keep_running = True @@ -35,10 +31,10 @@ class BaseLDAPRequestHandler(BaseRequestHandler): buf = b'' while self.keep_running: try: - shallowmsg, buf = ShallowLDAPMessage.from_ber(buf) + shallowmsg, buf = ldap.ShallowLDAPMessage.from_ber(buf) for respmsg in self.handle_message(shallowmsg): - self.request.sendall(LDAPMessage.to_ber(respmsg)) - except IncompleteBERError: + self.request.sendall(ldap.LDAPMessage.to_ber(respmsg)) + except asn1.IncompleteBERError: chunk = self.request.recv(5) if not chunk: self.keep_running = False @@ -58,38 +54,38 @@ class BaseLDAPRequestHandler(BaseRequestHandler): :rtype: iterable of LDAPMessage objects ''' msgtypes = { - BindRequest: (self.handle_bind, BindResponse), - UnbindRequest: (self.handle_unbind, None), - SearchRequest: (self.handle_search, SearchResultDone), - ModifyRequest: (self.handle_modify, ModifyResponse), - AddRequest: (self.handle_add, AddResponse), - DelRequest: (self.handle_delete, DelResponse), - ModifyDNRequest: (self.handle_modifydn, ModifyDNResponse), - CompareRequest: (self.handle_compare, CompareResponse), - AbandonRequest: (self.handle_abandon, None), - ExtendedRequest: (self.handle_extended, ExtendedResponse), + ldap.BindRequest: (self.handle_bind, ldap.BindResponse), + ldap.UnbindRequest: (self.handle_unbind, None), + ldap.SearchRequest: (self.handle_search, ldap.SearchResultDone), + ldap.ModifyRequest: (self.handle_modify, ldap.ModifyResponse), + ldap.AddRequest: (self.handle_add, ldap.AddResponse), + ldap.DelRequest: (self.handle_delete, ldap.DelResponse), + ldap.ModifyDNRequest: (self.handle_modifydn, ldap.ModifyDNResponse), + ldap.CompareRequest: (self.handle_compare, ldap.CompareResponse), + ldap.AbandonRequest: (self.handle_abandon, None), + ldap.ExtendedRequest: (self.handle_extended, ldap.ExtendedResponse), } handler, response_type = msgtypes.get(shallowmsg.protocolOpType, (None, None)) try: if handler is None: - raise LDAPProtocolError() + raise exceptions.LDAPProtocolError() try: msg = decode_msg(shallowmsg) except ValueError: self.on_recv_invalid(shallowmsg) - raise LDAPProtocolError() + raise exceptions.LDAPProtocolError() self.on_recv(msg) for args in handler(msg.protocolOp, msg.controls): response, controls = args if isinstance(args, tuple) else (args, None) - yield LDAPMessage(shallowmsg.messageID, response, controls) - except LDAPError as e: + yield ldap.LDAPMessage(shallowmsg.messageID, response, controls) + except exceptions.LDAPError as e: if response_type is not None: - respmsg = LDAPMessage(shallowmsg.messageID, response_type(e.code, diagnosticMessage=e.message)) + respmsg = ldap.LDAPMessage(shallowmsg.messageID, response_type(e.code, diagnosticMessage=e.message)) self.on_send(respmsg) yield respmsg except Exception as e: if response_type is not None: - respmsg = LDAPMessage(shallowmsg.messageID, response_type(LDAPResultCode.other)) + respmsg = ldap.LDAPMessage(shallowmsg.messageID, response_type(ldap.LDAPResultCode.other)) self.on_send(respmsg) yield respmsg self.on_exception(e) @@ -114,7 +110,7 @@ class BaseLDAPRequestHandler(BaseRequestHandler): def handle_bind(self, op, controls=None): reject_critical_controls(controls) - raise LDAPAuthMethodNotSupported() + raise exceptions.LDAPAuthMethodNotSupported() def handle_unbind(self, op, controls=None): reject_critical_controls(controls) @@ -122,34 +118,34 @@ class BaseLDAPRequestHandler(BaseRequestHandler): def handle_search(self, op, controls=None): reject_critical_controls(controls) - yield SearchResultDone(LDAPResultCode.success) + yield ldap.SearchResultDone(ldap.LDAPResultCode.success) def handle_modify(self, op, controls=None): reject_critical_controls(controls) - raise LDAPInsufficientAccessRights() + raise exceptions.LDAPInsufficientAccessRights() def handle_add(self, op, controls=None): reject_critical_controls(controls) - raise LDAPInsufficientAccessRights() + raise exceptions.LDAPInsufficientAccessRights() def handle_delete(self, op, controls=None): reject_critical_controls(controls) - raise LDAPInsufficientAccessRights() + raise exceptions.LDAPInsufficientAccessRights() def handle_modifydn(self, op, controls=None): reject_critical_controls(controls) - raise LDAPInsufficientAccessRights() + raise exceptions.LDAPInsufficientAccessRights() def handle_compare(self, op, controls=None): reject_critical_controls(controls) - raise LDAPInsufficientAccessRights() + raise exceptions.LDAPInsufficientAccessRights() def handle_abandon(self, op, controls=None): reject_critical_controls(controls) def handle_extended(self, op, controls=None): reject_critical_controls(controls) - raise LDAPProtocolError() + raise exceptions.LDAPProtocolError() class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): ''' @@ -160,7 +156,7 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): mechansims. Attributes can be accessed in a dict-like fashion. ''' - subschema = CORE_SUBSCHEMA + subschema = schema.CORE_SUBSCHEMA ''' .. py:attribute:: subschema @@ -176,7 +172,7 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): def setup(self): super().setup() - self.rootdse = RootDSE() + self.rootdse = directory.RootDSE() self.rootdse['objectClass'] = [b'top'] self.rootdse['supportedSASLMechanisms'] = self.get_sasl_mechanisms self.rootdse['supportedExtension'] = self.get_extentions @@ -193,11 +189,11 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Called whenever the root DSE attribute "supportedExtension" is queried.''' res = [] if self.supports_starttls: - res.append(EXT_STARTTLS_OID.encode()) + res.append(ldap.EXT_STARTTLS_OID.encode()) if self.supports_whoami: - res.append(EXT_WHOAMI_OID.encode()) + res.append(ldap.EXT_WHOAMI_OID.encode()) if self.supports_password_modify: - res.append(EXT_PASSWORD_MODIFY_OID.encode()) + res.append(ldap.EXT_PASSWORD_MODIFY_OID.encode()) return res def get_sasl_mechanisms(self): @@ -221,42 +217,42 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): def handle_bind(self, op, controls=None): reject_critical_controls(controls) if op.version != 3: - raise LDAPProtocolError('Unsupported protocol version') + raise exceptions.LDAPProtocolError('Unsupported protocol version') auth = op.authentication # Resume ongoing SASL dialog - if self.bind_sasl_state and isinstance(auth, SaslCredentials) \ + if self.bind_sasl_state and isinstance(auth, ldap.SaslCredentials) \ and auth.mechanism == self.bind_sasl_state[0]: mechanism, iterator = self.bind_sasl_state self.bind_sasl_state = None - resp_code = LDAPResultCode.saslBindInProgress + resp_code = ldap.LDAPResultCode.saslBindInProgress try: resp = iterator.send(auth.credentials) self.bind_sasl_state = (mechanism, iterator) except StopIteration as e: - resp_code = LDAPResultCode.success + resp_code = ldap.LDAPResultCode.success self.bind_object, resp = e.value - yield BindResponse(resp_code, serverSaslCreds=resp) + yield ldap.BindResponse(resp_code, serverSaslCreds=resp) return # If auth type or SASL method changed, abort SASL dialog self.bind_sasl_state = None - if isinstance(auth, SimpleAuthentication): + if isinstance(auth, ldap.SimpleAuthentication): self.bind_object = self.do_bind_simple(op.name, auth.password) - yield BindResponse(LDAPResultCode.success) - elif isinstance(auth, SaslCredentials): + yield ldap.BindResponse(ldap.LDAPResultCode.success) + elif isinstance(auth, ldap.SaslCredentials): ret = self.do_bind_sasl(auth.mechanism, auth.credentials) if isinstance(ret, tuple): self.bind_object, resp = ret - yield BindResponse(LDAPResultCode.success, serverSaslCreds=resp) + yield ldap.BindResponse(ldap.LDAPResultCode.success, serverSaslCreds=resp) return iterator = iter(ret) - resp_code = LDAPResultCode.saslBindInProgress + resp_code = ldap.LDAPResultCode.saslBindInProgress try: resp = next(iterator) self.bind_sasl_state = (auth.mechanism, iterator) except StopIteration as e: - resp_code = LDAPResultCode.success + resp_code = ldap.LDAPResultCode.success self.bind_object, resp = e.value - yield BindResponse(resp_code, serverSaslCreds=resp) + yield ldap.BindResponse(resp_code, serverSaslCreds=resp) else: yield from super().handle_bind(op, controls) @@ -304,7 +300,7 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Calld by :any:`do_bind_simple`. The default implementation always raises an :any:`LDAPInvalidCredentials` exception.''' - raise LDAPInvalidCredentials() + raise exceptions.LDAPInvalidCredentials() def do_bind_simple_authenticated(self, dn, password): '''Do LDAP BIND with simple name/password authentication (`RFC 4513 5.1.3.`_) @@ -321,7 +317,7 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Calld by :any:`do_bind_simple`. The default implementation always raises an `LDAPInvalidCredentials` exception.''' - raise LDAPInvalidCredentials() + raise exceptions.LDAPInvalidCredentials() def do_bind_sasl(self, mechanism, credentials=None, dn=None): '''Do LDAP BIND with SASL authentication (RFC 4513 and 4422) @@ -354,21 +350,21 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): ''' if not mechanism: # Request to abort current negotiation (RFC4513 5.2.1.2) - raise LDAPAuthMethodNotSupported() + raise exceptions.LDAPAuthMethodNotSupported() if mechanism == 'ANONYMOUS' and self.supports_sasl_anonymous: if credentials is not None: credentials = credentials.decode() return self.do_bind_sasl_anonymous(trace_info=credentials), None if mechanism == 'PLAIN' and self.supports_sasl_plain: if credentials is None: - raise LDAPProtocolError('Unsupported protocol version') + raise exceptions.LDAPProtocolError('Unsupported protocol version') authzid, authcid, password = credentials.split(b'\0', 2) return self.do_bind_sasl_plain(authcid.decode(), password.decode(), authzid.decode() or None), None if mechanism == 'EXTERNAL' and self.supports_sasl_external: if credentials is not None: credentials = credentials.decode() return self.do_bind_sasl_external(authzid=credentials), None - raise LDAPAuthMethodNotSupported() + raise exceptions.LDAPAuthMethodNotSupported() supports_sasl_anonymous = False @@ -386,7 +382,7 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Calld by :any:`do_bind_sasl`. The default implementation raises an :any:`LDAPAuthMethodNotSupported` exception.''' - raise LDAPAuthMethodNotSupported() + raise exceptions.LDAPAuthMethodNotSupported() supports_sasl_plain = False @@ -407,7 +403,7 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Calld by :any:`do_bind_sasl`. The default implementation raises an :any:`LDAPAuthMethodNotSupported` exception.''' - raise LDAPAuthMethodNotSupported() + raise exceptions.LDAPAuthMethodNotSupported() supports_sasl_external = False @@ -427,15 +423,15 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Calld by :any:`do_bind_sasl`. The default implementation raises an :any:`LDAPAuthMethodNotSupported` exception.''' - raise LDAPAuthMethodNotSupported() + raise exceptions.LDAPAuthMethodNotSupported() def handle_search(self, op, controls=None): for dn, attributes in self.do_search(op.baseObject, op.scope, op.filter): - pattributes = [PartialAttribute(name, values) for name, values in attributes.items()] + pattributes = [ldap.PartialAttribute(name, values) for name, values in attributes.items()] if 'subschemaSubentry' not in attributes and self.subschema is not None: - pattributes.append(PartialAttribute('subschemaSubentry', [bytes(self.subschema.dn)])) - yield SearchResultEntry(dn, pattributes) - yield SearchResultDone(LDAPResultCode.success) + pattributes.append(ldap.PartialAttribute('subschemaSubentry', [bytes(self.subschema.dn)])) + yield ldap.SearchResultEntry(dn, pattributes) + yield ldap.SearchResultDone(ldap.LDAPResultCode.success) def do_search(self, baseobj, scope, filter): '''Do LDAP SEARCH operation @@ -464,31 +460,31 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): def handle_extended(self, op, controls=None): reject_critical_controls(controls) - if op.requestName == EXT_STARTTLS_OID and self.supports_starttls: + if op.requestName == ldap.EXT_STARTTLS_OID and self.supports_starttls: # StartTLS (RFC 4511) - yield ExtendedResponse(LDAPResultCode.success, responseName=EXT_STARTTLS_OID) + yield ldap.ExtendedResponse(ldap.LDAPResultCode.success, responseName=ldap.EXT_STARTTLS_OID) try: self.do_starttls() except Exception as e: traceback.print_exc() self.keep_running = False - elif op.requestName == EXT_WHOAMI_OID and self.supports_whoami: + elif op.requestName == ldap.EXT_WHOAMI_OID and self.supports_whoami: # "Who am I?" Operation (RFC 4532) identity = (self.do_whoami() or '').encode() - yield ExtendedResponse(LDAPResultCode.success, responseValue=identity) - elif op.requestName == EXT_PASSWORD_MODIFY_OID and self.supports_password_modify: + yield ldap.ExtendedResponse(ldap.LDAPResultCode.success, responseValue=identity) + elif op.requestName == ldap.EXT_PASSWORD_MODIFY_OID and self.supports_password_modify: # Password Modify Extended Operation (RFC 3062) newpw = None if op.requestValue is None: newpw = self.do_passwd() else: - decoded, _ = PasswdModifyRequestValue.from_ber(op.requestValue) + decoded, _ = ldap.PasswdModifyRequestValue.from_ber(op.requestValue) newpw = self.do_passwd(decoded.userIdentity, decoded.oldPasswd, decoded.newPasswd) if newpw is None: - yield ExtendedResponse(LDAPResultCode.success) + yield ldap.ExtendedResponse(ldap.LDAPResultCode.success) else: - encoded = PasswdModifyResponseValue.to_ber(PasswdModifyResponseValue(newpw)) - yield ExtendedResponse(LDAPResultCode.success, responseValue=encoded) + encoded = ldap.PasswdModifyResponseValue.to_ber(ldap.PasswdModifyResponseValue(newpw)) + yield ldap.ExtendedResponse(ldap.LDAPResultCode.success, responseValue=encoded) else: yield from super().handle_extended(op, controls) @@ -542,4 +538,4 @@ class SimpleLDAPRequestHandler(BaseLDAPRequestHandler): Called by `handle_extended()` if :any:`supports_password_modify` is True. The default implementation always raises an :any:`LDAPUnwillingToPerform` error.''' - raise LDAPUnwillingToPerform() + raise exceptions.LDAPUnwillingToPerform()