1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19  """Core XMPP stream functionality. 
 20   
 21  Normative reference: 
 22    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__ 
 23  """ 
 24   
 25  __revision__="$Id: streambase.py 652 2006-08-27 19:41:15Z jajcus $" 
 26  __docformat__="restructuredtext en" 
 27   
 28  import libxml2 
 29  import socket 
 30  import os 
 31  import time 
 32  import random 
 33  import threading 
 34  import errno 
 35  import logging 
 36   
 37   
 38  from pyxmpp import xmlextra 
 39  from pyxmpp.expdict import ExpiringDictionary 
 40  from pyxmpp.utils import to_utf8 
 41  from pyxmpp.stanza import Stanza 
 42  from pyxmpp.error import StreamErrorNode 
 43  from pyxmpp.iq import Iq 
 44  from pyxmpp.presence import Presence 
 45  from pyxmpp.message import Message 
 46  from pyxmpp.jid import JID 
 47  from pyxmpp import resolver 
 48  from pyxmpp.stanzaprocessor import StanzaProcessor 
 49  from pyxmpp.exceptions import StreamError, StreamEncryptionRequired, HostMismatch, ProtocolError 
 50  from pyxmpp.exceptions import FatalStreamError, StreamParseError, StreamAuthenticationError 
 51   
 52  STREAM_NS="http://etherx.jabber.org/streams" 
 53  BIND_NS="urn:ietf:params:xml:ns:xmpp-bind" 
 54   
 65   
 66 -class StreamBase(StanzaProcessor,xmlextra.StreamHandler): 
  67      """Base class for a generic XMPP stream. 
 68   
 69      Responsible for establishing connection, parsing the stream, dispatching 
 70      received stanzas to apopriate handlers and sending application's stanzas. 
 71      This doesn't provide any authentication or encryption (both required by 
 72      the XMPP specification) and is not usable on its own. 
 73   
 74      Whenever we say "stream" here we actually mean two streams 
 75      (incoming and outgoing) of one connections, as defined by the XMPP 
 76      specification. 
 77   
 78      :Ivariables: 
 79          - `lock`: RLock object used to synchronize access to Stream object. 
 80          - `features`: stream features as annouced by the initiator. 
 81          - `me`: local stream endpoint JID. 
 82          - `peer`: remote stream endpoint JID. 
 83          - `process_all_stanzas`: when `True` then all stanzas received are 
 84            considered local. 
 85          - `initiator`: `True` if local stream endpoint is the initiating entity. 
 86          - `owner`: `Client`, `Component` or similar object "owning" this stream. 
 87          - `_reader`: the stream reader object (push parser) for the stream. 
 88      """ 
 89 -    def __init__(self, default_ns, extra_ns = (), keepalive = 0, owner = None): 
  90          """Initialize Stream object 
 91   
 92          :Parameters: 
 93            - `default_ns`: stream's default namespace ("jabber:client" for 
 94              client, "jabber:server" for server, etc.) 
 95            - `extra_ns`: sequence of extra namespace URIs to be defined for 
 96              the stream. 
 97            - `keepalive`: keepalive output interval. 0 to disable. 
 98            - `owner`: `Client`, `Component` or similar object "owning" this stream. 
 99          """ 
100          StanzaProcessor.__init__(self) 
101          xmlextra.StreamHandler.__init__(self) 
102          self.default_ns_uri=default_ns 
103          if extra_ns: 
104              self.extra_ns_uris=extra_ns 
105          else: 
106              self.extra_ns_uris=[] 
107          self.keepalive=keepalive 
108          self._reader_lock=threading.Lock() 
109          self.process_all_stanzas=False 
110          self.port=None 
111          self._reset() 
112          self.owner = owner 
113          self.__logger=logging.getLogger("pyxmpp.Stream") 
 114   
116          """Reset `Stream` object state making it ready to handle new 
117          connections.""" 
118          self.doc_in=None 
119          self.doc_out=None 
120          self.socket=None 
121          self._reader=None 
122          self.addr=None 
123          self.default_ns=None 
124          self.extra_ns={} 
125          self.stream_ns=None 
126          self._reader=None 
127          self.ioreader=None 
128          self.me=None 
129          self.peer=None 
130          self.skip=False 
131          self.stream_id=None 
132          self._iq_response_handlers=ExpiringDictionary() 
133          self._iq_get_handlers={} 
134          self._iq_set_handlers={} 
135          self._message_handlers=[] 
136          self._presence_handlers=[] 
137          self.eof=False 
138          self.initiator=None 
139          self.features=None 
140          self.authenticated=False 
141          self.peer_authenticated=False 
142          self.auth_method_used=None 
143          self.version=None 
144          self.last_keepalive=False 
 145   
148   
150          """Initialize stream on outgoing connection. 
151   
152          :Parameters: 
153            - `sock`: connected socket for the stream 
154            - `to`: name of the remote host 
155          """ 
156          self.eof=0 
157          self.socket=sock 
158          if to: 
159              self.peer=JID(to) 
160          else: 
161              self.peer=None 
162          self.initiator=1 
163          self._send_stream_start() 
164          self._make_reader() 
 165   
166 -    def connect(self,addr,port,service=None,to=None): 
 167          """Establish XMPP connection with given address. 
168   
169          [initiating entity only] 
170   
171          :Parameters: 
172              - `addr`: peer name or IP address 
173              - `port`: port number to connect to 
174              - `service`: service name (to be resolved using SRV DNS records) 
175              - `to`: peer name if different than `addr` 
176          """ 
177          self.lock.acquire() 
178          try: 
179              return self._connect(addr,port,service,to) 
180          finally: 
181              self.lock.release() 
 182   
183 -    def _connect(self,addr,port,service=None,to=None): 
 184          """Same as `Stream.connect` but assume `self.lock` is acquired.""" 
185          if to is None: 
186              to=str(addr) 
187          if service is not None: 
188              self.state_change("resolving srv",(addr,service)) 
189              addrs=resolver.resolve_srv(addr,service) 
190              if not addrs: 
191                  addrs=[(addr,port)] 
192          else: 
193              addrs=[(addr,port)] 
194          msg=None 
195          for addr,port in addrs: 
196              if type(addr) in (str, unicode): 
197                  self.state_change("resolving",addr) 
198              s=None 
199              for res in resolver.getaddrinfo(addr,port,0,socket.SOCK_STREAM): 
200                  family, socktype, proto, _unused, sockaddr = res 
201                  try: 
202                      s=socket.socket(family,socktype,proto) 
203                      self.state_change("connecting",sockaddr) 
204                      s.connect(sockaddr) 
205                      self.state_change("connected",sockaddr) 
206                  except socket.error, msg: 
207                      self.__logger.debug("Connect to %r failed" % (sockaddr,)) 
208                      if s: 
209                          s.close() 
210                          s=None 
211                      continue 
212                  break 
213              if s: 
214                  break 
215          if not s: 
216              if msg: 
217                  raise socket.error, msg 
218              else: 
219                  raise FatalStreamError,"Cannot connect" 
220   
221          self.addr=addr 
222          self.port=port 
223          self._connect_socket(s,to) 
224          self.last_keepalive=time.time() 
 225   
226 -    def accept(self,sock,myname): 
 227          """Accept incoming connection. 
228   
229          [receiving entity only] 
230   
231          :Parameters: 
232              - `sock`: a listening socket. 
233              - `myname`: local stream endpoint name.""" 
234          self.lock.acquire() 
235          try: 
236              return self._accept(sock,myname) 
237          finally: 
238              self.lock.release() 
 239   
241          """Same as `Stream.accept` but assume `self.lock` is acquired.""" 
242          self.eof=0 
243          self.socket,addr=sock.accept() 
244          self.__logger.debug("Connection from: %r" % (addr,)) 
245          self.addr,self.port=addr 
246          if myname: 
247              self.me=JID(myname) 
248          else: 
249              self.me=None 
250          self.initiator=0 
251          self._make_reader() 
252          self.last_keepalive=time.time() 
 253   
255          """Gracefully close the connection.""" 
256          self.lock.acquire() 
257          try: 
258              return self._disconnect() 
259          finally: 
260              self.lock.release() 
 261   
263          """Same as `Stream.disconnect` but assume `self.lock` is acquired.""" 
264          if self.doc_out: 
265              self._send_stream_end() 
 266   
267 -    def _post_connect(self): 
 268          """Called when connection is established. 
269   
270          This method is supposed to be overriden in derived classes.""" 
271          pass 
 272   
273 -    def _post_auth(self): 
 274          """Called when connection is authenticated. 
275   
276          This method is supposed to be overriden in derived classes.""" 
277          pass 
 278   
280          """Called when connection state is changed. 
281   
282          This method is supposed to be overriden in derived classes 
283          or replaced by an application. 
284   
285          It may be used to display the connection progress.""" 
286          self.__logger.debug("State: %s: %r" % (state,arg)) 
 287   
289          """Forcibly close the connection and clear the stream state.""" 
290          self.lock.acquire() 
291          try: 
292              return self._close() 
293          finally: 
294              self.lock.release() 
 295   
297          """Same as `Stream.close` but assume `self.lock` is acquired.""" 
298          self._disconnect() 
299          if self.doc_in: 
300              self.doc_in=None 
301          if self.features: 
302              self.features=None 
303          self._reader=None 
304          self.stream_id=None 
305          if self.socket: 
306              self.socket.close() 
307          self._reset() 
 308   
310          """Create ne `xmlextra.StreamReader` instace as `self._reader`.""" 
311          self._reader=xmlextra.StreamReader(self) 
 312   
314          """Process <stream:stream> (stream start) tag received from peer. 
315   
316          :Parameters: 
317              - `doc`: document created by the parser""" 
318          self.doc_in=doc 
319          self.__logger.debug("input document: %r" % (self.doc_in.serialize(),)) 
320   
321          try: 
322              r=self.doc_in.getRootElement() 
323              if r.ns().getContent() != STREAM_NS: 
324                  self._send_stream_error("invalid-namespace") 
325                  raise FatalStreamError,"Invalid namespace." 
326          except libxml2.treeError: 
327              self._send_stream_error("invalid-namespace") 
328              raise FatalStreamError,"Couldn't get the namespace." 
329   
330          self.version=r.prop("version") 
331          if self.version and self.version!="1.0": 
332              self._send_stream_error("unsupported-version") 
333              raise FatalStreamError,"Unsupported protocol version." 
334   
335          to_from_mismatch=0 
336          if self.initiator: 
337              self.stream_id=r.prop("id") 
338              peer=r.prop("from") 
339              if peer: 
340                  peer=JID(peer) 
341              if self.peer: 
342                  if peer and peer!=self.peer: 
343                      self.__logger.debug("peer hostname mismatch:" 
344                          " %r != %r" % (peer,self.peer)) 
345                      to_from_mismatch=1 
346              else: 
347                  self.peer=peer 
348          else: 
349              to=r.prop("to") 
350              if to: 
351                  to=self.check_to(to) 
352                  if not to: 
353                      self._send_stream_error("host-unknown") 
354                      raise FatalStreamError,'Bad "to"' 
355                  self.me=JID(to) 
356              self._send_stream_start(self.generate_id()) 
357              self._send_stream_features() 
358              self.state_change("fully connected",self.peer) 
359              self._post_connect() 
360   
361          if not self.version: 
362              self.state_change("fully connected",self.peer) 
363              self._post_connect() 
364   
365          if to_from_mismatch: 
366              raise HostMismatch 
 367   
369          """Process </stream:stream> (stream end) tag received from peer. 
370   
371          :Parameters: 
372              - `_unused`: document created by the parser""" 
373          self.__logger.debug("Stream ended") 
374          self.eof=1 
375          if self.doc_out: 
376              self._send_stream_end() 
377          if self.doc_in: 
378              self.doc_in=None 
379              self._reader=None 
380              if self.features: 
381                  self.features=None 
382          self.state_change("disconnected",self.peer) 
 383   
385          """Process stanza (first level child element of the stream) start tag 
386          -- do nothing. 
387   
388          :Parameters: 
389              - `doc`: parsed document 
390              - `node`: stanza's full XML 
391          """ 
392          pass 
 393   
394 -    def stanza(self, _unused, node): 
 395          """Process stanza (first level child element of the stream). 
396   
397          :Parameters: 
398              - `_unused`: parsed document 
399              - `node`: stanza's full XML 
400          """ 
401          self._process_node(node) 
 402   
404          """Handle stream XML parse error. 
405   
406          :Parameters: 
407              - `descr`: error description 
408          """ 
409          raise StreamParseError,descr 
 410   
412          """Send stream end tag.""" 
413          self.doc_out.getRootElement().addContent(" ") 
414          s=self.doc_out.getRootElement().serialize(encoding="UTF-8") 
415          end=s.rindex("<") 
416          try: 
417              self._write_raw(s[end:]) 
418          except (IOError,SystemError,socket.error),e: 
419              self.__logger.debug("Sending stream closing tag failed:"+str(e)) 
420          self.doc_out.freeDoc() 
421          self.doc_out=None 
422          if self.features: 
423              self.features=None 
 424   
426          """Send stream start tag.""" 
427          if self.doc_out: 
428              raise StreamError,"Stream start already sent" 
429          self.doc_out=libxml2.newDoc("1.0") 
430          root=self.doc_out.newChild(None, "stream", None) 
431          self.stream_ns=root.newNs(STREAM_NS,"stream") 
432          root.setNs(self.stream_ns) 
433          self.default_ns=root.newNs(self.default_ns_uri,None) 
434          for prefix,uri in self.extra_ns: 
435              self.extra_ns[uri]=root.newNs(uri,prefix) 
436          if self.peer and self.initiator: 
437              root.setProp("to",self.peer.as_utf8()) 
438          if self.me and not self.initiator: 
439              root.setProp("from",self.me.as_utf8()) 
440          root.setProp("version","1.0") 
441          if sid: 
442              root.setProp("id",sid) 
443              self.stream_id=sid 
444          sr=self.doc_out.serialize(encoding="UTF-8") 
445          self._write_raw(sr[:sr.find("/>")]+">") 
 446   
460   
462          """Restart the stream as needed after SASL and StartTLS negotiation.""" 
463          self._reader=None 
464           
465          self.doc_out=None 
466           
467                       
468          self.doc_in=None 
469          self.features=None 
470          if self.initiator: 
471              self._send_stream_start(self.stream_id) 
472          self._make_reader() 
 473   
475          """Create the <features/> element for the stream. 
476   
477          [receving entity only] 
478   
479          :returns: new <features/> element node.""" 
480          root=self.doc_out.getRootElement() 
481          features=root.newChild(root.ns(),"features",None) 
482          return features 
 483   
490   
492          """Write raw data to the stream socket. 
493   
494          :Parameters: 
495              - `data`: data to send""" 
496          self.lock.acquire() 
497          try: 
498              return self._write_raw(data) 
499          finally: 
500              self.lock.release() 
 501   
503          """Same as `Stream.write_raw` but assume `self.lock` is acquired.""" 
504          logging.getLogger("pyxmpp.Stream.out").debug("OUT: %r",data) 
505          try: 
506              self.socket.send(data) 
507          except (IOError,OSError,socket.error),e: 
508              raise FatalStreamError("IO Error: "+str(e)) 
 509   
511          """Write XML `xmlnode` to the stream. 
512   
513          :Parameters: 
514              - `xmlnode`: XML node to send.""" 
515          if self.eof or not self.socket or not self.doc_out: 
516              self.__logger.debug("Dropping stanza: %r" % (xmlnode,)) 
517              return 
518          xmlnode=xmlnode.docCopyNode(self.doc_out,1) 
519          self.doc_out.addChild(xmlnode) 
520          try: 
521              ns = xmlnode.ns() 
522          except libxml2.treeError: 
523              ns = None 
524          if ns and ns.content == xmlextra.COMMON_NS: 
525              xmlextra.replace_ns(xmlnode, ns, self.default_ns) 
526          s = xmlextra.safe_serialize(xmlnode) 
527          self._write_raw(s) 
528          xmlnode.unlinkNode() 
529          xmlnode.freeNode() 
 530   
531 -    def send(self,stanza): 
 532          """Write stanza to the stream. 
533   
534          :Parameters: 
535              - `stanza`: XMPP stanza to send.""" 
536          self.lock.acquire() 
537          try: 
538              return self._send(stanza) 
539          finally: 
540              self.lock.release() 
 541   
553   
555          """Do some housekeeping (cache expiration, timeout handling). 
556   
557          This method should be called periodically from the application's 
558          main loop.""" 
559          self.lock.acquire() 
560          try: 
561              return self._idle() 
562          finally: 
563              self.lock.release() 
 564   
566          """Same as `Stream.idle` but assume `self.lock` is acquired.""" 
567          self._iq_response_handlers.expire() 
568          if not self.socket or self.eof: 
569              return 
570          now=time.time() 
571          if self.keepalive and now-self.last_keepalive>=self.keepalive: 
572              self._write_raw(" ") 
573              self.last_keepalive=now 
 574   
576          """Return filedescriptor of the stream socket.""" 
577          self.lock.acquire() 
578          try: 
579              return self.socket.fileno() 
580          finally: 
581              self.lock.release() 
 582   
583 -    def loop(self,timeout): 
 584          """Simple "main loop" for the stream.""" 
585          self.lock.acquire() 
586          try: 
587              while not self.eof and self.socket is not None: 
588                  act=self._loop_iter(timeout) 
589                  if not act: 
590                      self._idle() 
591          finally: 
592              self.lock.release() 
 593   
595          """Single iteration of a simple "main loop" for the stream.""" 
596          self.lock.acquire() 
597          try: 
598              return self._loop_iter(timeout) 
599          finally: 
600              self.lock.release() 
 601   
603          """Same as `Stream.loop_iter` but assume `self.lock` is acquired.""" 
604          import select 
605          self.lock.release() 
606          try: 
607              try: 
608                  ifd, _unused, efd = select.select( [self.socket], [], [self.socket], timeout ) 
609              except select.error,e: 
610                  if e.args[0]!=errno.EINTR: 
611                      raise 
612                  ifd, _unused, efd=[], [], [] 
613          finally: 
614              self.lock.acquire() 
615          if self.socket in ifd or self.socket in efd: 
616              self._process() 
617              return True 
618          else: 
619              return False 
 620   
622          """Process stream's pending events. 
623   
624          Should be called whenever there is input available 
625          on `self.fileno()` socket descriptor. Is called by 
626          `self.loop_iter`.""" 
627          self.lock.acquire() 
628          try: 
629              self._process() 
630          finally: 
631              self.lock.release() 
 632   
634          """Same as `Stream.process` but assume `self.lock` is acquired.""" 
635          try: 
636              try: 
637                  self._read() 
638              except (xmlextra.error,),e: 
639                  self.__logger.exception("Exception during read()") 
640                  raise StreamParseError(unicode(e)) 
641              except: 
642                  raise 
643          except (IOError,OSError,socket.error),e: 
644              self.close() 
645              raise FatalStreamError("IO Error: "+str(e)) 
646          except (FatalStreamError,KeyboardInterrupt,SystemExit),e: 
647              self.close() 
648              raise 
 649   
651          """Read data pending on the stream socket and pass it to the parser.""" 
652          self.__logger.debug("StreamBase._read(), socket: %r",self.socket) 
653          if self.eof: 
654              return 
655          try: 
656              r=self.socket.recv(1024) 
657          except socket.error,e: 
658              if e.args[0]!=errno.EINTR: 
659                  raise 
660              return 
661          self._feed_reader(r) 
 662   
664          """Feed the stream reader with data received. 
665   
666          If `data` is None or empty, then stream end (peer disconnected) is 
667          assumed and the stream is closed. 
668   
669          :Parameters: 
670              - `data`: data received from the stream socket. 
671          :Types: 
672              - `data`: `unicode` 
673          """ 
674          logging.getLogger("pyxmpp.Stream.in").debug("IN: %r",data) 
675          if data: 
676              try: 
677                  r=self._reader.feed(data) 
678                  while r: 
679                      r=self._reader.feed("") 
680                  if r is None: 
681                      self.eof=1 
682                      self.disconnect() 
683              except StreamParseError: 
684                  self._send_stream_error("xml-not-well-formed") 
685                  raise 
686          else: 
687              self.eof=1 
688              self.disconnect() 
689          if self.eof: 
690              self.stream_end(None) 
 691   
693          """Process first level element of the stream. 
694   
695          The element may be stream error or features, StartTLS 
696          request/response, SASL request/response or a stanza. 
697   
698          :Parameters: 
699              - `xmlnode`: XML node describing the element 
700          """ 
701          ns_uri=xmlnode.ns().getContent() 
702          if ns_uri=="http://etherx.jabber.org/streams": 
703              self._process_stream_node(xmlnode) 
704              return 
705   
706          if ns_uri==self.default_ns_uri: 
707              stanza=stanza_factory(xmlnode, self) 
708              self.lock.release() 
709              try: 
710                  self.process_stanza(stanza) 
711              finally: 
712                  self.lock.acquire() 
713                  stanza.free() 
714          else: 
715              self.__logger.debug("Unhandled node: %r" % (xmlnode.serialize(),)) 
 716   
718          """Process first level stream-namespaced element of the stream. 
719   
720          The element may be stream error or stream features. 
721   
722          :Parameters: 
723              - `xmlnode`: XML node describing the element 
724          """ 
725          if xmlnode.name=="error": 
726              e=StreamErrorNode(xmlnode) 
727              self.lock.release() 
728              try: 
729                  self.process_stream_error(e) 
730              finally: 
731                  self.lock.acquire() 
732                  e.free() 
733              return 
734          elif xmlnode.name=="features": 
735              self.__logger.debug("Got stream features") 
736              self.__logger.debug("Node: %r" % (xmlnode,)) 
737              self.features=xmlnode.copyNode(1) 
738              self.doc_in.addChild(self.features) 
739              self._got_features() 
740              return 
741   
742          self.__logger.debug("Unhandled stream node: %r" % (xmlnode.serialize(),)) 
 743   
745          """Process stream error element received. 
746   
747          :Types: 
748              - `err`: `StreamErrorNode` 
749   
750          :Parameters: 
751              - `err`: error received 
752          """ 
753   
754          self.__logger.debug("Unhandled stream error: condition: %s %r" 
755                  % (err.get_condition().name,err.serialize())) 
 756   
758          """Check "to" attribute of received stream header. 
759   
760          :return: `to` if it is equal to `self.me`, None otherwise. 
761   
762          Should be overriden in derived classes which require other logic 
763          for handling that attribute.""" 
764          if to!=self.me: 
765              return None 
766          return to 
 767   
769          """Generate a random and unique stream ID. 
770   
771          :return: the id string generated.""" 
772          return "%i-%i-%s" % (os.getpid(),time.time(),str(random.random())[2:]) 
 773   
775          """Process incoming <stream:features/> element. 
776   
777          [initiating entity only] 
778   
779          The received features node is available in `self.features`.""" 
780          ctxt = self.doc_in.xpathNewContext() 
781          ctxt.setContextNode(self.features) 
782          ctxt.xpathRegisterNs("stream",STREAM_NS) 
783          ctxt.xpathRegisterNs("bind",BIND_NS) 
784          bind_n=None 
785          try: 
786              bind_n=ctxt.xpathEval("bind:bind") 
787          finally: 
788              ctxt.xpathFreeContext() 
789   
790          if self.authenticated: 
791              if bind_n: 
792                  self.bind(self.me.resource) 
793              else: 
794                  self.state_change("authorized",self.me) 
 795   
796 -    def bind(self,resource): 
 815   
817          """Handle resource binding success. 
818   
819          [initiating entity only] 
820   
821          :Parameters: 
822              - `stanza`: <iq type="result"/> stanza received. 
823   
824          Set `self.me` to the full JID negotiated.""" 
825          jid_n=stanza.xpath_eval("bind:bind/bind:jid",{"bind":BIND_NS}) 
826          if jid_n: 
827              self.me=JID(jid_n[0].getContent().decode("utf-8")) 
828          self.state_change("authorized",self.me) 
 829   
831          """Handle resource binding success. 
832   
833          [initiating entity only] 
834   
835          :raise FatalStreamError:""" 
836          raise FatalStreamError,"Resource binding failed" 
 837   
839          """Check if stream is connected. 
840   
841          :return: True if stream connection is active.""" 
842          if self.doc_in and self.doc_out and not self.eof: 
843              return True 
844          else: 
845              return False 
  846   
847   
848