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 __docformat__="restructuredtext en"
26
27 import libxml2
28 import socket
29 import os
30 import time
31 import random
32 import threading
33 import errno
34 import logging
35
36
37 from pyxmpp import xmlextra
38 from pyxmpp.expdict import ExpiringDictionary
39 from pyxmpp.utils import to_utf8, from_utf8
40 from pyxmpp.stanza import Stanza
41 from pyxmpp.error import StreamErrorNode
42 from pyxmpp.iq import Iq
43 from pyxmpp.presence import Presence
44 from pyxmpp.message import Message
45 from pyxmpp.jid import JID
46 from pyxmpp import resolver
47 from pyxmpp.stanzaprocessor import StanzaProcessor
48 from pyxmpp.exceptions import StreamError, StreamEncryptionRequired
49 from pyxmpp.exceptions import HostMismatch, ProtocolError
50 from pyxmpp.exceptions import DNSError, UnexpectedCNAMEError
51 from pyxmpp.exceptions import FatalStreamError, StreamParseError, StreamAuthenticationError
52
53 STREAM_NS="http://etherx.jabber.org/streams"
54 BIND_NS="urn:ietf:params:xml:ns:xmpp-bind"
55
66
67 -class StreamBase(StanzaProcessor,xmlextra.StreamHandler):
68 """Base class for a generic XMPP stream.
69
70 Responsible for establishing connection, parsing the stream, dispatching
71 received stanzas to apopriate handlers and sending application's stanzas.
72 This doesn't provide any authentication or encryption (both required by
73 the XMPP specification) and is not usable on its own.
74
75 Whenever we say "stream" here we actually mean two streams
76 (incoming and outgoing) of one connections, as defined by the XMPP
77 specification.
78
79 :Ivariables:
80 - `lock`: RLock object used to synchronize access to Stream object.
81 - `features`: stream features as annouced by the initiator.
82 - `me`: local stream endpoint JID.
83 - `peer`: remote stream endpoint JID.
84 - `process_all_stanzas`: when `True` then all stanzas received are
85 considered local.
86 - `initiator`: `True` if local stream endpoint is the initiating entity.
87 - `owner`: `Client`, `Component` or similar object "owning" this stream.
88 - `_reader`: the stream reader object (push parser) for the stream.
89 """
90 - def __init__(self, default_ns, extra_ns = (), keepalive = 0, owner = None):
91 """Initialize Stream object
92
93 :Parameters:
94 - `default_ns`: stream's default namespace ("jabber:client" for
95 client, "jabber:server" for server, etc.)
96 - `extra_ns`: sequence of extra namespace URIs to be defined for
97 the stream.
98 - `keepalive`: keepalive output interval. 0 to disable.
99 - `owner`: `Client`, `Component` or similar object "owning" this stream.
100 """
101 StanzaProcessor.__init__(self)
102 xmlextra.StreamHandler.__init__(self)
103 self.default_ns_uri=default_ns
104 if extra_ns:
105 self.extra_ns_uris=extra_ns
106 else:
107 self.extra_ns_uris=[]
108 self.keepalive=keepalive
109 self._reader_lock=threading.Lock()
110 self.process_all_stanzas=False
111 self.port=None
112 self._reset()
113 self.owner = owner
114 self.__logger=logging.getLogger("pyxmpp.Stream")
115
117 """Reset `Stream` object state making it ready to handle new
118 connections."""
119 self.doc_in=None
120 self.doc_out=None
121 self.socket=None
122 self._reader=None
123 self.addr=None
124 self.default_ns=None
125 self.extra_ns={}
126 self.stream_ns=None
127 self._reader=None
128 self.ioreader=None
129 self.me=None
130 self.peer=None
131 self.skip=False
132 self.stream_id=None
133 self._iq_response_handlers=ExpiringDictionary()
134 self._iq_get_handlers={}
135 self._iq_set_handlers={}
136 self._message_handlers=[]
137 self._presence_handlers=[]
138 self.eof=False
139 self.initiator=None
140 self.features=None
141 self.authenticated=False
142 self.peer_authenticated=False
143 self.auth_method_used=None
144 self.version=None
145 self.last_keepalive=False
146
149
151 """Initialize stream on outgoing connection.
152
153 :Parameters:
154 - `sock`: connected socket for the stream
155 - `to`: name of the remote host
156 """
157 self.eof=0
158 self.socket=sock
159 if to:
160 self.peer=JID(to)
161 else:
162 self.peer=None
163 self.initiator=1
164 self._send_stream_start()
165 self._make_reader()
166
167 - def connect(self,addr,port,service=None,to=None):
168 """Establish XMPP connection with given address.
169
170 [initiating entity only]
171
172 :Parameters:
173 - `addr`: peer name or IP address
174 - `port`: port number to connect to
175 - `service`: service name (to be resolved using SRV DNS records)
176 - `to`: peer name if different than `addr`
177 """
178 self.lock.acquire()
179 try:
180 return self._connect(addr,port,service,to)
181 finally:
182 self.lock.release()
183
184 - def _connect(self, addr, port, service = None, to = None):
185 """Same as `Stream.connect` but assume `self.lock` is acquired."""
186 if to is None:
187 to = from_utf8(addr)
188 allow_cname = True
189 if service is not None:
190 self.state_change("resolving srv", (addr, service))
191 addrs = resolver.resolve_srv(addr, service)
192 if not addrs:
193 addrs = [(addr, port)]
194 else:
195 allow_cname = False
196 else:
197 addrs=[(addr, port)]
198 exception = None
199 for addr, port in addrs:
200 if type(addr) in (str, unicode):
201 self.state_change("resolving", addr)
202 s=None
203 while True:
204 try:
205 addresses = resolver.getaddrinfo(addr, port, None,
206 socket.SOCK_STREAM, allow_cname = allow_cname)
207 break
208 except UnexpectedCNAMEError, err:
209 self.__logger.warning(str(err))
210 allow_cname = True
211 continue
212 except DNSError, err:
213 self.__logger.debug(str(err))
214 exception = err
215 addresses = []
216 break
217 for res in addresses:
218 family, socktype, proto, _unused, sockaddr = res
219 try:
220 s=socket.socket(family,socktype,proto)
221 self.state_change("connecting",sockaddr)
222 s.connect(sockaddr)
223 self.state_change("connected",sockaddr)
224 except socket.error, err:
225 exception = err
226 self.__logger.debug("Connect to %r failed" % (sockaddr,))
227 if s:
228 s.close()
229 s=None
230 continue
231 break
232 if s:
233 break
234 if not s:
235 if exception:
236 raise exception
237 else:
238 raise FatalStreamError,"Cannot connect"
239
240 self.addr=addr
241 self.port=port
242 self._connect_socket(s, to)
243 self.last_keepalive=time.time()
244
245 - def accept(self,sock,myname):
246 """Accept incoming connection.
247
248 [receiving entity only]
249
250 :Parameters:
251 - `sock`: a listening socket.
252 - `myname`: local stream endpoint name."""
253 self.lock.acquire()
254 try:
255 return self._accept(sock,myname)
256 finally:
257 self.lock.release()
258
260 """Same as `Stream.accept` but assume `self.lock` is acquired."""
261 self.eof=0
262 self.socket,addr=sock.accept()
263 self.__logger.debug("Connection from: %r" % (addr,))
264 self.addr,self.port=addr
265 if myname:
266 self.me=JID(myname)
267 else:
268 self.me=None
269 self.initiator=0
270 self._make_reader()
271 self.last_keepalive=time.time()
272
274 """Gracefully close the connection."""
275 self.lock.acquire()
276 try:
277 return self._disconnect()
278 finally:
279 self.lock.release()
280
282 """Same as `Stream.disconnect` but assume `self.lock` is acquired."""
283 if self.doc_out:
284 self._send_stream_end()
285
286 - def _post_connect(self):
287 """Called when connection is established.
288
289 This method is supposed to be overriden in derived classes."""
290 pass
291
292 - def _post_auth(self):
293 """Called when connection is authenticated.
294
295 This method is supposed to be overriden in derived classes."""
296 pass
297
299 """Called when connection state is changed.
300
301 This method is supposed to be overriden in derived classes
302 or replaced by an application.
303
304 It may be used to display the connection progress."""
305 self.__logger.debug("State: %s: %r" % (state,arg))
306
308 """Forcibly close the connection and clear the stream state."""
309 self.lock.acquire()
310 try:
311 return self._close()
312 finally:
313 self.lock.release()
314
316 """Same as `Stream.close` but assume `self.lock` is acquired."""
317 self._disconnect()
318 if self.doc_in:
319 self.doc_in=None
320 if self.features:
321 self.features=None
322 self._reader=None
323 self.stream_id=None
324 if self.socket:
325 self.socket.close()
326 self._reset()
327
329 """Create ne `xmlextra.StreamReader` instace as `self._reader`."""
330 self._reader=xmlextra.StreamReader(self)
331
386
388 """Process </stream:stream> (stream end) tag received from peer.
389
390 :Parameters:
391 - `_unused`: document created by the parser"""
392 self.__logger.debug("Stream ended")
393 self.eof=1
394 if self.doc_out:
395 self._send_stream_end()
396 if self.doc_in:
397 self.doc_in=None
398 self._reader=None
399 if self.features:
400 self.features=None
401 self.state_change("disconnected",self.peer)
402
404 """Process stanza (first level child element of the stream) start tag
405 -- do nothing.
406
407 :Parameters:
408 - `doc`: parsed document
409 - `node`: stanza's full XML
410 """
411 pass
412
413 - def stanza(self, _unused, node):
414 """Process stanza (first level child element of the stream).
415
416 :Parameters:
417 - `_unused`: parsed document
418 - `node`: stanza's full XML
419 """
420 self._process_node(node)
421
423 """Handle stream XML parse error.
424
425 :Parameters:
426 - `descr`: error description
427 """
428 raise StreamParseError,descr
429
431 """Send stream end tag."""
432 self.doc_out.getRootElement().addContent(" ")
433 s=self.doc_out.getRootElement().serialize(encoding="UTF-8")
434 end=s.rindex("<")
435 try:
436 self._write_raw(s[end:])
437 except (IOError,SystemError,socket.error),e:
438 self.__logger.debug("Sending stream closing tag failed:"+str(e))
439 self.doc_out.freeDoc()
440 self.doc_out=None
441 if self.features:
442 self.features=None
443
445 """Send stream start tag."""
446 if self.doc_out:
447 raise StreamError,"Stream start already sent"
448 self.doc_out=libxml2.newDoc("1.0")
449 root=self.doc_out.newChild(None, "stream", None)
450 self.stream_ns=root.newNs(STREAM_NS,"stream")
451 root.setNs(self.stream_ns)
452 self.default_ns=root.newNs(self.default_ns_uri,None)
453 for prefix,uri in self.extra_ns:
454 self.extra_ns[uri]=root.newNs(uri,prefix)
455 if self.peer and self.initiator:
456 root.setProp("to",self.peer.as_utf8())
457 if self.me and not self.initiator:
458 root.setProp("from",self.me.as_utf8())
459 root.setProp("version","1.0")
460 if sid:
461 root.setProp("id",sid)
462 self.stream_id=sid
463 sr=self.doc_out.serialize(encoding="UTF-8")
464 self._write_raw(sr[:sr.find("/>")]+">")
465
479
481 """Restart the stream as needed after SASL and StartTLS negotiation."""
482 self._reader=None
483
484 self.doc_out=None
485
486
487 self.doc_in=None
488 self.features=None
489 if self.initiator:
490 self._send_stream_start(self.stream_id)
491 self._make_reader()
492
494 """Create the <features/> element for the stream.
495
496 [receving entity only]
497
498 :returns: new <features/> element node."""
499 root=self.doc_out.getRootElement()
500 features=root.newChild(root.ns(),"features",None)
501 return features
502
509
511 """Write raw data to the stream socket.
512
513 :Parameters:
514 - `data`: data to send"""
515 self.lock.acquire()
516 try:
517 return self._write_raw(data)
518 finally:
519 self.lock.release()
520
522 """Same as `Stream.write_raw` but assume `self.lock` is acquired."""
523 logging.getLogger("pyxmpp.Stream.out").debug("OUT: %r",data)
524 try:
525 while data:
526 sent = self.socket.send(data)
527 data = data[sent:]
528 except (IOError,OSError,socket.error),e:
529 raise FatalStreamError("IO Error: "+str(e))
530
532 """Write XML `xmlnode` to the stream.
533
534 :Parameters:
535 - `xmlnode`: XML node to send."""
536 if self.eof or not self.socket or not self.doc_out:
537 self.__logger.debug("Dropping stanza: %r" % (xmlnode,))
538 return
539 xmlnode=xmlnode.docCopyNode(self.doc_out,1)
540 self.doc_out.addChild(xmlnode)
541 try:
542 ns = xmlnode.ns()
543 except libxml2.treeError:
544 ns = None
545 if ns and ns.content == xmlextra.COMMON_NS:
546 xmlextra.replace_ns(xmlnode, ns, self.default_ns)
547 s = xmlextra.safe_serialize(xmlnode)
548 self._write_raw(s)
549 xmlnode.unlinkNode()
550 xmlnode.freeNode()
551
552 - def send(self,stanza):
553 """Write stanza to the stream.
554
555 :Parameters:
556 - `stanza`: XMPP stanza to send."""
557 self.lock.acquire()
558 try:
559 return self._send(stanza)
560 finally:
561 self.lock.release()
562
574
576 """Do some housekeeping (cache expiration, timeout handling).
577
578 This method should be called periodically from the application's
579 main loop."""
580 self.lock.acquire()
581 try:
582 return self._idle()
583 finally:
584 self.lock.release()
585
587 """Same as `Stream.idle` but assume `self.lock` is acquired."""
588 self._iq_response_handlers.expire()
589 if not self.socket or self.eof:
590 return
591 now=time.time()
592 if self.keepalive and now-self.last_keepalive>=self.keepalive:
593 self._write_raw(" ")
594 self.last_keepalive=now
595
597 """Return filedescriptor of the stream socket."""
598 self.lock.acquire()
599 try:
600 return self.socket.fileno()
601 finally:
602 self.lock.release()
603
604 - def loop(self,timeout):
605 """Simple "main loop" for the stream."""
606 self.lock.acquire()
607 try:
608 while not self.eof and self.socket is not None:
609 act=self._loop_iter(timeout)
610 if not act:
611 self._idle()
612 finally:
613 self.lock.release()
614
616 """Single iteration of a simple "main loop" for the stream."""
617 self.lock.acquire()
618 try:
619 return self._loop_iter(timeout)
620 finally:
621 self.lock.release()
622
624 """Same as `Stream.loop_iter` but assume `self.lock` is acquired."""
625 import select
626 self.lock.release()
627 try:
628 if not self.socket:
629 time.sleep(timeout)
630 return False
631 try:
632 ifd, _unused, efd = select.select( [self.socket], [], [self.socket], timeout )
633 except select.error,e:
634 if e.args[0]!=errno.EINTR:
635 raise
636 ifd, _unused, efd=[], [], []
637 finally:
638 self.lock.acquire()
639 if self.socket in ifd or self.socket in efd:
640 self._process()
641 return True
642 else:
643 return False
644
646 """Process stream's pending events.
647
648 Should be called whenever there is input available
649 on `self.fileno()` socket descriptor. Is called by
650 `self.loop_iter`."""
651 self.lock.acquire()
652 try:
653 self._process()
654 finally:
655 self.lock.release()
656
658 """Same as `Stream.process` but assume `self.lock` is acquired."""
659 try:
660 try:
661 self._read()
662 except (xmlextra.error,),e:
663 self.__logger.exception("Exception during read()")
664 raise StreamParseError(unicode(e))
665 except:
666 raise
667 except (IOError,OSError,socket.error),e:
668 self.close()
669 raise FatalStreamError("IO Error: "+str(e))
670 except (FatalStreamError,KeyboardInterrupt,SystemExit),e:
671 self.close()
672 raise
673
675 """Read data pending on the stream socket and pass it to the parser."""
676 self.__logger.debug("StreamBase._read(), socket: %r",self.socket)
677 if self.eof:
678 return
679 try:
680 r=self.socket.recv(1024)
681 except socket.error,e:
682 if e.args[0]!=errno.EINTR:
683 raise
684 return
685 self._feed_reader(r)
686
688 """Feed the stream reader with data received.
689
690 If `data` is None or empty, then stream end (peer disconnected) is
691 assumed and the stream is closed.
692
693 :Parameters:
694 - `data`: data received from the stream socket.
695 :Types:
696 - `data`: `unicode`
697 """
698 logging.getLogger("pyxmpp.Stream.in").debug("IN: %r",data)
699 if data:
700 try:
701 r=self._reader.feed(data)
702 while r:
703 r=self._reader.feed("")
704 if r is None:
705 self.eof=1
706 self.disconnect()
707 except StreamParseError:
708 self._send_stream_error("xml-not-well-formed")
709 raise
710 else:
711 self.eof=1
712 self.disconnect()
713 if self.eof:
714 self.stream_end(None)
715
717 """Process first level element of the stream.
718
719 The element may be stream error or features, StartTLS
720 request/response, SASL request/response or a stanza.
721
722 :Parameters:
723 - `xmlnode`: XML node describing the element
724 """
725 ns_uri=xmlnode.ns().getContent()
726 if ns_uri=="http://etherx.jabber.org/streams":
727 self._process_stream_node(xmlnode)
728 return
729
730 if ns_uri==self.default_ns_uri:
731 stanza=stanza_factory(xmlnode, self)
732 self.lock.release()
733 try:
734 self.process_stanza(stanza)
735 finally:
736 self.lock.acquire()
737 stanza.free()
738 else:
739 self.__logger.debug("Unhandled node: %r" % (xmlnode.serialize(),))
740
742 """Process first level stream-namespaced element of the stream.
743
744 The element may be stream error or stream features.
745
746 :Parameters:
747 - `xmlnode`: XML node describing the element
748 """
749 if xmlnode.name=="error":
750 e=StreamErrorNode(xmlnode)
751 self.lock.release()
752 try:
753 self.process_stream_error(e)
754 finally:
755 self.lock.acquire()
756 e.free()
757 return
758 elif xmlnode.name=="features":
759 self.__logger.debug("Got stream features")
760 self.__logger.debug("Node: %r" % (xmlnode,))
761 self.features=xmlnode.copyNode(1)
762 self.doc_in.addChild(self.features)
763 self._got_features()
764 return
765
766 self.__logger.debug("Unhandled stream node: %r" % (xmlnode.serialize(),))
767
769 """Process stream error element received.
770
771 :Types:
772 - `err`: `StreamErrorNode`
773
774 :Parameters:
775 - `err`: error received
776 """
777
778 self.__logger.debug("Unhandled stream error: condition: %s %r"
779 % (err.get_condition().name,err.serialize()))
780
782 """Check "to" attribute of received stream header.
783
784 :return: `to` if it is equal to `self.me`, None otherwise.
785
786 Should be overriden in derived classes which require other logic
787 for handling that attribute."""
788 if to!=self.me:
789 return None
790 return to
791
793 """Generate a random and unique stream ID.
794
795 :return: the id string generated."""
796 return "%i-%i-%s" % (os.getpid(),time.time(),str(random.random())[2:])
797
799 """Process incoming <stream:features/> element.
800
801 [initiating entity only]
802
803 The received features node is available in `self.features`."""
804 ctxt = self.doc_in.xpathNewContext()
805 ctxt.setContextNode(self.features)
806 ctxt.xpathRegisterNs("stream",STREAM_NS)
807 ctxt.xpathRegisterNs("bind",BIND_NS)
808 bind_n=None
809 try:
810 bind_n=ctxt.xpathEval("bind:bind")
811 finally:
812 ctxt.xpathFreeContext()
813
814 if self.authenticated:
815 if bind_n:
816 self.bind(self.me.resource)
817 else:
818 self.state_change("authorized",self.me)
819
820 - def bind(self,resource):
839
841 """Handle resource binding success.
842
843 [initiating entity only]
844
845 :Parameters:
846 - `stanza`: <iq type="result"/> stanza received.
847
848 Set `self.me` to the full JID negotiated."""
849 jid_n=stanza.xpath_eval("bind:bind/bind:jid",{"bind":BIND_NS})
850 if jid_n:
851 self.me=JID(jid_n[0].getContent().decode("utf-8"))
852 self.state_change("authorized",self.me)
853
855 """Handle resource binding success.
856
857 [initiating entity only]
858
859 :raise FatalStreamError:"""
860 raise FatalStreamError,"Resource binding failed"
861
863 """Check if stream is connected.
864
865 :return: True if stream connection is active."""
866 if self.doc_in and self.doc_out and not self.eof:
867 return True
868 else:
869 return False
870
871
872