Coding standards and documentation improvements in auth_nb.py

This commit is contained in:
Stephan Erb 2008-12-26 15:39:18 +00:00
parent 7427399a2a
commit de73b76771
1 changed files with 162 additions and 102 deletions

View File

@ -14,10 +14,13 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
''' '''
Provides library with all Non-SASL and SASL authentication mechanisms. Provides plugs for SASL and NON-SASL authentication mechanisms.
Can be used both for client and transport authentication. Can be used both for client and transport authentication.
See client_nb.py
''' '''
from protocol import * from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
from client import PlugIn from client import PlugIn
import sha import sha
import base64 import base64
@ -41,14 +44,20 @@ except ImportError:
GSS_STATE_STEP = 0 GSS_STATE_STEP = 0
GSS_STATE_WRAP = 1 GSS_STATE_WRAP = 1
SASL_FAILURE = 'failure'
SASL_SUCCESS = 'success'
SASL_UNSUPPORTED = 'not-supported'
SASL_IN_PROCESS = 'in-process'
def challenge_splitter(data): def challenge_splitter(data):
''' Helper function that creates a dict from challenge string. ''' Helper function that creates a dict from challenge string.
Sample chalenge string:
Sample challenge string:
username="example.org",realm="somerealm",\ username="example.org",realm="somerealm",\
nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\ nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8
in the above example:
Expected result for challan:
dict['qop'] = ('auth','auth-int','auth-conf') dict['qop'] = ('auth','auth-int','auth-conf')
dict['realm'] = 'somerealm' dict['realm'] = 'somerealm'
''' '''
@ -102,43 +111,64 @@ def challenge_splitter(data):
quotes_open = False quotes_open = False
return dict_ return dict_
class SASL(PlugIn): class SASL(PlugIn):
''' Implements SASL authentication. ''' '''
def __init__(self,username,password, on_sasl): 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) PlugIn.__init__(self)
self.username=username self.username = username
self.password=password self.password = password
self.on_sasl = on_sasl self.on_sasl = on_sasl
self.realm = None self.realm = None
def plugin(self,owner):
def plugin(self, owner):
if 'version' not in self._owner.Dispatcher.Stream._document_attrs: if 'version' not in self._owner.Dispatcher.Stream._document_attrs:
self.startsasl='not-supported' self.startsasl = SASL_UNSUPPORTED
elif self._owner.Dispatcher.Stream.features: elif self._owner.Dispatcher.Stream.features:
try: try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features) self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
except NodeProcessed: except NodeProcessed:
pass pass
else: self.startsasl=None else:
self.startsasl = None
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)
def auth(self): 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 Start authentication. Result can be obtained via "SASL.startsasl"
two Dispatcher.Process() calls. ''' 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: if self.startsasl:
pass pass
elif self._owner.Dispatcher.Stream.features: elif self._owner.Dispatcher.Stream.features:
try: try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features) self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
except NodeProcessed: except NodeProcessed:
pass pass
else: self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS) else:
self._owner.RegisterHandler('features',
def plugout(self): self.FeaturesHandler, xmlns=NS_STREAMS)
''' 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)
def FeaturesHandler(self, conn, feats): def FeaturesHandler(self, conn, feats):
''' Used to determine if server supports SASL auth. Used internally. ''' ''' Used to determine if server supports SASL auth. Used internally. '''
@ -146,9 +176,11 @@ class SASL(PlugIn):
self.startsasl='not-supported' self.startsasl='not-supported'
log.error('SASL not supported by server') log.error('SASL not supported by server')
return return
self.mecs=[] self.mecs = []
for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags('mechanism'): for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags(
'mechanism'):
self.mecs.append(mec.getData()) self.mecs.append(mec.getData())
self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL) self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
@ -167,29 +199,30 @@ class SASL(PlugIn):
self.gss_step = GSS_STATE_STEP self.gss_step = GSS_STATE_STEP
elif 'DIGEST-MD5' in self.mecs: elif 'DIGEST-MD5' in self.mecs:
self.mecs.remove('DIGEST-MD5') self.mecs.remove('DIGEST-MD5')
node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
self.mechanism = 'DIGEST-MD5' self.mechanism = 'DIGEST-MD5'
elif 'PLAIN' in self.mecs: elif 'PLAIN' in self.mecs:
self.mecs.remove('PLAIN') self.mecs.remove('PLAIN')
sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server, sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server,
self.username, self.password) self.username, self.password)
node=Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'}, node = Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},
payload=[base64.encodestring(sasl_data).replace('\n','')]) payload=[base64.encodestring(sasl_data).replace('\n','')])
self.mechanism = 'PLAIN' self.mechanism = 'PLAIN'
else: else:
self.startsasl='failure' self.startsasl = SASL_FAILURE
log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.') log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.')
return return
self.startsasl='in-process' self.startsasl = SASL_IN_PROCESS
self._owner.send(node.__str__()) self._owner.send(str(node))
raise NodeProcessed raise NodeProcessed
def SASLHandler(self, conn, challenge): def SASLHandler(self, conn, challenge):
''' Perform next SASL auth step. Used internally. ''' ''' Perform next SASL auth step. Used internally. '''
if challenge.getNamespace() != NS_SASL: if challenge.getNamespace() != NS_SASL:
return return
### Handle Auth result
if challenge.getName() == 'failure': if challenge.getName() == 'failure':
self.startsasl = 'failure' self.startsasl = SASL_FAILURE
try: try:
reason = challenge.getChildren()[0] reason = challenge.getChildren()[0]
except Exception: except Exception:
@ -199,30 +232,34 @@ class SASL(PlugIn):
# There are other mechanisms to test # There are other mechanisms to test
self.MechanismHandler() self.MechanismHandler()
raise NodeProcessed raise NodeProcessed
if self.on_sasl : if self.on_sasl:
self.on_sasl () self.on_sasl()
raise NodeProcessed raise NodeProcessed
elif challenge.getName() == 'success': elif challenge.getName() == 'success':
self.startsasl='success' self.startsasl = SASL_SUCCESS
log.info('Successfully authenticated with remote server.') log.info('Successfully authenticated with remote server.')
handlers=self._owner.Dispatcher.dumpHandlers() handlers = self._owner.Dispatcher.dumpHandlers()
# Bosh specific dispatcher replugging
# save old features. They will be used in case we won't get response on # 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) # stream restart after SASL auth (happens with XMPP over BOSH with
# Openfire)
old_features = self._owner.Dispatcher.Stream.features old_features = self._owner.Dispatcher.Stream.features
self._owner.Dispatcher.PlugOut() self._owner.Dispatcher.PlugOut()
dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True, dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True,
old_features=old_features) old_features=old_features)
self._owner.Dispatcher.restoreHandlers(handlers) self._owner.Dispatcher.restoreHandlers(handlers)
self._owner.User = self.username self._owner.User = self.username
if self.on_sasl :
if self.on_sasl:
self.on_sasl() self.on_sasl()
raise NodeProcessed raise NodeProcessed
########################################3333
### Perform auth step
incoming_data = challenge.getData() incoming_data = challenge.getData()
data=base64.decodestring(incoming_data) data=base64.decodestring(incoming_data)
log.info('Got challenge:' + data) log.info('Got challenge:' + data)
if self.mechanism == 'GSSAPI': if self.mechanism == 'GSSAPI':
if self.gss_step == GSS_STATE_STEP: if self.gss_step == GSS_STATE_STEP:
rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
@ -237,55 +274,60 @@ class SASL(PlugIn):
if not response: if not response:
response = '' response = ''
self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, self._owner.send(Node('response', attrs={'xmlns':NS_SASL},
payload=response).__str__()) payload=response).__str__())
raise NodeProcessed raise NodeProcessed
# magic foo...
chal = challenge_splitter(data) chal = challenge_splitter(data)
if not self.realm and 'realm' in chal: if not self.realm and 'realm' in chal:
self.realm = chal['realm'] self.realm = chal['realm']
if 'qop' in chal and ((isinstance(chal['qop'], str) and \ if 'qop' in chal and ((isinstance(chal['qop'], str) and \
chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \
chal['qop'])): chal['qop'])):
resp={} resp = {}
resp['username'] = self.username resp['username'] = self.username
if self.realm: if self.realm:
resp['realm'] = self.realm resp['realm'] = self.realm
else: else:
resp['realm'] = self._owner.Server resp['realm'] = self._owner.Server
resp['nonce']=chal['nonce'] resp['nonce'] = chal['nonce']
resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in
itertools.repeat(random.randint, 7)) itertools.repeat(random.randint, 7))
resp['nc'] = ('00000001') resp['nc'] = ('00000001')
resp['qop'] = 'auth' resp['qop'] = 'auth'
resp['digest-uri'] = 'xmpp/'+self._owner.Server resp['digest-uri'] = 'xmpp/' + self._owner.Server
A1=C([H(C([resp['username'], resp['realm'], self.password])), A1=C([H(C([resp['username'], resp['realm'], self.password])),
resp['nonce'], resp['cnonce']]) resp['nonce'], resp['cnonce']])
A2=C(['AUTHENTICATE',resp['digest-uri']]) A2=C(['AUTHENTICATE',resp['digest-uri']])
response= HH(C([HH(A1), resp['nonce'], resp['nc'], resp['cnonce'], response= HH(C([HH(A1), resp['nonce'], resp['nc'], resp['cnonce'],
resp['qop'], HH(A2)])) resp['qop'], HH(A2)]))
resp['response'] = response resp['response'] = response
resp['charset'] = 'utf-8' resp['charset'] = 'utf-8'
sasl_data='' sasl_data = ''
for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', 'digest-uri', 'response', 'qop'): for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
if key in ['nc','qop','response','charset']: 'digest-uri', 'response', 'qop'):
sasl_data += "%s=%s," % (key,resp[key]) if key in ('nc','qop','response','charset'):
sasl_data += "%s=%s," % (key, resp[key])
else: else:
sasl_data += '%s="%s",' % (key,resp[key]) sasl_data += '%s="%s",' % (key, resp[key])
########################################3333 node = Node('response', attrs={'xmlns':NS_SASL},
node=Node('response', attrs={'xmlns':NS_SASL}, payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').
payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')]) replace('\n','')])
self._owner.send(node.__str__()) self._owner.send(str(node))
elif 'rspauth' in chal: elif 'rspauth' in chal:
self._owner.send(Node('response', attrs={'xmlns':NS_SASL}).__str__()) self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
else: else:
self.startsasl='failure' self.startsasl = SASL_FAILURE
log.error('Failed SASL authentification: unknown challenge') log.error('Failed SASL authentification: unknown challenge')
if self.on_sasl : if self.on_sasl:
self.on_sasl () self.on_sasl()
raise NodeProcessed raise NodeProcessed
class NonBlockingNonSASL(PlugIn): class NonBlockingNonSASL(PlugIn):
''' Implements old Non-SASL (JEP-0078) authentication used '''
in jabberd1.4 and transport authentication. Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
transport authentication.
''' '''
def __init__(self, user, password, resource, on_auth): def __init__(self, user, password, resource, on_auth):
''' Caches username, password and resource for auth. ''' ''' Caches username, password and resource for auth. '''
@ -296,34 +338,38 @@ class NonBlockingNonSASL(PlugIn):
self.on_auth = on_auth self.on_auth = on_auth
def plugin(self, owner): 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. ''' 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') log.info('Querying server about possible auth methods')
self.owner = owner self.owner = owner
owner.Dispatcher.SendAndWaitForResponse( owner.Dispatcher.SendAndWaitForResponse(
Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), func=self._on_username Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
) func=self._on_username)
def _on_username(self, resp): def _on_username(self, resp):
if not isResultNode(resp): if not isResultNode(resp):
log.error('No result node arrived! Aborting...') log.error('No result node arrived! Aborting...')
return self.on_auth(None) return self.on_auth(None)
iq=Iq(typ='set',node=resp) iq=Iq(typ='set',node=resp)
query=iq.getTag('query') query = iq.getTag('query')
query.setTagData('username',self.user) query.setTagData('username',self.user)
query.setTagData('resource',self.resource) query.setTagData('resource',self.resource)
if query.getTag('digest'): if query.getTag('digest'):
log.info("Performing digest authentication") log.info("Performing digest authentication")
query.setTagData('digest', query.setTagData('digest',
sha.new(self.owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()) sha.new(self.owner.Dispatcher.Stream._document_attrs['id'] +
self.password).hexdigest())
if query.getTag('password'): if query.getTag('password'):
query.delChild('password') query.delChild('password')
self._method='digest' self._method = 'digest'
elif query.getTag('token'): elif query.getTag('token'):
token=query.getTagData('token') token = query.getTagData('token')
seq=query.getTagData('sequence') seq = query.getTagData('sequence')
log.info("Performing zero-k authentication") log.info("Performing zero-k authentication")
def hasher(s): def hasher(s):
@ -336,58 +382,71 @@ class NonBlockingNonSASL(PlugIn):
query.setTagData('hash', hash_) query.setTagData('hash', hash_)
self._method='0k' self._method='0k'
else: else:
log.warn("Sequre methods unsupported, performing plain text authentication") log.warn("Sequre methods unsupported, performing plain text \
query.setTagData('password',self.password) authentication")
self._method='plain' query.setTagData('password', self.password)
resp=self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth) self._method = 'plain'
resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,func=self._on_auth)
def _on_auth(self, resp): def _on_auth(self, resp):
if isResultNode(resp): if isResultNode(resp):
log.info('Sucessfully authenticated with remove host.') log.info('Sucessfully authenticated with remote host.')
self.owner.User=self.user self.owner.User = self.user
self.owner.Resource=self.resource self.owner.Resource = self.resource
self.owner._registered_name=self.owner.User+'@'+self.owner.Server+'/'+self.owner.Resource self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\
'/'+self.owner.Resource
return self.on_auth(self._method) return self.on_auth(self._method)
log.error('Authentication failed!') log.error('Authentication failed!')
return self.on_auth(None) return self.on_auth(None)
class NonBlockingBind(PlugIn): class NonBlockingBind(PlugIn):
''' Bind some JID to the current connection to allow router know of our location.''' '''
Bind some JID to the current connection to allow router know of our
location. Must be plugged after successful SASL auth.
'''
def __init__(self): def __init__(self):
PlugIn.__init__(self) PlugIn.__init__(self)
self.bound=None self.bound = None
def plugin(self, owner): def plugin(self, owner):
''' Start resource binding, if allowed at this time. Used internally. ''' ''' Start resource binding, if allowed at this time. Used internally. '''
if self._owner.Dispatcher.Stream.features: if self._owner.Dispatcher.Stream.features:
try: try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features) self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
except NodeProcessed: except NodeProcessed:
pass pass
else: else:
self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS) self._owner.RegisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
def FeaturesHandler(self,conn,feats): def FeaturesHandler(self, conn, feats):
''' Determine if server supports resource binding and set some internal attributes accordingly. ''' '''
if not feats.getTag('bind',namespace=NS_BIND): Determine if server supports resource binding and set some internal
attributes accordingly.
'''
if not feats.getTag('bind', namespace=NS_BIND):
log.error('Server does not requested binding.') log.error('Server does not requested binding.')
# we try to bind resource anyway # we try to bind resource anyway
#self.bound='failure' #self.bound='failure'
self.bound=[] self.bound = []
return return
if feats.getTag('session',namespace=NS_SESSION): if feats.getTag('session', namespace=NS_SESSION):
self.session=1 self.session = 1
else: else:
self.session=-1 self.session = -1
self.bound=[] self.bound = []
def plugout(self): def plugout(self):
''' Remove Bind handler from owner's dispatcher. Used internally. ''' ''' Remove Bind handler from owner's dispatcher. Used internally. '''
self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS) self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
def NonBlockingBind(self, resource=None, on_bound=None): def NonBlockingBind(self, resource=None, on_bound=None):
''' Perform binding. Use provided resource name or random (if not provided). ''' ''' Perform binding.
Use provided resource name or random (if not provided).
'''
self.on_bound = on_bound self.on_bound = on_bound
self._resource = resource self._resource = resource
if self._resource: if self._resource:
@ -397,18 +456,19 @@ class NonBlockingBind(PlugIn):
self._owner.onreceive(None) self._owner.onreceive(None)
self._owner.Dispatcher.SendAndWaitForResponse( self._owner.Dispatcher.SendAndWaitForResponse(
Protocol('iq',typ='set', Protocol('iq',typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND},
payload=[Node('bind', attrs={'xmlns':NS_BIND}, payload=self._resource)]), payload=self._resource)]), func=self._on_bound)
func=self._on_bound)
def _on_bound(self, resp): def _on_bound(self, resp):
if isResultNode(resp): if isResultNode(resp):
self.bound.append(resp.getTag('bind').getTagData('jid')) self.bound.append(resp.getTag('bind').getTagData('jid'))
log.info('Successfully bound %s.'%self.bound[-1]) log.info('Successfully bound %s.' % self.bound[-1])
jid=JID(resp.getTag('bind').getTagData('jid')) jid = JID(resp.getTag('bind').getTagData('jid'))
self._owner.User=jid.getNode() self._owner.User = jid.getNode()
self._owner.Resource=jid.getResource() self._owner.Resource = jid.getResource()
self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
payload=[Node('session', attrs={'xmlns':NS_SESSION})]), func=self._on_session) payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
func=self._on_session)
elif resp: elif resp:
log.error('Binding failed: %s.' % resp.getTag('error')) log.error('Binding failed: %s.' % resp.getTag('error'))
self.on_bound(None) self.on_bound(None)