def handle_LDAPModifyDNRequest(self, request, controls, reply): self.checkControls(controls) dn = distinguishedname.DistinguishedName(request.entry) newrdn = distinguishedname.RelativeDistinguishedName(request.newrdn) deleteoldrdn = bool(request.deleteoldrdn) if not deleteoldrdn: raise ldaperrors.LDAPUnwillingToPerform( "Cannot handle preserving old RDN yet.") newSuperior = request.newSuperior if newSuperior is None: newSuperior = dn.up() else: newSuperior = distinguishedname.DistinguishedName(newSuperior) newdn = distinguishedname.DistinguishedName( listOfRDNs=(newrdn,)+newSuperior.split()) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _gotEntry(entry): d = entry.move(newdn) return d def _report(entry): return pureldap.LDAPModifyDNResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d
def handle_LDAPExtendedRequest( self, request: LDAPExtendedRequest, controls: pureldap.LDAPControl, reply_fn: Callable[..., None], ) -> Any: # Handle STARTTLS locally; proxy everything else. if request.requestName != pureldap.LDAPStartTLSRequest.oid: return self.handleUnknown(request, controls, reply_fn) if not self.sslctx: raise ldaperrors.LDAPProtocolError( STARTTLS_NOT_SUPPORTED_ERROR_MESSAGE) self.checkControls(controls) # assert can ignore controls, then do so try: # Send reply indicating TLS negotiation should start. msg = pureldap.LDAPExtendedResponse( resultCode=ldaperrors.Success.resultCode, responseName=pureldap.LDAPStartTLSRequest.oid, ) reply_fn(msg) # Start TLS negotiation! self.transport.startTLS(self.sslctx, self.factory) except ldaperrors.LDAPException: raise except Exception: raise ldaperrors.LDAPUnwillingToPerform()
def handle_LDAPExtendedRequest(self, request, controls, reply): raise ldaperrors.LDAPUnwillingToPerform()
def handle_LDAPModifyDNRequest(self, request, controls, reply): raise ldaperrors.LDAPUnwillingToPerform()
def perform_bind_sspi(self, dn: str, username: str, password: str, domain: str, permit_implicit: bool, targetspn=None): """Perform bind using native windows SSPI mechanism. If no username, password, or domain is provided, then we'll attempt to use the authproxy's existing process credentials to perform the bind. If targetspn is provided (and valid), then theoretically we might use kerberos instead of NTLM Returns: No return value from this function. Finishing without raising an exception means the bind succeeded Raises: LDAPUnwillingToPerform: If SSPI auth is not supported SSPIError: If the SSPI negotiation fails """ if sspi is None: msg = 'The SSPI bind type is only supported on Windows.' log.err(msg) raise ldaperrors.LDAPUnwillingToPerform(msg) auth_info: Optional[Tuple[str, str, str]] = (username, domain, password) # omitting 'domain' from this if statement; that way we can # specify ntlm_domain in config and have it apply to user # auth, but not automatically trip us into using configured, # rather than implicit, service account creds if not (username or password): if permit_implicit: auth_info = None else: # even passing a tuple of empty values still appears # to trigger the implicit-auth mechanism, so we need # to be explicit msg = 'Implicit authentication forbidden for this request.' log.err(msg) raise ldaperrors.LDAPUnwillingToPerform(msg) # SSPI negotiation can request some application-level # encryption/authentication, but AD apparently throws a fit if # you leave this on when using SSL/TLS (and expects you to # actually somehow sign all your LDAP requests otherwise) scflags = ISC_REQ_NO_INTEGRITY # these return codes mean we need to continue the handshake sspi_continue = set([ sspicon.SEC_I_CONTINUE_NEEDED, sspicon.SEC_I_COMPLETE_AND_CONTINUE ]) # any return code not in this set should be considered an error sspi_ok = sspi_continue.union( [sspicon.SEC_E_OK, sspicon.SEC_I_COMPLETE_NEEDED]) ca = self._create_sspi_authenticator(auth_info, targetspn, scflags) data = None # This negotiation is made up of challenge and response messages. # We will continue responding to challenges until a success or # failure case is hit while True: # get the next step in the handshake err, out_buf = ca.authorize(data) if err not in sspi_ok: raise SSPIError('SSPI negotiation failed', err) response = yield self._send_sspi_bind(dn, out_buf) if response.resultCode == ldaperrors.LDAPSaslBindInProgress.resultCode: data = self._recalculate_buffer_data(ca, response) elif response.resultCode == ldaperrors.Success.resultCode: break else: raise ldaperrors.get(response.resultCode, response.errorMessage) # if SSPI said we're done, but ldap response doesn't # agree, that's weird if err not in sspi_continue: raise SSPIError('SSPI negotiation should\'ve finished by now', err)
def perform_bind_sspi( self, username: str, password: str, domain: str, permit_implicit: bool, targetspn: str = None, ): """Perform bind using native windows SSPI mechanism. If no username, password, or domain is provided, then we'll attempt to use the authproxy's existing process credentials to perform the bind. This method uses the Negotiate authentication mechanism (GSS-SPNEGO). If a valid targetspn is provided, then SSPI will use Kerberos. Otherwise, NTLM will be used. Returns: No return value from this function. Finishing without raising an exception means the bind succeeded Raises: LDAPUnwillingToPerform: If SSPI auth is not supported SSPIError: If the SSPI negotiation fails """ if sspi is None: msg = "The SSPI bind type is only supported on Windows." log.error(msg) raise ldaperrors.LDAPUnwillingToPerform(msg) auth_info: Optional[Tuple[str, str, str]] = (username, domain, password) # omitting 'domain' from this if statement; that way we can # specify ntlm_domain in config and have it apply to user # auth, but not automatically trip us into using configured, # rather than implicit, service account creds if not (username or password): if permit_implicit: auth_info = None else: # even passing a tuple of empty values still appears # to trigger the implicit-auth mechanism, so we need # to be explicit msg = "Implicit authentication forbidden for this request." log.error(msg) raise ldaperrors.LDAPUnwillingToPerform(msg) # TLS and Sign and Seal are mutually eclusive security measures # so we only request integrity checking if we don't already have # it from the transport layer if self.factory.transport_type in const.AD_TRANSPORTS_WITH_SSL: scflags = ISC_REQ_NO_INTEGRITY else: scflags = sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_CONFIDENTIALITY # When doing an SSPI bind, we interact with two services: # # 1) SSPI # 2) LDAP # # SSPI creates a security context and provides the credentials to do # a SASL bind. The authentication is complete when both SSPI and LDAP # don't require any more steps. # # There is no guarantee that there will be the same number of interactions # with SSPI and LDAP for a single authentication. For example, when # doing a Kerberos authentication the entire flow is # authorize->bind->authorize due to there being no LDAP challenge. If # NTLM is used, though, the flow is authorize->bind(neg)->authorize->bind(auth). # Because of this, we must check whether we need to do another authorize # and/or bind on each iteration of the loop. # # More details on how to interact with SSPI and authorize(), particularly # the 'Remarks' section: # https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa375509(v=vs.85) response = None result_code = None out_buf = None err = None ca = self._create_sspi_authenticator(auth_info, targetspn, scflags) while err != sspicon.SEC_E_OK or result_code != ldaperrors.Success.resultCode: # If it's either our first iteration or SSPI says more steps are # necessary, authorize to get the data buffer for the next step. # Intentionally checking for err being None instead of falsey since # sspicon.SEC_E_OK is 0, which is also falsey! if err is None or err in SSPI_CONTINUE: err, out_buf = yield self._authorize(ca, response) # Send a bind request if this is our first iteration or if the # LDAP response from the previous iteration was a challenge. if (not response or response.resultCode == ldaperrors.LDAPSaslBindInProgress.resultCode): response = yield self._send_sspi_bind(out_buf) result_code = response.resultCode # if SSPI said we're done, but ldap response doesn't agree, we've # reached an unexpected state. Raise an error. if err == sspicon.SEC_E_OK and result_code != ldaperrors.Success.resultCode: raise SSPIError("SSPI negotiation should've finished by now", err) defer.returnValue(ca)