Logo Search packages:      
Sourcecode: gajim version File versions  Download package

auth_nb.py

##   auth_nb.py
##       based on auth.py
##
##   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 library with all Non-SASL and SASL authentication mechanisms.
Can be used both for client and transport authentication.
'''
import sys
from protocol import *
from auth import *
from client import PlugIn
import sha,base64,random,dispatcher_nb

def challenge_splitter(data):
      ''' Helper function that creates a dict from challenge string.
      Sample chalenge string:
            username="example.org",realm="somerealm",\
            nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
            nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8
      in the above example:
            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 == ',':
                        pass
                  else:
                        keyword = '%s%c' % (keyword, char)
            elif expecting == X_VALUE:
                  if char == '"':
                        if quotes_open:
                              end = True
                        else:
                              quotes_open = True
                  elif char == ",":
                        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

00085 class SASL(PlugIn):
      ''' Implements SASL authentication. '''
      def __init__(self,username,password, on_sasl):
            PlugIn.__init__(self)
            self.username=username
            self.password=password
            self.on_sasl = on_sasl
            self.realm = None
      def plugin(self,owner):
            if not self._owner.Dispatcher.Stream._document_attrs.has_key('version'): 
                  self.startsasl='not-supported'
            elif self._owner.Dispatcher.Stream.features:
                  try: 
                        self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
                  except NodeProcessed: 
                        pass
            else: self.startsasl=None

00103       def auth(self):
            ''' Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
                  either "success" or "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, self._owner.Dispatcher.Stream.features)
                  except NodeProcessed: 
                        pass
            else: self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)

00116       def plugout(self):
            ''' Remove SASL handlers from owner's dispatcher. Used internally. '''
            self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
            self._owner.UnregisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
            self._owner.UnregisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
            self._owner.UnregisterHandler('success', self.SASLHandler, xmlns=NS_SASL)

00123       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'
                  self.DEBUG('SASL not supported by server', 'error')
                  return
            mecs=[]
            for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags('mechanism'):
                  mecs.append(mec.getData())
            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)
            if "DIGEST-MD5" in mecs:
                  node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
            elif "PLAIN" in mecs:
                  sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server, 
                                                                                                      self.username, self.password)
                  node=Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'}, 
                                                payload=[base64.encodestring(sasl_data).replace('\n','')])
            else:
                  self.startsasl='failure'
                  self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.', 'error')
                  return
            self.startsasl='in-process'
            self._owner.send(node.__str__())
            raise NodeProcessed

00150       def SASLHandler(self, conn, challenge):
            ''' Perform next SASL auth step. Used internally. '''
            if challenge.getNamespace() <> NS_SASL: 
                  return
            if challenge.getName() == 'failure':
                  self.startsasl = 'failure'
                  try: 
                        reason = challenge.getChildren()[0]
                  except: 
                        reason = challenge
                  self.DEBUG('Failed SASL authentification: %s' % reason, 'error')
                  if self.on_sasl :
                        self.on_sasl ()
                  raise NodeProcessed
            elif challenge.getName() == 'success':
                  self.startsasl='success'
                  self.DEBUG('Successfully authenticated with remote server.', 'ok')
                  handlers=self._owner.Dispatcher.dumpHandlers()
                  self._owner.Dispatcher.PlugOut()
                  dispatcher_nb.Dispatcher().PlugIn(self._owner)
                  self._owner.Dispatcher.restoreHandlers(handlers)
                  self._owner.User = self.username
                  if self.on_sasl :
                        self.on_sasl ()
                  raise NodeProcessed
########################################3333
            incoming_data = challenge.getData()
            data=base64.decodestring(incoming_data)
            self.DEBUG('Got challenge:'+data,'ok')
            chal = challenge_splitter(data)
            if not self.realm and chal.has_key('realm'):
                  self.realm = chal['realm']
            if chal.has_key('qop') and ((type(chal['qop']) == str and \
            chal['qop'] =='auth') or (type(chal['qop']) == list and 'auth' in \
            chal['qop'])):
                  resp={}
                  resp['username'] = self.username
                  if self.realm:
                        resp['realm'] = self.realm
                  else:
                        resp['realm'] = self._owner.Server
                  resp['nonce']=chal['nonce']
                  cnonce=''
                  for i in range(7):
                        cnonce += hex(int(random.random() * 65536 * 4096))[2:]
                  resp['cnonce'] = cnonce
                  resp['nc'] = ('00000001')
                  resp['qop'] = 'auth'
                  resp['digest-uri'] = 'xmpp/'+self._owner.Server
                  A1=C([H(C([resp['username'], resp['realm'], self.password])), 
                                    resp['nonce'], resp['cnonce']])
                  A2=C(['AUTHENTICATE',resp['digest-uri']])
                  response= HH(C([HH(A1), resp['nonce'], resp['nc'], resp['cnonce'],
                                    resp['qop'], HH(A2)]))
                  resp['response'] = response
                  resp['charset'] = 'utf-8'
                  sasl_data=''
                  for key in ['charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', 'digest-uri', 'response', 'qop']:
                        if key in ['nc','qop','response','charset']: 
                              sasl_data += "%s=%s," % (key,resp[key])
                        else: 
                              sasl_data += '%s="%s",' % (key,resp[key])
########################################3333
                  node=Node('response', attrs={'xmlns':NS_SASL}, 
                        payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')])
                  self._owner.send(node.__str__())
            elif chal.has_key('rspauth'): 
                  self._owner.send(Node('response', attrs={'xmlns':NS_SASL}).__str__())
            else: 
                  self.startsasl='failure'
                  self.DEBUG('Failed SASL authentification: unknown challenge', 'error')
            if self.on_sasl :
                        self.on_sasl ()
            raise NodeProcessed
      
00225 class NonBlockingNonSASL(PlugIn):
      ''' Implements old Non-SASL (JEP-0078) authentication used 
      in jabberd1.4 and transport authentication.
      '''
00229       def __init__(self, user, password, resource, on_auth):
            ''' Caches username, password and resource for auth. '''
            PlugIn.__init__(self)
            self.DBG_LINE ='gen_auth'
            self.user = user
            self.password= password
            self.resource = resource
            self.on_auth = on_auth

00238       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. '''
            if not self.resource: 
                  return self.authComponent(owner)
            self.DEBUG('Querying server about possible auth methods', 'start')
            self.owner = owner 
            
            resp = owner.Dispatcher.SendAndWaitForResponse(
                  Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), func=self._on_username
            )
            
      def _on_username(self, resp):
            if not isResultNode(resp):
                  self.DEBUG('No result node arrived! Aborting...','error')
                  return self.on_auth(None)
            iq=Iq(typ='set',node=resp)
            query=iq.getTag('query')
            query.setTagData('username',self.user)
            query.setTagData('resource',self.resource)

            if query.getTag('digest'):
                  self.DEBUG("Performing digest authentication",'ok')
                  query.setTagData('digest', 
                        sha.new(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')
                  self.DEBUG("Performing zero-k authentication",'ok')
                  hash = sha.new(sha.new(self.password).hexdigest()+token).hexdigest()
                  for foo in xrange(int(seq)): 
                        hash = sha.new(hash).hexdigest()
                  query.setTagData('hash',hash)
                  self._method='0k'
            else:
                  self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
                  query.setTagData('password',self.password)
                  self._method='plain'
            resp=self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth)
            
      def _on_auth(self, resp):
            if isResultNode(resp):
                  self.DEBUG('Sucessfully authenticated with remove host.','ok')
                  self.owner.User=self.user
                  self.owner.Resource=self.resource
                  self.owner._registered_name=self.owner.User+'@'+self.owner.Server+'/'+self.owner.Resource
                  return self.on_auth(self._method)
            self.DEBUG('Authentication failed!','error')
            return self.on_auth(None)

00291       def authComponent(self,owner):
            ''' Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. '''
            self.handshake=0
            owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',
                  payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
            owner.RegisterHandler('handshake', self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT)
            self._owner.onreceive(self._on_auth_component)
            
00299       def _on_auth_component(self, data):
            ''' called when we receive some response, after we send the handshake '''
            if data:
                  self.Dispatcher.ProcessNonBlocking(data)
            if not self.handshake:
                  self.DEBUG('waiting on handshake', 'notify')
                  return
            self._owner.onreceive(None)
            owner._registered_name=self.user
            if self.handshake+1: 
                  return self.on_auth('ok')
            self.on_auth(None)

00312       def handshakeHandler(self,disp,stanza):
            ''' Handler for registering in dispatcher for accepting transport authentication. '''
            if stanza.getName() == 'handshake': 
                  self.handshake=1
            else: 
                  self.handshake=-1
      
00319 class NonBlockingBind(Bind):
      ''' Bind some JID to the current connection to allow router know of our location.'''
00321       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, self._owner.Dispatcher.Stream.features)
                  except NodeProcessed: 
                        pass
            else: self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)

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

00334       def NonBlockingBind(self, resource=None, on_bound=None):
            ''' Perform binding. Use provided resource name or random (if not provided). '''
            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)
            resp=self._owner.Dispatcher.SendAndWaitForResponse(
                  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):
                  self.bound.append(resp.getTag('bind').getTagData('jid'))
                  self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
                  jid=JID(resp.getTag('bind').getTagData('jid'))
                  self._owner.User=jid.getNode()
                  self._owner.Resource=jid.getResource()
                  self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', 
                        payload=[Node('session', attrs={'xmlns':NS_SESSION})]), func=self._on_session)
            elif resp:
                  self.DEBUG('Binding failed: %s.' % resp.getTag('error'),'error')
                  self.on_bound(None)
            else:
                  self.DEBUG('Binding failed: timeout expired.', 'error')
                  self.on_bound(None)
                  
      def _on_session(self, resp):
            self._owner.onreceive(None)
            if isResultNode(resp):
                  self.DEBUG('Successfully opened session.', 'ok')
                  self.session = 1
                  self.on_bound('ok')
            else:
                  self.DEBUG('Session open failed.', 'error')
                  self.session = 0
                  self.on_bound(None)
            self._owner.onreceive(None)
            if isResultNode(resp):
                  self.DEBUG('Successfully opened session.', 'ok')
                  self.session = 1
                  self.on_bound('ok')
            else:
                  self.DEBUG('Session open failed.', 'error')
                  self.session = 0
                  self.on_bound(None)

00384 class NBComponentBind(ComponentBind):
      ''' ComponentBind some JID to the current connection to allow 
      router know of our location.
      '''
00388       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, self._owner.Dispatcher.Stream.features)
                  except NodeProcessed: 
                        pass
            else:
                  self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
                  self.needsUnregister = 1

00399       def plugout(self):
            ''' Remove ComponentBind handler from owner's dispatcher. Used internally. '''
            if self.needsUnregister:
                  self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
      
00404       def Bind(self, domain = None, on_bind = None):
            ''' Perform binding. Use provided domain name (if not provided). '''
            self._owner.onreceive(self._on_bound)
            self.on_bind = on_bind
      
      def _on_bound(self, resp):
            if data:
                  self.Dispatcher.ProcessNonBlocking(data)
            if self.bound is None:
                  return
            self._owner.onreceive(None)
            self._owner.SendAndWaitForResponse(
                  Protocol('bind', attrs={'name':domain}, xmlns=NS_COMPONENT_1), 
                  func=self._on_bind_reponse)
      
      def _on_bind_reponse(self, res):
            if resp and resp.getAttr('error'):
                  self.DEBUG('Binding failed: %s.' % resp.getAttr('error'), 'error')
            elif resp:
                  self.DEBUG('Successfully bound.', 'ok')
                  if self.on_bind:
                        self.on_bind('ok')
            else:
                  self.DEBUG('Binding failed: timeout expired.', 'error')
            if self.on_bind:
                  self.on_bind(None)

Generated by  Doxygen 1.6.0   Back to index