class ElementClass:
    def __subclasshook__(cls, other):
def element_to_unicode(element): class StreamBase(XMLStreamHandler):
    """Base class for a generic XMPP stream.

    Responsible for establishing connection, parsing the stream, handling
    stream elements and passing stanzas receiver to other object.
    This doesn't provide any authentication or encryption (both required by
    the XMPP specification) and is not usable on its own.

    Whenever we say "stream" here we actually mean two streams
    (incoming and outgoing) of one connections, as defined by the XMPP
    specification.

    :Ivariables:
        - `authenticated`: `True` if local entity has authenticated to peer
        - `features`: stream features as annouced by the receiver.
        - `handlers`: handlers for stream elements
        - `initiator`: `True` if local stream endpoint is the initiating entity.
        - `lock`: RLock object used to synchronize access to Stream object.
        - `me`: local stream endpoint JID.
        - `peer_authenticated`: `True` if the peer has authenticated to us
        - `peer_language`: language of human-readable stream content selected
          by the peer
        - `peer`: remote stream endpoint JID.
        - `settings`: stream settings
        - `stanza_namespace`: default namespace of the stream
        - `tls_established`: `True` when the stream is protected by TLS
        - `transport`: transport used by this stream
        - `version`: Negotiated version of the XMPP protocol. (0,9) for the
          legacy (pre-XMPP) Jabber protocol.
        - `_element_handlers`: mapping from stream element names to lists of
          methods handling them
        - `_input_state`: `None`, "open" (<stream:stream> has been received)
          "restart" or "closed" (</stream:stream> or EOF has been received)
        - `_output_state`: `None`, "open" (<stream:stream> has been received)
          "restart" or "closed" (</stream:stream> or EOF has been received)
        - `_stanza_namespace_p`: qname prefix of the stanza namespace
        - `_stream_feature_handlers`: stream features handlers
    :Types:
        - `authenticated`: `bool`
        - `features`: :etree:`ElementTree.Element`
        - `handlers`: `list`
        - `initiator`: `bool`
        - `lock`: :std:`threading.RLock`
        - `me`: `JID`
        - `peer_authenticated`: `bool`
        - `peer_language`: `unicode`
        - `peer`: `JID`
        - `settings`: XMPPSettings
        - `stanza_namespace`: `unicode`
        - `tls_established`: `bool`
        - `transport`: `transport.XMPPTransport`
        - `version`: (`int`, `int`) tuple
        - `_element_handlers`: `dict`
        - `_input_state`: `unicode`
        - `_output_state`: `unicode`
        - `_stanza_namespace_p`: `unicode`
        - `_stream_feature_handlers`: `list` of `StreamFeatureHandler`
    """
    # pylint: disable-msg=R0902,R0904
    def __init__(self, stanza_namespace, stanza_route, handlers,
                                                            settings = None):
        """Initialize StreamBase object

        :Parameters:
          - `stanza_namespace`: stream's default namespace URI ("jabber:client"
            for client, "jabber:server" for server, etc.)
          - `stanza_route`: object to handle received stanzas
          - `handlers`: objects to handle the stream events and elements
          - `settings`: extra settings
        :Types:
          - `stanza_namespace`: `unicode`
          - `stanza_route`: `StanzaRoute`
          - `settings`: XMPPSettings
          - `handlers`: `list` of objects
        """
        XMLStreamHandler.__init__(self)
        self.lock = threading.RLock()
        if settings is None:
            settings = XMPPSettings()
        self.settings = settings
        self.stanza_namespace = stanza_namespace
        self._stanza_namespace_p = "{{{0}}}".format(stanza_namespace)
        self.stanza_route = stanza_route
        self.handlers = handlers
        self._stream_feature_handlers = []
        for handler in handlers:
            if isinstance(handler, StreamFeatureHandler):
                self._stream_feature_handlers.append(handler)
        self.me = None
        self.peer = None
        self.stream_id = None
        self.initiator = None
        self.features = None
        self.authenticated = False
        self.peer_authenticated = False
        self.tls_established = False
        self.auth_method_used = None
        self.version = None
        self.language = None
        self.peer_language = None
        self.transport = None
        self._input_state = None
        self._output_state = None
        self._element_handlers = {}

    def initiate(self, transport, to = None):
        """Initiate an XMPP connection over the `transport`.

        :Parameters:
            - `transport`: an XMPP transport instance
            - `to`: peer name
        """
        with self.lock:
            self.initiator = True
            self.transport = transport
            transport.set_target(self)
            if to:
                self.peer = JID(to)
            else:
                self.peer = None
            if transport.is_connected():
                self._initiate()

    def _initiate(self):
        """Initiate an XMPP connection over a connected `transport`.

        [ called with `lock` acquired ]
        """
        self._setup_stream_element_handlers()
        self._send_stream_start()

    def receive(self, transport, myname):
        """Receive an XMPP connection over the `transport`.

        :Parameters:
            - `transport`: an XMPP transport instance
            - `myname`: local stream endpoint name.
        """
        with self.lock:
            self.transport = transport
            transport.set_target(self)
            self.me = JID(myname)
            self.initiator = False
            self._setup_stream_element_handlers()

    def _setup_stream_element_handlers(self):
        """Set up stream element handlers.

        Scans the `handlers` list for `StreamFeatureHandler`
        instances and updates `_element_handlers` mapping with their
        methods decorated with @`stream_element_handler`
        """
        # pylint: disable-msg=W0212
        if self.initiator:
            mode = "initiator"
        else:
            mode = "receiver"
        self._element_handlers = {}
        for handler in self.handlers:
            if not isinstance(handler, StreamFeatureHandler):
                continue
            for _unused, meth in inspect.getmembers(handler, callable):
                if not hasattr(meth, "_pyxmpp_stream_element_handled"):
                    continue
                element_handled = meth._pyxmpp_stream_element_handled
                if element_handled in self._element_handlers:
                    # use only the first matching handler
                    continue
                if meth._pyxmpp_usage_restriction in (None, mode):
                    self._element_handlers[element_handled] = meth

    def disconnect(self):
        """Gracefully close the connection."""
        with self.lock:
            self.transport.disconnect()
            self._output_state = "closed"

    def event(self, event): # pylint: disable-msg=R0201
        """Handle a stream event.

        Called when connection state is changed.

        Should not be called with self.lock acquired!
        """
        event.stream = self
        logger.debug(u"Stream event: {0}".format(event))
        self.settings["event_queue"].put(event)
        return False

    def transport_connected(self):
        """Called when transport has been connected.

        Send the stream head if initiator.
        """
        with self.lock:
            if self.initiator:
                if self._output_state is None:
                    self._initiate()

    def close(self):
        """Forcibly close the connection and clear the stream state."""
        self.transport.close()

    def stream_start(self, element):
        """Process <stream:stream> (stream start) tag received from peer.

        `lock` is acquired when this method is called.

        :Parameters:
            - `element`: root element (empty) created by the parser"""
        with self.lock:
            logger.debug("input document: " + element_to_unicode(element))
            if not element.tag.startswith(STREAM_QNP):
                self._send_stream_error("invalid-namespace")
                raise FatalStreamError("Bad stream namespace")
            if element.tag != STREAM_ROOT_TAG:
                self._send_stream_error("bad-format")
                raise FatalStreamError("Bad root element")

            if self._input_state == "restart":
                event = StreamRestartedEvent(self.peer)
            else:
                event = StreamConnectedEvent(self.peer)
            self._input_state = "open"
            version = element.get("version")
            if version:
                try:
                    major, minor = version.split(".", 1)
                    major, minor = int(major), int(minor)
                except ValueError:
                    self._send_stream_error("unsupported-version")
                    raise FatalStreamError("Unsupported protocol version.")
                self.version = (major, minor)
            else:
                self.version = (0, 9)

            if self.version[0] != 1 and self.version != (0, 9):
                self._send_stream_error("unsupported-version")
                raise FatalStreamError("Unsupported protocol version.")

            peer_lang = element.get(XML_LANG_QNAME)
            self.peer_language = peer_lang
            if not self.initiator:
                lang = None
                languages = self.settings["languages"]
                while peer_lang:
                    if peer_lang in languages:
                        lang = peer_lang
                        break
                    match = LANG_SPLIT_RE.match(peer_lang)
                    if not match:
                        break
                    peer_lang = match.group(0)
                if lang:
                    self.language = lang

            if self.initiator:
                self.stream_id = element.get("id")
                peer = element.get("from")
                if peer:
                    peer = JID(peer)
                if self.peer:
                    if peer and peer != self.peer:
                        logger.debug("peer hostname mismatch: {0!r} != {1!r}"
                                                    .format(peer, self.peer))
                self.peer = peer
            else:
                to = element.get("to")
                if to:
                    to = self.check_to(to)
                    if not to:
                        self._send_stream_error("host-unknown")
                        raise FatalStreamError('Bad "to"')
                    self.me = JID(to)
                peer = element.get("from")
                if peer:
                    peer = JID(peer)
                self._send_stream_start(self.generate_id(), stream_to = peer)
                self._send_stream_features()
            self.event(event)


    def stream_end(self):
        """Process </stream:stream> (stream end) tag received from peer.
        """
        logger.debug("Stream ended")
        with self.lock:
            self._input_state = "closed"
            self.transport.disconnect()
            self._output_state = "closed"

    def stream_eof(self):
        """Process stream EOF.
        """
        self.stream_end()

    def stream_element(self, element):
        """Process first level child element of the stream).

        :Parameters:
            - `element`: XML element received
        :Types:
            - `element`: :etree:`ElementTree.Element`
        """
        with self.lock:
            self._process_element(element)

    def stream_parse_error(self, descr):
        """Called when an error is encountered in the stream.

        :Parameters:
            - `descr`: description of the error
        :Types:
            - `descr`: `unicode`"""
        self.send_stream_error("not-well-formed")
        raise StreamParseError(descr)

    def _send_stream_start(self, stream_id = None, stream_to = None):
        """Send stream start tag."""
        if self._output_state in ("open", "closed"):
            raise StreamError("Stream start already sent")
        if not self.language:
            self.language = self.settings["language"]
        if stream_to:
            stream_to = unicode(stream_to)
        elif self.peer and self.initiator:
            stream_to = unicode(self.peer)
        stream_from = None
        if self.me and (self.tls_established or not self.initiator):
            stream_from = unicode(self.me)
        if stream_id:
            self.stream_id = stream_id
        else:
            self.stream_id = None
        self.transport.send_stream_head(self.stanza_namespace,
                                        stream_from, stream_to,
                                    self.stream_id, language = self.language)
        self._output_state = "open"

    def send_stream_error(self, condition):
        """Send stream error element.

        :Parameters:
            - `condition`: stream error condition name, as defined in the
              XMPP specification.
        """
        with self.lock:
            self._send_stream_error(condition)

    def _send_stream_error(self, condition):
        """Same as `send_stream_error`, but expects `lock` acquired.
        """
        if self._output_state is "closed":
            return
        if self._output_state in (None, "restart"):
            self._send_stream_start()
        element = StreamErrorElement(condition).as_xml()
        self.transport.send_element(element)
        self.transport.disconnect()
        self._output_state = "closed"

    def _restart_stream(self):
        """Restart the stream as needed after SASL and StartTLS negotiation."""
        self._input_state = "restart"
        self._output_state = "restart"
        self.features = None
        self.transport.restart()
        if self.initiator:
            self._send_stream_start(self.stream_id)

    def _make_stream_features(self):
        """Create the <features/> element for the stream.

        [receiving entity only]

        :returns: new <features/> element
        :returntype: :etree:`ElementTree.Element`"""
        features = ElementTree.Element(FEATURES_TAG)
        for handler in self._stream_feature_handlers:
            handler.make_stream_features(self, features)
        return features

    def _send_stream_features(self):
        """Send stream <features/>.

        [receiving entity only]"""
        self.features = self._make_stream_features()
        self._write_element(self.features)

    def write_element(self, element):
        """Write XML `element` to the stream.

        :Parameters:
            - `element`: Element node to send.
        :Types:
            - `element`: :etree:`ElementTree.Element`
        """
        with self.lock:
            self._write_element(element)

    def _write_element(self, element):
        """Same as `write_element` but with `lock` already acquired.
        """
        self.transport.send_element(element)

    def send(self, stanza):
        """Write stanza to the stream.

        :Parameters:
            - `stanza`: XMPP stanza to send.
        :Types:
            - `stanza`: `pyxmpp2.stanza.Stanza`
        """
        with self.lock:
            return self._send(stanza)

    def _send(self, stanza):
        """Same as `send` but assume `lock` is acquired."""
        self.fix_out_stanza(stanza)
        element = stanza.as_xml()
        self._write_element(element)

    def _process_element(self, element):
        """Process first level element of the stream.

        The element may be stream error or features, StartTLS
        request/response, SASL request/response or a stanza.

        :Parameters:
            - `element`: XML element
        :Types:
            - `element`: :etree:`ElementTree.Element`
        """
        tag = element.tag
        if tag in self._element_handlers:
            handler = self._element_handlers[tag]
            logger.debug("Passing element {0!r} to method {1!r}"
                                                .format(element, handler))
            handled = handler(self, element)
            if handled:
                return
        if tag.startswith(self._stanza_namespace_p):
            stanza = stanza_factory(element, self, self.language)
            self.uplink_receive(stanza)
        elif tag == ERROR_TAG:
            error = StreamErrorElement(element)
            self.process_stream_error(error)
        elif tag == FEATURES_TAG:
            logger.debug("Got features element: {0}".format(serialize(element)))
            self._got_features(element)
        else:
            logger.debug("Unhandled element: {0}".format(serialize(element)))
            logger.debug(" known handlers: {0!r}".format(
                                                    self._element_handlers))

    def uplink_receive(self, stanza):
        """Handle stanza received from the stream."""
        with self.lock:
            if self.stanza_route:
                self.stanza_route.uplink_receive(stanza)
            else:
                logger.debug(u"Stanza dropped (no route): {0!r}".format(stanza))

    def process_stream_error(self, error):
        """Process stream error element received.

        :Parameters:
            - `error`: error received

        :Types:
            - `error`: `StreamErrorElement`
        """
        # pylint: disable-msg=R0201
        logger.debug("Unhandled stream error: condition: {0} {1!r}"
                            .format(error.condition_name, error.serialize()))

    def check_to(self, to):
        """Check "to" attribute of received stream header.

        :return: `to` if it is equal to `me`, None otherwise.

        Should be overriden in derived classes which require other logic
        for handling that attribute."""
        if to != self.me:
            return None
        return to

    def generate_id(self):
        """Generate a random and unique stream ID.

        :return: the id string generated."""
        # pylint: disable-msg=R0201
        return unicode(uuid.uuid4())

    def _got_features(self, features):
        """Process incoming <stream:features/> element.

        [initiating entity only]

        The received features node is available in `features`."""
        self.features = features
        logger.debug("got features, passing to event handlers...")
        handled = self.event(GotFeaturesEvent(self.features))
        logger.debug("  handled: {0}".format(handled))
        if not handled:
            mandatory_handled = []
            mandatory_not_handled = []
            logger.debug("  passing to stream features handlers: {0}"
                                    .format(self._stream_feature_handlers))
            for handler in self._stream_feature_handlers:
                ret = handler.handle_stream_features(self, self.features)
                if ret is None:
                    continue
                elif isinstance(ret, StreamFeatureHandled):
                    if ret.mandatory:
                        mandatory_handled.append(unicode(ret))
                        break
                    break
                elif isinstance(ret, StreamFeatureNotHandled):
                    if ret.mandatory:
                        mandatory_not_handled.append(unicode(ret))
                        break
                else:
                    raise ValueError("Wrong value returned from a stream"
                            " feature handler: {0!r}".format(ret))
            if mandatory_not_handled and not mandatory_handled:
                self.send_stream_error("unsupported-feature")
                raise FatalStreamError(
                        u"Unsupported mandatory-to-implement features: "
                                        + u" ".join(mandatory_not_handled))

    def is_connected(self):
        """Check if stream is is_connected and stanzas may be sent.

        :return: True if stream connection is active."""
        return self.transport.is_connected() and self._output_state == "open"

    def set_peer_authenticated(self, peer, restart_stream = False):
        """Mark the other side of the stream authenticated as `peer`

        :Parameters:
            - `peer`: local JID just authenticated
            - `restart_stream`: `True` when stream should be restarted (needed
              after SASL authentication)
        :Types:
            - `peer`: `JID`
            - `restart_stream`: `bool`
        """
        with self.lock:
            self.peer_authenticated = True
            self.peer = peer
            if restart_stream:
                self._restart_stream()
        self.event(AuthenticatedEvent(self.peer))

    def set_authenticated(self, me, restart_stream = False):
        """Mark stream authenticated as `me`.

        :Parameters:
            - `me`: local JID just authenticated
            - `restart_stream`: `True` when stream should be restarted (needed
              after SASL authentication)
        :Types:
            - `me`: `JID`
            - `restart_stream`: `bool`
        """
        with self.lock:
            self.authenticated = True
            self.me = me
            if restart_stream:
                self._restart_stream()
        self.event(AuthenticatedEvent(self.me))

    def fix_in_stanza(self, stanza):
        """Fix incoming stanza, setting the implicit fields.

        Used for for servers side of client stream to set proper stanza from.
        """
        # pylint: disable-msg=R0201
        return stanza

    def fix_out_stanza(self, stanza):
        """Fix outgoing, setting or clearing the implicit fields.

        Used for for client side of client stream to clear the 'from'
        attribute.
        """
        # pylint: disable-msg=R0201
        return stanza

    @property
    def auth_properties(self):
        """Authentication properties of the stream.

        Derived from the transport with 'local-jid' and 'service-type' added.
        """
        props = dict(self.settings["extra_auth_properties"])
        if self.transport:
            props.update(self.transport.auth_properties)
        props["local-jid"] = self.me
        props["service-type"] = "xmpp"
        return props class StreamSASLHandler(StreamFeatureHandler):
    """SASL authentication handler XMPP streams.

    :Ivariables:
        - `peer_sasl_mechanisms`: SASL mechanisms offered by peer
        - `authenticator`: the authenticator object
    :Types:
        - `peer_sasl_mechanisms`: `list` of `unicode`
        - `authenticator`: `sasl.ClientAuthenticator` or
          `sasl.ServerAuthenticator`
    """
    def __init__(self, settings = None):
        """Initialize the SASL handler"""
        if settings is None:
            settings = XMPPSettings()
        self.settings = settings
        self.peer_sasl_mechanisms = None
        self.authenticator = None

    def make_stream_features(self, stream, features):
        """Add SASL features to the <features/> element of the stream.

        [receiving entity only]

        :returns: update <features/> element."""
        mechs = self.settings['sasl_mechanisms']
        if mechs and not stream.authenticated:
            sub = ElementTree.SubElement(features, MECHANISMS_TAG)
            for mech in mechs:
                if mech in sasl.SERVER_MECHANISMS:
                    ElementTree.SubElement(sub, MECHANISM_TAG).text = mech
        return features

    def handle_stream_features(self, stream, features):
        """Process incoming <stream:features/> element.

        [initiating entity only]
        """
        element = features.find(MECHANISMS_TAG)
        self.peer_sasl_mechanisms = []
        if element is None:
            return None
        for sub in element:
            if sub.tag != MECHANISM_TAG:
                continue
            self.peer_sasl_mechanisms.append(sub.text)

        if stream.authenticated or not self.peer_sasl_mechanisms:
            return StreamFeatureNotHandled("SASL", mandatory = True)

        username = self.settings.get("username")
        if not username:
            # TODO: other rules for s2s
            if stream.me.local:
                username = stream.me.local
            else:
                username = None
        self._sasl_authenticate(stream, username, self.settings.get("authzid"))
        return StreamFeatureHandled("SASL", mandatory = True)

    @stream_element_handler(AUTH_TAG, "receiver")
    def process_sasl_auth(self, stream, element):
        """Process incoming <sasl:auth/> element.

        [receiving entity only]
        """
        if self.authenticator:
            logger.debug("Authentication already started")
            return False

        password_db = self.settings["password_database"]
        mechanism = element.get("mechanism")
        if not mechanism:
            stream.send_stream_error("bad-format")
            raise FatalStreamError("<sasl:auth/> with no mechanism")

        stream.auth_method_used = mechanism
        self.authenticator = sasl.server_authenticator_factory(mechanism,
                                                                password_db)

        content = element.text.encode("us-ascii")
        ret = self.authenticator.start(stream.auth_properties,
                                                a2b_base64(content))

        if isinstance(ret, sasl.Success):
            element = ElementTree.Element(SUCCESS_TAG)
            element.text = ret.encode()
        elif isinstance(ret, sasl.Challenge):
            element = ElementTree.Element(CHALLENGE_TAG)
            element.text = ret.encode()
        else:
            element = ElementTree.Element(FAILURE_TAG)
            ElementTree.SubElement(element, SASL_QNP + ret.reason)

        stream.write_element(element)

        if isinstance(ret, sasl.Success):
            self._handle_auth_success(stream, ret)
        elif isinstance(ret, sasl.Failure):
            raise SASLAuthenticationFailed("SASL authentication failed: {0}"
                                                            .format(ret.reason))
        return True

    def _handle_auth_success(self, stream, success):
        """Handle successful authentication.

        Send <success/> and mark the stream peer authenticated.

        [receiver only]
        """
        if not self._check_authorization(success.properties, stream):
            element = ElementTree.Element(FAILURE_TAG)
            ElementTree.SubElement(element, SASL_QNP + "invalid-authzid")
            return True
        authzid = success.properties.get("authzid")
        if authzid:
            peer = JID(success.authzid)
        elif "username" in success.properties:
            peer = JID(success.properties["username"], stream.me.domain)
        else:
            # anonymous
            peer = None
        stream.set_peer_authenticated(peer, True)

    @stream_element_handler(CHALLENGE_TAG, "initiator")
    def _process_sasl_challenge(self, stream, element):
        """Process incoming <sasl:challenge/> element.

        [initiating entity only]
        """
        if not self.authenticator:
            logger.debug("Unexpected SASL challenge")
            return False

        content = element.text.encode("us-ascii")
        ret = self.authenticator.challenge(a2b_base64(content))
        if isinstance(ret, sasl.Response):
            element = ElementTree.Element(RESPONSE_TAG)
            element.text = ret.encode()
        else:
            element = ElementTree.Element(ABORT_TAG)

        stream.write_element(element)

        if isinstance(ret, sasl.Failure):
            stream.disconnect()
            raise SASLAuthenticationFailed("SASL authentication failed")

        return True

    @stream_element_handler(RESPONSE_TAG, "receiver")
    def _process_sasl_response(self, stream, element):
        """Process incoming <sasl:response/> element.

        [receiving entity only]
        """
        if not self.authenticator:
            logger.debug("Unexpected SASL response")
            return False

        content = element.text.encode("us-ascii")
        ret = self.authenticator.response(a2b_base64(content))
        if isinstance(ret, sasl.Success):
            element = ElementTree.Element(SUCCESS_TAG)
            element.text = ret.encode()
        elif isinstance(ret, sasl.Challenge):
            element = ElementTree.Element(CHALLENGE_TAG)
            element.text = ret.encode()
        else:
            element = ElementTree.Element(FAILURE_TAG)
            ElementTree.SubElement(element, SASL_QNP + ret.reason)

        stream.write_element(element)

        if isinstance(ret, sasl.Success):
            self._handle_auth_success(stream, ret)
        elif isinstance(ret, sasl.Failure):
            raise SASLAuthenticationFailed("SASL authentication failed: {0!r}"
                                                            .format(ret.reson))
        return True

    def _check_authorization(self, properties, stream):
        """Check authorization id and other properties returned by the
        authentication mechanism.

        [receiving entity only]

        Allow only no authzid or authzid equal to current username@domain

        FIXME: other rules in s2s

        :Parameters:
            - `properties`: data obtained during authentication
        :Types:
            - `properties`: mapping

        :return: `True` if user is authorized to use a provided authzid
        :returntype: `bool`
        """
        authzid = properties.get("authzid")
        if not authzid:
            return True
        try:
            jid = JID(authzid)
        except ValueError:
            return False

        if "username" not in properties:
            result = False
        elif jid.local != properties["username"]:
            result = False
        elif jid.domain != stream.me.domain:
            result = False
        elif jid.resource:
            result = False
        else:
            result = True
        return result

    @stream_element_handler(SUCCESS_TAG, "initiator")
    def _process_sasl_success(self, stream, element):
        """Process incoming <sasl:success/> element.

        [initiating entity only]

        """
        if not self.authenticator:
            logger.debug("Unexpected SASL response")
            return False

        content = element.text

        if content:
            data = a2b_base64(content.encode("us-ascii"))
        else:
            data = None
        ret = self.authenticator.finish(data)
        if isinstance(ret, sasl.Success):
            logger.debug("SASL authentication succeeded")
            authzid = ret.properties.get("authzid")
            if authzid:
                me = JID(authzid)
            elif "username" in ret.properties:
                # FIXME: other rules for server
                me = JID(ret.properties["username"], stream.peer.domain)
            else:
                me = None
            stream.set_authenticated(me, True)
        else:
            logger.debug("SASL authentication failed")
            raise SASLAuthenticationFailed("Additional success data"
                                                        " procesing failed")
        return True

    @stream_element_handler(FAILURE_TAG, "initiator")
    def _process_sasl_failure(self, stream, element):
        """Process incoming <sasl:failure/> element.

        [initiating entity only]
        """
        _unused = stream
        if not self.authenticator:
            logger.debug("Unexpected SASL response")
            return False

        logger.debug("SASL authentication failed: {0!r}".format(
                                                element_to_unicode(element)))
        raise SASLAuthenticationFailed("SASL authentication failed")

    @stream_element_handler(ABORT_TAG, "receiver")
    def _process_sasl_abort(self, stream, element):
        """Process incoming <sasl:abort/> element.

        [receiving entity only]"""
        _unused, _unused = stream, element
        if not self.authenticator:
            logger.debug("Unexpected SASL response")
            return False

        self.authenticator = None
        logger.debug("SASL authentication aborted")
        return True

    def _sasl_authenticate(self, stream, username, authzid):
        """Start SASL authentication process.

        [initiating entity only]

        :Parameters:
            - `username`: user name.
            - `authzid`: authorization ID.
            - `mechanism`: SASL mechanism to use."""
        if not stream.initiator:
            raise SASLAuthenticationFailed("Only initiating entity start"
                                                        " SASL authentication")
        if stream.features is None or not self.peer_sasl_mechanisms:
            raise SASLNotAvailable("Peer doesn't support SASL")

        props = dict(stream.auth_properties)
        if not props.get("service-domain") and (
                                        stream.peer and stream.peer.domain):
            props["service-domain"] = stream.peer.domain
        if username is not None:
            props["username"] = username
        if authzid is not None:
            props["authzid"] = authzid
        if "password" in self.settings:
            props["password"] = self.settings["password"]
        props["available_mechanisms"] = self.peer_sasl_mechanisms
        enabled = sasl.filter_mechanism_list(
                            self.settings['sasl_mechanisms'], props,
                                            self.settings['insecure_auth'])
        if not enabled:
            raise SASLNotAvailable(
                                "None of SASL mechanism selected can be used")
        props["enabled_mechanisms"] = enabled

        mechanism = None
        for mech in enabled:
            if mech in self.peer_sasl_mechanisms:
                mechanism = mech
                break
        if not mechanism:
            raise SASLMechanismNotAvailable("Peer doesn't support any of"
                                                    " our SASL mechanisms")
        logger.debug("Our mechanism: {0!r}".format(mechanism))

        stream.auth_method_used = mechanism
        self.authenticator = sasl.client_authenticator_factory(mechanism)
        initial_response = self.authenticator.start(props)
        if not isinstance(initial_response, sasl.Response):
            raise SASLAuthenticationFailed("SASL initiation failed")

        element = ElementTree.Element(AUTH_TAG)
        element.set("mechanism", mechanism)
        if initial_response.data:
            if initial_response.encode:
                element.text = initial_response.encode()
            else:
                element.text = initial_response.data
        stream.write_element(element) class SASLAuthenticationFailed(StreamAuthenticationError):
    """Raised when stream SASL authentication fails."""
    pass class XMPPSettings(MutableMapping):
    """Container for various parameters used all over PyXMPP.

    It can be used like a regular dictionary, but will provide reasonable
    defaults for PyXMPP for parameters which are not explicitly set.

    All known PyXMPP settings are included in the :r:`settings list`.

    :CVariables:
        - `_defaults`: defaults for registered parameters.
        - `_defaults_factories`: factory functions providing default values
          which cannot be hard-coded.
    :Ivariables:
        - `_settings`: current values of the parameters explicitly set.
    """
    _defs = {}
    def __init__(self, data = None):
        """Create settings, optionally initialized with `data`.

        :Parameters:
            - `data`: initial data
        :Types:
            - `data`: any mapping, including `XMPPSettings`
        """
        if data is None:
            self._settings = {}
        else:
            self._settings = dict(data)
    def __len__(self):
        """Number of parameters set."""
        return len(self._settings)
    def __iter__(self):
        """Iterate over the parameter names."""
        for key in self._settings.iterkeys():
            return self[key]
    def __contains__(self, key):
        """Check if a parameter is set.

        :Parameters:
            - `key`: the parameter name
        :Types:
            - `key`: `unicode`
        """
        return key in self._settings
    def __getitem__(self, key):
        """Get a parameter value. Return the default if no value is set
        and the default is provided by PyXMPP.

        :Parameters:
            - `key`: the parameter name
        :Types:
            - `key`: `unicode`
        """
        return self.get(key, required = True)
    def __setitem__(self, key, value):
        """Set a parameter value.

        :Parameters:
            - `key`: the parameter name
            - `value`: the new value
        :Types:
            - `key`: `unicode`
        """
        self._settings[unicode(key)] = value
    def __delitem__(self, key):
        """Unset a parameter value.

        :Parameters:
            - `key`: the parameter name
        :Types:
            - `key`: `unicode`
        """
        del self._settings[key]
    def get(self, key, local_default = None, required = False):
        """Get a parameter value.

        If parameter is not set, return `local_default` if it is not `None`
        or the PyXMPP global default otherwise.

        :Raise `KeyError`: if parameter has no value and no global default

        :Return: parameter value
        """
        # pylint: disable-msg=W0221
        if key in self._settings:
            return self._settings[key]
        if local_default is not None:
            return local_default
        if key in self._defs:
            setting_def = self._defs[key]
            if setting_def.default is not None:
                return setting_def.default
            factory = setting_def.factory
            if factory is None:
                return None
            value = factory(self)
            if setting_def.cache is True:
                setting_def.default = value
            return value
        if required:
            raise KeyError(key)
        return local_default
    def keys(self):
        """Return names of parameters set.

        :Returntype: - `list` of `unicode`
        """
        return self._settings.keys()
    def items(self):
        """Return names and values of parameters set.

        :Returntype: - `list` of tuples
        """
        return self._settings.items()

    def load_arguments(self, args):
        """Load settings from :std:`ArgumentParser` output.

        :Parameters:
            - `args`: output of argument parsed based on the one
              returned by `get_arg_parser()`
        """
        for name, setting in self._defs.items():
            if sys.version_info.major < 3:
                # pylint: disable-msg=W0404
                from locale import getpreferredencoding
                encoding = getpreferredencoding()
                name = name.encode(encoding, "replace")
            attr = "pyxmpp2_" + name
            try:
                self[setting.name] = getattr(args, attr)
            except AttributeError:
                pass

    @classmethod
    def add_setting(cls, name, type = unicode, default = None, factory = None,
                        cache = False, default_d = None, doc = None,
                        cmdline_help = None, validator = None, basic = False):
        """Add a new setting definition.

        :Parameters:
            - `name`: setting name
            - `type`: setting type object or type description
            - `default`: default value
            - `factory`: default value factory
            - `cache`: if `True` the `factory` will be called only once
              and its value stored as a constant default.
            - `default_d`: description of the default value
            - `doc`: setting documentation
            - `cmdline_help`: command line argument description. When not
              provided then the setting won't be available as a command-line
              option
            - `basic`: when `True` the option is considered a basic option -
              one of those which should usually stay configurable in
              an application.
            - `validator`: function validating command-line option value string
              and returning proper value for the settings objects. Defaults
              to `type`.
        :Types:
            - `name`: `unicode`
            - `type`: type or `unicode`
            - `factory`: a callable
            - `cache`: `bool`
            - `default_d`: `unicode`
            - `doc`: `unicode`
            - `cmdline_help`: `unicode`
            - `basic`: `bool`
            - `validator`: a callable
        """
        # pylint: disable-msg=W0622,R0913
        setting_def = _SettingDefinition(name, type, default, factory,
                                            cache, default_d, doc,
                                            cmdline_help, validator, basic)
        if name not in cls._defs:
            cls._defs[name] = setting_def
            return
        duplicate = cls._defs[name]
        if duplicate.type != setting_def.type:
            raise ValueError("Setting duplicate, with a different type")
        if duplicate.default != setting_def.default:
            raise ValueError("Setting duplicate, with a different default")
        if duplicate.factory != setting_def.factory:
            raise ValueError("Setting duplicate, with a different factory")

    @staticmethod
    def validate_string_list(value):
        """Validator for string lists to be used with `add_setting`."""
        try:
            if sys.version_info.major < 3:
                # pylint: disable-msg=W0404
                from locale import getpreferredencoding
                encoding = getpreferredencoding()
                value = value.decode(encoding)
            return [x.strip() for x in value.split(u",")]
        except (AttributeError, TypeError, UnicodeError):
            raise ValueError("Bad string list")

    @staticmethod
    def validate_positive_int(value):
        """Positive integer validator to be used with `add_setting`."""
        value = int(value)
        if value <= 0:
            raise ValueError("Positive number required")
        return value

    @staticmethod
    def validate_positive_float(value):
        """Positive float validator to be used with `add_setting`."""
        value = float(value)
        if value <= 0:
            raise ValueError("Positive number required")
        return value

    @staticmethod
    def get_int_range_validator(start, stop):
        """Return an integer range validator to be used with `add_setting`.

        :Parameters:
            - `start`: minimum value for the integer
            - `stop`: the upper bound (maximum value + 1)
        :Types:
            - `start`: `int`
            - `stop`: `int`

        :return: a validator function
        """
        def validate_int_range(value):
            """Integer range validator."""
            value = int(value)
            if value >= start and value < stop:
                return value
            raise ValueError("Not in <{0},{1}) range".format(start, stop))
        return validate_int_range

    @classmethod
    def list_all(cls, basic = None):
        """List known settings.

        :Parameters:
            - `basic`: When `True` then limit output to the basic settings,
              when `False` list only the extra settings.
        """
        if basic is None:
            return [s for s in cls._defs]
        else:
            return [s.name for s in cls._defs.values() if s.basic == basic]

    @classmethod
    def get_arg_parser(cls, settings = None, option_prefix = u'--',
                                                            add_help = False):
        """Make a command-line option parser.

        The returned parser may be used as a parent parser for application
        argument parser.

        :Parameters:
            - `settings`: list of PyXMPP2 settings to consider. By default
              all 'basic' settings are provided.
            - `option_prefix`: custom prefix for PyXMPP2 options. E.g.
              ``'--xmpp'`` to differentiate them from not xmpp-related
              application options.
            - `add_help`: when `True` a '--help' option will be included
              (probably already added in the application parser object)
        :Types:
            - `settings`: list of `unicode`
            - `option_prefix`: `str`
            - `add_help`:

        :return: an argument parser object.
        :returntype: :std:`argparse.ArgumentParser`
        """
        # pylint: disable-msg=R0914,R0912
        parser = argparse.ArgumentParser(add_help = add_help,
                                            prefix_chars = option_prefix[0])
        if settings is None:
            settings = cls.list_all(basic = True)

        if sys.version_info.major < 3:
            # pylint: disable-msg=W0404
            from locale import getpreferredencoding
            encoding = getpreferredencoding()
            def decode_string_option(value):
                """Decode a string option."""
                return value.decode(encoding)

        for name in settings:
            if name not in cls._defs:
                logger.debug("get_arg_parser: ignoring unknown option {0}"
                                                                .format(name))
                return
            setting = cls._defs[name]
            if not setting.cmdline_help:
                logger.debug("get_arg_parser: option {0} has no cmdline"
                                                                .format(name))
                return
            if sys.version_info.major < 3:
                name = name.encode(encoding, "replace")
            option = option_prefix + name.replace("_", "-")
            dest = "pyxmpp2_" + name
            if setting.validator:
                opt_type = setting.validator
            elif setting.type is unicode and sys.version_info.major < 3:
                opt_type = decode_string_option
            else:
                opt_type = setting.type
            if setting.default_d:
                default_s = setting.default_d
                if sys.version_info.major < 3:
                    default_s = default_s.encode(encoding, "replace")
            elif setting.default is not None:
                default_s = repr(setting.default)
            else:
                default_s = None
            opt_help = setting.cmdline_help
            if sys.version_info.major < 3:
                opt_help = opt_help.encode(encoding, "replace")
            if default_s:
                opt_help += " (Default: {0})".format(default_s)
            if opt_type is bool:
                opt_action = _YesNoAction
            else:
                opt_action = "store"
            parser.add_argument(option,
                                action = opt_action,
                                default = setting.default,
                                type = opt_type,
                                help = opt_help,
                                metavar = name.upper(),
                                dest = dest)
        return parser class EventRecorder(EventHandler):
    """An event handler which records all events received and aborts the main
    loop on the `DisconnectedEvent`.

    :Ivariables:
        - `events_received`: events received
    :Types:
        - `events_received`: `list` of `Event`
    """
    def __init__(self):
        self.events_received = []
    @event_handler()
    def handle_event(self, event):
        """Handle any event: store it in `events_received`."""
        self.events_received.append(event)
        return False
    @event_handler(DisconnectedEvent)
    def handle_disconnected_event(self, event):
        """Handle the `DisconnectedEvent`: abort the main loop."""
        # pylint: disable=R0201
        event.stream.event(QUIT) class InitiatorSelectTestCase(NetworkTestCase):
    """Base class for XMPP initiator streams tests, using the
    `SelectMainLoop`.

    :Ivariables:
        - `stream`: The stream tested (to be created by a test method)
        - `transport`: TCPTransport used by the stream
        - `loop`: the main loop
    :Types:
        - `transport`: `TCPTransport`
        - `loop`: `MainLoop`
    """
    def setUp(self):
        super(InitiatorSelectTestCase, self).setUp()
        self.stream = None
        self.transport = None
        self.loop = None

    def start_transport(self, handlers):
        """Initialize a transport and a main loop with the provided handlers"""
        self.transport = TCPTransport()
        self.make_loop(handlers + [self.transport])

    def connect_transport(self):
        """Start a test server and connect the transport to it."""
        addr, port = self.start_server()
        self.transport.connect(addr, port)

    def make_loop(self, handlers):
        """Return a main loop object for use with this test suite."""
        self.loop = SelectMainLoop(None, handlers)

    def tearDown(self):
        super(InitiatorSelectTestCase, self).tearDown()
        self.loop = None
        self.stream = None
        if self.transport:
            self.transport.close()
        self.transport = None

    def wait(self, timeout = TIMEOUT, expect = None):
        """Wait until the main loop finishes, `timeout` seconds passes
        or current server input matches `expect`."""
        timeout = time.time() + timeout
        while not self.loop.finished:
            self.loop.loop_iteration(0.1)
            if expect:
                match = expect.match(self.server.rdata)
                if match:
                    return match.group(1)
            if time.time() > timeout:
                break

    def wait_short(self, timeout = 0.1):
        """Run a single main loop iteration."""
        self.loop.loop_iteration(timeout) class ReceiverSelectTestCase(NetworkTestCase):
    """Base class for XMPP receiver streams tests, using the
    `SelectMainLoop`"""
    def setUp(self):
        super(ReceiverSelectTestCase, self).setUp()
        self.stream = None
        self.transport = None
        self.loop = None
        self.addr = None

    def start_transport(self, handlers):
        """Create a listening socket for the tested stream,
        a transport a main loop and create a client connecting to the
        socket."""
        sock = self.make_listening_socket()
        self.addr = sock.getsockname()
        self.start_client(self.addr)
        self.transport = TCPTransport(sock = sock.accept()[0])
        sock.close()
        self.make_loop(handlers + [self.transport])

    def make_loop(self, handlers):
        """Create the main loop object."""
        self.loop = SelectMainLoop(None, handlers)

    def wait(self, timeout = TIMEOUT, expect = None):
        """Wait until the main loop finishes, `timeout` seconds passes
        or current server input matches `expect`."""
        timeout = time.time() + timeout
        while not self.loop.finished:
            self.loop.loop_iteration(0.1)
            if expect:
                match = expect.match(self.client.rdata)
                if match:
                    return match.group(1)
            if time.time() > timeout:
                break

    def wait_short(self, timeout = 0.1):
        """Run a single main loop iteration."""
        self.loop.loop_iteration(timeout)

    def tearDown(self):
        self.loop = None
        self.stream = None
        if self.transport:
            self.transport.close()
        self.transport = None
        super(ReceiverSelectTestCase, self).tearDown() def load_tests(loader, tests, pattern):
    """Use default test list, just remove the classes which names start with
    '_'."""
    # pylint: disable=W0613
    suite = filter_tests(tests)
    return suite def setup_logging():
    """Set up logging for the tests.

    Log level used depends on number of '-v' in sys.argv
    """
    # pylint: disable=W0603
    global logging_ready
    if logging_ready:
        return
    if sys.argv.count("-v") > 2:
        logging.basicConfig(level=logging.DEBUG)
    elif sys.argv.count("-v") == 2:
        logging.basicConfig(level=logging.INFO)
    else:
        logging.basicConfig(level=logging.ERROR)
    logging_ready = True
import unittest
import re
import base64
import binascii
import os
import pyxmpp2.etree
        from xml.etree import cElementTree
from pyxmpp2.etree import ElementTree
from pyxmpp2.streambase import StreamBase
from pyxmpp2.streamsasl import StreamSASLHandler
from pyxmpp2.streamevents import * # pylint: disable=W0614,W0401
from pyxmpp2.exceptions import SASLAuthenticationFailed
from pyxmpp2.settings import XMPPSettings
from pyxmpp2.test._util import EventRecorder
from pyxmpp2.test._util import InitiatorSelectTestCase
from pyxmpp2.test._util import ReceiverSelectTestCase
from pyxmpp2.test._support import load_tests, setup_logging
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# pylint: disable=C0111



if "PYXMPP2_ETREE" not in os.environ:
    # one of tests fails when xml.etree.ElementTree is used
    try:
        pyxmpp2.etree.ElementTree = cElementTree
    except ImportError:
        pass




C2S_SERVER_STREAM_HEAD = (b'<stream:stream version="1.0" from="127.0.0.1"'
                            b' xmlns:stream="http://etherx.jabber.org/streams"'
                            b' xmlns="jabber:client">')
C2S_CLIENT_STREAM_HEAD = (b'<stream:stream version="1.0" to="127.0.0.1"'
                            b' xmlns:stream="http://etherx.jabber.org/streams"'
                            b' xmlns="jabber:client">')

AUTH_FEATURES = b"""<stream:features>
     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
        <mechanism>PLAIN</mechanism>
     </mechanisms>
</stream:features>"""

BIND_FEATURES = b"""<stream:features>
     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
</stream:features>"""

PLAIN_AUTH = ("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
                                            " mechanism='PLAIN'>{0}</auth>")

STREAM_TAIL = b'</stream:stream>'

PARSE_ERROR_RESPONSE = (b'<stream:error><xml-not-well-formed'
                    b'  xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>'
                                        b'</stream:error></stream:stream>')

TIMEOUT = 1.0 # seconds

class TestInitiator(InitiatorSelectTestCase):
    def test_auth(self):
        handler = EventRecorder()
        settings = XMPPSettings({
                                u"username": u"user",
                                u"password": u"secret",
                                })
        self.stream = StreamBase(u"jabber:client", None,
                            [StreamSASLHandler(settings), handler], settings)
        self.start_transport([handler])
        self.stream.initiate(self.transport)
        self.connect_transport()
        self.server.write(C2S_SERVER_STREAM_HEAD)
        self.server.write(AUTH_FEATURES)
        xml = self.wait(expect = re.compile(br".*(<auth.*</auth>)"))
        self.assertIsNotNone(xml)
        element = ElementTree.XML(xml)
        self.assertEqual(element.tag, "{urn:ietf:params:xml:ns:xmpp-sasl}auth")
        mech = element.get("mechanism")
        self.assertEqual(mech, "PLAIN")
        data = binascii.a2b_base64(element.text.encode("utf-8"))
        self.assertEqual(data, b"\000user\000secret")
        self.server.rdata = b""
        self.server.write(
                        b"<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>")
        stream_start = self.wait(expect = re.compile(
                                                br"(<stream:stream[^>]*>)"))
        self.assertIsNotNone(stream_start)
        self.assertTrue(self.stream.authenticated)
        self.server.write(C2S_SERVER_STREAM_HEAD)
        self.server.write(BIND_FEATURES)
        self.server.write(b"</stream:stream>")
        self.server.disconnect()
        self.wait()
        event_classes = [e.__class__ for e in handler.events_received]
        self.assertEqual(event_classes, [ConnectingEvent, ConnectedEvent,
                    StreamConnectedEvent, GotFeaturesEvent,
                    AuthenticatedEvent, StreamRestartedEvent, GotFeaturesEvent,
                    DisconnectedEvent])

    def test_auth_fail(self):
        handler = EventRecorder()
        settings = XMPPSettings({
                                u"username": u"user",
                                u"password": u"bad",
                                })
        self.stream = StreamBase(u"jabber:client", None,
                            [StreamSASLHandler(settings), handler], settings)
        self.start_transport([handler])
        self.stream.initiate(self.transport)
        self.connect_transport()
        self.server.write(C2S_SERVER_STREAM_HEAD)
        self.server.write(AUTH_FEATURES)
        xml = self.wait(expect = re.compile(br".*(<auth.*</auth>)"))
        self.assertIsNotNone(xml)
        element = ElementTree.XML(xml)
        self.assertEqual(element.tag, "{urn:ietf:params:xml:ns:xmpp-sasl}auth")
        mech = element.get("mechanism")
        self.assertEqual(mech, "PLAIN")
        data = binascii.a2b_base64(element.text.encode("utf-8"))
        self.assertNotEqual(data, b"\000user\000secret")
        self.server.write(b"""<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<not-authorized/></failure>""")
        with self.assertRaises(SASLAuthenticationFailed):
            self.wait()
        self.assertFalse(self.stream.authenticated)
        self.server.disconnect()
        self.wait()
        event_classes = [e.__class__ for e in handler.events_received]
        self.assertEqual(event_classes, [ConnectingEvent, ConnectedEvent,
                    StreamConnectedEvent, GotFeaturesEvent, DisconnectedEvent])

class TestReceiver(ReceiverSelectTestCase):
    def test_auth(self):

        handler = EventRecorder()