1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 __version__="$Id"
17
18 """
19 When your handler is called it is getting the session instance as the first argument.
20 This is the difference from xmpppy 0.1 where you got the "Client" instance.
21 With Session class you can have "multi-session" client instead of having
22 one client for each connection. Is is specifically important when you are
23 writing the server.
24 """
25
26 from protocol import *
27
28
29 SOCKET_UNCONNECTED =0
30 SOCKET_ALIVE =1
31 SOCKET_DEAD =2
32
33 STREAM__NOT_OPENED =1
34 STREAM__OPENED =2
35 STREAM__CLOSING =3
36 STREAM__CLOSED =4
37
38 SESSION_NOT_AUTHED =1
39 SESSION_AUTHED =2
40 SESSION_BOUND =3
41 SESSION_OPENED =4
42 SESSION_CLOSED =5
43
45 """
46 The Session class instance is used for storing all session-related info like
47 credentials, socket/xml stream/session state flags, roster items (in case of
48 client type connection) etc.
49 Session object have no means of discovering is any info is ready to be read.
50 Instead you should use poll() (recomended) or select() methods for this purpose.
51 Session can be one of two types: 'server' and 'client'. 'server' session handles
52 inbound connection and 'client' one used to create an outbound one.
53 Session instance have multitude of internal attributes. The most imporant is the 'peer' one.
54 It is set once the peer is authenticated (client).
55 """
56 - def __init__(self,socket,owner,xmlns=None,peer=None):
57 """ When the session is created it's type (client/server) is determined from the beginning.
58 socket argument is the pre-created socket-like object.
59 It must have the following methods: send, recv, fileno, close.
60 owner is the 'master' instance that have Dispatcher plugged into it and generally
61 will take care about all session events.
62 xmlns is the stream namespace that will be used. Client must set this argument
63 If server sets this argument than stream will be dropped if opened with some another namespace.
64 peer is the name of peer instance. This is the flag that differentiates client session from
65 server session. Client must set it to the name of the server that will be connected, server must
66 leave this argument alone.
67 """
68 self.xmlns=xmlns
69 if peer:
70 self.TYP='client'
71 self.peer=peer
72 self._socket_state=SOCKET_UNCONNECTED
73 else:
74 self.TYP='server'
75 self.peer=None
76 self._socket_state=SOCKET_ALIVE
77 self._sock=socket
78 self._send=socket.send
79 self._recv=socket.recv
80 self.fileno=socket.fileno
81 self._registered=0
82
83 self.Dispatcher=owner.Dispatcher
84 self.DBG_LINE='session'
85 self.DEBUG=owner.Dispatcher.DEBUG
86 self._expected={}
87 self._owner=owner
88 if self.TYP=='server': self.ID=`random.random()`[2:]
89 else: self.ID=None
90
91 self.sendbuffer=''
92 self._stream_pos_queued=None
93 self._stream_pos_sent=0
94 self.deliver_key_queue=[]
95 self.deliver_queue_map={}
96 self.stanza_queue=[]
97
98 self._session_state=SESSION_NOT_AUTHED
99 self.waiting_features=[]
100 for feature in [NS_TLS,NS_SASL,NS_BIND,NS_SESSION]:
101 if feature in owner.features: self.waiting_features.append(feature)
102 self.features=[]
103 self.feature_in_process=None
104 self.slave_session=None
105 self.StartStream()
106
122
124 """ Reads all pending incoming data.
125 Raises IOError on disconnection.
126 Blocks until at least one byte is read."""
127
128 try: received = self._recv(10240)
129 except: received = ''
130
131 if len(received):
132 self.DEBUG(`self.fileno()`+' '+received,'got')
133 else:
134 self.DEBUG('Socket error while receiving data','warn')
135 self.set_socket_state(SOCKET_DEAD)
136 raise IOError("Peer disconnected")
137 return received
138
140 """ Put chunk into "immidiatedly send" queue.
141 Should only be used for auth/TLS stuff and like.
142 If you just want to shedule regular stanza for delivery use enqueue method.
143 """
144 if isinstance(chunk,Node): chunk = chunk.__str__().encode('utf-8')
145 elif type(chunk)==type(u''): chunk = chunk.encode('utf-8')
146 self.enqueue(chunk)
147
149 """ Takes Protocol instance as argument.
150 Puts stanza into "send" fifo queue. Items into the send queue are hold until
151 stream authenticated. After that this method is effectively the same as "sendnow" method."""
152 if isinstance(stanza,Protocol):
153 self.stanza_queue.append(stanza)
154 else: self.sendbuffer+=stanza
155 if self._socket_state>=SOCKET_ALIVE: self.push_queue()
156
158 """ If stream is authenticated than move items from "send" queue to "immidiatedly send" queue.
159 Else if the stream is failed then return all queued stanzas with error passed as argument.
160 Otherwise do nothing."""
161
162
163 if self._stream_state>=STREAM__CLOSED or self._socket_state>=SOCKET_DEAD:
164 self._owner.deactivatesession(self)
165 for key in self.deliver_key_queue:
166 self._dispatch(Error(self.deliver_queue_map[key],failreason),trusted=1)
167 for stanza in self.stanza_queue:
168 self._dispatch(Error(stanza,failreason),trusted=1)
169 self.deliver_queue_map,self.deliver_key_queue,self.stanza_queue={},[],[]
170 return
171 elif self._session_state>=SESSION_AUTHED:
172
173 for stanza in self.stanza_queue:
174 txt=stanza.__str__().encode('utf-8')
175 self.sendbuffer+=txt
176 self._stream_pos_queued+=len(txt)
177 self.deliver_queue_map[self._stream_pos_queued]=stanza
178 self.deliver_key_queue.append(self._stream_pos_queued)
179 self.stanza_queue=[]
180
181
183 """ Put the "immidiatedly send" queue content on the wire. Blocks until at least one byte sent."""
184 if self.sendbuffer:
185 try:
186
187 sent=self._send(self.sendbuffer)
188 except:
189
190 self.set_socket_state(SOCKET_DEAD)
191 self.DEBUG("Socket error while sending data",'error')
192 return self.terminate_stream()
193 self.DEBUG(`self.fileno()`+' '+self.sendbuffer[:sent],'sent')
194 self._stream_pos_sent+=sent
195 self.sendbuffer=self.sendbuffer[sent:]
196 self._stream_pos_delivered=self._stream_pos_sent
197 while self.deliver_key_queue and self._stream_pos_delivered>self.deliver_key_queue[0]:
198 del self.deliver_queue_map[self.deliver_key_queue[0]]
199 self.deliver_key_queue.remove(self.deliver_key_queue[0])
200
201
203 """ This is callback that is used to pass the received stanza forth to owner's dispatcher
204 _if_ the stream is authorised. Otherwise the stanza is just dropped.
205 The 'trusted' argument is used to emulate stanza receive.
206 This method is used internally.
207 """
208 self._owner.packets+=1
209 print self._owner.packets
210 if self._stream_state==STREAM__OPENED or trusted:
211 self.DEBUG(stanza.__str__(),'dispatch')
212 stanza.trusted=trusted
213 return self.Dispatcher.dispatch(stanza,self)
214
216 """ This callback is used to detect the stream namespace of incoming stream. Used internally. """
217 if not attrs.has_key('id') or not attrs['id']:
218 return self.terminate_stream(STREAM_INVALID_XML)
219 self.ID=attrs['id']
220 if not attrs.has_key('version'): self._owner.Dialback(self)
221
262
267
269 """ Declare some feature as illegal. Illegal features can not be used.
270 Example: BIND feature becomes illegal after Non-SASL auth. """
271 if feature in self.waiting_features: self.waiting_features.remove(feature)
272
283
307
316
318 """ Declare some feature as "negotiating now" to prevent other features from start negotiating. """
319 if self.feature_in_process: raise "Starting feature %s over %s !"%(f,self.feature_in_process)
320 self.feature_in_process=f
321
323 """ Declare some feature as "negotiated" to allow other features start negotiating. """
324 if self.feature_in_process<>f: raise "Stopping feature %s instead of %s !"%(f,self.feature_in_process)
325 self.feature_in_process=None
326
328 """ Change the underlaying socket state.
329 Socket starts with SOCKET_UNCONNECTED state
330 and then proceeds (possibly) to SOCKET_ALIVE
331 and then to SOCKET_DEAD """
332 if self._socket_state<newstate: self._socket_state=newstate
333
335 """ Change the session state.
336 Session starts with SESSION_NOT_AUTHED state
337 and then comes through
338 SESSION_AUTHED, SESSION_BOUND, SESSION_OPENED and SESSION_CLOSED states.
339 """
340 if self._session_state<newstate:
341 if self._session_state<SESSION_AUTHED and \
342 newstate>=SESSION_AUTHED: self._stream_pos_queued=self._stream_pos_sent
343 self._session_state=newstate
344
346 """ Change the underlaying XML stream state
347 Stream starts with STREAM__NOT_OPENED and then proceeds with
348 STREAM__OPENED, STREAM__CLOSING and STREAM__CLOSED states.
349 Note that some features (like TLS and SASL)
350 requires stream re-start so this state can have non-linear changes. """
351 if self._stream_state<newstate: self._stream_state=newstate
352