#
# (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License Version
# 2.1 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# pylint: disable-msg=W0221, W0201
"""Component (jabber:component:accept) stream handling.

Normative reference:
  - `JEP 114 <http://www.jabber.org/jeps/jep-0114.html>`__
"""

__revision__="$Id$"
__docformat__="restructuredtext en"

import hashlib

import logging

from pyxmpp.stream import Stream
from pyxmpp.streambase import stanza_factory,HostMismatch
from pyxmpp.xmlextra import common_doc,common_root
from pyxmpp.utils import to_utf8
from pyxmpp.exceptions import StreamError,FatalStreamError,ComponentStreamError,FatalComponentStreamError

class ComponentStream(Stream):
    """Handles jabberd component (jabber:component:accept) connection stream.

    :Ivariables:
        - `server`: server to use.
        - `port`: port number to use.
        - `secret`: authentication secret.
    :Types:
        - `server`: `unicode`
        - `port`: `int`
        - `secret`: `unicode`"""

    def __init__(self, jid, secret, server, port, keepalive = 0, owner = None):
        """Initialize a `ComponentStream` object.

        :Parameters:
            - `jid`: JID of the component.
            - `secret`: authentication secret.
            - `server`: server address.
            - `port`: TCP port number on the server.
            - `keepalive`: keepalive interval. 0 to disable.
            - `owner`: `Client`, `Component` or similar object "owning" this stream.
        """
        Stream.__init__(self, "jabber:component:accept",
                    sasl_mechanisms = [],
                    tls_settings = None,
                    keepalive = keepalive,
                    owner = owner)
        self.server=server
        self.port=port
        self.me=jid
        self.secret=secret
        self.process_all_stanzas=1
        self.__logger=logging.getLogger("pyxmpp.jabberd.ComponentStream")

    def _reset(self):
        """Reset `ComponentStream` object state, making the object ready to
        handle new connections."""
        Stream._reset(self)

    def connect(self,server=None,port=None):
        """Establish a client connection to a server.

        [component only]

        :Parameters:
            - `server`: name or address of the server to use.  If not given
              then use the one specified when creating the object.
            - `port`: port number of the server to use.  If not given then use
              the one specified when creating the object.

        :Types:
            - `server`: `unicode`
            - `port`: `int`"""
        self.lock.acquire()
        try:
            self._connect(server,port)
        finally:
            self.lock.release()

    def _connect(self,server=None,port=None):
        """Same as `ComponentStream.connect` but assume `self.lock` is acquired."""
        if self.me.node or self.me.resource:
            raise Value, "Component JID may have only domain defined"
        if not server:
            server=self.server
        if not port:
            port=self.port
        if not server or not port:
            raise ValueError, "Server or port not given"
        Stream._connect(self,server,port,None,self.me)

    def accept(self,sock):
        """Accept an incoming component connection.

        [server only]

        :Parameters:
            - `sock`: a listening socket."""
        Stream.accept(self,sock,None)

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

        Call `Stream.stream_start`, but ignore any `HostMismatch` error.

        :Parameters:
            - `doc`: document created by the parser"""
        try:
            Stream.stream_start(self,doc)
        except HostMismatch:
            pass

    def _post_connect(self):
        """Initialize authentication when the connection is established
        and we are the initiator."""
        if self.initiator:
            self._auth()

    def _compute_handshake(self):
        """Compute the authentication handshake value.

        :return: the computed hash value.
        :returntype: `str`"""
        return hashlib.sha1(to_utf8(self.stream_id)+to_utf8(self.secret)).hexdigest()

    def _auth(self):
        """Authenticate on the server.

        [component only]"""
        if self.authenticated:
            self.__logger.debug("_auth: already authenticated")
            return
        self.__logger.debug("doing handshake...")
        hash_value=self._compute_handshake()
        n=common_root.newTextChild(None,"handshake",hash_value)
        self._write_node(n)
        n.unlinkNode()
        n.freeNode()
        self.__logger.debug("handshake hash sent.")

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

        Handle component handshake (authentication) element, and
        treat elements in "jabber:component:accept", "jabber:client"
        and "jabber:server" equally (pass to `self.process_stanza`).
        All other elements are passed to `Stream._process_node`.

        :Parameters:
            - `node`: XML node describing the element
        """
        ns=node.ns()
        if ns:
            ns_uri=node.ns().getContent()
        if (not ns or ns_uri=="jabber:component:accept") and node.name=="handshake":
            if self.initiator and not self.authenticated:
                self.authenticated=1
                self.state_change("authenticated",self.me)
                self._post_auth()
                return
            elif not self.authenticated and node.getContent()==self._compute_handshake():
                self.peer=self.me
                n=common_doc.newChild(None,"handshake",None)
                self._write_node(n)
                n.unlinkNode()
                n.freeNode()
                self.peer_authenticated=1
                self.state_change("authenticated",self.peer)
                self._post_auth()
                return
            else:
                self._send_stream_error("not-authorized")
                raise FatalComponentStreamError,"Hanshake error."

        if ns_uri in ("jabber:component:accept","jabber:client","jabber:server"):
            stanza=stanza_factory(node)
            self.lock.release()
            try:
                self.process_stanza(stanza)
            finally:
                self.lock.acquire()
                stanza.free()
            return
        return Stream._process_node(self,node)

# vi: sts=4 et sw=4
