Skip to content
Snippets Groups Projects
auth_nb.py 26.6 KiB
Newer Older
##   auth_nb.py
##       based on auth.py, changes backported up to revision 1.41
##
##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
##       modified by Dimitur Kirov <dkirov@gmail.com>
##
##   This program is free software; you can redistribute it and/or modify
##   it under the terms of the GNU General Public License as published by
##   the Free Software Foundation; either version 2, or (at your option)
##   any later version.
##
##   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 General Public License for more details.
Provides plugs for SASL and NON-SASL authentication mechanisms.
Can be used both for client and transport authentication
from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
Yann Leboulanger's avatar
Yann Leboulanger committed
from protocol import NS_STREAM_MGMT
from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
Yann Leboulanger's avatar
Yann Leboulanger committed
from smacks import Smacks
import base64
import random
import itertools
import dispatcher_nb
import hashlib
import logging
log = logging.getLogger('gajim.c.x.auth_nb')

def HH(some): return hashlib.md5(some).hexdigest()
def H(some): return hashlib.md5(some).digest()
def C(some): return ':'.join(some)
SASL_FAILURE_IN_PROGRESS = 'failure-in-process'
SASL_FAILURE = 'failure'
SASL_SUCCESS = 'success'
SASL_UNSUPPORTED = 'not-supported'
SASL_IN_PROCESS = 'in-process'
def challenge_splitter(data):
    """
    Helper function that creates a dict from challenge string

    Sample challenge string:
            username="example.org",realm="somerealm",\
            nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
            nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8

    Expected result for challan:
            dict['qop'] = ('auth','auth-int','auth-conf')
            dict['realm'] = 'somerealm'
    """
    X_KEYWORD, X_VALUE, X_END = 0, 1, 2
    quotes_open = False
    keyword, value = '', ''
    dict_ = {}
    arr = None

    expecting = X_KEYWORD
    for iter_ in range(len(data) + 1):
        end = False
        if iter_ == len(data):
            expecting = X_END
            end = True
        else:
            char = data[iter_]
        if expecting == X_KEYWORD:
            if char == '=':
                expecting  = X_VALUE
            elif char in (',', ' ', '\t'):
                pass
            else:
                keyword = '%s%c' % (keyword, char)
        elif expecting == X_VALUE:
            if char == '"':
                if quotes_open:
                    end = True
                else:
                    quotes_open = True
            elif char in (',', ' ', '\t'):
                if quotes_open:
                    if not arr:
                        arr = [value]
                    else:
                        arr.append(value)
                    value = ""
                else:
                    end = True
            else:
                value = '%s%c' % (value, char)
        if end:
            if arr:
                arr.append(value)
                dict_[keyword] = arr
                arr = None
            else:
                dict_[keyword] = value
            value, keyword = '', ''
            expecting = X_KEYWORD
            quotes_open = False
    return dict_
    return dict(s.split('=', 1) for s in chatter.split(','))
class SASL(PlugIn):
    """
    Implements SASL authentication. Can be plugged into NonBlockingClient
    to start authentication
    """

    def __init__(self, username, password, on_sasl):
        """
        :param user: XMPP username
        :param password: XMPP password
        :param on_sasl: Callback, will be called after each SASL auth-step.
        """
        PlugIn.__init__(self)
        self.username = username
        self.password = password
        self.on_sasl = on_sasl
        self.realm = None

    def plugin(self, owner):
        if 'version' not in self._owner.Dispatcher.Stream._document_attrs:
            self.startsasl = SASL_UNSUPPORTED
        elif self._owner.Dispatcher.Stream.features:
            try:
                self.FeaturesHandler(self._owner.Dispatcher,
Yann Leboulanger's avatar
Yann Leboulanger committed
                    self._owner.Dispatcher.Stream.features)
            except NodeProcessed:
                pass
        else:
            self.startsasl = None

    def plugout(self):
        """
        Remove SASL handlers from owner's dispatcher. Used internally
        """
        if 'features' in  self._owner.__dict__:
            self._owner.UnregisterHandler('features', self.FeaturesHandler,
Yann Leboulanger's avatar
Yann Leboulanger committed
                xmlns=NS_STREAMS)
        if 'challenge' in  self._owner.__dict__:
            self._owner.UnregisterHandler('challenge', self.SASLHandler,
Yann Leboulanger's avatar
Yann Leboulanger committed
                xmlns=NS_SASL)
        if 'failure' in  self._owner.__dict__:
            self._owner.UnregisterHandler('failure', self.SASLHandler,
Yann Leboulanger's avatar
Yann Leboulanger committed
                xmlns=NS_SASL)
        if 'success' in  self._owner.__dict__:
            self._owner.UnregisterHandler('success', self.SASLHandler,
Yann Leboulanger's avatar
Yann Leboulanger committed
                xmlns=NS_SASL)

    def auth(self):
        """
        Start authentication. Result can be obtained via "SASL.startsasl"
        attribute and will be either SASL_SUCCESS or SASL_FAILURE

        Note that successfull auth will take at least two Dispatcher.Process()
        calls.
        """
        if self.startsasl:
            pass
        elif self._owner.Dispatcher.Stream.features:
            try:
                self.FeaturesHandler(self._owner.Dispatcher,
Yann Leboulanger's avatar
Yann Leboulanger committed
                    self._owner.Dispatcher.Stream.features)
            except NodeProcessed:
                pass
        else:
            self._owner.RegisterHandler('features',
Yann Leboulanger's avatar
Yann Leboulanger committed
                self.FeaturesHandler, xmlns=NS_STREAMS)

    def FeaturesHandler(self, conn, feats):
        """
        Used to determine if server supports SASL auth. Used internally
        """
        if not feats.getTag('mechanisms', namespace=NS_SASL):
            self.startsasl='not-supported'
            return
        self.mecs = []
        for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags(
        'mechanism'):
            self.mecs.append(mec.getData())

Yann Leboulanger's avatar
Yann Leboulanger committed
        self._owner.RegisterHandler('challenge', self.SASLHandler,
            xmlns=NS_SASL)
        self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
        self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
        self.MechanismHandler()

    def MechanismHandler(self):
        if 'ANONYMOUS' in self.mecs and self.username is None:
            self.mecs.remove('ANONYMOUS')
Yann Leboulanger's avatar
Yann Leboulanger committed
            node = Node('auth', attrs={'xmlns': NS_SASL,
                'mechanism': 'ANONYMOUS'})
            self.mechanism = 'ANONYMOUS'
            self.startsasl = SASL_IN_PROCESS
            self._owner.send(str(node))
            raise NodeProcessed
        if "EXTERNAL" in self.mecs:
            self.mecs.remove('EXTERNAL')
Yann Leboulanger's avatar
Yann Leboulanger committed
            sasl_data = u'%s@%s' % (self.username, self._owner.Server)
            sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
                '\n', '')
            node = Node('auth', attrs={'xmlns': NS_SASL,
                'mechanism': 'EXTERNAL'}, payload=[sasl_data])
            self.mechanism = 'EXTERNAL'
            self.startsasl = SASL_IN_PROCESS
            self._owner.send(str(node))
            raise NodeProcessed
        if 'GSSAPI' in self.mecs and have_kerberos:
            self.mecs.remove('GSSAPI')
            try:
                self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \
Yann Leboulanger's avatar
Yann Leboulanger committed
                    self._owner.xmpp_hostname)[1]
                kerberos.authGSSClientStep(self.gss_vc, '')
                response = kerberos.authGSSClientResponse(self.gss_vc)
Yann Leboulanger's avatar
Yann Leboulanger committed
                node=Node('auth', attrs={'xmlns': NS_SASL,
                    'mechanism': 'GSSAPI'}, payload=(response or ''))
                self.mechanism = 'GSSAPI'
                self.gss_step = GSS_STATE_STEP
                self.startsasl = SASL_IN_PROCESS
                self._owner.send(str(node))
                raise NodeProcessed
            except kerberos.GSSError, e:
                log.info('GSSAPI authentication failed: %s' % str(e))
        if 'SCRAM-SHA-1' in self.mecs:
            self.mecs.remove('SCRAM-SHA-1')
            self.mechanism = 'SCRAM-SHA-1'
            self._owner._caller.get_password(self.set_password, self.mechanism)
            self.scram_step = 0
            self.startsasl = SASL_IN_PROCESS
            raise NodeProcessed
        if 'DIGEST-MD5' in self.mecs:
            self.mecs.remove('DIGEST-MD5')
Yann Leboulanger's avatar
Yann Leboulanger committed
            node = Node('auth', attrs={'xmlns': NS_SASL,
                'mechanism': 'DIGEST-MD5'})
            self.mechanism = 'DIGEST-MD5'
            self.startsasl = SASL_IN_PROCESS
            self._owner.send(str(node))
            raise NodeProcessed
        if 'PLAIN' in self.mecs:
            self.mecs.remove('PLAIN')
            self.mechanism = 'PLAIN'
            self._owner._caller.get_password(self.set_password, self.mechanism)
            self.startsasl = SASL_IN_PROCESS
            raise NodeProcessed
        self.startsasl = SASL_FAILURE
        log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
            'PLAIN mecanisms.')
        if self.on_sasl:
            self.on_sasl()
        return

    def SASLHandler(self, conn, challenge):
        """
        Perform next SASL auth step. Used internally
        """
        if challenge.getNamespace() != NS_SASL:
            return

        def scram_base64(s):
            return ''.join(s.encode('base64').split('\n'))

        incoming_data = challenge.getData()
        data=base64.decodestring(incoming_data)
            log.info('Failed SASL authentification: %s' % reason)
            self._owner.send(str(Node('abort', attrs={'xmlns': NS_SASL})))
                # There are other mechanisms to test, but wait for <failure>
                # answer from server
                self.startsasl = SASL_FAILURE_IN_PROGRESS
                raise NodeProcessed
            if self.on_sasl:
                self.on_sasl()
            raise NodeProcessed
            if self.startsasl == SASL_FAILURE_IN_PROGRESS:
                self.MechanismHandler()
                raise NodeProcessed
            self.startsasl = SASL_FAILURE
            try:
                reason = challenge.getChildren()[0]
            except Exception:
                reason = challenge
            on_auth_fail(reason)
        elif challenge.getName() == 'success':
            if self.mechanism == 'SCRAM-SHA-1':
                # check data-with-success
                data = scram_parse(data)
                if data['v'] != scram_base64(self.scram_ServerSignature):
                    on_auth_fail('ServerSignature is wrong')

            self.startsasl = SASL_SUCCESS
            log.info('Successfully authenticated with remote server.')
            handlers = self._owner.Dispatcher.dumpHandlers()

            # Bosh specific dispatcher replugging
Yann Leboulanger's avatar
Yann Leboulanger committed
            # save old features. They will be used in case we won't get response
            # on stream restart after SASL auth (happens with XMPP over BOSH
            # with Openfire)
            old_features = self._owner.Dispatcher.Stream.features
            self._owner.Dispatcher.PlugOut()
            dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner,
Yann Leboulanger's avatar
Yann Leboulanger committed
                after_SASL=True, old_features=old_features)
            self._owner.Dispatcher.restoreHandlers(handlers)
            self._owner.User = self.username

            if self.on_sasl:
                self.on_sasl()
            raise NodeProcessed

        ### Perform auth step
        log.info('Got challenge:' + data)

        if self.mechanism == 'GSSAPI':
            if self.gss_step == GSS_STATE_STEP:
                rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
                if rc != kerberos.AUTH_GSS_CONTINUE:
                    self.gss_step = GSS_STATE_WRAP
            elif self.gss_step == GSS_STATE_WRAP:
                rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data)
                response = kerberos.authGSSClientResponse(self.gss_vc)
                rc = kerberos.authGSSClientWrap(self.gss_vc, response,
Yann Leboulanger's avatar
Yann Leboulanger committed
                    kerberos.authGSSClientUserName(self.gss_vc))
            response = kerberos.authGSSClientResponse(self.gss_vc)
            if not response:
                response = ''
            self._owner.send(Node('response', attrs={'xmlns': NS_SASL},
Yann Leboulanger's avatar
Yann Leboulanger committed
                payload=response).__str__())
        if self.mechanism == 'SCRAM-SHA-1':
            hashfn = hashlib.sha1
            def HMAC(k, s):
                return hmac.HMAC(key=k, msg=s, digestmod=hashfn).digest()
                r = (chr(ord(px) ^ ord(py)) for px, py in zip(x, y))
                ui_1 = HMAC(s, salt + '\0\0\0\01')
            if self.scram_step == 0:
                self.scram_step = 1
                self.scram_soup += ',' + data + ','
                data = scram_parse(data)
                # TODO: Should check cnonce here.
                # TODO: Channel binding data goes in here too.
                r = 'c=' + scram_base64(self.scram_gs2)
                r += ',r=' + data['r']
                self.scram_soup += r
                salt = data['s'].decode('base64')
                iter = int(data['i'])
                SaltedPassword = Hi(self.password, salt, iter)
                # TODO: Could cache this, along with salt+iter.
                ClientKey = HMAC(SaltedPassword, 'Client Key')
                ClientSignature = HMAC(StoredKey, self.scram_soup)
                ClientProof = XOR(ClientKey, ClientSignature)
                r += ',p=' + scram_base64(ClientProof)
                ServerKey = HMAC(SaltedPassword, 'Server Key')
                self.scram_ServerSignature = HMAC(ServerKey, self.scram_soup)
                sasl_data = scram_base64(r)
                node = Node('response', attrs={'xmlns': NS_SASL},
                    payload=[sasl_data])
                self._owner.send(str(node))
                raise NodeProcessed
            if self.scram_step == 1:
                data = scram_parse(data)
                if data['v'].decode('base64') != self.scram_ServerSignature:
                    # TODO: Not clear what to do here - need to abort.
                node = Node('response', attrs={'xmlns': NS_SASL});
                self._owner.send(str(node))
                raise NodeProcessed

        # magic foo...
        chal = challenge_splitter(data)
        if not self.realm and 'realm' in chal:
            self.realm = chal['realm']
        if 'qop' in chal and ((isinstance(chal['qop'], str) and \
        chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \
        chal['qop'])):
            self.resp = {}
            self.resp['username'] = self.username
            if self.realm:
                self.resp['realm'] = self.realm
            else:
                self.resp['realm'] = self._owner.Server
            self.resp['nonce'] = chal['nonce']
Yann Leboulanger's avatar
Yann Leboulanger committed
            self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint \
                in itertools.repeat(random.randint, 7))
            self.resp['nc'] = ('00000001')
            self.resp['qop'] = 'auth'
            self.resp['digest-uri'] = 'xmpp/' + self._owner.Server
            self.resp['charset'] = 'utf-8'
            # Password is now required
            self._owner._caller.get_password(self.set_password, self.mechanism)
            # Check rspauth value
            if chal['rspauth'] != self.digest_rspauth:
Yann Leboulanger's avatar
Yann Leboulanger committed
                on_auth_fail('rspauth is wrong')
            self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
        else:
            self.startsasl = SASL_FAILURE
            log.info('Failed SASL authentification: unknown challenge')
        if self.on_sasl:
            self.on_sasl()
        raise NodeProcessed

Alexander Cherniuk's avatar
Alexander Cherniuk committed
    @staticmethod
    def _convert_to_iso88591(string):
        try:
            string = string.decode('utf-8').encode('iso-8859-1')
        except UnicodeEncodeError:
            pass
        return string

    def set_password(self, password):
Alexander Cherniuk's avatar
Alexander Cherniuk committed
        self.password = '' if password is None else password
Alexander Cherniuk's avatar
Alexander Cherniuk committed
            nonce = ''.join('%x' % randint(0, 2 ** 28) for randint in \
                itertools.repeat(random.randint, 7))
            self.scram_soup = 'n=' + self.username + ',r=' + nonce
            self.scram_gs2 = 'n,,' # No CB yet.
            sasl_data = (self.scram_gs2 + self.scram_soup).encode('base64').\
                replace('\n', '')
            node = Node('auth', attrs={'xmlns': NS_SASL,
                'mechanism': self.mechanism}, payload=[sasl_data])
        elif self.mechanism == 'DIGEST-MD5':
Alexander Cherniuk's avatar
Alexander Cherniuk committed
            hash_username = self._convert_to_iso88591(self.resp['username'])
            hash_realm = self._convert_to_iso88591(self.resp['realm'])
            hash_password = self._convert_to_iso88591(self.password)
            A1 = C([H(C([hash_username, hash_realm, hash_password])),
                self.resp['nonce'], self.resp['cnonce']])
            A2 = C(['AUTHENTICATE', self.resp['digest-uri']])
            response = HH(C([HH(A1), self.resp['nonce'], self.resp['nc'],
                self.resp['cnonce'], self.resp['qop'], HH(A2)]))
            A2 = C(['', self.resp['digest-uri']])
            self.digest_rspauth = HH(C([HH(A1), self.resp['nonce'],
                self.resp['nc'], self.resp['cnonce'], self.resp['qop'],
                HH(A2)]))
            self.resp['response'] = response
            sasl_data = u''
            for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
            'digest-uri', 'response', 'qop'):
                if key in ('nc', 'qop', 'response', 'charset'):
                    sasl_data += u"%s=%s," % (key, self.resp[key])
                else:
                    sasl_data += u'%s="%s",' % (key, self.resp[key])
            sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace(
Yann Leboulanger's avatar
Yann Leboulanger committed
                '\r', '').replace('\n', '')
            node = Node('response', attrs={'xmlns': NS_SASL},
                payload=[sasl_data])
        elif self.mechanism == 'PLAIN':
            sasl_data = u'\x00%s\x00%s' % (self.username, self.password)
            sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
Yann Leboulanger's avatar
Yann Leboulanger committed
                '\n', '')
            node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
Yann Leboulanger's avatar
Yann Leboulanger committed
                payload=[sasl_data])
        self._owner.send(str(node))
class NonBlockingNonSASL(PlugIn):
    """
    Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
    transport authentication
    """

    def __init__(self, user, password, resource, on_auth):
        """
        Caches username, password and resource for auth
        """
        PlugIn.__init__(self)
        self.user = user
        if password is None:
            self.password = ''
        else:
            self.password = password
        self.resource = resource
        self.on_auth = on_auth

    def plugin(self, owner):
        """
        Determine the best auth method (digest/0k/plain) and use it for auth.
        Returns used method name on success. Used internally
        """
        log.info('Querying server about possible auth methods')
        self.owner = owner

        owner.Dispatcher.SendAndWaitForResponse(
Yann Leboulanger's avatar
Yann Leboulanger committed
            Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
            func=self._on_username)

    def _on_username(self, resp):
        if not isResultNode(resp):
            log.info('No result node arrived! Aborting...')
        iq=Iq(typ='set', node=resp)
        query = iq.getTag('query')
        query.setTagData('username', self.user)
        query.setTagData('resource', self.resource)

        if query.getTag('digest'):
            log.info("Performing digest authentication")
            query.setTagData('digest',
Yann Leboulanger's avatar
Yann Leboulanger committed
                hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id']
                + self.password).hexdigest())
            if query.getTag('password'):
                query.delChild('password')
            self._method = 'digest'
        elif query.getTag('token'):
            token = query.getTagData('token')
            seq = query.getTagData('sequence')
            log.info("Performing zero-k authentication")

            def hasher(s):
                return hashlib.sha1(s).hexdigest()

            def hash_n_times(s, count):
                return count and hasher(hash_n_times(s, count-1)) or s

Yann Leboulanger's avatar
Yann Leboulanger committed
            hash_ = hash_n_times(hasher(hasher(self.password) + token),
                int(seq))
            query.setTagData('hash', hash_)
            self._method='0k'
        else:
            log.warn("Secure methods unsupported, performing plain text \
Yann Leboulanger's avatar
Yann Leboulanger committed
                authentication")
            query.setTagData('password', self.password)
            self._method = 'plain'
Yann Leboulanger's avatar
Yann Leboulanger committed
        resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,
            func=self._on_auth)

    def _on_auth(self, resp):
        if isResultNode(resp):
            log.info('Sucessfully authenticated with remote host.')
            self.owner.User = self.user
            self.owner.Resource = self.resource
Yann Leboulanger's avatar
Yann Leboulanger committed
            self.owner._registered_name = self.owner.User + '@' + \
                self.owner.Server+ '/' + self.owner.Resource
            return self.on_auth(self._method)
        return self.on_auth(None)
class NonBlockingBind(PlugIn):
    """
    Bind some JID to the current connection to allow router know of our
    location. Must be plugged after successful SASL auth
    """

    def __init__(self):
        PlugIn.__init__(self)
        self.bound = None
        self.supports_sm = False
zimio's avatar
zimio committed
        self.resuming = False

    def plugin(self, owner):
        ''' Start resource binding, if allowed at this time. Used internally. '''
        if self._owner.Dispatcher.Stream.features:
            try:
                self.FeaturesHandler(self._owner.Dispatcher,
Yann Leboulanger's avatar
Yann Leboulanger committed
                    self._owner.Dispatcher.Stream.features)
            except NodeProcessed:
                pass
        else:
            self._owner.RegisterHandler('features', self.FeaturesHandler,
Yann Leboulanger's avatar
Yann Leboulanger committed
                xmlns=NS_STREAMS)

    def FeaturesHandler(self, conn, feats):
        """
        Determine if server supports resource binding and set some internal
        attributes accordingly.
Yann Leboulanger's avatar
Yann Leboulanger committed

        It also checks if server supports stream management
Yann Leboulanger's avatar
Yann Leboulanger committed

        if feats.getTag('sm', namespace=NS_STREAM_MGMT):
            self.supports_sm = True # server supports stream management
zimio's avatar
zimio committed
            if self.resuming:
                self._owner._caller.sm.resume_request()
Yann Leboulanger's avatar
Yann Leboulanger committed

        if not feats.getTag('bind', namespace=NS_BIND):
            log.info('Server does not requested binding.')
            # we try to bind resource anyway
            #self.bound='failure'
            self.bound = []
            return
        if feats.getTag('session', namespace=NS_SESSION):
            self.session = 1
        else:
            self.session = -1
        self.bound = []

    def plugout(self):
        """
        Remove Bind handler from owner's dispatcher. Used internally
        """
        self._owner.UnregisterHandler('features', self.FeaturesHandler,
Yann Leboulanger's avatar
Yann Leboulanger committed
            xmlns=NS_STREAMS)

    def NonBlockingBind(self, resource=None, on_bound=None):
        """
        Perform binding. Use provided resource name or random (if not provided).
        """
zimio's avatar
zimio committed
        if self.resuming: # We don't bind if we resume the stream
            return
        self.on_bound = on_bound
        self._resource = resource
        if self._resource:
            self._resource = [Node('resource', payload=[self._resource])]
        else:
            self._resource = []

        self._owner.onreceive(None)
        self._owner.Dispatcher.SendAndWaitForResponse(
Yann Leboulanger's avatar
Yann Leboulanger committed
            Protocol('iq', typ='set', payload=[Node('bind',
            attrs={'xmlns': NS_BIND}, payload=self._resource)]),
            func=self._on_bound)

    def _on_bound(self, resp):
        if isResultNode(resp):
            if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'):
                self.bound.append(resp.getTag('bind').getTagData('jid'))
                log.info('Successfully bound %s.' % self.bound[-1])
                jid = JID(resp.getTag('bind').getTagData('jid'))
                self._owner.User = jid.getNode()
                self._owner.Resource = jid.getResource()
                # Only negociate stream management after bounded
zimio's avatar
zimio committed
                sm = self._owner._caller.sm
Yann Leboulanger's avatar
Yann Leboulanger committed
                if self.supports_sm:
                    # starts negociation
zimio's avatar
zimio committed
                    sm.set_owner(self._owner)
                    self._owner.Dispatcher.sm = sm
                if hasattr(self, 'session') and self.session == -1:
                    # Server don't want us to initialize a session
                    log.info('No session required.')
                    self.on_bound('ok')
                else:
                    self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
Yann Leboulanger's avatar
Yann Leboulanger committed
                        payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
                        func=self._on_session)
            log.info('Binding failed: %s.' % resp.getTag('error'))
            self.on_bound(None)

    def _on_session(self, resp):
        self._owner.onreceive(None)
        if isResultNode(resp):
            log.info('Successfully opened session.')
            self.session = 1
            self.on_bound('ok')
        else:
            log.error('Session open failed.')
            self.session = 0
            self.on_bound(None)