root/branches/gajim_0.11.1/src/common/proxy65_manager.py

Revision 8839, 7.6 kB (checked in by asterix, 14 months ago)

<activate> tag must contain a jid according to proxy65 XEP.

Line 
1##
2## Copyright (C) 2006 Gajim Team
3##
4## Contributors for this file:
5##      - 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
9## by the Free Software Foundation; version 2 only.
10##
11## This program is distributed in the hope that it will be useful,
12## but WITHOUT ANY WARRANTY; without even the implied warranty of
13## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14## GNU General Public License for more details.
15##
16import socket 
17import struct
18import errno
19
20import common.xmpp
21from common import gajim
22from common import helpers
23from socks5 import Socks5
24from common.xmpp.idlequeue import IdleObject
25
26S_INITIAL = 0
27S_STARTED = 1
28S_RESOLVED = 2
29S_ACTIVATED = 3
30S_FINISHED = 4
31
32CONNECT_TIMEOUT = 20
33
34class Proxy65Manager:
35        ''' keep records for file transfer proxies. Each time account
36        establishes a connection to its server call proxy65manger.resolve(proxy)
37        for every proxy that is convigured within the account. The class takes
38        care to resolve and test each proxy only once.'''
39        def __init__(self, idlequeue):
40                # dict {proxy: proxy properties}
41                self.idlequeue = idlequeue
42                self.proxies = {}
43                # dict {account: proxy} default proxy for account
44                self.default_proxies = {}
45       
46        def resolve(self, proxy, connection, default = None):
47                ''' start '''
48                if self.proxies.has_key(proxy):
49                        resolver = self.proxies[proxy]
50                else:
51                        # proxy is being ressolved for the first time
52                        resolver = ProxyResolver(proxy)
53                        self.proxies[proxy] = resolver
54                        resolver.add_connection(connection)
55                if default:
56                        # add this proxy as default for account
57                        self.default_proxies[default] = proxy
58       
59        def disconnect(self, connection):
60                for resolver in self.proxies.values():
61                        resolver.disconnect(connection)
62       
63        def resolve_result(self, proxy, query):
64                if not self.proxies.has_key(proxy):
65                        return
66                jid = None
67                for item in query.getChildren():
68                        if item.getName() == 'streamhost':
69                                host = item.getAttr('host')
70                                port = item.getAttr('port')
71                                jid = item.getAttr('jid')
72                                self.proxies[proxy].resolve_result(host, port, jid)
73                                # we can have only one streamhost
74                                raise common.xmpp.NodeProcessed
75       
76        def error_cb(self, proxy, query):
77                sid = query.getAttr('sid')
78                for resolver in self.proxies.values():
79                        if resolver.sid == sid:
80                                resolver.keep_conf()
81                                break
82       
83        def get_default_for_name(self, account):
84                if self.default_proxies.has_key(account):
85                        return self.default_proxies[account]
86       
87        def get_proxy(self, proxy, account):
88                if self.proxies.has_key(proxy):
89                        resolver = self.proxies[proxy]
90                        if resolver.state == S_FINISHED:
91                                return (resolver.host, resolver.port, resolver.jid)
92                return (None, 0, None)
93
94class ProxyResolver:
95        def resolve_result(self, host, port, jid):
96                ''' test if host has a real proxy65 listening on port '''
97                self.host = unicode(host)
98                self.port = int(port)
99                self.jid = unicode(jid)
100                self.state = S_RESOLVED
101                self.host_tester = HostTester(self.host, self.port, self.jid, 
102                                self._on_connect_success, self._on_connect_failure)
103                self.host_tester.connect()
104       
105        def _on_connect_success(self):
106                iq = common.xmpp.Protocol(name = 'iq', to = self.jid, typ = 'set')
107                query = iq.setTag('query')
108                query.setNamespace(common.xmpp.NS_BYTESTREAM)
109                query.setAttr('sid',  self.sid)
110               
111                activate = query.setTag('activate')
112                activate.setData(self.jid + "/" + self.sid)
113
114                if self.active_connection:
115                        self.active_connection.send(iq)
116                        self.state = S_ACTIVATED
117                else:
118                        self.state = S_INITIAL
119               
120        def keep_conf(self):
121                self.state = S_FINISHED
122       
123        def _on_connect_failure(self):
124                self.state = S_FINISHED
125                self.host = None
126                self.port = 0
127                self.jid = None
128       
129        def disconnect(self, connection):
130                if self.host_tester:
131                        self.host_tester.disconnect()
132                        self.host_tester = None
133                try:
134                        self.connections.remove(connection)
135                except ValueError:
136                        pass
137                if connection == self.active_connection:
138                        self.active_connection = None
139                        if self.state != S_FINISHED:
140                                self.state = S_INITIAL
141                                self.try_next_connection()
142       
143        def try_next_connection(self):
144                ''' try to resolve proxy with the next possible connection '''
145                if self.connections:
146                        connection = self.connections.pop(0)
147                        self.start_resolve(connection)
148       
149        def add_connection(self, connection):
150                ''' add a new connection in case the first fails '''
151                self.connections.append(connection)
152                if self.state == S_INITIAL:
153                        self.start_resolve(connection)
154       
155        def start_resolve(self, connection):
156                ''' request network address from proxy '''
157                self.state = S_STARTED
158                self.active_connection = connection
159                iq = common.xmpp.Protocol(name = 'iq', to = self.proxy, typ = 'get')
160                query = iq.setTag('query')
161                query.setNamespace(common.xmpp.NS_BYTESTREAM)
162                connection.send(iq)
163       
164        def __init__(self, proxy):
165                self.proxy = proxy
166                self.state = S_INITIAL
167                self.active_connection = None
168                self.connections = []
169                self.host_tester = None
170                self.jid = None
171                self.host = None
172                self.port = None
173                self.sid = helpers.get_random_string_16()
174               
175class HostTester(Socks5, IdleObject):
176        ''' fake proxy tester. '''
177        def __init__(self, host, port, jid, on_success, on_failure):
178                ''' try to establish and auth to proxy at (host, port)
179                call on_success, or on_failure according to the result'''
180                self.host = host
181                self.port = port
182                self.jid = jid
183                self.on_success = on_success
184                self.on_failure = on_failure
185                self._sock = None
186                self.file_props = {}
187                Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
188       
189        def connect(self):
190                ''' create the socket and plug it to the idlequeue '''
191                if self.host is None:
192                        self.on_failure()
193                        return None
194                self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
195                self._sock.setblocking(False)
196                self.fd = self._sock.fileno()
197                self.state = 0 # about to be connected
198                gajim.idlequeue.plug_idle(self, True, False)
199                self.do_connect()
200                self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
201                return None
202               
203        def read_timeout(self):
204                self.idlequeue.remove_timeout(self.fd)
205                self.pollend()
206               
207        def pollend(self):
208                self.disconnect()
209                self.on_failure()
210       
211        def pollout(self):
212                self.idlequeue.remove_timeout(self.fd)
213                if self.state == 0:
214                        self.do_connect()
215                        return
216                elif self.state == 1: # send initially: version and auth types
217                        data = self._get_auth_buff()
218                        self.send_raw(data)
219                else:
220                        return
221                self.state += 1
222                # unplug and plug for reading
223                gajim.idlequeue.plug_idle(self, False, True)
224                gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
225       
226        def pollin(self):
227                self.idlequeue.remove_timeout(self.fd)
228                if self.state == 2:
229                        self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
230                        # begin negotiation. on success 'address' != 0
231                        buff = self.receive()
232                        if buff == '':
233                                # end connection
234                                self.pollend()
235                                return
236                        # read auth response
237                        if buff is None or len(buff) != 2:
238                                return None
239                        version, method = struct.unpack('!BB', buff[:2])
240                        if version != 0x05 or method == 0xff:
241                                self.pollend()
242                        self.disconnect()
243                        self.on_success()
244                else:
245                        self.disconnect()
246       
247        def do_connect(self):
248                try:
249                        self._sock.connect((self.host, self.port))
250                        self._sock.setblocking(False)
251                        self._send=self._sock.send
252                        self._recv=self._sock.recv
253                except Exception, ee:
254                        (errnum, errstr) = ee
255                        if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
256                                # still trying to connect
257                                return
258                        # win32 needs this
259                        if errnum not in (0, 10056, errno.EISCONN):
260                                # connection failed
261                                self.on_failure()
262                                return
263                        # socket is already connected
264                        self._sock.setblocking(False)
265                        self._send=self._sock.send
266                        self._recv=self._sock.recv
267                self.buff = ''
268                self.state = 1 # connected
269                self.idlequeue.plug_idle(self, True, False)
270                return 
271               
Note: See TracBrowser for help on using the browser.