| 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 | ## |
|---|
| 16 | import socket |
|---|
| 17 | import struct |
|---|
| 18 | import errno |
|---|
| 19 | |
|---|
| 20 | import common.xmpp |
|---|
| 21 | from common import gajim |
|---|
| 22 | from common import helpers |
|---|
| 23 | from socks5 import Socks5 |
|---|
| 24 | from common.xmpp.idlequeue import IdleObject |
|---|
| 25 | |
|---|
| 26 | S_INITIAL = 0 |
|---|
| 27 | S_STARTED = 1 |
|---|
| 28 | S_RESOLVED = 2 |
|---|
| 29 | S_ACTIVATED = 3 |
|---|
| 30 | S_FINISHED = 4 |
|---|
| 31 | |
|---|
| 32 | CONNECT_TIMEOUT = 20 |
|---|
| 33 | |
|---|
| 34 | class 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 | |
|---|
| 94 | class 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 | |
|---|
| 175 | class 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 | |
|---|