Package nbxmpp :: Module auth_nb
[hide private]
[frames] | no frames]

Source Code for Module nbxmpp.auth_nb

  1  ##   auth_nb.py 
  2  ##       based on auth.py, changes backported up to revision 1.41 
  3  ## 
  4  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 
  5  ##       modified by Dimitur Kirov <dkirov@gmail.com> 
  6  ## 
  7  ##   This program is free software; you can redistribute it and/or modify 
  8  ##   it under the terms of the GNU General Public License as published by 
  9  ##   the Free Software Foundation; either version 2, or (at your option) 
 10  ##   any later version. 
 11  ## 
 12  ##   This program is distributed in the hope that it will be useful, 
 13  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  ##   GNU General Public License for more details. 
 16   
 17  """ 
 18  Provides plugs for SASL and NON-SASL authentication mechanisms. 
 19  Can be used both for client and transport authentication 
 20   
 21  See client_nb.py 
 22  """ 
 23   
 24  from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH 
 25  from protocol import NS_STREAM_MGMT 
 26  from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID 
 27  from plugin import PlugIn 
 28  from smacks import Smacks 
 29  import base64 
 30  import random 
 31  import itertools 
 32  import dispatcher_nb 
 33  import hashlib 
 34  import hmac 
 35  import hashlib 
 36   
 37  import logging 
 38  log = logging.getLogger('nbxmpp.auth_nb') 
39 40 -def HH(some): return hashlib.md5(some).hexdigest()
41 -def H(some): return hashlib.md5(some).digest()
42 -def C(some): return ':'.join(some)
43 44 try: 45 kerberos = __import__('kerberos') 46 have_kerberos = True 47 except ImportError: 48 have_kerberos = False 49 50 GSS_STATE_STEP = 0 51 GSS_STATE_WRAP = 1 52 SASL_FAILURE_IN_PROGRESS = 'failure-in-process' 53 SASL_FAILURE = 'failure' 54 SASL_SUCCESS = 'success' 55 SASL_UNSUPPORTED = 'not-supported' 56 SASL_IN_PROCESS = 'in-process'
57 58 -def challenge_splitter(data):
59 """ 60 Helper function that creates a dict from challenge string 61 62 Sample challenge string: 63 - username="example.org",realm="somerealm", 64 nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk", 65 nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 66 67 Expected result for challan: 68 - dict['qop'] = ('auth','auth-int','auth-conf') 69 - dict['realm'] = 'somerealm' 70 """ 71 X_KEYWORD, X_VALUE, X_END = 0, 1, 2 72 quotes_open = False 73 keyword, value = '', '' 74 dict_ = {} 75 arr = None 76 77 expecting = X_KEYWORD 78 for iter_ in range(len(data) + 1): 79 end = False 80 if iter_ == len(data): 81 expecting = X_END 82 end = True 83 else: 84 char = data[iter_] 85 if expecting == X_KEYWORD: 86 if char == '=': 87 expecting = X_VALUE 88 elif char in (',', ' ', '\t'): 89 pass 90 else: 91 keyword = '%s%c' % (keyword, char) 92 elif expecting == X_VALUE: 93 if char == '"': 94 if quotes_open: 95 end = True 96 else: 97 quotes_open = True 98 elif char in (',', ' ', '\t'): 99 if quotes_open: 100 if not arr: 101 arr = [value] 102 else: 103 arr.append(value) 104 value = "" 105 else: 106 end = True 107 else: 108 value = '%s%c' % (value, char) 109 if end: 110 if arr: 111 arr.append(value) 112 dict_[keyword] = arr 113 arr = None 114 else: 115 dict_[keyword] = value 116 value, keyword = '', '' 117 expecting = X_KEYWORD 118 quotes_open = False 119 return dict_
120
121 -def scram_parse(chatter):
122 return dict(s.split('=', 1) for s in chatter.split(','))
123
124 -class SASL(PlugIn):
125 """ 126 Implements SASL authentication. Can be plugged into NonBlockingClient 127 to start authentication 128 """ 129
130 - def __init__(self, username, password, on_sasl):
131 """ 132 :param username: XMPP username 133 :param password: XMPP password 134 :param on_sasl: Callback, will be called after each SASL auth-step. 135 """ 136 PlugIn.__init__(self) 137 self.username = username 138 self.password = password 139 self.on_sasl = on_sasl 140 self.realm = None
141
142 - def plugin(self, owner):
143 if 'version' not in self._owner.Dispatcher.Stream._document_attrs: 144 self.startsasl = SASL_UNSUPPORTED 145 elif self._owner.Dispatcher.Stream.features: 146 try: 147 self.FeaturesHandler(self._owner.Dispatcher, 148 self._owner.Dispatcher.Stream.features) 149 except NodeProcessed: 150 pass 151 else: 152 self.startsasl = None
153
154 - def plugout(self):
155 """ 156 Remove SASL handlers from owner's dispatcher. Used internally 157 """ 158 if 'features' in self._owner.__dict__: 159 self._owner.UnregisterHandler('features', self.FeaturesHandler, 160 xmlns=NS_STREAMS) 161 if 'challenge' in self._owner.__dict__: 162 self._owner.UnregisterHandler('challenge', self.SASLHandler, 163 xmlns=NS_SASL) 164 if 'failure' in self._owner.__dict__: 165 self._owner.UnregisterHandler('failure', self.SASLHandler, 166 xmlns=NS_SASL) 167 if 'success' in self._owner.__dict__: 168 self._owner.UnregisterHandler('success', self.SASLHandler, 169 xmlns=NS_SASL)
170
171 - def auth(self):
172 """ 173 Start authentication. Result can be obtained via "SASL.startsasl" 174 attribute and will be either SASL_SUCCESS or SASL_FAILURE 175 176 Note that successfull auth will take at least two Dispatcher.Process() 177 calls. 178 """ 179 if self.startsasl: 180 pass 181 elif self._owner.Dispatcher.Stream.features: 182 try: 183 self.FeaturesHandler(self._owner.Dispatcher, 184 self._owner.Dispatcher.Stream.features) 185 except NodeProcessed: 186 pass 187 else: 188 self._owner.RegisterHandler('features', 189 self.FeaturesHandler, xmlns=NS_STREAMS)
190
191 - def FeaturesHandler(self, conn, feats):
192 """ 193 Used to determine if server supports SASL auth. Used internally 194 """ 195 if not feats.getTag('mechanisms', namespace=NS_SASL): 196 self.startsasl='not-supported' 197 log.info('SASL not supported by server') 198 return 199 self.mecs = [] 200 for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( 201 'mechanism'): 202 self.mecs.append(mec.getData()) 203 204 self._owner.RegisterHandler('challenge', self.SASLHandler, 205 xmlns=NS_SASL) 206 self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) 207 self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) 208 self.MechanismHandler()
209
210 - def MechanismHandler(self):
211 if 'ANONYMOUS' in self.mecs and self.username is None: 212 self.mecs.remove('ANONYMOUS') 213 node = Node('auth', attrs={'xmlns': NS_SASL, 214 'mechanism': 'ANONYMOUS'}) 215 self.mechanism = 'ANONYMOUS' 216 self.startsasl = SASL_IN_PROCESS 217 self._owner.send(str(node)) 218 raise NodeProcessed 219 if "EXTERNAL" in self.mecs: 220 self.mecs.remove('EXTERNAL') 221 sasl_data = u'%s@%s' % (self.username, self._owner.Server) 222 sasl_data = sasl_data.encode('utf-8').encode('base64').replace( 223 '\n', '') 224 node = Node('auth', attrs={'xmlns': NS_SASL, 225 'mechanism': 'EXTERNAL'}, payload=[sasl_data]) 226 self.mechanism = 'EXTERNAL' 227 self.startsasl = SASL_IN_PROCESS 228 self._owner.send(str(node)) 229 raise NodeProcessed 230 if 'GSSAPI' in self.mecs and have_kerberos: 231 self.mecs.remove('GSSAPI') 232 try: 233 self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \ 234 self._owner.xmpp_hostname)[1] 235 kerberos.authGSSClientStep(self.gss_vc, '') 236 response = kerberos.authGSSClientResponse(self.gss_vc) 237 node=Node('auth', attrs={'xmlns': NS_SASL, 238 'mechanism': 'GSSAPI'}, payload=(response or '')) 239 self.mechanism = 'GSSAPI' 240 self.gss_step = GSS_STATE_STEP 241 self.startsasl = SASL_IN_PROCESS 242 self._owner.send(str(node)) 243 raise NodeProcessed 244 except kerberos.GSSError, e: 245 log.info('GSSAPI authentication failed: %s' % str(e)) 246 if 'SCRAM-SHA-1' in self.mecs: 247 self.mecs.remove('SCRAM-SHA-1') 248 self.mechanism = 'SCRAM-SHA-1' 249 self._owner._caller.get_password(self.set_password, self.mechanism) 250 self.scram_step = 0 251 self.startsasl = SASL_IN_PROCESS 252 raise NodeProcessed 253 if 'DIGEST-MD5' in self.mecs: 254 self.mecs.remove('DIGEST-MD5') 255 node = Node('auth', attrs={'xmlns': NS_SASL, 256 'mechanism': 'DIGEST-MD5'}) 257 self.mechanism = 'DIGEST-MD5' 258 self.startsasl = SASL_IN_PROCESS 259 self._owner.send(str(node)) 260 raise NodeProcessed 261 if 'PLAIN' in self.mecs: 262 self.mecs.remove('PLAIN') 263 self.mechanism = 'PLAIN' 264 self._owner._caller.get_password(self.set_password, self.mechanism) 265 self.startsasl = SASL_IN_PROCESS 266 raise NodeProcessed 267 if 'X-MESSENGER-OAUTH2' in self.mecs: 268 self.mecs.remove('X-MESSENGER-OAUTH2') 269 self.mechanism = 'X-MESSENGER-OAUTH2' 270 self._owner._caller.get_password(self.set_password, self.mechanism) 271 self.startsasl = SASL_IN_PROCESS 272 raise NodeProcessed 273 self.startsasl = SASL_FAILURE 274 log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and ' 275 'PLAIN mecanisms.') 276 if self.on_sasl: 277 self.on_sasl() 278 return
279
280 - def SASLHandler(self, conn, challenge):
281 """ 282 Perform next SASL auth step. Used internally 283 """ 284 if challenge.getNamespace() != NS_SASL: 285 return 286 287 def scram_base64(s): 288 return ''.join(s.encode('base64').split('\n'))
289 290 incoming_data = challenge.getData() 291 data=base64.decodestring(incoming_data) 292 ### Handle Auth result 293 def on_auth_fail(reason): 294 log.info('Failed SASL authentification: %s' % reason) 295 self._owner.send(str(Node('abort', attrs={'xmlns': NS_SASL}))) 296 if len(self.mecs) > 0: 297 # There are other mechanisms to test, but wait for <failure> 298 # answer from server 299 self.startsasl = SASL_FAILURE_IN_PROGRESS 300 raise NodeProcessed 301 if self.on_sasl: 302 self.on_sasl() 303 raise NodeProcessed
304 305 if challenge.getName() == 'failure': 306 if self.startsasl == SASL_FAILURE_IN_PROGRESS: 307 self.MechanismHandler() 308 raise NodeProcessed 309 self.startsasl = SASL_FAILURE 310 try: 311 reason = challenge.getChildren()[0] 312 except Exception: 313 reason = challenge 314 on_auth_fail(reason) 315 elif challenge.getName() == 'success': 316 if self.mechanism == 'SCRAM-SHA-1': 317 # check data-with-success 318 data = scram_parse(data) 319 if data['v'] != scram_base64(self.scram_ServerSignature): 320 on_auth_fail('ServerSignature is wrong') 321 322 self.startsasl = SASL_SUCCESS 323 log.info('Successfully authenticated with remote server.') 324 handlers = self._owner.Dispatcher.dumpHandlers() 325 326 # Bosh specific dispatcher replugging 327 # save old features. They will be used in case we won't get response 328 # on stream restart after SASL auth (happens with XMPP over BOSH 329 # with Openfire) 330 old_features = self._owner.Dispatcher.Stream.features 331 self._owner.Dispatcher.PlugOut() 332 dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner, 333 after_SASL=True, old_features=old_features) 334 self._owner.Dispatcher.restoreHandlers(handlers) 335 self._owner.User = self.username 336 337 if self.on_sasl: 338 self.on_sasl() 339 raise NodeProcessed 340 341 ### Perform auth step 342 log.info('Got challenge:' + data) 343 344 if self.mechanism == 'GSSAPI': 345 if self.gss_step == GSS_STATE_STEP: 346 rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) 347 if rc != kerberos.AUTH_GSS_CONTINUE: 348 self.gss_step = GSS_STATE_WRAP 349 elif self.gss_step == GSS_STATE_WRAP: 350 rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data) 351 response = kerberos.authGSSClientResponse(self.gss_vc) 352 rc = kerberos.authGSSClientWrap(self.gss_vc, response, 353 kerberos.authGSSClientUserName(self.gss_vc)) 354 response = kerberos.authGSSClientResponse(self.gss_vc) 355 if not response: 356 response = '' 357 self._owner.send(Node('response', attrs={'xmlns': NS_SASL}, 358 payload=response).__str__()) 359 raise NodeProcessed 360 if self.mechanism == 'SCRAM-SHA-1': 361 hashfn = hashlib.sha1 362 363 def HMAC(k, s): 364 return hmac.HMAC(key=k, msg=s, digestmod=hashfn).digest() 365 366 def XOR(x, y): 367 r = (chr(ord(px) ^ ord(py)) for px, py in zip(x, y)) 368 return ''.join(r) 369 370 def Hi(s, salt, iters): 371 ii = 1 372 try: 373 s = s.encode('utf-8') 374 except: 375 pass 376 ui_1 = HMAC(s, salt + '\0\0\0\01') 377 ui = ui_1 378 for i in range(iters - 1): 379 ii += 1 380 ui_1 = HMAC(s, ui_1) 381 ui = XOR(ui, ui_1) 382 return ui 383 384 def scram_H(s): 385 return hashfn(s).digest() 386 387 if self.scram_step == 0: 388 self.scram_step = 1 389 self.scram_soup += ',' + data + ',' 390 data = scram_parse(data) 391 # TODO: Should check cnonce here. 392 # TODO: Channel binding data goes in here too. 393 r = 'c=' + scram_base64(self.scram_gs2) 394 r += ',r=' + data['r'] 395 self.scram_soup += r 396 salt = data['s'].decode('base64') 397 iter = int(data['i']) 398 SaltedPassword = Hi(self.password, salt, iter) 399 # TODO: Could cache this, along with salt+iter. 400 ClientKey = HMAC(SaltedPassword, 'Client Key') 401 StoredKey = scram_H(ClientKey) 402 ClientSignature = HMAC(StoredKey, self.scram_soup) 403 ClientProof = XOR(ClientKey, ClientSignature) 404 r += ',p=' + scram_base64(ClientProof) 405 ServerKey = HMAC(SaltedPassword, 'Server Key') 406 self.scram_ServerSignature = HMAC(ServerKey, self.scram_soup) 407 sasl_data = scram_base64(r) 408 node = Node('response', attrs={'xmlns': NS_SASL}, 409 payload=[sasl_data]) 410 self._owner.send(str(node)) 411 raise NodeProcessed 412 413 if self.scram_step == 1: 414 data = scram_parse(data) 415 if data['v'].decode('base64') != self.scram_ServerSignature: 416 # TODO: Not clear what to do here - need to abort. 417 raise Exception 418 node = Node('response', attrs={'xmlns': NS_SASL}); 419 self._owner.send(str(node)) 420 raise NodeProcessed 421 422 # magic foo... 423 chal = challenge_splitter(data) 424 if not self.realm and 'realm' in chal: 425 self.realm = chal['realm'] 426 if 'qop' in chal and ((isinstance(chal['qop'], str) and \ 427 chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ 428 chal['qop'])): 429 self.resp = {} 430 self.resp['username'] = self.username 431 if self.realm: 432 self.resp['realm'] = self.realm 433 else: 434 self.resp['realm'] = self._owner.Server 435 self.resp['nonce'] = chal['nonce'] 436 self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint \ 437 in itertools.repeat(random.randint, 7)) 438 self.resp['nc'] = ('00000001') 439 self.resp['qop'] = 'auth' 440 self.resp['digest-uri'] = 'xmpp/' + self._owner.Server 441 self.resp['charset'] = 'utf-8' 442 # Password is now required 443 self._owner._caller.get_password(self.set_password, self.mechanism) 444 elif 'rspauth' in chal: 445 # Check rspauth value 446 if chal['rspauth'] != self.digest_rspauth: 447 on_auth_fail('rspauth is wrong') 448 self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) 449 else: 450 self.startsasl = SASL_FAILURE 451 log.info('Failed SASL authentification: unknown challenge') 452 if self.on_sasl: 453 self.on_sasl() 454 raise NodeProcessed 455 456 @staticmethod
457 - def _convert_to_iso88591(string):
458 try: 459 string = string.decode('utf-8').encode('iso-8859-1') 460 except UnicodeEncodeError: 461 pass 462 return string
463
464 - def set_password(self, password):
465 self.password = '' if password is None else password 466 if self.mechanism == 'SCRAM-SHA-1': 467 nonce = ''.join('%x' % randint(0, 2 ** 28) for randint in \ 468 itertools.repeat(random.randint, 7)) 469 self.scram_soup = 'n=' + self.username + ',r=' + nonce 470 self.scram_gs2 = 'n,,' # No CB yet. 471 sasl_data = (self.scram_gs2 + self.scram_soup).encode('base64').\ 472 replace('\n', '') 473 node = Node('auth', attrs={'xmlns': NS_SASL, 474 'mechanism': self.mechanism}, payload=[sasl_data]) 475 elif self.mechanism == 'DIGEST-MD5': 476 hash_username = self._convert_to_iso88591(self.resp['username']) 477 hash_realm = self._convert_to_iso88591(self.resp['realm']) 478 hash_password = self._convert_to_iso88591(self.password) 479 A1 = C([H(C([hash_username, hash_realm, hash_password])), 480 self.resp['nonce'], self.resp['cnonce']]) 481 A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) 482 response = HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], 483 self.resp['cnonce'], self.resp['qop'], HH(A2)])) 484 A2 = C(['', self.resp['digest-uri']]) 485 self.digest_rspauth = HH(C([HH(A1), self.resp['nonce'], 486 self.resp['nc'], self.resp['cnonce'], self.resp['qop'], 487 HH(A2)])) 488 self.resp['response'] = response 489 sasl_data = u'' 490 for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', 491 'digest-uri', 'response', 'qop'): 492 if key in ('nc', 'qop', 'response', 'charset'): 493 sasl_data += u"%s=%s," % (key, self.resp[key]) 494 else: 495 sasl_data += u'%s="%s",' % (key, self.resp[key]) 496 sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( 497 '\r', '').replace('\n', '') 498 node = Node('response', attrs={'xmlns': NS_SASL}, 499 payload=[sasl_data]) 500 elif self.mechanism == 'PLAIN': 501 sasl_data = u'\x00%s\x00%s' % (self.username, self.password) 502 sasl_data = sasl_data.encode('utf-8').encode('base64').replace( 503 '\n', '') 504 node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, 505 payload=[sasl_data]) 506 elif self.mechanism == 'X-MESSENGER-OAUTH2': 507 node = Node('auth', attrs={'xmlns': NS_SASL, 508 'mechanism': 'X-MESSENGER-OAUTH2'}) 509 node.addData(password) 510 self._owner.send(str(node))
511
512 513 -class NonBlockingNonSASL(PlugIn):
514 """ 515 Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and 516 transport authentication 517 """ 518
519 - def __init__(self, user, password, resource, on_auth):
520 """ 521 Caches username, password and resource for auth 522 """ 523 PlugIn.__init__(self) 524 self.user = user 525 if password is None: 526 self.password = '' 527 else: 528 self.password = password 529 self.resource = resource 530 self.on_auth = on_auth
531
532 - def plugin(self, owner):
533 """ 534 Determine the best auth method (digest/0k/plain) and use it for auth. 535 Returns used method name on success. Used internally 536 """ 537 log.info('Querying server about possible auth methods') 538 self.owner = owner 539 540 owner.Dispatcher.SendAndWaitForResponse( 541 Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), 542 func=self._on_username)
543
544 - def _on_username(self, resp):
545 if not isResultNode(resp): 546 log.info('No result node arrived! Aborting...') 547 return self.on_auth(None) 548 549 iq=Iq(typ='set', node=resp) 550 query = iq.getTag('query') 551 query.setTagData('username', self.user) 552 query.setTagData('resource', self.resource) 553 554 if query.getTag('digest'): 555 log.info("Performing digest authentication") 556 query.setTagData('digest', 557 hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id'] 558 + self.password).hexdigest()) 559 if query.getTag('password'): 560 query.delChild('password') 561 self._method = 'digest' 562 elif query.getTag('token'): 563 token = query.getTagData('token') 564 seq = query.getTagData('sequence') 565 log.info("Performing zero-k authentication") 566 567 def hasher(s): 568 return hashlib.sha1(s).hexdigest()
569 570 def hash_n_times(s, count): 571 return count and hasher(hash_n_times(s, count-1)) or s
572 573 hash_ = hash_n_times(hasher(hasher(self.password) + token), 574 int(seq)) 575 query.setTagData('hash', hash_) 576 self._method='0k' 577 else: 578 log.warn("Secure methods unsupported, performing plain text \ 579 authentication") 580 self._method = 'plain' 581 self._owner._caller.get_password(self._on_password, self._method) 582 return 583 resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, 584 func=self._on_auth) 585
586 - def _on_password(self, password):
587 self.password = '' if password is None else password 588 iq=Iq('set', NS_AUTH) 589 query = iq.getTag('query') 590 query.setTagData('username', self.user) 591 query.setTagData('resource', self.resource) 592 query.setTagData('password', self.password) 593 resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, 594 func=self._on_auth)
595
596 - def _on_auth(self, resp):
597 if isResultNode(resp): 598 log.info('Sucessfully authenticated with remote host.') 599 self.owner.User = self.user 600 self.owner.Resource = self.resource 601 self.owner._registered_name = self.owner.User + '@' + \ 602 self.owner.Server+ '/' + self.owner.Resource 603 return self.on_auth(self._method) 604 log.info('Authentication failed!') 605 return self.on_auth(None)
606
607 608 -class NonBlockingBind(PlugIn):
609 """ 610 Bind some JID to the current connection to allow router know of our 611 location. Must be plugged after successful SASL auth 612 """ 613
614 - def __init__(self):
615 PlugIn.__init__(self) 616 self.bound = None 617 self.supports_sm = False 618 self.resuming = False
619
620 - def plugin(self, owner):
621 ''' Start resource binding, if allowed at this time. Used internally. ''' 622 if self._owner.Dispatcher.Stream.features: 623 try: 624 self.FeaturesHandler(self._owner.Dispatcher, 625 self._owner.Dispatcher.Stream.features) 626 except NodeProcessed: 627 pass 628 else: 629 self._owner.RegisterHandler('features', self.FeaturesHandler, 630 xmlns=NS_STREAMS)
631
632 - def FeaturesHandler(self, conn, feats):
633 """ 634 Determine if server supports resource binding and set some internal 635 attributes accordingly. 636 637 It also checks if server supports stream management 638 """ 639 640 if feats.getTag('sm', namespace=NS_STREAM_MGMT): 641 self.supports_sm = True # server supports stream management 642 if self.resuming: 643 self._owner._caller.sm.resume_request() 644 645 if not feats.getTag('bind', namespace=NS_BIND): 646 log.info('Server does not requested binding.') 647 # we try to bind resource anyway 648 #self.bound='failure' 649 self.bound = [] 650 return 651 if feats.getTag('session', namespace=NS_SESSION): 652 self.session = 1 653 else: 654 self.session = -1 655 self.bound = []
656
657 - def plugout(self):
658 """ 659 Remove Bind handler from owner's dispatcher. Used internally 660 """ 661 self._owner.UnregisterHandler('features', self.FeaturesHandler, 662 xmlns=NS_STREAMS)
663
664 - def NonBlockingBind(self, resource=None, on_bound=None):
665 """ 666 Perform binding. Use provided resource name or random (if not provided). 667 """ 668 if self.resuming: # We don't bind if we resume the stream 669 return 670 self.on_bound = on_bound 671 self._resource = resource 672 if self._resource: 673 self._resource = [Node('resource', payload=[self._resource])] 674 else: 675 self._resource = [] 676 677 self._owner.onreceive(None) 678 self._owner.Dispatcher.SendAndWaitForResponse( 679 Protocol('iq', typ='set', payload=[Node('bind', 680 attrs={'xmlns': NS_BIND}, payload=self._resource)]), 681 func=self._on_bound)
682
683 - def _on_bound(self, resp):
684 if isResultNode(resp): 685 if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'): 686 self.bound.append(resp.getTag('bind').getTagData('jid')) 687 log.info('Successfully bound %s.' % self.bound[-1]) 688 jid = JID(resp.getTag('bind').getTagData('jid')) 689 self._owner.User = jid.getNode() 690 self._owner.Resource = jid.getResource() 691 # Only negociate stream management after bounded 692 sm = self._owner._caller.sm 693 if self.supports_sm: 694 # starts negociation 695 sm.supports_sm = True 696 sm.set_owner(self._owner) 697 sm.negociate() 698 self._owner.Dispatcher.sm = sm 699 700 if hasattr(self, 'session') and self.session == -1: 701 # Server don't want us to initialize a session 702 log.info('No session required.') 703 self.on_bound('ok') 704 else: 705 self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', 706 payload=[Node('session', attrs={'xmlns':NS_SESSION})]), 707 func=self._on_session) 708 return 709 if resp: 710 log.info('Binding failed: %s.' % resp.getTag('error')) 711 self.on_bound(None) 712 else: 713 log.info('Binding failed: timeout expired.') 714 self.on_bound(None)
715
716 - def _on_session(self, resp):
717 self._owner.onreceive(None) 718 if isResultNode(resp): 719 log.info('Successfully opened session.') 720 self.session = 1 721 self.on_bound('ok') 722 else: 723 log.error('Session open failed.') 724 self.session = 0 725 self.on_bound(None)
726