root/branches/gajim_0.9/src/common/socks5.py

Revision 4700, 27.8 kB (checked in by asterix, 3 years ago)

change copyright from "Gajim Team" to real people

  • Property svn:eol-style set to LF
Line 
1
2##      common/xmpp/socks5.py
3##
4## Contributors for this file:
5##      - Yann Le Boulanger <asterix@lagaule.org>
6##      - Nikos Kouremenos <nkour@jabber.org>
7##      - Dimitur Kirov <dkirov@gmail.com>
8##
9## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
10##                         Vincent Hanquez <tab@snarc.org>
11## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
12##                    Vincent Hanquez <tab@snarc.org>
13##                    Nikos Kouremenos <nkour@jabber.org>
14##                    Dimitur Kirov <dkirov@gmail.com>
15##                    Travis Shirk <travis@pobox.com>
16##                    Norman Rasmussen <norman@rasmussen.co.za>
17##
18## This program is free software; you can redistribute it and/or modify
19## it under the terms of the GNU General Public License as published
20## by the Free Software Foundation; version 2 only.
21##
22## This program is distributed in the hope that it will be useful,
23## but WITHOUT ANY WARRANTY; without even the implied warranty of
24## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25## GNU General Public License for more details.
26##
27
28
29import socket
30import select
31import os
32import struct
33import sha
34import time
35
36from errno import EWOULDBLOCK
37from errno import ENOBUFS
38from errno import EINTR
39
40MAX_BUFF_LEN = 65536
41
42class SocksQueue:
43        ''' queue for all file requests objects '''
44        def __init__(self, complete_transfer_cb = None, progress_transfer_cb = None):
45                self.connected = 0
46                self.readers = {}
47                self.files_props = {}
48                self.senders = {}
49                self.idx = 1
50                self.listener = None
51                self.sha_handlers = {}
52                self.complete_transfer_cb = complete_transfer_cb
53                self.progress_transfer_cb = progress_transfer_cb
54                self.on_success = None
55                self.on_failure = None
56               
57        def start_listener(self, host, port, sha_str, sha_handler, sid):
58                self.sha_handlers[sha_str] = (sha_handler, sid)
59                if self.listener == None:
60                        self.listener = Socks5Listener(host, port)
61                        self.listener.bind()
62                        if self.listener.started is False:
63                                self.listener = None
64                                import sys
65                                sys.stderr.write('\n\n\n========================================\
66========================\nUnable to bind to port %s. \nMaybe you have another \
67running instance of Gajim. \nFile Transfer will be canceled.\n==================\
68==============================================\n\n\n' % port)
69                                return None
70                        self.connected += 1
71                return self.listener
72               
73        def send_success_reply(self, file_props, streamhost):
74                if file_props.has_key('streamhost-used') and \
75                        file_props['streamhost-used'] is True:
76                                if file_props.has_key('proxyhosts'):
77                                        for proxy in file_props['proxyhosts']:
78                                                if proxy == streamhost:
79                                                        self.on_success(streamhost)
80                                                        return 2
81                                return 0
82                if file_props.has_key('streamhosts'):
83                        for host in file_props['streamhosts']:
84                                if streamhost['state'] == 1:
85                                        return 0
86                        streamhost['state'] = 1
87                        self.on_success(streamhost)
88                        return 1
89                return 0
90
91        def connect_to_hosts(self, account, sid, on_success = None, 
92                on_failure = None):
93                self.on_success = on_success
94                self.on_failure = on_failure
95                if not self.files_props.has_key(account):
96                        pass
97                        # FIXME ---- show error dialog
98                else:
99                        file_props = self.files_props[account][sid]
100                file_props['success_cb'] = on_success
101                file_props['failure_cb'] = on_failure
102               
103                # add streamhosts to the queue
104                for streamhost in file_props['streamhosts']:
105                        receiver = Socks5Receiver(streamhost, sid, file_props)
106                        self.add_receiver(account, receiver)
107                        streamhost['idx'] = receiver.queue_idx
108               
109        def _socket_connected(self, streamhost, file_props):
110                for host in file_props['streamhosts']:
111                        if host != streamhost and host.has_key('idx'):
112                                if host['state'] == 1:
113                                        self.remove_receiver(streamhost['idx'])
114                                        return
115                                else:
116                                        host['state'] = -1
117                                self.remove_receiver(host['idx'])
118               
119        def _connection_refused(self, streamhost, file_props, idx):
120                if file_props is None:
121                        return
122                streamhost['state'] = -1
123                self.remove_receiver(idx)
124                if file_props.has_key('streamhosts'):
125                        for host in file_props['streamhosts']:
126                                if host['state'] != -1:
127                                        return
128                if file_props.has_key('failure_cb') and file_props['failure_cb']:
129                        file_props['failure_cb'](streamhost['initiator'], streamhost['id'], 
130                                file_props['sid'], code = 404)
131               
132        def add_receiver(self, account, sock5_receiver):
133                ''' add new file request '''
134                self.readers[self.idx] = sock5_receiver
135                sock5_receiver.queue_idx = self.idx
136                sock5_receiver.queue = self
137                sock5_receiver.account = account
138                self.idx += 1
139                result = sock5_receiver.connect()
140                self.connected += 1
141                if result != None:
142                        result = sock5_receiver.main()
143                        self.process_result(result, sock5_receiver)
144                        return 1
145                return None
146               
147        def get_file_from_sender(self, file_props, account):
148                if file_props is None:
149                        return
150                        file_props['hash']
151                if file_props.has_key('hash') and \
152                        self.senders.has_key(file_props['hash']):
153                       
154                        sender = self.senders[file_props['hash']]
155                        sender.account = account
156                        result = get_file_contents(0)
157                        self.process_result(result, sender)
158                       
159        def result_sha(self, sha_str, idx):
160                if self.sha_handlers.has_key(sha_str):
161                        props = self.sha_handlers[sha_str]
162                        props[0](props[1], idx)
163        def activate_proxy(self, idx):
164                if not self.readers.has_key(idx):
165                        return
166                reader = self.readers[idx]
167                if reader.file_props['type'] != 's':
168                        return
169                if reader.state != 5:
170                        return
171                reader.state = 6
172                if reader.connected:
173                        reader.file_props['error'] = 0
174                        reader.file_props['disconnect_cb'] = reader.disconnect
175                        reader.file_props['started'] = True
176                        reader.file_props['completed'] = False
177                        reader.file_props['paused'] = False
178                        reader.file_props['stalled'] = False
179                        reader.file_props['elapsed-time'] = 0
180                        reader.file_props['last-time'] = time.time()
181                        reader.file_props['received-len'] = 0
182                        reader.pauses = 0
183                        result = reader.write_next()
184                        self.process_result(result, reader)
185       
186        def send_file(self, file_props, account):
187                if file_props.has_key('hash') and \
188                        self.senders.has_key(file_props['hash']):
189                        sender = self.senders[file_props['hash']]
190                        file_props['streamhost-used'] = True
191                        sender.account = account
192                        if file_props['type'] == 's':
193                                sender.file_props = file_props
194                                result = sender.send_file()
195                                self.process_result(result, sender)
196                        else:
197                                file_props['elapsed-time'] = 0
198                                file_props['last-time'] = time.time()
199                                file_props['received-len'] = 0
200                                sender.file_props = file_props
201                               
202        def add_file_props(self, account, file_props):
203                ''' file_prop to the dict of current file_props.
204                It is identified by account name and sid
205                '''
206                if file_props is None or \
207                        file_props.has_key('sid') is False:
208                        return
209                _id = file_props['sid']
210                if not self.files_props.has_key(account):
211                        self.files_props[account] = {}
212                self.files_props[account][_id] = file_props
213       
214        def remove_file_props(self, account, sid):
215                if self.files_props.has_key(account):
216                        fl_props = self.files_props[account]
217                        if fl_props.has_key(sid):
218                                del(fl_props[sid])
219               
220                if len(self.files_props) == 0:
221                        self.connected = 0
222               
223        def get_file_props(self, account, sid):
224                ''' get fil_prop by account name and session id '''
225                if self.files_props.has_key(account):
226                        fl_props = self.files_props[account]
227                        if fl_props.has_key(sid):
228                                return fl_props[sid]
229                return None
230
231        def process(self, timeout=0):
232                ''' Process all registered connection.
233                they can be receivers, senders and one listener
234                '''
235                if self.listener is not None:
236                        if self.listener.pending_connection():
237                                _sock = self.listener.accept_conn()
238                                sock_hash =  _sock.__hash__()
239                                if not self.senders.has_key(sock_hash):
240                                        self.senders[sock_hash] = Socks5Sender(sock_hash, self, 
241                                                _sock[0], _sock[1][0],  _sock[1][1])
242                                        self.connected += 1
243                                       
244                for idx in self.senders.keys():
245                        sender = self.senders[idx]
246                        if sender.connected:
247                                if sender.state < 5:
248                                        if sender.pending_data(timeout):
249                                                result = sender.main()
250                                                if sender.state == 4:
251                                                        self.result_sha(sender.sha_msg, idx)
252                                                if result is None:
253                                                        continue
254                                                if result == -1:
255                                                        sender.disconnect()
256                                elif sender.state == 5:
257                                        if sender.file_props is not None and \
258                                        sender.file_props['type'] == 'r':
259                                                result = sender.get_file_contents(0)
260                                                self.process_result(result, sender)
261                                elif sender.state == 7:
262                                        while True:
263                                                if sender.file_props['paused']:
264                                                        break
265                                                if not sender.connected:
266                                                        self.process_result(-1, sender)
267                                                        break
268                                                if sender.state == 8:
269                                                        self.remove_sender(idx)
270                                                        break
271                                                result = sender.write_next()
272                                                self.process_result(result, sender)
273                                                if result is None or result <= 0:
274                                                        break
275                                elif sender.state == 8:
276                                        self.remove_sender(idx)
277                        else:
278                                self.remove_sender(idx)
279                keys = self.readers.keys()
280                for idx in keys:
281                        if not self.readers.has_key(idx):
282                                continue
283                        receiver = self.readers[idx]
284                        if receiver.state == 0:
285                                res = receiver.do_connect()
286                                continue
287                        if receiver.connected:
288                                if receiver.file_props['paused']:
289                                        continue
290                                if receiver.state < 5:
291                                        pd = receiver.pending_data(0)
292                                        if pd:
293                                                result = receiver.main(0)
294                                                self.process_result(result, receiver)
295                                elif receiver.state == 5: # wait for proxy reply
296                                        pass
297                                else:
298                                        if receiver.file_props['type'] == 'r':
299                                                result = receiver.get_file_contents(timeout)
300                                        else:
301                                                result = receiver.write_next()
302                                        self.process_result(result, receiver)
303                        else:
304                                self.remove_receiver(idx)
305               
306        def process_result(self, result, actor):
307                ''' Take appropriate actions upon the result:
308                [ 0, - 1 ] complete/end transfer
309                [ > 0 ] send progress message
310                [ None ] do nothing
311                '''
312                if result is None:
313                        return
314                if result in (0, -1) and self.complete_transfer_cb is not None:
315                        account = actor.account
316                        self.complete_transfer_cb(account, actor.file_props)
317                elif self.progress_transfer_cb is not None:
318                        self.progress_transfer_cb(actor.account, actor.file_props)
319       
320        def remove_receiver(self, idx, do_disconnect = True):
321                ''' Remove reciver from the list and decrease
322                the number of active connections with 1'''
323                if idx != -1:
324                        if self.readers.has_key(idx):
325                                if do_disconnect:
326                                        self.readers[idx].disconnect()
327                                else:
328                                        if self.readers[idx].streamhost is not None:
329                                                self.readers[idx].streamhost['state'] = -1
330                                        del(self.readers[idx])
331       
332        def remove_sender(self, idx, do_disconnect = True):
333                ''' Remove sender from the list of senders and decrease the
334                number of active connections with 1'''
335                if idx != -1:
336                        if self.senders.has_key(idx):
337                                if do_disconnect:
338                                        self.senders[idx].disconnect()
339                                        return
340                                else:
341                                        del(self.senders[idx])
342                                        if self.connected > 0:
343                                                self.connected -= 1
344                        if len(self.senders) == 0 and self.listener is not None:
345                                self.listener.disconnect()
346                                self.listener = None
347                                self.connected -= 1
348       
349class Socks5:
350        def __init__(self, host, port, initiator, target, sid):
351                if host is not None:
352                        self.host = socket.gethostbyname(host)
353                self.port = port
354                self.initiator = initiator
355                self.target = target
356                self.sid = sid
357                self._sock = None
358                self.account = None
359                self.state = 0 # not connected
360                self.pauses = 0
361                self.size = 0
362                self.remaining_buff = ''
363                self.fd = None
364               
365        def open_file_for_reading(self):
366                if self.fd == None:
367                        try:
368                                self.fd = open(self.file_props['file-name'],'rb')
369                        except IOError, e:
370                                self.close_file()
371                                raise IOError, e
372               
373        def close_file(self):
374                try:
375                        self.fd.close()
376                except:
377                        pass
378               
379        def get_fd(self):
380                ''' Test if file is already open and return its fd,
381                or just open the file and return the fd.
382                '''
383                if self.file_props.has_key('fd'):
384                        fd = self.file_props['fd']
385                else:
386                        fd = open(self.file_props['file-name'],'wb')
387                        self.file_props['fd'] = fd
388                        self.file_props['elapsed-time'] = 0
389                        self.file_props['last-time'] = time.time()
390                        self.file_props['received-len'] = 0
391                return fd
392
393        def rem_fd(self, fd):
394                if self.file_props.has_key('fd'):
395                        del(self.file_props['fd'])
396                try:
397                        fd.close()
398                except:
399                        pass
400                       
401               
402        def receive(self):
403                ''' Reads small chunks of data.
404                        Calls owner's disconnected() method if appropriate.'''
405                if self.pending_read():
406                        received = ''
407                        try: 
408                                add = self._recv(64)
409                        except Exception, e: 
410                                add=''
411                        received +=add
412                        if len(add) == 0:
413                                self.disconnect()
414                else:
415                        return None
416                return add
417       
418        def send_raw(self,raw_data):
419                ''' Writes raw outgoing data. '''
420                try:
421                        lenn = self._send(raw_data)
422                except Exception, e:
423                        self.disconnect()
424                return len(raw_data)
425       
426        def write_next(self):
427                if self.remaining_buff != '':
428                        buff = self.remaining_buff
429                        self.remaining_buff = ''
430                else:
431                        try:
432                                self.open_file_for_reading()
433                        except IOError, e:
434                                self.state = 8 # end connection
435                                self.disconnect()
436                                self.file_props['error'] = -7 # unable to read from file
437                                return -1
438                        buff = self.fd.read(MAX_BUFF_LEN)
439                if len(buff) > 0:
440                        lenn = 0
441                        try:
442                                lenn = self._send(buff)
443                        except Exception, e:
444                                if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK):
445                                        # peer stopped reading
446                                        self.state = 8 # end connection
447                                        self.close_file()
448                                        self.disconnect()
449                                        self.file_props['error'] = -1
450                                        return -1
451                        self.size += lenn
452                        current_time = time.time()
453                        self.file_props['elapsed-time'] += current_time - \
454                                self.file_props['last-time']
455                        self.file_props['last-time'] = current_time
456                        self.file_props['received-len'] = self.size
457                        if self.size >= int(self.file_props['size']):
458                                self.state = 8 # end connection
459                                self.file_props['error'] = 0
460                                self.close_file()
461                                self.disconnect()
462                                return -1
463                        if lenn != len(buff):
464                                self.remaining_buff = buff[lenn:]
465                        else:
466                                self.remaining_buff = ''
467                        if lenn == 0:
468                                self.pauses +=1
469                        else:
470                                self.pauses = 0
471                        if self.pauses > 24:
472                                self.file_props['stalled'] = True
473                        else:
474                                self.file_props['stalled'] = False
475                        self.state = 7 # continue to write in the socket
476                        if lenn == 0 and self.file_props['stalled'] is False:
477                                return None
478                        return lenn
479                else:
480                        self.state = 8 # end connection
481                        self.close_file()
482                        self.disconnect()
483                        return -1
484       
485        def get_file_contents(self, timeout):
486                ''' read file contents from socket and write them to file ''', \
487                        self.file_props['type'], self.file_props['sid']
488                if self.file_props is None or \
489                        self.file_props.has_key('file-name') is False:
490                        self.file_props['error'] = -2
491                        return None
492                fd = None
493                if self.remaining_buff != '':
494                        fd = self.get_fd()
495                        fd.write(self.remaining_buff)
496                        lenn = len(self.remaining_buff)
497                        current_time = time.time()
498                        self.file_props['elapsed-time'] += current_time - \
499                                self.file_props['last-time']
500                        self.file_props['last-time'] = current_time
501                        self.file_props['received-len'] += lenn
502                        self.remaining_buff = ''
503                        if self.file_props['received-len'] == int(self.file_props['size']):
504                                self.rem_fd(fd)
505                                self.disconnect()
506                                self.file_props['error'] = 0
507                                self.file_props['completed'] = True
508                                return 0
509                else:
510                        while self.pending_read(timeout):
511                                fd = self.get_fd()
512                                try: 
513                                        buff = self._recv(MAX_BUFF_LEN)
514                                except Exception, e:
515                                        buff = ''
516                                first_byte = False
517                                if self.file_props['received-len'] == 0: 
518                                        if len(buff) > 0: 
519                                                # delimiter between auth and data 
520                                                if ord(buff[0]) == 0xD: 
521                                                        first_byte = True 
522                                                        buff = buff[1:]
523                                current_time = time.time()
524                                self.file_props['elapsed-time'] += current_time - \
525                                        self.file_props['last-time']
526                                self.file_props['last-time'] = current_time
527                                self.file_props['received-len'] += len(buff)
528                                try:
529                                        fd.write(buff)
530                                except IOError, e:
531                                        self.rem_fd(fd)
532                                        self.disconnect(False)
533                                        self.file_props['error'] = -6 # file system error
534                                        return 0
535                                if len(buff) == 0 and first_byte is False:
536                                        # Transfer stopped  somehow:
537                                        # reset, paused or network error
538                                        self.rem_fd(fd)
539                                        self.disconnect(False)
540                                        self.file_props['error'] = -1
541                                        return 0
542                                if self.file_props['received-len'] >= int(self.file_props['size']):
543                                        # transfer completed
544                                        self.rem_fd(fd)
545                                        self.disconnect()
546                                        self.file_props['error'] = 0
547                                        self.file_props['completed'] = True
548                                        return 0
549                        # return number of read bytes. It can be used in progressbar
550                if fd == None:
551                        self.pauses +=1
552                else:
553                        self.pauses = 0
554                if self.pauses > 24:
555                        self.file_props['stalled'] = True
556                else:
557                        self.file_props['stalled'] = False
558                if fd == None and self.file_props['stalled'] is False:
559                        return None
560                if self.file_props.has_key('received-len'):
561                        if self.file_props['received-len'] != 0:
562                                return self.file_props['received-len']
563                return None
564       
565        def disconnect(self, cb = True):
566                ''' Closes the socket. '''
567                self._sock.close()