def _check_connection(self):
        disconnected = False
        if not self.rpc_server:
            discon_msg = "no rpc server"
            disconnected = True
        elif self.rpc_server.transport.disconnected:
            discon_msg = "transport disconnected"
            disconnected = True
        elif not self.rpc_server.transport.connected:
            discon_msg = "transport not connected"
            disconnected = True
        elif (self.rpc_server.factory.idle_rpc_timeout is not None
              and self.rpc_server.last_rpc is not None):
            last_rpc = int(time.time() - self.rpc_server.last_rpc)
            if last_rpc > self.rpc_server.factory.idle_rpc_timeout:
                discon_msg = "Missed pings for {0} seconds, maximum {1} seconds allowed.".format(
                    last_rpc, self.rpc_server.factory.idle_rpc_timeout)
                try:
                    # Disconnect the transport just in case.
                    self.rpc_server.transport.abortConnection()
                    log.msg(
                        "Connection to Duo service was intentionally closed.")
                except Exception as e:
                    log.msg(
                        "Attempted to forcibly disconnect the transport but was unable. Transport likely already disconnected. Exception: {}"
                        .format(e))

                disconnected = True

        if disconnected:
            # No connection to service found! Fix that.
            log.msg("DRPC Disconnected: {0}".format(discon_msg))
            log.msg("(Re)connecting to service...")
            # Notify depending on the subclass implementation
            self.log_disconnect(self.rpc_server)

            self.rpc_server = None

            try:
                self.rpc_server = yield self.perform_join()
            except Exception as e:
                if is_fatal_error(e):
                    log.error("Error: {e}", e=str(e))
                    log.msg(
                        "This exception requires manual intervention. Directory Sync and/or SSO functionality will "
                        "be unavailable until the problem is resolved")
                    self.stopService()
                else:
                    log.msg(
                        "Error connecting to service: {0}. Will retry again in {1} seconds."
                        .format(str(e), self.reconnect_interval))
            else:
                self.log_connect(self.rpc_server)
Пример #2
0
def _info_callback(conn, where, ret):
    """Use the info callback to gather information about attempted
    SSL connections and warn about incompatibilities. See the man
    page[1] for further information.

    [1] https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_info_callback.html
    """
    if where & OpenSSL.SSL.SSL_CB_WRITE_ALERT:
        if conn.get_cipher_name() is None:
            log.error(
                "Unable to establish SSL connection. "
                "Client may be attemping incompatible protocol version or cipher."
            )
Пример #3
0
 def _verify_ldap_config_args(self, bind_dn: str, bind_pw: str,
                              auth_type: str, transport_type: str) -> None:
     if util.is_windows_os():
         if auth_type not in const.AD_AUTH_TYPES_WIN:
             raise drpc_exceptions.CallBadArgError(["auth_type"])
     else:
         if auth_type not in const.AD_AUTH_TYPES_NIX:
             raise drpc_exceptions.CallBadArgError(["auth_type"])
     if transport_type not in const.AD_TRANSPORTS:
         raise drpc_exceptions.CallBadArgError(["transport_type"])
     if auth_type != const.AD_AUTH_TYPE_SSPI and (bind_dn == ""
                                                  or bind_pw == ""):
         e = drpc_exceptions.CallError(
             ERR_LDAP_CONFIGURATION_FAILED,
             {
                 "authproxy_configuration_error":
                 "Missing {0} or {1}".format(CONFIG_BIND_USER,
                                             CONFIG_BIND_PASSWORD)
             },
         )
         log.error("{msg}. Error: {error}",
                   msg=ERR_LDAP_CONFIGURATION_FAILED,
                   error=e)
         raise e
Пример #4
0
    def _paged_search(
        self,
        client,
        base_dn,
        filter_text,
        attributes,
        pagination=PAGINATION_TYPE_CRITICAL,
        max_result_size=None,
        page_size=DEFAULT_PAGE_SIZE,
    ):
        """
        Given a bound client, search exhaustively using RFC 2696.
        Return a list of dictionaries containing the attributes
        of the resulting entries.

        * attributes: Set of lower-case byte-strings.
        """
        res = []

        def handle_msg(value, controls, d):
            try:
                if isinstance(value, pureldap.LDAPSearchResultDone):
                    e = ldaperrors.get(value.resultCode, value.errorMessage)
                    if isinstance(e, (ldaperrors.Success,
                                      ldaperrors.LDAPSizeLimitExceeded)):
                        cookie = get_cookie(controls)
                        d.callback((None, cookie))
                    else:
                        d.callback((e, None))
                elif isinstance(value, pureldap.LDAPSearchResultEntry):
                    # Always send DN. Overwrite DN from attribute set, if any.
                    obj = {
                        "distinguishedname": [escape_bytes(value.objectName)]
                    }

                    for k, vs in value.attributes:
                        # Smash attribute name case.
                        k = k.decode().lower()

                        # Server may not honor attributes (e.g.
                        # SearchByTreeWalkingMixin).
                        if attributes and k.encode() not in attributes:
                            continue

                        # Covert value to list and encode for JSON.
                        vs = [escape_bytes(v) for v in vs]

                        obj[k] = vs

                    # Refuse to return certain attributes even if all
                    # attributes were requested.
                    obj.pop("userpassword", None)

                    res.append(obj)
            except Exception:
                log.failure("Unexpected error handling message")
            finally:
                return isinstance(value, (
                    pureldap.LDAPBindResponse,
                    pureldap.LDAPSearchResultDone,
                ))

        if filter_text:
            filter_obj = ldapfilter.parseFilter(filter_text)
        else:
            filter_obj = None
        op = pureldap.LDAPSearchRequest(
            baseObject=base_dn,
            scope=pureldap.LDAP_SCOPE_wholeSubtree,
            derefAliases=0,
            sizeLimit=0,
            timeLimit=0,
            typesOnly=0,
            filter=filter_obj,
            attributes=attributes,
        )

        if pagination == PAGINATION_TYPE_CRITICAL:
            # AD may ignore the RFC 2696 control if it is not
            # critical. The caller can override this default if the
            # control should be present but not critical or absent.
            criticality = True
        else:
            criticality = False

        cookie = pureber.BEROctetString("")
        while True:
            if pagination == PAGINATION_TYPE_DISABLE:
                controls = None
            else:
                controls = [
                    (
                        RFC_2696_CONTROL_TYPE,
                        criticality,
                        pureber.BERSequence([
                            pureber.BERInteger(page_size),
                            cookie,
                        ]),
                    ),
                ]

            d = defer.Deferred()
            yield client.send(
                op=op,
                controls=controls,
                handler=functools.partial(handle_msg, d=d),
                return_controls=True,
            )

            # handle_msg() is synchronous so d should be called by the
            # time it returns to LDAPClient.handle().
            if d.called:
                e, cookie = yield d
            else:
                log.error("Paging cookie not found!")
                break
            if e is not None:
                # So the RPC caller can distinguish between problems
                # with the search (e.g. bad configuration) and
                # searches that return no results.
                if isinstance(e, ldaperrors.LDAPOperationsError
                              ) and e.message.decode().startswith(
                                  const.LDAP_SUCCESSFUL_BIND_NEEDED_ERROR):
                    raise ldap.client.ConnectionNotProperlyBoundError(
                        "Search failed because either there was no bind on this connection or there were insufficient privileges with the bound user. If you are attempting to use integrated authentication with SSPI please make sure the server running the Authentication Proxy is domain joined."
                    )
                else:
                    raise e
            if not cookie.value:
                break
            if max_result_size is not None and len(res) > max_result_size:
                break
        defer.returnValue(res)
Пример #5
0
    def fetch_ldap_attributes_from_server(
        self,
        host,
        port,
        rikey,
        desired_attributes,
        user_dn,
        ntlm_domain=None,
        ntlm_workstation=None,
        auth_type=const.AD_AUTH_TYPE_NTLM_V2,
        transport_type=const.AD_TRANSPORT_STARTTLS,
        ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH,
        ssl_verify_hostname=True,
        ssl_ca_certs=None,
        timeout=60,
        call_id=None,
    ):
        creds = self.get_creds_for_ldap_idp(rikey, auth_type)
        bind_dn = creds.username
        bind_pw = creds.password

        log.msg((
            "Performing user attributes fetch: "
            "call_id={call_id} host={host} port={port} rikey={rikey} "
            "desired_attributes={desired_attributes} user_dn={user_dn} ntlm_domain={ntlm_domain} "
            "ntlm_workstation={ntlm_workstation} auth_type={auth_type} transport_type={transport_type} "
            "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} "
            "ssl_ca_certs={ssl_ca_certs}").format(
                call_id=call_id,
                host=host,
                port=port,
                rikey=rikey,
                desired_attributes=desired_attributes,
                user_dn=user_dn,
                ntlm_domain=ntlm_domain,
                ntlm_workstation=ntlm_workstation,
                auth_type=auth_type,
                transport_type=transport_type,
                ssl_verify_depth=ssl_verify_depth,
                ssl_verify_hostname=ssl_verify_hostname,
                ssl_ca_certs=ssl_ca_certs is not None,
            ))

        self._verify_ldap_config_args(bind_dn, bind_pw, auth_type,
                                      transport_type)

        client, timeout_dc = yield self._get_client(
            host,
            port,
            transport_type,
            ssl_verify_depth,
            ssl_verify_hostname,
            ssl_ca_certs,
            timeout,
            self.debug,
            self.is_logging_insecure,
        )

        # Try to do everything network-related
        try:
            # Bind as service user, for searching
            try:
                yield client.perform_bind(
                    auth_type=auth_type,
                    dn=bind_dn,
                    username=bind_dn,
                    password=bind_pw,
                    domain=ntlm_domain,
                    workstation=ntlm_workstation,
                    permit_implicit=True,
                )
            except Exception as e:
                if timeout_dc.active():
                    log.failure(ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED,
                                         {"error": six.text_type(e)})
                else:
                    log.failure(ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(
                        ldap_base.ERR_LDAP_TIMEOUT,
                        {
                            "during": "bind",
                            "error": six.text_type(e)
                        },
                    )

            # Search for the user
            try:
                result = yield client.perform_search(
                    user_dn,
                    None,
                    attributes=desired_attributes,
                    scope=pureldap.LDAP_SCOPE_baseObject,
                )

                if len(result) != 1:
                    log.error(AUTH_INVALID_USER)
                    raise Exception(AUTH_INVALID_USER)

                result = result[0]
            except distinguishedname.InvalidRelativeDistinguishedName as irdn:
                log.failure(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION)
                raise drpc.CallError(
                    ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION,
                    {"error": six.text_type(irdn)},
                )
            except Exception as e:
                log.failure(ldap_base.ERR_LDAP_SEARCH_FAILED)
                raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED,
                                     {"error": six.text_type(e)})
        finally:
            # Clean up networking
            if timeout_dc.active():
                timeout_dc.cancel()

            try:
                client.transport.abortConnection()
            except Exception:
                pass

        # At this point, result is a single LDAPEntry, or something has gone wrong and we ideally raised somewhere above
        encoded_dict = transform_result(result, desired_attributes)

        logged_atts = transform_result(
            result,
            desired_attributes,
            value_transform=self.to_unicode_ignore_errors)
        log.msg("For user dn {dn}, found attributes {atts}".format(
            dn=user_dn, atts=logged_atts))

        defer.returnValue(encoded_dict)
Пример #6
0
    def authenticate_against_server(
        self,
        host: str,
        port: str,
        username: str,
        password: str,
        service_account_username: str,
        service_account_password: str,
        base_dns: Optional[List[str]] = None,
        ntlm_domain: Optional[str] = None,
        ntlm_workstation: Optional[str] = None,
        auth_type: str = const.AD_AUTH_TYPE_NTLM_V2,
        transport_type: str = const.AD_TRANSPORT_STARTTLS,
        ssl_verify_depth: int = const.DEFAULT_SSL_VERIFY_DEPTH,
        ssl_verify_hostname: bool = True,
        ssl_ca_certs: Optional[str] = None,
        timeout: int = 60,
        username_attributes: Optional[List[str]] = None,
        call_id: Optional[int] = None,
    ):
        """
        * username_attribute: attribute within AD that we will compare the
          username against.

        See do_ldap_search for further argument documentation.

        Returns: tuple(bool, str).
            - True if the auth was successful,
            - The full dn of the user who authed
        """

        bind_dn = service_account_username
        bind_pw = service_account_password
        # At this point we *should* have a username attribute passed to us. But if we don't have one
        # we will default to the most popular attribute, samaccountname. This will only work for AD.
        username_attributes = (username_attributes
                               if username_attributes else ["samaccountname"])
        base_dns = base_dns if base_dns else [""]

        log.msg((
            "Performing LDAP authentication: "
            "call_id={call_id} host={host} port={port} base_dns={base_dns} "
            "auth_type={auth_type} transport_type={transport_type} "
            "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} "
            "ssl_ca_certs={ssl_ca_certs} username={username} username_attributes={username_attributes}"
        ).format(
            call_id=call_id,
            host=host,
            port=port,
            base_dns=base_dns,
            auth_type=auth_type,
            transport_type=transport_type,
            ssl_verify_depth=ssl_verify_depth,
            ssl_verify_hostname=ssl_verify_hostname,
            username=username,
            username_attributes=username_attributes,
            ssl_ca_certs=ssl_ca_certs is not None,
        ))

        # The factory has a username attribute field on it but we will not be using it
        cl, timeout_dc = yield self._get_client(
            host,
            port,
            transport_type,
            ssl_verify_depth,
            ssl_verify_hostname,
            ssl_ca_certs,
            timeout,
            self.debug,
            self.is_logging_insecure,
        )

        try:
            # Primary bind. This authenticates us to AD and allows us to
            # make search queries.
            try:
                yield cl.perform_bind(
                    auth_type=auth_type,
                    dn=bind_dn,
                    username=bind_dn,
                    password=bind_pw,
                    domain=ntlm_domain,
                    workstation=ntlm_workstation,
                    permit_implicit=True,
                )
            except Exception as e:
                if timeout_dc.active():
                    log.failure(ldap_base.ERR_LDAP_BIND_FAILED)
                    raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED, {
                        "error": str(e),
                    })
                else:
                    log.failure(ldap_base.ERR_LDAP_TIMEOUT)
                    raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, {
                        "during": "bind",
                        "error": str(e),
                    })

            username_match = dict.fromkeys(username_attributes, username)
            filterObject = yield cl.user_filter_object(
                username_matches=username_match)

            # Search for the user. With AD, the user's cn is their full
            # name, not username. So, we need to search for the user by comparing
            # the username to a selection of specific attributes. For example:
            # sAMAccountName or userprincipalname

            search_hits: List[BytesLDAPEntry] = []
            try:
                # Always fetch msDS-PrincipalName since we need the user's
                # sAMAccountName and domain. The username entered by the user
                # may not be in a valid format for SSPI or NTLM depending on
                # the username attributes configured, but the sAMAccountName
                # that's part of msDS-PrincipalName will always work.
                attributes_to_fetch = {
                    str(attr).lower()
                    for attr in username_attributes
                }
                attributes_to_fetch.add("msds-principalname")
                # Also fetch some helpful attributes for debugging if something goes wrong
                attributes_to_fetch.add("objectclass")
                attributes_to_fetch.add("objectcategory")

                # To check multiple base dns we need to perform a search for each one. If the
                # combination of all of these searches returns more than 1 unique user we will
                # fail the auth
                for base_dn in base_dns:
                    result = yield cl.perform_search(
                        base_dn,
                        filterObject,
                        attributes=tuple(attributes_to_fetch))
                    search_hits.extend(result)
            except distinguishedname.InvalidRelativeDistinguishedName as e:
                log.failure(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION)
                raise drpc.CallError(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION, {
                    "error": str(e),
                })
            except Exception as e:
                log.failure(
                    "{msg} for {username}",
                    msg=ldap_base.ERR_LDAP_SEARCH_FAILED,
                    username=username,
                )
                raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                    "error": str(e),
                })

            if len(search_hits) == 0:
                err_msg = AUTH_INVALID_USER
                err = drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                    "error": err_msg,
                })
                log.error(
                    "{msg} {username}. Error: {error}",
                    msg=err_msg,
                    username=username,
                    error=err,
                )
                raise err

            if len(search_hits) > 1:
                err_msg = AUTH_TOO_MANY_USERS
                err = drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {
                    "error": err_msg,
                })
                log.error(
                    "{msg} while searching for {username}: {users}. Error: {error}",
                    msg=err_msg,
                    username=username,
                    users=[user.dn.getText() for user in search_hits],
                    error=err,
                )
                raise err

            user_result = search_hits[0]
            user_full_dn = user_result.dn.getText()
            # Log out the search result
            matched_attributes = determine_matched_attributes(
                username, username_attributes, user_result)
            log.msg("Found {username} with attributes {atts}".format(
                username=username, atts=matched_attributes))

            # Initialize these as None since Plain binds don't these values
            bind_username = None
            user_domain = None
            # If the auth type is not plain, determine the user's domain and
            # sAMAccountName by pulling them from msDS-PrincipalName. We need
            # both in order to do NTLM and SSPI authentications.
            if auth_type != AD_AUTH_TYPE_PLAIN:
                msds_principalname_attribute_set = user_result.get(
                    "msDS-PrincipalName")
                if not msds_principalname_attribute_set:
                    # msDS-PrincipalName must be provided so we can determine
                    # the domain of the user authenticating. Abort the
                    # authentication if the attribute doesn't exist for the user.
                    err = drpc.CallError(
                        ERR_LDAP_MISSING_REQUIRED_ATTRIBUTE,
                        {
                            "error":
                            MSDS_PRINCIPAL_NAME_MISSING.format(username)
                        },
                    )
                    log.error(
                        "Error: {error}, User Result: {user_result}",
                        error=err,
                        user_result=user_result,
                    )
                    raise err

                msds_principalname = list(
                    user_result.get("msDS-PrincipalName"))[0].decode("utf8")
                if not msds_principalname or "\\" not in msds_principalname:
                    # This means that msDS-PrincipalName was empty, a form
                    # we can't work with (e.g. SID), or didn't have any
                    # domain information on it. We can't guarantee we'll
                    # log in the correct user without knowing their domain,
                    # so abort the authentication.
                    err = drpc.CallError(
                        ERR_LDAP_INVALID_ATTRIBUTE_VALUE,
                        {
                            "error":
                            INVALID_MSDS_PRINCIPAL_NAME.format(username)
                        },
                    )
                    log.error(
                        "Error: {error}, User Result: {user_result}",
                        error=err,
                        user_result=user_result,
                    )
                    raise err

                user_domain, bind_username = msds_principalname.split("\\", 1)

            # Secondary bind. Assuming the user we queried exists, attempt
            # to bind as them (this is essentially performing an authentication).
            try:
                yield cl.perform_bind(
                    auth_type=auth_type,
                    dn=user_full_dn,
                    username=bind_username,
                    password=password,
                    domain=user_domain,
                    workstation=ntlm_workstation,
                    permit_implicit=False,
                )
            except Exception as e:
                log.msg(
                    "Authentication failed for user {username} ({dn})".format(
                        username=username, dn=user_full_dn))
                log.msg(e)
                defer.returnValue((False, None))
        finally:
            if timeout_dc.active():
                timeout_dc.cancel()
            try:
                cl.transport.abortConnection()
            except Exception:
                pass

        log.msg("Authentication succeeded for user {username} ({dn})".format(
            username=username, dn=user_full_dn))
        defer.returnValue((True, user_full_dn))
Пример #7
0
    def perform_authentication(
        self,
        servers,
        username,
        password,
        service_account_username,
        service_account_password,
        base_dns=None,
        ntlm_domain=None,
        ntlm_workstation=None,
        auth_type=const.AD_AUTH_TYPE_NTLM_V2,
        transport_type=const.AD_TRANSPORT_STARTTLS,
        ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH,
        ssl_verify_hostname=True,
        ssl_ca_certs=None,
        timeout=60,
        username_attributes=None,
        call_id=None,
    ):
        """
        Authenticates against the provided servers in random order until
        a successful authentication occurs or the list of servers has been
        exhausted.

        See do_ldap_search for further argument documentation.

        Args:
            servers (list): List of dicts, each containing a 'hostname' and 'port'

        Returns:
            A dict containing:
                success (bool): True if the auth  was successful
                msg (str): The message associated with the result of the auth
                exceptions (list): Dict of CallError keyed on hostname
                    containing any errors that occurred during the
                    authentication process. Note that the presence of errors
                    does not indicate auth failure! For example, the exceptions
                    list will be non-empty if the first server could not be
                    reached but the second server serviced the auth.

        """
        # Assume user authentication never legitimately uses anonymous bind.
        if not password:
            defer.returnValue({
                "success": False,
                "msg": AUTH_FAILED,
                "exceptions": {},
            })

        exceptions = {}
        auth_successful = False
        user_full_dn = None
        # Try each server in random order
        for server in servers:
            host = server["hostname"]
            port = server["port"]
            try:
                auth_successful, user_full_dn = yield self.authenticate_against_server(
                    host=host,
                    port=port,
                    username=username,
                    password=password,
                    service_account_username=service_account_username,
                    service_account_password=service_account_password,
                    base_dns=base_dns,
                    ntlm_domain=ntlm_domain,
                    ntlm_workstation=ntlm_workstation,
                    auth_type=auth_type,
                    transport_type=transport_type,
                    ssl_verify_depth=ssl_verify_depth,
                    ssl_verify_hostname=ssl_verify_hostname,
                    ssl_ca_certs=ssl_ca_certs,
                    timeout=timeout,
                    username_attributes=username_attributes,
                    call_id=call_id,
                )
                # If we reach this line without an exception, the auth was
                # serviced successfully and we don't need to check any more
                # servers.
                if auth_successful:
                    log.sso_ldap(
                        msg="Successful authentication against server",
                        query_type=log.SSO_LDAP_QUERY_TYPE_AUTH,
                        status=log.SSO_LDAP_QUERY_SUCCEEDED,
                        server=host,
                        port=port,
                        username=username,
                        proxy_key=self.proxy_key,
                    )
                else:
                    log.sso_ldap(
                        msg="Failed authentication against server",
                        query_type=log.SSO_LDAP_QUERY_TYPE_AUTH,
                        status=log.SSO_LDAP_QUERY_FAILED,
                        server=host,
                        port=port,
                        username=username,
                        proxy_key=self.proxy_key,
                        reason="Invalid credentials",
                    )
                break
            except drpc.CallError as e:
                # serialize the errors in a similar format to what drpc does
                log.sso_ldap(
                    msg="Failed authentication against server",
                    query_type=log.SSO_LDAP_QUERY_TYPE_AUTH,
                    status=log.SSO_LDAP_QUERY_FAILED,
                    server=host,
                    port=port,
                    username=username,
                    proxy_key=self.proxy_key,
                    reason=e.error,
                )
                exceptions[host] = {
                    "error": e.error,
                    "error_args": e.error_args,
                }

        if self.connection_failures_for_all_servers(servers, exceptions):
            raise drpc.CallError(
                ERR_LDAP_CANNOT_SERVICE_REQUEST,
                {
                    "error":
                    str("Failed to communicate with any domain controllers")
                },
            )

        if self.too_many_users_failure_on_all_servers(servers, exceptions):
            error = drpc.CallError(
                ERR_LDAP_NON_UNIQUE_USERNAME_ATTR,
                {
                    "error":
                    "Each domain controller returned more than one user for the provided username attributes."
                },
            )
            log.error(
                "{msg} {username}. Error: {error}",
                msg=ERR_LDAP_NON_UNIQUE_USERNAME_ATTR,
                username=username,
                error=error,
            )
            raise error

        defer.returnValue({
            "success": auth_successful,
            "user_full_dn": user_full_dn,
            "msg": AUTH_SUCCEEDED if auth_successful else AUTH_FAILED,
            "exceptions": exceptions,
        })
    def get_func_for_drpc_call(self, call_name):
        if call_name in self.drpc_calls:
            return self.drpc_calls[call_name]

        log.error("Unknown DRPC call '{0}'".format(call_name))
        return None