Package common :: Module proxy65_manager
[hide private]
[frames] | no frames]

Source Code for Module common.proxy65_manager

  1  # -*- coding:utf-8 -*- 
  2  ## src/common/proxy65_manager.py 
  3  ## 
  4  ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> 
  5  ##                    Jean-Marie Traissard <jim AT lapin.org> 
  6  ## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org> 
  7  ## 
  8  ## This file is part of Gajim. 
  9  ## 
 10  ## Gajim is free software; you can redistribute it and/or modify 
 11  ## it under the terms of the GNU General Public License as published 
 12  ## by the Free Software Foundation; version 3 only. 
 13  ## 
 14  ## Gajim is distributed in the hope that it will be useful, 
 15  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 16  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 17  ## GNU General Public License for more details. 
 18  ## 
 19  ## You should have received a copy of the GNU General Public License 
 20  ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. 
 21  ## 
 22   
 23  import socket 
 24  import struct 
 25  import errno 
 26  import logging 
 27  log = logging.getLogger('gajim.c.proxy65_manager') 
 28   
 29  import common.xmpp 
 30  from common import gajim 
 31  from common import helpers 
 32  from socks5 import Socks5 
 33  from common.xmpp.idlequeue import IdleObject 
 34   
 35  S_INITIAL = 0 
 36  S_STARTED = 1 
 37  S_RESOLVED = 2 
 38  S_ACTIVATED = 3 
 39  S_FINISHED = 4 
 40   
 41  CONNECT_TIMEOUT = 20 
 42   
43 -class Proxy65Manager:
44 """ 45 Keep records for file transfer proxies. Each time account establishes a 46 connection to its server call proxy65manger.resolve(proxy) for every proxy 47 that is convigured within the account. The class takes care to resolve and 48 test each proxy only once 49 """ 50
51 - def __init__(self, idlequeue):
52 # dict {proxy: proxy properties} 53 self.idlequeue = idlequeue 54 self.proxies = {} 55 # dict {account: proxy} default proxy for account 56 self.default_proxies = {}
57
58 - def resolve(self, proxy, connection, sender_jid, default=None):
59 """ 60 Start 61 """ 62 if proxy in self.proxies: 63 resolver = self.proxies[proxy] 64 else: 65 # proxy is being ressolved for the first time 66 resolver = ProxyResolver(proxy, sender_jid) 67 self.proxies[proxy] = resolver 68 resolver.add_connection(connection) 69 if default: 70 # add this proxy as default for account 71 self.default_proxies[default] = proxy
72
73 - def disconnect(self, connection):
74 for resolver in self.proxies.values(): 75 resolver.disconnect(connection)
76
77 - def resolve_result(self, proxy, query):
78 if proxy not in self.proxies: 79 return 80 jid = None 81 for item in query.getChildren(): 82 if item.getName() == 'streamhost': 83 host = item.getAttr('host') 84 port = item.getAttr('port') 85 jid = item.getAttr('jid') 86 if not host or not port or not jid: 87 self.proxies[proxy]._on_connect_failure() 88 self.proxies[proxy].resolve_result(host, port, jid) 89 # we can have only one streamhost 90 raise common.xmpp.NodeProcessed
91
92 - def error_cb(self, proxy, query):
93 sid = query.getAttr('sid') 94 for resolver in self.proxies.values(): 95 if resolver.sid == sid: 96 resolver.keep_conf() 97 break
98
99 - def get_default_for_name(self, account):
100 if account in self.default_proxies: 101 return self.default_proxies[account]
102
103 - def get_proxy(self, proxy, account):
104 if proxy in self.proxies: 105 resolver = self.proxies[proxy] 106 if resolver.state == S_FINISHED: 107 return (resolver.host, resolver.port, resolver.jid) 108 return (None, 0, None)
109
110 -class ProxyResolver:
111 - def resolve_result(self, host, port, jid):
112 """ 113 Test if host has a real proxy65 listening on port 114 """ 115 self.host = str(host) 116 self.port = int(port) 117 self.jid = unicode(jid) 118 self.state = S_INITIAL 119 log.info('start resolving %s:%s' % (self.host, self.port)) 120 self.receiver_tester = ReceiverTester(self.host, self.port, self.jid, 121 self.sid, self.sender_jid, self._on_receiver_success, 122 self._on_connect_failure) 123 self.receiver_tester.connect()
124
125 - def _on_receiver_success(self):
126 log.debug('Receiver successfully connected %s:%s' % (self.host, 127 self.port)) 128 self.host_tester = HostTester(self.host, self.port, self.jid, 129 self.sid, self.sender_jid, self._on_connect_success, 130 self._on_connect_failure) 131 self.host_tester.connect()
132
133 - def _on_connect_success(self):
134 log.debug('Host successfully connected %s:%s' % (self.host, self.port)) 135 iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set') 136 query = iq.setTag('query') 137 query.setNamespace(common.xmpp.NS_BYTESTREAM) 138 query.setAttr('sid', self.sid) 139 140 activate = query.setTag('activate') 141 activate.setData('test@gajim.org/test2') 142 143 if self.active_connection: 144 log.debug('Activating bytestream on %s:%s' % (self.host, self.port)) 145 self.active_connection.SendAndCallForResponse(iq, 146 self._result_received) 147 self.state = S_ACTIVATED 148 else: 149 self.state = S_INITIAL
150
151 - def _result_received(self, data):
152 self.disconnect(self.active_connection) 153 if data.getType() == 'result': 154 self.keep_conf() 155 else: 156 self._on_connect_failure()
157
158 - def keep_conf(self):
159 log.debug('Bytestream activated %s:%s' % (self.host, self.port)) 160 self.state = S_FINISHED
161
162 - def _on_connect_failure(self):
163 self.state = S_FINISHED 164 self.host = None 165 self.port = 0 166 self.jid = None
167
168 - def disconnect(self, connection):
169 if self.host_tester: 170 self.host_tester.disconnect() 171 self.host_tester = None 172 if self.receiver_tester: 173 self.receiver_tester.disconnect() 174 self.receiver_tester = None 175 try: 176 self.connections.remove(connection) 177 except ValueError: 178 pass 179 if connection == self.active_connection: 180 self.active_connection = None 181 if self.state != S_FINISHED: 182 self.state = S_INITIAL 183 self.try_next_connection()
184
185 - def try_next_connection(self):
186 """ 187 Try to resolve proxy with the next possible connection 188 """ 189 if self.connections: 190 connection = self.connections.pop(0) 191 self.start_resolve(connection)
192
193 - def add_connection(self, connection):
194 """ 195 Add a new connection in case the first fails 196 """ 197 self.connections.append(connection) 198 if self.state == S_INITIAL: 199 self.start_resolve(connection)
200
201 - def start_resolve(self, connection):
202 """ 203 Request network address from proxy 204 """ 205 self.state = S_STARTED 206 self.active_connection = connection 207 iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get') 208 query = iq.setTag('query') 209 query.setNamespace(common.xmpp.NS_BYTESTREAM) 210 connection.send(iq)
211
212 - def __init__(self, proxy, sender_jid):
213 self.proxy = proxy 214 self.state = S_INITIAL 215 self.active_connection = None 216 self.connections = [] 217 self.host_tester = None 218 self.receiver_tester = None 219 self.jid = None 220 self.host = None 221 self.port = None 222 self.sid = helpers.get_random_string_16() 223 self.sender_jid = sender_jid
224
225 -class HostTester(Socks5, IdleObject):
226 """ 227 Fake proxy tester 228 """ 229
230 - def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
231 """ 232 Try to establish and auth to proxy at (host, port) 233 234 Calls on_success, or on_failure according to the result. 235 """ 236 self.host = host 237 self.port = port 238 self.jid = jid 239 self.on_success = on_success 240 self.on_failure = on_failure 241 self._sock = None 242 self.file_props = {'is_a_proxy': True, 243 'proxy_sender': sender_jid, 244 'proxy_receiver': 'test@gajim.org/test2'} 245 Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) 246 self.sid = sid
247
248 - def connect(self):
249 """ 250 Create the socket and plug it to the idlequeue 251 """ 252 if self.host is None: 253 self.on_failure() 254 return None 255 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 256 self._sock.setblocking(False) 257 self.fd = self._sock.fileno() 258 self.state = 0 # about to be connected 259 gajim.idlequeue.plug_idle(self, True, False) 260 self.do_connect() 261 self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) 262 return None
263
264 - def read_timeout(self):
265 self.idlequeue.remove_timeout(self.fd) 266 self.pollend()
267
268 - def pollend(self):
269 self.disconnect() 270 self.on_failure()
271
272 - def pollout(self):
273 self.idlequeue.remove_timeout(self.fd) 274 if self.state == 0: 275 self.do_connect() 276 return 277 elif self.state == 1: # send initially: version and auth types 278 data = self._get_auth_buff() 279 self.send_raw(data) 280 else: 281 return 282 self.state += 1 283 # unplug and plug for reading 284 gajim.idlequeue.plug_idle(self, False, True) 285 gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
286
287 - def pollin(self):
288 self.idlequeue.remove_timeout(self.fd) 289 if self.state == 2: 290 self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) 291 # begin negotiation. on success 'address' != 0 292 buff = self.receive() 293 if buff == '': 294 # end connection 295 self.pollend() 296 return 297 # read auth response 298 if buff is None or len(buff) != 2: 299 return None 300 version, method = struct.unpack('!BB', buff[:2]) 301 if version != 0x05 or method == 0xff: 302 self.pollend() 303 return 304 data = self._get_request_buff(self._get_sha1_auth()) 305 self.send_raw(data) 306 self.state += 1 307 log.debug('Host authenticating to %s:%s' % (self.host, self.port)) 308 elif self.state == 3: 309 log.debug('Host authenticated to %s:%s' % (self.host, self.port)) 310 self.on_success() 311 self.disconnect() 312 self.state += 1 313 else: 314 assert False, 'unexpected state: %d' % self.state
315
316 - def do_connect(self):
317 try: 318 self._sock.connect((self.host, self.port)) 319 self._sock.setblocking(False) 320 log.debug('Host Connecting to %s:%s' % (self.host, self.port)) 321 self._send = self._sock.send 322 self._recv = self._sock.recv 323 except Exception, ee: 324 errnum = ee[0] 325 # 56 is for freebsd 326 if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): 327 # still trying to connect 328 return 329 # win32 needs this 330 if errnum not in (0, 10056, errno.EISCONN): 331 # connection failed 332 self.on_failure() 333 return 334 # socket is already connected 335 self._sock.setblocking(False) 336 self._send = self._sock.send 337 self._recv = self._sock.recv 338 self.buff = '' 339 self.state = 1 # connected 340 log.debug('Host connected to %s:%s' % (self.host, self.port)) 341 self.idlequeue.plug_idle(self, True, False) 342 return
343
344 -class ReceiverTester(Socks5, IdleObject):
345 """ 346 Fake proxy tester 347 """ 348
349 - def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
350 """ 351 Try to establish and auth to proxy at (host, port) 352 353 Call on_success, or on_failure according to the result. 354 """ 355 self.host = host 356 self.port = port 357 self.jid = jid 358 self.on_success = on_success 359 self.on_failure = on_failure 360 self._sock = None 361 self.file_props = {'is_a_proxy': True, 362 'proxy_sender': sender_jid, 363 'proxy_receiver': 'test@gajim.org/test2'} 364 Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) 365 self.sid = sid
366
367 - def connect(self):
368 """ 369 Create the socket and plug it to the idlequeue 370 """ 371 if self.host is None: 372 self.on_failure() 373 return None 374 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 375 self._sock.setblocking(False) 376 self.fd = self._sock.fileno() 377 self.state = 0 # about to be connected 378 gajim.idlequeue.plug_idle(self, True, False) 379 self.do_connect() 380 self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) 381 return None
382
383 - def read_timeout(self):
384 self.idlequeue.remove_timeout(self.fd) 385 self.pollend()
386
387 - def pollend(self):
388 self.disconnect() 389 self.on_failure()
390
391 - def pollout(self):
392 self.idlequeue.remove_timeout(self.fd) 393 if self.state == 0: 394 self.do_connect() 395 return 396 elif self.state == 1: # send initially: version and auth types 397 data = self._get_auth_buff() 398 self.send_raw(data) 399 else: 400 return 401 self.state += 1 402 # unplug and plug for reading 403 gajim.idlequeue.plug_idle(self, False, True) 404 gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
405
406 - def pollin(self):
407 self.idlequeue.remove_timeout(self.fd) 408 if self.state in (2, 3): 409 self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) 410 # begin negotiation. on success 'address' != 0 411 buff = self.receive() 412 if buff == '': 413 # end connection 414 self.pollend() 415 return 416 if self.state == 2: 417 # read auth response 418 if buff is None or len(buff) != 2: 419 return None 420 version, method = struct.unpack('!BB', buff[:2]) 421 if version != 0x05 or method == 0xff: 422 self.pollend() 423 return 424 log.debug('Receiver authenticating to %s:%s' % (self.host, self.port)) 425 data = self._get_request_buff(self._get_sha1_auth()) 426 self.send_raw(data) 427 self.state += 1 428 elif self.state == 3: 429 # read connect response 430 if buff is None or len(buff) < 2: 431 return None 432 version, reply = struct.unpack('!BB', buff[:2]) 433 if version != 0x05 or reply != 0x00: 434 self.pollend() 435 return 436 log.debug('Receiver authenticated to %s:%s' % (self.host, self.port)) 437 self.on_success() 438 self.disconnect() 439 self.state += 1 440 else: 441 assert False, 'unexpected state: %d' % self.state
442
443 - def do_connect(self):
444 try: 445 self._sock.setblocking(False) 446 self._sock.connect((self.host, self.port)) 447 log.debug('Receiver Connecting to %s:%s' % (self.host, self.port)) 448 self._send = self._sock.send 449 self._recv = self._sock.recv 450 except Exception, ee: 451 errnum = ee[0] 452 # 56 is for freebsd 453 if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): 454 # still trying to connect 455 return 456 # win32 needs this 457 if errnum not in (0, 10056, errno.EISCONN): 458 # connection failed 459 self.on_failure() 460 return 461 # socket is already connected 462 self._sock.setblocking(False) 463 self._send = self._sock.send 464 self._recv = self._sock.recv 465 self.buff = '' 466 self.state = 1 # connected 467 log.debug('Receiver connected to %s:%s' % (self.host, self.port)) 468 self.idlequeue.plug_idle(self, True, False)
469