1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 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' 
 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   
122      return dict(s.split('=', 1) for s in chatter.split(',')) 
 123   
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   
153   
170   
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   
209   
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   
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           
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                   
298                   
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                   
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               
327               
328               
329               
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           
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                   
392                   
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                   
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                       
417                      raise Exception 
418                  node = Node('response', attrs={'xmlns': NS_SASL}); 
419                  self._owner.send(str(node)) 
420                  raise NodeProcessed 
421   
422           
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               
443              self._owner._caller.get_password(self.set_password, self.mechanism) 
444          elif 'rspauth' in chal: 
445               
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 
458          try: 
459              string = string.decode('utf-8').encode('iso-8859-1') 
460          except UnicodeEncodeError: 
461              pass 
462          return string 
 463   
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,,'  
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   
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   
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   
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   
595   
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   
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   
615          PlugIn.__init__(self) 
616          self.bound = None 
617          self.supports_sm = False 
618          self.resuming = False 
 619   
631   
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  
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               
648               
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   
663   
665          """ 
666          Perform binding. Use provided resource name or random (if not provided). 
667          """ 
668          if self.resuming:  
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   
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                   
692                  sm = self._owner._caller.sm 
693                  if self.supports_sm: 
694                       
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                       
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   
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