From 01c1465dfb06c73cc78bdcc9f4c1e7efddb63ebe Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 1 Jun 2010 15:09:42 +0800 Subject: [PATCH 002/121] add namespace for jingle file transfer add jingle FT to gajim.gajim_optional_features --- src/chat_control.py | 6 +++--- src/common/connection_handlers.py | 2 +- src/common/helpers.py | 1 + src/common/xmpp/protocol.py | 1 + src/filetransfers_window.py | 8 ++++++++ src/gui_menu_builder.py | 4 ++-- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index e71a6d2a5..1ebd45cd0 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -51,7 +51,7 @@ from common.logger import constants from common.pep import MOODS, ACTIVITIES from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION -from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP +from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER from command_system.implementation.middleware import ChatCommandProcessor from command_system.implementation.middleware import CommandTools @@ -1496,12 +1496,12 @@ class ChatControl(ChatControlBase): self._video_button.set_sensitive(self.video_available) # Send file - if self.contact.supports(NS_FILE) and self.contact.resource: + if (self.contact.supports(NS_FILE) or self.contact.supports(NS_JINGLE_FILE_TRANSFER)) and self.contact.resource: self._send_file_button.set_sensitive(True) self._send_file_button.set_tooltip_text('') else: self._send_file_button.set_sensitive(False) - if not self.contact.supports(NS_FILE): + if not (self.contact.supports(NS_FILE) or self.contact.supports(NS_JINGLE_FILE_TRANSFER)): self._send_file_button.set_tooltip_text(_( "This contact does not support file transfer.")) else: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index d60e14b5e..26a23a45d 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -182,7 +182,7 @@ class ConnectionDisco: query = iq.setTag('query') query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[0]) for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, - common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS): + common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS, common.xmpp.NS_JINGLE_FILE_TRANSFER): feature = common.xmpp.Node('feature') feature.setAttr('var', f) query.addChild(node=feature) diff --git a/src/common/helpers.py b/src/common/helpers.py index 367520d75..6e1b5df7b 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -1316,6 +1316,7 @@ def update_optional_features(account = None): gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO) gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO) gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_FILE_TRANSFER) gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity], gajim.gajim_common_features + gajim.gajim_optional_features[a]) # re-send presence with new hash diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 750cd7bf9..0d1f15422 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -69,6 +69,7 @@ NS_JINGLE_ERRORS='urn:xmpp:jingle:errors:1' # XEP-01 NS_JINGLE_RTP ='urn:xmpp:jingle:apps:rtp:1' # XEP-0167 NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio' # XEP-0167 NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-0167 +NS_JINGLE_FILE_TRANSFER='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234 NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 NS_LAST ='jabber:iq:last' diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 3b5a09faf..8f337605b 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -35,6 +35,10 @@ from common import gajim from common import helpers from common.protocol.bytestream import (is_transfer_active, is_transfer_paused, is_transfer_stopped) +from common.xmpp.protocol import NS_JINGLE_FILE_TRANSFER +import logging + +log = logging.getLogger('gajim.filetransfer_window') C_IMAGE = 0 C_LABELS = 1 @@ -299,6 +303,10 @@ class FileTransfersWindow: if file_props is None: return False self.add_transfer(account, contact, file_props) + if contact.supports(NS_JINGLE_FILE_TRANSFER): + log.info("contacts supports jingle file transfer") + else: + log.info("contacts does not support jingle file transfer") gajim.connections[account].send_file_request(file_props) return True diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index fbc93d1b1..30b514e97 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -25,7 +25,7 @@ import message_control from common import gajim from common import helpers -from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION +from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION, NS_JINGLE_FILE_TRANSFER def build_resources_submenu(contacts, account, action, room_jid=None, room_account=None, cap=None): @@ -225,7 +225,7 @@ control=None): else: start_chat_menuitem.connect('activate', gajim.interface.on_open_chat_window, contact, account) - if contact.supports(NS_FILE): + if contact.supports(NS_FILE) or contact.supports(NS_JINGLE_FILE_TRANSFER): send_file_menuitem.set_sensitive(True) send_file_menuitem.connect('activate', roster.on_send_file_menuitem_activate, contact, account) From b7048aa0da0a5ef102d9cd293666e5f7fbd403eb Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 1 Jun 2010 15:14:18 +0800 Subject: [PATCH 003/121] fix typo. 'contacts' -> 'contact' --- src/filetransfers_window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 8f337605b..0350c50e7 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -304,9 +304,9 @@ class FileTransfersWindow: return False self.add_transfer(account, contact, file_props) if contact.supports(NS_JINGLE_FILE_TRANSFER): - log.info("contacts supports jingle file transfer") + log.info("contact supports jingle file transfer") else: - log.info("contacts does not support jingle file transfer") + log.info("contact does not support jingle file transfer") gajim.connections[account].send_file_request(file_props) return True From 048feb552888cdfdad05835ca9ba5f6703cff8ed Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sun, 6 Jun 2010 23:22:52 +0800 Subject: [PATCH 004/121] added JingleFileTransfer for managing jingle ft content code added to trigger jingle ft session when needed --- src/common/caps_cache.py | 4 +- src/common/jingle.py | 15 +++++++ src/common/jingle_ft.py | 78 +++++++++++++++++++++++++++++++++++++ src/filetransfers_window.py | 3 +- 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/common/jingle_ft.py diff --git a/src/common/caps_cache.py b/src/common/caps_cache.py index af69c8aeb..e9ad0bf73 100644 --- a/src/common/caps_cache.py +++ b/src/common/caps_cache.py @@ -38,10 +38,10 @@ import logging log = logging.getLogger('gajim.c.caps_cache') from common.xmpp import (NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS) + NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS, NS_JINGLE_FILE_TRANSFER) # Features where we cannot safely assume that the other side supports them FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO] + NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_FILE_TRANSFER] # Query entry status codes NEW = 0 diff --git a/src/common/jingle.py b/src/common/jingle.py index da2a5d031..a196bb626 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -35,7 +35,10 @@ import helpers from jingle_session import JingleSession, JingleStates from jingle_rtp import JingleAudio, JingleVideo +from jingle_ft import JingleFileTransfer +import logging +logger = logging.getLogger('gajim.c.jingle') class ConnectionJingle(object): """ @@ -124,6 +127,18 @@ class ConnectionJingle(object): jingle.start_session() return jingle.sid + def start_file_transfer(self, jid, file_props): + logger.info("start file transfer with file: %s", str(file_props)) + jingle = self.get_jingle_session(jid, media='file') + if jingle: + jingle.add_content('file', JingleFileTransfer(jingle, file_props)) + else: + jingle = JingleSession(self, weinitiate=True, jid=jid) + self.__sessions[jingle.sid] = jingle + jingle.add_content('file', JingleFileTransfer(jingle, file_props)) + jingle.start_session() + return jingle.sid + def iter_jingle_sessions(self, jid, sid=None, media=None): if sid: diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py new file mode 100644 index 000000000..190a88be4 --- /dev/null +++ b/src/common/jingle_ft.py @@ -0,0 +1,78 @@ +# -*- coding:utf-8 -*- +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + + +""" +Handles Jingle File Transfer (XEP 0234) +""" + +import gajim +import xmpp +from jingle_content import contents, JingleContent +from jingle_transport import JingleTransportICEUDP +import logging + +log = logging.getLogger('gajim.c.jingle_ft') + + +class JingleFileTransfer(JingleContent): + def __init__(self, session, file_props, transport=None): + JingleContent.__init__(self, session, transport) + + #events we might be interested in + self.callbacks['session-initiate'] += [self.__on_session_initiate] + self.callbacks['session-accept'] += [self.__on_session_accept] + self.callbacks['session-terminate'] += [self.__on_session_terminate] + self.callbacks['transport-accept'] += [self.__on_transport_accept] + self.callbacks['transport-replace'] += [self.__on_transport_replace] #fallback transport method + self.callbacks['transport-reject'] += [self.__on_transport_reject] + self.callbacks['transport-info'] += [self.__on_transport_info] + + self.file_props = file_props + + if transport == None: + self.transport = JingleTransportICEUDP() + + def __on_session_initiate(self, stanza, content, error, action): + log.info("__on_session_initiate") + pass + + def __on_session_accept(self, stanza, content, error, action): + log.info("__on_session_accept") + pass + + def __on_session_terminate(self, stanza, content, error, action): + log.info("__on_session_terminate") + pass + + def __on_transport_accept(self, stanza, content, error, action): + log.info("__on_transport_accept") + pass + + def __on_transport_replace(self, stanza, content, error, action): + log.info("__on_transport_replace") + pass + + def __on_transport_reject(self, stanza, content, error, action): + log.info("__on_transport_reject") + pass + + def __on_transport_info(self, stanza, content, error, action): + log.info("__on_transport_info") + pass + + def _fill_content(self, content): + content.addChild("description", namespace = xmpp.NS_JINGLE_FILE_TRANSFER) diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 0350c50e7..4b3b17bab 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -305,9 +305,10 @@ class FileTransfersWindow: self.add_transfer(account, contact, file_props) if contact.supports(NS_JINGLE_FILE_TRANSFER): log.info("contact supports jingle file transfer") + gajim.connections[account].start_file_transfer(contact.get_full_jid(), file_props) else: log.info("contact does not support jingle file transfer") - gajim.connections[account].send_file_request(file_props) + gajim.connections[account].send_file_request(file_props) return True def _start_receive(self, file_path, account, contact, file_props): From d62dd5a521abeceb045bbbb990661d7b0bf27d24 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 8 Jun 2010 16:39:44 +0800 Subject: [PATCH 005/121] construct well-formed jingle FT session-initiate stanza --- src/common/jingle_ft.py | 20 +++++++++++++++++++- src/filetransfers_window.py | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 190a88be4..7463d1c40 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -75,4 +75,22 @@ class JingleFileTransfer(JingleContent): pass def _fill_content(self, content): - content.addChild("description", namespace = xmpp.NS_JINGLE_FILE_TRANSFER) + description_node = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') + + sioffer = xmpp.simplexml.Node(tag='offer') + file_tag = sioffer.setTag('file', namespace=xmpp.NS_FILE) + file_tag.setAttr('name', self.file_props['name']) + file_tag.setAttr('size', self.file_props['size']) + desc = file_tag.setTag('desc') + if 'desc' in self.file_props: + desc.setData(self.file_props['desc']) + + description_node.addChild(node=sioffer) + + content.addChild(node=description_node) + +def get_content(desc): + return JingleFileTransfer + + +contents[xmpp.NS_JINGLE_FILE_TRANSFER] = get_content diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 4b3b17bab..1036ebe97 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -304,7 +304,7 @@ class FileTransfersWindow: return False self.add_transfer(account, contact, file_props) if contact.supports(NS_JINGLE_FILE_TRANSFER): - log.info("contact supports jingle file transfer") + log.info("contact %s supports jingle file transfer"%(contact.get_full_jid())) gajim.connections[account].start_file_transfer(contact.get_full_jid(), file_props) else: log.info("contact does not support jingle file transfer") From 182a4486302b6ac3011b4938a4ac276e492a0b30 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 8 Jun 2010 21:24:41 +0800 Subject: [PATCH 006/121] receive jingle file transfer session initiate message, shout 'FILE-REQUEST' event to connection object. --- src/common/jingle.py | 2 +- src/common/jingle_ft.py | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index a196bb626..2dd933e2f 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -135,7 +135,7 @@ class ConnectionJingle(object): else: jingle = JingleSession(self, weinitiate=True, jid=jid) self.__sessions[jingle.sid] = jingle - jingle.add_content('file', JingleFileTransfer(jingle, file_props)) + jingle.add_content('file', JingleFileTransfer(jingle, file_props=file_props)) jingle.start_session() return jingle.sid diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 7463d1c40..7de99aa0d 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -29,7 +29,7 @@ log = logging.getLogger('gajim.c.jingle_ft') class JingleFileTransfer(JingleContent): - def __init__(self, session, file_props, transport=None): + def __init__(self, session, transport=None, file_props=None): JingleContent.__init__(self, session, transport) #events we might be interested in @@ -42,13 +42,47 @@ class JingleFileTransfer(JingleContent): self.callbacks['transport-info'] += [self.__on_transport_info] self.file_props = file_props + if file_props == None: + self.weinitiate = False + else: + self.weinitiate = True if transport == None: self.transport = JingleTransportICEUDP() + + self.session = session def __on_session_initiate(self, stanza, content, error, action): - log.info("__on_session_initiate") - pass + jid = unicode(stanza.getFrom()) + log.info("jid:%s"%jid) + + file_props = {'type': 'r'} + file_props['sender'] = jid + file_props['request-id'] = unicode(stanza.getAttr('id')) + + file_tag = content.getTag('description').getTag('offer').getTag('file') + for attribute in file_tag.getAttrs(): + if attribute in ('name', 'size', 'hash', 'date'): + val = file_tag.getAttr(attribute) + if val is None: + continue + file_props[attribute] = val + file_desc_tag = file_tag.getTag('desc') + if file_desc_tag is not None: + file_props['desc'] = file_desc_tag.getData() + + file_props['receiver'] = self.session.ourjid + log.info("ourjid: %s"%self.session.ourjid) + file_props['sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) + file_props['transfered_size'] = [] + + log.info("FT request: %s"%file_props) + + #TODO + #add file transfer to queue + self.session.connection.dispatch('FILE_REQUEST', (jid, file_props)) + + def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") From 5f96675d561fe855b31518680c6e3fe2d75fbeeb Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 14 Jun 2010 20:41:24 +0800 Subject: [PATCH 007/121] send session-accept stanza if user approve file transfer --- src/common/jingle_ft.py | 14 ++++++++++++++ src/common/protocol/bytestream.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 7de99aa0d..6ba2d43f3 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -47,10 +47,20 @@ class JingleFileTransfer(JingleContent): else: self.weinitiate = True + if self.file_props != None: + self.file_props['sender'] = session.ourjid + self.file_props['session-type'] = 'jingle' + self.file_props['sid'] = session.sid + self.file_props['transfered_size'] = [] + + log.info("FT request: %s"%file_props) + + if transport == None: self.transport = JingleTransportICEUDP() self.session = session + self.media = 'file' def __on_session_initiate(self, stanza, content, error, action): jid = unicode(stanza.getFrom()) @@ -60,6 +70,8 @@ class JingleFileTransfer(JingleContent): file_props['sender'] = jid file_props['request-id'] = unicode(stanza.getAttr('id')) + file_props['session-type'] = 'jingle' + file_tag = content.getTag('description').getTag('offer').getTag('file') for attribute in file_tag.getAttrs(): if attribute in ('name', 'size', 'hash', 'date'): @@ -75,6 +87,8 @@ class JingleFileTransfer(JingleContent): log.info("ourjid: %s"%self.session.ourjid) file_props['sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) file_props['transfered_size'] = [] + + self.file_props = file_props log.info("FT request: %s"%file_props) diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index c4f5578ea..52d7bbe1c 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -37,6 +37,9 @@ from common import dataforms from common.socks5 import Socks5Receiver +import logging +log = logging.getLogger('gajim.c.protocol.bytestream') + def is_transfer_paused(file_props): if 'stopped' in file_props and file_props['stopped']: @@ -125,6 +128,21 @@ class ConnectionBytestream: # user response to ConfirmationDialog may come after we've disconneted if not self.connection or self.connected < 2: return + + #file transfer initiated by a jingle session + log.info("send_file_approval: jingle session accept") + if file_props.get('session-type') == 'jingle': + session = self.get_jingle_session(file_props['sender'], file_props['sid']) + if not session: + return + jid = gajim.get_jid_without_resource(file_props['sender']) + resource = gajim.get_resource_from_jid(file_props['sender']) + + if not session.accepted: + session.approve_session() + session.approve_content('file') + return + iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') iq.setAttr('id', file_props['request-id']) si = iq.setTag('si', namespace=xmpp.NS_SI) @@ -138,6 +156,7 @@ class ConnectionBytestream: field = _feature.setField('stream-method') field.delAttr('type') field.setValue(xmpp.NS_BYTESTREAM) + log.info("send_file_approval: %s"%(str(iq))) self.connection.send(iq) def send_file_rejection(self, file_props, code='403', typ=None): From e61e5db0b7c7fbea809daecf25261439ad0be569 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 14 Jun 2010 21:21:22 +0800 Subject: [PATCH 008/121] fix coding style: use 'is' operator when comparing object to None. put space around '%' operator. remove redundant str() on arguments. --- src/common/jingle_ft.py | 14 +++++++------- src/common/protocol/bytestream.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 6ba2d43f3..3bd50b989 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -42,21 +42,21 @@ class JingleFileTransfer(JingleContent): self.callbacks['transport-info'] += [self.__on_transport_info] self.file_props = file_props - if file_props == None: + if file_props is None: self.weinitiate = False else: self.weinitiate = True - if self.file_props != None: + if self.file_props is not None: self.file_props['sender'] = session.ourjid self.file_props['session-type'] = 'jingle' self.file_props['sid'] = session.sid self.file_props['transfered_size'] = [] - log.info("FT request: %s"%file_props) + log.info("FT request: %s" % file_props) - if transport == None: + if transport is None: self.transport = JingleTransportICEUDP() self.session = session @@ -64,7 +64,7 @@ class JingleFileTransfer(JingleContent): def __on_session_initiate(self, stanza, content, error, action): jid = unicode(stanza.getFrom()) - log.info("jid:%s"%jid) + log.info("jid:%s" % jid) file_props = {'type': 'r'} file_props['sender'] = jid @@ -84,13 +84,13 @@ class JingleFileTransfer(JingleContent): file_props['desc'] = file_desc_tag.getData() file_props['receiver'] = self.session.ourjid - log.info("ourjid: %s"%self.session.ourjid) + log.info("ourjid: %s" % self.session.ourjid) file_props['sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) file_props['transfered_size'] = [] self.file_props = file_props - log.info("FT request: %s"%file_props) + log.info("FT request: %s" % file_props) #TODO #add file transfer to queue diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 52d7bbe1c..7ed0764e1 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -156,7 +156,7 @@ class ConnectionBytestream: field = _feature.setField('stream-method') field.delAttr('type') field.setValue(xmpp.NS_BYTESTREAM) - log.info("send_file_approval: %s"%(str(iq))) + log.info("send_file_approval: %s" % iq) self.connection.send(iq) def send_file_rejection(self, file_props, code='403', typ=None): From b85e7849e8a8b57fff656a8f1dd7e653db22d430 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sat, 19 Jun 2010 22:52:17 +0800 Subject: [PATCH 009/121] contruct transport candidates for jingle socks5 bytestream --- src/common/jingle_ft.py | 14 +++- src/common/jingle_session.py | 2 + src/common/jingle_transport.py | 119 ++++++++++++++++++++++++++++++ src/common/protocol/bytestream.py | 2 +- src/common/xmpp/protocol.py | 1 + 5 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 3bd50b989..7e609086f 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -22,7 +22,7 @@ Handles Jingle File Transfer (XEP 0234) import gajim import xmpp from jingle_content import contents, JingleContent -from jingle_transport import JingleTransportICEUDP +from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 import logging log = logging.getLogger('gajim.c.jingle_ft') @@ -32,6 +32,8 @@ class JingleFileTransfer(JingleContent): def __init__(self, session, transport=None, file_props=None): JingleContent.__init__(self, session, transport) + log.info("transport value: %s" % transport) + #events we might be interested in self.callbacks['session-initiate'] += [self.__on_session_initiate] self.callbacks['session-accept'] += [self.__on_session_accept] @@ -57,7 +59,10 @@ class JingleFileTransfer(JingleContent): if transport is None: - self.transport = JingleTransportICEUDP() + self.transport = JingleTransportSocks5() + self.transport.set_file_props(self.file_props) + self.transport.set_our_jid(session.ourjid) + log.info('ourjid: %s' % session.ourjid) self.session = session self.media = 'file' @@ -89,7 +94,10 @@ class JingleFileTransfer(JingleContent): file_props['transfered_size'] = [] self.file_props = file_props - + if self.transport is None: + self.transport = JingleTransportSocks5() + self.transport.set_our_jid(self.session.ourjid) + self.transport.set_file_props(self.file_props) log.info("FT request: %s" % file_props) #TODO diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index f2537a9f9..b2e9eb9c1 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -502,6 +502,8 @@ class JingleSession(object): for element in jingle.iterTags('content'): transport = get_jingle_transport(element.getTag('transport')) + if transport: + transport.ourjid = self.ourjid content_type = get_jingle_content(element.getTag('description')) if content_type: try: diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 521564e87..312dcf121 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -16,6 +16,13 @@ Handles Jingle Transports (currently only ICE-UDP) """ import xmpp +import socket +from common import gajim +from common.protocol.bytestream import ConnectionSocks5Bytestream +import logging + +log = logging.getLogger('gajim.c.jingle_transport') + transports = {} @@ -71,6 +78,117 @@ class JingleTransport(object): """ return [] +class JingleTransportSocks5(JingleTransport): + """ + Socks5 transport in jingle scenario + Note: Don't forget to call set_file_props after initialization + """ + def __init__(self): + JingleTransport.__init__(self, TransportType.streaming) + + def set_file_props(self, file_props): + self.file_props = file_props + + def set_our_jid(self, jid): + self.ourjid = jid + + def make_candidate(self, candidate): + import logging + log = logging.getLogger() + log.info('candidate dict, %s' % candidate) + attrs = { + 'cid': candidate['candidate_id'], + 'host': candidate['host'], + 'jid': candidate['jid'], + 'port': candidate['port'], + 'priority': candidate['priority'], + 'type': candidate['type'] + } + + return xmpp.Node('candidate', attrs=attrs) + + def make_transport(self, candidates=None): + self._add_local_ips_as_candidates() + self._add_additional_candidates() + self._add_proxy_candidates() + transport = JingleTransport.make_transport(self, candidates) + transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) + return transport + + def parse_transport_stanza(self, transport): + pass + + def _add_local_ips_as_candidates(self): + local_ip_cand = [] + port = gajim.config.get('file_transfers_port') + type_preference = 126 #type preference of connection type. XEP-0260 section 2.2 + jid_wo_resource = gajim.get_jid_without_resource(self.ourjid) + conn = gajim.connections[jid_wo_resource] + c = {'host': conn.peerhost[0]} + c['candidate_id'] = conn.connection.getAnID() + c['port'] = port + c['type'] = 'direct' + c['jid'] = self.ourjid + c['priority'] = (2**16) * type_preference + + local_ip_cand.append(c) + + for addr in socket.getaddrinfo(socket.gethostname(), None): + if not addr[4][0] in local_ip_cand and not addr[4][0].startswith('127'): + c = {'host': addr[4][0]} + c['candidate_id'] = conn.connection.getAnID() + c['port'] = port + c['type'] = 'direct' + c['jid'] = self.ourjid + c['priority'] = (2**16) * type_preference + local_ip_cand.append(c) + + self.candidates += local_ip_cand + + def _add_additional_candidates(self): + type_preference = 126 + additional_ip_cand = [] + port = gajim.config.get('file_transfers_port') + ft_add_hosts = gajim.config.get('ft_add_hosts_to_send') + jid_wo_resource = gajim.get_jid_without_resource(self.ourjid) + conn = gajim.connections[jid_wo_resource] + + if ft_add_hosts: + hosts = [e.strip() for e in ft_add_hosts.split(',')] + for h in hosts: + c = {'host': h} + c['candidate_id'] = conn.connection.getAnID() + c['port'] = port + c['type'] = 'direct' + c['jid'] = self.ourjid + c['priority'] = (2**16) * type_preference + additional_ip_cand.append(c) + self.candidates += additional_ip_cand + + def _add_proxy_candidates(self): + type_preference = 10 + proxy_cand = [] + socks5conn = ConnectionSocks5Bytestream() + socks5conn.name = self.ourjid + proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props) + jid_wo_resource = gajim.get_jid_without_resource(self.ourjid) + conn = gajim.connections[jid_wo_resource] + + if proxyhosts: + file_props['proxy_receiver'] = unicode(file_props['receiver']) + file_props['proxy_sender'] = unicode(file_props['sender']) + file_props['proxyhosts'] = proxyhosts + + for proxyhost in proxyhosts: + c = {'host': proxyhost['host']} + c['candidate_id'] = conn.connection.getAnID() + c['port'] = proxyhost['port'] + c['type'] = 'proxy' + c['jid'] = self.ourjid + c['priority'] = (2**16) * type_preference + proxy_cand.append(c) + self.candidates += proxy_cand + import farsight @@ -146,3 +264,4 @@ class JingleTransportICEUDP(JingleTransport): return candidates transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP +transports[xmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5 diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 7ed0764e1..32f4c170c 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -141,7 +141,7 @@ class ConnectionBytestream: if not session.accepted: session.approve_session() session.approve_content('file') - return + return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') iq.setAttr('id', file_props['request-id']) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 0d1f15422..746bce722 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -72,6 +72,7 @@ NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-01 NS_JINGLE_FILE_TRANSFER='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234 NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 +NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260 NS_LAST ='jabber:iq:last' NS_LOCATION ='http://jabber.org/protocol/geoloc' # XEP-0080 NS_MESSAGE ='message' # Jabberd2 From 2b745570ecb77d611c263fd1585108b1f39af80f Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 1 Jul 2010 13:25:33 +0800 Subject: [PATCH 010/121] add file_props structure to socks5queue --- src/common/jingle_ft.py | 2 -- src/common/protocol/bytestream.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 7e609086f..535e08445 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -100,8 +100,6 @@ class JingleFileTransfer(JingleContent): self.transport.set_file_props(self.file_props) log.info("FT request: %s" % file_props) - #TODO - #add file transfer to queue self.session.connection.dispatch('FILE_REQUEST', (jid, file_props)) diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 32f4c170c..f0776181a 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -138,6 +138,8 @@ class ConnectionBytestream: jid = gajim.get_jid_without_resource(file_props['sender']) resource = gajim.get_resource_from_jid(file_props['sender']) + gajim.socks5queue.add_file_props(session.ourjid, file_props) + if not session.accepted: session.approve_session() session.approve_content('file') From 22037557344d71bb27b7bd8031298207792b9151 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 1 Jul 2010 15:48:44 +0800 Subject: [PATCH 011/121] parse transport, construct streamhosts --- src/common/jingle_ft.py | 14 ++++++++------ src/common/jingle_transport.py | 16 +++++++++++++++- src/common/protocol/bytestream.py | 7 ++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 535e08445..691dbba4b 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -98,6 +98,13 @@ class JingleFileTransfer(JingleContent): self.transport = JingleTransportSocks5() self.transport.set_our_jid(self.session.ourjid) self.transport.set_file_props(self.file_props) + if self.file_props.has_key("streamhosts"): + self.file_props['streamhosts'].extend(self.transport.remote_candidates) + else: + self.file_props['streamhosts'] = self.transport.remote_candidates + for host in self.file_props['streamhosts']: + host['initiator'] = self.session.initiator + host['target'] = self.session.responder log.info("FT request: %s" % file_props) self.session.connection.dispatch('FILE_REQUEST', (jid, file_props)) @@ -106,27 +113,22 @@ class JingleFileTransfer(JingleContent): def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") - pass + gajim.socks5queue.send_file(self.file_props, self.session.ourjid) def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") - pass def __on_transport_accept(self, stanza, content, error, action): log.info("__on_transport_accept") - pass def __on_transport_replace(self, stanza, content, error, action): log.info("__on_transport_replace") - pass def __on_transport_reject(self, stanza, content, error, action): log.info("__on_transport_reject") - pass def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") - pass def _fill_content(self, content): description_node = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 312dcf121..91fc50a64 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -85,6 +85,7 @@ class JingleTransportSocks5(JingleTransport): """ def __init__(self): JingleTransport.__init__(self, TransportType.streaming) + self.remote_candidates = [] def set_file_props(self, file_props): self.file_props = file_props @@ -116,7 +117,20 @@ class JingleTransportSocks5(JingleTransport): return transport def parse_transport_stanza(self, transport): - pass + candidates = [] + for candidate in transport.iterTags('candidate'): + cand = { + 'state': 0, + 'target': self.ourjid, + 'host': candidate['host'], + 'port': candidate['port'] + } + candidates.append(cand) + + # we need this when we construct file_props on session-initiation + self.remote_candidates = candidates + return candidates + def _add_local_ips_as_candidates(self): local_ip_cand = [] diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index f0776181a..f75ceca61 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -137,12 +137,17 @@ class ConnectionBytestream: return jid = gajim.get_jid_without_resource(file_props['sender']) resource = gajim.get_resource_from_jid(file_props['sender']) - + sid = file_props['sid'] gajim.socks5queue.add_file_props(session.ourjid, file_props) if not session.accepted: session.approve_session() session.approve_content('file') + + if not gajim.socks5queue.get_file_props(session.ourjid, sid): + gajim.socks5queue.add_file_props(session.ourjid, file_props) + gajim.socks5queue.connect_to_hosts(session.ourjid, sid, + None, None) return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') From d0adcb1a054321a2e6ceabf0107dc7d86e553fd8 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sat, 3 Jul 2010 16:22:47 +0800 Subject: [PATCH 012/121] dispatch iq-result for a jingle iq stanza, start listener after iq-result on session-initiate. --- src/common/jingle.py | 16 +++++++++++++--- src/common/jingle_ft.py | 28 ++++++++++++++++++++++++++++ src/common/jingle_session.py | 31 +++++++++++++++++++++++++++++-- src/common/protocol/bytestream.py | 3 ++- src/common/socks5.py | 7 +++++++ src/common/stanza_session.py | 2 +- 6 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 2dd933e2f..b63273a7c 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -84,16 +84,26 @@ class ConnectionJingle(object): raise xmpp.NodeProcessed jingle = stanza.getTag('jingle') - if not jingle: return - sid = jingle.getAttr('sid') + # a jingle element is not necessary in iq-result stanza + # don't check for that + if jingle: + sid = jingle.getAttr('sid') + else: + sid = None + for sesn in self.__sessions.values(): + if id in sesn.iq_ids: + sesn.on_stanza(stanza) + return # do we need to create a new jingle object if sid not in self.__sessions: #TODO: tie-breaking and other things... - newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) + newjingle = JingleSession(con=self, weinitiate=False, jid=jid, + iq_id = id, sid=sid) self.__sessions[sid] = newjingle # we already have such session in dispatcher... + self.__sessions[sid].collect_iq_id(id) self.__sessions[sid].on_stanza(stanza) # Delete invalid/unneeded sessions if sid in self.__sessions and self.__sessions[sid].state == JingleStates.ended: diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 691dbba4b..ab0d7438e 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -23,6 +23,8 @@ import gajim import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 +from common import helpers + import logging log = logging.getLogger('gajim.c.jingle_ft') @@ -42,6 +44,7 @@ class JingleFileTransfer(JingleContent): self.callbacks['transport-replace'] += [self.__on_transport_replace] #fallback transport method self.callbacks['transport-reject'] += [self.__on_transport_reject] self.callbacks['transport-info'] += [self.__on_transport_info] + self.callbacks['iq-result'] += [self.__on_iq_result] self.file_props = file_props if file_props is None: @@ -51,6 +54,7 @@ class JingleFileTransfer(JingleContent): if self.file_props is not None: self.file_props['sender'] = session.ourjid + self.file_props['receiver'] = session.peerjid self.file_props['session-type'] = 'jingle' self.file_props['sid'] = session.sid self.file_props['transfered_size'] = [] @@ -129,6 +133,25 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") + + def __on_iq_result(self, stanza, content, error, action): + log.info("__on_iq_result") + + if self.weinitiate: + receiver = self.file_props['receiver'] + sender = self.file_props['sender'] + + sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, receiver) + self.file_props['sha_str'] = sha_str + + port = gajim.config.get('file_transfers_port') + + listener = gajim.socks5queue.start_listener(port, sha_str, + self._store_socks5_sid, self.file_props['sid']) + + if not listener: + return + # send error message, notify the user def _fill_content(self, content): description_node = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') @@ -144,6 +167,11 @@ class JingleFileTransfer(JingleContent): description_node.addChild(node=sioffer) content.addChild(node=description_node) + + def _store_socks5_sid(self, sid, hash_id): + # callback from socsk5queue.start_listener + self.file_props['hash'] = hash_id + return def get_content(desc): return JingleFileTransfer diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index b2e9eb9c1..d0302359d 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -30,6 +30,8 @@ import gajim #Get rid of that? import xmpp from jingle_transport import get_jingle_transport from jingle_content import get_jingle_content, JingleContentSetupException +import logging +log = logging.getLogger("gajim.c.jingle_session") # FIXME: Move it to JingleSession.States? class JingleStates(object): @@ -58,7 +60,7 @@ class JingleSession(object): negotiated between an initiator and a responder. """ - def __init__(self, con, weinitiate, jid, sid=None): + def __init__(self, con, weinitiate, jid, iq_id = None, sid=None): """ con -- connection object, weinitiate -- boolean, are we the initiator? @@ -83,6 +85,14 @@ class JingleSession(object): if not sid: sid = con.connection.getAnID() self.sid = sid # sessionid + + # iq stanza id, used to determine which sessions to summon callback + # later on when iq-result stanza arrives + if iq_id is not None: + self.iq_ids = [iq_id] + else: + self.iq_ids = [] + self.accepted = True # is this session accepted by user @@ -110,10 +120,14 @@ class JingleSession(object): 'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO 'transport-accept': [self.__ack], #TODO 'transport-reject': [self.__ack], #TODO - 'iq-result': [], + 'iq-result': [self.__broadcast], 'iq-error': [self.__on_error], } + def collect_iq_id(self, iq_id): + if iq_id is not None: + self.iq_ids.append(iq_id) + def approve_session(self): """ Called when user accepts session in UI (when we aren't the initiator) @@ -463,6 +477,17 @@ class JingleSession(object): """ Broadcast the stanza contents to proper content handlers """ + #if jingle is None: # it is a iq-result stanza + # for cn in self.contents.values(): + # cn.on_stanza(stanza, None, error, action) + # return + + # special case: iq-result stanza does not come with a jingle element + if action == 'iq-result': + for cn in self.contents.values(): + cn.on_stanza(stanza, None, error, action) + return + for content in jingle.iterTags('content'): name = content['name'] creator = content['creator'] @@ -606,6 +631,7 @@ class JingleSession(object): self.__append_contents(jingle) self.__broadcast(stanza, jingle, None, 'session-initiate-sent') self.connection.connection.send(stanza) + self.collect_iq_id(stanza.getID()) self.state = JingleStates.pending def __session_accept(self): @@ -614,6 +640,7 @@ class JingleSession(object): self.__append_contents(jingle) self.__broadcast(stanza, jingle, None, 'session-accept-sent') self.connection.connection.send(stanza) + self.collect_iq_id(stanza.getID()) self.state = JingleStates.active def __session_info(self, payload=None): diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index f75ceca61..69b289125 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -147,7 +147,8 @@ class ConnectionBytestream: if not gajim.socks5queue.get_file_props(session.ourjid, sid): gajim.socks5queue.add_file_props(session.ourjid, file_props) gajim.socks5queue.connect_to_hosts(session.ourjid, sid, - None, None) + lambda streamhost: log.info("connected to" + str(streamhost)), + lambda a, b, c, d: log.info("connect error!" + a + b + c + d)) return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') diff --git a/src/common/socks5.py b/src/common/socks5.py index 4d99cdb21..f411dae9f 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -35,6 +35,10 @@ from errno import EISCONN from errno import EINPROGRESS from errno import EAFNOSUPPORT from xmpp.idlequeue import IdleObject + +import logging +log = logging.getLogger('gajim.c.socks5') + MAX_BUFF_LEN = 65536 # after foo seconds without activity label transfer as 'stalled' @@ -257,6 +261,7 @@ class SocksQueue: def send_file(self, file_props, account): if 'hash' in file_props and file_props['hash'] in self.senders: + log.info("socks5: sending file") sender = self.senders[file_props['hash']] file_props['streamhost-used'] = True sender.account = account @@ -269,6 +274,8 @@ class SocksQueue: file_props['last-time'] = self.idlequeue.current_time() file_props['received-len'] = 0 sender.file_props = file_props + else: + log.info("socks5: NOT sending file") def add_file_props(self, account, file_props): """ diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index 028bee596..b4ab7c2dc 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -37,7 +37,7 @@ from hashlib import sha256 from hmac import HMAC from common import crypto -if gajim.HAVE_PYCRYPTO: +if gajim.HAVE_PYCRYPTO and False: from Crypto.Cipher import AES from Crypto.PublicKey import RSA From 14fe189b00849efebea233b1bdd8a89c17e20e3b Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 5 Jul 2010 13:46:53 +0800 Subject: [PATCH 013/121] send transport info, start "send_file" after receiving transport-info. TODO: implement file transfer complete callback, various session management code for abnormal FT interactions. --- src/common/jingle_ft.py | 34 ++++++++++++++++++++++++++++++- src/common/jingle_transport.py | 3 ++- src/common/protocol/bytestream.py | 8 +------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index ab0d7438e..4e3198a12 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -117,7 +117,6 @@ class JingleFileTransfer(JingleContent): def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") - gajim.socks5queue.send_file(self.file_props, self.session.ourjid) def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") @@ -133,6 +132,7 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") + gajim.socks5queue.send_file(self.file_props, self.session.ourjid) def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") @@ -152,7 +152,39 @@ class JingleFileTransfer(JingleContent): if not listener: return # send error message, notify the user + else: # session-accept iq-result + if not gajim.socks5queue.get_file_props(self.session.ourjid, self.file_props['sid']): + gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) + gajim.socks5queue.connect_to_hosts(self.session.ourjid, self.file_props['sid'], + self.send_candidate_used, self._on_connect_error) + + def send_candidate_used(self, streamhost): + """ + send candidate-used stanza + """ + log.info("send_candidate_used") + if streamhost is None: + return + + content = xmpp.Node('content') + content.setAttr('creator', 'initiator') + content.setAttr('name', 'file') + + transport = xmpp.Node('transport') + transport.setAttr('xmlns', xmpp.NS_JINGLE_BYTESTREAM) + + candidateused = xmpp.Node('candidate-used') + candidateused.setAttr('cid', streamhost['cid']) + + transport.addChild(node=candidateused) + content.addChild(node=transport) + self.session.send_transport_info(content) + + def _on_connect_error(self, to, _id, sid, code=404): + log.info("connect error, sid=" + sid) + return + def _fill_content(self, content): description_node = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 91fc50a64..7187fbb48 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -123,7 +123,8 @@ class JingleTransportSocks5(JingleTransport): 'state': 0, 'target': self.ourjid, 'host': candidate['host'], - 'port': candidate['port'] + 'port': candidate['port'], + 'cid': candidate['cid'] } candidates.append(cand) diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 69b289125..71f4327c8 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -143,13 +143,7 @@ class ConnectionBytestream: if not session.accepted: session.approve_session() session.approve_content('file') - - if not gajim.socks5queue.get_file_props(session.ourjid, sid): - gajim.socks5queue.add_file_props(session.ourjid, file_props) - gajim.socks5queue.connect_to_hosts(session.ourjid, sid, - lambda streamhost: log.info("connected to" + str(streamhost)), - lambda a, b, c, d: log.info("connect error!" + a + b + c + d)) - return + return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') iq.setAttr('id', file_props['request-id']) From fbc4144c82e723bfd326fd6fad9801f3156fc36c Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 5 Jul 2010 13:47:47 +0800 Subject: [PATCH 014/121] remove debug tweak --- src/common/stanza_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index b4ab7c2dc..028bee596 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -37,7 +37,7 @@ from hashlib import sha256 from hmac import HMAC from common import crypto -if gajim.HAVE_PYCRYPTO and False: +if gajim.HAVE_PYCRYPTO: from Crypto.Cipher import AES from Crypto.PublicKey import RSA From 92988cf2bad1a0f8b2fd3aeb73fb2adb61f7ba0d Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 5 Jul 2010 19:54:59 +0800 Subject: [PATCH 015/121] fix bug: use jid without resource --- src/common/jingle_ft.py | 6 ++++-- src/common/protocol/bytestream.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 4e3198a12..8a3a4d024 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -132,7 +132,8 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") - gajim.socks5queue.send_file(self.file_props, self.session.ourjid) + jid = gajim.get_jid_without_resource(self.session.ourjid) + gajim.socks5queue.send_file(self.file_props, jid) def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") @@ -155,7 +156,8 @@ class JingleFileTransfer(JingleContent): else: # session-accept iq-result if not gajim.socks5queue.get_file_props(self.session.ourjid, self.file_props['sid']): gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) - gajim.socks5queue.connect_to_hosts(self.session.ourjid, self.file_props['sid'], + jid = gajim.get_jid_without_resource(self.session.ourjid) + gajim.socks5queue.connect_to_hosts(jid, self.file_props['sid'], self.send_candidate_used, self._on_connect_error) def send_candidate_used(self, streamhost): diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 71f4327c8..a1ed9d593 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -138,7 +138,8 @@ class ConnectionBytestream: jid = gajim.get_jid_without_resource(file_props['sender']) resource = gajim.get_resource_from_jid(file_props['sender']) sid = file_props['sid'] - gajim.socks5queue.add_file_props(session.ourjid, file_props) + wr_ourjid = gajim.get_jid_without_resource(session.ourjid) + gajim.socks5queue.add_file_props(wr_ourjid, file_props) if not session.accepted: session.approve_session() From e5eb62e12b4cbeef3b1336638ebb049a1abf23f8 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 6 Jul 2010 12:29:21 +0200 Subject: [PATCH 016/121] correctly get connection object in JingleTransportSocks5 --- src/common/jingle_ft.py | 2 ++ src/common/jingle_transport.py | 31 ++++++++++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 8a3a4d024..872274022 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -66,6 +66,7 @@ class JingleFileTransfer(JingleContent): self.transport = JingleTransportSocks5() self.transport.set_file_props(self.file_props) self.transport.set_our_jid(session.ourjid) + self.transport.set_connection(session.connection) log.info('ourjid: %s' % session.ourjid) self.session = session @@ -101,6 +102,7 @@ class JingleFileTransfer(JingleContent): if self.transport is None: self.transport = JingleTransportSocks5() self.transport.set_our_jid(self.session.ourjid) + self.transport.set_connection(self.session.connection) self.transport.set_file_props(self.file_props) if self.file_props.has_key("streamhosts"): self.file_props['streamhosts'].extend(self.transport.remote_candidates) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 7187fbb48..7806d0aac 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -85,6 +85,7 @@ class JingleTransportSocks5(JingleTransport): """ def __init__(self): JingleTransport.__init__(self, TransportType.streaming) + self.connection = None self.remote_candidates = [] def set_file_props(self, file_props): @@ -92,6 +93,9 @@ class JingleTransportSocks5(JingleTransport): def set_our_jid(self, jid): self.ourjid = jid + + def set_connection(self, conn): + self.connection = conn def make_candidate(self, candidate): import logging @@ -134,13 +138,13 @@ class JingleTransportSocks5(JingleTransport): def _add_local_ips_as_candidates(self): + if not self.connection: + return local_ip_cand = [] port = gajim.config.get('file_transfers_port') type_preference = 126 #type preference of connection type. XEP-0260 section 2.2 - jid_wo_resource = gajim.get_jid_without_resource(self.ourjid) - conn = gajim.connections[jid_wo_resource] - c = {'host': conn.peerhost[0]} - c['candidate_id'] = conn.connection.getAnID() + c = {'host': self.connection.peerhost[0]} + c['candidate_id'] = self.connection.connection.getAnID() c['port'] = port c['type'] = 'direct' c['jid'] = self.ourjid @@ -161,18 +165,18 @@ class JingleTransportSocks5(JingleTransport): self.candidates += local_ip_cand def _add_additional_candidates(self): + if not self.connection: + return type_preference = 126 additional_ip_cand = [] port = gajim.config.get('file_transfers_port') ft_add_hosts = gajim.config.get('ft_add_hosts_to_send') - jid_wo_resource = gajim.get_jid_without_resource(self.ourjid) - conn = gajim.connections[jid_wo_resource] if ft_add_hosts: hosts = [e.strip() for e in ft_add_hosts.split(',')] for h in hosts: c = {'host': h} - c['candidate_id'] = conn.connection.getAnID() + c['candidate_id'] = self.connection.connection.getAnID() c['port'] = port c['type'] = 'direct' c['jid'] = self.ourjid @@ -181,22 +185,23 @@ class JingleTransportSocks5(JingleTransport): self.candidates += additional_ip_cand def _add_proxy_candidates(self): + if not self.connection: + return type_preference = 10 proxy_cand = [] socks5conn = ConnectionSocks5Bytestream() socks5conn.name = self.ourjid proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props) - jid_wo_resource = gajim.get_jid_without_resource(self.ourjid) - conn = gajim.connections[jid_wo_resource] if proxyhosts: - file_props['proxy_receiver'] = unicode(file_props['receiver']) - file_props['proxy_sender'] = unicode(file_props['sender']) - file_props['proxyhosts'] = proxyhosts + self.file_props['proxy_receiver'] = unicode( + self.file_props['receiver']) + self.file_props['proxy_sender'] = unicode(self.file_props['sender']) + self.file_props['proxyhosts'] = proxyhosts for proxyhost in proxyhosts: c = {'host': proxyhost['host']} - c['candidate_id'] = conn.connection.getAnID() + c['candidate_id'] = self.connection.connection.getAnID() c['port'] = proxyhost['port'] c['type'] = 'proxy' c['jid'] = self.ourjid From 8b66c1e69dd10100b797d17a9d5ae8584760f2bd Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Wed, 7 Jul 2010 22:39:01 +0800 Subject: [PATCH 017/121] send session-terminate on file transfer completion --- src/gui_interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui_interface.py b/src/gui_interface.py index 769b14ded..b4551e4cb 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -79,6 +79,7 @@ from common import helpers from common import dataforms from common import passwords from common import logging_helpers +from common import jingle import roster_window import profile_window @@ -1505,6 +1506,12 @@ class Interface: txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' + # if we are the sender of the file and the file transfer was initiated with jingle + # send session-terminate stanza + if file_props.has_key('session-type') and file_props['session-type'] == 'jingle': + sender = gajim.get_jid_without_resource(file_props['sender']) + jingle_session = gajim.connections[sender].get_jingle_session(sender, file_props['sid']) + jingle_session.end_session() path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' From 6f5b6f261221dec3062c8c02c93665a8ec89092a Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Wed, 7 Jul 2010 23:14:29 +0800 Subject: [PATCH 018/121] use the 'in' operator on a dict instead of has_key --- src/gui_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui_interface.py b/src/gui_interface.py index b4551e4cb..4a63b5a82 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1508,7 +1508,7 @@ class Interface: img_name = 'gajim-ft_stopped' # if we are the sender of the file and the file transfer was initiated with jingle # send session-terminate stanza - if file_props.has_key('session-type') and file_props['session-type'] == 'jingle': + if 'session-type' in file_props and file_props['session-type'] == 'jingle': sender = gajim.get_jid_without_resource(file_props['sender']) jingle_session = gajim.connections[sender].get_jingle_session(sender, file_props['sid']) jingle_session.end_session() From d55ba9269edfe69b902ce9474e6ebbb7fb9c1f05 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 8 Jul 2010 16:05:19 +0800 Subject: [PATCH 019/121] fix bug: assign jingle session id as sid in file_props, so that file transfer window gets updated correctly for the sender. --- src/common/jingle.py | 4 +++- src/filetransfers_window.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index b63273a7c..004760349 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -138,13 +138,15 @@ class ConnectionJingle(object): return jingle.sid def start_file_transfer(self, jid, file_props): - logger.info("start file transfer with file: %s", str(file_props)) + logger.info("start file transfer with file: %s" % file_props) jingle = self.get_jingle_session(jid, media='file') if jingle: + file_props['sid'] = jingle.sid jingle.add_content('file', JingleFileTransfer(jingle, file_props)) else: jingle = JingleSession(self, weinitiate=True, jid=jid) self.__sessions[jingle.sid] = jingle + file_props['sid'] = jingle.sid jingle.add_content('file', JingleFileTransfer(jingle, file_props=file_props)) jingle.start_session() return jingle.sid diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 1036ebe97..4fd550418 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -302,13 +302,17 @@ class FileTransfersWindow: file_path, file_name, file_desc) if file_props is None: return False - self.add_transfer(account, contact, file_props) if contact.supports(NS_JINGLE_FILE_TRANSFER): log.info("contact %s supports jingle file transfer"%(contact.get_full_jid())) - gajim.connections[account].start_file_transfer(contact.get_full_jid(), file_props) + # this call has the side effect of setting file_props['sid'] to the jingle sid, but for the sake of clarity + # make it explicit here + sid = gajim.connections[account].start_file_transfer(contact.get_full_jid(), file_props) + file_props['sid'] = sid + self.add_transfer(account, contact, file_props) else: log.info("contact does not support jingle file transfer") gajim.connections[account].send_file_request(file_props) + self.add_transfer(account, contact, file_props) return True def _start_receive(self, file_path, account, contact, file_props): From 2fec9f56f37f3b3d98cf478661865a1e3e8f916e Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 12 Jul 2010 10:20:31 +0800 Subject: [PATCH 020/121] generate proxy host correctly --- src/common/jingle_transport.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 7806d0aac..fc0a6f264 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -189,8 +189,7 @@ class JingleTransportSocks5(JingleTransport): return type_preference = 10 proxy_cand = [] - socks5conn = ConnectionSocks5Bytestream() - socks5conn.name = self.ourjid + socks5conn = self.connection proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props) if proxyhosts: @@ -204,7 +203,7 @@ class JingleTransportSocks5(JingleTransport): c['candidate_id'] = self.connection.connection.getAnID() c['port'] = proxyhost['port'] c['type'] = 'proxy' - c['jid'] = self.ourjid + c['jid'] = proxyhost['jid'] c['priority'] = (2**16) * type_preference proxy_cand.append(c) self.candidates += proxy_cand From c29fbdb61759285a8befe535a980e735937a19ee Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 13 Jul 2010 10:38:31 +0800 Subject: [PATCH 021/121] enable FT over proxy --- src/common/jingle_ft.py | 37 ++++++++++++++++++++++++++++++++-- src/common/jingle_transport.py | 21 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 872274022..a0f931aae 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -24,6 +24,7 @@ import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 from common import helpers +from common.socks5 import Socks5Receiver import logging @@ -99,6 +100,7 @@ class JingleFileTransfer(JingleContent): file_props['transfered_size'] = [] self.file_props = file_props + self.session.connection.files_props[file_props['sid']] = file_props if self.transport is None: self.transport = JingleTransportSocks5() self.transport.set_our_jid(self.session.ourjid) @@ -134,13 +136,44 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") - jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.send_file(self.file_props, jid) + streamhost_cid = content.getTag('transport').getTag('candidate-used').getAttr('cid') + streamhost_used = None + for cand in self.transport.candidates: + if cand['candidate_id'] == streamhost_cid: + streamhost_used = cand + break + if streamhost_used == None: + log.info("unknow streamhost") + return + if streamhost_used['type'] == 'proxy': + self.file_props['streamhost-used'] = True + for proxy in self.file_props['proxyhosts']: + if proxy['host'] == streamhost_used['host'] and \ + proxy['port'] == streamhost_used['port'] and \ + proxy['jid'] == streamhost_used['jid']: + streamhost_used = proxy + break + if 'streamhosts' not in self.file_props: + self.file_props['streamhosts'] = [] + self.file_props['streamhosts'].append(streamhost_used) + self.file_props['is_a_proxy'] = True + receiver = Socks5Receiver(gajim.idlequeue, streamhost_used, + self.file_props['sid'], self.file_props) + #gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) + gajim.socks5queue.add_receiver(self.session.ourjid, receiver) + streamhost_used['idx'] = receiver.queue_idx + gajim.socks5queue.on_success = self.transport._on_proxy_auth_ok + pass + else: + jid = gajim.get_jid_without_resource(self.session.ourjid) + gajim.socks5queue.send_file(self.file_props, jid) + def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") if self.weinitiate: + self.session.connection.files_props[self.file_props['sid']] = self.file_props receiver = self.file_props['receiver'] sender = self.file_props['sender'] diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index fc0a6f264..009c1eb4f 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -160,6 +160,8 @@ class JingleTransportSocks5(JingleTransport): c['type'] = 'direct' c['jid'] = self.ourjid c['priority'] = (2**16) * type_preference + c['initiator'] = self.file_props['sender'] + c['target'] = self.file_props['receiver'] local_ip_cand.append(c) self.candidates += local_ip_cand @@ -181,6 +183,8 @@ class JingleTransportSocks5(JingleTransport): c['type'] = 'direct' c['jid'] = self.ourjid c['priority'] = (2**16) * type_preference + c['initiator'] = self.file_props['sender'] + c['target'] = self.file_props['receiver'] additional_ip_cand.append(c) self.candidates += additional_ip_cand @@ -205,9 +209,26 @@ class JingleTransportSocks5(JingleTransport): c['type'] = 'proxy' c['jid'] = proxyhost['jid'] c['priority'] = (2**16) * type_preference + c['initiator'] = self.file_props['sender'] + c['target'] = self.file_props['receiver'] proxy_cand.append(c) self.candidates += proxy_cand + def _on_proxy_auth_ok(self, proxy): + log.info('proxy auth ok for ' + str(proxy)) + # send activate request to proxy, send activated confirmation to peer + if not self.connection: + return + file_props = self.file_props + iq = xmpp.Iq(to=proxy['initiator'], typ='set') + auth_id = "au_" + proxy['sid'] + iq.setID(auth_id) + query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) + query.setAttr('sid', proxy['sid']) + activate = query.setTag('activate') + activate.setData(file_props['proxy_receiver']) + iq.setID(auth_id) + self.connection.connection.send(iq) import farsight From 2938838f54e354af420f448b7acba4977da171b4 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 13 Jul 2010 10:43:14 +0800 Subject: [PATCH 022/121] remove "pass" --- src/common/jingle_ft.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index a0f931aae..237422774 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -164,7 +164,6 @@ class JingleFileTransfer(JingleContent): gajim.socks5queue.add_receiver(self.session.ourjid, receiver) streamhost_used['idx'] = receiver.queue_idx gajim.socks5queue.on_success = self.transport._on_proxy_auth_ok - pass else: jid = gajim.get_jid_without_resource(self.session.ourjid) gajim.socks5queue.send_file(self.file_props, jid) From 7d0029879a575e2d2ebdbf03cda27198267dcc80 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 15 Jul 2010 13:38:53 +0800 Subject: [PATCH 023/121] send proxy activated stanza to peer --- src/common/jingle_ft.py | 31 +++++++++++++++++++++++-------- src/common/jingle_session.py | 1 + src/common/jingle_transport.py | 24 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 237422774..fb6b43c7e 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -30,6 +30,11 @@ import logging log = logging.getLogger('gajim.c.jingle_ft') +STATE_NOT_STARTED = 0 +STATE_INITIALIZED = 1 +STATE_ACCEPTED = 2 +STATE_TRANSPORT_INFO = 3 +STATE_PROXY_ACTIVATED = 4 class JingleFileTransfer(JingleContent): def __init__(self, session, transport=None, file_props=None): @@ -47,6 +52,8 @@ class JingleFileTransfer(JingleContent): self.callbacks['transport-info'] += [self.__on_transport_info] self.callbacks['iq-result'] += [self.__on_iq_result] + self.state = STATE_NOT_STARTED + self.file_props = file_props if file_props is None: self.weinitiate = False @@ -137,6 +144,8 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") + if not self.weinitiate: # proxy activated from initiator + return streamhost_cid = content.getTag('transport').getTag('candidate-used').getAttr('cid') streamhost_used = None for cand in self.transport.candidates: @@ -171,7 +180,8 @@ class JingleFileTransfer(JingleContent): def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") - if self.weinitiate: + if self.weinitiate and self.state == STATE_NOT_STARTED: + self.state = STATE_INITIALIZED self.session.connection.files_props[self.file_props['sid']] = self.file_props receiver = self.file_props['receiver'] sender = self.file_props['sender'] @@ -187,13 +197,18 @@ class JingleFileTransfer(JingleContent): if not listener: return # send error message, notify the user - else: # session-accept iq-result - if not gajim.socks5queue.get_file_props(self.session.ourjid, self.file_props['sid']): - gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) - jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.connect_to_hosts(jid, self.file_props['sid'], - self.send_candidate_used, self._on_connect_error) - + elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result + self.state = STATE_ACCEPTED + if not gajim.socks5queue.get_file_props(self.session.ourjid, self.file_props['sid']): + gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) + jid = gajim.get_jid_without_resource(self.session.ourjid) + gajim.socks5queue.connect_to_hosts(jid, self.file_props['sid'], + self.send_candidate_used, self._on_connect_error) + elif not self.weinitiate and self.state == STATE_ACCEPTED: # transport-info iq-result + self.state = STATE_TRANSPORT_INFO + elif self.weinitiate and self.state == STATE_INITIALIZED: # proxy activated + self.state = STATE_PROXY_ACTIVATED + def send_candidate_used(self, streamhost): """ send candidate-used stanza diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index d0302359d..862b6cf2e 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -277,6 +277,7 @@ class JingleSession(object): stanza, jingle = self.__make_jingle('transport-info') jingle.addChild(node=content) self.connection.connection.send(stanza) + self.collect_iq_id(stanza.getID()) def send_description_info(self, content): assert self.state != JingleStates.ended diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 009c1eb4f..7c1adf374 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -229,6 +229,30 @@ class JingleTransportSocks5(JingleTransport): activate.setData(file_props['proxy_receiver']) iq.setID(auth_id) self.connection.connection.send(iq) + + content = xmpp.Node('content') + content.setAttr('creator', 'initiator') + content.setAttr('name', 'file') + transport = xmpp.Node('transport') + transport.setAttr('xmlns', xmpp.NS_JINGLE_BYTESTREAM) + activated = xmpp.Node('activated') + cid = None + for host in self.candidates: + if host['host'] == proxy['host'] and \ + host['jid'] == proxy['jid'] and \ + host['port'] == proxy['port']: + cid = host['candidate_id'] + break + if cid is None: + return + activated.setAttr('cid', cid) + transport.addChild(node=activated) + content.addChild(node=transport) + sesn = self.connection.get_jingle_session(self.ourjid, self.file_props['sid']) + + if sesn is None: + return + sesn.send_transport_info(content) import farsight From e75dc0c4085103358e8f639bfe5228570329f002 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 15 Jul 2010 14:32:34 +0800 Subject: [PATCH 024/121] fix incorrect use of jid where account name should be used as parameter --- src/common/jingle_ft.py | 12 +++++------- src/common/protocol/bytestream.py | 3 +-- src/gui_interface.py | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index fb6b43c7e..3dcc09c0e 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -169,13 +169,12 @@ class JingleFileTransfer(JingleContent): self.file_props['is_a_proxy'] = True receiver = Socks5Receiver(gajim.idlequeue, streamhost_used, self.file_props['sid'], self.file_props) - #gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) - gajim.socks5queue.add_receiver(self.session.ourjid, receiver) + gajim.socks5queue.add_receiver(self.session.connection.name, receiver) streamhost_used['idx'] = receiver.queue_idx gajim.socks5queue.on_success = self.transport._on_proxy_auth_ok else: jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.send_file(self.file_props, jid) + gajim.socks5queue.send_file(self.file_props, self.session.connection.name) def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") @@ -199,10 +198,9 @@ class JingleFileTransfer(JingleContent): # send error message, notify the user elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result self.state = STATE_ACCEPTED - if not gajim.socks5queue.get_file_props(self.session.ourjid, self.file_props['sid']): - gajim.socks5queue.add_file_props(self.session.ourjid, self.file_props) - jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.connect_to_hosts(jid, self.file_props['sid'], + if not gajim.socks5queue.get_file_props(self.session.connection.name, self.file_props['sid']): + gajim.socks5queue.add_file_props(self.session.connection.name, self.file_props) + gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], self.send_candidate_used, self._on_connect_error) elif not self.weinitiate and self.state == STATE_ACCEPTED: # transport-info iq-result self.state = STATE_TRANSPORT_INFO diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index a1ed9d593..ff0bd6599 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -138,8 +138,7 @@ class ConnectionBytestream: jid = gajim.get_jid_without_resource(file_props['sender']) resource = gajim.get_resource_from_jid(file_props['sender']) sid = file_props['sid'] - wr_ourjid = gajim.get_jid_without_resource(session.ourjid) - gajim.socks5queue.add_file_props(wr_ourjid, file_props) + gajim.socks5queue.add_file_props(self.name, file_props) if not session.accepted: session.approve_session() diff --git a/src/gui_interface.py b/src/gui_interface.py index 4a63b5a82..9f77390d3 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1510,7 +1510,7 @@ class Interface: # send session-terminate stanza if 'session-type' in file_props and file_props['session-type'] == 'jingle': sender = gajim.get_jid_without_resource(file_props['sender']) - jingle_session = gajim.connections[sender].get_jingle_session(sender, file_props['sid']) + jingle_session = gajim.connections[account].get_jingle_session(sender, file_props['sid']) jingle_session.end_session() path = gtkgui_helpers.get_icon_path(img_name, 48) else: From 4583482246fa1af23f64a640d763ed2329074da4 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 19 Jul 2010 14:21:01 +0800 Subject: [PATCH 025/121] add namespace for XTLS --- src/common/connection_handlers.py | 3 ++- src/common/helpers.py | 1 + src/common/xmpp/protocol.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 26a23a45d..9b289ca5a 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -182,7 +182,8 @@ class ConnectionDisco: query = iq.setTag('query') query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[0]) for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, - common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS, common.xmpp.NS_JINGLE_FILE_TRANSFER): + common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS, + common.xmpp.NS_JINGLE_FILE_TRANSFER, common.xmpp.NS_JINGLE_XTLS): feature = common.xmpp.Node('feature') feature.setAttr('var', f) query.addChild(node=feature) diff --git a/src/common/helpers.py b/src/common/helpers.py index 6e1b5df7b..eb8a22967 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -1317,6 +1317,7 @@ def update_optional_features(account = None): gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO) gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_FILE_TRANSFER) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_XTLS) gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity], gajim.gajim_common_features + gajim.gajim_optional_features[a]) # re-send presence with new hash diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 746bce722..319851570 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -70,6 +70,7 @@ NS_JINGLE_RTP ='urn:xmpp:jingle:apps:rtp:1' # XEP-01 NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio' # XEP-0167 NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-0167 NS_JINGLE_FILE_TRANSFER='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234 +NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260 From 30cbdce234b7fda7213aabb986d0ea8bb06f58f7 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 22 Jul 2010 15:05:06 +0800 Subject: [PATCH 026/121] if the peer supports jingle XTLS, send session-initiate with security element --- src/common/jingle.py | 17 +++++++++++++++-- src/common/jingle_ft.py | 13 ++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 004760349..0aa23be21 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -36,6 +36,7 @@ import helpers from jingle_session import JingleSession, JingleStates from jingle_rtp import JingleAudio, JingleVideo from jingle_ft import JingleFileTransfer +import gajim import logging logger = logging.getLogger('gajim.c.jingle') @@ -140,14 +141,26 @@ class ConnectionJingle(object): def start_file_transfer(self, jid, file_props): logger.info("start file transfer with file: %s" % file_props) jingle = self.get_jingle_session(jid, media='file') + contact = gajim.contacts.get_contact_with_highest_priority(self.name, + gajim.get_jid_without_resource(jid)) + if contact is None: + return + if contact.supports(xmpp.NS_JINGLE_XTLS): + use_security = True + else: + use_security = False if jingle: file_props['sid'] = jingle.sid - jingle.add_content('file', JingleFileTransfer(jingle, file_props)) + jingle.add_content('file', + JingleFileTransfer(jingle, file_props = file_props, use_security=use_security) + ) else: jingle = JingleSession(self, weinitiate=True, jid=jid) self.__sessions[jingle.sid] = jingle file_props['sid'] = jingle.sid - jingle.add_content('file', JingleFileTransfer(jingle, file_props=file_props)) + jingle.add_content('file', + JingleFileTransfer(jingle, file_props=file_props, use_security=use_security) + ) jingle.start_session() return jingle.sid diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 3dcc09c0e..bc08c741d 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -37,7 +37,7 @@ STATE_TRANSPORT_INFO = 3 STATE_PROXY_ACTIVATED = 4 class JingleFileTransfer(JingleContent): - def __init__(self, session, transport=None, file_props=None): + def __init__(self, session, transport=None, file_props=None, use_security=False): JingleContent.__init__(self, session, transport) log.info("transport value: %s" % transport) @@ -54,6 +54,8 @@ class JingleFileTransfer(JingleContent): self.state = STATE_NOT_STARTED + self.use_security = use_security + self.file_props = file_props if file_props is None: self.weinitiate = False @@ -246,6 +248,15 @@ class JingleFileTransfer(JingleContent): desc.setData(self.file_props['desc']) description_node.addChild(node=sioffer) + + if self.use_security: + security = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_XTLS + ' security') + # TODO: add fingerprint element + for m in ('x509', ): # supported authentication methods + method = xmpp.simplexml.Node(tag='method') + method.setAttr('name', m) + security.addChild(node=method) + content.addChild(node=security) content.addChild(node=description_node) From 84debaabcaf749742b0840aeecea65c452864248 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 22 Jul 2010 16:20:14 +0800 Subject: [PATCH 027/121] if peer does not include a security element in content node, set use_security to false --- src/common/jingle_ft.py | 11 +++++++++++ src/common/protocol/bytestream.py | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index bc08c741d..eec4aa79a 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -92,6 +92,12 @@ class JingleFileTransfer(JingleContent): file_props['session-type'] = 'jingle' + security = content.getTag('security') + if not security: + self.use_security = False + else: + self.use_security = True + file_tag = content.getTag('description').getTag('offer').getTag('file') for attribute in file_tag.getAttrs(): if attribute in ('name', 'size', 'hash', 'date'): @@ -130,6 +136,11 @@ class JingleFileTransfer(JingleContent): def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") + + security = content.getTag('security') + if not security: # responder can not verify our fingerprint + self.use_security = False + def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index ff0bd6599..509b01218 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -135,8 +135,6 @@ class ConnectionBytestream: session = self.get_jingle_session(file_props['sender'], file_props['sid']) if not session: return - jid = gajim.get_jid_without_resource(file_props['sender']) - resource = gajim.get_resource_from_jid(file_props['sender']) sid = file_props['sid'] gajim.socks5queue.add_file_props(self.name, file_props) From 797b3fe6a9f60d2e9037c7ce7b26bc70781f1f67 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sat, 24 Jul 2010 10:12:54 +0800 Subject: [PATCH 028/121] remove unnecessary branch --- src/common/jingle.py | 5 +---- src/common/jingle_ft.py | 8 +------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 0aa23be21..2bb674a46 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -145,10 +145,7 @@ class ConnectionJingle(object): gajim.get_jid_without_resource(jid)) if contact is None: return - if contact.supports(xmpp.NS_JINGLE_XTLS): - use_security = True - else: - use_security = False + use_security = contact.supports(xmpp.NS_JINGLE_XTLS) if jingle: file_props['sid'] = jingle.sid jingle.add_content('file', diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index eec4aa79a..4bf8e6c05 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -92,11 +92,7 @@ class JingleFileTransfer(JingleContent): file_props['session-type'] = 'jingle' - security = content.getTag('security') - if not security: - self.use_security = False - else: - self.use_security = True + self.use_security = bool(content.getTag('security')) file_tag = content.getTag('description').getTag('offer').getTag('file') for attribute in file_tag.getAttrs(): @@ -132,8 +128,6 @@ class JingleFileTransfer(JingleContent): self.session.connection.dispatch('FILE_REQUEST', (jid, file_props)) - - def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") From 216c370c1f3c4e5f417cfe26ac8caa4f9b3e23a7 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Mon, 26 Jul 2010 20:57:11 +0800 Subject: [PATCH 029/121] add fingerprint argument, we shall use it to retrieve certificates later on. --- src/common/socks5.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index f411dae9f..364f0eadc 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -79,14 +79,14 @@ class SocksQueue: self.on_success = None self.on_failure = None - def start_listener(self, port, sha_str, sha_handler, sid): + def start_listener(self, port, sha_str, sha_handler, sid, fingerprint=None): """ Start waiting for incomming connections on (host, port) and do a socks5 authentication using sid for generated SHA """ self.sha_handlers[sha_str] = (sha_handler, sid) if self.listener is None: - self.listener = Socks5Listener(self.idlequeue, port) + self.listener = Socks5Listener(self.idlequeue, port, fingerprint=fingerprint) self.listener.queue = self self.listener.bind() if self.listener.started is False: @@ -117,7 +117,7 @@ class SocksQueue: return 1 return 0 - def connect_to_hosts(self, account, sid, on_success=None, on_failure=None): + def connect_to_hosts(self, account, sid, on_success=None, on_failure=None, fingerprint=None): self.on_success = on_success self.on_failure = on_failure file_props = self.files_props[account][sid] @@ -125,7 +125,7 @@ class SocksQueue: # add streamhosts to the queue for streamhost in file_props['streamhosts']: - receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props) + receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props, fingerprint=fingerprint) self.add_receiver(account, receiver) streamhost['idx'] = receiver.queue_idx @@ -848,12 +848,14 @@ class Socks5Sender(Socks5, IdleObject): self.queue.remove_sender(self.queue_idx, False) class Socks5Listener(IdleObject): - def __init__(self, idlequeue, port): + def __init__(self, idlequeue, port, fingerprint=None): """ Handle all incomming connections on (0.0.0.0, port) This class implements IdleObject, but we will expect only pollin events though + + fingerprint: fingerprint of certificates we shall use, set to None if TLS connection not desired """ self.port = port self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC, @@ -939,7 +941,10 @@ class Socks5Listener(IdleObject): return _sock class Socks5Receiver(Socks5, IdleObject): - def __init__(self, idlequeue, streamhost, sid, file_props = None): + def __init__(self, idlequeue, streamhost, sid, file_props = None, fingerprint=None): + """ + fingerprint: fingerprint of certificates we shall use, set to None if TLS connection not desired + """ self.queue_idx = -1 self.streamhost = streamhost self.queue = None From e9af72e9444fe5657581f2eb99ae1982f6752da4 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 27 Jul 2010 13:02:44 +0800 Subject: [PATCH 030/121] add jingle_xtls.py, get_context helper function --- src/common/jingle_ft.py | 1 + src/common/jingle_xtls.py | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/common/jingle_xtls.py diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 4bf8e6c05..9e3ec53a2 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -93,6 +93,7 @@ class JingleFileTransfer(JingleContent): file_props['session-type'] = 'jingle' self.use_security = bool(content.getTag('security')) + # TODO: extract fingerprint element, encryption method element for later use file_tag = content.getTag('description').getTag('offer').getTag('file') for attribute in file_tag.getAttrs(): diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py new file mode 100644 index 000000000..cc5ea3945 --- /dev/null +++ b/src/common/jingle_xtls.py @@ -0,0 +1,43 @@ +# -*- coding:utf-8 -*- +## src/common/jingle_xtls.py +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +import logging +log = logging.getLogger('gajim.c.jingle_xtls') + +PYOPENSSL_PRESENT = False + +try: + import OpenSSL + PYOPENSSL_PRESENT = True + from OpenSSL import SSL, Context +except ImportError: + log.info("PyOpenSSL not available") + +def default_callback(connection, certificate, error_num, depth, return_code): + log.info("certificate: %s" % certificate) + return return_code + +def get_context(fingerprint, verify_cb=None): + """ + constructs and returns the context objects + """ + ctx = SSL.Context(TLSv1_METHOD) + ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) + # TODO: set private key, set certificate, set verification path + return ctx + From 2b603fd7e1335a07143a5542c698cf2c26628d3b Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 27 Jul 2010 21:29:12 +0800 Subject: [PATCH 031/121] add some code to allow testing using some pre-existing certificates. TODO: manually handle handshake states to allow non-blocking I/O --- src/common/jingle_ft.py | 5 +++-- src/common/jingle_xtls.py | 12 +++++++++++- src/common/socks5.py | 13 +++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 9e3ec53a2..d340ddbf9 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -199,7 +199,7 @@ class JingleFileTransfer(JingleContent): port = gajim.config.get('file_transfers_port') listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props['sid']) + self._store_socks5_sid, self.file_props['sid'], fingerprint = 'server') if not listener: return @@ -209,7 +209,8 @@ class JingleFileTransfer(JingleContent): if not gajim.socks5queue.get_file_props(self.session.connection.name, self.file_props['sid']): gajim.socks5queue.add_file_props(self.session.connection.name, self.file_props) gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], - self.send_candidate_used, self._on_connect_error) + self.send_candidate_used, self._on_connect_error, + fingerprint = 'client') elif not self.weinitiate and self.state == STATE_ACCEPTED: # transport-info iq-result self.state = STATE_TRANSPORT_INFO elif self.weinitiate and self.state == STATE_INITIALIZED: # proxy activated diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index cc5ea3945..10a63bc4a 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -16,6 +16,8 @@ ## along with Gajim. If not, see . ## +import os + import logging log = logging.getLogger('gajim.c.jingle_xtls') @@ -36,8 +38,16 @@ def get_context(fingerprint, verify_cb=None): """ constructs and returns the context objects """ - ctx = SSL.Context(TLSv1_METHOD) + ctx = SSL.Context(SSL.TLSv1_METHOD) ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) # TODO: set private key, set certificate, set verification path + if fingerprint == 'server': # for testing purposes only + ctx.use_privatekey_file (os.path.expanduser('~/certs/server.pkey')) + ctx.use_certificate_file(os.path.expanduser('~/certs/server.cert')) + ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) + elif fingerprint == 'client': + ctx.use_privatekey_file (os.path.expanduser('~/certs/client.pkey')) + ctx.use_certificate_file(os.path.expanduser('~/certs/client.cert')) + ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) return ctx diff --git a/src/common/socks5.py b/src/common/socks5.py index 364f0eadc..09489bde0 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -36,6 +36,11 @@ from errno import EINPROGRESS from errno import EAFNOSUPPORT from xmpp.idlequeue import IdleObject +import jingle_xtls + +if jingle_xtls.PYOPENSSL_PRESENT: + import OpenSSL + import logging log = logging.getLogger('gajim.c.socks5') @@ -867,12 +872,16 @@ class Socks5Listener(IdleObject): self.started = False self._sock = None self.fd = -1 + self.fingerprint = fingerprint def bind(self): for ai in self.ais: # try the different possibilities (ipv6, ipv4, etc.) try: self._serv = socket.socket(*ai[:3]) + if not self.fingerprint is None: + self._serv = OpenSSL.SSL.Connection( + jingle_xtls.get_context('server'), self._serv) except socket.error, e: if e.args[0] == EAFNOSUPPORT: self.ai = None @@ -949,6 +958,7 @@ class Socks5Receiver(Socks5, IdleObject): self.streamhost = streamhost self.queue = None self.file_props = file_props + self.fingerprint = fingerprint self.connect_timeout = 0 self.connected = False self.pauses = 0 @@ -992,6 +1002,9 @@ class Socks5Receiver(Socks5, IdleObject): for ai in self.ais: try: self._sock = socket.socket(*ai[:3]) + if not self.fingerprint is None: + self._sock = OpenSSL.SSL.Connection( + jingle_xtls.get_context('client'), self._sock) # this will not block the GUI self._sock.setblocking(False) self._server = ai[4] From ae97a3ed837f1eca57ac625271f959b76ef0b1a4 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Thu, 29 Jul 2010 21:40:40 +0800 Subject: [PATCH 032/121] wrap IO operations on SSL.Connection objects in try, catch SSL exceptions caused by SSL rehandshake request and simply ignore, retrying the IO should succeed. --- src/common/jingle_xtls.py | 9 ++- src/common/socks5.py | 117 +++++++++++++++++++++++++------------- 2 files changed, 84 insertions(+), 42 deletions(-) diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 10a63bc4a..8fe6140e7 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -39,13 +39,16 @@ def get_context(fingerprint, verify_cb=None): constructs and returns the context objects """ ctx = SSL.Context(SSL.TLSv1_METHOD) - ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) - # TODO: set private key, set certificate, set verification path + if fingerprint == 'server': # for testing purposes only + ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) + ctx.use_privatekey_file (os.path.expanduser('~/certs/server.pkey')) ctx.use_certificate_file(os.path.expanduser('~/certs/server.cert')) ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) - elif fingerprint == 'client': + elif fingerprint == 'client': + ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) + ctx.use_privatekey_file (os.path.expanduser('~/certs/client.pkey')) ctx.use_certificate_file(os.path.expanduser('~/certs/client.cert')) ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) diff --git a/src/common/socks5.py b/src/common/socks5.py index 09489bde0..cf51601d8 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -317,7 +317,7 @@ class SocksQueue: sock_hash = sock.__hash__() if sock_hash not in self.senders: self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, - sock[0], sock[1][0], sock[1][1]) + sock[0], sock[1][0], sock[1][1], fingerprint='server') self.connected += 1 def process_result(self, result, actor): @@ -453,6 +453,10 @@ class Socks5: received = '' try: add = self._recv(64) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('SSL rehandshake request : ' + repr(e)) + raise e except Exception: add = '' received += add @@ -466,7 +470,11 @@ class Socks5: """ try: self._send(raw_data) - except Exception: + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('SSL rehandshake request :' + repr(e)) + raise e + except Exception, e: self.disconnect() return len(raw_data) @@ -487,6 +495,10 @@ class Socks5: lenn = 0 try: lenn = self._send(buff) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('SSL rehandshake request :' + repr(e)) + raise e except Exception, e: if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK): # peer stopped reading @@ -557,6 +569,10 @@ class Socks5: return 0 try: buff = self._recv(MAX_BUFF_LEN) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('SSL rehandshake request :' + repr(e)) + raise e except Exception: buff = '' current_time = self.idlequeue.current_time() @@ -682,7 +698,12 @@ class Socks5: """ Connect response: version, auth method """ - buff = self._recv() + try: + buff = self._recv() + except (SSL.WantReadError, SSL.WantWriteError, + SSL.WantX509LookupError), e: + log.info("SSL rehandshake request : " + repr(e)) + raise e try: version, method = struct.unpack('!BB', buff) except Exception: @@ -716,11 +737,15 @@ class Socks5Sender(Socks5, IdleObject): """ def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, - port=None): + port=None, fingerprint = None): + self.fingerprint = fingerprint self.queue_idx = sock_hash self.queue = parent Socks5.__init__(self, idlequeue, host, port, None, None, None) self._sock = _sock + if not self.fingerprint is None: + self._sock = OpenSSL.SSL.Connection( + jingle_xtls.get_context('server'), self._sock) self._sock.setblocking(False) self.fd = _sock.fileno() self._recv = _sock.recv @@ -782,17 +807,21 @@ class Socks5Sender(Socks5, IdleObject): def pollin(self): if self.connected: - if self.state < 5: - result = self.main() - if self.state == 4: - self.queue.result_sha(self.sha_msg, self.queue_idx) - if result == -1: - self.disconnect() - - elif self.state == 5: - if self.file_props is not None and self.file_props['type'] == 'r': - result = self.get_file_contents(0) - self.queue.process_result(result, self) + try: + if self.state < 5: + result = self.main() + if self.state == 4: + self.queue.result_sha(self.sha_msg, self.queue_idx) + if result == -1: + self.disconnect() + + elif self.state == 5: + if self.file_props is not None and self.file_props['type'] == 'r': + result = self.get_file_contents(0) + self.queue.process_result(result, self) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') else: self.disconnect() @@ -1028,19 +1057,24 @@ class Socks5Receiver(Socks5, IdleObject): def pollout(self): self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - self.send_raw(self._get_auth_buff()) - elif self.state == 3: # send 'connect' request - self.send_raw(self._get_request_buff(self._get_sha1_auth())) - elif self.file_props['type'] != 'r': - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) + try: + if self.state == 0: + self.do_connect() return - result = self.write_next() - self.queue.process_result(result, self) + elif self.state == 1: # send initially: version and auth types + self.send_raw(self._get_auth_buff()) + elif self.state == 3: # send 'connect' request + self.send_raw(self._get_request_buff(self._get_sha1_auth())) + elif self.file_props['type'] != 'r': + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + result = self.write_next() + self.queue.process_result(result, self) + return + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') return self.state += 1 # unplug and plug for reading @@ -1059,19 +1093,24 @@ class Socks5Receiver(Socks5, IdleObject): def pollin(self): self.idlequeue.remove_timeout(self.fd) if self.connected: - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) + try: + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + if self.state < 5: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + result = self.main(0) + self.queue.process_result(result, self) + elif self.state == 5: # wait for proxy reply + pass + elif self.file_props['type'] == 'r': + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.get_file_contents(0) + self.queue.process_result(result, self) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') return - if self.state < 5: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - result = self.main(0) - self.queue.process_result(result, self) - elif self.state == 5: # wait for proxy reply - pass - elif self.file_props['type'] == 'r': - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - result = self.get_file_contents(0) - self.queue.process_result(result, self) else: self.disconnect() From a3e5e42375968d7c9a4fe84969ebcb3627cdb3c4 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Fri, 6 Aug 2010 21:57:13 +0800 Subject: [PATCH 033/121] add code to generate self signed certificates --- src/common/jingle_xtls.py | 91 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 8fe6140e7..1b0e23d84 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -26,10 +26,14 @@ PYOPENSSL_PRESENT = False try: import OpenSSL PYOPENSSL_PRESENT = True - from OpenSSL import SSL, Context except ImportError: log.info("PyOpenSSL not available") +if PYOPENSSL_PRESENT: + from OpenSSL import SSL + from OpenSSL.SSL import Context + from OpenSSL import crypto + def default_callback(connection, certificate, error_num, depth, return_code): log.info("certificate: %s" % certificate) return return_code @@ -54,3 +58,88 @@ def get_context(fingerprint, verify_cb=None): ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) return ctx +# the following code is partly due to pyopenssl examples + +TYPE_RSA = crypto.TYPE_RSA +TYPE_DSA = crypto.TYPE_DSA + +def createKeyPair(type, bits): + """ + Create a public/private key pair. + + Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA + bits - Number of bits to use in the key + Returns: The public/private key pair in a PKey object + """ + pkey = crypto.PKey() + pkey.generate_key(type, bits) + return pkey + +def createCertRequest(pkey, digest="md5", **name): + """ + Create a certificate request. + + Arguments: pkey - The key to associate with the request + digest - Digestion method to use for signing, default is md5 + **name - The name of the subject of the request, possible + arguments are: + C - Country name + ST - State or province name + L - Locality name + O - Organization name + OU - Organizational unit name + CN - Common name + emailAddress - E-mail address + Returns: The certificate request in an X509Req object + """ + req = crypto.X509Req() + subj = req.get_subject() + + for (key,value) in name.items(): + setattr(subj, key, value) + + req.set_pubkey(pkey) + req.sign(pkey, digest) + return req + +def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="md5"): + """ + Generate a certificate given a certificate request. + + Arguments: req - Certificate reqeust to use + issuerCert - The certificate of the issuer + issuerKey - The private key of the issuer + serial - Serial number for the certificate + notBefore - Timestamp (relative to now) when the certificate + starts being valid + notAfter - Timestamp (relative to now) when the certificate + stops being valid + digest - Digest method to use for signing, default is md5 + Returns: The signed certificate in an X509 object + """ + cert = crypto.X509() + cert.set_serial_number(serial) + cert.gmtime_adj_notBefore(notBefore) + cert.gmtime_adj_notAfter(notAfter) + cert.set_issuer(issuerCert.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(issuerKey, digest) + return cert + +def make_certs(filepath, CN): + """ + make self signed certificates + filepath : absolute path of certificate file, will be appended the '.pkey' and '.cert' extensions + CN : common name + """ + key = createKeyPair(TYPE_RSA, 1024) + req = createCertRequest(key, CN=CN) + cert = createCertificate(req, (req, key), 0, (0, 60*60*24*365*5)) # five years + open(filepath + '.pkey', 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) + open(filepath + '.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + + +if __name__ == '__main__': + make_certs('./selfcert', 'gajim') + From 4fa60f9b3da0759a8ce420db9e8678afead2ea79 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sun, 8 Aug 2010 16:42:30 +0800 Subject: [PATCH 034/121] define pubkey callbacks --- src/common/connection_handlers.py | 13 ++++++++++++- src/common/xmpp/protocol.py | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 9b289ca5a..657462203 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -183,7 +183,9 @@ class ConnectionDisco: query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[0]) for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS, - common.xmpp.NS_JINGLE_FILE_TRANSFER, common.xmpp.NS_JINGLE_XTLS): + common.xmpp.NS_JINGLE_FILE_TRANSFER, common.xmpp.NS_JINGLE_XTLS, + common.xmpp.NS_PUBKEY_PUBKEY, common.xmpp.NS_PUBKEY_REVOKE, + common.xmpp.NS_PUBKEY_ATTEST): feature = common.xmpp.Node('feature') feature.setAttr('var', f) query.addChild(node=feature) @@ -2238,6 +2240,12 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): for i in iq_obj.getQueryPayload(): df[i.getName()] = i.getData() self.dispatch('SEARCH_FORM', (jid, df, False)) + + def _PubkeyGetCB(self, con, obj): + log.info('PubkeyGetCB') + + def _PubkeyResultCB(self, con, obj): + log.info('PubkeyResultCB') def _StreamCB(self, con, obj): if obj.getTag('conflict'): @@ -2334,3 +2342,6 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): con.RegisterHandler('presence', self._StanzaArrivedCB) con.RegisterHandler('message', self._StanzaArrivedCB) con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams') + con.RegisterHandler('iq', self._PubkeyGetCB, 'get', common.xmpp.NS_PUBKEY_PUBKEY) + con.RegisterHandler('iq', self._PubkeyResultCB, 'result', common.xmpp.NS_PUBKEY_PUBKEY) + diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 319851570..0f7b4c57c 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -131,6 +131,10 @@ NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' NS_RECEIPTS ='urn:xmpp:receipts' +NS_PUBKEY_PUBKEY='urn:xmpp:pubkey:2' # XEP-0189 +NS_PUBKEY_REVOKE='urn:xmpp:revoke:2' +NS_PUBKEY_ATTEST='urn:xmpp:attest:2' + xmpp_stream_error_conditions = ''' bad-format -- -- -- The entity has sent XML that cannot be processed. From 02c1eaf93003b533b7bdac562de0bf06efa0e41f Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sun, 8 Aug 2010 21:25:29 +0800 Subject: [PATCH 035/121] load multiple certificate files --- src/common/connection_handlers.py | 2 ++ src/common/jingle_xtls.py | 49 ++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 657462203..ef3fca1cf 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -51,6 +51,8 @@ from common.pep import ConnectionPEP from common.protocol.caps import ConnectionCaps from common.protocol.bytestream import ConnectionSocks5Bytestream import common.caps_cache as capscache +import common.jingle_xtls + if gajim.HAVE_FARSIGHT: from common.jingle import ConnectionJingle else: diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 1b0e23d84..5e7f6547d 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -34,10 +34,45 @@ if PYOPENSSL_PRESENT: from OpenSSL.SSL import Context from OpenSSL import crypto +SELF_SIGNED_CERTIFICATE = 'localcert' + def default_callback(connection, certificate, error_num, depth, return_code): log.info("certificate: %s" % certificate) return return_code +def load_cert_file(cert_path, cert_store): + """ + This is almost identical to the one in common.xmpp.tls_nb + """ + if not os.path.isfile(cert_path): + return + try: + f = open(cert_path) + except IOError, e: + log.warning('Unable to open certificate file %s: %s' % \ + (cert_path, str(e))) + return + lines = f.readlines() + i = 0 + begin = -1 + for line in lines: + if 'BEGIN CERTIFICATE' in line: + begin = i + elif 'END CERTIFICATE' in line and begin > -1: + cert = ''.join(lines[begin:i+2]) + try: + x509cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + cert_store.add_cert(x509cert) + except OpenSSL.crypto.Error, exception_obj: + log.warning('Unable to load a certificate from file %s: %s' %\ + (cert_path, exception_obj.args[0][0][2])) + except: + log.warning('Unknown error while loading certificate from file ' + '%s' % cert_path) + begin = -1 + i += 1 + def get_context(fingerprint, verify_cb=None): """ constructs and returns the context objects @@ -46,16 +81,15 @@ def get_context(fingerprint, verify_cb=None): if fingerprint == 'server': # for testing purposes only ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) - - ctx.use_privatekey_file (os.path.expanduser('~/certs/server.pkey')) - ctx.use_certificate_file(os.path.expanduser('~/certs/server.cert')) - ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) elif fingerprint == 'client': ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) - ctx.use_privatekey_file (os.path.expanduser('~/certs/client.pkey')) - ctx.use_certificate_file(os.path.expanduser('~/certs/client.cert')) - ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) + ctx.use_privatekey_file (os.path.expanduser('~/certs/' + SELF_SIGNED_CERTIFICATE + '.pkey')) + ctx.use_certificate_file(os.path.expanduser('~/certs/' + SELF_SIGNED_CERTIFICATE + '.cert')) + # ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) + store = ctx.get_cert_store() + for f in os.listdir(os.path.expanduser('~/certs/')): + load_cert_file(os.path.join(os.path.expanduser('~/certs'), f), store) return ctx # the following code is partly due to pyopenssl examples @@ -142,4 +176,3 @@ def make_certs(filepath, CN): if __name__ == '__main__': make_certs('./selfcert', 'gajim') - From e81072700252b0a33035afbdd0b640688d2729dc Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sun, 8 Aug 2010 21:55:32 +0800 Subject: [PATCH 036/121] create certs path if it does not exist --- src/common/check_paths.py | 9 +++++++++ src/common/jingle_xtls.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 456bc0e3e..4ce00dcf9 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -29,6 +29,7 @@ import stat from common import gajim import logger +from common import jingle_xtls # DO NOT MOVE ABOVE OF import gajim import sqlite3 as sqlite @@ -267,6 +268,7 @@ def check_and_possibly_create_paths(): MY_DATA = configpaths.gajimpaths['MY_DATA'] MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] MY_CACHE = configpaths.gajimpaths['MY_CACHE'] + XTLS_CERTS = os.path.expanduser('~/certs/') if not os.path.exists(MY_DATA): create_path(MY_DATA) @@ -332,6 +334,13 @@ def check_and_possibly_create_paths(): print _('%s is a directory but should be a file') % CACHE_DB_PATH print _('Gajim will now exit') sys.exit() + + if not os.path.exists(XTLS_CERTS): + create_path(XTLS_CERTS) + if not (os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.cert')) and + os.path.exist(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): + jingle_xtls.make_certs(XTLS_CERTS + jingle_xtls.SELF_SIGNED_CERTIFICATE, 'gajim') + def create_path(directory): print _('creating %s directory') % directory diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 5e7f6547d..1b1b00400 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -86,7 +86,6 @@ def get_context(fingerprint, verify_cb=None): ctx.use_privatekey_file (os.path.expanduser('~/certs/' + SELF_SIGNED_CERTIFICATE + '.pkey')) ctx.use_certificate_file(os.path.expanduser('~/certs/' + SELF_SIGNED_CERTIFICATE + '.cert')) - # ctx.load_verify_locations(os.path.expanduser('~/certs/CA.cert')) store = ctx.get_cert_store() for f in os.listdir(os.path.expanduser('~/certs/')): load_cert_file(os.path.join(os.path.expanduser('~/certs'), f), store) From 048d875b3be6695736dcca8595fa84bf7d84f806 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Sun, 8 Aug 2010 22:04:50 +0800 Subject: [PATCH 037/121] fix bug, os.path.exist -> os.path.exists. Define certificate path --- src/common/check_paths.py | 4 ++-- src/common/jingle_xtls.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 4ce00dcf9..9d623e333 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -268,7 +268,7 @@ def check_and_possibly_create_paths(): MY_DATA = configpaths.gajimpaths['MY_DATA'] MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] MY_CACHE = configpaths.gajimpaths['MY_CACHE'] - XTLS_CERTS = os.path.expanduser('~/certs/') + XTLS_CERTS = os.path.expanduser(jingle_xtls.CERTIFICATE_DIR) if not os.path.exists(MY_DATA): create_path(MY_DATA) @@ -338,7 +338,7 @@ def check_and_possibly_create_paths(): if not os.path.exists(XTLS_CERTS): create_path(XTLS_CERTS) if not (os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.cert')) and - os.path.exist(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): + os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): jingle_xtls.make_certs(XTLS_CERTS + jingle_xtls.SELF_SIGNED_CERTIFICATE, 'gajim') diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 1b1b00400..ea932b672 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -34,6 +34,7 @@ if PYOPENSSL_PRESENT: from OpenSSL.SSL import Context from OpenSSL import crypto +CERTIFICATE_DIR = '~/certs/' SELF_SIGNED_CERTIFICATE = 'localcert' def default_callback(connection, certificate, error_num, depth, return_code): @@ -84,11 +85,11 @@ def get_context(fingerprint, verify_cb=None): elif fingerprint == 'client': ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) - ctx.use_privatekey_file (os.path.expanduser('~/certs/' + SELF_SIGNED_CERTIFICATE + '.pkey')) - ctx.use_certificate_file(os.path.expanduser('~/certs/' + SELF_SIGNED_CERTIFICATE + '.cert')) + ctx.use_privatekey_file (os.path.expanduser(CERTIFICATE_DIR + SELF_SIGNED_CERTIFICATE + '.pkey')) + ctx.use_certificate_file(os.path.expanduser(CERTIFICATE_DIR + SELF_SIGNED_CERTIFICATE + '.cert')) store = ctx.get_cert_store() - for f in os.listdir(os.path.expanduser('~/certs/')): - load_cert_file(os.path.join(os.path.expanduser('~/certs'), f), store) + for f in os.listdir(os.path.expanduser(CERTIFICATE_DIR)): + load_cert_file(os.path.join(os.path.expanduser(CERTIFICATE_DIR), f), store) return ctx # the following code is partly due to pyopenssl examples From 91a68d30bee366cb7aeb18091bd83a33bdf5aae6 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 10 Aug 2010 20:34:46 +0800 Subject: [PATCH 038/121] add code to send/request certificates --- src/common/connection_handlers.py | 5 ++++ src/common/jingle_xtls.py | 44 +++++++++++++++++++++++++++++++ src/common/protocol/bytestream.py | 3 +++ src/gui_interface.py | 12 ++++++++- 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index ef3fca1cf..9c99f4f81 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -2245,9 +2245,14 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): def _PubkeyGetCB(self, con, obj): log.info('PubkeyGetCB') + jid_from = unicode(obj.getAttr('from')) + sid = obj.getAttr('id') + self.dispatch('PUBKEY_REQUEST', (con, obj, jid_from, sid)) def _PubkeyResultCB(self, con, obj): log.info('PubkeyResultCB') + jid_from = unicode(obj.getAttr('from')) + self.dispatch('PUBKEY_RESULT', (con, obj, jid_from)); def _StreamCB(self, con, obj): if obj.getTag('conflict'): diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index ea932b672..3ca718fa3 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -19,6 +19,8 @@ import os import logging +import common +import gajim log = logging.getLogger('gajim.c.jingle_xtls') PYOPENSSL_PRESENT = False @@ -92,6 +94,48 @@ def get_context(fingerprint, verify_cb=None): load_cert_file(os.path.join(os.path.expanduser(CERTIFICATE_DIR), f), store) return ctx +def send_cert(con, jid_from, sid): + certpath = os.path.expanduser(CERTIFICATE_DIR + SELF_SIGNED_CERTIFICATE + '.cert') + certfile = open(certpath, 'r') + certificate = '' + for line in certfile.readlines(): + if not line.startswith('-'): + certificate += line + iq = common.xmpp.Iq('result', to=jid_from); + iq.setAttr('id', sid) + + pubkey = iq.setTag('pubkeys') + pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY) + + keyinfo = pubkey.setTag('keyinfo') + name = keyinfo.setTag('name') + name.setData('CertificateHash') + cert = keyinfo.setTag('x509cert') + cert.setData(certificate) + + con.send(iq) + +def handle_new_cert(con, obj, jid_from): + jid = gajim.get_jid_without_resource(jid_from) + certpath = os.path.join(os.path.expanduser(CERTIFICATE_DIR), jid) + certpath += '.cert' + + x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert') + + cert = x509cert.getData() + + f = open(certpath, 'w') + f.write('-----BEGIN CERTIFICATE-----\n') + f.write(cert) + f.write('-----END CERTIFICATE-----\n') + +def send_cert_request(con, to_jid): + iq = common.xmpp.Iq('get', to=to_jid) + iq.setAttr('id', con.connection.getAnID()) + pubkey = iq.setTag('pubkeys') + pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY) + con.connection.send(iq) + # the following code is partly due to pyopenssl examples TYPE_RSA = crypto.TYPE_RSA diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 509b01218..1de47a465 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -34,6 +34,7 @@ from common import xmpp from common import gajim from common import helpers from common import dataforms +from common import jingle_xtls from common.socks5 import Socks5Receiver @@ -139,6 +140,8 @@ class ConnectionBytestream: gajim.socks5queue.add_file_props(self.name, file_props) if not session.accepted: + if session.get_content('file').use_security: + jingle_xtls.send_cert_request(self, file_props['receiver']) session.approve_session() session.approve_content('file') return diff --git a/src/gui_interface.py b/src/gui_interface.py index 9f77390d3..d7cedf00a 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -2111,6 +2111,14 @@ class Interface: pm_ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, account) if pm_ctrl and hasattr(pm_ctrl, "update_contact"): pm_ctrl.update_contact() + + def handle_event_pubkey_request(self, account, data): + con, obj, jid_from, sid = data + common.jingle_xtls.send_cert(con, jid_from, sid) + + def handle_event_pubkey_result(self, account, data): + con, obj, jid_from = data + common.jingle_xtls.handle_new_cert(con, obj, jid_from) def create_core_handlers_list(self): self.handlers = { @@ -2203,7 +2211,9 @@ class Interface: 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], 'JINGLE_ERROR': [self.handle_event_jingle_error], 'PEP_RECEIVED': [self.handle_event_pep_received], - 'CAPS_RECEIVED': [self.handle_event_caps_received] + 'CAPS_RECEIVED': [self.handle_event_caps_received], + 'PUBKEY_REQUEST': [self.handle_event_pubkey_request], + 'PUBKEY_RESULT': [self.handle_event_pubkey_result], } def register_core_handlers(self): From 42f6580d1d36c88724d96977e0838bf81b93ff59 Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Tue, 10 Aug 2010 21:10:45 +0800 Subject: [PATCH 039/121] move cert directory to ~/.local/share/gajim/certs --- src/common/check_paths.py | 2 +- src/common/configpaths.py | 4 +++- src/common/jingle_xtls.py | 12 ++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 9d623e333..29c6846c6 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -339,7 +339,7 @@ def check_and_possibly_create_paths(): create_path(XTLS_CERTS) if not (os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.cert')) and os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): - jingle_xtls.make_certs(XTLS_CERTS + jingle_xtls.SELF_SIGNED_CERTIFICATE, 'gajim') + jingle_xtls.make_certs(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE), 'gajim') def create_path(directory): diff --git a/src/common/configpaths.py b/src/common/configpaths.py index 465e76676..8c2df8b58 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -140,7 +140,8 @@ class ConfigPaths: d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem', 'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets', - 'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities'} + 'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities', + 'MY_PEER_CERTS': u'certs'} for name in d: self.add(name, TYPE_DATA, windowsify(d[name])) @@ -150,6 +151,7 @@ class ConfigPaths: self.add(name, TYPE_CACHE, windowsify(d[name])) self.add('MY_CONFIG', TYPE_CONFIG, '') + self.add('MY_CERT', TYPE_CONFIG, 'localcert') basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir)) self.add('DATA', None, os.path.join(basedir, windowsify(u'data'))) diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 3ca718fa3..ace3c1817 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -23,6 +23,9 @@ import common import gajim log = logging.getLogger('gajim.c.jingle_xtls') +from common import configpaths +gajimpath = configpaths.gajimpaths + PYOPENSSL_PRESENT = False try: @@ -36,7 +39,8 @@ if PYOPENSSL_PRESENT: from OpenSSL.SSL import Context from OpenSSL import crypto -CERTIFICATE_DIR = '~/certs/' +CERTIFICATE_DIR = gajimpath['MY_PEER_CERTS'] +print 'CERTIFICATE_DIR: ', CERTIFICATE_DIR SELF_SIGNED_CERTIFICATE = 'localcert' def default_callback(connection, certificate, error_num, depth, return_code): @@ -87,15 +91,15 @@ def get_context(fingerprint, verify_cb=None): elif fingerprint == 'client': ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) - ctx.use_privatekey_file (os.path.expanduser(CERTIFICATE_DIR + SELF_SIGNED_CERTIFICATE + '.pkey')) - ctx.use_certificate_file(os.path.expanduser(CERTIFICATE_DIR + SELF_SIGNED_CERTIFICATE + '.cert')) + ctx.use_privatekey_file (os.path.expanduser(os.path.join(CERTIFICATE_DIR, SELF_SIGNED_CERTIFICATE) + '.pkey')) + ctx.use_certificate_file(os.path.expanduser(os.path.join(CERTIFICATE_DIR, SELF_SIGNED_CERTIFICATE) + '.cert')) store = ctx.get_cert_store() for f in os.listdir(os.path.expanduser(CERTIFICATE_DIR)): load_cert_file(os.path.join(os.path.expanduser(CERTIFICATE_DIR), f), store) return ctx def send_cert(con, jid_from, sid): - certpath = os.path.expanduser(CERTIFICATE_DIR + SELF_SIGNED_CERTIFICATE + '.cert') + certpath = os.path.expanduser(os.path.join(CERTIFICATE_DIR, SELF_SIGNED_CERTIFICATE) + '.cert') certfile = open(certpath, 'r') certificate = '' for line in certfile.readlines(): From 08c854aefaed1aaeff32a1227400d656717c34cd Mon Sep 17 00:00:00 2001 From: Zhenchao Li Date: Wed, 11 Aug 2010 00:50:14 +0800 Subject: [PATCH 040/121] put local certificates in ~/.config/gajim/ , accept session once key exchange completes --- src/common/check_paths.py | 7 ++++--- src/common/configpaths.py | 2 +- src/common/jingle_xtls.py | 29 ++++++++++++++++++++++++----- src/common/protocol/bytestream.py | 4 +++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 29c6846c6..c9f7eae6c 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -269,6 +269,7 @@ def check_and_possibly_create_paths(): MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] MY_CACHE = configpaths.gajimpaths['MY_CACHE'] XTLS_CERTS = os.path.expanduser(jingle_xtls.CERTIFICATE_DIR) + LOCAL_XTLS_CERTS = os.path.expanduser(jingle_xtls.LOCAL_CERT_DIR) if not os.path.exists(MY_DATA): create_path(MY_DATA) @@ -337,9 +338,9 @@ def check_and_possibly_create_paths(): if not os.path.exists(XTLS_CERTS): create_path(XTLS_CERTS) - if not (os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.cert')) and - os.path.exists(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): - jingle_xtls.make_certs(os.path.join(XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE), 'gajim') + if not (os.path.exists(os.path.join(LOCAL_XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.cert')) and + os.path.exists(os.path.join(LOCAL_XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): + jingle_xtls.make_certs(os.path.join(LOCAL_XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE), 'gajim') def create_path(directory): diff --git a/src/common/configpaths.py b/src/common/configpaths.py index 8c2df8b58..7cb10c666 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -151,7 +151,7 @@ class ConfigPaths: self.add(name, TYPE_CACHE, windowsify(d[name])) self.add('MY_CONFIG', TYPE_CONFIG, '') - self.add('MY_CERT', TYPE_CONFIG, 'localcert') + self.add('MY_CERT', TYPE_CONFIG, '') basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir)) self.add('DATA', None, os.path.join(basedir, windowsify(u'data'))) diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index ace3c1817..4cd097b2c 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -28,6 +28,16 @@ gajimpath = configpaths.gajimpaths PYOPENSSL_PRESENT = False +pending_sessions = {} # key-exchange id -> session, accept that session once key-exchange completes + +def key_exchange_pend(id, session): + pending_sessions[id] = session + +def approve_pending_session(id): + session = pending_sessions[id] + session.approve_session() + session.approve_content('file') + try: import OpenSSL PYOPENSSL_PRESENT = True @@ -40,7 +50,9 @@ if PYOPENSSL_PRESENT: from OpenSSL import crypto CERTIFICATE_DIR = gajimpath['MY_PEER_CERTS'] +LOCAL_CERT_DIR = gajimpath['MY_CERT'] print 'CERTIFICATE_DIR: ', CERTIFICATE_DIR +print 'MY_CERT_DIR: ', LOCAL_CERT_DIR SELF_SIGNED_CERTIFICATE = 'localcert' def default_callback(connection, certificate, error_num, depth, return_code): @@ -87,19 +99,20 @@ def get_context(fingerprint, verify_cb=None): ctx = SSL.Context(SSL.TLSv1_METHOD) if fingerprint == 'server': # for testing purposes only - ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) + ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) elif fingerprint == 'client': ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) - ctx.use_privatekey_file (os.path.expanduser(os.path.join(CERTIFICATE_DIR, SELF_SIGNED_CERTIFICATE) + '.pkey')) - ctx.use_certificate_file(os.path.expanduser(os.path.join(CERTIFICATE_DIR, SELF_SIGNED_CERTIFICATE) + '.cert')) + ctx.use_privatekey_file (os.path.expanduser(os.path.join(LOCAL_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.pkey')) + ctx.use_certificate_file(os.path.expanduser(os.path.join(LOCAL_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.cert')) store = ctx.get_cert_store() for f in os.listdir(os.path.expanduser(CERTIFICATE_DIR)): load_cert_file(os.path.join(os.path.expanduser(CERTIFICATE_DIR), f), store) + print 'certificate file' + f + ' loaded', 'fingerprint', fingerprint return ctx def send_cert(con, jid_from, sid): - certpath = os.path.expanduser(os.path.join(CERTIFICATE_DIR, SELF_SIGNED_CERTIFICATE) + '.cert') + certpath = os.path.expanduser(os.path.join(LOCAL_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.cert') certfile = open(certpath, 'r') certificate = '' for line in certfile.readlines(): @@ -124,6 +137,8 @@ def handle_new_cert(con, obj, jid_from): certpath = os.path.join(os.path.expanduser(CERTIFICATE_DIR), jid) certpath += '.cert' + id = obj.getAttr('id') + x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert') cert = x509cert.getData() @@ -133,12 +148,16 @@ def handle_new_cert(con, obj, jid_from): f.write(cert) f.write('-----END CERTIFICATE-----\n') + approve_pending_session(id) + def send_cert_request(con, to_jid): iq = common.xmpp.Iq('get', to=to_jid) - iq.setAttr('id', con.connection.getAnID()) + id = con.connection.getAnID() + iq.setAttr('id', id) pubkey = iq.setTag('pubkeys') pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY) con.connection.send(iq) + return unicode(id) # the following code is partly due to pyopenssl examples diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 1de47a465..dc637adb8 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -141,7 +141,9 @@ class ConnectionBytestream: if not session.accepted: if session.get_content('file').use_security: - jingle_xtls.send_cert_request(self, file_props['receiver']) + id = jingle_xtls.send_cert_request(self, file_props['sender']) + jingle_xtls.key_exchange_pend(id, session) + return session.approve_session() session.approve_content('file') return From fb41b653682f18c9cad57fcb0420364f1e6beb2d Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 11 Aug 2010 08:44:49 +0200 Subject: [PATCH 041/121] no need to go through GUI to handle cert request / reply --- src/common/connection_handlers.py | 21 +++++++++++---------- src/gui_interface.py | 10 ---------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 9c99f4f81..475101423 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -45,13 +45,13 @@ import common.xmpp from common import helpers from common import gajim from common import exceptions +from common import jingle_xtls from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub from common.pep import ConnectionPEP from common.protocol.caps import ConnectionCaps from common.protocol.bytestream import ConnectionSocks5Bytestream import common.caps_cache as capscache -import common.jingle_xtls if gajim.HAVE_FARSIGHT: from common.jingle import ConnectionJingle @@ -2242,17 +2242,18 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): for i in iq_obj.getQueryPayload(): df[i.getName()] = i.getData() self.dispatch('SEARCH_FORM', (jid, df, False)) - - def _PubkeyGetCB(self, con, obj): + + def _PubkeyGetCB(self, con, iq_obj): log.info('PubkeyGetCB') - jid_from = unicode(obj.getAttr('from')) - sid = obj.getAttr('id') - self.dispatch('PUBKEY_REQUEST', (con, obj, jid_from, sid)) - - def _PubkeyResultCB(self, con, obj): + jid_from = helpers.get_full_jid_from_iq(iq_obj) + sid = iq_obj.getAttr('id') + jingle_xtls.send_cert(con, jid_from, sid) + raise common.xmpp.NodeProcessed + + def _PubkeyResultCB(self, con, iq_obj): log.info('PubkeyResultCB') - jid_from = unicode(obj.getAttr('from')) - self.dispatch('PUBKEY_RESULT', (con, obj, jid_from)); + jid_from = helpers.get_full_jid_from_iq(iq_obj) + jingle_xtls.handle_new_cert(con, iq_obj, jid_from) def _StreamCB(self, con, obj): if obj.getTag('conflict'): diff --git a/src/gui_interface.py b/src/gui_interface.py index d7cedf00a..fa3e25e18 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -2111,14 +2111,6 @@ class Interface: pm_ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, account) if pm_ctrl and hasattr(pm_ctrl, "update_contact"): pm_ctrl.update_contact() - - def handle_event_pubkey_request(self, account, data): - con, obj, jid_from, sid = data - common.jingle_xtls.send_cert(con, jid_from, sid) - - def handle_event_pubkey_result(self, account, data): - con, obj, jid_from = data - common.jingle_xtls.handle_new_cert(con, obj, jid_from) def create_core_handlers_list(self): self.handlers = { @@ -2212,8 +2204,6 @@ class Interface: 'JINGLE_ERROR': [self.handle_event_jingle_error], 'PEP_RECEIVED': [self.handle_event_pep_received], 'CAPS_RECEIVED': [self.handle_event_caps_received], - 'PUBKEY_REQUEST': [self.handle_event_pubkey_request], - 'PUBKEY_RESULT': [self.handle_event_pubkey_result], } def register_core_handlers(self): From b6d746115d047e14a486bd561895fb1f9bddc599 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 11 Aug 2010 08:46:53 +0200 Subject: [PATCH 042/121] handle cert path more commonly --- src/common/check_paths.py | 16 ++++++++++------ src/common/gajim.py | 2 ++ src/common/jingle_xtls.py | 22 ++++++++-------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index c9f7eae6c..49e846a93 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -268,8 +268,8 @@ def check_and_possibly_create_paths(): MY_DATA = configpaths.gajimpaths['MY_DATA'] MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] MY_CACHE = configpaths.gajimpaths['MY_CACHE'] - XTLS_CERTS = os.path.expanduser(jingle_xtls.CERTIFICATE_DIR) - LOCAL_XTLS_CERTS = os.path.expanduser(jingle_xtls.LOCAL_CERT_DIR) + XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS'] + LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT'] if not os.path.exists(MY_DATA): create_path(MY_DATA) @@ -338,10 +338,14 @@ def check_and_possibly_create_paths(): if not os.path.exists(XTLS_CERTS): create_path(XTLS_CERTS) - if not (os.path.exists(os.path.join(LOCAL_XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.cert')) and - os.path.exists(os.path.join(LOCAL_XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE + '.pkey'))): - jingle_xtls.make_certs(os.path.join(LOCAL_XTLS_CERTS, jingle_xtls.SELF_SIGNED_CERTIFICATE), 'gajim') - + if not os.path.exists(LOCAL_XTLS_CERTS): + create_path(LOCAL_XTLS_CERTS) + cert_name = os.path.join(LOCAL_XTLS_CERTS, + jingle_xtls.SELF_SIGNED_CERTIFICATE) + if not (os.path.exists(cert_name + '.cert') and os.path.exists( + cert_name + '.pkey')): + jingle_xtls.make_certs(cert_name, 'gajim') + def create_path(directory): print _('creating %s directory') % directory diff --git a/src/common/gajim.py b/src/common/gajim.py index 214f5e369..c547e26d2 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -84,10 +84,12 @@ MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS'] MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS'] MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS'] MY_CACERTS = gajimpaths['MY_CACERTS'] +MY_PEER_CERTS_PATH = gajimpaths['MY_PEER_CERTS'] TMP = gajimpaths['TMP'] DATA_DIR = gajimpaths['DATA'] ICONS_DIR = gajimpaths['ICONS'] HOME_DIR = gajimpaths['HOME'] +MY_CERT_DIR = gajimpaths['MY_CERT'] try: LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 4cd097b2c..3aee03a9d 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -20,12 +20,9 @@ import os import logging import common -import gajim +from common import gajim log = logging.getLogger('gajim.c.jingle_xtls') -from common import configpaths -gajimpath = configpaths.gajimpaths - PYOPENSSL_PRESENT = False pending_sessions = {} # key-exchange id -> session, accept that session once key-exchange completes @@ -49,10 +46,6 @@ if PYOPENSSL_PRESENT: from OpenSSL.SSL import Context from OpenSSL import crypto -CERTIFICATE_DIR = gajimpath['MY_PEER_CERTS'] -LOCAL_CERT_DIR = gajimpath['MY_CERT'] -print 'CERTIFICATE_DIR: ', CERTIFICATE_DIR -print 'MY_CERT_DIR: ', LOCAL_CERT_DIR SELF_SIGNED_CERTIFICATE = 'localcert' def default_callback(connection, certificate, error_num, depth, return_code): @@ -103,16 +96,17 @@ def get_context(fingerprint, verify_cb=None): elif fingerprint == 'client': ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) - ctx.use_privatekey_file (os.path.expanduser(os.path.join(LOCAL_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.pkey')) - ctx.use_certificate_file(os.path.expanduser(os.path.join(LOCAL_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.cert')) + cert_name = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + ctx.use_privatekey_file (cert_name + '.pkey') + ctx.use_certificate_file(cert_name + '.cert') store = ctx.get_cert_store() - for f in os.listdir(os.path.expanduser(CERTIFICATE_DIR)): - load_cert_file(os.path.join(os.path.expanduser(CERTIFICATE_DIR), f), store) + for f in os.listdir(os.path.expanduser(gajim.MY_PEER_CERTS_PATH)): + load_cert_file(os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), f), store) print 'certificate file' + f + ' loaded', 'fingerprint', fingerprint return ctx def send_cert(con, jid_from, sid): - certpath = os.path.expanduser(os.path.join(LOCAL_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.cert') + certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.cert' certfile = open(certpath, 'r') certificate = '' for line in certfile.readlines(): @@ -134,7 +128,7 @@ def send_cert(con, jid_from, sid): def handle_new_cert(con, obj, jid_from): jid = gajim.get_jid_without_resource(jid_from) - certpath = os.path.join(os.path.expanduser(CERTIFICATE_DIR), jid) + certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid) certpath += '.cert' id = obj.getAttr('id') From b8914c62198897fc8cf7685174dfa0494fa52bea Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 24 Aug 2010 23:12:34 +0200 Subject: [PATCH 043/121] coding standards --- src/common/jingle_ft.py | 129 ++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 59 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index d340ddbf9..5db8f206b 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -27,7 +27,6 @@ from common import helpers from common.socks5 import Socks5Receiver import logging - log = logging.getLogger('gajim.c.jingle_ft') STATE_NOT_STARTED = 0 @@ -37,25 +36,27 @@ STATE_TRANSPORT_INFO = 3 STATE_PROXY_ACTIVATED = 4 class JingleFileTransfer(JingleContent): - def __init__(self, session, transport=None, file_props=None, use_security=False): + def __init__(self, session, transport=None, file_props=None, + use_security=False): JingleContent.__init__(self, session, transport) - + log.info("transport value: %s" % transport) - - #events we might be interested in + + # events we might be interested in self.callbacks['session-initiate'] += [self.__on_session_initiate] self.callbacks['session-accept'] += [self.__on_session_accept] self.callbacks['session-terminate'] += [self.__on_session_terminate] self.callbacks['transport-accept'] += [self.__on_transport_accept] - self.callbacks['transport-replace'] += [self.__on_transport_replace] #fallback transport method + self.callbacks['transport-replace'] += [self.__on_transport_replace] + # fallback transport method self.callbacks['transport-reject'] += [self.__on_transport_reject] self.callbacks['transport-info'] += [self.__on_transport_info] self.callbacks['iq-result'] += [self.__on_iq_result] self.state = STATE_NOT_STARTED - + self.use_security = use_security - + self.file_props = file_props if file_props is None: self.weinitiate = False @@ -68,9 +69,8 @@ class JingleFileTransfer(JingleContent): self.file_props['session-type'] = 'jingle' self.file_props['sid'] = session.sid self.file_props['transfered_size'] = [] - - log.info("FT request: %s" % file_props) + log.info("FT request: %s" % file_props) if transport is None: self.transport = JingleTransportSocks5() @@ -81,11 +81,11 @@ class JingleFileTransfer(JingleContent): self.session = session self.media = 'file' - + def __on_session_initiate(self, stanza, content, error, action): jid = unicode(stanza.getFrom()) log.info("jid:%s" % jid) - + file_props = {'type': 'r'} file_props['sender'] = jid file_props['request-id'] = unicode(stanza.getAttr('id')) @@ -93,8 +93,9 @@ class JingleFileTransfer(JingleContent): file_props['session-type'] = 'jingle' self.use_security = bool(content.getTag('security')) - # TODO: extract fingerprint element, encryption method element for later use - + # TODO: extract fingerprint element, encryption method element for later + # use + file_tag = content.getTag('description').getTag('offer').getTag('file') for attribute in file_tag.getAttrs(): if attribute in ('name', 'size', 'hash', 'date'): @@ -105,7 +106,7 @@ class JingleFileTransfer(JingleContent): file_desc_tag = file_tag.getTag('desc') if file_desc_tag is not None: file_props['desc'] = file_desc_tag.getData() - + file_props['receiver'] = self.session.ourjid log.info("ourjid: %s" % self.session.ourjid) file_props['sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) @@ -119,7 +120,8 @@ class JingleFileTransfer(JingleContent): self.transport.set_connection(self.session.connection) self.transport.set_file_props(self.file_props) if self.file_props.has_key("streamhosts"): - self.file_props['streamhosts'].extend(self.transport.remote_candidates) + self.file_props['streamhosts'].extend( + self.transport.remote_candidates) else: self.file_props['streamhosts'] = self.transport.remote_candidates for host in self.file_props['streamhosts']: @@ -131,11 +133,10 @@ class JingleFileTransfer(JingleContent): def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") - + security = content.getTag('security') if not security: # responder can not verify our fingerprint self.use_security = False - def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") @@ -145,16 +146,17 @@ class JingleFileTransfer(JingleContent): def __on_transport_replace(self, stanza, content, error, action): log.info("__on_transport_replace") - + def __on_transport_reject(self, stanza, content, error, action): log.info("__on_transport_reject") def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") - + if not self.weinitiate: # proxy activated from initiator return - streamhost_cid = content.getTag('transport').getTag('candidate-used').getAttr('cid') + streamhost_cid = content.getTag('transport').getTag('candidate-used').\ + getAttr('cid') streamhost_used = None for cand in self.transport.candidates: if cand['candidate_id'] == streamhost_cid: @@ -167,8 +169,8 @@ class JingleFileTransfer(JingleContent): self.file_props['streamhost-used'] = True for proxy in self.file_props['proxyhosts']: if proxy['host'] == streamhost_used['host'] and \ - proxy['port'] == streamhost_used['port'] and \ - proxy['jid'] == streamhost_used['jid']: + proxy['port'] == streamhost_used['port'] and \ + proxy['jid'] == streamhost_used['jid']: streamhost_used = proxy break if 'streamhosts' not in self.file_props: @@ -176,75 +178,85 @@ class JingleFileTransfer(JingleContent): self.file_props['streamhosts'].append(streamhost_used) self.file_props['is_a_proxy'] = True receiver = Socks5Receiver(gajim.idlequeue, streamhost_used, - self.file_props['sid'], self.file_props) - gajim.socks5queue.add_receiver(self.session.connection.name, receiver) + self.file_props['sid'], self.file_props) + gajim.socks5queue.add_receiver(self.session.connection.name, + receiver) streamhost_used['idx'] = receiver.queue_idx gajim.socks5queue.on_success = self.transport._on_proxy_auth_ok else: jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.send_file(self.file_props, self.session.connection.name) - + gajim.socks5queue.send_file(self.file_props, + self.session.connection.name) + def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") - + if self.weinitiate and self.state == STATE_NOT_STARTED: self.state = STATE_INITIALIZED - self.session.connection.files_props[self.file_props['sid']] = self.file_props + self.session.connection.files_props[self.file_props['sid']] = \ + self.file_props receiver = self.file_props['receiver'] sender = self.file_props['sender'] - - sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, receiver) + + sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, + receiver) self.file_props['sha_str'] = sha_str - + port = gajim.config.get('file_transfers_port') - + listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props['sid'], fingerprint = 'server') - + self._store_socks5_sid, self.file_props['sid'], + fingerprint='server') + if not listener: return # send error message, notify the user - elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result + elif not self.weinitiate and self.state == STATE_NOT_STARTED: + # session-accept iq-result self.state = STATE_ACCEPTED - if not gajim.socks5queue.get_file_props(self.session.connection.name, self.file_props['sid']): - gajim.socks5queue.add_file_props(self.session.connection.name, self.file_props) - gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], - self.send_candidate_used, self._on_connect_error, - fingerprint = 'client') - elif not self.weinitiate and self.state == STATE_ACCEPTED: # transport-info iq-result + if not gajim.socks5queue.get_file_props( + self.session.connection.name, self.file_props['sid']): + gajim.socks5queue.add_file_props(self.session.connection.name, + self.file_props) + gajim.socks5queue.connect_to_hosts(self.session.connection.name, + self.file_props['sid'], self.send_candidate_used, + self._on_connect_error, fingerprint='client') + elif not self.weinitiate and self.state == STATE_ACCEPTED: + # transport-info iq-result self.state = STATE_TRANSPORT_INFO - elif self.weinitiate and self.state == STATE_INITIALIZED: # proxy activated + elif self.weinitiate and self.state == STATE_INITIALIZED: + # proxy activated self.state = STATE_PROXY_ACTIVATED - + def send_candidate_used(self, streamhost): """ send candidate-used stanza """ - log.info("send_candidate_used") + log.info('send_candidate_used') if streamhost is None: return - + content = xmpp.Node('content') content.setAttr('creator', 'initiator') content.setAttr('name', 'file') - + transport = xmpp.Node('transport') transport.setAttr('xmlns', xmpp.NS_JINGLE_BYTESTREAM) - + candidateused = xmpp.Node('candidate-used') candidateused.setAttr('cid', streamhost['cid']) - + transport.addChild(node=candidateused) content.addChild(node=transport) self.session.send_transport_info(content) - + def _on_connect_error(self, to, _id, sid, code=404): - log.info("connect error, sid=" + sid) - return - + log.info('connect error, sid=' + sid) + def _fill_content(self, content): - description_node = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') + description_node = xmpp.simplexml.Node( + tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') sioffer = xmpp.simplexml.Node(tag='offer') file_tag = sioffer.setTag('file', namespace=xmpp.NS_FILE) @@ -255,9 +267,10 @@ class JingleFileTransfer(JingleContent): desc.setData(self.file_props['desc']) description_node.addChild(node=sioffer) - + if self.use_security: - security = xmpp.simplexml.Node(tag=xmpp.NS_JINGLE_XTLS + ' security') + security = xmpp.simplexml.Node( + tag=xmpp.NS_JINGLE_XTLS + ' security') # TODO: add fingerprint element for m in ('x509', ): # supported authentication methods method = xmpp.simplexml.Node(tag='method') @@ -266,14 +279,12 @@ class JingleFileTransfer(JingleContent): content.addChild(node=security) content.addChild(node=description_node) - + def _store_socks5_sid(self, sid, hash_id): # callback from socsk5queue.start_listener self.file_props['hash'] = hash_id - return def get_content(desc): return JingleFileTransfer - contents[xmpp.NS_JINGLE_FILE_TRANSFER] = get_content From 556236ac3876c7101b1199b461029412dfaddc30 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 24 Aug 2010 23:24:18 +0200 Subject: [PATCH 044/121] don't use XTLS to transfer files when we don't use the security element --- src/common/jingle_ft.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 5db8f206b..4d662749a 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -204,9 +204,12 @@ class JingleFileTransfer(JingleContent): port = gajim.config.get('file_transfers_port') + fingerprint = None + if self.use_security: + fingerprint = 'server' listener = gajim.socks5queue.start_listener(port, sha_str, self._store_socks5_sid, self.file_props['sid'], - fingerprint='server') + fingerprint=fingerprint) if not listener: return @@ -218,9 +221,12 @@ class JingleFileTransfer(JingleContent): self.session.connection.name, self.file_props['sid']): gajim.socks5queue.add_file_props(self.session.connection.name, self.file_props) + fingerprint = None + if self.use_security: + fingerprint = 'client' gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], self.send_candidate_used, - self._on_connect_error, fingerprint='client') + self._on_connect_error, fingerprint=fingerprint) elif not self.weinitiate and self.state == STATE_ACCEPTED: # transport-info iq-result self.state = STATE_TRANSPORT_INFO From 833983eab8b07c4a36c4d5182745b9a2826ff068 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Aug 2010 12:05:14 +0200 Subject: [PATCH 045/121] - use transport sid to compute hash sent to proxies - Don't use XTLS when using proxies --- src/common/jingle.py | 18 +++++------ src/common/jingle_ft.py | 18 +++++++---- src/common/jingle_transport.py | 50 ++++++++++++++++++++----------- src/common/protocol/bytestream.py | 14 ++++----- src/common/socks5.py | 14 ++++++--- 5 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 2bb674a46..0ee21e28e 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -141,26 +141,26 @@ class ConnectionJingle(object): def start_file_transfer(self, jid, file_props): logger.info("start file transfer with file: %s" % file_props) jingle = self.get_jingle_session(jid, media='file') - contact = gajim.contacts.get_contact_with_highest_priority(self.name, + contact = gajim.contacts.get_contact_with_highest_priority(self.name, gajim.get_jid_without_resource(jid)) if contact is None: return use_security = contact.supports(xmpp.NS_JINGLE_XTLS) if jingle: file_props['sid'] = jingle.sid - jingle.add_content('file', - JingleFileTransfer(jingle, file_props = file_props, use_security=use_security) - ) + c = JingleFileTransfer(jingle, file_props=file_props, + use_security=use_security) + jingle.add_content('file', c) else: jingle = JingleSession(self, weinitiate=True, jid=jid) self.__sessions[jingle.sid] = jingle file_props['sid'] = jingle.sid - jingle.add_content('file', - JingleFileTransfer(jingle, file_props=file_props, use_security=use_security) - ) + c = JingleFileTransfer(jingle, file_props=file_props, + use_security=use_security) + jingle.add_content('file', c) jingle.start_session() - return jingle.sid - + return c.transport.sid + def iter_jingle_sessions(self, jid, sid=None, media=None): if sid: diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 4d662749a..647819383 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -67,18 +67,21 @@ class JingleFileTransfer(JingleContent): self.file_props['sender'] = session.ourjid self.file_props['receiver'] = session.peerjid self.file_props['session-type'] = 'jingle' - self.file_props['sid'] = session.sid + self.file_props['session-sid'] = session.sid self.file_props['transfered_size'] = [] log.info("FT request: %s" % file_props) if transport is None: self.transport = JingleTransportSocks5() + self.transport.set_connection(session.connection) self.transport.set_file_props(self.file_props) self.transport.set_our_jid(session.ourjid) - self.transport.set_connection(session.connection) log.info('ourjid: %s' % session.ourjid) + if self.file_props is not None: + self.file_props['sid'] = self.transport.sid + self.session = session self.media = 'file' @@ -109,17 +112,19 @@ class JingleFileTransfer(JingleContent): file_props['receiver'] = self.session.ourjid log.info("ourjid: %s" % self.session.ourjid) - file_props['sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) + file_props['session-sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) file_props['transfered_size'] = [] self.file_props = file_props - self.session.connection.files_props[file_props['sid']] = file_props + if self.transport is None: self.transport = JingleTransportSocks5() self.transport.set_our_jid(self.session.ourjid) self.transport.set_connection(self.session.connection) + self.file_props['sid'] = self.transport.sid + self.session.connection.files_props[file_props['sid']] = file_props self.transport.set_file_props(self.file_props) - if self.file_props.has_key("streamhosts"): + if self.file_props.has_key('streamhosts'): self.file_props['streamhosts'].extend( self.transport.remote_candidates) else: @@ -247,7 +252,8 @@ class JingleFileTransfer(JingleContent): content.setAttr('name', 'file') transport = xmpp.Node('transport') - transport.setAttr('xmlns', xmpp.NS_JINGLE_BYTESTREAM) + transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) + transport.setAttr('sid', self.transport.sid) candidateused = xmpp.Node('candidate-used') candidateused.setAttr('cid', streamhost['cid']) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 7c1adf374..59113f100 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -17,6 +17,7 @@ Handles Jingle Transports (currently only ICE-UDP) import xmpp import socket +from common import helpers from common import gajim from common.protocol.bytestream import ConnectionSocks5Bytestream import logging @@ -29,7 +30,7 @@ transports = {} def get_jingle_transport(node): namespace = node.getNamespace() if namespace in transports: - return transports[namespace]() + return transports[namespace](node) class TransportType(object): @@ -83,10 +84,13 @@ class JingleTransportSocks5(JingleTransport): Socks5 transport in jingle scenario Note: Don't forget to call set_file_props after initialization """ - def __init__(self): + def __init__(self, node=None): JingleTransport.__init__(self, TransportType.streaming) self.connection = None self.remote_candidates = [] + self.sid = None + if node and node.getAttr('sid'): + self.sid = node.getAttr('sid') def set_file_props(self, file_props): self.file_props = file_props @@ -96,7 +100,9 @@ class JingleTransportSocks5(JingleTransport): def set_connection(self, conn): self.connection = conn - + if not self.sid: + self.sid = self.connection.connection.getAnID() + def make_candidate(self, candidate): import logging log = logging.getLogger() @@ -118,26 +124,32 @@ class JingleTransportSocks5(JingleTransport): self._add_proxy_candidates() transport = JingleTransport.make_transport(self, candidates) transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) + transport.setAttr('sid', self.sid) return transport def parse_transport_stanza(self, transport): candidates = [] for candidate in transport.iterTags('candidate'): + typ = 'direct' # default value + if candidate.has_attr('type'): + typ = candidate['type'] cand = { 'state': 0, 'target': self.ourjid, 'host': candidate['host'], 'port': candidate['port'], - 'cid': candidate['cid'] + 'cid': candidate['cid'], + 'type': typ } candidates.append(cand) - + # we need this when we construct file_props on session-initiation self.remote_candidates = candidates return candidates - + def _add_local_ips_as_candidates(self): + return if not self.connection: return local_ip_cand = [] @@ -149,9 +161,9 @@ class JingleTransportSocks5(JingleTransport): c['type'] = 'direct' c['jid'] = self.ourjid c['priority'] = (2**16) * type_preference - + local_ip_cand.append(c) - + for addr in socket.getaddrinfo(socket.gethostname(), None): if not addr[4][0] in local_ip_cand and not addr[4][0].startswith('127'): c = {'host': addr[4][0]} @@ -167,13 +179,14 @@ class JingleTransportSocks5(JingleTransport): self.candidates += local_ip_cand def _add_additional_candidates(self): + return if not self.connection: return type_preference = 126 additional_ip_cand = [] port = gajim.config.get('file_transfers_port') - ft_add_hosts = gajim.config.get('ft_add_hosts_to_send') - + ft_add_hosts = gajim.config.get('ft_add_hosts_to_send') + if ft_add_hosts: hosts = [e.strip() for e in ft_add_hosts.split(',')] for h in hosts: @@ -187,7 +200,7 @@ class JingleTransportSocks5(JingleTransport): c['target'] = self.file_props['receiver'] additional_ip_cand.append(c) self.candidates += additional_ip_cand - + def _add_proxy_candidates(self): if not self.connection: return @@ -201,7 +214,7 @@ class JingleTransportSocks5(JingleTransport): self.file_props['receiver']) self.file_props['proxy_sender'] = unicode(self.file_props['sender']) self.file_props['proxyhosts'] = proxyhosts - + for proxyhost in proxyhosts: c = {'host': proxyhost['host']} c['candidate_id'] = self.connection.connection.getAnID() @@ -213,7 +226,7 @@ class JingleTransportSocks5(JingleTransport): c['target'] = self.file_props['receiver'] proxy_cand.append(c) self.candidates += proxy_cand - + def _on_proxy_auth_ok(self, proxy): log.info('proxy auth ok for ' + str(proxy)) # send activate request to proxy, send activated confirmation to peer @@ -229,12 +242,12 @@ class JingleTransportSocks5(JingleTransport): activate.setData(file_props['proxy_receiver']) iq.setID(auth_id) self.connection.connection.send(iq) - + content = xmpp.Node('content') content.setAttr('creator', 'initiator') content.setAttr('name', 'file') transport = xmpp.Node('transport') - transport.setAttr('xmlns', xmpp.NS_JINGLE_BYTESTREAM) + transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) activated = xmpp.Node('activated') cid = None for host in self.candidates: @@ -248,8 +261,9 @@ class JingleTransportSocks5(JingleTransport): activated.setAttr('cid', cid) transport.addChild(node=activated) content.addChild(node=transport) - sesn = self.connection.get_jingle_session(self.ourjid, self.file_props['sid']) - + sesn = self.connection.get_jingle_session(self.ourjid, + self.file_props['session-sid']) + if sesn is None: return sesn.send_transport_info(content) @@ -257,7 +271,7 @@ class JingleTransportSocks5(JingleTransport): import farsight class JingleTransportICEUDP(JingleTransport): - def __init__(self): + def __init__(self, node): JingleTransport.__init__(self, TransportType.datagram) def make_candidate(self, candidate): diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 966e65a0e..0fa7a361e 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -136,16 +136,16 @@ class ConnectionBytestream: # file transfer initiated by a jingle session log.info("send_file_approval: jingle session accept") if file_props.get('session-type') == 'jingle': - session = self.get_jingle_session(file_props['sender'], file_props['sid']) + session = self.get_jingle_session(file_props['sender'], + file_props['session-sid']) if not session: return - sid = file_props['sid'] gajim.socks5queue.add_file_props(self.name, file_props) - + if not session.accepted: if session.get_content('file').use_security: - id = jingle_xtls.send_cert_request(self, file_props['sender']) - jingle_xtls.key_exchange_pend(id, session) + id_ = jingle_xtls.send_cert_request(self, file_props['sender']) + jingle_xtls.key_exchange_pend(id_, session) return session.approve_session() session.approve_content('file') @@ -356,7 +356,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): if 'idx' in host and host['idx'] > 0: gajim.socks5queue.remove_receiver(host['idx']) gajim.socks5queue.remove_sender(host['idx']) - + if 'direction' in file_props: # it's a IBB sid = file_props['sid'] @@ -822,7 +822,7 @@ class ConnectionIBBytestream(ConnectionBytestream): {'sid':sid})])) file_props['completed'] = True del self.files_props[sid] - + def IBBMessageHandler(self, conn, stanza): """ Receive next portion of incoming datastream and store it write diff --git a/src/common/socks5.py b/src/common/socks5.py index cf51601d8..12c39755a 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -122,7 +122,8 @@ class SocksQueue: return 1 return 0 - def connect_to_hosts(self, account, sid, on_success=None, on_failure=None, fingerprint=None): + def connect_to_hosts(self, account, sid, on_success=None, on_failure=None, + fingerprint=None): self.on_success = on_success self.on_failure = on_failure file_props = self.files_props[account][sid] @@ -130,7 +131,12 @@ class SocksQueue: # add streamhosts to the queue for streamhost in file_props['streamhosts']: - receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props, fingerprint=fingerprint) + if 'type' in streamhost and streamhost['type'] == 'proxy': + fp = None + else: + fd = fingerprint + receiver = Socks5Receiver(self.idlequeue, streamhost, sid, + file_props, fingerprint=fp) self.add_receiver(account, receiver) streamhost['idx'] = receiver.queue_idx @@ -453,7 +459,7 @@ class Socks5: received = '' try: add = self._recv(64) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, OpenSSL.SSL.WantX509LookupError), e: log.info('SSL rehandshake request : ' + repr(e)) raise e @@ -814,7 +820,7 @@ class Socks5Sender(Socks5, IdleObject): self.queue.result_sha(self.sha_msg, self.queue_idx) if result == -1: self.disconnect() - + elif self.state == 5: if self.file_props is not None and self.file_props['type'] == 'r': result = self.get_file_contents(0) From 6c25400646ee444b3cc157627e976e9e5fce81ce Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Aug 2010 12:13:15 +0200 Subject: [PATCH 046/121] remove some debugging stuff --- src/common/jingle_transport.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 59113f100..9fd00e5e5 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -149,7 +149,6 @@ class JingleTransportSocks5(JingleTransport): def _add_local_ips_as_candidates(self): - return if not self.connection: return local_ip_cand = [] @@ -179,7 +178,6 @@ class JingleTransportSocks5(JingleTransport): self.candidates += local_ip_cand def _add_additional_candidates(self): - return if not self.connection: return type_preference = 126 From e00daf8b166efac2f15bcd0779129fe6d6b89185 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Aug 2010 12:16:44 +0200 Subject: [PATCH 047/121] remove useless import --- src/common/jingle_transport.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 9fd00e5e5..04b3da912 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -17,7 +17,6 @@ Handles Jingle Transports (currently only ICE-UDP) import xmpp import socket -from common import helpers from common import gajim from common.protocol.bytestream import ConnectionSocks5Bytestream import logging From 6911821a28bb78e82f9c9bcb3f50565c9c887b42 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Aug 2010 12:21:12 +0200 Subject: [PATCH 048/121] fix a typo --- src/common/socks5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index 12c39755a..e735f7ac5 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -134,7 +134,7 @@ class SocksQueue: if 'type' in streamhost and streamhost['type'] == 'proxy': fp = None else: - fd = fingerprint + fp = fingerprint receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props, fingerprint=fp) self.add_receiver(account, receiver) From f03cdbbebf2f5ad5b403e19794c8b28512b3e486 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Aug 2010 13:01:35 +0200 Subject: [PATCH 049/121] send a session-terminate at the end of a jingle filetransfer --- src/gui_interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui_interface.py b/src/gui_interface.py index 60110b79a..da19d1938 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1407,11 +1407,18 @@ class Interface: if 'stalled' in file_props and file_props['stalled'] or \ 'paused' in file_props and file_props['paused']: return + if file_props['type'] == 'r': # we receive a file jid = unicode(file_props['sender']) else: # we send a file jid = unicode(file_props['receiver']) + # End jingle session + if file_props['session-type'] == 'jingle' and file_props['type'] == 'r': + session = gajim.connections[account].get_jingle_session(jid, + sid=file_props['session-sid']) + session.end_session() + if helpers.allow_popup_window(account): if file_props['error'] == 0: if gajim.config.get('notify_on_file_complete'): From 286d788da0a455fd9d278c24c9cb8904b2605520 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 10:36:58 +0200 Subject: [PATCH 050/121] Name of filetransfer content is now random to be able to have 2 transfer in the same session. send and handle content-add in filetranfer --- src/common/jingle.py | 47 ++++++++++++++++--------------- src/common/jingle_ft.py | 3 +- src/common/jingle_session.py | 18 ++++++------ src/common/jingle_transport.py | 15 +++++++--- src/common/jingle_xtls.py | 29 +++++++++---------- src/common/protocol/bytestream.py | 3 +- src/common/socks5.py | 2 +- 7 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 0ee21e28e..c98a3ed3e 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -48,7 +48,7 @@ class ConnectionJingle(object): def __init__(self): # dictionary: sessionid => JingleSession object - self.__sessions = {} + self.__sessions__ = {} # dictionary: (jid, iq stanza id) => JingleSession object, # one time callbacks @@ -58,12 +58,12 @@ class ConnectionJingle(object): """ Remove a jingle session from a jingle stanza dispatcher """ - if sid in self.__sessions: + if sid in self.__sessions__: #FIXME: Move this elsewhere? - for content in self.__sessions[sid].contents.values(): + for content in self.__sessions__[sid].contents.values(): content.destroy() - self.__sessions[sid].callbacks = [] - del self.__sessions[sid] + self.__sessions__[sid].callbacks = [] + del self.__sessions__[sid] def _JingleCB(self, con, stanza): """ @@ -91,23 +91,23 @@ class ConnectionJingle(object): sid = jingle.getAttr('sid') else: sid = None - for sesn in self.__sessions.values(): + for sesn in self.__sessions__.values(): if id in sesn.iq_ids: sesn.on_stanza(stanza) return # do we need to create a new jingle object - if sid not in self.__sessions: + if sid not in self.__sessions__: #TODO: tie-breaking and other things... newjingle = JingleSession(con=self, weinitiate=False, jid=jid, iq_id = id, sid=sid) - self.__sessions[sid] = newjingle + self.__sessions__[sid] = newjingle # we already have such session in dispatcher... - self.__sessions[sid].collect_iq_id(id) - self.__sessions[sid].on_stanza(stanza) + self.__sessions__[sid].collect_iq_id(id) + self.__sessions__[sid].on_stanza(stanza) # Delete invalid/unneeded sessions - if sid in self.__sessions and self.__sessions[sid].state == JingleStates.ended: + if sid in self.__sessions__ and self.__sessions__[sid].state == JingleStates.ended: self.delete_jingle_session(sid) raise xmpp.NodeProcessed @@ -120,7 +120,7 @@ class ConnectionJingle(object): jingle.add_content('voice', JingleAudio(jingle)) else: jingle = JingleSession(self, weinitiate=True, jid=jid) - self.__sessions[jingle.sid] = jingle + self.__sessions__[jingle.sid] = jingle jingle.add_content('voice', JingleAudio(jingle)) jingle.start_session() return jingle.sid @@ -133,7 +133,7 @@ class ConnectionJingle(object): jingle.add_content('video', JingleVideo(jingle)) else: jingle = JingleSession(self, weinitiate=True, jid=jid) - self.__sessions[jingle.sid] = jingle + self.__sessions__[jingle.sid] = jingle jingle.add_content('video', JingleVideo(jingle)) jingle.start_session() return jingle.sid @@ -150,24 +150,25 @@ class ConnectionJingle(object): file_props['sid'] = jingle.sid c = JingleFileTransfer(jingle, file_props=file_props, use_security=use_security) - jingle.add_content('file', c) + jingle.add_content('file' + helpers.get_random_string_16(), c) + jingle.on_session_state_changed(c) else: jingle = JingleSession(self, weinitiate=True, jid=jid) - self.__sessions[jingle.sid] = jingle + self.__sessions__[jingle.sid] = jingle file_props['sid'] = jingle.sid c = JingleFileTransfer(jingle, file_props=file_props, use_security=use_security) - jingle.add_content('file', c) + jingle.add_content('file' + helpers.get_random_string_16(), c) jingle.start_session() return c.transport.sid def iter_jingle_sessions(self, jid, sid=None, media=None): if sid: - return (session for session in self.__sessions.values() if session.sid == sid) - sessions = (session for session in self.__sessions.values() if session.peerjid == jid) + return (session for session in self.__sessions__.values() if session.sid == sid) + sessions = (session for session in self.__sessions__.values() if session.peerjid == jid) if media: - if media not in ('audio', 'video'): + if media not in ('audio', 'video', 'file'): return tuple() else: return (session for session in sessions if session.get_content(media)) @@ -177,14 +178,14 @@ class ConnectionJingle(object): def get_jingle_session(self, jid, sid=None, media=None): if sid: - if sid in self.__sessions: - return self.__sessions[sid] + if sid in self.__sessions__: + return self.__sessions__[sid] else: return None elif media: - if media not in ('audio', 'video'): + if media not in ('audio', 'video', 'file'): return None - for session in self.__sessions.values(): + for session in self.__sessions__.values(): if session.peerjid == jid and session.get_content(media): return session diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 647819383..89c261e2e 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -44,6 +44,7 @@ class JingleFileTransfer(JingleContent): # events we might be interested in self.callbacks['session-initiate'] += [self.__on_session_initiate] + self.callbacks['content-add'] += [self.__on_session_initiate] self.callbacks['session-accept'] += [self.__on_session_accept] self.callbacks['session-terminate'] += [self.__on_session_terminate] self.callbacks['transport-accept'] += [self.__on_transport_accept] @@ -249,7 +250,7 @@ class JingleFileTransfer(JingleContent): content = xmpp.Node('content') content.setAttr('creator', 'initiator') - content.setAttr('name', 'file') + content.setAttr('name', self.name) transport = xmpp.Node('transport') transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 7a1bcc2c8..7124e7b33 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -85,14 +85,14 @@ class JingleSession(object): if not sid: sid = con.connection.getAnID() self.sid = sid # sessionid - + # iq stanza id, used to determine which sessions to summon callback # later on when iq-result stanza arrives if iq_id is not None: self.iq_ids = [iq_id] else: self.iq_ids = [] - + self.accepted = True # is this session accepted by user @@ -127,7 +127,7 @@ class JingleSession(object): def collect_iq_id(self, iq_id): if iq_id is not None: self.iq_ids.append(iq_id) - + def approve_session(self): """ Called when user accepts session in UI (when we aren't the initiator) @@ -340,7 +340,7 @@ class JingleSession(object): break elif child.getNamespace() == xmpp.NS_STANZAS: error_name = child.getName() - self.__dispatch_error(error_name, text, error.getAttribute('type')) + self.__dispatch_error(error_name, text, error.getAttr('type')) # FIXME: Not sure when we would want to do that... def __on_transport_replace(self, stanza, jingle, error, action): @@ -482,13 +482,13 @@ class JingleSession(object): # for cn in self.contents.values(): # cn.on_stanza(stanza, None, error, action) # return - + # special case: iq-result stanza does not come with a jingle element if action == 'iq-result': for cn in self.contents.values(): cn.on_stanza(stanza, None, error, action) return - + for content in jingle.iterTags('content'): name = content['name'] creator = content['creator'] @@ -673,7 +673,8 @@ class JingleSession(object): stanza, jingle = self.__make_jingle('content-add') self.__append_content(jingle, content) self.__broadcast(stanza, jingle, None, 'content-add-sent') - self.connection.connection.send(stanza) + id_ = self.connection.connection.send(stanza) + self.collect_iq_id(id_) def __content_accept(self, content): # TODO: test @@ -681,7 +682,8 @@ class JingleSession(object): stanza, jingle = self.__make_jingle('content-accept') self.__append_content(jingle, content) self.__broadcast(stanza, jingle, None, 'content-accept-sent') - self.connection.connection.send(stanza) + id_ = self.connection.connection.send(stanza) + self.collect_iq_id(id_) def __content_reject(self, content): assert self.state != JingleStates.ended diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 04b3da912..ff47a7c23 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -224,6 +224,13 @@ class JingleTransportSocks5(JingleTransport): proxy_cand.append(c) self.candidates += proxy_cand + def get_content(self): + sesn = self.connection.get_jingle_session(self.ourjid, + self.file_props['session-sid']) + for content in sesn.contents.values(): + if content.transport == self: + return content + def _on_proxy_auth_ok(self, proxy): log.info('proxy auth ok for ' + str(proxy)) # send activate request to proxy, send activated confirmation to peer @@ -242,15 +249,15 @@ class JingleTransportSocks5(JingleTransport): content = xmpp.Node('content') content.setAttr('creator', 'initiator') - content.setAttr('name', 'file') + c = self.get_content() + content.setAttr('name', c.name) transport = xmpp.Node('transport') transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) activated = xmpp.Node('activated') cid = None for host in self.candidates: - if host['host'] == proxy['host'] and \ - host['jid'] == proxy['jid'] and \ - host['port'] == proxy['port']: + if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \ + and host['port'] == proxy['port']: cid = host['candidate_id'] break if cid is None: diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 3aee03a9d..e36527158 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -29,7 +29,7 @@ pending_sessions = {} # key-exchange id -> session, accept that session once key def key_exchange_pend(id, session): pending_sessions[id] = session - + def approve_pending_session(id): session = pending_sessions[id] session.approve_session() @@ -45,9 +45,11 @@ if PYOPENSSL_PRESENT: from OpenSSL import SSL from OpenSSL.SSL import Context from OpenSSL import crypto + TYPE_RSA = crypto.TYPE_RSA + TYPE_DSA = crypto.TYPE_DSA SELF_SIGNED_CERTIFICATE = 'localcert' - + def default_callback(connection, certificate, error_num, depth, return_code): log.info("certificate: %s" % certificate) return return_code @@ -95,7 +97,7 @@ def get_context(fingerprint, verify_cb=None): ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback) elif fingerprint == 'client': ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback) - + cert_name = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) ctx.use_privatekey_file (cert_name + '.pkey') ctx.use_certificate_file(cert_name + '.cert') @@ -114,16 +116,16 @@ def send_cert(con, jid_from, sid): certificate += line iq = common.xmpp.Iq('result', to=jid_from); iq.setAttr('id', sid) - + pubkey = iq.setTag('pubkeys') pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY) - + keyinfo = pubkey.setTag('keyinfo') name = keyinfo.setTag('name') name.setData('CertificateHash') cert = keyinfo.setTag('x509cert') cert.setData(certificate) - + con.send(iq) def handle_new_cert(con, obj, jid_from): @@ -132,18 +134,18 @@ def handle_new_cert(con, obj, jid_from): certpath += '.cert' id = obj.getAttr('id') - + x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert') - + cert = x509cert.getData() - - f = open(certpath, 'w') + + f = open(certpath, 'w') f.write('-----BEGIN CERTIFICATE-----\n') f.write(cert) f.write('-----END CERTIFICATE-----\n') - + approve_pending_session(id) - + def send_cert_request(con, to_jid): iq = common.xmpp.Iq('get', to=to_jid) id = con.connection.getAnID() @@ -155,9 +157,6 @@ def send_cert_request(con, to_jid): # the following code is partly due to pyopenssl examples -TYPE_RSA = crypto.TYPE_RSA -TYPE_DSA = crypto.TYPE_DSA - def createKeyPair(type, bits): """ Create a public/private key pair. diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 0fa7a361e..3dca76e51 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -148,7 +148,8 @@ class ConnectionBytestream: jingle_xtls.key_exchange_pend(id_, session) return session.approve_session() - session.approve_content('file') + + session.approve_content('file') return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') diff --git a/src/common/socks5.py b/src/common/socks5.py index e735f7ac5..6dc70f955 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -914,7 +914,7 @@ class Socks5Listener(IdleObject): # try the different possibilities (ipv6, ipv4, etc.) try: self._serv = socket.socket(*ai[:3]) - if not self.fingerprint is None: + if self.fingerprint is not None: self._serv = OpenSSL.SSL.Connection( jingle_xtls.get_context('server'), self._serv) except socket.error, e: From f951df7eadca8eb894eb7331702ada942b046b86 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 11:09:35 +0200 Subject: [PATCH 051/121] ability to accept correct content by its name, not only by it's media --- src/common/jingle_session.py | 13 +++++++------ src/common/jingle_xtls.py | 24 ++++++++++++------------ src/common/protocol/bytestream.py | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 7124e7b33..ae49f07bd 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -142,14 +142,14 @@ class JingleSession(object): reason.addChild('decline') self._session_terminate(reason) - def approve_content(self, media): - content = self.get_content(media) + def approve_content(self, media, name=None): + content = self.get_content(media, name) if content: content.accepted = True self.on_session_state_changed(content) - def reject_content(self, media): - content = self.get_content(media) + def reject_content(self, media, name=None): + content = self.get_content(media, name) if content: if self.state == JingleStates.active: self.__content_reject(content) @@ -167,13 +167,14 @@ class JingleSession(object): reason.addChild('cancel') self._session_terminate(reason) - def get_content(self, media=None): + def get_content(self, media=None, name=None): if media is None: return for content in self.contents.values(): if content.media == media: - return content + if name is None or content.name == name: + return content def add_content(self, name, content, creator='we'): """ diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index e36527158..2933a5193 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -25,15 +25,15 @@ log = logging.getLogger('gajim.c.jingle_xtls') PYOPENSSL_PRESENT = False -pending_sessions = {} # key-exchange id -> session, accept that session once key-exchange completes +pending_contents = {} # key-exchange id -> session, accept that session once key-exchange completes -def key_exchange_pend(id, session): - pending_sessions[id] = session +def key_exchange_pend(id_, content): + pending_contents[id_] = content -def approve_pending_session(id): - session = pending_sessions[id] - session.approve_session() - session.approve_content('file') +def approve_pending_content(id_): + content = pending_contents[id_] + content.session.approve_session() + content.session.approve_content('file', name=content.name) try: import OpenSSL @@ -133,7 +133,7 @@ def handle_new_cert(con, obj, jid_from): certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid) certpath += '.cert' - id = obj.getAttr('id') + id_ = obj.getAttr('id') x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert') @@ -144,16 +144,16 @@ def handle_new_cert(con, obj, jid_from): f.write(cert) f.write('-----END CERTIFICATE-----\n') - approve_pending_session(id) + approve_pending_content(id_) def send_cert_request(con, to_jid): iq = common.xmpp.Iq('get', to=to_jid) - id = con.connection.getAnID() - iq.setAttr('id', id) + id_ = con.connection.getAnID() + iq.setAttr('id', id_) pubkey = iq.setTag('pubkeys') pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY) con.connection.send(iq) - return unicode(id) + return unicode(id_) # the following code is partly due to pyopenssl examples diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 3dca76e51..45a62e76d 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -140,16 +140,24 @@ class ConnectionBytestream: file_props['session-sid']) if not session: return + content = None + for c in session.contents.values(): + if c.transport.sid == file_props['sid']: + content = c + break + if not content: + return gajim.socks5queue.add_file_props(self.name, file_props) if not session.accepted: - if session.get_content('file').use_security: - id_ = jingle_xtls.send_cert_request(self, file_props['sender']) - jingle_xtls.key_exchange_pend(id_, session) + if session.get_content('file', content.name).use_security: + id_ = jingle_xtls.send_cert_request(self, + file_props['sender']) + jingle_xtls.key_exchange_pend(id_, content) return session.approve_session() - session.approve_content('file') + session.approve_content('file', content.name) return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') From 99981e57b892a04e32fa03249218f55e6fd7cfb2 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 11:56:12 +0200 Subject: [PATCH 052/121] ability to send several files at the same time. We need to accept them all before transfer starts for the moment. --- src/common/jingle_ft.py | 3 ++- src/common/socks5.py | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 89c261e2e..4a612180b 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -188,7 +188,8 @@ class JingleFileTransfer(JingleContent): gajim.socks5queue.add_receiver(self.session.connection.name, receiver) streamhost_used['idx'] = receiver.queue_idx - gajim.socks5queue.on_success = self.transport._on_proxy_auth_ok + gajim.socks5queue.on_success[self.file_props['sid']] = \ + self.transport._on_proxy_auth_ok else: jid = gajim.get_jid_without_resource(self.session.ourjid) gajim.socks5queue.send_file(self.file_props, diff --git a/src/common/socks5.py b/src/common/socks5.py index 6dc70f955..f7bdc7884 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -81,8 +81,8 @@ class SocksQueue: self.complete_transfer_cb = complete_transfer_cb self.progress_transfer_cb = progress_transfer_cb self.error_cb = error_cb - self.on_success = None - self.on_failure = None + self.on_success = {} # {id: cb} + self.on_failure = {} # {id: cb} def start_listener(self, port, sha_str, sha_handler, sid, fingerprint=None): """ @@ -110,7 +110,7 @@ class SocksQueue: if 'proxyhosts' in file_props: for proxy in file_props['proxyhosts']: if proxy == streamhost: - self.on_success(streamhost) + self.on_success[file_props['sid']](streamhost) return 2 return 0 if 'streamhosts' in file_props: @@ -118,14 +118,14 @@ class SocksQueue: if streamhost['state'] == 1: return 0 streamhost['state'] = 1 - self.on_success(streamhost) + self.on_success[file_props['sid']](streamhost) return 1 return 0 def connect_to_hosts(self, account, sid, on_success=None, on_failure=None, fingerprint=None): - self.on_success = on_success - self.on_failure = on_failure + self.on_success[sid] = on_success + self.on_failure[sid] = on_failure file_props = self.files_props[account][sid] file_props['failure_cb'] = on_failure @@ -304,6 +304,8 @@ class SocksQueue: if account in self.files_props: fl_props = self.files_props[account] if sid in fl_props: + del self.on_success[sid] + del self.on_failure[sid] del(fl_props[sid]) if len(self.files_props) == 0: From ac2b2ac3a17dc4085c1909ea15aefb97b4fcb827 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 13:18:16 +0200 Subject: [PATCH 053/121] accept session as soon as one content is accepted --- src/common/jingle_ft.py | 2 ++ src/common/jingle_session.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 4a612180b..b8b7e0d19 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -223,6 +223,8 @@ class JingleFileTransfer(JingleContent): # send error message, notify the user elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result + if not self.sent: + return self.state = STATE_ACCEPTED if not gajim.socks5queue.get_file_props( self.session.connection.name, self.file_props['sid']): diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index ae49f07bd..20bf55f4b 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -247,6 +247,9 @@ class JingleSession(object): """ Return True when all codecs and candidates are ready (for all contents) """ + for c in self.contents.itervalues(): + if c.is_ready(): + return True return (all((content.is_ready() for content in self.contents.itervalues())) and self.accepted) @@ -623,7 +626,8 @@ class JingleSession(object): # TODO: integrate with __appendContent? # TODO: parameters 'name', 'content'? for content in self.contents.values(): - self.__append_content(jingle, content) + if content.is_ready(): + self.__append_content(jingle, content) def __session_initiate(self): assert self.state == JingleStates.ended From 0f44e376a7955d1e9d2c2068268f003063a52d78 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 13:31:29 +0200 Subject: [PATCH 054/121] minor fix for jingle audio / video --- src/common/jingle_rtp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index 536fda8d6..b32d0419b 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -37,7 +37,7 @@ log = logging.getLogger('gajim.c.jingle_rtp') class JingleRTPContent(JingleContent): def __init__(self, session, media, transport=None): if transport is None: - transport = JingleTransportICEUDP() + transport = JingleTransportICEUDP(None) JingleContent.__init__(self, session, transport) self.media = media self._dtmf_running = False From 38dce6e2c1eea0b2f602cdf6bc24e09bb39e4c36 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 13:52:40 +0200 Subject: [PATCH 055/121] prevent traceback --- src/common/socks5.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index f7bdc7884..db2ef0b63 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -304,8 +304,10 @@ class SocksQueue: if account in self.files_props: fl_props = self.files_props[account] if sid in fl_props: - del self.on_success[sid] - del self.on_failure[sid] + if sid in self.on_success: + del self.on_success[sid] + if sid in self.on_failure: + del self.on_failure[sid] del(fl_props[sid]) if len(self.files_props) == 0: From 164a3891badacf093e8e2feb77c0bf71cede565e Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 13:52:53 +0200 Subject: [PATCH 056/121] nicer code --- src/common/jingle_session.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 20bf55f4b..54092f780 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -247,10 +247,7 @@ class JingleSession(object): """ Return True when all codecs and candidates are ready (for all contents) """ - for c in self.contents.itervalues(): - if c.is_ready(): - return True - return (all((content.is_ready() for content in self.contents.itervalues())) + return (any((content.is_ready() for content in self.contents.itervalues())) and self.accepted) def accept_session(self): From 5234e42a28de708fe1ce30350e073226a8a561a2 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 16:56:08 +0200 Subject: [PATCH 057/121] [Thibg] handle negociated variable correctly and use it for file transfer. --- src/common/jingle_content.py | 15 +++++++++++---- src/common/jingle_ft.py | 2 +- src/common/jingle_rtp.py | 12 ++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index af94561c2..e2a5c9a28 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -55,14 +55,16 @@ class JingleContent(object): self.callbacks = { # these are called when *we* get stanzas - 'content-accept': [self.__on_transport_info], + 'content-accept': [self.__on_transport_info, + self.__on_content_accept], 'content-add': [self.__on_transport_info], 'content-modify': [], 'content-reject': [], 'content-remove': [], 'description-info': [], 'security-info': [], - 'session-accept': [self.__on_transport_info], + 'session-accept': [self.__on_transport_info, + self.__on_content_accept], 'session-info': [], 'session-initiate': [self.__on_transport_info], 'session-terminate': [], @@ -73,16 +75,21 @@ class JingleContent(object): 'iq-result': [], 'iq-error': [], # these are called when *we* sent these stanzas - 'content-accept-sent': [self.__fill_jingle_stanza], + 'content-accept-sent': [self.__fill_jingle_stanza, + self.__on_content_accept], 'content-add-sent': [self.__fill_jingle_stanza], 'session-initiate-sent': [self.__fill_jingle_stanza], - 'session-accept-sent': [self.__fill_jingle_stanza], + 'session-accept-sent': [self.__fill_jingle_stanza, + self.__on_content_accept], 'session-terminate-sent': [], } def is_ready(self): return self.accepted and not self.sent + def __on_content_accept(self, stanza, content, error, action): + self.on_negotiated() + def on_negotiated(self): if self.accepted: self.negotiated = True diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index b8b7e0d19..697d24867 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -223,7 +223,7 @@ class JingleFileTransfer(JingleContent): # send error message, notify the user elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result - if not self.sent: + if not self.negotiated: return self.state = STATE_ACCEPTED if not gajim.socks5queue.get_file_props( diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index b32d0419b..6d738bc56 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -53,12 +53,8 @@ class JingleRTPContent(JingleContent): self.callbacks['session-initiate'] += [self.__on_remote_codecs] self.callbacks['content-add'] += [self.__on_remote_codecs] self.callbacks['description-info'] += [self.__on_remote_codecs] - self.callbacks['content-accept'] += [self.__on_remote_codecs, - self.__on_content_accept] - self.callbacks['session-accept'] += [self.__on_remote_codecs, - self.__on_content_accept] - self.callbacks['session-accept-sent'] += [self.__on_content_accept] - self.callbacks['content-accept-sent'] += [self.__on_content_accept] + self.callbacks['content-accept'] += [self.__on_remote_codecs] + self.callbacks['session-accept'] += [self.__on_remote_codecs] self.callbacks['session-terminate'] += [self.__stop] self.callbacks['session-terminate-sent'] += [self.__stop] @@ -234,14 +230,14 @@ class JingleRTPContent(JingleContent): def get_fallback_src(self): return gst.element_factory_make('fakesrc') - def __on_content_accept(self, stanza, content, error, action): + def on_negotiated(self): if self.accepted: if self.transport.remote_candidates: self.p2pstream.set_remote_candidates(self.transport.remote_candidates) self.transport.remote_candidates = [] # TODO: farsight.DIRECTION_BOTH only if senders='both' self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH) - self.on_negotiated() + JingleContent.on_negotiated(self) def __on_remote_codecs(self, stanza, content, error, action): """ From ac0e0448b2626a6e0d7269119d2383ee69c7ea56 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Aug 2010 16:56:46 +0200 Subject: [PATCH 058/121] send content-remove when a filetranfer is finished but other are still running --- src/gui_interface.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/gui_interface.py b/src/gui_interface.py index da19d1938..a30ac6e66 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1417,7 +1417,15 @@ class Interface: if file_props['session-type'] == 'jingle' and file_props['type'] == 'r': session = gajim.connections[account].get_jingle_session(jid, sid=file_props['session-sid']) - session.end_session() + # get content: + content = None + for c in session.contents.values(): + if c.file_props['sid'] == file_props['sid']: + content = c + break + if not content: + return + session.remove_content('initiator', c.name) if helpers.allow_popup_window(account): if file_props['error'] == 0: @@ -1484,12 +1492,6 @@ class Interface: txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' - # if we are the sender of the file and the file transfer was initiated with jingle - # send session-terminate stanza - if 'session-type' in file_props and file_props['session-type'] == 'jingle': - sender = gajim.get_jid_without_resource(file_props['sender']) - jingle_session = gajim.connections[account].get_jingle_session(sender, file_props['sid']) - jingle_session.end_session() path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' From f7b0659c04df496b0b5c7695f30315881f69d154 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 24 Jun 2011 16:35:10 +0200 Subject: [PATCH 059/121] fix some bad merge --- src/common/connection_handlers.py | 307 ------------------------------ src/common/jingle.py | 2 +- 2 files changed, 1 insertion(+), 308 deletions(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 566140df1..9518e14e6 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -2128,310 +2128,3 @@ ConnectionJingle, ConnectionIBBytestream): con.RegisterHandler('iq', self._PubkeyGetCB, 'get', common.xmpp.NS_PUBKEY_PUBKEY) con.RegisterHandler('iq', self._PubkeyResultCB, 'result', common.xmpp.NS_PUBKEY_PUBKEY) -class HelperEvent: - def get_jid_resource(self): - if self.id_ in self.conn.groupchat_jids: - self.fjid = self.conn.groupchat_jids[self.id_] - del self.conn.groupchat_jids[self.id_] - else: - self.fjid = helpers.get_full_jid_from_iq(self.iq_obj) - self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid) - - def get_id(self): - self.id_ = self.iq_obj.getID() - -class HttpAuthReceivedEvent(nec.NetworkIncomingEvent): - name = 'http-auth-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - self.opt = gajim.config.get_per('accounts', self.conn.name, 'http_auth') - self.iq_id = self.iq_obj.getTagAttr('confirm', 'id') - self.method = self.iq_obj.getTagAttr('confirm', 'method') - self.url = self.iq_obj.getTagAttr('confirm', 'url') - # In case it's a message with a body - self.msg = self.iq_obj.getTagData('body') - return True - -class LastResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): - name = 'last-result-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - self.get_id() - self.get_jid_resource() - if self.id_ in self.conn.last_ids: - self.conn.last_ids.remove(self.id_) - - self.status = '' - self.seconds = -1 - - if self.iq_obj.getType() == 'error': - return True - - qp = self.iq_obj.getTag('query') - sec = qp.getAttr('seconds') - self.status = qp.getData() - try: - self.seconds = int(sec) - except Exception: - return - - return True - -class VersionResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): - name = 'version-result-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - self.get_id() - self.get_jid_resource() - if self.id_ in self.conn.version_ids: - self.conn.version_ids.remove(self.id_) - - self.client_info = '' - self.os_info = '' - - if self.iq_obj.getType() == 'error': - return True - - qp = self.iq_obj.getTag('query') - if qp.getTag('name'): - self.client_info += qp.getTag('name').getData() - if qp.getTag('version'): - self.client_info += ' ' + qp.getTag('version').getData() - if qp.getTag('os'): - self.os_info += qp.getTag('os').getData() - - return True - -class TimeResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): - name = 'time-result-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - self.get_id() - self.get_jid_resource() - if self.id_ in self.conn.entity_time_ids: - self.conn.entity_time_ids.remove(self.id_) - - self.time_info = '' - - if self.iq_obj.getType() == 'error': - return True - - qp = self.iq_obj.getTag('time') - if not qp: - # wrong answer - return - tzo = qp.getTag('tzo').getData() - if tzo.lower() == 'z': - tzo = '0:0' - tzoh, tzom = tzo.split(':') - utc_time = qp.getTag('utc').getData() - ZERO = datetime.timedelta(0) - class UTC(datetime.tzinfo): - def utcoffset(self, dt): - return ZERO - def tzname(self, dt): - return "UTC" - def dst(self, dt): - return ZERO - - class contact_tz(datetime.tzinfo): - def utcoffset(self, dt): - return datetime.timedelta(hours=int(tzoh), minutes=int(tzom)) - def tzname(self, dt): - return "remote timezone" - def dst(self, dt): - return ZERO - - try: - t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ') - t = t.replace(tzinfo=UTC()) - self.time_info = t.astimezone(contact_tz()).strftime('%c') - except ValueError, e: - log.info('Wrong time format: %s' % str(e)) - return - - return True - -class GMailQueryReceivedEvent(nec.NetworkIncomingEvent): - name = 'gmail-notify' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - if not self.iq_obj.getTag('mailbox'): - return - mb = self.iq_obj.getTag('mailbox') - if not mb.getAttr('url'): - return - self.conn.gmail_url = mb.getAttr('url') - if mb.getNamespace() != common.xmpp.NS_GMAILNOTIFY: - return - self.newmsgs = mb.getAttr('total-matched') - if not self.newmsgs: - return - if self.newmsgs == '0': - return - # there are new messages - self.gmail_messages_list = [] - if mb.getTag('mail-thread-info'): - gmail_messages = mb.getTags('mail-thread-info') - for gmessage in gmail_messages: - unread_senders = [] - for sender in gmessage.getTag('senders').getTags( - 'sender'): - if sender.getAttr('unread') != '1': - continue - if sender.getAttr('name'): - unread_senders.append(sender.getAttr('name') + \ - '< ' + sender.getAttr('address') + '>') - else: - unread_senders.append(sender.getAttr('address')) - - if not unread_senders: - continue - gmail_subject = gmessage.getTag('subject').getData() - gmail_snippet = gmessage.getTag('snippet').getData() - tid = int(gmessage.getAttr('tid')) - if not self.conn.gmail_last_tid or \ - tid > self.conn.gmail_last_tid: - self.conn.gmail_last_tid = tid - self.gmail_messages_list.append({ - 'From': unread_senders, - 'Subject': gmail_subject, - 'Snippet': gmail_snippet, - 'url': gmessage.getAttr('url'), - 'participation': gmessage.getAttr('participation'), - 'messages': gmessage.getAttr('messages'), - 'date': gmessage.getAttr('date')}) - self.conn.gmail_last_time = int(mb.getAttr('result-time')) - - self.jid = gajim.get_jid_from_account(self.name) - log.debug(('You have %s new gmail e-mails on %s.') % (self.newmsgs, - self.jid)) - return True - -class RosterItemExchangeEvent(nec.NetworkIncomingEvent, HelperEvent): - name = 'roster-item-exchange-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - self.get_id() - self.get_jid_resource() - self.exchange_items_list = {} - items_list = self.iq_obj.getTag('x').getChildren() - if not items_list: - return - self.action = items_list[0].getAttr('action') - if self.action is None: - self.action = 'add' - for item in self.iq_obj.getTag('x', namespace=common.xmpp.NS_ROSTERX).\ - getChildren(): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - name = item.getAttr('name') - contact = gajim.contacts.get_contact(self.conn.name, jid) - groups = [] - same_groups = True - for group in item.getTags('group'): - groups.append(group.getData()) - # check that all suggested groups are in the groups we have for this - # contact - if not contact or group not in contact.groups: - same_groups = False - if contact: - # check that all groups we have for this contact are in the - # suggested groups - for group in contact.groups: - if group not in groups: - same_groups = False - if contact.sub in ('both', 'to') and same_groups: - continue - self.exchange_items_list[jid] = [] - self.exchange_items_list[jid].append(name) - self.exchange_items_list[jid].append(groups) - if exchange_items_list: - return True - -class VersionRequestEvent(nec.NetworkIncomingEvent): - name = 'version-request-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - return True - -class LastRequestEvent(nec.NetworkIncomingEvent): - name = 'last-request-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - return True - -class TimeRequestEvent(nec.NetworkIncomingEvent): - name = 'time-request-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - return True - -class TimeRevisedRequestEvent(nec.NetworkIncomingEvent): - name = 'time-revised-request-received' - base_network_events = [] - - def generate(self): - if not self.conn: - self.conn = self.base_event.conn - if not self.iq_obj: - self.iq_obj = self.base_event.xmpp_iq - - return True \ No newline at end of file diff --git a/src/common/jingle.py b/src/common/jingle.py index 6b3ff8649..5c11b70ac 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -154,7 +154,7 @@ class ConnectionJingle(object): jingle.on_session_state_changed(c) else: jingle = JingleSession(self, weinitiate=True, jid=jid) - self.__sessions__[jingle.sid] = jingle + self._sessions[jingle.sid] = jingle file_props['sid'] = jingle.sid c = JingleFileTransfer(jingle, file_props=file_props, use_security=use_security) From dc3b203168dc7f0174e9f6ab04e2380890fbe0f7 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 24 Jun 2011 18:24:42 +0200 Subject: [PATCH 060/121] use event system for jingle FT --- src/chat_control.py | 2 +- src/common/connection_handlers_events.py | 91 +++++++++++++++++------- src/common/jingle_ft.py | 53 ++------------ src/common/jingle_xtls.py | 3 +- src/common/socks5.py | 5 +- 5 files changed, 76 insertions(+), 78 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 70a0f3fe0..a31af5cf4 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -53,7 +53,7 @@ from common.logger import constants from common.pep import MOODS, ACTIVITIES from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION -from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP +from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER from common.connection_handlers_events import MessageOutgoingEvent from command_system.implementation.middleware import ChatCommandProcessor diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index 63c7000ac..0b83de8bc 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -34,6 +34,7 @@ from common import exceptions from common.zeroconf import zeroconf from common.logger import LOG_DB_PATH from common.pep import SUPPORTED_PERSONAL_USER_EVENTS +from common.jingle_transport import JingleTransportSocks5 import gtkgui_helpers @@ -1818,6 +1819,10 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'file-request-received' base_network_events = [] + def init(self): + self.jingle_content = None + self.FT_content = None + def generate(self): self.get_id() self.fjid = self.conn._ft_get_from(self.stanza) @@ -1825,28 +1830,36 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.file_props = {'type': 'r'} self.file_props['sender'] = self.fjid self.file_props['request-id'] = self.id_ - si = self.stanza.getTag('si') - profile = si.getAttr('profile') - if profile != xmpp.NS_FILE: - self.conn.send_file_rejection(self.file_props, code='400', typ='profile') - raise xmpp.NodeProcessed - feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE) - if not feature_tag: - return - form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA) - if not form_tag: - return - self.dataform = dataforms.ExtendForm(node=form_tag) - for f in self.dataform.iter_fields(): - if f.var == 'stream-method' and f.type == 'list-single': - values = [o[1] for o in f.options] - self.file_props['stream-methods'] = ' '.join(values) - if xmpp.NS_BYTESTREAM in values or xmpp.NS_IBB in values: - break + if self.jingle_content: + self.file_props['session-type'] = 'jingle' + self.file_props['stream-methods'] = xmpp.NS_BYTESTREAM + file_tag = self.jingle_content.getTag('description').getTag( + 'offer').getTag('file') else: - self.conn.send_file_rejection(self.file_props, code='400', typ='stream') - raise xmpp.NodeProcessed - file_tag = si.getTag('file') + si = self.stanza.getTag('si') + profile = si.getAttr('profile') + if profile != xmpp.NS_FILE: + self.conn.send_file_rejection(self.file_props, code='400', + typ='profile') + raise xmpp.NodeProcessed + feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE) + if not feature_tag: + return + form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA) + if not form_tag: + return + self.dataform = dataforms.ExtendForm(node=form_tag) + for f in self.dataform.iter_fields(): + if f.var == 'stream-method' and f.type == 'list-single': + values = [o[1] for o in f.options] + self.file_props['stream-methods'] = ' '.join(values) + if xmpp.NS_BYTESTREAM in values or xmpp.NS_IBB in values: + break + else: + self.conn.send_file_rejection(self.file_props, code='400', + typ='stream') + raise xmpp.NodeProcessed + file_tag = si.getTag('file') for attribute in file_tag.getAttrs(): if attribute in ('name', 'size', 'hash', 'date'): val = file_tag.getAttr(attribute) @@ -1857,13 +1870,41 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): if file_desc_tag is not None: self.file_props['desc'] = file_desc_tag.getData() - mime_type = si.getAttr('mime-type') - if mime_type is not None: - self.file_props['mime-type'] = mime_type + if not self.jingle_content: + mime_type = si.getAttr('mime-type') + if mime_type is not None: + self.file_props['mime-type'] = mime_type self.file_props['receiver'] = self.conn._ft_get_our_jid() - self.file_props['sid'] = unicode(si.getAttr('id')) self.file_props['transfered_size'] = [] + if self.jingle_content: + self.FT_content.use_security = bool(self.jingle_content.getTag( + 'security')) + self.file_props['session-sid'] = unicode(self.stanza.getTag( + 'jingle').getAttr('sid')) + + self.FT_content.file_props = self.file_props + if not self.FT_content.transport: + self.FT_content.transport = JingleTransportSocks5() + self.FT_content.transport.set_our_jid( + self.FT_content.session.ourjid) + self.FT_content.transport.set_connection( + self.FT_content.session.connection) + self.file_props['sid'] = self.FT_content.transport.sid + self.FT_content.session.connection.files_props[ + self.file_props['sid']] = self.file_props + self.FT_content.transport.set_file_props(self.file_props) + if self.file_props.has_key('streamhosts'): + self.file_props['streamhosts'].extend( + self.FT_content.transport.remote_candidates) + else: + self.file_props['streamhosts'] = \ + self.FT_content.transport.remote_candidates + for host in self.file_props['streamhosts']: + host['initiator'] = self.FT_content.session.initiator + host['target'] = self.FT_content.session.responder + else: + self.file_props['sid'] = unicode(si.getAttr('id')) return True class FileRequestErrorEvent(nec.NetworkIncomingEvent): diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 697d24867..e385202c6 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -25,6 +25,7 @@ from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 from common import helpers from common.socks5 import Socks5Receiver +from common.connection_handlers_events import FileRequestReceivedEvent import logging log = logging.getLogger('gajim.c.jingle_ft') @@ -87,55 +88,9 @@ class JingleFileTransfer(JingleContent): self.media = 'file' def __on_session_initiate(self, stanza, content, error, action): - jid = unicode(stanza.getFrom()) - log.info("jid:%s" % jid) - - file_props = {'type': 'r'} - file_props['sender'] = jid - file_props['request-id'] = unicode(stanza.getAttr('id')) - - file_props['session-type'] = 'jingle' - - self.use_security = bool(content.getTag('security')) - # TODO: extract fingerprint element, encryption method element for later - # use - - file_tag = content.getTag('description').getTag('offer').getTag('file') - for attribute in file_tag.getAttrs(): - if attribute in ('name', 'size', 'hash', 'date'): - val = file_tag.getAttr(attribute) - if val is None: - continue - file_props[attribute] = val - file_desc_tag = file_tag.getTag('desc') - if file_desc_tag is not None: - file_props['desc'] = file_desc_tag.getData() - - file_props['receiver'] = self.session.ourjid - log.info("ourjid: %s" % self.session.ourjid) - file_props['session-sid'] = unicode(stanza.getTag('jingle').getAttr('sid')) - file_props['transfered_size'] = [] - - self.file_props = file_props - - if self.transport is None: - self.transport = JingleTransportSocks5() - self.transport.set_our_jid(self.session.ourjid) - self.transport.set_connection(self.session.connection) - self.file_props['sid'] = self.transport.sid - self.session.connection.files_props[file_props['sid']] = file_props - self.transport.set_file_props(self.file_props) - if self.file_props.has_key('streamhosts'): - self.file_props['streamhosts'].extend( - self.transport.remote_candidates) - else: - self.file_props['streamhosts'] = self.transport.remote_candidates - for host in self.file_props['streamhosts']: - host['initiator'] = self.session.initiator - host['target'] = self.session.responder - log.info("FT request: %s" % file_props) - - self.session.connection.dispatch('FILE_REQUEST', (jid, file_props)) + gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, + conn=self.session.connection, stanza=stanza, jingle_content=content, + FT_content=self)) def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py index 2933a5193..edff95355 100644 --- a/src/common/jingle_xtls.py +++ b/src/common/jingle_xtls.py @@ -104,7 +104,8 @@ def get_context(fingerprint, verify_cb=None): store = ctx.get_cert_store() for f in os.listdir(os.path.expanduser(gajim.MY_PEER_CERTS_PATH)): load_cert_file(os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), f), store) - print 'certificate file' + f + ' loaded', 'fingerprint', fingerprint + log.debug('certificate file ' + f + ' loaded fingerprint ' + \ + fingerprint) return ctx def send_cert(con, jid_from, sid): diff --git a/src/common/socks5.py b/src/common/socks5.py index db2ef0b63..976c0d6ae 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -753,10 +753,11 @@ class Socks5Sender(Socks5, IdleObject): self.queue = parent Socks5.__init__(self, idlequeue, host, port, None, None, None) self._sock = _sock - if not self.fingerprint is None: + if self.fingerprint is not None: self._sock = OpenSSL.SSL.Connection( jingle_xtls.get_context('server'), self._sock) - self._sock.setblocking(False) + else: + self._sock.setblocking(False) self.fd = _sock.fileno() self._recv = _sock.recv self._send = _sock.send From a8fe25aaa4e02c92b228d8c3350b86f77c30ebde Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Tue, 28 Jun 2011 18:31:07 -0400 Subject: [PATCH 061/121] JingleFT IBB fallback --- src/common/jingle_content.py | 7 +++- src/common/jingle_ft.py | 18 ++++++++++- src/common/jingle_session.py | 59 ++++++++++++++++++++++++++++++---- src/common/jingle_transport.py | 36 ++++++++++++++++++++- src/common/xmpp/protocol.py | 1 + 5 files changed, 111 insertions(+), 10 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index e2a5c9a28..586a86acb 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -16,6 +16,7 @@ Handles Jingle contents (XEP 0166) """ import xmpp +from jingle_transport import JingleTransportIBB contents = {} @@ -69,7 +70,7 @@ class JingleContent(object): 'session-initiate': [self.__on_transport_info], 'session-terminate': [], 'transport-info': [self.__on_transport_info], - 'transport-replace': [], + 'transport-replace': [self.__on_transport_replace], 'transport-accept': [], 'transport-reject': [], 'iq-result': [], @@ -109,6 +110,10 @@ class JingleContent(object): for callback in self.callbacks[action]: callback(stanza, content, error, action) + def __on_transport_replace(self, stanza, content, error, action): + + content.addChild(node=self.transport.make_transport()) + def __on_transport_info(self, stanza, content, error, action): """ Got a new transport candidate diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index e385202c6..bbb1a39f7 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -23,6 +23,8 @@ import gajim import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 +from jingle_transport import JingleTransportIBB +from jingle_session import JingleStates from common import helpers from common.socks5 import Socks5Receiver from common.connection_handlers_events import FileRequestReceivedEvent @@ -35,6 +37,7 @@ STATE_INITIALIZED = 1 STATE_ACCEPTED = 2 STATE_TRANSPORT_INFO = 3 STATE_PROXY_ACTIVATED = 4 +STATE_TRANSPORT_REPLACE = 5 class JingleFileTransfer(JingleContent): def __init__(self, session, transport=None, file_props=None, @@ -98,6 +101,19 @@ class JingleFileTransfer(JingleContent): security = content.getTag('security') if not security: # responder can not verify our fingerprint self.use_security = False + + if self.state == STATE_TRANSPORT_REPLACE: + # We ack the session accept + response = stanza.buildReply('result') + con = self.session.connection + con.connection.send(response) + # We send the file + con.files_props[self.file_props['sid']] = self.file_props + fp = open(self.file_props['file-name'], 'r') + con.OpenStream( self.transport.sid, self.session.peerjid, + fp, blocksize=4096) + raise xmpp.NodeProcessed + def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") @@ -197,7 +213,7 @@ class JingleFileTransfer(JingleContent): elif self.weinitiate and self.state == STATE_INITIALIZED: # proxy activated self.state = STATE_PROXY_ACTIVATED - + def send_candidate_used(self, streamhost): """ send candidate-used stanza diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index bddb17b1e..be435d025 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -28,8 +28,10 @@ Handles Jingle sessions (XEP 0166) import gajim #Get rid of that? import xmpp -from jingle_transport import get_jingle_transport +from jingle_transport import get_jingle_transport, JingleTransportIBB from jingle_content import get_jingle_content, JingleContentSetupException +from jingle_content import JingleContent +from jingle_ft import STATE_TRANSPORT_REPLACE from common.connection_handlers_events import * import logging log = logging.getLogger("gajim.c.jingle_session") @@ -213,11 +215,19 @@ class JingleSession(object): if not self.contents: self.end_session() - def modify_content(self, creator, name, *someother): - """ - We do not need this now - """ - pass + def modify_content(self, creator, name, transport = None): + ''' + Currently used for transport replacement + ''' + + content = self.contents[(creator,name)] + transport.set_sid(content.transport.sid) + transport.set_file_props(content.transport.file_props) + content.transport = transport + # The content will have to be resend now that it is modified + content.sent = False + content.accepted = True + def on_session_state_changed(self, content=None): if self.state == JingleStates.ended: @@ -343,18 +353,43 @@ class JingleSession(object): error_name = child.getName() self.__dispatch_error(error_name, text, error.getAttr('type')) # FIXME: Not sure when we would want to do that... + def transport_replace(self): + transport = JingleTransportIBB() + # For debug only, delete this and replace for a function + # that will identify contents by its sid + for creator, name in self.contents.keys(): + self.modify_content(creator, name, transport) + cont = self.contents[(creator, name)] + cont.transport = transport + cont.state = STATE_TRANSPORT_REPLACE + + stanza, jingle = self.__make_jingle('transport-replace') + self.__append_contents(jingle) + self.__broadcast(stanza, jingle, None, 'transport-replace') + self.connection.connection.send(stanza) + #self.collect_iq_id(stanza.getID()) + def __on_transport_replace(self, stanza, jingle, error, action): for content in jingle.iterTags('content'): creator = content['creator'] name = content['name'] if (creator, name) in self.contents: transport_ns = content.getTag('transport').getNamespace() - if transport_ns == xmpp.JINGLE_ICE_UDP: + if transport_ns == xmpp.NS_JINGLE_ICE_UDP: # FIXME: We don't manage anything else than ICE-UDP now... # What was the previous transport?!? # Anyway, content's transport is not modifiable yet pass + elif transport_ns == xmpp.NS_JINGLE_IBB: + + transport = JingleTransportIBB() + self.modify_content(creator, name, transport) + #self.state = JingleStates.pending + self.contents[(creator,name)].state = STATE_TRANSPORT_REPLACE + self.__ack(stanza, jingle, error, action) + self.__session_accept() + else: stanza, jingle = self.__make_jingle('transport-reject') content = jingle.setTag('content', attrs={'creator': creator, @@ -400,6 +435,7 @@ class JingleSession(object): if self.state != JingleStates.pending: raise OutOfOrder self.state = JingleStates.active + def __on_content_accept(self, stanza, jingle, error, action): """ @@ -562,12 +598,19 @@ class JingleSession(object): return (contents, contents_rejected, failure_reason) def __dispatch_error(self, error=None, text=None, type_=None): + + if type_ == 'cancel' and error == 'item-not-found': + # We coudln't connect with sock5stream, we fallback to IBB + self.transport_replace() + return if text: text = '%s (%s)' % (error, text) if type_ != 'modify': gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None, conn=self.connection, jingle_session=self, reason=text or error)) + + def __reason_from_stanza(self, stanza): # TODO: Move to GUI? @@ -595,6 +638,8 @@ class JingleSession(object): attrs['initiator'] = self.initiator elif action == 'session-accept': attrs['responder'] = self.responder + elif action == 'transport-replace': + attrs['initiator'] = self.initiator jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) if reason is not None: jingle.addChild(node=reason) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index ff47a7c23..3c582b773 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -165,7 +165,7 @@ class JingleTransportSocks5(JingleTransport): for addr in socket.getaddrinfo(socket.gethostname(), None): if not addr[4][0] in local_ip_cand and not addr[4][0].startswith('127'): c = {'host': addr[4][0]} - c['candidate_id'] = conn.connection.getAnID() + c['candidate_id'] = self.connection.connection.getAnID() c['port'] = port c['type'] = 'direct' c['jid'] = self.ourjid @@ -272,6 +272,39 @@ class JingleTransportSocks5(JingleTransport): return sesn.send_transport_info(content) + +class JingleTransportIBB(JingleTransport): + + def __init__(self, node=None, block_sz=None): + + JingleTransport.__init__(self, TransportType.streaming) + + if block_sz: + self.block_sz = block_sz + else: + self.block_sz = '4096' + + self.connection = None + self.sid = None + if node and node.getAttr('sid'): + self.sid = node.getAttr('sid') + + + def set_sid(self, sid): + self.sid = sid + + def make_transport(self): + + transport = JingleTransport.make_transport(self) + transport.setNamespace(xmpp.NS_JINGLE_IBB) + transport.setAttr('block-size', self.block_sz) + transport.setAttr('sid', self.sid) + return transport + + def set_file_props(self, file_props): + self.file_props = file_props + + import farsight class JingleTransportICEUDP(JingleTransport): @@ -347,3 +380,4 @@ class JingleTransportICEUDP(JingleTransport): transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP transports[xmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5 +transports[xmpp.NS_JINGLE_IBB] = JingleTransportIBB diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index df4b57a82..f32863674 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -91,6 +91,7 @@ NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EX NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260 +NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' # XEP-0261 NS_LAST = 'jabber:iq:last' NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080 NS_MESSAGE = 'message' # Jabberd2 From ff972b6b67ccde58ef303f7be5292a7fe08d28a8 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 2 Jul 2011 00:47:33 +0200 Subject: [PATCH 062/121] fix import loops --- src/common/jingle_ft.py | 1 - src/common/protocol/bytestream.py | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index bbb1a39f7..f5e2dd9a9 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -24,7 +24,6 @@ import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 from jingle_transport import JingleTransportIBB -from jingle_session import JingleStates from common import helpers from common.socks5 import Socks5Receiver from common.connection_handlers_events import FileRequestReceivedEvent diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index a872faf80..eda3dcccb 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -37,8 +37,6 @@ from common import xmpp from common import gajim from common import helpers from common import dataforms -from common.connection_handlers_events import FileRequestReceivedEvent, \ - FileRequestErrorEvent from common import ged from common import jingle_xtls @@ -250,6 +248,7 @@ class ConnectionBytestream: raise xmpp.NodeProcessed def _siSetCB(self, con, iq_obj): + from common.connection_handlers_events import FileRequestReceivedEvent gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self, stanza=iq_obj)) raise xmpp.NodeProcessed @@ -269,6 +268,7 @@ class ConnectionBytestream: return jid = self._ft_get_from(iq_obj) file_props['error'] = -3 + from common.connection_handlers_events import FileRequestErrorEvent gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self, jid=jid, file_props=file_props, error_msg='')) raise xmpp.NodeProcessed @@ -302,6 +302,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): if contact.get_full_jid() == receiver_jid: file_props['error'] = -5 self.remove_transfer(file_props) + from common.connection_handlers_events import \ + FileRequestErrorEvent gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self, jid=contact.jid, file_props=file_props, error_msg='')) @@ -364,6 +366,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): self._result_socks5_sid, file_props['sid']) if not listener: file_props['error'] = -5 + from common.connection_handlers_events import FileRequestErrorEvent gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self, jid=unicode(receiver), file_props=file_props, error_msg='')) self._connect_error(unicode(receiver), file_props['sid'], @@ -494,6 +497,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): if file_props is not None: self.disconnect_transfer(file_props) file_props['error'] = -3 + from common.connection_handlers_events import \ + FileRequestErrorEvent gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self, jid=to, file_props=file_props, error_msg=msg)) @@ -526,6 +531,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): return file_props = self.files_props[id_] file_props['error'] = -4 + from common.connection_handlers_events import FileRequestErrorEvent gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self, jid=jid, file_props=file_props, error_msg='')) raise xmpp.NodeProcessed From f49f8163a620dfa005dc4176a9c7d888eb5f31cd Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 2 Jul 2011 00:52:35 +0200 Subject: [PATCH 063/121] prevent traceback when using normal socks5 FT --- src/gui_interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui_interface.py b/src/gui_interface.py index 384e3cdfd..080ec6297 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -922,7 +922,8 @@ class Interface: jid = unicode(file_props['receiver']) # End jingle session - if file_props['session-type'] == 'jingle' and file_props['type'] == 'r': + if file_props.get('session-type') == 'jingle' and file_props['type'] ==\ + 'r': session = gajim.connections[account].get_jingle_session(jid, sid=file_props['session-sid']) # get content: From 8fbaaba356c616bec788aedd55e1f634dd7b2a69 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Tue, 5 Jul 2011 14:05:16 -0400 Subject: [PATCH 064/121] Fixes jingleFT IBB fallback --- src/common/jingle_content.py | 11 +++++++++++ src/common/jingle_ft.py | 10 ++++++++-- src/common/jingle_session.py | 8 ++------ src/common/protocol/bytestream.py | 7 +++---- src/common/socks5.py | 2 +- src/gajim.py | 4 +++- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 586a86acb..8fb583431 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -138,6 +138,17 @@ class JingleContent(object): content = self.__content() content.addChild(node=self.transport.make_transport([candidate])) self.session.send_transport_info(content) + + def send_error_candidate(self): + """ + Sends a candidate-error when we can't connect to a candidate. + """ + content = self.__content() + tp = self.transport.make_transport() + tp.addChild(name='candidate-error') + content.addChild(node=tp) + self.session.send_transport_info(content) + def send_description_info(self): content = self.__content() diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index f5e2dd9a9..757b96d83 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -23,7 +23,6 @@ import gajim import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 -from jingle_transport import JingleTransportIBB from common import helpers from common.socks5 import Socks5Receiver from common.connection_handlers_events import FileRequestReceivedEvent @@ -88,6 +87,7 @@ class JingleFileTransfer(JingleContent): self.session = session self.media = 'file' + def __on_session_initiate(self, stanza, content, error, action): gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, @@ -131,6 +131,9 @@ class JingleFileTransfer(JingleContent): if not self.weinitiate: # proxy activated from initiator return + if content.getTag('transport').getTag('candidate-error'): + self.session.transport_replace() + return streamhost_cid = content.getTag('transport').getTag('candidate-used').\ getAttr('cid') streamhost_used = None @@ -147,7 +150,7 @@ class JingleFileTransfer(JingleContent): if proxy['host'] == streamhost_used['host'] and \ proxy['port'] == streamhost_used['port'] and \ proxy['jid'] == streamhost_used['jid']: - streamhost_used = proxy + host_used = proxy break if 'streamhosts' not in self.file_props: self.file_props['streamhosts'] = [] @@ -238,6 +241,9 @@ class JingleFileTransfer(JingleContent): self.session.send_transport_info(content) def _on_connect_error(self, to, _id, sid, code=404): + if code == 404 and self.file_props['sid'] == sid: + self.send_error_candidate() + log.info('connect error, sid=' + sid) def _fill_content(self, content): diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index be435d025..2a6cb8669 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -314,7 +314,7 @@ class JingleSession(object): self.__send_error(stanza, 'bad-request') return # FIXME: If we aren't initiated and it's not a session-initiate... - if action != 'session-initiate' and self.state == JingleStates.ended: + if action not in ['session-initiate','session-terminate'] and self.state == JingleStates.ended: self.__send_error(stanza, 'item-not-found', 'unknown-session') return else: @@ -367,7 +367,7 @@ class JingleSession(object): self.__append_contents(jingle) self.__broadcast(stanza, jingle, None, 'transport-replace') self.connection.connection.send(stanza) - #self.collect_iq_id(stanza.getID()) + self.state = JingleStates.pending def __on_transport_replace(self, stanza, jingle, error, action): @@ -599,10 +599,6 @@ class JingleSession(object): def __dispatch_error(self, error=None, text=None, type_=None): - if type_ == 'cancel' and error == 'item-not-found': - # We coudln't connect with sock5stream, we fallback to IBB - self.transport_replace() - return if text: text = '%s (%s)' % (error, text) if type_ != 'modify': diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index eda3dcccb..561ae3cae 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -128,7 +128,7 @@ class ConnectionBytestream: feature.addChild(node=_feature) field = _feature.setField('stream-method') field.setAttr('type', 'list-single') - #field.addOption(xmpp.NS_BYTESTREAM) + field.addOption(xmpp.NS_BYTESTREAM) field.addOption(xmpp.NS_IBB) self.connection.send(iq) @@ -795,8 +795,7 @@ class ConnectionIBBytestream(ConnectionBytestream): if file_props['seq'] == 65536: file_props['seq'] = 0 self.last_sent_ibb_id = self.connection.send(xmpp.Protocol('iq', - file_props['direction'][1:], payload=[datanode, - self._ampnode])) + file_props['direction'][1:], 'set', payload=[datanode])) current_time = time.time() file_props['elapsed-time'] += current_time - file_props[ 'last-time'] @@ -871,7 +870,7 @@ class ConnectionIBBytestream(ConnectionBytestream): # look in sending files if sid in self.files_props.keys(): conn.send(stanza.buildReply('result')) - gajim.socks5queue.complete_transfer_cb(self.name, file_props) + gajim.socks5queue.complete_transfer_cb(self.name, self.files_props[sid]) del self.files_props[sid] # look in receiving files elif gajim.socks5queue.get_file_props(self.name, sid): diff --git a/src/common/socks5.py b/src/common/socks5.py index 976c0d6ae..a1e48d285 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -209,7 +209,7 @@ class SocksQueue: return # failure_cb exists - this means that it has never been called if 'failure_cb' in file_props and file_props['failure_cb']: - file_props['failure_cb'](streamhost['initiator'], streamhost['id'], + file_props['failure_cb'](streamhost['initiator'], streamhost['idx'], file_props['sid'], code = 404) del(file_props['failure_cb']) diff --git a/src/gajim.py b/src/gajim.py index e87349f9d..4a5c94ac2 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -36,11 +36,13 @@ ## from common import demandimport + demandimport.enable() demandimport.ignore += ['gobject._gobject', 'libasyncns', 'i18n', 'logging.NullHandler', 'dbus.glib', 'dbus.service', 'command_system.implementation.standard', 'OpenSSL.SSL', 'OpenSSL.crypto', - 'common.sleepy', 'DLFCN', 'dl', 'xml.sax', 'xml.sax.handler', 'ic'] + 'common.sleepy', 'DLFCN', 'dl', 'xml.sax', 'xml.sax.handler', 'ic' + ,'FileRequestReceivedEvent'] import os import sys From 27649a64aa2638c08942ce5d6cb5bf66aea76e14 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 5 Jul 2011 20:35:36 +0200 Subject: [PATCH 065/121] remove useless demandimport ignore --- src/gajim.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gajim.py b/src/gajim.py index 4a5c94ac2..e87349f9d 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -36,13 +36,11 @@ ## from common import demandimport - demandimport.enable() demandimport.ignore += ['gobject._gobject', 'libasyncns', 'i18n', 'logging.NullHandler', 'dbus.glib', 'dbus.service', 'command_system.implementation.standard', 'OpenSSL.SSL', 'OpenSSL.crypto', - 'common.sleepy', 'DLFCN', 'dl', 'xml.sax', 'xml.sax.handler', 'ic' - ,'FileRequestReceivedEvent'] + 'common.sleepy', 'DLFCN', 'dl', 'xml.sax', 'xml.sax.handler', 'ic'] import os import sys From c0e4756f12b80203dab429e593d864022a04dd2b Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 14 Jul 2011 19:07:14 -0400 Subject: [PATCH 066/121] fixed session-accept --- src/common/jingle_session.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 2a6cb8669..8f96b54a1 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -385,7 +385,7 @@ class JingleSession(object): transport = JingleTransportIBB() self.modify_content(creator, name, transport) - #self.state = JingleStates.pending + self.state = JingleStates.pending self.contents[(creator,name)].state = STATE_TRANSPORT_REPLACE self.__ack(stanza, jingle, error, action) self.__session_accept() @@ -627,14 +627,12 @@ class JingleSession(object): return (reason, text) def __make_jingle(self, action, reason=None): - stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid)) + stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid), + frm=self.responder) attrs = {'action': action, 'sid': self.sid} - if action == 'session-initiate': - attrs['initiator'] = self.initiator - elif action == 'session-accept': - attrs['responder'] = self.responder - elif action == 'transport-replace': + if action == 'session-initiate' or action == 'session-accept' or \ + action == 'transport-replace': attrs['initiator'] = self.initiator jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) if reason is not None: From 2d624a7b965c65450ae48a73b5a347dc83f365f0 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sun, 17 Jul 2011 18:28:38 -0400 Subject: [PATCH 067/121] test case for jingle --- src/common/jingle_ft.py | 8 +- test/unit/test_jingle.py | 157 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 test/unit/test_jingle.py diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 757b96d83..ea87c7e43 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -74,12 +74,12 @@ class JingleFileTransfer(JingleContent): self.file_props['transfered_size'] = [] log.info("FT request: %s" % file_props) - + if transport is None: self.transport = JingleTransportSocks5() - self.transport.set_connection(session.connection) - self.transport.set_file_props(self.file_props) - self.transport.set_our_jid(session.ourjid) + self.transport.set_connection(session.connection) + self.transport.set_file_props(self.file_props) + self.transport.set_our_jid(session.ourjid) log.info('ourjid: %s' % session.ourjid) if self.file_props is not None: diff --git a/test/unit/test_jingle.py b/test/unit/test_jingle.py new file mode 100644 index 000000000..94131ede7 --- /dev/null +++ b/test/unit/test_jingle.py @@ -0,0 +1,157 @@ +''' +Tests for dispatcher_nb.py +''' +import unittest + +import lib +lib.setup_env() + +from mock import Mock + +from common.protocol.bytestream import ConnectionIBBytestream, ConnectionSocks5Bytestream +from common.xmpp import dispatcher_nb +from common.xmpp import protocol +from common.jingle import ConnectionJingle +from common import gajim +from common.socks5 import SocksQueue +import common + + +session_init = ''' + + + + + + + + + + + + + + + + + + + + + + ''' + + +transport_info = ''' + + + + + + + + + + +''' + +class Connection(Mock, ConnectionJingle, ConnectionSocks5Bytestream, + ConnectionIBBytestream): + + def __init__(self): + Mock.__init__(self) + ConnectionJingle.__init__(self) + ConnectionSocks5Bytestream.__init__(self) + ConnectionIBBytestream.__init__(self) + self.connected = 2 # This tells gajim we are connected + + + def send(self, stanza=None, when=None): + # Called when gajim wants to send something + print str(stanza) + +class TestJingle(unittest.TestCase): + + def setUp(self): + self.dispatcher = dispatcher_nb.XMPPDispatcher() + gajim.nec = Mock() + gajim.socks5queue = SocksQueue(Mock()) + # Setup mock client + self.client = Connection() + self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one? + self.client._caller = Connection() + self.client.defaultNamespace = protocol.NS_CLIENT + self.client.Connection = Connection() # mock transport + self.con = self.client.Connection + self.con.server_resource = None + self.con.connection = Connection() + + ''' + Fake file_props when we recieve a file. Gajim creates a file_props + out of a FileRequestRecieve event and from then on it changes in + a lot of places. It is easier to just copy it in here. + If the session_initiate stanza changes, this also must change. + ''' + self.recieve_file = {'stream-methods': + 'http://jabber.org/protocol/bytestreams', + 'sender': u'jtest@thiessen.im/Gajim', + 'file-name': u'test_recieved_file', + 'request-id': u'43', 'sid': u'39', + 'session-sid': u'38', 'session-type': 'jingle', + 'transfered_size': [], 'receiver': + u'jingleft@thiessen.im/Gajim', 'desc': '', + u'size': u'2273', 'type': 'r', + 'streamhosts': [{'initiator': + u'jtest@thiessen.im/Gajim', + 'target': u'jingleft@thiessen.im/Gajim', + 'cid': u'41', 'state': 0, 'host': u'192.168.2.100', + 'type': u'direct', 'port': u'28011'}, + {'initiator': u'jtest@thiessen.im/Gajim', + 'target': u'jingleft@thiessen.im/Gajim', + 'cid': u'42', 'state': 0, 'host': u'192.168.2.100', + 'type': u'proxy', 'port': u'5000'}], + u'name': u'to'} + + def tearDown(self): + # Unplug if needed + if hasattr(self.dispatcher, '_owner'): + self.dispatcher.PlugOut() + + def _simulate_connect(self): + self.dispatcher.PlugIn(self.client) # client is owner + # Simulate that we have established a connection + self.dispatcher.StreamInit() + self.dispatcher.ProcessNonBlocking("") + + def _simulate_jingle_session(self): + + self.dispatcher.RegisterHandler('iq', self.con._JingleCB, 'set' + , common.xmpp.NS_JINGLE) + self.dispatcher.ProcessNonBlocking(session_init) + session = self.con._sessions.values()[0] # The only session we have + jft = session.contents.values()[0] # jingleFT object + jft.file_props = self.recieve_file # We plug file_props manually + # The user accepts to recieve the file + # we have to manually simulate this behavior + session.approve_session() + self.con.send_file_approval(self.recieve_file) + + self.dispatcher.ProcessNonBlocking(transport_info) + + + def test_jingle_session(self): + self._simulate_connect() + self._simulate_jingle_session() + + + + +if __name__ == '__main__': + unittest.main() From d92d86e6bca75141af8166b5a155dd05cddb3c7f Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 22 Jul 2011 16:15:34 -0400 Subject: [PATCH 068/121] connecting to candidates in session-accept --- src/common/jingle_content.py | 1 + src/common/jingle_ft.py | 30 +++++++++++++++++++++++++----- src/common/jingle_session.py | 2 +- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 8fb583431..005d45831 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -100,6 +100,7 @@ class JingleContent(object): """ Add a list of candidates to the list of remote candidates """ + self.transport.remote_candidates = candidates pass def on_stanza(self, stanza, content, error, action): diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index ea87c7e43..c3bb9a9a6 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -96,15 +96,15 @@ class JingleFileTransfer(JingleContent): def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") - + con = self.session.connection security = content.getTag('security') if not security: # responder can not verify our fingerprint self.use_security = False - + + if self.state == STATE_TRANSPORT_REPLACE: # We ack the session accept response = stanza.buildReply('result') - con = self.session.connection con.connection.send(response) # We send the file con.files_props[self.file_props['sid']] = self.file_props @@ -112,7 +112,27 @@ class JingleFileTransfer(JingleContent): con.OpenStream( self.transport.sid, self.session.peerjid, fp, blocksize=4096) raise xmpp.NodeProcessed - + + self.file_props['streamhosts'] = self.transport.remote_candidates + for host in self.file_props['streamhosts']: + host['initiator'] = self.session.initiator + host['target'] = self.session.responder + + response = stanza.buildReply('result') + con.connection.send(response) + + if not gajim.socks5queue.get_file_props( + self.session.connection.name, self.file_props['sid']): + gajim.socks5queue.add_file_props(self.session.connection.name, + self.file_props) + fingerprint = None + if self.use_security: + fingerprint = 'client' + gajim.socks5queue.connect_to_hosts(self.session.connection.name, + self.file_props['sid'], self.send_candidate_used, + self._on_connect_error, fingerprint=fingerprint) + + raise xmpp.NodeProcessed def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") @@ -245,7 +265,7 @@ class JingleFileTransfer(JingleContent): self.send_error_candidate() log.info('connect error, sid=' + sid) - + def _fill_content(self, content): description_node = xmpp.simplexml.Node( tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 8f96b54a1..29dcf6b71 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -113,7 +113,7 @@ class JingleSession(object): 'description-info': [self.__broadcast, self.__ack], #TODO 'security-info': [self.__ack], #TODO 'session-accept': [self.__on_session_accept, self.__on_content_accept, - self.__broadcast, self.__ack], + self.__broadcast], 'session-info': [self.__broadcast, self.__on_session_info, self.__ack], 'session-initiate': [self.__on_session_initiate, self.__broadcast, self.__ack], From d21b0914719e341c08eb6e61fe859302ebd66cca Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 22 Jul 2011 16:44:28 -0400 Subject: [PATCH 069/121] fix session-initiate --- src/common/jingle_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 29dcf6b71..f560b8330 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -628,7 +628,7 @@ class JingleSession(object): def __make_jingle(self, action, reason=None): stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid), - frm=self.responder) + frm=self.ourjid) attrs = {'action': action, 'sid': self.sid} if action == 'session-initiate' or action == 'session-accept' or \ From 11f1c8c275cf0d61b27084204307da14c32bab45 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 22 Jul 2011 17:03:56 -0400 Subject: [PATCH 070/121] fix candidate-error --- src/common/jingle_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 3c582b773..3cd191d21 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -295,7 +295,7 @@ class JingleTransportIBB(JingleTransport): def make_transport(self): - transport = JingleTransport.make_transport(self) + transport = xmpp.Node('transport') transport.setNamespace(xmpp.NS_JINGLE_IBB) transport.setAttr('block-size', self.block_sz) transport.setAttr('sid', self.sid) From 6402505983ec451b77648e3a082001ee9aced47d Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 22 Jul 2011 18:00:29 -0400 Subject: [PATCH 071/121] fix candidate-error --- src/common/jingle_content.py | 2 +- src/common/jingle_transport.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 005d45831..dcc7e5bf8 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -145,7 +145,7 @@ class JingleContent(object): Sends a candidate-error when we can't connect to a candidate. """ content = self.__content() - tp = self.transport.make_transport() + tp = self.transport.make_transport(add_candidates=False) tp.addChild(name='candidate-error') content.addChild(node=tp) self.session.send_transport_info(content) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 3cd191d21..8db288b48 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -117,11 +117,14 @@ class JingleTransportSocks5(JingleTransport): return xmpp.Node('candidate', attrs=attrs) - def make_transport(self, candidates=None): - self._add_local_ips_as_candidates() - self._add_additional_candidates() - self._add_proxy_candidates() - transport = JingleTransport.make_transport(self, candidates) + def make_transport(self, candidates=None, add_candidates = True): + if add_candidates: + self._add_local_ips_as_candidates() + self._add_additional_candidates() + self._add_proxy_candidates() + transport = JingleTransport.make_transport(self, candidates) + else: + transport = xmpp.Node('transport') transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) transport.setAttr('sid', self.sid) return transport From d2fddc2562e7acf0587df736c7d68180a283dacf Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Wed, 27 Jul 2011 14:45:10 -0400 Subject: [PATCH 072/121] initiator on transport-info --- src/common/jingle_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index f560b8330..10ae90924 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -632,7 +632,7 @@ class JingleSession(object): attrs = {'action': action, 'sid': self.sid} if action == 'session-initiate' or action == 'session-accept' or \ - action == 'transport-replace': + action == 'transport-replace' or action == 'transport-info': attrs['initiator'] = self.initiator jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) if reason is not None: From d5ffbf8834f2928a4ab7aa5228096d25fb2e678a Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Wed, 27 Jul 2011 16:47:55 -0400 Subject: [PATCH 073/121] include ibb and bytestream, jingle transports in feature disco --- src/common/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/helpers.py b/src/common/helpers.py index d306f9ec8..25255655f 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -1318,6 +1318,8 @@ def update_optional_features(account = None): gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_FILE_TRANSFER) gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_XTLS) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_BYTESTREAM) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_IBB) gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity], gajim.gajim_common_features + gajim.gajim_optional_features[a]) # re-send presence with new hash From af9d304c52e800965ed45cacf5bb253160d706e0 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Wed, 27 Jul 2011 22:01:49 -0400 Subject: [PATCH 074/121] add initiator to every jingle stanza --- src/common/jingle_session.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 10ae90924..71b8b6d58 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -630,10 +630,9 @@ class JingleSession(object): stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid), frm=self.ourjid) attrs = {'action': action, - 'sid': self.sid} - if action == 'session-initiate' or action == 'session-accept' or \ - action == 'transport-replace' or action == 'transport-info': - attrs['initiator'] = self.initiator + 'sid': self.sid, + 'initiator' : self.initiator} + jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) if reason is not None: jingle.addChild(node=reason) From af2e7c89afca3ce8e8bedffab94d50331972c9e9 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 1 Aug 2011 23:12:30 -0400 Subject: [PATCH 075/121] move plug_idle into socks5queue --- src/common/socks5.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index a1e48d285..1aa53ac43 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -328,6 +328,8 @@ class SocksQueue: if sock_hash not in self.senders: self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, sock[0], sock[1][0], sock[1][1], fingerprint='server') + # Start waiting for data + self.idlequeue.plug_idle(self.senders[sock_hash], False, True) self.connected += 1 def process_result(self, result, actor): @@ -764,8 +766,7 @@ class Socks5Sender(Socks5, IdleObject): self.connected = True self.state = 1 # waiting for first bytes self.file_props = None - # start waiting for data - self.idlequeue.plug_idle(self, False, True) + def read_timeout(self): self.idlequeue.remove_timeout(self.fd) From 92b932ca077064f72f23595707c02cffd76d12b9 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 1 Aug 2011 23:13:56 -0400 Subject: [PATCH 076/121] added testing suite for socks5 --- test/unit/test_socks5.py | 128 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 test/unit/test_socks5.py diff --git a/test/unit/test_socks5.py b/test/unit/test_socks5.py new file mode 100644 index 000000000..ced87045e --- /dev/null +++ b/test/unit/test_socks5.py @@ -0,0 +1,128 @@ +''' +Tests for dispatcher_nb.py +''' +import unittest + +import lib +lib.setup_env() + +from mock import Mock +import sys + +from common.socks5 import * + +class fake_sock(Mock): + def __init__(self, sockobj): + Mock.__init__(self) + + self.sockobj = sockobj + + + def setup_stream(self): + sha1 = self.sockobj._get_sha1_auth() + + self.incoming = [] + self.incoming.append(self.sockobj._get_auth_response()) + self.incoming.append( + self.sockobj._get_request_buff(sha1, 0x00) + ) + self.outgoing = [] + self.outgoing.append(self.sockobj._get_auth_buff()) + self.outgoing.append(self.sockobj._get_request_buff( + sha1 + )) + def switch_stream(self): + # Roles are reversed, client will be expecting server stream + # and server will be expecting client stream + + temp = self.incoming + self.incoming = self.outgoing + self.outgoing = temp + + def _recv(self, foo): + return self.incoming.pop(0) + + def _send(self, data): + # This method is surrounded by a try block, + # we can't use assert here + + if data != self.outgoing[0]: + print 'FAILED SENDING TEST' + self.outgoing.pop(0) + +class fake_idlequeue(Mock): + + def __init__(self): + Mock.__init__(self) + + def plug_idle(self, obj, writable=True, readable=True): + + if readable: + obj.pollin() + if writable: + obj.pollout() + +class TestSocks5(unittest.TestCase): + ''' + Test class for Socks5 + ''' + def setUp(self): + streamhost = { 'host': None, + 'port': 1, + 'initiator' : None, + 'target' : None} + #self.sockobj = Socks5Receiver(fake_idlequeue(), streamhost, None) + self.sockobj = Socks5Sender(fake_idlequeue(), None, None, Mock() ) + sock = fake_sock(self.sockobj) + self.sockobj._sock = sock + self.sockobj._recv = sock._recv + self.sockobj._send = sock._send + self.sockobj.state = 1 + self.sockobj.connected = True + self.sockobj.pollend = self._pollend + + # Something that the receiver needs + #self.sockobj.file_props['type'] = 'r' + + # Something that the sender needs + self.sockobj.file_props = {} + self.sockobj.file_props['type'] = 'r' + self.sockobj.queue = Mock() + self.sockobj.queue.process_result = self._pollend + + def _pollend(self, foo = None, duu = None): + # This is a disconnect function + sys.exit("end of the road") + + def _check_inout(self): + # Check if there isn't anything else to receive or send + sock = self.sockobj._sock + assert(sock.incoming == []) + assert(sock.outgoing == []) + + def test_client_negoc(self): + return + self.sockobj._sock.setup_stream() + try: + self.sockobj.pollout() + except SystemExit: + pass + + self._check_inout() + + def test_server_negoc(self): + + self.sockobj._sock.setup_stream() + self.sockobj._sock.switch_stream() + try: + self.sockobj.idlequeue.plug_idle(self.sockobj, False, True) + except SystemExit: + pass + self._check_inout() + + + + +if __name__ == '__main__': + + unittest.main() From 4c451a2283a90ad6bafde4f1b63d2a7cf260760d Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 4 Aug 2011 17:02:55 -0400 Subject: [PATCH 077/121] commenting experimental code --- src/common/jingle_ft.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index c3bb9a9a6..dbd14068c 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -112,15 +112,15 @@ class JingleFileTransfer(JingleContent): con.OpenStream( self.transport.sid, self.session.peerjid, fp, blocksize=4096) raise xmpp.NodeProcessed - + ''' self.file_props['streamhosts'] = self.transport.remote_candidates for host in self.file_props['streamhosts']: host['initiator'] = self.session.initiator host['target'] = self.session.responder - + ''' response = stanza.buildReply('result') con.connection.send(response) - + ''' if not gajim.socks5queue.get_file_props( self.session.connection.name, self.file_props['sid']): gajim.socks5queue.add_file_props(self.session.connection.name, @@ -131,7 +131,7 @@ class JingleFileTransfer(JingleContent): gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], self.send_candidate_used, self._on_connect_error, fingerprint=fingerprint) - + ''' raise xmpp.NodeProcessed def __on_session_terminate(self, stanza, content, error, action): From 9f3d472564f9e2a86c3342601aa076bca2ffa317 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 8 Aug 2011 19:43:07 -0400 Subject: [PATCH 078/121] bidirectional jingle FT with socks5 --- src/common/jingle_ft.py | 57 +-- src/common/socks5.py | 788 +++++++++++++++++++++++----------------- 2 files changed, 486 insertions(+), 359 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index c3bb9a9a6..4e2452c5d 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -51,6 +51,7 @@ class JingleFileTransfer(JingleContent): self.callbacks['session-terminate'] += [self.__on_session_terminate] self.callbacks['transport-accept'] += [self.__on_transport_accept] self.callbacks['transport-replace'] += [self.__on_transport_replace] + self.callbacks['session-accept-sent'] += [self._listen_host] # fallback transport method self.callbacks['transport-reject'] += [self.__on_transport_reject] self.callbacks['transport-info'] += [self.__on_transport_info] @@ -117,7 +118,8 @@ class JingleFileTransfer(JingleContent): for host in self.file_props['streamhosts']: host['initiator'] = self.session.initiator host['target'] = self.session.responder - + host['sid'] = self.file_props['sid'] + response = stanza.buildReply('result') con.connection.send(response) @@ -130,7 +132,8 @@ class JingleFileTransfer(JingleContent): fingerprint = 'client' gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], self.send_candidate_used, - self._on_connect_error, fingerprint=fingerprint) + self._on_connect_error, fingerprint=fingerprint, + receiving=False) raise xmpp.NodeProcessed @@ -195,25 +198,8 @@ class JingleFileTransfer(JingleContent): self.state = STATE_INITIALIZED self.session.connection.files_props[self.file_props['sid']] = \ self.file_props - receiver = self.file_props['receiver'] - sender = self.file_props['sender'] - - sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, - receiver) - self.file_props['sha_str'] = sha_str - - port = gajim.config.get('file_transfers_port') - - fingerprint = None - if self.use_security: - fingerprint = 'server' - listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props['sid'], - fingerprint=fingerprint) - - if not listener: - return - # send error message, notify the user + # Listen on configured port for file transfer + self._listen_host() elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result if not self.negotiated: @@ -295,7 +281,36 @@ class JingleFileTransfer(JingleContent): def _store_socks5_sid(self, sid, hash_id): # callback from socsk5queue.start_listener self.file_props['hash'] = hash_id + + def _listen_host(self, stanza=None, content=None, error=None + , action=None): + + receiver = self.file_props['receiver'] + sender = self.file_props['sender'] + sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, + receiver) + self.file_props['sha_str'] = sha_str + + port = gajim.config.get('file_transfers_port') + + fingerprint = None + if self.use_security: + fingerprint = 'server' + return + if self.weinitiate: + listener = gajim.socks5queue.start_listener(port, sha_str, + self._store_socks5_sid, self.file_props['sid'], + fingerprint=fingerprint, type='sender') + else: + listener = gajim.socks5queue.start_listener(port, sha_str, + self._store_socks5_sid, self.file_props['sid'], + fingerprint=fingerprint, type='receiver') + + if not listener: + # send error message, notify the user + return + def get_content(desc): return JingleFileTransfer diff --git a/src/common/socks5.py b/src/common/socks5.py index 1aa53ac43..8a3b8b5f5 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -84,11 +84,13 @@ class SocksQueue: self.on_success = {} # {id: cb} self.on_failure = {} # {id: cb} - def start_listener(self, port, sha_str, sha_handler, sid, fingerprint=None): + def start_listener(self, port, sha_str, sha_handler, sid, fingerprint=None, + type='sender'): """ Start waiting for incomming connections on (host, port) and do a socks5 authentication using sid for generated SHA """ + self.type = type # It says whether we are sending or receiving self.sha_handlers[sha_str] = (sha_handler, sid) if self.listener is None: self.listener = Socks5Listener(self.idlequeue, port, fingerprint=fingerprint) @@ -123,7 +125,7 @@ class SocksQueue: return 0 def connect_to_hosts(self, account, sid, on_success=None, on_failure=None, - fingerprint=None): + fingerprint=None, receiving=True): self.on_success[sid] = on_success self.on_failure[sid] = on_failure file_props = self.files_props[account][sid] @@ -135,38 +137,59 @@ class SocksQueue: fp = None else: fp = fingerprint - receiver = Socks5Receiver(self.idlequeue, streamhost, sid, - file_props, fingerprint=fp) - self.add_receiver(account, receiver) - streamhost['idx'] = receiver.queue_idx + if receiving: + self.type = 'receiver' + socks5obj = Socks5Receiver(self.idlequeue, streamhost, sid, + 'client', file_props, + fingerprint=fp) + self.add_sockobj(account, socks5obj) + else: + self.type = 'sender' + socks5obj = Socks5Sender(self.idlequeue, + file_props['sha_str'], self, mode='client' , + _sock=None, host=str(streamhost['host']), + port=int(streamhost['port']), fingerprint=fp, + connected=False) + socks5obj.file_props = file_props + socks5obj.streamhost = streamhost + self.add_sockobj(account, socks5obj, type='sender') + + socks5obj.file_props = file_props + streamhost['idx'] = socks5obj.queue_idx def _socket_connected(self, streamhost, file_props): """ Called when there is a host connected to one of the senders's - streamhosts. Stop othere attempts for connections + streamhosts. Stop other attempts for connections """ for host in file_props['streamhosts']: if host != streamhost and 'idx' in host: if host['state'] == 1: # remove current - self.remove_receiver(streamhost['idx']) + if self.type == 'sender': + self.remove_sender(streamhost['idx'], False) + else: + self.remove_receiver(streamhost['idx']) return # set state -2, meaning that this streamhost is stopped, # but it may be connectected later if host['state'] >= 0: - self.remove_receiver(host['idx']) + if self.type == 'sender': + self.remove_sender(host['idx'], False) + else: + self.remove_receiver(host['idx']) host['idx'] = -1 host['state'] = -2 - def reconnect_receiver(self, receiver, streamhost): + def reconnect_client(self, client, streamhost): """ Check the state of all streamhosts and if all has failed, then emit connection failure cb. If there are some which are still not connected try to establish connection to one of them """ - self.idlequeue.remove_timeout(receiver.fd) - self.idlequeue.unplug_idle(receiver.fd) - file_props = receiver.file_props + self.idlequeue.remove_timeout(client.fd) + self.idlequeue.unplug_idle(client.fd) + file_props = client.file_props streamhost['state'] = -1 # boolean, indicates that there are hosts, which are not tested yet unused_hosts = False @@ -180,20 +203,22 @@ class SocksQueue: for host in file_props['streamhosts']: if host['state'] == -2: host['state'] = 0 - receiver = Socks5Receiver(self.idlequeue, host, host['sid'], - file_props) - self.add_receiver(receiver.account, receiver) - host['idx'] = receiver.queue_idx + # FIXME: make the sender reconnect also + print 'reconnecting using socks receiver' + client = Socks5Receiver(self.idlequeue, host, host['sid'], + 'client',file_props) + self.add_sockobj(client.account, client) + host['idx'] = client.queue_idx # we still have chances to connect return if 'received-len' not in file_props or file_props['received-len'] == 0: # there are no other streamhosts and transfer hasn't started - self._connection_refused(streamhost, file_props, receiver.queue_idx) + self._connection_refused(streamhost, file_props, client.queue_idx) else: # transfer stopped, it is most likely stopped from sender - receiver.disconnect() + client.disconnect() file_props['error'] = -1 - self.process_result(-1, receiver) + self.process_result(-1, client) def _connection_refused(self, streamhost, file_props, idx): """ @@ -213,20 +238,23 @@ class SocksQueue: file_props['sid'], code = 404) del(file_props['failure_cb']) - def add_receiver(self, account, sock5_receiver): + def add_sockobj(self, account, sockobj, type='receiver'): """ - Add new file request + Add new file a sockobj type receiver or sendder """ - self.readers[self.idx] = sock5_receiver - sock5_receiver.queue_idx = self.idx - sock5_receiver.queue = self - sock5_receiver.account = account + if type == 'receiver': + self.readers[self.idx] = sockobj + else: + self.senders[self.idx] = sockobj + sockobj.queue_idx = self.idx + sockobj.queue = self + sockobj.account = account self.idx += 1 - result = sock5_receiver.connect() + result = sockobj.connect() self.connected += 1 if result is not None: - result = sock5_receiver.main() - self.process_result(result, sock5_receiver) + result = sockobj.main() + self.process_result(result, sockobj) return 1 return None @@ -325,13 +353,26 @@ class SocksQueue: def on_connection_accepted(self, sock): sock_hash = sock.__hash__() - if sock_hash not in self.senders: + if self.type == 'sender' and (sock_hash not in self.senders): self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, - sock[0], sock[1][0], sock[1][1], fingerprint='server') + sock[0], 'server', sock[1][0], sock[1][1], + fingerprint='server') # Start waiting for data self.idlequeue.plug_idle(self.senders[sock_hash], False, True) self.connected += 1 - + + if self.type == 'receiver' and (sock_hash not in self.readers): + sh = {} + sh['host'] = sock[1][0] + sh['port'] = sock[1][1] + sh['initiator'] = None + sh['target'] = None + self.readers[sock_hash] = Socks5Receiver(idlequeue=self.idlequeue, + streamhost=sh,sid=None, file_props=None, + mode='server',fingerprint='server') + self.readers[sock_hash].set_sock(sock[0]) + self.connected += 1 + def process_result(self, result, actor): """ Take appropriate actions upon the result: @@ -373,10 +414,13 @@ class SocksQueue: """ if idx != -1: if idx in self.senders: + sender = self.senders[idx] if do_disconnect: self.senders[idx].disconnect() return else: + self.idlequeue.unplug_idle(sender.fd) + self.idlequeue.remove_timeout(sender.fd) del(self.senders[idx]) if self.connected > 0: self.connected -= 1 @@ -407,7 +451,329 @@ class Socks5: self.size = 0 self.remaining_buff = '' self.file = None + self.connected = False + def start_transfer(self): + """ + Must be implemented by subclass. + """ + pass + + def _is_connected(self): + if self.state < 5: + return False + return True + + def connect(self): + """ + Create the socket and plug it to the idlequeue + """ + if self.ais is None: + return None + + for ai in self.ais: + try: + self._sock = socket.socket(*ai[:3]) + if not self.fingerprint is None: + self._sock = OpenSSL.SSL.Connection( + jingle_xtls.get_context('client'), self._sock) + # this will not block the GUI + self._sock.setblocking(False) + self._server = ai[4] + break + except socket.error, e: + if not isinstance(e, basestring) and e[0] == EINPROGRESS: + break + # for all other errors, we try other addresses + continue + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + self.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None + + def do_connect(self): + try: + self._sock.connect(self._server) + self._sock.setblocking(False) + self._send=self._sock.send + self._recv=self._sock.recv + except Exception, ee: + errnum = ee[0] + self.connect_timeout += 1 + if errnum == 111 or self.connect_timeout > 1000: + self.queue._connection_refused(self.streamhost, + self.file_props, self.queue_idx) + self.connected = False + return None + # win32 needs this + elif errnum not in (10056, EISCONN) or self.state != 0: + return None + else: # socket is already connected + self._sock.setblocking(False) + self._send=self._sock.send + self._recv=self._sock.recv + self.buff = '' + self.connected = True + self.file_props['connected'] = True + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['paused'] = False + self.state = 1 # connected + + # stop all others connections to sender's streamhosts + self.queue._socket_connected(self.streamhost, self.file_props) + self.idlequeue.plug_idle(self, True, False) + return 1 # we are connected + + def svr_main(self): + """ + Initial requests for verifying the connection + """ + if self.state == 1: # initial read + buff = self.receive() + if not self.connected: + return -1 + mechs = self._parse_auth_buff(buff) + if mechs is None: + return -1 # invalid auth methods received + elif self.state == 3: # get next request + buff = self.receive() + req_type, self.sha_msg = self._parse_request_buff(buff)[:2] + if req_type != 0x01: + return -1 # request is not of type 'connect' + self.state += 1 # go to the next step + # unplug & plug for writing + self.idlequeue.plug_idle(self, True, False) + return None + + def clnt_main(self, timeout=0): + """ + Begin negotiation. on success 'address' != 0 + """ + result = 1 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return + + if self.state == 2: # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.disconnect() + elif self.state == 4: # get approve of our request + if buff is None: + return None + sub_buff = buff[:4] + if len(sub_buff) < 4: + return None + version, address_type = struct.unpack('!BxxB', buff[:4]) + addrlen = 0 + if address_type == 0x03: + addrlen = ord(buff[4]) + address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) + portlen = len(buff[addrlen + 5:]) + if portlen == 1: + port, = struct.unpack('!B', buff[addrlen + 5]) + elif portlen == 2: + port, = struct.unpack('!H', buff[addrlen + 5:]) + else: # Gaim bug :) + port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) + self.remaining_buff = buff[addrlen + 7:] + self.state = 5 # for senders: init file_props and send '\n' + if self.queue.on_success: + result = self.queue.send_success_reply(self.file_props, + self.streamhost) + if result == 0: + self.state = 8 + self.disconnect() + + # for senders: init file_props + if result == 1 and self.state == 5: + if self.file_props['type'] == 's': + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['stalled'] = False + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + # start sending file contents to socket + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + self.idlequeue.plug_idle(self, True, False) + else: + # receiving file contents from socket + self.idlequeue.plug_idle(self, False, True) + self.file_props['continue_cb'] = self.continue_paused_transfer + # we have set up the connection, next - retrieve file + self.state = 6 + if self.state < 5: + self.idlequeue.plug_idle(self, True, False) + self.state += 1 + return None + + def pollout(self): + if self.mode == 'client': + self.clnt_pollout() + elif self.mode == 'server': + self.svr_pollout() + + def svr_pollout(self): + if not self.connected: + self.disconnect() + return + self.idlequeue.remove_timeout(self.fd) + if self.state == 2: # send reply with desired auth type + self.send_raw(self._get_auth_response()) + elif self.state == 4: # send positive response to the 'connect' + self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) + elif self.state == 7: + if self.file_props['paused']: + self.file_props['continue_cb'] = self.continue_paused_transfer + self.idlequeue.plug_idle(self, False, False) + return + result = self.start_transfer() # send + self.queue.process_result(result, self) + if result is None or result <= 0: + self.disconnect() + return + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + elif self.state == 8: + self.disconnect() + return + else: + self.disconnect() + if self.state < 5: + self.state += 1 + # unplug and plug this time for reading + self.idlequeue.plug_idle(self, False, True) + + def clnt_pollout(self): + self.idlequeue.remove_timeout(self.fd) + try: + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + self.send_raw(self._get_auth_buff()) + elif self.state == 3: # send 'connect' request + self.send_raw(self._get_request_buff(self._get_sha1_auth())) + elif self.file_props['type'] != 'r': + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + result = self.start_transfer() # send + self.queue.process_result(result, self) + return + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') + return + self.state += 1 + # unplug and plug for reading + self.idlequeue.plug_idle(self, False, True) + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + + + def pollin(self): + + if self.mode == 'client': + self.clnt_pollin() + elif self.mode == 'server': + self.svr_pollin() + + def svr_pollin(self): + if self.connected: + try: + if self.state < 5: + result = self.svr_main() + if self.state == 4: + self.queue.result_sha(self.sha_msg, self.queue_idx) + if result == -1: + self.disconnect() + + elif self.state == 5: + if self.file_props is not None and self.file_props['type'] == 'r': + result = self.start_transfer() # receive + self.queue.process_result(result, self) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') + else: + self.disconnect() + + def clnt_pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.connected: + try: + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + if self.state < 5: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + result = self.clnt_main(0) + self.queue.process_result(result, self) + elif self.state == 5: # wait for proxy reply + pass + elif self.file_props['type'] == 'r': + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.start_transfer() # receive + self.queue.process_result(result, self) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') + return + else: + self.disconnect() + + + def pollend(self): + + if self.mode == 'client': + self.clnt_pollend() + elif self.mode == 'server': + self.svr_pollend() + + def svr_pollend(self): + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) + + def clnt_pollend(self): + if self.state >= 5: + # error during transfer + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) + else: + self.queue.reconnect_client(self, self.streamhost) + + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state > 5: + # no activity for foo seconds + if self.file_props['stalled'] == False: + self.file_props['stalled'] = True + self.queue.process_result(-1, self) + #if 'received-len' not in self.file_props: + # self.file_props['received-len'] = 0 + if SEND_TIMEOUT > 0: + self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) + else: + # stop transfer, there is no error code for this + self.pollend() + + else: + if self.mode == 'client': + self.queue.reconnect_client(self, self.streamhost) + def open_file_for_reading(self): if self.file is None: try: @@ -748,95 +1114,41 @@ class Socks5Sender(Socks5, IdleObject): Class for sending file to socket over socks5 """ - def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, - port=None, fingerprint = None): + def __init__(self, idlequeue, sock_hash, parent, mode,_sock, host=None, + port=None, fingerprint = None, connected=True): self.fingerprint = fingerprint self.queue_idx = sock_hash self.queue = parent + self.mode = mode # client or server + self.file_props = {} + self.file_props['paused'] = False Socks5.__init__(self, idlequeue, host, port, None, None, None) self._sock = _sock - if self.fingerprint is not None: - self._sock = OpenSSL.SSL.Connection( - jingle_xtls.get_context('server'), self._sock) - else: - self._sock.setblocking(False) - self.fd = _sock.fileno() - self._recv = _sock.recv - self._send = _sock.send - self.connected = True - self.state = 1 # waiting for first bytes - self.file_props = None - - - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state > 5: - # no activity for foo seconds - if self.file_props['stalled'] == False: - self.file_props['stalled'] = True - self.queue.process_result(-1, self) - if SEND_TIMEOUT > 0: - self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) + + + if _sock is not None: + if self.fingerprint is not None: + self._sock = OpenSSL.SSL.Connection( + jingle_xtls.get_context('server'), self._sock) else: - # stop transfer, there is no error code for this - self.pollend() - - def pollout(self): - if not self.connected: - self.disconnect() - return - self.idlequeue.remove_timeout(self.fd) - if self.state == 2: # send reply with desired auth type - self.send_raw(self._get_auth_response()) - elif self.state == 4: # send positive response to the 'connect' - self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) - elif self.state == 7: - if self.file_props['paused']: - self.file_props['continue_cb'] = self.continue_paused_transfer - self.idlequeue.plug_idle(self, False, False) - return - result = self.write_next() - self.queue.process_result(result, self) - if result is None or result <= 0: - self.disconnect() - return - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - elif self.state == 8: - self.disconnect() - return - else: - self.disconnect() - if self.state < 5: - self.state += 1 - # unplug and plug this time for reading - self.idlequeue.plug_idle(self, False, True) - - def pollend(self): - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) - - def pollin(self): - if self.connected: - try: - if self.state < 5: - result = self.main() - if self.state == 4: - self.queue.result_sha(self.sha_msg, self.queue_idx) - if result == -1: - self.disconnect() - - elif self.state == 5: - if self.file_props is not None and self.file_props['type'] == 'r': - result = self.get_file_contents(0) - self.queue.process_result(result, self) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: - log.info('caught SSL exception, ignored') - else: - self.disconnect() + self._sock.setblocking(False) + + self.fd = _sock.fileno() + self._recv = _sock.recv + self._send = _sock.send + self.connected = connected + self.state = 1 # waiting for first bytes + self.connect_timeout = 0 + + def start_transfer(self): + """ + Send the file + """ + return self.write_next() + + + def send_file(self): """ Start sending the file over verified connection @@ -860,27 +1172,6 @@ class Socks5Sender(Socks5, IdleObject): self.idlequeue.plug_idle(self, True, False) return self.write_next() # initial for nl byte - def main(self): - """ - Initial requests for verifying the connection - """ - if self.state == 1: # initial read - buff = self.receive() - if not self.connected: - return -1 - mechs = self._parse_auth_buff(buff) - if mechs is None: - return -1 # invalid auth methods received - elif self.state == 3: # get next request - buff = self.receive() - req_type, self.sha_msg = self._parse_request_buff(buff)[:2] - if req_type != 0x01: - return -1 # request is not of type 'connect' - self.state += 1 # go to the next step - # unplug & plug for writing - self.idlequeue.plug_idle(self, True, False) - return None - def disconnect(self, cb=True): """ Close the socket @@ -991,7 +1282,8 @@ class Socks5Listener(IdleObject): return _sock class Socks5Receiver(Socks5, IdleObject): - def __init__(self, idlequeue, streamhost, sid, file_props = None, fingerprint=None): + def __init__(self, idlequeue, streamhost, sid, mode, file_props = None, + fingerprint=None): """ fingerprint: fingerprint of certificates we shall use, set to None if TLS connection not desired """ @@ -1012,221 +1304,41 @@ class Socks5Receiver(Socks5, IdleObject): self.file_props['paused'] = False self.file_props['continue_cb'] = self.continue_paused_transfer self.file_props['stalled'] = False + self.mode = mode # client or server Socks5.__init__(self, idlequeue, streamhost['host'], int(streamhost['port']), streamhost['initiator'], streamhost['target'], sid) - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state > 5: - # no activity for foo seconds - if self.file_props['stalled'] == False: - self.file_props['stalled'] = True - if 'received-len' not in self.file_props: - self.file_props['received-len'] = 0 - self.queue.process_result(-1, self) - if READ_TIMEOUT > 0: - self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT) - else: - # stop transfer, there is no error code for this - self.pollend() - else: - self.queue.reconnect_receiver(self, self.streamhost) - def connect(self): + def receive_file(self): """ - Create the socket and plug it to the idlequeue + Start receiving the file over verified connection """ - if self.ais is None: - return None - - for ai in self.ais: - try: - self._sock = socket.socket(*ai[:3]) - if not self.fingerprint is None: - self._sock = OpenSSL.SSL.Connection( - jingle_xtls.get_context('client'), self._sock) - # this will not block the GUI - self._sock.setblocking(False) - self._server = ai[4] - break - except socket.error, e: - if not isinstance(e, basestring) and e[0] == EINPROGRESS: - break - # for all other errors, we try other addresses - continue - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - self.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None - - def _is_connected(self): - if self.state < 5: - return False - return True - - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - try: - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - self.send_raw(self._get_auth_buff()) - elif self.state == 3: # send 'connect' request - self.send_raw(self._get_request_buff(self._get_sha1_auth())) - elif self.file_props['type'] != 'r': - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - result = self.write_next() - self.queue.process_result(result, self) - return - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: - log.info('caught SSL exception, ignored') + if self.file_props['started']: return - self.state += 1 - # unplug and plug for reading - self.idlequeue.plug_idle(self, False, True) - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - - def pollend(self): - if self.state >= 5: - # error during transfer - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) - else: - self.queue.reconnect_receiver(self, self.streamhost) - - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.connected: - try: - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - if self.state < 5: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - result = self.main(0) - self.queue.process_result(result, self) - elif self.state == 5: # wait for proxy reply - pass - elif self.file_props['type'] == 'r': - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - result = self.get_file_contents(0) - self.queue.process_result(result, self) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: - log.info('caught SSL exception, ignored') - return - else: - self.disconnect() - - def do_connect(self): - try: - self._sock.connect(self._server) - self._sock.setblocking(False) - self._send=self._sock.send - self._recv=self._sock.recv - except Exception, ee: - errnum = ee[0] - self.connect_timeout += 1 - if errnum == 111 or self.connect_timeout > 1000: - self.queue._connection_refused(self.streamhost, - self.file_props, self.queue_idx) - return None - # win32 needs this - elif errnum not in (10056, EISCONN) or self.state != 0: - return None - else: # socket is already connected - self._sock.setblocking(False) - self._send=self._sock.send - self._recv=self._sock.recv - self.buff = '' - self.connected = True - self.file_props['connected'] = True + self.file_props['error'] = 0 self.file_props['disconnect_cb'] = self.disconnect - self.state = 1 # connected + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + self.file_props['connected'] = True + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + self.state = 7 + # plug for reading + self.idlequeue.plug_idle(self, False, True) + return self.get_file_contents(0) # initial for nl byte - # stop all others connections to sender's streamhosts - self.queue._socket_connected(self.streamhost, self.file_props) - self.idlequeue.plug_idle(self, True, False) - return 1 # we are connected - - def main(self, timeout=0): + def start_transfer(self): """ - Begin negotiation. on success 'address' != 0 + Receive the file """ - result = 1 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return + return self.get_file_contents(0) - if self.state == 2: # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.disconnect() - elif self.state == 4: # get approve of our request - if buff is None: - return None - sub_buff = buff[:4] - if len(sub_buff) < 4: - return None - version, address_type = struct.unpack('!BxxB', buff[:4]) - addrlen = 0 - if address_type == 0x03: - addrlen = ord(buff[4]) - address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) - portlen = len(buff[addrlen + 5:]) - if portlen == 1: - port, = struct.unpack('!B', buff[addrlen + 5]) - elif portlen == 2: - port, = struct.unpack('!H', buff[addrlen + 5:]) - else: # Gaim bug :) - port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) - self.remaining_buff = buff[addrlen + 7:] - self.state = 5 # for senders: init file_props and send '\n' - if self.queue.on_success: - result = self.queue.send_success_reply(self.file_props, - self.streamhost) - if result == 0: - self.state = 8 - self.disconnect() - - # for senders: init file_props - if result == 1 and self.state == 5: - if self.file_props['type'] == 's': - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['stalled'] = False - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - # start sending file contents to socket - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(self, True, False) - else: - # receiving file contents from socket - self.idlequeue.plug_idle(self, False, True) - self.file_props['continue_cb'] = self.continue_paused_transfer - # we have set up the connection, next - retrieve file - self.state = 6 - if self.state < 5: - self.idlequeue.plug_idle(self, True, False) - self.state += 1 - return None def disconnect(self, cb=True): """ From 4284927434d18c5acfb865a6556e45641ee9f880 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sun, 14 Aug 2011 23:49:23 -0400 Subject: [PATCH 079/121] get priority from candidate stanza --- src/common/jingle_transport.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 8db288b48..46376045e 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -141,7 +141,8 @@ class JingleTransportSocks5(JingleTransport): 'host': candidate['host'], 'port': candidate['port'], 'cid': candidate['cid'], - 'type': typ + 'type': typ, + 'priority': candidate['priority'] } candidates.append(cand) From 60df47650607c211de05f7d920ab18003e19401f Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sun, 14 Aug 2011 23:59:39 -0400 Subject: [PATCH 080/121] Wait until candidate negociation ends; Socks5 connection problem fixed --- src/common/jingle_ft.py | 158 +++++++++++++++++++++++++++++++--------- src/common/socks5.py | 141 ++++++++++++++++++++++------------- 2 files changed, 217 insertions(+), 82 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 4e2452c5d..2ee4cf983 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -35,7 +35,17 @@ STATE_INITIALIZED = 1 STATE_ACCEPTED = 2 STATE_TRANSPORT_INFO = 3 STATE_PROXY_ACTIVATED = 4 -STATE_TRANSPORT_REPLACE = 5 +# We send the candidates and we are waiting for a reply +STATE_CAND_SENT_PENDING_REPLY = 5 +# We received the candidates and we are waiting to reply +STATE_CAND_RECEIVED_PENDING_REPLY = 6 +# We have sent and received the candidates +# This also includes any candidate-error received or sent +STATE_CAND_SENT_AND_RECEIVED = 7 +# We are transfering the file +STATE_TRANSFERING = 8 +STATE_TRANSPORT_REPLACE = 9 + class JingleFileTransfer(JingleContent): def __init__(self, session, transport=None, file_props=None, @@ -88,7 +98,7 @@ class JingleFileTransfer(JingleContent): self.session = session self.media = 'file' - + self.nominated_cand = {} def __on_session_initiate(self, stanza, content, error, action): gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, @@ -152,10 +162,20 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") - if not self.weinitiate: # proxy activated from initiator - return + #if not self.weinitiate: # proxy activated from initiator + # return if content.getTag('transport').getTag('candidate-error'): - self.session.transport_replace() + self.nominated_cand['peer-cand'] = False + if self.state == STATE_CAND_SENT_PENDING_REPLY: + #self.state = STATE_CAND_SENT_AND_RECEIVED + if not self.nominated_cand['our-cand'] and \ + not self.nominated_cand['peer-cand']: + if not self.weinitiate: + return + self.session.transport_replace() + else: + self.state = STATE_CAND_RECEIVED_PENDING_REPLY + return streamhost_cid = content.getTag('transport').getTag('candidate-used').\ getAttr('cid') @@ -167,29 +187,17 @@ class JingleFileTransfer(JingleContent): if streamhost_used == None: log.info("unknow streamhost") return - if streamhost_used['type'] == 'proxy': - self.file_props['streamhost-used'] = True - for proxy in self.file_props['proxyhosts']: - if proxy['host'] == streamhost_used['host'] and \ - proxy['port'] == streamhost_used['port'] and \ - proxy['jid'] == streamhost_used['jid']: - host_used = proxy - break - if 'streamhosts' not in self.file_props: - self.file_props['streamhosts'] = [] - self.file_props['streamhosts'].append(streamhost_used) - self.file_props['is_a_proxy'] = True - receiver = Socks5Receiver(gajim.idlequeue, streamhost_used, - self.file_props['sid'], self.file_props) - gajim.socks5queue.add_receiver(self.session.connection.name, - receiver) - streamhost_used['idx'] = receiver.queue_idx - gajim.socks5queue.on_success[self.file_props['sid']] = \ - self.transport._on_proxy_auth_ok + # We save the candidate nominated by peer + self.nominated_cand['peer-cand'] = streamhost_used + if self.state == STATE_CAND_SENT_PENDING_REPLY: + response = stanza.buildReply('result') + self.session.connection.connection.send(response) + self.start_transfer(streamhost_used) + raise xmpp.NodeProcessed else: - jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.send_file(self.file_props, - self.session.connection.name) + self.state = STATE_CAND_RECEIVED_PENDING_REPLY + + def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") @@ -221,6 +229,16 @@ class JingleFileTransfer(JingleContent): elif self.weinitiate and self.state == STATE_INITIALIZED: # proxy activated self.state = STATE_PROXY_ACTIVATED + elif self.state == STATE_CAND_SENT_AND_RECEIVED: + + if not self.nominated_cand['our-cand'] and \ + not self.nominated_cand['peer-cand']: + if not self.weinitiate: + return + self.session.transport_replace() + return + # initiate transfer + self.start_transfer(None) def send_candidate_used(self, streamhost): """ @@ -229,7 +247,13 @@ class JingleFileTransfer(JingleContent): log.info('send_candidate_used') if streamhost is None: return - + + self.nominated_cand['our-cand'] = streamhost + if self.state == STATE_CAND_RECEIVED_PENDING_REPLY: + self.state = STATE_CAND_SENT_AND_RECEIVED + else: + self.state = STATE_CAND_SENT_PENDING_REPLY + content = xmpp.Node('content') content.setAttr('creator', 'initiator') content.setAttr('name', self.name) @@ -246,9 +270,16 @@ class JingleFileTransfer(JingleContent): self.session.send_transport_info(content) + def _on_connect_error(self, to, _id, sid, code=404): - if code == 404 and self.file_props['sid'] == sid: - self.send_error_candidate() + self.nominated_cand['our-cand'] = False + self.send_error_candidate() + + if self.state == STATE_CAND_RECEIVED_PENDING_REPLY: + self.state = STATE_CAND_SENT_AND_RECEIVED + else: + self.state = STATE_CAND_SENT_PENDING_REPLY + log.info('connect error, sid=' + sid) @@ -297,20 +328,81 @@ class JingleFileTransfer(JingleContent): fingerprint = None if self.use_security: fingerprint = 'server' - return + if self.weinitiate: listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props['sid'], + self._store_socks5_sid, self.file_props, fingerprint=fingerprint, type='sender') else: listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props['sid'], + self._store_socks5_sid, self.file_props, fingerprint=fingerprint, type='receiver') if not listener: # send error message, notify the user return + def isOurCandUsed(self): + if self.nominated_cand['peer-cand'] == False: + return True + if self.nominated_cand['our-cand'] == False: + return False + + peer_pr = int(self.nominated_cand['peer-cand']['priority']) + our_pr = int(self.nominated_cand['our-cand']['priority']) + + if peer_pr != our_pr: + if peer_pr > our_pr: + # Choose peer host + return False + else: + # Choose our host + return True + else: + if self.weinitiate: + # Choose our host + return True + else: + # Choose peer host + return False + + + + def start_transfer(self, streamhost_used): + + self.state = STATE_TRANSFERING + + if self.isOurCandUsed(): + print 'our' + else: + print 'peer' + + + # FIXME if streamhost_used is none where do we get the proxy host + if streamhost_used and streamhost_used['type'] == 'proxy': + self.file_props['streamhost-used'] = True + for proxy in self.file_props['proxyhosts']: + if proxy['host'] == streamhost_used['host'] and \ + proxy['port'] == streamhost_used['port'] and \ + proxy['jid'] == streamhost_used['jid']: + host_used = proxy + break + if 'streamhosts' not in self.file_props: + self.file_props['streamhosts'] = [] + self.file_props['streamhosts'].append(streamhost_used) + self.file_props['is_a_proxy'] = True + receiver = Socks5Receiver(gajim.idlequeue, streamhost_used, + self.file_props['sid'], self.file_props) + gajim.socks5queue.add_receiver(self.session.connection.name, + receiver) + streamhost_used['idx'] = receiver.queue_idx + gajim.socks5queue.on_success[self.file_props['sid']] = \ + self.transport._on_proxy_auth_ok + else: + jid = gajim.get_jid_without_resource(self.session.ourjid) + gajim.socks5queue.send_file(self.file_props, + self.session.connection.name) + def get_content(desc): return JingleFileTransfer diff --git a/src/common/socks5.py b/src/common/socks5.py index 8a3b8b5f5..37e3034fb 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -84,16 +84,18 @@ class SocksQueue: self.on_success = {} # {id: cb} self.on_failure = {} # {id: cb} - def start_listener(self, port, sha_str, sha_handler, sid, fingerprint=None, + def start_listener(self, port, sha_str, sha_handler, fp, fingerprint=None, type='sender'): """ Start waiting for incomming connections on (host, port) and do a socks5 authentication using sid for generated SHA """ + sid = fp['sid'] self.type = type # It says whether we are sending or receiving self.sha_handlers[sha_str] = (sha_handler, sid) if self.listener is None: - self.listener = Socks5Listener(self.idlequeue, port, fingerprint=fingerprint) + self.listener = Socks5Listener(self.idlequeue, port, fp, + fingerprint=fingerprint) self.listener.queue = self self.listener.bind() if self.listener.started is False: @@ -130,7 +132,7 @@ class SocksQueue: self.on_failure[sid] = on_failure file_props = self.files_props[account][sid] file_props['failure_cb'] = on_failure - + # add streamhosts to the queue for streamhost in file_props['streamhosts']: if 'type' in streamhost and streamhost['type'] == 'proxy': @@ -149,8 +151,8 @@ class SocksQueue: file_props['sha_str'], self, mode='client' , _sock=None, host=str(streamhost['host']), port=int(streamhost['port']), fingerprint=fp, - connected=False) - socks5obj.file_props = file_props + connected=False, file_props=file_props) + #socks5obj.file_props = file_props socks5obj.streamhost = streamhost self.add_sockobj(account, socks5obj, type='sender') @@ -234,7 +236,7 @@ class SocksQueue: return # failure_cb exists - this means that it has never been called if 'failure_cb' in file_props and file_props['failure_cb']: - file_props['failure_cb'](streamhost['initiator'], streamhost['idx'], + file_props['failure_cb'](streamhost['initiator'], None, file_props['sid'], code = 404) del(file_props['failure_cb']) @@ -258,14 +260,6 @@ class SocksQueue: return 1 return None - def get_file_from_sender(self, file_props, account): - if file_props is None: - return - if 'hash' in file_props and file_props['hash'] in self.senders: - sender = self.senders[file_props['hash']] - sender.account = account - result = self.get_file_contents(0) - self.process_result(result, sender) def result_sha(self, sha_str, idx): if sha_str in self.sha_handlers: @@ -313,7 +307,9 @@ class SocksQueue: file_props['last-time'] = self.idlequeue.current_time() file_props['received-len'] = 0 sender.file_props = file_props + else: + log.info("socks5: NOT sending file") def add_file_props(self, account, file_props): @@ -351,12 +347,12 @@ class SocksQueue: return fl_props[sid] return None - def on_connection_accepted(self, sock): + def on_connection_accepted(self, sock, listener): sock_hash = sock.__hash__() if self.type == 'sender' and (sock_hash not in self.senders): self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, - sock[0], 'server', sock[1][0], sock[1][1], - fingerprint='server') + 'server', sock[0], sock[1][0], sock[1][1], + fingerprint='server', file_props=listener.file_props) # Start waiting for data self.idlequeue.plug_idle(self.senders[sock_hash], False, True) self.connected += 1 @@ -371,6 +367,7 @@ class SocksQueue: streamhost=sh,sid=None, file_props=None, mode='server',fingerprint='server') self.readers[sock_hash].set_sock(sock[0]) + self.readers[sock_hash].queue = self self.connected += 1 def process_result(self, result, actor): @@ -452,6 +449,7 @@ class Socks5: self.remaining_buff = '' self.file = None self.connected = False + self.type = '' def start_transfer(self): """ @@ -474,9 +472,10 @@ class Socks5: for ai in self.ais: try: self._sock = socket.socket(*ai[:3]) + ''' if not self.fingerprint is None: self._sock = OpenSSL.SSL.Connection( - jingle_xtls.get_context('client'), self._sock) + jingle_xtls.get_context('client'), self._sock)''' # this will not block the GUI self._sock.setblocking(False) self._server = ai[4] @@ -495,6 +494,7 @@ class Socks5: def do_connect(self): try: + #self._sock.setblocking(True) self._sock.connect(self._server) self._sock.setblocking(False) self._send=self._sock.send @@ -587,6 +587,7 @@ class Socks5: if self.queue.on_success: result = self.queue.send_success_reply(self.file_props, self.streamhost) + if result == 0: self.state = 8 self.disconnect() @@ -605,11 +606,13 @@ class Socks5: self.file_props['received-len'] = 0 self.pauses = 0 # start sending file contents to socket - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(self, True, False) + #self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + #self.idlequeue.plug_idle(self, True, False) + self.idlequeue.plug_idle(self, False, False) else: # receiving file contents from socket self.idlequeue.plug_idle(self, False, True) + self.file_props['continue_cb'] = self.continue_paused_transfer # we have set up the connection, next - retrieve file self.state = 6 @@ -699,9 +702,14 @@ class Socks5: self.disconnect() elif self.state == 5: - if self.file_props is not None and self.file_props['type'] == 'r': - result = self.start_transfer() # receive - self.queue.process_result(result, self) + if self.type == 'sender': + # We wait for the end of the negotiation to + # send the file + self.state = 7 + self.idlequeue.plug_idle(self, False, False) + else: + result = self.start_transfer() # receive + self.queue.process_result(result, self) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, OpenSSL.SSL.WantX509LookupError), e: log.info('caught SSL exception, ignored') @@ -1115,24 +1123,28 @@ class Socks5Sender(Socks5, IdleObject): """ def __init__(self, idlequeue, sock_hash, parent, mode,_sock, host=None, - port=None, fingerprint = None, connected=True): + port=None, fingerprint = None, connected=True, + file_props={}): + self.fingerprint = fingerprint self.queue_idx = sock_hash self.queue = parent self.mode = mode # client or server - self.file_props = {} - self.file_props['paused'] = False + self.file_props = file_props + + + Socks5.__init__(self, idlequeue, host, port, None, None, None) self._sock = _sock if _sock is not None: - if self.fingerprint is not None: + '''if self.fingerprint is not None: self._sock = OpenSSL.SSL.Connection( - jingle_xtls.get_context('server'), self._sock) + jingle_xtls.get_context('server'), _sock) else: self._sock.setblocking(False) - + ''' self.fd = _sock.fileno() self._recv = _sock.recv self._send = _sock.send @@ -1140,21 +1152,6 @@ class Socks5Sender(Socks5, IdleObject): self.state = 1 # waiting for first bytes self.connect_timeout = 0 - - def start_transfer(self): - """ - Send the file - """ - return self.write_next() - - - - def send_file(self): - """ - Start sending the file over verified connection - """ - if self.file_props['started']: - return self.file_props['error'] = 0 self.file_props['disconnect_cb'] = self.disconnect self.file_props['started'] = True @@ -1166,6 +1163,39 @@ class Socks5Sender(Socks5, IdleObject): self.file_props['elapsed-time'] = 0 self.file_props['last-time'] = self.idlequeue.current_time() self.file_props['received-len'] = 0 + self.type = 'sender' + + def start_transfer(self): + """ + Send the file + """ + return self.write_next() + + + def set_connection_sock(self, _sock): + + self._sock = _sock + + if self.fingerprint is not None: + self._sock = OpenSSL.SSL.Connection( + jingle_xtls.get_context('client'), self._sock) + else: + self._sock.setblocking(False) + + self.fd = _sock.fileno() + self._recv = _sock.recv + self._send = _sock.send + self.connected = True + self.state = 1 # waiting for first bytes + self.file_props = None + # start waiting for data + self.idlequeue.plug_idle(self, False, True) + + def send_file(self): + """ + Start sending the file over verified connection + """ + self.pauses = 0 self.state = 7 # plug for writing @@ -1185,7 +1215,7 @@ class Socks5Sender(Socks5, IdleObject): self.queue.remove_sender(self.queue_idx, False) class Socks5Listener(IdleObject): - def __init__(self, idlequeue, port, fingerprint=None): + def __init__(self, idlequeue, port, fp, fingerprint=None): """ Handle all incomming connections on (0.0.0.0, port) @@ -1205,15 +1235,16 @@ class Socks5Listener(IdleObject): self._sock = None self.fd = -1 self.fingerprint = fingerprint - + self.file_props = fp + def bind(self): for ai in self.ais: # try the different possibilities (ipv6, ipv4, etc.) try: self._serv = socket.socket(*ai[:3]) - if self.fingerprint is not None: + '''if self.fingerprint is not None: self._serv = OpenSSL.SSL.Connection( - jingle_xtls.get_context('server'), self._serv) + jingle_xtls.get_context('server'), self._serv)''' except socket.error, e: if e.args[0] == EAFNOSUPPORT: self.ai = None @@ -1232,6 +1263,7 @@ class Socks5Listener(IdleObject): # will fail when port as busy, or we don't have rights to bind try: self._serv.bind(ai[4]) + f = ai[4] self.ai = ai break except Exception: @@ -1257,7 +1289,7 @@ class Socks5Listener(IdleObject): Accept a new incomming connection and notify queue """ sock = self.accept_conn() - self.queue.on_connection_accepted(sock) + self.queue.on_connection_accepted(sock, self) def disconnect(self): """ @@ -1314,6 +1346,7 @@ class Socks5Receiver(Socks5, IdleObject): """ Start receiving the file over verified connection """ + print "receiving file" if self.file_props['started']: return self.file_props['error'] = 0 @@ -1339,6 +1372,16 @@ class Socks5Receiver(Socks5, IdleObject): """ return self.get_file_contents(0) + def set_sock(self, _sock): + self._sock = _sock + self._sock.setblocking(False) + self.fd = _sock.fileno() + self._recv = _sock.recv + self._send = _sock.send + self.connected = True + self.state = 1 # waiting for first bytes + # start waiting for data + self.idlequeue.plug_idle(self, False, True) def disconnect(self, cb=True): """ From f4cc439a7e4700fe9f646ad11ae2e02ac0ae300b Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 15 Aug 2011 12:16:17 -0400 Subject: [PATCH 081/121] add test for connection --- test/unit/test_socks5.py | 55 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/test/unit/test_socks5.py b/test/unit/test_socks5.py index ced87045e..3d5d92943 100644 --- a/test/unit/test_socks5.py +++ b/test/unit/test_socks5.py @@ -8,8 +8,10 @@ lib.setup_env() from mock import Mock import sys +import socket from common.socks5 import * +from common import jingle_xtls class fake_sock(Mock): def __init__(self, sockobj): @@ -20,7 +22,7 @@ class fake_sock(Mock): def setup_stream(self): sha1 = self.sockobj._get_sha1_auth() - + self.incoming = [] self.incoming.append(self.sockobj._get_auth_response()) self.incoming.append( @@ -71,8 +73,11 @@ class TestSocks5(unittest.TestCase): 'port': 1, 'initiator' : None, 'target' : None} + queue = Mock() + queue.file_props = {} #self.sockobj = Socks5Receiver(fake_idlequeue(), streamhost, None) - self.sockobj = Socks5Sender(fake_idlequeue(), None, None, Mock() ) + self.sockobj = Socks5Sender(fake_idlequeue(), None, 'server', Mock() , + None, None, True, file_props={}) sock = fake_sock(self.sockobj) self.sockobj._sock = sock self.sockobj._recv = sock._recv @@ -87,6 +92,7 @@ class TestSocks5(unittest.TestCase): # Something that the sender needs self.sockobj.file_props = {} self.sockobj.file_props['type'] = 'r' + self.sockobj.file_props['paused'] = '' self.sockobj.queue = Mock() self.sockobj.queue.process_result = self._pollend @@ -100,6 +106,49 @@ class TestSocks5(unittest.TestCase): assert(sock.incoming == []) assert(sock.outgoing == []) + def test_connection_server(self): + return + mocksock = self.sockobj._sock + mocksock.setup_stream() + #self.sockobj._sock.switch_stream() + s = socket.socket(2, 1, 6) + server = ('127.0.0.1', 28000) + + s.connect(server) + + s.send(mocksock.outgoing.pop(0)) + self.assertEquals(s.recv(64), mocksock.incoming.pop(0)) + + s.send(mocksock.outgoing.pop(0)) + self.assertEquals(s.recv(64), mocksock.incoming.pop(0)) + + def test_connection_client(self): + + + mocksock = self.sockobj._sock + mocksock.setup_stream() + mocksock.switch_stream() + s = socket.socket(10, 1, 6) + + + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + netadd = ('::', 28000, 0, 0) + s.bind(netadd) + s.listen(socket.SOMAXCONN) + (s, address) = s.accept() + + + self.assertEquals(s.recv(64), mocksock.incoming.pop(0)) + s.send(mocksock.outgoing.pop(0)) + + buff = s.recv(64) + inco = mocksock.incoming.pop(0) + #self.assertEquals(s.recv(64), mocksock.incoming.pop(0)) + s.send(mocksock.outgoing.pop(0)) + def test_client_negoc(self): return self.sockobj._sock.setup_stream() @@ -111,7 +160,7 @@ class TestSocks5(unittest.TestCase): self._check_inout() def test_server_negoc(self): - + return self.sockobj._sock.setup_stream() self.sockobj._sock.switch_stream() try: From 609ee7979159bea631fde7af02dd272e01591bae Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sun, 21 Aug 2011 19:02:58 -0400 Subject: [PATCH 082/121] connect to candidate with highest priority --- src/common/jingle_ft.py | 30 +++--- src/common/socks5.py | 210 ++++++++++++++++++++++++---------------- 2 files changed, 137 insertions(+), 103 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 2ee4cf983..e52702536 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -342,6 +342,10 @@ class JingleFileTransfer(JingleContent): # send error message, notify the user return def isOurCandUsed(self): + ''' + If this method returns true then the candidate we nominated will be + used, if false, the candidate nominated by peer will be used + ''' if self.nominated_cand['peer-cand'] == False: return True @@ -352,32 +356,24 @@ class JingleFileTransfer(JingleContent): our_pr = int(self.nominated_cand['our-cand']['priority']) if peer_pr != our_pr: - if peer_pr > our_pr: - # Choose peer host - return False - else: - # Choose our host - return True + return peer_pr > our_pr else: - if self.weinitiate: - # Choose our host - return True - else: - # Choose peer host - return False - + return self.weinitiate def start_transfer(self, streamhost_used): self.state = STATE_TRANSFERING + # It tells wether we start the transfer as client or server + type = None + if self.isOurCandUsed(): - print 'our' + type = 'client' else: - print 'peer' - + type = 'server' + print type # FIXME if streamhost_used is none where do we get the proxy host if streamhost_used and streamhost_used['type'] == 'proxy': self.file_props['streamhost-used'] = True @@ -401,7 +397,7 @@ class JingleFileTransfer(JingleContent): else: jid = gajim.get_jid_without_resource(self.session.ourjid) gajim.socks5queue.send_file(self.file_props, - self.session.connection.name) + self.session.connection.name, type) def get_content(desc): return JingleFileTransfer diff --git a/src/common/socks5.py b/src/common/socks5.py index 37e3034fb..2bf246cf5 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -152,11 +152,9 @@ class SocksQueue: _sock=None, host=str(streamhost['host']), port=int(streamhost['port']), fingerprint=fp, connected=False, file_props=file_props) - #socks5obj.file_props = file_props socks5obj.streamhost = streamhost self.add_sockobj(account, socks5obj, type='sender') - socks5obj.file_props = file_props streamhost['idx'] = socks5obj.queue_idx def _socket_connected(self, streamhost, file_props): @@ -242,12 +240,13 @@ class SocksQueue: def add_sockobj(self, account, sockobj, type='receiver'): """ - Add new file a sockobj type receiver or sendder + Add new file a sockobj type receiver or sender, and use it to connect + to server """ if type == 'receiver': - self.readers[self.idx] = sockobj + self._add(sockobj, self.readers, sockobj.file_props, self.idx) else: - self.senders[self.idx] = sockobj + self._add(sockobj, self.senders, sockobj.file_props, self.idx) sockobj.queue_idx = self.idx sockobj.queue = self sockobj.account = account @@ -260,57 +259,66 @@ class SocksQueue: return 1 return None - + def _add(self, sockobj, sockobjects, fp, hash): + ''' + Adds the sockobj to the current list of sockobjects + ''' + keys = (fp['sid'], fp['name'], hash) + sockobjects[keys] = sockobj + + def result_sha(self, sha_str, idx): if sha_str in self.sha_handlers: props = self.sha_handlers[sha_str] props[0](props[1], idx) def activate_proxy(self, idx): - if idx not in self.readers: + if not self.isHashInSockObjs(self.readers, idx): return - reader = self.readers[idx] - if reader.file_props['type'] != 's': - return - if reader.state != 5: - return - reader.state = 6 - if reader.connected: - reader.file_props['error'] = 0 - reader.file_props['disconnect_cb'] = reader.disconnect - reader.file_props['started'] = True - reader.file_props['completed'] = False - reader.file_props['paused'] = False - reader.file_props['stalled'] = False - reader.file_props['elapsed-time'] = 0 - reader.file_props['last-time'] = self.idlequeue.current_time() - reader.file_props['received-len'] = 0 - reader.pauses = 0 - # start sending file to proxy - self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(reader, True, False) - result = reader.write_next() - self.process_result(result, reader) + for key in self.readers.keys(): + if idx in key: + reader = self.readers[key] + if reader.file_props['type'] != 's': + return + if reader.state != 5: + return + reader.state = 6 + if reader.connected: + reader.file_props['error'] = 0 + reader.file_props['disconnect_cb'] = reader.disconnect + reader.file_props['started'] = True + reader.file_props['completed'] = False + reader.file_props['paused'] = False + reader.file_props['stalled'] = False + reader.file_props['elapsed-time'] = 0 + reader.file_props['last-time'] = self.idlequeue.current_time() + reader.file_props['received-len'] = 0 + reader.pauses = 0 + # start sending file to proxy + self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT) + self.idlequeue.plug_idle(reader, True, False) + result = reader.write_next() + self.process_result(result, reader) - def send_file(self, file_props, account): - if 'hash' in file_props and file_props['hash'] in self.senders: - log.info("socks5: sending file") - sender = self.senders[file_props['hash']] - file_props['streamhost-used'] = True - sender.account = account - if file_props['type'] == 's': - sender.file_props = file_props - result = sender.send_file() - self.process_result(result, sender) - else: - file_props['elapsed-time'] = 0 - file_props['last-time'] = self.idlequeue.current_time() - file_props['received-len'] = 0 - sender.file_props = file_props + def send_file(self, file_props, account, type): + for key in self.senders.keys(): + if file_props['name'] in key and file_props['sid'] in key \ + and self.senders[key].mode == type: + + log.info("socks5: sending file") + sender = self.senders[key] + file_props['streamhost-used'] = True + sender.account = account + if file_props['type'] == 's': + sender.file_props = file_props + result = sender.send_file() + self.process_result(result, sender) + else: + file_props['elapsed-time'] = 0 + file_props['last-time'] = self.idlequeue.current_time() + file_props['received-len'] = 0 + sender.file_props = file_props - else: - - log.info("socks5: NOT sending file") def add_file_props(self, account, file_props): """ @@ -346,30 +354,49 @@ class SocksQueue: if sid in fl_props: return fl_props[sid] return None + + def isHashInSockObjs(self, sockobjs, hash): + ''' + It tells wether there is a particular hash in sockobjs or not + ''' + for key in sockobjs: + if hash in key: + return True + return False def on_connection_accepted(self, sock, listener): sock_hash = sock.__hash__() - if self.type == 'sender' and (sock_hash not in self.senders): - self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, - 'server', sock[0], sock[1][0], sock[1][1], - fingerprint='server', file_props=listener.file_props) + if self.type == 'sender' and \ + not self.isHashInSockObjs(self.senders, sock_hash): + + sockobj = Socks5Sender(self.idlequeue, sock_hash, self, + 'server', sock[0], sock[1][0], sock[1][1], + fingerprint='server', file_props=listener.file_props) + self._add(sockobj, self.senders, listener.file_props, sock_hash) # Start waiting for data - self.idlequeue.plug_idle(self.senders[sock_hash], False, True) + self.idlequeue.plug_idle(sockobj, False, True) self.connected += 1 - if self.type == 'receiver' and (sock_hash not in self.readers): + if self.type == 'receiver' and \ + not self.isHashInSockObjs(self.readers, sock_hash): sh = {} sh['host'] = sock[1][0] sh['port'] = sock[1][1] sh['initiator'] = None sh['target'] = None - self.readers[sock_hash] = Socks5Receiver(idlequeue=self.idlequeue, - streamhost=sh,sid=None, file_props=None, + + sockobj = Socks5Receiver(idlequeue=self.idlequeue, + streamhost=sh,sid=None, + file_props=listener.file_props, mode='server',fingerprint='server') - self.readers[sock_hash].set_sock(sock[0]) - self.readers[sock_hash].queue = self + + self._add(sockobj, self.readers, listener.file_props, sock_hash) + + sockobj.set_sock(sock[0]) + sockobj.queue = self self.connected += 1 + def process_result(self, result, actor): """ Take appropriate actions upon the result: @@ -393,16 +420,17 @@ class SocksQueue: connections with 1 """ if idx != -1: - if idx in self.readers: - reader = self.readers[idx] - self.idlequeue.unplug_idle(reader.fd) - self.idlequeue.remove_timeout(reader.fd) - if do_disconnect: - reader.disconnect() - else: - if reader.streamhost is not None: - reader.streamhost['state'] = -1 - del(self.readers[idx]) + for key in self.readers.keys(): + if idx in key: + reader = self.readers[key] + self.idlequeue.unplug_idle(reader.fd) + self.idlequeue.remove_timeout(reader.fd) + if do_disconnect: + reader.disconnect() + else: + if reader.streamhost is not None: + reader.streamhost['state'] = -1 + del(self.readers[key]) def remove_sender(self, idx, do_disconnect=True): """ @@ -410,17 +438,18 @@ class SocksQueue: connections with 1 """ if idx != -1: - if idx in self.senders: - sender = self.senders[idx] - if do_disconnect: - self.senders[idx].disconnect() - return - else: - self.idlequeue.unplug_idle(sender.fd) - self.idlequeue.remove_timeout(sender.fd) - del(self.senders[idx]) - if self.connected > 0: - self.connected -= 1 + for key in self.senders.keys(): + if idx in key: + sender = self.senders[key] + if do_disconnect: + sender.disconnect() + return + else: + self.idlequeue.unplug_idle(sender.fd) + self.idlequeue.remove_timeout(sender.fd) + del(self.senders[key]) + if self.connected > 0: + self.connected -= 1 if len(self.senders) == 0 and self.listener is not None: self.listener.disconnect() self.listener = None @@ -702,14 +731,24 @@ class Socks5: self.disconnect() elif self.state == 5: + self.state = 7 if self.type == 'sender': # We wait for the end of the negotiation to # send the file - self.state = 7 self.idlequeue.plug_idle(self, False, False) else: - result = self.start_transfer() # receive - self.queue.process_result(result, self) + # We plug for reading + self.idlequeue.plug_idle(self, False, True) + return + + elif self.state == 7: + if self.file_props['paused']: + self.file_props['continue_cb'] = self.continue_paused_transfer + self.idlequeue.plug_idle(self, False, False) + return + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.start_transfer() # send + self.queue.process_result(result, self) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, OpenSSL.SSL.WantX509LookupError), e: log.info('caught SSL exception, ignored') @@ -770,8 +809,8 @@ class Socks5: if self.file_props['stalled'] == False: self.file_props['stalled'] = True self.queue.process_result(-1, self) - #if 'received-len' not in self.file_props: - # self.file_props['received-len'] = 0 + if 'received-len' not in self.file_props: + self.file_props['received-len'] = 0 if SEND_TIMEOUT > 0: self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) else: @@ -1322,13 +1361,11 @@ class Socks5Receiver(Socks5, IdleObject): self.queue_idx = -1 self.streamhost = streamhost self.queue = None - self.file_props = file_props self.fingerprint = fingerprint self.connect_timeout = 0 self.connected = False self.pauses = 0 - if not self.file_props: - self.file_props = {} + self.file_props = file_props self.file_props['disconnect_cb'] = self.disconnect self.file_props['error'] = 0 self.file_props['started'] = True @@ -1336,6 +1373,7 @@ class Socks5Receiver(Socks5, IdleObject): self.file_props['paused'] = False self.file_props['continue_cb'] = self.continue_paused_transfer self.file_props['stalled'] = False + self.file_props['received-len'] = 0 self.mode = mode # client or server Socks5.__init__(self, idlequeue, streamhost['host'], int(streamhost['port']), streamhost['initiator'], streamhost['target'], From 19bc86d9a3bc1d4022ac92098ad26828aed979eb Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 22 Aug 2011 23:57:29 -0400 Subject: [PATCH 083/121] fix candidate priority --- src/common/jingle_ft.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index e52702536..4201307b9 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -356,7 +356,7 @@ class JingleFileTransfer(JingleContent): our_pr = int(self.nominated_cand['our-cand']['priority']) if peer_pr != our_pr: - return peer_pr > our_pr + return our_pr > peer_pr else: return self.weinitiate @@ -373,7 +373,6 @@ class JingleFileTransfer(JingleContent): else: type = 'server' - print type # FIXME if streamhost_used is none where do we get the proxy host if streamhost_used and streamhost_used['type'] == 'proxy': self.file_props['streamhost-used'] = True From a3d772e505605c10de87c60f24cdfb40f2081577 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 24 Aug 2011 10:36:00 +0200 Subject: [PATCH 084/121] coding standards --- src/common/socks5.py | 218 +++++++++++++++++++++---------------------- 1 file changed, 107 insertions(+), 111 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index 2bf246cf5..5c7e436ae 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -67,7 +67,7 @@ class SocksQueue: """ def __init__(self, idlequeue, complete_transfer_cb=None, - progress_transfer_cb=None, error_cb=None): + progress_transfer_cb=None, error_cb=None): self.connected = 0 self.readers = {} self.files_props = {} @@ -85,7 +85,7 @@ class SocksQueue: self.on_failure = {} # {id: cb} def start_listener(self, port, sha_str, sha_handler, fp, fingerprint=None, - type='sender'): + type='sender'): """ Start waiting for incomming connections on (host, port) and do a socks5 authentication using sid for generated SHA @@ -95,15 +95,15 @@ class SocksQueue: self.sha_handlers[sha_str] = (sha_handler, sid) if self.listener is None: self.listener = Socks5Listener(self.idlequeue, port, fp, - fingerprint=fingerprint) + fingerprint=fingerprint) self.listener.queue = self self.listener.bind() if self.listener.started is False: self.listener = None # We cannot bind port, call error callback and fail self.error_cb(_('Unable to bind to port %s.') % port, - _('Maybe you have another running instance of Gajim. File ' - 'Transfer will be cancelled.')) + _('Maybe you have another running instance of Gajim. File ' + 'Transfer will be cancelled.')) return None self.connected += 1 return self.listener @@ -132,7 +132,7 @@ class SocksQueue: self.on_failure[sid] = on_failure file_props = self.files_props[account][sid] file_props['failure_cb'] = on_failure - + # add streamhosts to the queue for streamhost in file_props['streamhosts']: if 'type' in streamhost and streamhost['type'] == 'proxy': @@ -142,19 +142,17 @@ class SocksQueue: if receiving: self.type = 'receiver' socks5obj = Socks5Receiver(self.idlequeue, streamhost, sid, - 'client', file_props, - fingerprint=fp) + 'client', file_props, fingerprint=fp) self.add_sockobj(account, socks5obj) - else: + else: self.type = 'sender' - socks5obj = Socks5Sender(self.idlequeue, - file_props['sha_str'], self, mode='client' , - _sock=None, host=str(streamhost['host']), - port=int(streamhost['port']), fingerprint=fp, - connected=False, file_props=file_props) + socks5obj = Socks5Sender(self.idlequeue, file_props['sha_str'], + self, mode='client' , _sock=None, + host=str(streamhost['host']), port=int(streamhost['port']), + fingerprint=fp, connected=False, file_props=file_props) socks5obj.streamhost = streamhost self.add_sockobj(account, socks5obj, type='sender') - + streamhost['idx'] = socks5obj.queue_idx def _socket_connected(self, streamhost, file_props): @@ -206,7 +204,7 @@ class SocksQueue: # FIXME: make the sender reconnect also print 'reconnecting using socks receiver' client = Socks5Receiver(self.idlequeue, host, host['sid'], - 'client',file_props) + 'client',file_props) self.add_sockobj(client.account, client) host['idx'] = client.queue_idx # we still have chances to connect @@ -235,7 +233,7 @@ class SocksQueue: # failure_cb exists - this means that it has never been called if 'failure_cb' in file_props and file_props['failure_cb']: file_props['failure_cb'](streamhost['initiator'], None, - file_props['sid'], code = 404) + file_props['sid'], code = 404) del(file_props['failure_cb']) def add_sockobj(self, account, sockobj, type='receiver'): @@ -264,9 +262,8 @@ class SocksQueue: Adds the sockobj to the current list of sockobjects ''' keys = (fp['sid'], fp['name'], hash) - sockobjects[keys] = sockobj - - + sockobjects[keys] = sockobj + def result_sha(self, sha_str, idx): if sha_str in self.sha_handlers: props = self.sha_handlers[sha_str] @@ -303,8 +300,8 @@ class SocksQueue: def send_file(self, file_props, account, type): for key in self.senders.keys(): if file_props['name'] in key and file_props['sid'] in key \ - and self.senders[key].mode == type: - + and self.senders[key].mode == type: + log.info("socks5: sending file") sender = self.senders[key] file_props['streamhost-used'] = True @@ -318,7 +315,6 @@ class SocksQueue: file_props['last-time'] = self.idlequeue.current_time() file_props['received-len'] = 0 sender.file_props = file_props - def add_file_props(self, account, file_props): """ @@ -354,7 +350,7 @@ class SocksQueue: if sid in fl_props: return fl_props[sid] return None - + def isHashInSockObjs(self, sockobjs, hash): ''' It tells wether there is a particular hash in sockobjs or not @@ -367,16 +363,16 @@ class SocksQueue: def on_connection_accepted(self, sock, listener): sock_hash = sock.__hash__() if self.type == 'sender' and \ - not self.isHashInSockObjs(self.senders, sock_hash): - - sockobj = Socks5Sender(self.idlequeue, sock_hash, self, - 'server', sock[0], sock[1][0], sock[1][1], - fingerprint='server', file_props=listener.file_props) + not self.isHashInSockObjs(self.senders, sock_hash): + + sockobj = Socks5Sender(self.idlequeue, sock_hash, self, 'server', + sock[0], sock[1][0], sock[1][1], fingerprint='server', + file_props=listener.file_props) self._add(sockobj, self.senders, listener.file_props, sock_hash) # Start waiting for data self.idlequeue.plug_idle(sockobj, False, True) self.connected += 1 - + if self.type == 'receiver' and \ not self.isHashInSockObjs(self.readers, sock_hash): sh = {} @@ -384,19 +380,18 @@ class SocksQueue: sh['port'] = sock[1][1] sh['initiator'] = None sh['target'] = None - - sockobj = Socks5Receiver(idlequeue=self.idlequeue, - streamhost=sh,sid=None, - file_props=listener.file_props, - mode='server',fingerprint='server') - + + sockobj = Socks5Receiver(idlequeue=self.idlequeue, + streamhost=sh,sid=None, file_props=listener.file_props, + mode='server',fingerprint='server') + self._add(sockobj, self.readers, listener.file_props, sock_hash) - + sockobj.set_sock(sock[0]) sockobj.queue = self self.connected += 1 - - + + def process_result(self, result, actor): """ Take appropriate actions upon the result: @@ -461,7 +456,7 @@ class Socks5: try: self.host = host self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM) + socket.SOCK_STREAM) except socket.gaierror: self.ais = None self.idlequeue = idlequeue @@ -485,12 +480,12 @@ class Socks5: Must be implemented by subclass. """ pass - + def _is_connected(self): if self.state < 5: return False return True - + def connect(self): """ Create the socket and plug it to the idlequeue @@ -532,8 +527,8 @@ class Socks5: errnum = ee[0] self.connect_timeout += 1 if errnum == 111 or self.connect_timeout > 1000: - self.queue._connection_refused(self.streamhost, - self.file_props, self.queue_idx) + self.queue._connection_refused(self.streamhost, self.file_props, + self.queue_idx) self.connected = False return None # win32 needs this @@ -554,7 +549,7 @@ class Socks5: self.queue._socket_connected(self.streamhost, self.file_props) self.idlequeue.plug_idle(self, True, False) return 1 # we are connected - + def svr_main(self): """ Initial requests for verifying the connection @@ -575,7 +570,7 @@ class Socks5: # unplug & plug for writing self.idlequeue.plug_idle(self, True, False) return None - + def clnt_main(self, timeout=0): """ Begin negotiation. on success 'address' != 0 @@ -615,7 +610,7 @@ class Socks5: self.state = 5 # for senders: init file_props and send '\n' if self.queue.on_success: result = self.queue.send_success_reply(self.file_props, - self.streamhost) + self.streamhost) if result == 0: self.state = 8 @@ -641,7 +636,7 @@ class Socks5: else: # receiving file contents from socket self.idlequeue.plug_idle(self, False, True) - + self.file_props['continue_cb'] = self.continue_paused_transfer # we have set up the connection, next - retrieve file self.state = 6 @@ -649,13 +644,13 @@ class Socks5: self.idlequeue.plug_idle(self, True, False) self.state += 1 return None - + def pollout(self): if self.mode == 'client': self.clnt_pollout() elif self.mode == 'server': self.svr_pollout() - + def svr_pollout(self): if not self.connected: self.disconnect() @@ -685,7 +680,7 @@ class Socks5: self.state += 1 # unplug and plug this time for reading self.idlequeue.plug_idle(self, False, True) - + def clnt_pollout(self): self.idlequeue.remove_timeout(self.fd) try: @@ -700,26 +695,25 @@ class Socks5: if self.file_props['paused']: self.idlequeue.plug_idle(self, False, False) return - result = self.start_transfer() # send + result = self.start_transfer() # send self.queue.process_result(result, self) return except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('caught SSL exception, ignored') return self.state += 1 # unplug and plug for reading self.idlequeue.plug_idle(self, False, True) self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - - + + def pollin(self): - if self.mode == 'client': self.clnt_pollin() elif self.mode == 'server': self.svr_pollin() - + def svr_pollin(self): if self.connected: try: @@ -731,16 +725,16 @@ class Socks5: self.disconnect() elif self.state == 5: - self.state = 7 - if self.type == 'sender': - # We wait for the end of the negotiation to - # send the file - self.idlequeue.plug_idle(self, False, False) - else: - # We plug for reading - self.idlequeue.plug_idle(self, False, True) - return - + self.state = 7 + if self.type == 'sender': + # We wait for the end of the negotiation to + # send the file + self.idlequeue.plug_idle(self, False, False) + else: + # We plug for reading + self.idlequeue.plug_idle(self, False, True) + return + elif self.state == 7: if self.file_props['paused']: self.file_props['continue_cb'] = self.continue_paused_transfer @@ -750,11 +744,11 @@ class Socks5: result = self.start_transfer() # send self.queue.process_result(result, self) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('caught SSL exception, ignored') else: self.disconnect() - + def clnt_pollin(self): self.idlequeue.remove_timeout(self.fd) if self.connected: @@ -773,26 +767,25 @@ class Socks5: result = self.start_transfer() # receive self.queue.process_result(result, self) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('caught SSL exception, ignored') return else: self.disconnect() - + def pollend(self): - if self.mode == 'client': self.clnt_pollend() elif self.mode == 'server': self.svr_pollend() - + def svr_pollend(self): self.state = 8 # end connection self.disconnect() self.file_props['error'] = -1 self.queue.process_result(-1, self) - + def clnt_pollend(self): if self.state >= 5: # error during transfer @@ -801,7 +794,7 @@ class Socks5: self.queue.process_result(-1, self) else: self.queue.reconnect_client(self, self.streamhost) - + def read_timeout(self): self.idlequeue.remove_timeout(self.fd) if self.state > 5: @@ -816,11 +809,11 @@ class Socks5: else: # stop transfer, there is no error code for this self.pollend() - + else: if self.mode == 'client': self.queue.reconnect_client(self, self.streamhost) - + def open_file_for_reading(self): if self.file is None: try: @@ -879,7 +872,7 @@ class Socks5: try: add = self._recv(64) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('SSL rehandshake request : ' + repr(e)) raise e except Exception: @@ -896,7 +889,7 @@ class Socks5: try: self._send(raw_data) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('SSL rehandshake request :' + repr(e)) raise e except Exception, e: @@ -921,7 +914,7 @@ class Socks5: try: lenn = self._send(buff) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('SSL rehandshake request :' + repr(e)) raise e except Exception, e: @@ -934,7 +927,7 @@ class Socks5: self.size += lenn current_time = self.idlequeue.current_time() self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] + self.file_props['last-time'] self.file_props['last-time'] = current_time self.file_props['received-len'] = self.size if self.size >= int(self.file_props['size']): @@ -975,7 +968,7 @@ class Socks5: lenn = len(self.remaining_buff) current_time = self.idlequeue.current_time() self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] + self.file_props['last-time'] self.file_props['last-time'] = current_time self.file_props['received-len'] += lenn self.remaining_buff = '' @@ -995,14 +988,14 @@ class Socks5: try: buff = self._recv(MAX_BUFF_LEN) except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: + OpenSSL.SSL.WantX509LookupError), e: log.info('SSL rehandshake request :' + repr(e)) raise e except Exception: buff = '' current_time = self.idlequeue.current_time() self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] + self.file_props['last-time'] self.file_props['last-time'] = current_time self.file_props['received-len'] += len(buff) if len(buff) == 0: @@ -1081,10 +1074,12 @@ class Socks5: return struct.pack('!BB', 0x05, 0x00) def _get_connect_buff(self): - ''' Connect request by domain name ''' + """ + Connect request by domain name + """ buff = struct.pack('!BBBBB%dsBB' % len(self.host), - 0x05, 0x01, 0x00, 0x03, len(self.host), self.host, - self.port >> 8, self.port & 0xff) + 0x05, 0x01, 0x00, 0x03, len(self.host), self.host, self.port >> 8, + self.port & 0xff) return buff def _get_request_buff(self, msg, command = 0x01): @@ -1151,8 +1146,8 @@ class Socks5: if 'is_a_proxy' in self.file_props: del(self.file_props['is_a_proxy']) return hashlib.sha1('%s%s%s' % (self.sid, - self.file_props['proxy_sender'], - self.file_props['proxy_receiver'])).hexdigest() + self.file_props['proxy_sender'], + self.file_props['proxy_receiver'])).hexdigest() return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\ hexdigest() @@ -1162,35 +1157,34 @@ class Socks5Sender(Socks5, IdleObject): """ def __init__(self, idlequeue, sock_hash, parent, mode,_sock, host=None, - port=None, fingerprint = None, connected=True, - file_props={}): - + port=None, fingerprint = None, connected=True, file_props={}): + self.fingerprint = fingerprint self.queue_idx = sock_hash self.queue = parent self.mode = mode # client or server self.file_props = file_props - - - + + + Socks5.__init__(self, idlequeue, host, port, None, None, None) self._sock = _sock - - + + if _sock is not None: '''if self.fingerprint is not None: self._sock = OpenSSL.SSL.Connection( jingle_xtls.get_context('server'), _sock) else: self._sock.setblocking(False) - ''' + ''' self.fd = _sock.fileno() self._recv = _sock.recv self._send = _sock.send self.connected = connected self.state = 1 # waiting for first bytes self.connect_timeout = 0 - + self.file_props['error'] = 0 self.file_props['disconnect_cb'] = self.disconnect self.file_props['started'] = True @@ -1209,18 +1203,18 @@ class Socks5Sender(Socks5, IdleObject): Send the file """ return self.write_next() - - + + def set_connection_sock(self, _sock): - + self._sock = _sock - + if self.fingerprint is not None: self._sock = OpenSSL.SSL.Connection( jingle_xtls.get_context('client'), self._sock) else: self._sock.setblocking(False) - + self.fd = _sock.fileno() self._recv = _sock.recv self._send = _sock.send @@ -1229,12 +1223,12 @@ class Socks5Sender(Socks5, IdleObject): self.file_props = None # start waiting for data self.idlequeue.plug_idle(self, False, True) - + def send_file(self): """ Start sending the file over verified connection """ - + self.pauses = 0 self.state = 7 # plug for writing @@ -1261,7 +1255,8 @@ class Socks5Listener(IdleObject): This class implements IdleObject, but we will expect only pollin events though - fingerprint: fingerprint of certificates we shall use, set to None if TLS connection not desired + fingerprint: fingerprint of certificates we shall use, set to None if + TLS connection not desired """ self.port = port self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC, @@ -1275,7 +1270,7 @@ class Socks5Listener(IdleObject): self.fd = -1 self.fingerprint = fingerprint self.file_props = fp - + def bind(self): for ai in self.ais: # try the different possibilities (ipv6, ipv4, etc.) @@ -1353,10 +1348,11 @@ class Socks5Listener(IdleObject): return _sock class Socks5Receiver(Socks5, IdleObject): - def __init__(self, idlequeue, streamhost, sid, mode, file_props = None, - fingerprint=None): + def __init__(self, idlequeue, streamhost, sid, mode, file_props = None, + fingerprint=None): """ - fingerprint: fingerprint of certificates we shall use, set to None if TLS connection not desired + fingerprint: fingerprint of certificates we shall use, set to None if + TLS connection not desired """ self.queue_idx = -1 self.streamhost = streamhost From e49a48d7da648a7ba2167c0132d6612553443524 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 24 Aug 2011 10:42:16 +0200 Subject: [PATCH 085/121] coding standards + choose correct streamhost when we receive the streamhost_used after we sent it --- src/common/jingle_ft.py | 83 +++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 4201307b9..8a7a63518 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -85,7 +85,7 @@ class JingleFileTransfer(JingleContent): self.file_props['transfered_size'] = [] log.info("FT request: %s" % file_props) - + if transport is None: self.transport = JingleTransportSocks5() self.transport.set_connection(session.connection) @@ -111,8 +111,8 @@ class JingleFileTransfer(JingleContent): security = content.getTag('security') if not security: # responder can not verify our fingerprint self.use_security = False - - + + if self.state == STATE_TRANSPORT_REPLACE: # We ack the session accept response = stanza.buildReply('result') @@ -120,19 +120,19 @@ class JingleFileTransfer(JingleContent): # We send the file con.files_props[self.file_props['sid']] = self.file_props fp = open(self.file_props['file-name'], 'r') - con.OpenStream( self.transport.sid, self.session.peerjid, + con.OpenStream( self.transport.sid, self.session.peerjid, fp, blocksize=4096) raise xmpp.NodeProcessed - + self.file_props['streamhosts'] = self.transport.remote_candidates for host in self.file_props['streamhosts']: host['initiator'] = self.session.initiator host['target'] = self.session.responder host['sid'] = self.file_props['sid'] - + response = stanza.buildReply('result') con.connection.send(response) - + if not gajim.socks5queue.get_file_props( self.session.connection.name, self.file_props['sid']): gajim.socks5queue.add_file_props(self.session.connection.name, @@ -144,7 +144,7 @@ class JingleFileTransfer(JingleContent): self.file_props['sid'], self.send_candidate_used, self._on_connect_error, fingerprint=fingerprint, receiving=False) - + raise xmpp.NodeProcessed def __on_session_terminate(self, stanza, content, error, action): @@ -175,7 +175,7 @@ class JingleFileTransfer(JingleContent): self.session.transport_replace() else: self.state = STATE_CAND_RECEIVED_PENDING_REPLY - + return streamhost_cid = content.getTag('transport').getTag('candidate-used').\ getAttr('cid') @@ -192,12 +192,12 @@ class JingleFileTransfer(JingleContent): if self.state == STATE_CAND_SENT_PENDING_REPLY: response = stanza.buildReply('result') self.session.connection.connection.send(response) - self.start_transfer(streamhost_used) + self.start_transfer() raise xmpp.NodeProcessed else: self.state = STATE_CAND_RECEIVED_PENDING_REPLY - - + + def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") @@ -230,16 +230,15 @@ class JingleFileTransfer(JingleContent): # proxy activated self.state = STATE_PROXY_ACTIVATED elif self.state == STATE_CAND_SENT_AND_RECEIVED: - if not self.nominated_cand['our-cand'] and \ - not self.nominated_cand['peer-cand']: - if not self.weinitiate: - return - self.session.transport_replace() + not self.nominated_cand['peer-cand']: + if not self.weinitiate: return + self.session.transport_replace() + return # initiate transfer - self.start_transfer(None) - + self.start_transfer() + def send_candidate_used(self, streamhost): """ send candidate-used stanza @@ -247,13 +246,13 @@ class JingleFileTransfer(JingleContent): log.info('send_candidate_used') if streamhost is None: return - + self.nominated_cand['our-cand'] = streamhost if self.state == STATE_CAND_RECEIVED_PENDING_REPLY: self.state = STATE_CAND_SENT_AND_RECEIVED else: self.state = STATE_CAND_SENT_PENDING_REPLY - + content = xmpp.Node('content') content.setAttr('creator', 'initiator') content.setAttr('name', self.name) @@ -274,15 +273,15 @@ class JingleFileTransfer(JingleContent): def _on_connect_error(self, to, _id, sid, code=404): self.nominated_cand['our-cand'] = False self.send_error_candidate() - + if self.state == STATE_CAND_RECEIVED_PENDING_REPLY: self.state = STATE_CAND_SENT_AND_RECEIVED else: self.state = STATE_CAND_SENT_PENDING_REPLY - - + + log.info('connect error, sid=' + sid) - + def _fill_content(self, content): description_node = xmpp.simplexml.Node( tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description') @@ -312,10 +311,10 @@ class JingleFileTransfer(JingleContent): def _store_socks5_sid(self, sid, hash_id): # callback from socsk5queue.start_listener self.file_props['hash'] = hash_id - + def _listen_host(self, stanza=None, content=None, error=None , action=None): - + receiver = self.file_props['receiver'] sender = self.file_props['sender'] @@ -328,7 +327,7 @@ class JingleFileTransfer(JingleContent): fingerprint = None if self.use_security: fingerprint = 'server' - + if self.weinitiate: listener = gajim.socks5queue.start_listener(port, sha_str, self._store_socks5_sid, self.file_props, @@ -346,33 +345,35 @@ class JingleFileTransfer(JingleContent): If this method returns true then the candidate we nominated will be used, if false, the candidate nominated by peer will be used ''' - + if self.nominated_cand['peer-cand'] == False: return True if self.nominated_cand['our-cand'] == False: return False - + peer_pr = int(self.nominated_cand['peer-cand']['priority']) our_pr = int(self.nominated_cand['our-cand']['priority']) - + if peer_pr != our_pr: return our_pr > peer_pr else: return self.weinitiate - - - def start_transfer(self, streamhost_used): - + + + def start_transfer(self): + self.state = STATE_TRANSFERING - + # It tells wether we start the transfer as client or server - type = None - + type = None + if self.isOurCandUsed(): type = 'client' + streamhost_used = self.nominated_cand['our-cand'] else: - type = 'server' - + type = 'server' + streamhost_used = self.nominated_cand['peer-cand'] + # FIXME if streamhost_used is none where do we get the proxy host if streamhost_used and streamhost_used['type'] == 'proxy': self.file_props['streamhost-used'] = True @@ -397,7 +398,7 @@ class JingleFileTransfer(JingleContent): jid = gajim.get_jid_without_resource(self.session.ourjid) gajim.socks5queue.send_file(self.file_props, self.session.connection.name, type) - + def get_content(desc): return JingleFileTransfer From 4c112419c0ca4a85de5e023a7441527d587b41aa Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 24 Aug 2011 23:53:36 +0200 Subject: [PATCH 086/121] start transfer when we get a candidate-error, but we found a remote usable streamhost --- src/common/jingle_ft.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 8a7a63518..6b9d5bcda 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -167,12 +167,16 @@ class JingleFileTransfer(JingleContent): if content.getTag('transport').getTag('candidate-error'): self.nominated_cand['peer-cand'] = False if self.state == STATE_CAND_SENT_PENDING_REPLY: - #self.state = STATE_CAND_SENT_AND_RECEIVED if not self.nominated_cand['our-cand'] and \ not self.nominated_cand['peer-cand']: if not self.weinitiate: return self.session.transport_replace() + else: + response = stanza.buildReply('result') + self.session.connection.connection.send(response) + self.start_transfer() + raise xmpp.NodeProcessed else: self.state = STATE_CAND_RECEIVED_PENDING_REPLY From 212f33cafa9cf2d65ea659a5cc09710934cf8fae Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 29 Oct 2011 00:09:45 -0400 Subject: [PATCH 087/121] socks5 proxy fixed --- src/common/jingle_content.py | 10 ++-- src/common/jingle_ft.py | 75 ++++++++++++++++++---------- src/common/jingle_transport.py | 82 +++++++++++++++++++------------ src/common/protocol/bytestream.py | 4 +- src/common/socks5.py | 40 ++++++++------- 5 files changed, 127 insertions(+), 84 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index dcc7e5bf8..079000f85 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -101,7 +101,6 @@ class JingleContent(object): Add a list of candidates to the list of remote candidates """ self.transport.remote_candidates = candidates - pass def on_stanza(self, stanza, content, error, action): """ @@ -112,15 +111,14 @@ class JingleContent(object): callback(stanza, content, error, action) def __on_transport_replace(self, stanza, content, error, action): - content.addChild(node=self.transport.make_transport()) - + def __on_transport_info(self, stanza, content, error, action): """ Got a new transport candidate """ candidates = self.transport.parse_transport_stanza( - content.getTag('transport')) + content.getTag('transport')) if candidates: self.add_remote_candidates(candidates) @@ -139,7 +137,7 @@ class JingleContent(object): content = self.__content() content.addChild(node=self.transport.make_transport([candidate])) self.session.send_transport_info(content) - + def send_error_candidate(self): """ Sends a candidate-error when we can't connect to a candidate. @@ -149,7 +147,7 @@ class JingleContent(object): tp.addChild(name='candidate-error') content.addChild(node=tp) self.session.send_transport_info(content) - + def send_description_info(self): content = self.__content() diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 6b9d5bcda..99d76831c 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -24,7 +24,7 @@ import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 from common import helpers -from common.socks5 import Socks5Receiver +from common.socks5 import Socks5Receiver, Socks5Sender from common.connection_handlers_events import FileRequestReceivedEvent import logging @@ -181,6 +181,14 @@ class JingleFileTransfer(JingleContent): self.state = STATE_CAND_RECEIVED_PENDING_REPLY return + + if content.getTag('transport').getTag('activated'): + self.state = STATE_TRANSFERING + jid = gajim.get_jid_without_resource(self.session.ourjid) + gajim.socks5queue.send_file(self.file_props, + self.session.connection.name, 'client') + return + streamhost_cid = content.getTag('transport').getTag('candidate-used').\ getAttr('cid') streamhost_used = None @@ -274,7 +282,7 @@ class JingleFileTransfer(JingleContent): self.session.send_transport_info(content) - def _on_connect_error(self, to, _id, sid, code=404): + def _on_connect_error(self, sid): self.nominated_cand['our-cand'] = False self.send_error_candidate() @@ -321,7 +329,6 @@ class JingleFileTransfer(JingleContent): receiver = self.file_props['receiver'] sender = self.file_props['sender'] - sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, receiver) self.file_props['sha_str'] = sha_str @@ -368,36 +375,52 @@ class JingleFileTransfer(JingleContent): self.state = STATE_TRANSFERING - # It tells wether we start the transfer as client or server - type = None - if self.isOurCandUsed(): - type = 'client' streamhost_used = self.nominated_cand['our-cand'] else: - type = 'server' streamhost_used = self.nominated_cand['peer-cand'] + + if streamhost_used['type'] == 'proxy': + self.file_props['is_a_proxy'] = True - # FIXME if streamhost_used is none where do we get the proxy host - if streamhost_used and streamhost_used['type'] == 'proxy': + if not self.weinitiate and streamhost_used['type'] == 'proxy': + r = gajim.socks5queue.readers + for reader in r: + if r[reader].host == streamhost_used['host'] and \ + r[reader].connected: + return + + if streamhost_used['type'] == 'proxy': self.file_props['streamhost-used'] = True - for proxy in self.file_props['proxyhosts']: - if proxy['host'] == streamhost_used['host'] and \ - proxy['port'] == streamhost_used['port'] and \ - proxy['jid'] == streamhost_used['jid']: - host_used = proxy - break - if 'streamhosts' not in self.file_props: - self.file_props['streamhosts'] = [] - self.file_props['streamhosts'].append(streamhost_used) - self.file_props['is_a_proxy'] = True - receiver = Socks5Receiver(gajim.idlequeue, streamhost_used, - self.file_props['sid'], self.file_props) - gajim.socks5queue.add_receiver(self.session.connection.name, - receiver) - streamhost_used['idx'] = receiver.queue_idx + streamhost_used['sid'] = self.file_props['sid'] + self.file_props['streamhosts'] = [] + self.file_props['streamhosts'].append(streamhost_used) + self.file_props['proxyhosts'] = [] + self.file_props['proxyhosts'].append(streamhost_used) + self.file_props['is_a_proxy'] = True + + gajim.socks5queue.idx += 1 + idx = gajim.socks5queue.idx + sockobj = Socks5Sender(gajim.idlequeue, idx, + gajim.socks5queue, + mode='client', + _sock=None, + host=str(streamhost_used['host']), + port=int(streamhost_used['port']), + fingerprint=None, + connected=False, + file_props=self.file_props) + sockobj.proxy = True + sockobj.streamhost = streamhost_used + gajim.socks5queue.add_sockobj(self.session.connection.name, + sockobj, 'sender') + streamhost_used['idx'] = sockobj.queue_idx + # If we offered the nominated candidate used, we activate + # the proxy + if not self.isOurCandUsed(): gajim.socks5queue.on_success[self.file_props['sid']] = \ - self.transport._on_proxy_auth_ok + self.transport._on_proxy_auth_ok + # TODO: add on failure else: jid = gajim.get_jid_without_resource(self.session.ourjid) gajim.socks5queue.send_file(self.file_props, diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 46376045e..868e0f373 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -118,7 +118,7 @@ class JingleTransportSocks5(JingleTransport): return xmpp.Node('candidate', attrs=attrs) def make_transport(self, candidates=None, add_candidates = True): - if add_candidates: + if add_candidates: self._add_local_ips_as_candidates() self._add_additional_candidates() self._add_proxy_candidates() @@ -139,10 +139,10 @@ class JingleTransportSocks5(JingleTransport): 'state': 0, 'target': self.ourjid, 'host': candidate['host'], - 'port': candidate['port'], + 'port': int(candidate['port']), 'cid': candidate['cid'], 'type': typ, - 'priority': candidate['priority'] + 'priority': candidate['priority'] } candidates.append(cand) @@ -151,11 +151,22 @@ class JingleTransportSocks5(JingleTransport): return candidates + def _add_candidates(self, candidates): + for cand in candidates: + in_remote = False + for cand2 in self.remote_candidates: + if cand['host'] == cand2['host'] and \ + cand['port'] == cand2['port']: + in_remote = True + break + if not in_remote: + self.candidates.append(cand) + def _add_local_ips_as_candidates(self): if not self.connection: return local_ip_cand = [] - port = gajim.config.get('file_transfers_port') + port = int(gajim.config.get('file_transfers_port')) type_preference = 126 #type preference of connection type. XEP-0260 section 2.2 c = {'host': self.connection.peerhost[0]} c['candidate_id'] = self.connection.connection.getAnID() @@ -178,14 +189,14 @@ class JingleTransportSocks5(JingleTransport): c['target'] = self.file_props['receiver'] local_ip_cand.append(c) - self.candidates += local_ip_cand + self._add_candidates(local_ip_cand) def _add_additional_candidates(self): if not self.connection: return type_preference = 126 additional_ip_cand = [] - port = gajim.config.get('file_transfers_port') + port = int(gajim.config.get('file_transfers_port')) ft_add_hosts = gajim.config.get('ft_add_hosts_to_send') if ft_add_hosts: @@ -200,7 +211,8 @@ class JingleTransportSocks5(JingleTransport): c['initiator'] = self.file_props['sender'] c['target'] = self.file_props['receiver'] additional_ip_cand.append(c) - self.candidates += additional_ip_cand + + self._add_candidates(additional_ip_cand) def _add_proxy_candidates(self): if not self.connection: @@ -219,14 +231,15 @@ class JingleTransportSocks5(JingleTransport): for proxyhost in proxyhosts: c = {'host': proxyhost['host']} c['candidate_id'] = self.connection.connection.getAnID() - c['port'] = proxyhost['port'] + c['port'] = int(proxyhost['port']) c['type'] = 'proxy' c['jid'] = proxyhost['jid'] c['priority'] = (2**16) * type_preference c['initiator'] = self.file_props['sender'] c['target'] = self.file_props['receiver'] proxy_cand.append(c) - self.candidates += proxy_cand + + self._add_candidates(proxy_cand) def get_content(self): sesn = self.connection.get_jingle_session(self.ourjid, @@ -240,54 +253,59 @@ class JingleTransportSocks5(JingleTransport): # send activate request to proxy, send activated confirmation to peer if not self.connection: return - file_props = self.file_props - iq = xmpp.Iq(to=proxy['initiator'], typ='set') + sesn = self.connection.get_jingle_session(self.ourjid, + self.file_props['session-sid']) + if sesn is None: + return + + iq = xmpp.Iq(to=proxy['jid'], frm=self.ourjid, typ='set') auth_id = "au_" + proxy['sid'] iq.setID(auth_id) query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) query.setAttr('sid', proxy['sid']) activate = query.setTag('activate') - activate.setData(file_props['proxy_receiver']) + activate.setData(sesn.peerjid) iq.setID(auth_id) self.connection.connection.send(iq) + content = xmpp.Node('content') content.setAttr('creator', 'initiator') c = self.get_content() content.setAttr('name', c.name) transport = xmpp.Node('transport') transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) + transport.setAttr('sid', proxy['sid']) activated = xmpp.Node('activated') cid = None - for host in self.candidates: - if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \ - and host['port'] == proxy['port']: - cid = host['candidate_id'] - break + + if 'cid' in proxy: + cid = proxy['cid'] + else: + for host in self.candidates: + if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \ + and host['port'] == proxy['port']: + cid = host['candidate_id'] + break if cid is None: - return + raise Exception, 'cid is missing' activated.setAttr('cid', cid) transport.addChild(node=activated) content.addChild(node=transport) - sesn = self.connection.get_jingle_session(self.ourjid, - self.file_props['session-sid']) - - if sesn is None: - return sesn.send_transport_info(content) class JingleTransportIBB(JingleTransport): - + def __init__(self, node=None, block_sz=None): - + JingleTransport.__init__(self, TransportType.streaming) - + if block_sz: self.block_sz = block_sz else: self.block_sz = '4096' - + self.connection = None self.sid = None if node and node.getAttr('sid'): @@ -296,19 +314,19 @@ class JingleTransportIBB(JingleTransport): def set_sid(self, sid): self.sid = sid - + def make_transport(self): - + transport = xmpp.Node('transport') transport.setNamespace(xmpp.NS_JINGLE_IBB) transport.setAttr('block-size', self.block_sz) transport.setAttr('sid', self.sid) - return transport - + return transport + def set_file_props(self, file_props): self.file_props = file_props - + import farsight class JingleTransportICEUDP(JingleTransport): diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index 561ae3cae..cf4edd546 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -363,7 +363,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): port = gajim.config.get('file_transfers_port') listener = gajim.socks5queue.start_listener(port, sha_str, - self._result_socks5_sid, file_props['sid']) + self._result_socks5_sid, file_props) if not listener: file_props['error'] = -5 from common.connection_handlers_events import FileRequestErrorEvent @@ -660,7 +660,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream): if 'stopped' in file_props and file_props['stopped']: self.remove_transfer(file_props) else: - gajim.socks5queue.send_file(file_props, self.name) + gajim.socks5queue.send_file(file_props, self.name, 'client') if 'fast' in file_props: fasts = file_props['fast'] if len(fasts) > 0: diff --git a/src/common/socks5.py b/src/common/socks5.py index 5c7e436ae..91849a296 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -113,8 +113,8 @@ class SocksQueue: file_props['streamhost-used'] is True: if 'proxyhosts' in file_props: for proxy in file_props['proxyhosts']: - if proxy == streamhost: - self.on_success[file_props['sid']](streamhost) + if proxy['host'] == streamhost['host']: + self.on_success[file_props['sid']](proxy) return 2 return 0 if 'streamhosts' in file_props: @@ -145,8 +145,13 @@ class SocksQueue: 'client', file_props, fingerprint=fp) self.add_sockobj(account, socks5obj) else: + if 'sha_str' in file_props: + idx = file_props['sha_str'] + else: + idx = self.idx + self.idx = self.idx + 1 self.type = 'sender' - socks5obj = Socks5Sender(self.idlequeue, file_props['sha_str'], + socks5obj = Socks5Sender(self.idlequeue, idx, self, mode='client' , _sock=None, host=str(streamhost['host']), port=int(streamhost['port']), fingerprint=fp, connected=False, file_props=file_props) @@ -202,7 +207,6 @@ class SocksQueue: if host['state'] == -2: host['state'] = 0 # FIXME: make the sender reconnect also - print 'reconnecting using socks receiver' client = Socks5Receiver(self.idlequeue, host, host['sid'], 'client',file_props) self.add_sockobj(client.account, client) @@ -225,15 +229,16 @@ class SocksQueue: if file_props is None: return streamhost['state'] = -1 + # FIXME: should only the receiver be remove? what if we are sending? self.remove_receiver(idx, False) if 'streamhosts' in file_props: for host in file_props['streamhosts']: if host['state'] != -1: return + self.readers = {} # failure_cb exists - this means that it has never been called if 'failure_cb' in file_props and file_props['failure_cb']: - file_props['failure_cb'](streamhost['initiator'], None, - file_props['sid'], code = 404) + file_props['failure_cb'](file_props['sid']) del(file_props['failure_cb']) def add_sockobj(self, account, sockobj, type='receiver'): @@ -306,15 +311,10 @@ class SocksQueue: sender = self.senders[key] file_props['streamhost-used'] = True sender.account = account - if file_props['type'] == 's': - sender.file_props = file_props - result = sender.send_file() - self.process_result(result, sender) - else: - file_props['elapsed-time'] = 0 - file_props['last-time'] = self.idlequeue.current_time() - file_props['received-len'] = 0 - sender.file_props = file_props + + sender.file_props = file_props + result = sender.send_file() + self.process_result(result, sender) def add_file_props(self, account, file_props): """ @@ -611,6 +611,10 @@ class Socks5: if self.queue.on_success: result = self.queue.send_success_reply(self.file_props, self.streamhost) + if self.type == 'sender' and self.proxy: + self.queue.process_result( self.send_file() + , self) + return if result == 0: self.state = 8 @@ -1143,6 +1147,7 @@ class Socks5: """ Get sha of sid + Initiator jid + Target jid """ + if 'is_a_proxy' in self.file_props: del(self.file_props['is_a_proxy']) return hashlib.sha1('%s%s%s' % (self.sid, @@ -1164,10 +1169,10 @@ class Socks5Sender(Socks5, IdleObject): self.queue = parent self.mode = mode # client or server self.file_props = file_props + self.proxy = False - - Socks5.__init__(self, idlequeue, host, port, None, None, None) + Socks5.__init__(self, idlequeue, host, port, None, None,file_props['sid']) self._sock = _sock @@ -1380,7 +1385,6 @@ class Socks5Receiver(Socks5, IdleObject): """ Start receiving the file over verified connection """ - print "receiving file" if self.file_props['started']: return self.file_props['error'] = 0 From f35c3d9143bbd5fb8741e6012bfa3d6314f429ba Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 29 Oct 2011 11:34:05 +0200 Subject: [PATCH 088/121] send a candidate-error when we get a session-accept with no streamhost --- src/common/socks5.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/socks5.py b/src/common/socks5.py index 91849a296..faf3cce8a 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -133,6 +133,9 @@ class SocksQueue: file_props = self.files_props[account][sid] file_props['failure_cb'] = on_failure + if not file_props['streamhosts']: + on_failure(file_props['sid']) + # add streamhosts to the queue for streamhost in file_props['streamhosts']: if 'type' in streamhost and streamhost['type'] == 'proxy': From 8408b177581cfff3fe97d7eb675315dba87fd584 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 29 Oct 2011 14:31:06 -0400 Subject: [PATCH 089/121] fix socks5 FT --- src/common/jingle_ft.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 99d76831c..527673eda 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -374,10 +374,15 @@ class JingleFileTransfer(JingleContent): def start_transfer(self): self.state = STATE_TRANSFERING + + # It tells wether we start the transfer as client or server + type = None if self.isOurCandUsed(): + type = 'client' streamhost_used = self.nominated_cand['our-cand'] else: + type = 'server' streamhost_used = self.nominated_cand['peer-cand'] if streamhost_used['type'] == 'proxy': From e4fa96cce3aab9f057a4e1283ee490c9e4621bfa Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 2 Nov 2011 00:09:33 +0100 Subject: [PATCH 090/121] make jingleFT work when receiver's proxy is used --- src/common/jingle_ft.py | 29 +++++++++++++++++++++++------ src/common/jingle_transport.py | 3 --- src/common/socks5.py | 6 +++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 527673eda..c0a0ae412 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -387,6 +387,12 @@ class JingleFileTransfer(JingleContent): if streamhost_used['type'] == 'proxy': self.file_props['is_a_proxy'] = True + if self.weinitiate: + self.file_props['proxy_sender'] = streamhost_used['initiator'] + self.file_props['proxy_receiver'] = streamhost_used['target'] + else: + self.file_props['proxy_sender'] = streamhost_used['target'] + self.file_props['proxy_receiver'] = streamhost_used['initiator'] if not self.weinitiate and streamhost_used['type'] == 'proxy': r = gajim.socks5queue.readers @@ -394,7 +400,14 @@ class JingleFileTransfer(JingleContent): if r[reader].host == streamhost_used['host'] and \ r[reader].connected: return - + + if self.weinitiate and streamhost_used['type'] == 'proxy': + s = gajim.socks5queue.senders + for sender in s: + if s[sender].host == streamhost_used['host'] and \ + s[sender].connected: + return + if streamhost_used['type'] == 'proxy': self.file_props['streamhost-used'] = True streamhost_used['sid'] = self.file_props['sid'] @@ -402,11 +415,11 @@ class JingleFileTransfer(JingleContent): self.file_props['streamhosts'].append(streamhost_used) self.file_props['proxyhosts'] = [] self.file_props['proxyhosts'].append(streamhost_used) - self.file_props['is_a_proxy'] = True - - gajim.socks5queue.idx += 1 - idx = gajim.socks5queue.idx - sockobj = Socks5Sender(gajim.idlequeue, idx, + + if self.weinitiate: + gajim.socks5queue.idx += 1 + idx = gajim.socks5queue.idx + sockobj = Socks5Sender(gajim.idlequeue, idx, gajim.socks5queue, mode='client', _sock=None, @@ -415,6 +428,10 @@ class JingleFileTransfer(JingleContent): fingerprint=None, connected=False, file_props=self.file_props) + else: + sockobj = Socks5Receiver(gajim.idlequeue, streamhost_used, + sid=self.file_props['sid'], mode='client', + file_props=self.file_props, fingerprint=None) sockobj.proxy = True sockobj.streamhost = streamhost_used gajim.socks5queue.add_sockobj(self.session.connection.name, diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 868e0f373..1bed1aa1a 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -223,9 +223,6 @@ class JingleTransportSocks5(JingleTransport): proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props) if proxyhosts: - self.file_props['proxy_receiver'] = unicode( - self.file_props['receiver']) - self.file_props['proxy_sender'] = unicode(self.file_props['sender']) self.file_props['proxyhosts'] = proxyhosts for proxyhost in proxyhosts: diff --git a/src/common/socks5.py b/src/common/socks5.py index faf3cce8a..cc3ad7753 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -115,7 +115,7 @@ class SocksQueue: for proxy in file_props['proxyhosts']: if proxy['host'] == streamhost['host']: self.on_success[file_props['sid']](proxy) - return 2 + return 1 return 0 if 'streamhosts' in file_props: for host in file_props['streamhosts']: @@ -154,6 +154,10 @@ class SocksQueue: idx = self.idx self.idx = self.idx + 1 self.type = 'sender' + if 'type' in streamhost and streamhost['type'] == 'proxy': + file_props['is_a_proxy'] = True + file_props['proxy_sender'] = streamhost['target'] + file_props['proxy_receiver'] = streamhost['initiator'] socks5obj = Socks5Sender(self.idlequeue, idx, self, mode='client' , _sock=None, host=str(streamhost['host']), port=int(streamhost['port']), From bc78e35d55b5962e849360ea6d21053287e07150 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 17 Nov 2011 23:55:44 -0500 Subject: [PATCH 091/121] refactoring --- src/common/jingle_ft.py | 9 +- src/common/socks5.py | 750 +++++++++++++++++++++------------------- 2 files changed, 405 insertions(+), 354 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index c0a0ae412..f1981f533 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -24,7 +24,7 @@ import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 from common import helpers -from common.socks5 import Socks5Receiver, Socks5Sender +from common.socks5 import Socks5ReceiverClient, Socks5SenderClient from common.connection_handlers_events import FileRequestReceivedEvent import logging @@ -419,9 +419,8 @@ class JingleFileTransfer(JingleContent): if self.weinitiate: gajim.socks5queue.idx += 1 idx = gajim.socks5queue.idx - sockobj = Socks5Sender(gajim.idlequeue, idx, + sockobj = Socks5SenderClient(gajim.idlequeue, idx, gajim.socks5queue, - mode='client', _sock=None, host=str(streamhost_used['host']), port=int(streamhost_used['port']), @@ -429,8 +428,8 @@ class JingleFileTransfer(JingleContent): connected=False, file_props=self.file_props) else: - sockobj = Socks5Receiver(gajim.idlequeue, streamhost_used, - sid=self.file_props['sid'], mode='client', + sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used, + sid=self.file_props['sid'], file_props=self.file_props, fingerprint=None) sockobj.proxy = True sockobj.streamhost = streamhost_used diff --git a/src/common/socks5.py b/src/common/socks5.py index cc3ad7753..81868eece 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -144,8 +144,8 @@ class SocksQueue: fp = fingerprint if receiving: self.type = 'receiver' - socks5obj = Socks5Receiver(self.idlequeue, streamhost, sid, - 'client', file_props, fingerprint=fp) + socks5obj = Socks5ReceiverClient(self.idlequeue, streamhost, sid, + file_props, fingerprint=fp) self.add_sockobj(account, socks5obj) else: if 'sha_str' in file_props: @@ -158,10 +158,10 @@ class SocksQueue: file_props['is_a_proxy'] = True file_props['proxy_sender'] = streamhost['target'] file_props['proxy_receiver'] = streamhost['initiator'] - socks5obj = Socks5Sender(self.idlequeue, idx, - self, mode='client' , _sock=None, - host=str(streamhost['host']), port=int(streamhost['port']), - fingerprint=fp, connected=False, file_props=file_props) + socks5obj = Socks5SenderClient(self.idlequeue, idx, + self, _sock=None,host=str(streamhost['host']), + port=int(streamhost['port']),fingerprint=fp, + connected=False, file_props=file_props) socks5obj.streamhost = streamhost self.add_sockobj(account, socks5obj, type='sender') @@ -214,8 +214,8 @@ class SocksQueue: if host['state'] == -2: host['state'] = 0 # FIXME: make the sender reconnect also - client = Socks5Receiver(self.idlequeue, host, host['sid'], - 'client',file_props) + client = Socks5ReceiverClient(self.idlequeue, host, host['sid'], + file_props) self.add_sockobj(client.account, client) host['idx'] = client.queue_idx # we still have chances to connect @@ -311,8 +311,13 @@ class SocksQueue: def send_file(self, file_props, account, type): for key in self.senders.keys(): + if isinstance(self.senders[key], Socks5SenderClient): + objtype = 'client' + else: + objtype = 'server' + if file_props['name'] in key and file_props['sid'] in key \ - and self.senders[key].mode == type: + and objtype == type: log.info("socks5: sending file") sender = self.senders[key] @@ -372,7 +377,7 @@ class SocksQueue: if self.type == 'sender' and \ not self.isHashInSockObjs(self.senders, sock_hash): - sockobj = Socks5Sender(self.idlequeue, sock_hash, self, 'server', + sockobj = Socks5SenderServer(self.idlequeue, sock_hash, self, sock[0], sock[1][0], sock[1][1], fingerprint='server', file_props=listener.file_props) self._add(sockobj, self.senders, listener.file_props, sock_hash) @@ -388,9 +393,9 @@ class SocksQueue: sh['initiator'] = None sh['target'] = None - sockobj = Socks5Receiver(idlequeue=self.idlequeue, + sockobj = Socks5ReceiverServer(idlequeue=self.idlequeue, streamhost=sh,sid=None, file_props=listener.file_props, - mode='server',fingerprint='server') + fingerprint='server') self._add(sockobj, self.readers, listener.file_props, sock_hash) @@ -429,10 +434,12 @@ class SocksQueue: self.idlequeue.remove_timeout(reader.fd) if do_disconnect: reader.disconnect() + break else: if reader.streamhost is not None: reader.streamhost['state'] = -1 del(self.readers[key]) + break def remove_sender(self, idx, do_disconnect=True): """ @@ -482,11 +489,6 @@ class Socks5: self.connected = False self.type = '' - def start_transfer(self): - """ - Must be implemented by subclass. - """ - pass def _is_connected(self): if self.state < 5: @@ -557,255 +559,6 @@ class Socks5: self.idlequeue.plug_idle(self, True, False) return 1 # we are connected - def svr_main(self): - """ - Initial requests for verifying the connection - """ - if self.state == 1: # initial read - buff = self.receive() - if not self.connected: - return -1 - mechs = self._parse_auth_buff(buff) - if mechs is None: - return -1 # invalid auth methods received - elif self.state == 3: # get next request - buff = self.receive() - req_type, self.sha_msg = self._parse_request_buff(buff)[:2] - if req_type != 0x01: - return -1 # request is not of type 'connect' - self.state += 1 # go to the next step - # unplug & plug for writing - self.idlequeue.plug_idle(self, True, False) - return None - - def clnt_main(self, timeout=0): - """ - Begin negotiation. on success 'address' != 0 - """ - result = 1 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return - - if self.state == 2: # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.disconnect() - elif self.state == 4: # get approve of our request - if buff is None: - return None - sub_buff = buff[:4] - if len(sub_buff) < 4: - return None - version, address_type = struct.unpack('!BxxB', buff[:4]) - addrlen = 0 - if address_type == 0x03: - addrlen = ord(buff[4]) - address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) - portlen = len(buff[addrlen + 5:]) - if portlen == 1: - port, = struct.unpack('!B', buff[addrlen + 5]) - elif portlen == 2: - port, = struct.unpack('!H', buff[addrlen + 5:]) - else: # Gaim bug :) - port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) - self.remaining_buff = buff[addrlen + 7:] - self.state = 5 # for senders: init file_props and send '\n' - if self.queue.on_success: - result = self.queue.send_success_reply(self.file_props, - self.streamhost) - if self.type == 'sender' and self.proxy: - self.queue.process_result( self.send_file() - , self) - return - - if result == 0: - self.state = 8 - self.disconnect() - - # for senders: init file_props - if result == 1 and self.state == 5: - if self.file_props['type'] == 's': - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['stalled'] = False - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - # start sending file contents to socket - #self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - #self.idlequeue.plug_idle(self, True, False) - self.idlequeue.plug_idle(self, False, False) - else: - # receiving file contents from socket - self.idlequeue.plug_idle(self, False, True) - - self.file_props['continue_cb'] = self.continue_paused_transfer - # we have set up the connection, next - retrieve file - self.state = 6 - if self.state < 5: - self.idlequeue.plug_idle(self, True, False) - self.state += 1 - return None - - def pollout(self): - if self.mode == 'client': - self.clnt_pollout() - elif self.mode == 'server': - self.svr_pollout() - - def svr_pollout(self): - if not self.connected: - self.disconnect() - return - self.idlequeue.remove_timeout(self.fd) - if self.state == 2: # send reply with desired auth type - self.send_raw(self._get_auth_response()) - elif self.state == 4: # send positive response to the 'connect' - self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) - elif self.state == 7: - if self.file_props['paused']: - self.file_props['continue_cb'] = self.continue_paused_transfer - self.idlequeue.plug_idle(self, False, False) - return - result = self.start_transfer() # send - self.queue.process_result(result, self) - if result is None or result <= 0: - self.disconnect() - return - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - elif self.state == 8: - self.disconnect() - return - else: - self.disconnect() - if self.state < 5: - self.state += 1 - # unplug and plug this time for reading - self.idlequeue.plug_idle(self, False, True) - - def clnt_pollout(self): - self.idlequeue.remove_timeout(self.fd) - try: - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - self.send_raw(self._get_auth_buff()) - elif self.state == 3: # send 'connect' request - self.send_raw(self._get_request_buff(self._get_sha1_auth())) - elif self.file_props['type'] != 'r': - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - result = self.start_transfer() # send - self.queue.process_result(result, self) - return - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: - log.info('caught SSL exception, ignored') - return - self.state += 1 - # unplug and plug for reading - self.idlequeue.plug_idle(self, False, True) - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - - - def pollin(self): - if self.mode == 'client': - self.clnt_pollin() - elif self.mode == 'server': - self.svr_pollin() - - def svr_pollin(self): - if self.connected: - try: - if self.state < 5: - result = self.svr_main() - if self.state == 4: - self.queue.result_sha(self.sha_msg, self.queue_idx) - if result == -1: - self.disconnect() - - elif self.state == 5: - self.state = 7 - if self.type == 'sender': - # We wait for the end of the negotiation to - # send the file - self.idlequeue.plug_idle(self, False, False) - else: - # We plug for reading - self.idlequeue.plug_idle(self, False, True) - return - - elif self.state == 7: - if self.file_props['paused']: - self.file_props['continue_cb'] = self.continue_paused_transfer - self.idlequeue.plug_idle(self, False, False) - return - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - result = self.start_transfer() # send - self.queue.process_result(result, self) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: - log.info('caught SSL exception, ignored') - else: - self.disconnect() - - def clnt_pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.connected: - try: - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - if self.state < 5: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - result = self.clnt_main(0) - self.queue.process_result(result, self) - elif self.state == 5: # wait for proxy reply - pass - elif self.file_props['type'] == 'r': - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - result = self.start_transfer() # receive - self.queue.process_result(result, self) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, - OpenSSL.SSL.WantX509LookupError), e: - log.info('caught SSL exception, ignored') - return - else: - self.disconnect() - - - def pollend(self): - if self.mode == 'client': - self.clnt_pollend() - elif self.mode == 'server': - self.svr_pollend() - - def svr_pollend(self): - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) - - def clnt_pollend(self): - if self.state >= 5: - # error during transfer - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) - else: - self.queue.reconnect_client(self, self.streamhost) - def read_timeout(self): self.idlequeue.remove_timeout(self.fd) if self.state > 5: @@ -822,7 +575,8 @@ class Socks5: self.pollend() else: - if self.mode == 'client': + if isinstance(self, Socks5SenderClient) or isinstance(self, + Socks5ReceiverClient): self.queue.reconnect_client(self, self.streamhost) def open_file_for_reading(self): @@ -1162,24 +916,23 @@ class Socks5: self.file_props['proxy_receiver'])).hexdigest() return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\ hexdigest() + -class Socks5Sender(Socks5, IdleObject): +class Socks5Sender(IdleObject): """ Class for sending file to socket over socks5 """ - def __init__(self, idlequeue, sock_hash, parent, mode,_sock, host=None, + def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, port=None, fingerprint = None, connected=True, file_props={}): self.fingerprint = fingerprint self.queue_idx = sock_hash self.queue = parent - self.mode = mode # client or server self.file_props = file_props self.proxy = False - Socks5.__init__(self, idlequeue, host, port, None, None,file_props['sid']) self._sock = _sock @@ -1259,6 +1012,383 @@ class Socks5Sender(Socks5, IdleObject): if self.queue is not None: self.queue.remove_sender(self.queue_idx, False) +class Socks5Receiver(IdleObject): + + def __init__(self, idlequeue, streamhost, sid, file_props = None, + fingerprint=None): + """ + fingerprint: fingerprint of certificates we shall use, set to None if + TLS connection not desired + """ + self.queue_idx = -1 + self.streamhost = streamhost + self.queue = None + self.fingerprint = fingerprint + self.connect_timeout = 0 + self.connected = False + self.pauses = 0 + self.file_props = file_props + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['error'] = 0 + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + self.file_props['received-len'] = 0 + + + def receive_file(self): + """ + Start receiving the file over verified connection + """ + if self.file_props['started']: + return + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + self.file_props['connected'] = True + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + self.state = 7 + # plug for reading + self.idlequeue.plug_idle(self, False, True) + return self.get_file_contents(0) # initial for nl byte + + def start_transfer(self): + """ + Receive the file + """ + return self.get_file_contents(0) + + def set_sock(self, _sock): + self._sock = _sock + self._sock.setblocking(False) + self.fd = _sock.fileno() + self._recv = _sock.recv + self._send = _sock.send + self.connected = True + self.state = 1 # waiting for first bytes + # start waiting for data + self.idlequeue.plug_idle(self, False, True) + + def disconnect(self, cb=True): + """ + Close the socket. Remove self from queue if cb is True + """ + # close connection + Socks5.disconnect(self) + if cb is True: + self.file_props['disconnect_cb'] = None + if self.queue is not None: + self.queue.remove_receiver(self.queue_idx, False) + +class Socks5Server(Socks5): + def __init__(self, idlequeue, host, port, initiator, target, sid): + + Socks5.__init__(self, idlequeue, host, port, initiator, target, sid) + + + def main(self): + """ + Initial requests for verifying the connection + """ + if self.state == 1: # initial read + buff = self.receive() + if not self.connected: + return -1 + mechs = self._parse_auth_buff(buff) + if mechs is None: + return -1 # invalid auth methods received + elif self.state == 3: # get next request + buff = self.receive() + req_type, self.sha_msg = self._parse_request_buff(buff)[:2] + if req_type != 0x01: + return -1 # request is not of type 'connect' + self.state += 1 # go to the next step + # unplug & plug for writing + self.idlequeue.plug_idle(self, True, False) + return None + + + def pollin(self): + if self.connected: + try: + if self.state < 5: + result = self.main() + if self.state == 4: + self.queue.result_sha(self.sha_msg, self.queue_idx) + if result == -1: + self.disconnect() + + elif self.state == 5: + self.state = 7 + if self.type == 'sender': + # We wait for the end of the negotiation to + # send the file + self.idlequeue.plug_idle(self, False, False) + else: + # We plug for reading + self.idlequeue.plug_idle(self, False, True) + return + + elif self.state == 7: + if self.file_props['paused']: + self.file_props['continue_cb'] = self.continue_paused_transfer + self.idlequeue.plug_idle(self, False, False) + return + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.start_transfer() # send + self.queue.process_result(result, self) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') + else: + self.disconnect() + + + def pollend(self): + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) + + def pollout(self): + if not self.connected: + self.disconnect() + return + self.idlequeue.remove_timeout(self.fd) + if self.state == 2: # send reply with desired auth type + self.send_raw(self._get_auth_response()) + elif self.state == 4: # send positive response to the 'connect' + self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) + elif self.state == 7: + if self.file_props['paused']: + self.file_props['continue_cb'] = self.continue_paused_transfer + self.idlequeue.plug_idle(self, False, False) + return + result = self.start_transfer() # send + self.queue.process_result(result, self) + if result is None or result <= 0: + self.disconnect() + return + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + elif self.state == 8: + self.disconnect() + return + else: + self.disconnect() + if self.state < 5: + self.state += 1 + # unplug and plug this time for reading + self.idlequeue.plug_idle(self, False, True) + + +class Socks5Client(Socks5): + + def __init__(self, idlequeue, host, port, initiator, target, sid): + + Socks5.__init__(self, idlequeue, host, port, initiator, target, sid) + + def main(self, timeout=0): + """ + Begin negotiation. on success 'address' != 0 + """ + result = 1 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return + + if self.state == 2: # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.disconnect() + elif self.state == 4: # get approve of our request + if buff is None: + return None + sub_buff = buff[:4] + if len(sub_buff) < 4: + return None + version, address_type = struct.unpack('!BxxB', buff[:4]) + addrlen = 0 + if address_type == 0x03: + addrlen = ord(buff[4]) + address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) + portlen = len(buff[addrlen + 5:]) + if portlen == 1: + port, = struct.unpack('!B', buff[addrlen + 5]) + elif portlen == 2: + port, = struct.unpack('!H', buff[addrlen + 5:]) + else: # Gaim bug :) + port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) + self.remaining_buff = buff[addrlen + 7:] + self.state = 5 # for senders: init file_props and send '\n' + if self.queue.on_success: + result = self.queue.send_success_reply(self.file_props, + self.streamhost) + if self.type == 'sender' and self.proxy: + self.queue.process_result( self.send_file() + , self) + return + + if result == 0: + self.state = 8 + self.disconnect() + + # for senders: init file_props + if result == 1 and self.state == 5: + if self.file_props['type'] == 's': + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['stalled'] = False + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + # start sending file contents to socket + #self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + #self.idlequeue.plug_idle(self, True, False) + self.idlequeue.plug_idle(self, False, False) + else: + # receiving file contents from socket + self.idlequeue.plug_idle(self, False, True) + + self.file_props['continue_cb'] = self.continue_paused_transfer + # we have set up the connection, next - retrieve file + self.state = 6 + if self.state < 5: + self.idlequeue.plug_idle(self, True, False) + self.state += 1 + return None + + + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.connected: + try: + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + if self.state < 5: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + result = self.main(0) + self.queue.process_result(result, self) + elif self.state == 5: # wait for proxy reply + pass + elif self.file_props['type'] == 'r': + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.start_transfer() # receive + self.queue.process_result(result, self) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') + return + else: + self.disconnect() + + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + try: + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + self.send_raw(self._get_auth_buff()) + elif self.state == 3: # send 'connect' request + self.send_raw(self._get_request_buff(self._get_sha1_auth())) + elif self.file_props['type'] != 'r': + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + result = self.start_transfer() # send + self.queue.process_result(result, self) + return + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError, + OpenSSL.SSL.WantX509LookupError), e: + log.info('caught SSL exception, ignored') + return + self.state += 1 + # unplug and plug for reading + self.idlequeue.plug_idle(self, False, True) + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + + def pollend(self): + if self.state >= 5: + # error during transfer + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) + else: + self.queue.reconnect_client(self, self.streamhost) + + + +class Socks5SenderClient(Socks5Client, Socks5Sender): + + def __init__(self, idlequeue, sock_hash, parent,_sock, host=None, + port=None, fingerprint = None, connected=True, file_props={}): + + Socks5.__init__(self, idlequeue, host, port, None, None, + file_props['sid']) + + Socks5Sender.__init__(self,idlequeue, sock_hash, parent,_sock, + host, port, fingerprint , connected, file_props) + + + + +class Socks5SenderServer(Socks5Server, Socks5Sender): + + def __init__(self, idlequeue, sock_hash, parent,_sock, host=None, + port=None, fingerprint = None, connected=True, file_props={}): + + Socks5.__init__(self, idlequeue, host, port, None, None, + file_props['sid']) + + Socks5Sender.__init__(self,idlequeue, sock_hash, parent, _sock, + host, port, fingerprint , connected, file_props) + + +class Socks5ReceiverClient(Socks5Client, Socks5Receiver): + + def __init__(self, idlequeue, streamhost, sid, file_props = None, + fingerprint=None): + Socks5.__init__(self, idlequeue, streamhost['host'], + int(streamhost['port']), streamhost['initiator'], + streamhost['target'], sid) + + Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props, + fingerprint) + + + +class Socks5ReceiverServer(Socks5Server, Socks5Receiver): + + def __init__(self, idlequeue, streamhost, sid, file_props = None, + fingerprint=None): + + Socks5.__init__(self, idlequeue, streamhost['host'], + int(streamhost['port']), streamhost['initiator'], + streamhost['target'], sid) + + Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props, + fingerprint) + + + class Socks5Listener(IdleObject): def __init__(self, idlequeue, port, fp, fingerprint=None): """ @@ -1359,82 +1489,4 @@ class Socks5Listener(IdleObject): _sock[0].setblocking(False) return _sock -class Socks5Receiver(Socks5, IdleObject): - def __init__(self, idlequeue, streamhost, sid, mode, file_props = None, - fingerprint=None): - """ - fingerprint: fingerprint of certificates we shall use, set to None if - TLS connection not desired - """ - self.queue_idx = -1 - self.streamhost = streamhost - self.queue = None - self.fingerprint = fingerprint - self.connect_timeout = 0 - self.connected = False - self.pauses = 0 - self.file_props = file_props - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['error'] = 0 - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['continue_cb'] = self.continue_paused_transfer - self.file_props['stalled'] = False - self.file_props['received-len'] = 0 - self.mode = mode # client or server - Socks5.__init__(self, idlequeue, streamhost['host'], - int(streamhost['port']), streamhost['initiator'], streamhost['target'], - sid) - - def receive_file(self): - """ - Start receiving the file over verified connection - """ - if self.file_props['started']: - return - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['continue_cb'] = self.continue_paused_transfer - self.file_props['stalled'] = False - self.file_props['connected'] = True - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - self.state = 7 - # plug for reading - self.idlequeue.plug_idle(self, False, True) - return self.get_file_contents(0) # initial for nl byte - - def start_transfer(self): - """ - Receive the file - """ - return self.get_file_contents(0) - - def set_sock(self, _sock): - self._sock = _sock - self._sock.setblocking(False) - self.fd = _sock.fileno() - self._recv = _sock.recv - self._send = _sock.send - self.connected = True - self.state = 1 # waiting for first bytes - # start waiting for data - self.idlequeue.plug_idle(self, False, True) - - def disconnect(self, cb=True): - """ - Close the socket. Remove self from queue if cb is True - """ - # close connection - Socks5.disconnect(self) - if cb is True: - self.file_props['disconnect_cb'] = None - if self.queue is not None: - self.queue.remove_receiver(self.queue_idx, False) From 543e0265ded8f918340df23b0a68273e9fa13291 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 18 Nov 2011 15:07:25 -0500 Subject: [PATCH 092/121] refactoring --- src/common/socks5.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index 81868eece..9afe48d3c 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -311,13 +311,8 @@ class SocksQueue: def send_file(self, file_props, account, type): for key in self.senders.keys(): - if isinstance(self.senders[key], Socks5SenderClient): - objtype = 'client' - else: - objtype = 'server' - - if file_props['name'] in key and file_props['sid'] in key \ - and objtype == type: + if file_props['name'] in key and file_props['sid'] in key \ + and self.senders[key].type == type: log.info("socks5: sending file") sender = self.senders[key] @@ -575,8 +570,7 @@ class Socks5: self.pollend() else: - if isinstance(self, Socks5SenderClient) or isinstance(self, - Socks5ReceiverClient): + if self.type == 'client': self.queue.reconnect_client(self, self.streamhost) def open_file_for_reading(self): @@ -1347,6 +1341,8 @@ class Socks5SenderClient(Socks5Client, Socks5Sender): Socks5Sender.__init__(self,idlequeue, sock_hash, parent,_sock, host, port, fingerprint , connected, file_props) + self.type = 'client' + @@ -1362,6 +1358,8 @@ class Socks5SenderServer(Socks5Server, Socks5Sender): host, port, fingerprint , connected, file_props) + self.type = 'server' + class Socks5ReceiverClient(Socks5Client, Socks5Receiver): def __init__(self, idlequeue, streamhost, sid, file_props = None, @@ -1373,7 +1371,9 @@ class Socks5ReceiverClient(Socks5Client, Socks5Receiver): Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props, fingerprint) + self.type = 'client' + class Socks5ReceiverServer(Socks5Server, Socks5Receiver): @@ -1387,6 +1387,7 @@ class Socks5ReceiverServer(Socks5Server, Socks5Receiver): Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props, fingerprint) + self.type = 'server' class Socks5Listener(IdleObject): From eec056ccd1c8e7fd82d112610e2deba259205d9c Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 18 Nov 2011 18:21:34 -0500 Subject: [PATCH 093/121] refactoring --- src/common/socks5.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index 9afe48d3c..e70ccb38a 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -1088,6 +1088,7 @@ class Socks5Server(Socks5): Socks5.__init__(self, idlequeue, host, port, initiator, target, sid) + self.type = 'server' def main(self): """ @@ -1190,6 +1191,8 @@ class Socks5Client(Socks5): Socks5.__init__(self, idlequeue, host, port, initiator, target, sid) + self.type = 'client' + def main(self, timeout=0): """ Begin negotiation. on success 'address' != 0 @@ -1341,7 +1344,6 @@ class Socks5SenderClient(Socks5Client, Socks5Sender): Socks5Sender.__init__(self,idlequeue, sock_hash, parent,_sock, host, port, fingerprint , connected, file_props) - self.type = 'client' @@ -1358,7 +1360,6 @@ class Socks5SenderServer(Socks5Server, Socks5Sender): host, port, fingerprint , connected, file_props) - self.type = 'server' class Socks5ReceiverClient(Socks5Client, Socks5Receiver): @@ -1371,7 +1372,6 @@ class Socks5ReceiverClient(Socks5Client, Socks5Receiver): Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props, fingerprint) - self.type = 'client' @@ -1387,7 +1387,6 @@ class Socks5ReceiverServer(Socks5Server, Socks5Receiver): Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props, fingerprint) - self.type = 'server' class Socks5Listener(IdleObject): From 78e7361bf2ec892ee476eefbbf656f31fa4d2d53 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 22 Dec 2011 20:12:11 -0500 Subject: [PATCH 094/121] refactoring --- src/common/jingle_transport.py | 35 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 1bed1aa1a..6470bdb44 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -59,6 +59,8 @@ class JingleTransport(object): Build a candidate stanza for the given candidate """ pass + + def make_transport(self, candidates=None): """ @@ -77,6 +79,20 @@ class JingleTransport(object): Return the list of transport candidates from a transport stanza """ return [] + + def set_connection(self, conn): + self.connection = conn + if not self.sid: + self.sid = self.connection.connection.getAnID() + + def set_file_props(self, file_props): + self.file_props = file_props + + def set_our_jid(self, jid): + self.ourjid = jid + + def set_sid(self, sid): + self.sid = sid class JingleTransportSocks5(JingleTransport): """ @@ -91,16 +107,6 @@ class JingleTransportSocks5(JingleTransport): if node and node.getAttr('sid'): self.sid = node.getAttr('sid') - def set_file_props(self, file_props): - self.file_props = file_props - - def set_our_jid(self, jid): - self.ourjid = jid - - def set_connection(self, conn): - self.connection = conn - if not self.sid: - self.sid = self.connection.connection.getAnID() def make_candidate(self, candidate): import logging @@ -309,9 +315,6 @@ class JingleTransportIBB(JingleTransport): self.sid = node.getAttr('sid') - def set_sid(self, sid): - self.sid = sid - def make_transport(self): transport = xmpp.Node('transport') @@ -319,11 +322,7 @@ class JingleTransportIBB(JingleTransport): transport.setAttr('block-size', self.block_sz) transport.setAttr('sid', self.sid) return transport - - def set_file_props(self, file_props): - self.file_props = file_props - - + import farsight class JingleTransportICEUDP(JingleTransport): From c9f2176ccb7549ea2d2e0761c41f579df3af7f36 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 22 Dec 2011 21:52:37 -0500 Subject: [PATCH 095/121] fix bad refactoring --- src/common/jingle_ft.py | 8 ++++---- src/common/socks5.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index f1981f533..b8b06c235 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -376,13 +376,13 @@ class JingleFileTransfer(JingleContent): self.state = STATE_TRANSFERING # It tells wether we start the transfer as client or server - type = None + mode = None if self.isOurCandUsed(): - type = 'client' + mode = 'client' streamhost_used = self.nominated_cand['our-cand'] else: - type = 'server' + mode = 'server' streamhost_used = self.nominated_cand['peer-cand'] if streamhost_used['type'] == 'proxy': @@ -445,7 +445,7 @@ class JingleFileTransfer(JingleContent): else: jid = gajim.get_jid_without_resource(self.session.ourjid) gajim.socks5queue.send_file(self.file_props, - self.session.connection.name, type) + self.session.connection.name, mode) def get_content(desc): return JingleFileTransfer diff --git a/src/common/socks5.py b/src/common/socks5.py index e70ccb38a..47fe1b6d3 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -309,10 +309,10 @@ class SocksQueue: result = reader.write_next() self.process_result(result, reader) - def send_file(self, file_props, account, type): + def send_file(self, file_props, account, mode): for key in self.senders.keys(): if file_props['name'] in key and file_props['sid'] in key \ - and self.senders[key].type == type: + and self.senders[key].mode == mode: log.info("socks5: sending file") sender = self.senders[key] @@ -570,7 +570,7 @@ class Socks5: self.pollend() else: - if self.type == 'client': + if self.mode == 'client': self.queue.reconnect_client(self, self.streamhost) def open_file_for_reading(self): @@ -1088,7 +1088,7 @@ class Socks5Server(Socks5): Socks5.__init__(self, idlequeue, host, port, initiator, target, sid) - self.type = 'server' + self.mode = 'server' def main(self): """ @@ -1191,7 +1191,7 @@ class Socks5Client(Socks5): Socks5.__init__(self, idlequeue, host, port, initiator, target, sid) - self.type = 'client' + self.mode = 'client' def main(self, timeout=0): """ @@ -1338,7 +1338,7 @@ class Socks5SenderClient(Socks5Client, Socks5Sender): def __init__(self, idlequeue, sock_hash, parent,_sock, host=None, port=None, fingerprint = None, connected=True, file_props={}): - Socks5.__init__(self, idlequeue, host, port, None, None, + Socks5Client.__init__(self, idlequeue, host, port, None, None, file_props['sid']) Socks5Sender.__init__(self,idlequeue, sock_hash, parent,_sock, @@ -1353,7 +1353,7 @@ class Socks5SenderServer(Socks5Server, Socks5Sender): def __init__(self, idlequeue, sock_hash, parent,_sock, host=None, port=None, fingerprint = None, connected=True, file_props={}): - Socks5.__init__(self, idlequeue, host, port, None, None, + Socks5Server.__init__(self, idlequeue, host, port, None, None, file_props['sid']) Socks5Sender.__init__(self,idlequeue, sock_hash, parent, _sock, @@ -1365,7 +1365,7 @@ class Socks5ReceiverClient(Socks5Client, Socks5Receiver): def __init__(self, idlequeue, streamhost, sid, file_props = None, fingerprint=None): - Socks5.__init__(self, idlequeue, streamhost['host'], + Socks5Client.__init__(self, idlequeue, streamhost['host'], int(streamhost['port']), streamhost['initiator'], streamhost['target'], sid) @@ -1380,7 +1380,7 @@ class Socks5ReceiverServer(Socks5Server, Socks5Receiver): def __init__(self, idlequeue, streamhost, sid, file_props = None, fingerprint=None): - Socks5.__init__(self, idlequeue, streamhost['host'], + Socks5Server.__init__(self, idlequeue, streamhost['host'], int(streamhost['port']), streamhost['initiator'], streamhost['target'], sid) From 952ea6a9b6163ad164ed2419c337f653e1464575 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 24 Dec 2011 18:12:05 -0500 Subject: [PATCH 096/121] fix closing listening port --- src/common/socks5.py | 46 +++++++++++++++++++++++++++++--------------- src/gui_interface.py | 5 +++-- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index 47fe1b6d3..bed4df2a1 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -93,19 +93,19 @@ class SocksQueue: sid = fp['sid'] self.type = type # It says whether we are sending or receiving self.sha_handlers[sha_str] = (sha_handler, sid) - if self.listener is None: - self.listener = Socks5Listener(self.idlequeue, port, fp, + self.listener = Socks5Listener(self.idlequeue, port, fp, fingerprint=fingerprint) - self.listener.queue = self - self.listener.bind() - if self.listener.started is False: - self.listener = None - # We cannot bind port, call error callback and fail - self.error_cb(_('Unable to bind to port %s.') % port, + self.listener.queue = self + self.listener.bind() + if self.listener.started is False: + self.listener = None + # We cannot bind port, call error callback and fail + self.error_cb(_('Unable to bind to port %s.') % port, _('Maybe you have another running instance of Gajim. File ' 'Transfer will be cancelled.')) - return None - self.connected += 1 + return None + + self.connected += 1 return self.listener def send_success_reply(self, file_props, streamhost): @@ -311,6 +311,12 @@ class SocksQueue: def send_file(self, file_props, account, mode): for key in self.senders.keys(): + if self.senders == {}: + # Python acts very weird with this. When there is no keys + # in the dictionary It says that it has a key. + # Maybe it is my machine. Without this there is a KeyError + # traceback. + return if file_props['name'] in key and file_props['sid'] in key \ and self.senders[key].mode == mode: @@ -416,7 +422,7 @@ class SocksQueue: elif self.progress_transfer_cb is not None: self.progress_transfer_cb(actor.account, actor.file_props) - def remove_receiver(self, idx, do_disconnect=True): + def remove_receiver(self, idx, do_disconnect=True, remove_all=False): """ Remove reciver from the list and decrease the number of active connections with 1 @@ -429,14 +435,16 @@ class SocksQueue: self.idlequeue.remove_timeout(reader.fd) if do_disconnect: reader.disconnect() - break + if not remove_all: + break else: if reader.streamhost is not None: reader.streamhost['state'] = -1 del(self.readers[key]) - break - - def remove_sender(self, idx, do_disconnect=True): + if not remove_all: + break + + def remove_sender(self, idx, do_disconnect=True, remove_all=False): """ Remove sender from the list of senders and decrease the number of active connections with 1 @@ -447,13 +455,16 @@ class SocksQueue: sender = self.senders[key] if do_disconnect: sender.disconnect() - return + if not remove_all: + break else: self.idlequeue.unplug_idle(sender.fd) self.idlequeue.remove_timeout(sender.fd) del(self.senders[key]) if self.connected > 0: self.connected -= 1 + if not remove_all: + break if len(self.senders) == 0 and self.listener is not None: self.listener.disconnect() self.listener = None @@ -483,6 +494,7 @@ class Socks5: self.file = None self.connected = False self.type = '' + self.mode = '' def _is_connected(self): @@ -796,6 +808,8 @@ class Socks5: self.close_file() self.idlequeue.remove_timeout(self.fd) self.idlequeue.unplug_idle(self.fd) + if self.mode == 'server': + self.queue.listener.disconnect() try: self._sock.shutdown(socket.SHUT_RDWR) self._sock.close() diff --git a/src/gui_interface.py b/src/gui_interface.py index 080ec6297..9d255e5e4 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -918,9 +918,10 @@ class Interface: if file_props['type'] == 'r': # we receive a file jid = unicode(file_props['sender']) + gajim.socks5queue.remove_receiver(file_props['sid'], True, True) else: # we send a file jid = unicode(file_props['receiver']) - + gajim.socks5queue.remove_sender(file_props['sid'], True, True) # End jingle session if file_props.get('session-type') == 'jingle' and file_props['type'] ==\ 'r': @@ -957,7 +958,7 @@ class Interface: elif file_props['error'] in (-1, -6): msg_type = 'file-stopped' event_type = _('File Transfer Stopped') - + if event_type == '': # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) # this should never happen but it does. see process_result() in From b85f96b9290b3875a7bff2973f07a0fbe7c745ff Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 26 Dec 2011 21:18:45 -0500 Subject: [PATCH 097/121] fix sending multiple files at the same time --- src/common/jingle_ft.py | 5 ++++- src/common/jingle_session.py | 10 ++++++--- src/common/socks5.py | 42 ++++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index b8b06c235..f2862640a 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -372,7 +372,10 @@ class JingleFileTransfer(JingleContent): def start_transfer(self): - + + # We start the transfer, session negociation is over. + self.session.state = 0 # STATE 0 SESSION_ENDED + self.state = STATE_TRANSFERING # It tells wether we start the transfer as client or server diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 71b8b6d58..f8ada4fdc 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -243,10 +243,15 @@ class JingleSession(object): elif content and self.weinitiate: self.__content_accept(content) elif self.state == JingleStates.active: - # We can either send a content-add or a content-accept + # We can either send a content-add or a content-accept. However, if + # we are sending a file we can only use session_initiate. if not content: return - if (content.creator == 'initiator') == self.weinitiate: + we_created_content = (content.creator == 'initiator') \ + == self.weinitiate + if we_created_content and content.media == 'file': + self.__session_initiate() + if we_created_content: # We initiated this content. It's a pending content-add. self.__content_add(content) else: @@ -694,7 +699,6 @@ class JingleSession(object): self.connection.connection.send(stanza) def _session_terminate(self, reason=None): - assert self.state != JingleStates.ended stanza, jingle = self.__make_jingle('session-terminate', reason=reason) self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent') if self.connection.connection and self.connection.connected >= 2: diff --git a/src/common/socks5.py b/src/common/socks5.py index bed4df2a1..992f84072 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -93,17 +93,22 @@ class SocksQueue: sid = fp['sid'] self.type = type # It says whether we are sending or receiving self.sha_handlers[sha_str] = (sha_handler, sid) - self.listener = Socks5Listener(self.idlequeue, port, fp, - fingerprint=fingerprint) - self.listener.queue = self - self.listener.bind() - if self.listener.started is False: - self.listener = None - # We cannot bind port, call error callback and fail - self.error_cb(_('Unable to bind to port %s.') % port, - _('Maybe you have another running instance of Gajim. File ' - 'Transfer will be cancelled.')) - return None + if self.listener is None or self.listener.connections == []: + self.listener = Socks5Listener(self.idlequeue, port, fp, + fingerprint=fingerprint) + self.listener.queue = self + self.listener.bind() + if self.listener.started is False: + self.listener = None + # We cannot bind port, call error callback and fail + self.error_cb(_('Unable to bind to port %s.') % port, + _('Maybe you have another running instance of Gajim. File ' + 'Transfer will be cancelled.')) + return None + else: + # There is already a listener, we update the file's information + # on the new connection. + self.listener.file_props = fp self.connected += 1 return self.listener @@ -724,6 +729,7 @@ class Socks5: """ Read file contents from socket and write them to file """ + if self.file_props is None or ('file-name' in self.file_props) is False: self.file_props['error'] = -2 return None @@ -773,14 +779,14 @@ class Socks5: # Transfer stopped somehow: # reset, paused or network error self.rem_fd(fd) - self.disconnect(False) + self.disconnect() self.file_props['error'] = -1 return 0 try: fd.write(buff) except IOError, e: self.rem_fd(fd) - self.disconnect(False) + self.disconnect() self.file_props['error'] = -6 # file system error return 0 if self.file_props['received-len'] >= int(self.file_props['size']): @@ -809,7 +815,12 @@ class Socks5: self.idlequeue.remove_timeout(self.fd) self.idlequeue.unplug_idle(self.fd) if self.mode == 'server': - self.queue.listener.disconnect() + try: + self.queue.listener.connections.remove(self._sock) + except ValueError: + pass # Not in list + if self.queue.listener.connections == []: + self.queue.listener.disconnect() try: self._sock.shutdown(socket.SHUT_RDWR) self._sock.close() @@ -1127,6 +1138,7 @@ class Socks5Server(Socks5): def pollin(self): + self.idlequeue.remove_timeout(self.fd) if self.connected: try: if self.state < 5: @@ -1426,6 +1438,7 @@ class Socks5Listener(IdleObject): self.fd = -1 self.fingerprint = fingerprint self.file_props = fp + self.connections = [] def bind(self): for ai in self.ais: @@ -1501,6 +1514,7 @@ class Socks5Listener(IdleObject): """ _sock = self._serv.accept() _sock[0].setblocking(False) + self.connections.append(_sock[0]) return _sock From c491bd3f59a1bfa442644b722abe3386cfb8f038 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Tue, 27 Dec 2011 23:47:00 -0500 Subject: [PATCH 098/121] create a new session for every new file transfer; enable OpenSSL --- src/common/jingle.py | 22 +++++++--------------- src/common/jingle_ft.py | 3 --- src/common/jingle_session.py | 29 ++++++++++++++++++----------- src/common/socks5.py | 12 ++++++------ 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 5c11b70ac..b8238a4b3 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -140,26 +140,18 @@ class ConnectionJingle(object): def start_file_transfer(self, jid, file_props): logger.info("start file transfer with file: %s" % file_props) - jingle = self.get_jingle_session(jid, media='file') contact = gajim.contacts.get_contact_with_highest_priority(self.name, gajim.get_jid_without_resource(jid)) if contact is None: return use_security = contact.supports(xmpp.NS_JINGLE_XTLS) - if jingle: - file_props['sid'] = jingle.sid - c = JingleFileTransfer(jingle, file_props=file_props, - use_security=use_security) - jingle.add_content('file' + helpers.get_random_string_16(), c) - jingle.on_session_state_changed(c) - else: - jingle = JingleSession(self, weinitiate=True, jid=jid) - self._sessions[jingle.sid] = jingle - file_props['sid'] = jingle.sid - c = JingleFileTransfer(jingle, file_props=file_props, - use_security=use_security) - jingle.add_content('file' + helpers.get_random_string_16(), c) - jingle.start_session() + jingle = JingleSession(self, weinitiate=True, jid=jid) + self._sessions[jingle.sid] = jingle + file_props['sid'] = jingle.sid + c = JingleFileTransfer(jingle, file_props=file_props, + use_security=use_security) + jingle.add_content('file' + helpers.get_random_string_16(), c) + jingle.start_session() return c.transport.sid diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index f2862640a..794d953e9 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -373,9 +373,6 @@ class JingleFileTransfer(JingleContent): def start_transfer(self): - # We start the transfer, session negociation is over. - self.session.state = 0 # STATE 0 SESSION_ENDED - self.state = STATE_TRANSFERING # It tells wether we start the transfer as client or server diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index f8ada4fdc..76c0aabd9 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -488,19 +488,25 @@ class JingleSession(object): # subscription) and the receiver has a policy of not communicating via # Jingle with unknown entities, it SHOULD return a # error. - - # Check if there's already a session with this user: - for session in self.connection.iter_jingle_sessions(self.peerjid): - if not session is self: - reason = xmpp.Node('reason') - alternative_session = reason.setTag('alternative-session') - alternative_session.setTagData('sid', session.sid) - self.__ack(stanza, jingle, error, action) - self._session_terminate(reason) - raise xmpp.NodeProcessed - + + # Lets check what kind of jingle session does the peer want contents, contents_rejected, reason_txt = self.__parse_contents(jingle) + + + # If we are not receivin a file + # Check if there's already a session with this user: + if contents[0][0] != 'file': + for session in self.connection.iter_jingle_sessions(self.peerjid): + if not session is self: + reason = xmpp.Node('reason') + alternative_session = reason.setTag('alternative-session') + alternative_session.setTagData('sid', session.sid) + self.__ack(stanza, jingle, error, action) + self._session_terminate(reason) + raise xmpp.NodeProcessed + + # If there's no content we understand... if not contents: @@ -699,6 +705,7 @@ class JingleSession(object): self.connection.connection.send(stanza) def _session_terminate(self, reason=None): + assert self.state != JingleStates.ended stanza, jingle = self.__make_jingle('session-terminate', reason=reason) self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent') if self.connection.connection and self.connection.connected >= 2: diff --git a/src/common/socks5.py b/src/common/socks5.py index 992f84072..2aa5f991d 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -517,10 +517,10 @@ class Socks5: for ai in self.ais: try: self._sock = socket.socket(*ai[:3]) - ''' + if not self.fingerprint is None: self._sock = OpenSSL.SSL.Connection( - jingle_xtls.get_context('client'), self._sock)''' + jingle_xtls.get_context('client'), self._sock) # this will not block the GUI self._sock.setblocking(False) self._server = ai[4] @@ -956,12 +956,12 @@ class Socks5Sender(IdleObject): if _sock is not None: - '''if self.fingerprint is not None: + if self.fingerprint is not None: self._sock = OpenSSL.SSL.Connection( jingle_xtls.get_context('server'), _sock) else: self._sock.setblocking(False) - ''' + self.fd = _sock.fileno() self._recv = _sock.recv self._send = _sock.send @@ -1445,9 +1445,9 @@ class Socks5Listener(IdleObject): # try the different possibilities (ipv6, ipv4, etc.) try: self._serv = socket.socket(*ai[:3]) - '''if self.fingerprint is not None: + if self.fingerprint is not None: self._serv = OpenSSL.SSL.Connection( - jingle_xtls.get_context('server'), self._serv)''' + jingle_xtls.get_context('server'), self._serv) except socket.error, e: if e.args[0] == EAFNOSUPPORT: self.ai = None From 41b7bd7b415c1272911d400fe93f25e7c0638a72 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 29 Dec 2011 14:08:35 +0100 Subject: [PATCH 099/121] fix sending jingle ack --- src/common/jingle_ft.py | 4 ++++ src/common/jingle_session.py | 1 + 2 files changed, 5 insertions(+) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 794d953e9..02e5193c0 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -116,6 +116,7 @@ class JingleFileTransfer(JingleContent): if self.state == STATE_TRANSPORT_REPLACE: # We ack the session accept response = stanza.buildReply('result') + response.delChild(response.getQuery()) con.connection.send(response) # We send the file con.files_props[self.file_props['sid']] = self.file_props @@ -131,6 +132,7 @@ class JingleFileTransfer(JingleContent): host['sid'] = self.file_props['sid'] response = stanza.buildReply('result') + response.delChild(response.getQuery()) con.connection.send(response) if not gajim.socks5queue.get_file_props( @@ -174,6 +176,7 @@ class JingleFileTransfer(JingleContent): self.session.transport_replace() else: response = stanza.buildReply('result') + response.delChild(response.getQuery()) self.session.connection.connection.send(response) self.start_transfer() raise xmpp.NodeProcessed @@ -203,6 +206,7 @@ class JingleFileTransfer(JingleContent): self.nominated_cand['peer-cand'] = streamhost_used if self.state == STATE_CAND_SENT_PENDING_REPLY: response = stanza.buildReply('result') + response.delChild(response.getQuery()) self.session.connection.connection.send(response) self.start_transfer() raise xmpp.NodeProcessed diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 5eaffd5dd..4ade92a0a 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -344,6 +344,7 @@ class JingleSession(object): Default callback for action stanzas -- simple ack and stop processing """ response = stanza.buildReply('result') + response.delChild(response.getQuery()) self.connection.connection.send(response) def __on_error(self, stanza, jingle, error, action): From f3b09203d6769f4f7a92c450db6fe55df9dd42f5 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 29 Dec 2011 17:22:51 -0500 Subject: [PATCH 100/121] dont use content-remove for FT use session-terminate --- src/gui_interface.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/gui_interface.py b/src/gui_interface.py index 09618b6f9..3879cceaf 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -927,19 +927,10 @@ class Interface: jid = unicode(file_props['receiver']) gajim.socks5queue.remove_sender(file_props['sid'], True, True) # End jingle session - if file_props.get('session-type') == 'jingle' and file_props['type'] ==\ - 'r': - session = gajim.connections[account].get_jingle_session(jid, + session = gajim.connections[account].get_jingle_session(jid, sid=file_props['session-sid']) - # get content: - content = None - for c in session.contents.values(): - if c.file_props['sid'] == file_props['sid']: - content = c - break - if not content: - return - session.remove_content('initiator', c.name) + if session: + session.end_session() if helpers.allow_popup_window(account): if file_props['error'] == 0: From 930c71cc044d0e6cff9fcbce4282b0b76c119e86 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Fri, 6 Jan 2012 23:18:50 -0500 Subject: [PATCH 101/121] complete IBB jingle support, fixes #6998 --- src/common/jingle_ft.py | 36 ++++++++++++++++++++++++---------- src/common/jingle_transport.py | 11 ++++++----- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 02e5193c0..13a31394b 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -22,7 +22,7 @@ Handles Jingle File Transfer (XEP 0234) import gajim import xmpp from jingle_content import contents, JingleContent -from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 +from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5, JingleTransportIBB, TransportType from common import helpers from common.socks5 import Socks5ReceiverClient, Socks5SenderClient from common.connection_handlers_events import FileRequestReceivedEvent @@ -61,7 +61,7 @@ class JingleFileTransfer(JingleContent): self.callbacks['session-terminate'] += [self.__on_session_terminate] self.callbacks['transport-accept'] += [self.__on_transport_accept] self.callbacks['transport-replace'] += [self.__on_transport_replace] - self.callbacks['session-accept-sent'] += [self._listen_host] + self.callbacks['session-accept-sent'] += [self.__transport_setup] # fallback transport method self.callbacks['transport-reject'] += [self.__on_transport_reject] self.callbacks['transport-info'] += [self.__on_transport_info] @@ -119,10 +119,7 @@ class JingleFileTransfer(JingleContent): response.delChild(response.getQuery()) con.connection.send(response) # We send the file - con.files_props[self.file_props['sid']] = self.file_props - fp = open(self.file_props['file-name'], 'r') - con.OpenStream( self.transport.sid, self.session.peerjid, - fp, blocksize=4096) + self.__start_IBB_transfer(con) raise xmpp.NodeProcessed self.file_props['streamhosts'] = self.transport.remote_candidates @@ -142,11 +139,14 @@ class JingleFileTransfer(JingleContent): fingerprint = None if self.use_security: fingerprint = 'client' - gajim.socks5queue.connect_to_hosts(self.session.connection.name, + if self.transport.type == TransportType.SOCKS5: + gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], self.send_candidate_used, self._on_connect_error, fingerprint=fingerprint, receiving=False) - + elif self.transport.type == TransportType.IBB: + self.state = STATE_TRANSFERING + self.__start_IBB_transfer(self.session.connection) raise xmpp.NodeProcessed def __on_session_terminate(self, stanza, content, error, action): @@ -224,6 +224,7 @@ class JingleFileTransfer(JingleContent): self.file_props # Listen on configured port for file transfer self._listen_host() + elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result if not self.negotiated: @@ -254,6 +255,22 @@ class JingleFileTransfer(JingleContent): return # initiate transfer self.start_transfer() + + def __start_IBB_transfer(self, con): + con.files_props[self.file_props['sid']] = self.file_props + fp = open(self.file_props['file-name'], 'r') + con.OpenStream( self.transport.sid, self.session.peerjid, + fp, blocksize=4096) + + def __transport_setup(self, stanza=None, content=None, error=None + , action=None): + # Sets up a few transport specific things for the file transfer + if self.transport.type == TransportType.SOCKS5: + self._listen_host() + + if self.transport.type == TransportType.IBB: + self.state = STATE_TRANSFERING + def send_candidate_used(self, streamhost): """ @@ -328,8 +345,7 @@ class JingleFileTransfer(JingleContent): # callback from socsk5queue.start_listener self.file_props['hash'] = hash_id - def _listen_host(self, stanza=None, content=None, error=None - , action=None): + def _listen_host(self): receiver = self.file_props['receiver'] sender = self.file_props['sender'] diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 87d2a3026..f41c85042 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -36,8 +36,9 @@ class TransportType(object): """ Possible types of a JingleTransport """ - datagram = 1 - streaming = 2 + ICEUDP = 1 + SOCKS5 = 2 + IBB = 3 class JingleTransport(object): @@ -98,7 +99,7 @@ class JingleTransportSocks5(JingleTransport): Note: Don't forget to call set_file_props after initialization """ def __init__(self, node=None): - JingleTransport.__init__(self, TransportType.streaming) + JingleTransport.__init__(self, TransportType.SOCKS5) self.connection = None self.remote_candidates = [] self.sid = None @@ -300,7 +301,7 @@ class JingleTransportIBB(JingleTransport): def __init__(self, node=None, block_sz=None): - JingleTransport.__init__(self, TransportType.streaming) + JingleTransport.__init__(self, TransportType.IBB) if block_sz: self.block_sz = block_sz @@ -328,7 +329,7 @@ except Exception: class JingleTransportICEUDP(JingleTransport): def __init__(self, node): - JingleTransport.__init__(self, TransportType.datagram) + JingleTransport.__init__(self, TransportType.ICEUDP) def make_candidate(self, candidate): types = {farsight.CANDIDATE_TYPE_HOST: 'host', From 5017c7e17c906073341656fd518b54bdc6c3fca6 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 7 Jan 2012 14:38:59 -0500 Subject: [PATCH 102/121] fixes ibb transfer --- src/common/protocol/bytestream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index bea8b1048..d95409edd 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -780,7 +780,7 @@ class ConnectionIBBytestream(ConnectionBytestream): elif typ == 'set' and stanza.getTag('close', namespace=xmpp.NS_IBB): self.StreamCloseHandler(conn, stanza) elif typ == 'result': - self.StreamCommitHandler(conn, stanza) + self.SendHandler() elif typ == 'error': self.StreamOpenReplyHandler(conn, stanza) else: From 76bc866a98f79bd44a449a29c6184176e8a34d76 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 7 Jan 2012 20:49:07 -0500 Subject: [PATCH 103/121] handle errors in IBB --- src/common/protocol/bytestream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index d95409edd..9c83ddea1 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -782,7 +782,7 @@ class ConnectionIBBytestream(ConnectionBytestream): elif typ == 'result': self.SendHandler() elif typ == 'error': - self.StreamOpenReplyHandler(conn, stanza) + gajim.socks5queue.error_cb() else: conn.send(xmpp.Error(stanza, xmpp.ERR_BAD_REQUEST)) raise xmpp.NodeProcessed From 61a039d894cbeb1710093a327508045085da29ee Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 8 Jan 2012 20:29:24 +0100 Subject: [PATCH 104/121] fix sending result iq in IBB --- src/common/protocol/bytestream.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index d95409edd..a230def40 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -781,8 +781,8 @@ class ConnectionIBBytestream(ConnectionBytestream): self.StreamCloseHandler(conn, stanza) elif typ == 'result': self.SendHandler() - elif typ == 'error': - self.StreamOpenReplyHandler(conn, stanza) +# elif typ == 'error': +# self.StreamOpenReplyHandler(conn, stanza) else: conn.send(xmpp.Error(stanza, xmpp.ERR_BAD_REQUEST)) raise xmpp.NodeProcessed @@ -957,13 +957,17 @@ class ConnectionIBBytestream(ConnectionBytestream): log.debug('StreamCloseHandler called sid->%s' % sid) # look in sending files if sid in self.files_props.keys(): - conn.send(stanza.buildReply('result')) + reply = stanza.buildReply('result') + reply.delChild('close') + conn.send(reply) gajim.socks5queue.complete_transfer_cb(self.name, self.files_props[sid]) del self.files_props[sid] # look in receiving files elif gajim.socks5queue.get_file_props(self.name, sid): file_props = gajim.socks5queue.get_file_props(self.name, sid) - conn.send(stanza.buildReply('result')) + reply = stanza.buildReply('result') + reply.delChild('close') + conn.send(reply) file_props['fp'].close() gajim.socks5queue.complete_transfer_cb(self.name, file_props) gajim.socks5queue.remove_file_props(self.name, sid) @@ -1003,6 +1007,7 @@ class ConnectionIBBytestream(ConnectionBytestream): if stanza.getTag('data'): if self.IBBMessageHandler(conn, stanza): reply = stanza.buildReply('result') + reply.delChild('data') conn.send(reply) raise xmpp.NodeProcessed elif syn_id == self.last_sent_ibb_id: From 69544d5f5cac51246e3ebcf8afe5046dcea532fe Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 8 Jan 2012 20:44:15 +0100 Subject: [PATCH 105/121] coding standards --- src/common/jingle_ft.py | 76 ++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 13a31394b..20bc26203 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -22,7 +22,8 @@ Handles Jingle File Transfer (XEP 0234) import gajim import xmpp from jingle_content import contents, JingleContent -from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5, JingleTransportIBB, TransportType +from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 +from jingle_transport import JingleTransportIBB, TransportType from common import helpers from common.socks5 import Socks5ReceiverClient, Socks5SenderClient from common.connection_handlers_events import FileRequestReceivedEvent @@ -124,9 +125,9 @@ class JingleFileTransfer(JingleContent): self.file_props['streamhosts'] = self.transport.remote_candidates for host in self.file_props['streamhosts']: - host['initiator'] = self.session.initiator - host['target'] = self.session.responder - host['sid'] = self.file_props['sid'] + host['initiator'] = self.session.initiator + host['target'] = self.session.responder + host['sid'] = self.file_props['sid'] response = stanza.buildReply('result') response.delChild(response.getQuery()) @@ -135,15 +136,15 @@ class JingleFileTransfer(JingleContent): if not gajim.socks5queue.get_file_props( self.session.connection.name, self.file_props['sid']): gajim.socks5queue.add_file_props(self.session.connection.name, - self.file_props) + self.file_props) fingerprint = None if self.use_security: fingerprint = 'client' if self.transport.type == TransportType.SOCKS5: gajim.socks5queue.connect_to_hosts(self.session.connection.name, - self.file_props['sid'], self.send_candidate_used, - self._on_connect_error, fingerprint=fingerprint, - receiving=False) + self.file_props['sid'], self.send_candidate_used, + self._on_connect_error, fingerprint=fingerprint, + receiving=False) elif self.transport.type == TransportType.IBB: self.state = STATE_TRANSFERING self.__start_IBB_transfer(self.session.connection) @@ -213,8 +214,6 @@ class JingleFileTransfer(JingleContent): else: self.state = STATE_CAND_RECEIVED_PENDING_REPLY - - def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") @@ -224,7 +223,7 @@ class JingleFileTransfer(JingleContent): self.file_props # Listen on configured port for file transfer self._listen_host() - + elif not self.weinitiate and self.state == STATE_NOT_STARTED: # session-accept iq-result if not self.negotiated: @@ -255,22 +254,21 @@ class JingleFileTransfer(JingleContent): return # initiate transfer self.start_transfer() - + def __start_IBB_transfer(self, con): con.files_props[self.file_props['sid']] = self.file_props fp = open(self.file_props['file-name'], 'r') - con.OpenStream( self.transport.sid, self.session.peerjid, - fp, blocksize=4096) + con.OpenStream( self.transport.sid, self.session.peerjid, fp, + blocksize=4096) - def __transport_setup(self, stanza=None, content=None, error=None - , action=None): + def __transport_setup(self, stanza=None, content=None, error=None, + action=None): # Sets up a few transport specific things for the file transfer if self.transport.type == TransportType.SOCKS5: self._listen_host() - + if self.transport.type == TransportType.IBB: self.state = STATE_TRANSFERING - def send_candidate_used(self, streamhost): """ @@ -302,7 +300,6 @@ class JingleFileTransfer(JingleContent): self.session.send_transport_info(content) - def _on_connect_error(self, sid): self.nominated_cand['our-cand'] = False self.send_error_candidate() @@ -312,7 +309,6 @@ class JingleFileTransfer(JingleContent): else: self.state = STATE_CAND_SENT_PENDING_REPLY - log.info('connect error, sid=' + sid) def _fill_content(self, content): @@ -346,11 +342,10 @@ class JingleFileTransfer(JingleContent): self.file_props['hash'] = hash_id def _listen_host(self): - receiver = self.file_props['receiver'] sender = self.file_props['sender'] sha_str = helpers.get_auth_sha(self.file_props['sid'], sender, - receiver) + receiver) self.file_props['sha_str'] = sha_str port = gajim.config.get('file_transfers_port') @@ -361,22 +356,22 @@ class JingleFileTransfer(JingleContent): if self.weinitiate: listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props, - fingerprint=fingerprint, type='sender') + self._store_socks5_sid, self.file_props, + fingerprint=fingerprint, type='sender') else: listener = gajim.socks5queue.start_listener(port, sha_str, - self._store_socks5_sid, self.file_props, - fingerprint=fingerprint, type='receiver') + self._store_socks5_sid, self.file_props, + fingerprint=fingerprint, type='receiver') if not listener: - # send error message, notify the user + # send error message, notify the user return + def isOurCandUsed(self): ''' If this method returns true then the candidate we nominated will be used, if false, the candidate nominated by peer will be used ''' - if self.nominated_cand['peer-cand'] == False: return True if self.nominated_cand['our-cand'] == False: @@ -390,11 +385,9 @@ class JingleFileTransfer(JingleContent): else: return self.weinitiate - def start_transfer(self): - self.state = STATE_TRANSFERING - + # It tells wether we start the transfer as client or server mode = None @@ -404,7 +397,7 @@ class JingleFileTransfer(JingleContent): else: mode = 'server' streamhost_used = self.nominated_cand['peer-cand'] - + if streamhost_used['type'] == 'proxy': self.file_props['is_a_proxy'] = True if self.weinitiate: @@ -428,7 +421,7 @@ class JingleFileTransfer(JingleContent): s[sender].connected: return - if streamhost_used['type'] == 'proxy': + if streamhost_used['type'] == 'proxy': self.file_props['streamhost-used'] = True streamhost_used['sid'] = self.file_props['sid'] self.file_props['streamhosts'] = [] @@ -440,21 +433,18 @@ class JingleFileTransfer(JingleContent): gajim.socks5queue.idx += 1 idx = gajim.socks5queue.idx sockobj = Socks5SenderClient(gajim.idlequeue, idx, - gajim.socks5queue, - _sock=None, - host=str(streamhost_used['host']), - port=int(streamhost_used['port']), - fingerprint=None, - connected=False, - file_props=self.file_props) + gajim.socks5queue, _sock=None, + host=str(streamhost_used['host']), + port=int(streamhost_used['port']), fingerprint=None, + connected=False, file_props=self.file_props) else: sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used, sid=self.file_props['sid'], file_props=self.file_props, fingerprint=None) sockobj.proxy = True - sockobj.streamhost = streamhost_used - gajim.socks5queue.add_sockobj(self.session.connection.name, - sockobj, 'sender') + sockobj.streamhost = streamhost_used + gajim.socks5queue.add_sockobj(self.session.connection.name, + sockobj, 'sender') streamhost_used['idx'] = sockobj.queue_idx # If we offered the nominated candidate used, we activate # the proxy From 8bbca8a0a7f781ecf05cdc11f2a40a3c2aa4d5a5 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 8 Jan 2012 20:48:03 +0100 Subject: [PATCH 106/121] coding standards --- src/common/jingle.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 6a7519e00..028642158 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -78,11 +78,11 @@ class ConnectionJingle(object): """ # get data jid = helpers.get_full_jid_from_iq(stanza) - id = stanza.getID() + id_ = stanza.getID() - if (jid, id) in self.__iq_responses.keys(): - self.__iq_responses[(jid, id)].on_stanza(stanza) - del self.__iq_responses[(jid, id)] + if (jid, id_) in self.__iq_responses.keys(): + self.__iq_responses[(jid, id_)].on_stanza(stanza) + del self.__iq_responses[(jid, id_)] raise xmpp.NodeProcessed jingle = stanza.getTag('jingle') @@ -93,7 +93,7 @@ class ConnectionJingle(object): else: sid = None for sesn in self._sessions.values(): - if id in sesn.iq_ids: + if id_ in sesn.iq_ids: sesn.on_stanza(stanza) return @@ -101,14 +101,15 @@ class ConnectionJingle(object): if sid not in self._sessions: #TODO: tie-breaking and other things... newjingle = JingleSession(con=self, weinitiate=False, jid=jid, - iq_id=id, sid=sid) + iq_id=id_, sid=sid) self._sessions[sid] = newjingle # we already have such session in dispatcher... - self._sessions[sid].collect_iq_id(id) + self._sessions[sid].collect_iq_id(id_) self._sessions[sid].on_stanza(stanza) # Delete invalid/unneeded sessions - if sid in self._sessions and self._sessions[sid].state == JingleStates.ended: + if sid in self._sessions and \ + self._sessions[sid].state == JingleStates.ended: self.delete_jingle_session(sid) raise xmpp.NodeProcessed @@ -142,7 +143,7 @@ class ConnectionJingle(object): def start_file_transfer(self, jid, file_props): logger.info("start file transfer with file: %s" % file_props) contact = gajim.contacts.get_contact_with_highest_priority(self.name, - gajim.get_jid_without_resource(jid)) + gajim.get_jid_without_resource(jid)) if contact is None: return use_security = contact.supports(xmpp.NS_JINGLE_XTLS) @@ -150,25 +151,26 @@ class ConnectionJingle(object): self._sessions[jingle.sid] = jingle file_props['sid'] = jingle.sid c = JingleFileTransfer(jingle, file_props=file_props, - use_security=use_security) + use_security=use_security) jingle.add_content('file' + helpers.get_random_string_16(), c) jingle.start_session() return c.transport.sid - def iter_jingle_sessions(self, jid, sid=None, media=None): if sid: - return (session for session in self._sessions.values() if session.sid == sid) - sessions = (session for session in self._sessions.values() if session.peerjid == jid) + return (session for session in self._sessions.values() if \ + session.sid == sid) + sessions = (session for session in self._sessions.values() if \ + session.peerjid == jid) if media: if media not in ('audio', 'video', 'file'): return tuple() else: - return (session for session in sessions if session.get_content(media)) + return (session for session in sessions if \ + session.get_content(media)) else: return sessions - def get_jingle_session(self, jid, sid=None, media=None): if sid: if sid in self._sessions: From 07cb125303b0d805565aae6c8b881db101358432 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 8 Jan 2012 20:57:05 +0100 Subject: [PATCH 107/121] send file using IBB directly if receiver don't support socks5 --- src/common/jingle.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 028642158..580552165 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -38,6 +38,7 @@ from jingle_session import JingleSession, JingleStates if gajim.HAVE_FARSIGHT: from jingle_rtp import JingleAudio, JingleVideo from jingle_ft import JingleFileTransfer +from jingle_transport import JingleTransportSocks5, JingleTransportIBB import logging logger = logging.getLogger('gajim.c.jingle') @@ -150,8 +151,12 @@ class ConnectionJingle(object): jingle = JingleSession(self, weinitiate=True, jid=jid) self._sessions[jingle.sid] = jingle file_props['sid'] = jingle.sid - c = JingleFileTransfer(jingle, file_props=file_props, - use_security=use_security) + if contact.supports(xmpp.NS_JINGLE_BYTESTREAM): + transport = JingleTransportSocks5() + elif contact.supports(xmpp.NS_JINGLE_IBB): + transport = JingleTransportIBB() + c = JingleFileTransfer(jingle, transport=transport, + file_props=file_props, use_security=use_security) jingle.add_content('file' + helpers.get_random_string_16(), c) jingle.start_session() return c.transport.sid From 376777091e7cdc9d021595481f517ae182cae6c5 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sun, 15 Jan 2012 19:37:00 -0500 Subject: [PATCH 108/121] support for xep-300 --- src/common/gajim.py | 4 +- src/common/jingle.py | 14 ++++++ src/common/jingle_ft.py | 39 +++++++++++++--- src/common/jingle_session.py | 27 +++++++++-- src/common/xmpp/protocol.py | 91 ++++++++++++++++++++++++++++++++++-- 5 files changed, 161 insertions(+), 14 deletions(-) diff --git a/src/common/gajim.py b/src/common/gajim.py index 83ae590f7..6face0fbf 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -208,7 +208,9 @@ gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE, 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog', 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN, - xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL] + xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL, + xmpp.NS_HASHES, xmpp.NS_HASHES_MD5, xmpp.NS_HASHES_SHA1, + xmpp.NS_HASHES_SHA256, xmpp.NS_HASHES_SHA512] # Optional features gajim supports per account gajim_optional_features = {} diff --git a/src/common/jingle.py b/src/common/jingle.py index 6a7519e00..3f3c8f726 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -151,10 +151,24 @@ class ConnectionJingle(object): file_props['sid'] = jingle.sid c = JingleFileTransfer(jingle, file_props=file_props, use_security=use_security) + c.hash_algo = self.__hash_support(contact) jingle.add_content('file' + helpers.get_random_string_16(), c) jingle.start_session() return c.transport.sid + def __hash_support(self, contact): + + if contact.supports(xmpp.NS_HASHES): + if contact.supports(xmpp.NS_HASHES_MD5): + return 'md5' + elif contact.supports(xmpp.NS_HASHES_SHA1): + return 'sha-1' + elif contact.supports(xmpp.NS_HASHES_SHA256): + return 'sha-256' + elif contact.supports(xmpp.NS_HASHES_SHA512): + return 'sha-512' + + return None def iter_jingle_sessions(self, jid, sid=None, media=None): if sid: diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 13a31394b..f2fa6893e 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -26,7 +26,7 @@ from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5, Jingl from common import helpers from common.socks5 import Socks5ReceiverClient, Socks5SenderClient from common.connection_handlers_events import FileRequestReceivedEvent - +import threading import logging log = logging.getLogger('gajim.c.jingle_ft') @@ -56,9 +56,11 @@ class JingleFileTransfer(JingleContent): # events we might be interested in self.callbacks['session-initiate'] += [self.__on_session_initiate] + self.callbacks['session-initiate-sent'] += [self.__on_session_initiate_sent] self.callbacks['content-add'] += [self.__on_session_initiate] self.callbacks['session-accept'] += [self.__on_session_accept] - self.callbacks['session-terminate'] += [self.__on_session_terminate] + self.callbacks['session-terminate'] += [self.__on_session_terminate] + self.callbacks['session-info'] += [self.__on_session_info] self.callbacks['transport-accept'] += [self.__on_transport_accept] self.callbacks['transport-replace'] += [self.__on_transport_replace] self.callbacks['session-accept-sent'] += [self.__transport_setup] @@ -99,12 +101,36 @@ class JingleFileTransfer(JingleContent): self.session = session self.media = 'file' self.nominated_cand = {} + + # Hash algorithm that we are using to calculate the integrity of the + # file. Could be 'md5', 'sha-1', etc... + self.hash_algo = None def __on_session_initiate(self, stanza, content, error, action): gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self.session.connection, stanza=stanza, jingle_content=content, FT_content=self)) - + def __on_session_initiate_sent(self, stanza, content, error, action): + # Calculate file_hash in a new thread + self.hashThread = threading.Thread(target=self.__calcHash) + self.hashThread.start() + + def __calcHash(self): + if self.hash_algo == None: + return + try: + file = open(self.file_props['file-name'], 'r') + except: + return + h = xmpp.Hashes() + h.calculateHash(self.hash_algo, file) + checksum = xmpp.Node(tag='checksum', + payload=[xmpp.Node(tag='file', payload=[h])]) + checksum.setNamespace(xmpp.NS_JINGLE_FILE_TRANSFER) + # Send hash in a session info + self.session.__session_info(checksum ) + + def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") con = self.session.connection @@ -152,6 +178,9 @@ class JingleFileTransfer(JingleContent): def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") + def __on_session_info(self, stanza, content, error, action): + pass + def __on_transport_accept(self, stanza, content, error, action): log.info("__on_transport_accept") @@ -164,8 +193,6 @@ class JingleFileTransfer(JingleContent): def __on_transport_info(self, stanza, content, error, action): log.info("__on_transport_info") - #if not self.weinitiate: # proxy activated from initiator - # return if content.getTag('transport').getTag('candidate-error'): self.nominated_cand['peer-cand'] = False if self.state == STATE_CAND_SENT_PENDING_REPLY: @@ -392,7 +419,7 @@ class JingleFileTransfer(JingleContent): def start_transfer(self): - + self.state = STATE_TRANSFERING # It tells wether we start the transfer as client or server diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 4ade92a0a..4d5a184b3 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -98,7 +98,7 @@ class JingleSession(object): self.accepted = True # is this session accepted by user - + self.file_hash = None # callbacks to call on proper contents # use .prepend() to add new callbacks, especially when you're going # to send error instead of ack @@ -126,6 +126,7 @@ class JingleSession(object): 'iq-result': [self.__broadcast], 'iq-error': [self.__on_error], } + def collect_iq_id(self, iq_id): if iq_id is not None: @@ -416,9 +417,20 @@ class JingleSession(object): def __on_session_info(self, stanza, jingle, error, action): # TODO: ringing, active, (un)hold, (un)mute payload = jingle.getPayload() - if payload: - self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify') - raise xmpp.NodeProcessed + for p in payload: + if p.getName() == 'checksum': + hashes = p.getTag('file').getTag(name='hashes', + namespace=xmpp.NS_HASHES) + for hash in hashes.getChildren(): + algo = hash.getAttr('algo') + if algo in xmpp.Hashes.supported: + data = hash.getData() + self.file_hash = data + print data + raise xmpp.NodeProcessed + self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify') + raise xmpp.NodeProcessed + def __on_content_remove(self, stanza, jingle, error, action): for content in jingle.iterTags('content'): @@ -704,6 +716,13 @@ class JingleSession(object): if payload: jingle.addChild(node=payload) self.connection.connection.send(stanza) + + def _JingleFileTransfer__session_info(self, p): + # For some strange reason when I call + # self.session.__session_info(h) from the jingleFileTransfer object + # within a thread, this method gets called instead. Even though, it + # isn't being called explicitly. + self.__session_info(p) def _session_terminate(self, reason=None): assert self.state != JingleStates.ended diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 87bc44ec6..82b16a9c0 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -23,6 +23,7 @@ sub- stanzas) handling routines from simplexml import Node, NodeBuilder import time import string +import hashlib def ascii_upper(s): trans_table = string.maketrans(string.ascii_lowercase, @@ -89,7 +90,7 @@ NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166 NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167 NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167 NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167 -NS_JINGLE_FILE_TRANSFER='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234 +NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234 NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 @@ -160,7 +161,12 @@ NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2' NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2' NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2' NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198 - +NS_HASHES = 'urn:xmpp:hashes:0' # XEP-300 +NS_HASHES_MD5 = 'urn:xmpp:hash-function-textual-names:md5' +NS_HASHES_SHA1 = 'urn:xmpp:hash-function-textual-names:sha-1' +NS_HASHES_SHA256 = 'urn:xmpp:hash-function-textual-names:sha-256' +NS_HASHES_SHA512 = 'urn:xmpp:hash-function-textual-names:sha-512' + xmpp_stream_error_conditions = ''' bad-format -- -- -- The entity has sent XML that cannot be processed. bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix. @@ -1030,7 +1036,85 @@ class Iq(Protocol): attrs={'id': self.getID()}) iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS()) return iq - + +class Hashes(Node): + """ + Hash elements for various XEPs as defined in XEP-300 + """ + + """ + RECOMENDED HASH USE: + Algorithm Support + MD2 MUST NOT + MD4 MUST NOT + MD5 MAY + SHA-1 MUST + SHA-256 MUST + SHA-512 SHOULD + """ + + supported = ('md5', 'sha-1', 'sha-256', 'sha-512') + + def __init__(self, nsp=NS_HASHES): + Node.__init__(self, None, {}, [], None, None,False, None) + self.setNamespace(nsp) + self.setName('hashes') + + def calculateHash(self, algo, file_string): + """ + Calculate the hash and add it. It is preferable doing it here + instead of doing it all over the place in Gajim. + """ + hl = None + hash = None + + # file_string can be a string or a file + if type(file_string) == str: # if it is a string + if algo == 'md5': + hl = hashlib.md5() + elif algo == 'sha-1': + hl = hashlib.sha1() + elif algo == 'sha-256': + hl = hashlib.sha256() + elif algo == 'sha-512': + hl = hashlib.sha512() + + if hl == None: + # Raise exception + raise Exception('Hash algorithm not supported') + else: + hl.update(file_string) + hash = hl.hexdigest() + else: # if it is a file + + if algo == 'md5': + hl = hashlib.md5() + elif algo == 'sha-1': + hl = hashlib.sha1() + elif algo == 'sha-256': + hl = hashlib.sha256() + elif algo == 'sha-512': + hl = hashlib.sha512() + + if hl == None: + # Raise exception + raise Exception('Hash algorithm not supported') + else: + for line in file_string: + hl.update(line) + hash = hl.hexdigest() + + self.addHash(hash, algo) + + def addHash(self, hash, algo): + """ + More than one hash can be added. Although it is permitted, it should + not be done for big files because it could slow down Gajim. + """ + attrs = {} + attrs['algo'] = algo + self.addChild('hash', attrs, [hash]) + class Acks(Node): """ Acknowledgement elements for Stream Management @@ -1413,3 +1497,4 @@ class DataForm(Node): Simple dictionary interface for setting datafields values by their names """ return self.setField(name).setValue(val) + From 5142ebd626572c769cf36490436933a75780ecc3 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 23 Jan 2012 22:28:07 -0500 Subject: [PATCH 109/121] check file integrity when transfer is completed --- src/common/jingle.py | 2 +- src/common/jingle_ft.py | 10 +++++----- src/common/jingle_session.py | 7 ++++++- src/common/xmpp/protocol.py | 3 +-- src/gui_interface.py | 28 ++++++++++++++++++++++------ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index f4de9eb7a..d4e5b6ada 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -157,7 +157,7 @@ class ConnectionJingle(object): transport = JingleTransportIBB() c = JingleFileTransfer(jingle, transport=transport, file_props=file_props, use_security=use_security) - c.hash_algo = self.__hash_support(contact) + jingle.hash_algo = self.__hash_support(contact) jingle.add_content('file' + helpers.get_random_string_16(), c) jingle.start_session() return c.transport.sid diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 59ea6d256..c5c7c8af6 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -103,9 +103,7 @@ class JingleFileTransfer(JingleContent): self.media = 'file' self.nominated_cand = {} - # Hash algorithm that we are using to calculate the integrity of the - # file. Could be 'md5', 'sha-1', etc... - self.hash_algo = None + def __on_session_initiate(self, stanza, content, error, action): gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, @@ -117,14 +115,16 @@ class JingleFileTransfer(JingleContent): self.hashThread.start() def __calcHash(self): - if self.hash_algo == None: + if self.session.hash_algo == None: return try: file = open(self.file_props['file-name'], 'r') except: return h = xmpp.Hashes() - h.calculateHash(self.hash_algo, file) + hash = h.calculateHash(self.session.hash_algo, file) + self.file_props['hash'] = hash + h.addHash(hash, self.session.hash_algo) checksum = xmpp.Node(tag='checksum', payload=[xmpp.Node(tag='file', payload=[h])]) checksum.setNamespace(xmpp.NS_JINGLE_FILE_TRANSFER) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 4d5a184b3..14c9080fd 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -98,6 +98,9 @@ class JingleSession(object): self.accepted = True # is this session accepted by user + # Hash algorithm that we are using to calculate the integrity of the + # file. Could be 'md5', 'sha-1', etc... + self.hash_algo = None self.file_hash = None # callbacks to call on proper contents # use .prepend() to add new callbacks, especially when you're going @@ -424,9 +427,11 @@ class JingleSession(object): for hash in hashes.getChildren(): algo = hash.getAttr('algo') if algo in xmpp.Hashes.supported: + self.hash_algo = algo data = hash.getData() + # This only works because there is only one session + # per file in jingleFT self.file_hash = data - print data raise xmpp.NodeProcessed self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify') raise xmpp.NodeProcessed diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 82b16a9c0..c2c00f9d4 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -1067,7 +1067,6 @@ class Hashes(Node): """ hl = None hash = None - # file_string can be a string or a file if type(file_string) == str: # if it is a string if algo == 'md5': @@ -1104,7 +1103,7 @@ class Hashes(Node): hl.update(line) hash = hl.hexdigest() - self.addHash(hash, algo) + return hash def addHash(self, hash, algo): """ diff --git a/src/gui_interface.py b/src/gui_interface.py index 3879cceaf..3fe7b5f30 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -71,6 +71,7 @@ from session import ChatControlSession import common.sleepy from common.xmpp import idlequeue +from common.xmpp import Hashes from common.zeroconf import connection_zeroconf from common import resolver from common import caps_cache @@ -908,6 +909,22 @@ class Interface: self.last_ftwindow_update = time.time() self.instances['file_transfers'].set_progress(file_props['type'], file_props['sid'], file_props['received-len']) + def __compare_hashes(self, account, file_props): + session = gajim.connections[account].get_jingle_session(jid=None, + sid=file_props['session-sid']) + h = Hashes() + try: + file = open(file_props['file-name'], 'r') + except: + return + hash = h.calculateHash(session.hash_algo, file) + # If the hash we received and the hash of the file are the same, + # then the file is not corrupt + if session.file_hash == hash: + print "they are te same" + # End jingle session + if session: + session.end_session() def handle_event_file_rcv_completed(self, account, file_props): ft = self.instances['file_transfers'] @@ -919,18 +936,17 @@ class Interface: if 'stalled' in file_props and file_props['stalled'] or \ 'paused' in file_props and file_props['paused']: return - + if file_props['type'] == 'r': # we receive a file jid = unicode(file_props['sender']) + # Compare hashes in a new thread + self.hashThread = Thread(target=self.__compare_hashes, + args=(account, file_props)) + self.hashThread.start() gajim.socks5queue.remove_receiver(file_props['sid'], True, True) else: # we send a file jid = unicode(file_props['receiver']) gajim.socks5queue.remove_sender(file_props['sid'], True, True) - # End jingle session - session = gajim.connections[account].get_jingle_session(jid, - sid=file_props['session-sid']) - if session: - session.end_session() if helpers.allow_popup_window(account): if file_props['error'] == 0: From 3b5cabac5b6de9f259b83fa48c05ca4ac172ff22 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 24 Jan 2012 07:28:40 +0100 Subject: [PATCH 110/121] coding standards --- src/common/xmpp/protocol.py | 14 +++++++------- src/gui_interface.py | 22 ++++++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index c2c00f9d4..007549691 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -1066,7 +1066,7 @@ class Hashes(Node): instead of doing it all over the place in Gajim. """ hl = None - hash = None + hash_ = None # file_string can be a string or a file if type(file_string) == str: # if it is a string if algo == 'md5': @@ -1083,7 +1083,7 @@ class Hashes(Node): raise Exception('Hash algorithm not supported') else: hl.update(file_string) - hash = hl.hexdigest() + hash_ = hl.hexdigest() else: # if it is a file if algo == 'md5': @@ -1101,25 +1101,25 @@ class Hashes(Node): else: for line in file_string: hl.update(line) - hash = hl.hexdigest() + hash_ = hl.hexdigest() - return hash + return hash_ - def addHash(self, hash, algo): + def addHash(self, hash_, algo): """ More than one hash can be added. Although it is permitted, it should not be done for big files because it could slow down Gajim. """ attrs = {} attrs['algo'] = algo - self.addChild('hash', attrs, [hash]) + self.addChild('hash', attrs, [hash_]) class Acks(Node): """ Acknowledgement elements for Stream Management """ def __init__(self, nsp=NS_STREAM_MGMT): - Node.__init__(self, None, {}, [], None, None,False, None) + Node.__init__(self, None, {}, [], None, None, False, None) self.setNamespace(nsp) def buildAnswer(self, handled): diff --git a/src/gui_interface.py b/src/gui_interface.py index 3fe7b5f30..6349ff3da 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -909,18 +909,20 @@ class Interface: self.last_ftwindow_update = time.time() self.instances['file_transfers'].set_progress(file_props['type'], file_props['sid'], file_props['received-len']) + def __compare_hashes(self, account, file_props): session = gajim.connections[account].get_jingle_session(jid=None, - sid=file_props['session-sid']) + sid=file_props['session-sid']) h = Hashes() try: - file = open(file_props['file-name'], 'r') + file_ = open(file_props['file-name'], 'r') except: return - hash = h.calculateHash(session.hash_algo, file) + hash_ = h.calculateHash(session.hash_algo, file_) + file_.close() # If the hash we received and the hash of the file are the same, # then the file is not corrupt - if session.file_hash == hash: + if session.file_hash == hash_: print "they are te same" # End jingle session if session: @@ -930,18 +932,18 @@ class Interface: ft = self.instances['file_transfers'] if file_props['error'] == 0: ft.set_progress(file_props['type'], file_props['sid'], - file_props['received-len']) + file_props['received-len']) else: ft.set_status(file_props['type'], file_props['sid'], 'stop') if 'stalled' in file_props and file_props['stalled'] or \ - 'paused' in file_props and file_props['paused']: + 'paused' in file_props and file_props['paused']: return - + if file_props['type'] == 'r': # we receive a file jid = unicode(file_props['sender']) # Compare hashes in a new thread - self.hashThread = Thread(target=self.__compare_hashes, - args=(account, file_props)) + self.hashThread = Thread(target=self.__compare_hashes, + args=(account, file_props)) self.hashThread.start() gajim.socks5queue.remove_receiver(file_props['sid'], True, True) else: # we send a file @@ -969,7 +971,7 @@ class Interface: elif file_props['error'] in (-1, -6): msg_type = 'file-stopped' event_type = _('File Transfer Stopped') - + if event_type == '': # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) # this should never happen but it does. see process_result() in From c02b43f88f04a58d770e3eca059dfb40b86222cf Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Tue, 24 Jan 2012 10:04:14 -0500 Subject: [PATCH 111/121] coding standards --- src/common/jingle_ft.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index c5c7c8af6..a3dfaec31 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -122,9 +122,9 @@ class JingleFileTransfer(JingleContent): except: return h = xmpp.Hashes() - hash = h.calculateHash(self.session.hash_algo, file) - self.file_props['hash'] = hash - h.addHash(hash, self.session.hash_algo) + hash_ = h.calculateHash(self.session.hash_algo, file) + self.file_props['hash'] = hash_ + h.addHash(hash_, self.session.hash_algo) checksum = xmpp.Node(tag='checksum', payload=[xmpp.Node(tag='file', payload=[h])]) checksum.setNamespace(xmpp.NS_JINGLE_FILE_TRANSFER) From b2897e36bbbf8bf4ba5746421fd108b4f9140ad3 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Tue, 24 Jan 2012 16:51:26 -0500 Subject: [PATCH 112/121] better way to handle non supported hash algorithms --- src/common/jingle_ft.py | 8 ++++++-- src/common/xmpp/protocol.py | 10 ++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index a3dfaec31..0d7f05790 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -118,11 +118,15 @@ class JingleFileTransfer(JingleContent): if self.session.hash_algo == None: return try: - file = open(self.file_props['file-name'], 'r') + file_ = open(self.file_props['file-name'], 'r') except: + # can't open file return h = xmpp.Hashes() - hash_ = h.calculateHash(self.session.hash_algo, file) + hash_ = h.calculateHash(self.session.hash_algo, file_) + if not hash_: + # Hash alogrithm not supported + return self.file_props['hash'] = hash_ h.addHash(hash_, self.session.hash_algo) checksum = xmpp.Node(tag='checksum', diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 007549691..3a8f092a7 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -1078,10 +1078,7 @@ class Hashes(Node): elif algo == 'sha-512': hl = hashlib.sha512() - if hl == None: - # Raise exception - raise Exception('Hash algorithm not supported') - else: + if hl: hl.update(file_string) hash_ = hl.hexdigest() else: # if it is a file @@ -1095,10 +1092,7 @@ class Hashes(Node): elif algo == 'sha-512': hl = hashlib.sha512() - if hl == None: - # Raise exception - raise Exception('Hash algorithm not supported') - else: + if hl: for line in file_string: hl.update(line) hash_ = hl.hexdigest() From ca1eadaa2d92a50edac4f95ba776fd929c3d4b1f Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sun, 12 Feb 2012 19:36:20 -0500 Subject: [PATCH 113/121] refactoring --- src/common/jingle_ft.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 0d7f05790..06024ac4d 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -34,18 +34,16 @@ log = logging.getLogger('gajim.c.jingle_ft') STATE_NOT_STARTED = 0 STATE_INITIALIZED = 1 STATE_ACCEPTED = 2 -STATE_TRANSPORT_INFO = 3 -STATE_PROXY_ACTIVATED = 4 # We send the candidates and we are waiting for a reply -STATE_CAND_SENT_PENDING_REPLY = 5 +STATE_CAND_SENT = 3 # We received the candidates and we are waiting to reply -STATE_CAND_RECEIVED_PENDING_REPLY = 6 +STATE_CAND_RECEIVED = 4 # We have sent and received the candidates # This also includes any candidate-error received or sent -STATE_CAND_SENT_AND_RECEIVED = 7 +STATE_CAND_SENT_AND_RECEIVED = 5 +STATE_TRANSPORT_REPLACE = 6 # We are transfering the file -STATE_TRANSFERING = 8 -STATE_TRANSPORT_REPLACE = 9 +STATE_TRANSFERING = 7 class JingleFileTransfer(JingleContent): @@ -200,7 +198,7 @@ class JingleFileTransfer(JingleContent): if content.getTag('transport').getTag('candidate-error'): self.nominated_cand['peer-cand'] = False - if self.state == STATE_CAND_SENT_PENDING_REPLY: + if self.state == STATE_CAND_SENT: if not self.nominated_cand['our-cand'] and \ not self.nominated_cand['peer-cand']: if not self.weinitiate: @@ -213,7 +211,7 @@ class JingleFileTransfer(JingleContent): self.start_transfer() raise xmpp.NodeProcessed else: - self.state = STATE_CAND_RECEIVED_PENDING_REPLY + self.state = STATE_CAND_RECEIVED return @@ -236,14 +234,14 @@ class JingleFileTransfer(JingleContent): return # We save the candidate nominated by peer self.nominated_cand['peer-cand'] = streamhost_used - if self.state == STATE_CAND_SENT_PENDING_REPLY: + if self.state == STATE_CAND_SENT: response = stanza.buildReply('result') response.delChild(response.getQuery()) self.session.connection.connection.send(response) self.start_transfer() raise xmpp.NodeProcessed else: - self.state = STATE_CAND_RECEIVED_PENDING_REPLY + self.state = STATE_CAND_RECEIVED @@ -272,12 +270,6 @@ class JingleFileTransfer(JingleContent): gajim.socks5queue.connect_to_hosts(self.session.connection.name, self.file_props['sid'], self.send_candidate_used, self._on_connect_error, fingerprint=fingerprint) - elif not self.weinitiate and self.state == STATE_ACCEPTED: - # transport-info iq-result - self.state = STATE_TRANSPORT_INFO - elif self.weinitiate and self.state == STATE_INITIALIZED: - # proxy activated - self.state = STATE_PROXY_ACTIVATED elif self.state == STATE_CAND_SENT_AND_RECEIVED: if not self.nominated_cand['our-cand'] and \ not self.nominated_cand['peer-cand']: @@ -313,10 +305,10 @@ class JingleFileTransfer(JingleContent): return self.nominated_cand['our-cand'] = streamhost - if self.state == STATE_CAND_RECEIVED_PENDING_REPLY: + if self.state == STATE_CAND_RECEIVED: self.state = STATE_CAND_SENT_AND_RECEIVED else: - self.state = STATE_CAND_SENT_PENDING_REPLY + self.state = STATE_CAND_SENT content = xmpp.Node('content') content.setAttr('creator', 'initiator') @@ -339,10 +331,10 @@ class JingleFileTransfer(JingleContent): self.nominated_cand['our-cand'] = False self.send_error_candidate() - if self.state == STATE_CAND_RECEIVED_PENDING_REPLY: + if self.state == STATE_CAND_RECEIVED: self.state = STATE_CAND_SENT_AND_RECEIVED else: - self.state = STATE_CAND_SENT_PENDING_REPLY + self.state = STATE_CAND_SENT log.info('connect error, sid=' + sid) From df17f8751d2bef4fd35ffc22e3e3708e14f58db6 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 18 Feb 2012 21:06:08 +0100 Subject: [PATCH 114/121] handle GUI while checking file hash. Show show re-request dialog when hash is incorrect. TODO: re-request file to sender. --- src/dialogs.py | 7 ++--- src/filetransfers_window.py | 61 ++++++++++++++++++++++++++++++++++--- src/gui_interface.py | 52 ++++++++++++++++++++++++------- src/roster_window.py | 3 ++ 4 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/dialogs.py b/src/dialogs.py index 647b6f96b..8e0b80a36 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -1615,16 +1615,15 @@ class YesNoDialog(HigDialog): """ def __init__(self, pritext, sectext='', checktext='', on_response_yes=None, - on_response_no=None): + on_response_no=None, type_=gtk.MESSAGE_QUESTION): self.user_response_yes = on_response_yes self.user_response_no = on_response_no if hasattr(gajim.interface, 'roster') and gajim.interface.roster: parent = gajim.interface.roster.window else: parent = None - HigDialog.__init__(self, parent, gtk.MESSAGE_QUESTION, - gtk.BUTTONS_YES_NO, pritext, sectext, - on_response_yes=self.on_response_yes, + HigDialog.__init__(self, parent, type_, gtk.BUTTONS_YES_NO, pritext, + sectext, on_response_yes=self.on_response_yes, on_response_no=self.on_response_no) if checktext: diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 501da4df8..bc96b43cd 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -46,7 +46,8 @@ C_FILE = 2 C_TIME = 3 C_PROGRESS = 4 C_PERCENT = 5 -C_SID = 6 +C_PULSE = 6 +C_SID = 7 class FileTransfersWindow: @@ -65,7 +66,8 @@ class FileTransfersWindow: shall_notify = gajim.config.get('notify_on_file_complete') self.notify_ft_checkbox.set_active(shall_notify ) - self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str) + self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, + int, str) self.tree.set_model(self.model) col = gtk.TreeViewColumn() @@ -112,6 +114,7 @@ class FileTransfersWindow: col.pack_start(renderer, expand=False) col.add_attribute(renderer, 'text', C_PROGRESS) col.add_attribute(renderer, 'value', C_PERCENT) + col.add_attribute(renderer, 'pulse', C_PULSE) col.set_resizable(True) col.set_expand(False) self.tree.append_column(col) @@ -125,6 +128,8 @@ class FileTransfersWindow: 'pause': gtk.STOCK_MEDIA_PAUSE, 'continue': gtk.STOCK_MEDIA_PLAY, 'ok': gtk.STOCK_APPLY, + 'computing': gtk.STOCK_EXECUTE, + 'hash_error': gtk.STOCK_STOP, } self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) @@ -244,6 +249,21 @@ class FileTransfersWindow: dialogs.ErrorDialog(_('File transfer stopped'), sectext) self.tree.get_selection().unselect_all() + def show_hash_error(self, jid, file_props): + def on_yes(dummy): + # TODO: Request the file to the sender + pass + + if file_props['type'] == 'r': + file_name = os.path.basename(file_props['file-name']) + else: + file_name = file_props['name'] + dialogs.YesNoDialog(('File transfer error'), + _('The file %(file)s has been fully received, but it seems to be ' + 'wrongly received.\nDo you want to reload it?') % \ + {'file': file_name}, on_response_yes=on_yes, + type_=gtk.MESSAGE_ERROR) + def show_file_send_request(self, account, contact): win = gtk.ScrolledWindow() win.set_shadow_type(gtk.SHADOW_IN) @@ -449,6 +469,36 @@ class FileTransfersWindow: file_props['stopped'] = True elif status == 'ok': file_props['completed'] = True + text = self._format_percent(100) + received_size = int(file_props['received-len']) + full_size = int(file_props['size']) + text += helpers.convert_bytes(received_size) + '/' + \ + helpers.convert_bytes(full_size) + self.model.set(iter_, C_PROGRESS, text) + self.model.set(iter_, C_PULSE, gobject.constants.G_MAXINT) + elif status == 'computing': + self.model.set(iter_, C_PULSE, 1) + text = _('Checking file...') + '\n' + received_size = int(file_props['received-len']) + full_size = int(file_props['size']) + text += helpers.convert_bytes(received_size) + '/' + \ + helpers.convert_bytes(full_size) + self.model.set(iter_, C_PROGRESS, text) + def pulse(): + p = self.model.get(iter_, C_PULSE)[0] + if p == gobject.constants.G_MAXINT: + return False + self.model.set(iter_, C_PULSE, p + 1) + return True + gobject.timeout_add(100, pulse) + elif status == 'hash_error': + text = _('File error') + '\n' + received_size = int(file_props['received-len']) + full_size = int(file_props['size']) + text += helpers.convert_bytes(received_size) + '/' + \ + helpers.convert_bytes(full_size) + self.model.set(iter_, C_PROGRESS, text) + self.model.set(iter_, C_PULSE, gobject.constants.G_MAXINT) self.model.set(iter_, C_IMAGE, self.get_icon(status)) path = self.model.get_path(iter_) self.select_func(path) @@ -589,7 +639,10 @@ class FileTransfersWindow: status = 'stop' self.model.set(iter_, 0, self.get_icon(status)) if transfered_size == full_size: - self.set_status(typ, sid, 'ok') + if file_props['type'] == 'r': + self.set_status(typ, sid, 'computing') + else: + self.set_status(typ, sid, 'ok') elif just_began: path = self.model.get_path(iter_) self.select_func(path) @@ -655,7 +708,7 @@ class FileTransfersWindow: file_name = file_props['name'] text_props = gobject.markup_escape_text(file_name) + '\n' text_props += contact.get_shown_name() - self.model.set(iter_, 1, text_labels, 2, text_props, C_SID, + self.model.set(iter_, 1, text_labels, 2, text_props, C_PULSE, -1, C_SID, file_props['type'] + file_props['sid']) self.set_progress(file_props['type'], file_props['sid'], 0, iter_) if 'started' in file_props and file_props['started'] is False: diff --git a/src/gui_interface.py b/src/gui_interface.py index 6349ff3da..3401343ab 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -913,6 +913,12 @@ class Interface: def __compare_hashes(self, account, file_props): session = gajim.connections[account].get_jingle_session(jid=None, sid=file_props['session-sid']) + ft_win = self.instances['file_transfers'] + if not session.file_hash: + # We disn't get the hash, sender probably don't support that + jid = unicode(file_props['sender']) + self.popup_ft_result(account, jid, file_props) + ft_win.set_status(file_props['type'], file_props['sid'], 'ok') h = Hashes() try: file_ = open(file_props['file-name'], 'r') @@ -922,8 +928,16 @@ class Interface: file_.close() # If the hash we received and the hash of the file are the same, # then the file is not corrupt - if session.file_hash == hash_: - print "they are te same" + jid = unicode(file_props['sender']) + if session.file_hash != hash_: + self.popup_ft_result(account, jid, file_props) + ft_win.set_status(file_props['type'], file_props['sid'], 'ok') + else: + # wrong hash, we need to get the file again! + file_props['error'] = -10 + self.popup_ft_result(account, jid, file_props) + ft_win.set_status(file_props['type'], file_props['sid'], + 'hash_error') # End jingle session if session: session.end_session() @@ -949,7 +963,10 @@ class Interface: else: # we send a file jid = unicode(file_props['receiver']) gajim.socks5queue.remove_sender(file_props['sid'], True, True) + self.popup_ft_result(account, jid, file_props) + def popup_ft_result(self, account, jid, file_props): + ft = self.instances['file_transfers'] if helpers.allow_popup_window(account): if file_props['error'] == 0: if gajim.config.get('notify_on_file_complete'): @@ -960,6 +977,8 @@ class Interface: elif file_props['error'] == -6: ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) + elif file_props['error'] == -10: + ft.show_hash_error(jid, file_props) return msg_type = '' @@ -971,6 +990,9 @@ class Interface: elif file_props['error'] in (-1, -6): msg_type = 'file-stopped' event_type = _('File Transfer Stopped') + elif file_props['error'] == -10: + msg_type = 'file-hash-error' + event_type = _('File Transfer Failed') if event_type == '': # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) @@ -988,16 +1010,20 @@ class Interface: # get the name of the sender, as it is in the roster sender = unicode(file_props['sender']).split('/')[0] name = gajim.contacts.get_first_contact_from_jid(account, - sender).get_shown_name() + sender).get_shown_name() filename = os.path.basename(file_props['file-name']) if event_type == _('File Transfer Completed'): txt = _('You successfully received %(filename)s from ' '%(name)s.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' - else: # ft stopped + elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s from %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' + else: # ft hash error + txt = _('File transfer of %(filename)s from %(name)s ' + 'failed.') % {'filename': filename, 'name': name} + img_name = 'gajim-ft_stopped' else: receiver = file_props['receiver'] if hasattr(receiver, 'jid'): @@ -1005,24 +1031,28 @@ class Interface: receiver = receiver.split('/')[0] # get the name of the contact, as it is in the roster name = gajim.contacts.get_first_contact_from_jid(account, - receiver).get_shown_name() + receiver).get_shown_name() filename = os.path.basename(file_props['file-name']) if event_type == _('File Transfer Completed'): txt = _('You successfully sent %(filename)s to %(name)s.')\ - % {'filename': filename, 'name': name} + % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' - else: # ft stopped + elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' + else: # ft hash error + txt = _('File transfer of %(filename)s to %(name)s ' + 'failed.') % {'filename': filename, 'name': name} + img_name = 'gajim-ft_stopped' path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' path = '' if gajim.config.get('notify_on_file_complete') and \ - (gajim.config.get('autopopupaway') or \ - gajim.connections[account].connected in (2, 3)): + (gajim.config.get('autopopupaway') or \ + gajim.connections[account].connected in (2, 3)): # we want to be notified and we are online/chat or we don't mind # bugged when away/na/busy notify.popup(event_type, jid, account, msg_type, path_to_image=path, @@ -1514,7 +1544,7 @@ class Interface: no_queue = len(gajim.events.get_events(account, jid)) == 0 # type_ can be gc-invitation file-send-error file-error # file-request-error file-request file-completed file-stopped - # jingle-incoming + # file-hash-error jingle-incoming # event_type can be in advancedNotificationWindow.events_list event_types = {'file-request': 'ft_request', 'file-completed': 'ft_finished'} @@ -1643,7 +1673,7 @@ class Interface: w = ctrl.parent_win elif type_ in ('normal', 'file-request', 'file-request-error', 'file-send-error', 'file-error', 'file-stopped', 'file-completed', - 'jingle-incoming'): + 'file-hash-error', 'jingle-incoming'): # Get the first single message event event = gajim.events.get_first_event(account, fjid, type_) if not event: diff --git a/src/roster_window.py b/src/roster_window.py index 1bd218a53..ce3af0a09 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1917,6 +1917,9 @@ class RosterWindow: ft.show_stopped(jid, data, error_msg=msg_err) gajim.events.remove_events(account, jid, event) return True + elif event.type_ == 'file-hash-error': + ft.show_hash_error(jid, data) + gajim.events.remove_events(account, jid, event) elif event.type_ == 'file-completed': ft.show_completed(jid, data) gajim.events.remove_events(account, jid, event) From c9fa13e458add48d1456b72c5b6c97d7a4be3200 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 18 Feb 2012 21:21:00 +0100 Subject: [PATCH 115/121] revert debug test --- src/gui_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui_interface.py b/src/gui_interface.py index 3401343ab..c989995d6 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -929,7 +929,7 @@ class Interface: # If the hash we received and the hash of the file are the same, # then the file is not corrupt jid = unicode(file_props['sender']) - if session.file_hash != hash_: + if session.file_hash == hash_: self.popup_ft_result(account, jid, file_props) ft_win.set_status(file_props['type'], file_props['sid'], 'ok') else: From c412a6a55f61293c772f32737d2d70c3aa4cf715 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Mon, 19 Mar 2012 15:52:33 -0400 Subject: [PATCH 116/121] decline jingleFT correctly --- src/common/jingle_session.py | 9 ++++++++- src/common/protocol/bytestream.py | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 14c9080fd..3ddf45eac 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -148,6 +148,14 @@ class JingleSession(object): reason = xmpp.Node('reason') reason.addChild('decline') self._session_terminate(reason) + + def cancel_session(self): + """ + Called when user declines session in UI (when we aren't the initiator) + """ + reason = xmpp.Node('reason') + reason.addChild('cancel') + self._session_terminate(reason) def approve_content(self, media, name=None): content = self.get_content(media, name) @@ -730,7 +738,6 @@ class JingleSession(object): self.__session_info(p) def _session_terminate(self, reason=None): - assert self.state != JingleStates.ended stanza, jingle = self.__make_jingle('session-terminate', reason=reason) self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent') if self.connection.connection and self.connection.connected >= 2: diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index dd7486ae2..6af6d1637 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -195,6 +195,10 @@ class ConnectionBytestream: # user response to ConfirmationDialog may come after we've disconneted if not self.connection or self.connected < 2: return + if file_props['session-type'] == 'jingle': + jingle = self._sessions[file_props['session-sid']] + jingle.cancel_session() + return iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error') iq.setAttr('id', file_props['request-id']) if code == '400' and typ in ('stream', 'profile'): From c5ebac3f776df0d809087455883861c5fda62e1e Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Wed, 21 Mar 2012 15:54:46 -0400 Subject: [PATCH 117/121] handles file transfer cancel properly --- src/common/connection_handlers_events.py | 10 ++++++++++ src/common/jingle_session.py | 2 +- src/gui_interface.py | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index ad348d4a3..e03a610f7 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -1399,6 +1399,16 @@ class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent): self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid) self.sid = self.jingle_session.sid return True + +class JingleTransferCancelledEvent(nec.NetworkIncomingEvent): + name = 'jingleFT-cancelled-received' + base_network_events = [] + + def generate(self): + self.fjid = self.jingle_session.peerjid + self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid) + self.sid = self.jingle_session.sid + return True class JingleErrorReceivedEvent(nec.NetworkIncomingEvent): name = 'jingle-error-received' diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 3ddf45eac..9518e3b98 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -585,7 +585,7 @@ class JingleSession(object): else: # TODO text = reason - gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None, + gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None, conn=self.connection, jingle_session=self, media=None, reason=text)) diff --git a/src/gui_interface.py b/src/gui_interface.py index c989995d6..7566db12b 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1150,6 +1150,24 @@ class Interface: 'resource. Please type a new one'), resource=proposed_resource, ok_handler=on_ok) + def handle_event_jingleft_cancel(self, obj): + ft = self.instances['file_transfers'] + file_props = None + + # get the file_props of our session + for sid in obj.conn.files_props: + fp = obj.conn.files_props[sid] + if fp['session-sid'] == obj.sid: + file_props = fp + break + + ft.set_status(file_props['type'], file_props['sid'], 'stop') + file_props['error'] = -4 # is it the right error code? + + ft.show_stopped(obj.jid, file_props, 'Peer cancelled ' + + 'the transfer') + obj.conn.delete_jingle_session(obj.sid) + def handle_event_jingle_incoming(self, obj): # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, # data...)) @@ -1486,6 +1504,7 @@ class Interface: self.handle_event_jingle_disconnected], 'jingle-error-received': [self.handle_event_jingle_error], 'jingle-request-received': [self.handle_event_jingle_incoming], + 'jingleFT-cancelled-received': [self.handle_event_jingleft_cancel], 'last-result-received': [self.handle_event_last_status_time], 'message-error': [self.handle_event_msgerror], 'message-not-sent': [self.handle_event_msgnotsent], From 48524a60c0d93e17041306976dc004402e9f322a Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Thu, 22 Mar 2012 00:56:11 -0400 Subject: [PATCH 118/121] fix handling file transfer cancel properly --- src/common/jingle.py | 2 ++ src/common/jingle_session.py | 10 +++++++--- src/gui_interface.py | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index d4e5b6ada..5a9c39dee 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -149,6 +149,8 @@ class ConnectionJingle(object): return use_security = contact.supports(xmpp.NS_JINGLE_XTLS) jingle = JingleSession(self, weinitiate=True, jid=jid) + # this is a file transfer + jingle.session_type_FT = True self._sessions[jingle.sid] = jingle file_props['sid'] = jingle.sid if contact.supports(xmpp.NS_JINGLE_BYTESTREAM): diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 9518e3b98..a83cb191a 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -102,6 +102,8 @@ class JingleSession(object): # file. Could be 'md5', 'sha-1', etc... self.hash_algo = None self.file_hash = None + # Tells whether this session is a file transfer or not + self.session_type_FT = False # callbacks to call on proper contents # use .prepend() to add new callbacks, especially when you're going # to send error instead of ack @@ -585,9 +587,11 @@ class JingleSession(object): else: # TODO text = reason - gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None, - conn=self.connection, jingle_session=self, media=None, - reason=text)) + + if reason == 'cancel' and self.session_type_FT: + gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None, + conn=self.connection, jingle_session=self, media=None, + reason=text)) def __broadcast_all(self, stanza, jingle, error, action): """ diff --git a/src/gui_interface.py b/src/gui_interface.py index 7566db12b..d16b4f7e6 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1166,7 +1166,6 @@ class Interface: ft.show_stopped(obj.jid, file_props, 'Peer cancelled ' + 'the transfer') - obj.conn.delete_jingle_session(obj.sid) def handle_event_jingle_incoming(self, obj): # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, From 026e5b44f1e405a31e5b98b0357088c424cbf3ad Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Wed, 4 Apr 2012 21:04:23 -0400 Subject: [PATCH 119/121] refactoring: implemented state machine pattern --- src/common/jingle_ft.py | 205 +++++++--------------------- src/common/jingle_ftstates.py | 244 ++++++++++++++++++++++++++++++++++ src/common/jingle_session.py | 1 - 3 files changed, 289 insertions(+), 161 deletions(-) create mode 100644 src/common/jingle_ftstates.py diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index 06024ac4d..b5f7ed948 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -22,13 +22,13 @@ Handles Jingle File Transfer (XEP 0234) import gajim import xmpp from jingle_content import contents, JingleContent -from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 -from jingle_transport import JingleTransportIBB, TransportType +from jingle_transport import * from common import helpers from common.socks5 import Socks5ReceiverClient, Socks5SenderClient from common.connection_handlers_events import FileRequestReceivedEvent import threading import logging +from jingle_ftstates import * log = logging.getLogger('gajim.c.jingle_ft') STATE_NOT_STARTED = 0 @@ -68,8 +68,6 @@ class JingleFileTransfer(JingleContent): self.callbacks['transport-info'] += [self.__on_transport_info] self.callbacks['iq-result'] += [self.__on_iq_result] - self.state = STATE_NOT_STARTED - self.use_security = use_security self.file_props = file_props @@ -100,8 +98,21 @@ class JingleFileTransfer(JingleContent): self.session = session self.media = 'file' self.nominated_cand = {} - - + + self.state = STATE_NOT_STARTED + self.states = {STATE_INITIALIZED : StateInitialized(self), + STATE_CAND_SENT : StateCandSent(self), + STATE_CAND_RECEIVED : StateCandReceived(self), + STATE_TRANSFERING : StateTransfering(self), + STATE_TRANSPORT_REPLACE : StateTransportReplace(self), + STATE_CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self) + } + + def __state_changed(self, nextstate, args=None): + # Executes the next state action and sets the next state + st = self.states[nextstate] + st.action(args) + self.state = nextstate def __on_session_initiate(self, stanza, content, error, action): gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, @@ -148,7 +159,7 @@ class JingleFileTransfer(JingleContent): response.delChild(response.getQuery()) con.connection.send(response) # We send the file - self.__start_IBB_transfer(con) + self.__state_changed(STATE_TRANSFERING) raise xmpp.NodeProcessed self.file_props['streamhosts'] = self.transport.remote_candidates @@ -170,12 +181,11 @@ class JingleFileTransfer(JingleContent): fingerprint = 'client' if self.transport.type == TransportType.SOCKS5: gajim.socks5queue.connect_to_hosts(self.session.connection.name, - self.file_props['sid'], self.send_candidate_used, + self.file_props['sid'], self.on_connect, self._on_connect_error, fingerprint=fingerprint, receiving=False) - elif self.transport.type == TransportType.IBB: - self.state = STATE_TRANSFERING - self.__start_IBB_transfer(self.session.connection) + return + self.__state_changed(STATE_TRANSFERING) raise xmpp.NodeProcessed def __on_session_terminate(self, stanza, content, error, action): @@ -203,16 +213,16 @@ class JingleFileTransfer(JingleContent): not self.nominated_cand['peer-cand']: if not self.weinitiate: return - self.session.transport_replace() + self.__state_changed(STATE_TRANSPORT_REPLACE) else: response = stanza.buildReply('result') response.delChild(response.getQuery()) self.session.connection.connection.send(response) - self.start_transfer() + self.__state_changed(STATE_TRANSFERING) raise xmpp.NodeProcessed else: - self.state = STATE_CAND_RECEIVED - + args = {'candError' : True} + self.__state_changed(STATE_CAND_RECEIVED, args) return if content.getTag('transport').getTag('activated'): @@ -222,73 +232,39 @@ class JingleFileTransfer(JingleContent): self.session.connection.name, 'client') return - streamhost_cid = content.getTag('transport').getTag('candidate-used').\ - getAttr('cid') - streamhost_used = None - for cand in self.transport.candidates: - if cand['candidate_id'] == streamhost_cid: - streamhost_used = cand - break - if streamhost_used == None: - log.info("unknow streamhost") - return - # We save the candidate nominated by peer - self.nominated_cand['peer-cand'] = streamhost_used + args = {'content' : content, + 'sendCand' : False} if self.state == STATE_CAND_SENT: + self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args) response = stanza.buildReply('result') response.delChild(response.getQuery()) self.session.connection.connection.send(response) - self.start_transfer() + self.__state_changed(STATE_TRANSFERING) raise xmpp.NodeProcessed else: - self.state = STATE_CAND_RECEIVED + self.__state_changed(STATE_CAND_RECEIVED, args) def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result") - if self.weinitiate and self.state == STATE_NOT_STARTED: - self.state = STATE_INITIALIZED - self.session.connection.files_props[self.file_props['sid']] = \ - self.file_props - # Listen on configured port for file transfer - self._listen_host() - - elif not self.weinitiate and self.state == STATE_NOT_STARTED: - # session-accept iq-result - if not self.negotiated: - return - self.state = STATE_ACCEPTED - if not gajim.socks5queue.get_file_props( - self.session.connection.name, self.file_props['sid']): - gajim.socks5queue.add_file_props(self.session.connection.name, - self.file_props) - fingerprint = None - if self.use_security: - fingerprint = 'client' - gajim.socks5queue.connect_to_hosts(self.session.connection.name, - self.file_props['sid'], self.send_candidate_used, - self._on_connect_error, fingerprint=fingerprint) + if self.state == STATE_NOT_STARTED: + self.__state_changed(STATE_INITIALIZED) elif self.state == STATE_CAND_SENT_AND_RECEIVED: if not self.nominated_cand['our-cand'] and \ not self.nominated_cand['peer-cand']: if not self.weinitiate: return - self.session.transport_replace() + self.__state_changed(STATE_TRANSPORT_REPLACE) return # initiate transfer - self.start_transfer() + self.__state_changed(STATE_TRANSFERING) - def __start_IBB_transfer(self, con): - con.files_props[self.file_props['sid']] = self.file_props - fp = open(self.file_props['file-name'], 'r') - con.OpenStream( self.transport.sid, self.session.peerjid, fp, - blocksize=4096) - def __transport_setup(self, stanza=None, content=None, error=None, action=None): # Sets up a few transport specific things for the file transfer + # TODO: Do this inside of a state class if self.transport.type == TransportType.SOCKS5: self._listen_host() @@ -296,48 +272,30 @@ class JingleFileTransfer(JingleContent): self.state = STATE_TRANSFERING - def send_candidate_used(self, streamhost): + def on_connect(self, streamhost): """ send candidate-used stanza """ log.info('send_candidate_used') if streamhost is None: return + args = {'streamhost' : streamhost, + 'sendCand' : True} self.nominated_cand['our-cand'] = streamhost if self.state == STATE_CAND_RECEIVED: - self.state = STATE_CAND_SENT_AND_RECEIVED + self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args) else: - self.state = STATE_CAND_SENT - - content = xmpp.Node('content') - content.setAttr('creator', 'initiator') - content.setAttr('name', self.name) - - transport = xmpp.Node('transport') - transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) - transport.setAttr('sid', self.transport.sid) - - candidateused = xmpp.Node('candidate-used') - candidateused.setAttr('cid', streamhost['cid']) - - transport.addChild(node=candidateused) - content.addChild(node=transport) - - self.session.send_transport_info(content) + self.__state_changed(STATE_CAND_SENT, args) def _on_connect_error(self, sid): - self.nominated_cand['our-cand'] = False - self.send_error_candidate() - - if self.state == STATE_CAND_RECEIVED: - self.state = STATE_CAND_SENT_AND_RECEIVED - else: - self.state = STATE_CAND_SENT - - log.info('connect error, sid=' + sid) + args = {'candError' : True} + if self.state == STATE_CAND_RECEIVED: + self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args) + else: + self.__state_changed(STATE_CAND_SENT, args) def _fill_content(self, content): description_node = xmpp.simplexml.Node( @@ -415,79 +373,6 @@ class JingleFileTransfer(JingleContent): return self.weinitiate - def start_transfer(self): - - self.state = STATE_TRANSFERING - - # It tells wether we start the transfer as client or server - mode = None - - if self.isOurCandUsed(): - mode = 'client' - streamhost_used = self.nominated_cand['our-cand'] - else: - mode = 'server' - streamhost_used = self.nominated_cand['peer-cand'] - - if streamhost_used['type'] == 'proxy': - self.file_props['is_a_proxy'] = True - if self.weinitiate: - self.file_props['proxy_sender'] = streamhost_used['initiator'] - self.file_props['proxy_receiver'] = streamhost_used['target'] - else: - self.file_props['proxy_sender'] = streamhost_used['target'] - self.file_props['proxy_receiver'] = streamhost_used['initiator'] - - if not self.weinitiate and streamhost_used['type'] == 'proxy': - r = gajim.socks5queue.readers - for reader in r: - if r[reader].host == streamhost_used['host'] and \ - r[reader].connected: - return - - if self.weinitiate and streamhost_used['type'] == 'proxy': - s = gajim.socks5queue.senders - for sender in s: - if s[sender].host == streamhost_used['host'] and \ - s[sender].connected: - return - - if streamhost_used['type'] == 'proxy': - self.file_props['streamhost-used'] = True - streamhost_used['sid'] = self.file_props['sid'] - self.file_props['streamhosts'] = [] - self.file_props['streamhosts'].append(streamhost_used) - self.file_props['proxyhosts'] = [] - self.file_props['proxyhosts'].append(streamhost_used) - - if self.weinitiate: - gajim.socks5queue.idx += 1 - idx = gajim.socks5queue.idx - sockobj = Socks5SenderClient(gajim.idlequeue, idx, - gajim.socks5queue, _sock=None, - host=str(streamhost_used['host']), - port=int(streamhost_used['port']), fingerprint=None, - connected=False, file_props=self.file_props) - else: - sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used, - sid=self.file_props['sid'], - file_props=self.file_props, fingerprint=None) - sockobj.proxy = True - sockobj.streamhost = streamhost_used - gajim.socks5queue.add_sockobj(self.session.connection.name, - sockobj, 'sender') - streamhost_used['idx'] = sockobj.queue_idx - # If we offered the nominated candidate used, we activate - # the proxy - if not self.isOurCandUsed(): - gajim.socks5queue.on_success[self.file_props['sid']] = \ - self.transport._on_proxy_auth_ok - # TODO: add on failure - else: - jid = gajim.get_jid_without_resource(self.session.ourjid) - gajim.socks5queue.send_file(self.file_props, - self.session.connection.name, mode) - def get_content(desc): return JingleFileTransfer diff --git a/src/common/jingle_ftstates.py b/src/common/jingle_ftstates.py new file mode 100644 index 000000000..72e5a4856 --- /dev/null +++ b/src/common/jingle_ftstates.py @@ -0,0 +1,244 @@ +## +## Copyright (C) 2006 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import gajim +import xmpp +from jingle_transport import * + +class JingleFileTransferStates: + + # This class implements the state machine design pattern + + def __init__(self, jingleft): + + self.jft = jingleft + + def action(self, args=None): + ''' + This method MUST be overriden by a subclass + ''' + raise Exception('This is an abstract method!!') + + +class StateInitialized(JingleFileTransferStates): + + ''' + This state initializes the file transfer + ''' + + def action(self, args=None): + if self.jft.weinitiate: + # update connection's fileprops + self.jft.session.connection.files_props[self.jft.file_props['sid']] = \ + self.jft.file_props + # Listen on configured port for file transfer + self.jft._listen_host() # TODO: Rename this to listen_host() + else: + # Add file_props to the queue + if not gajim.socks5queue.get_file_props( + self.jft.session.connection.name, self.jft.file_props['sid']): + gajim.socks5queue.add_file_props( + self.jft.session.connection.name, + self.jft.file_props) + fingerprint = None + if self.jft.use_security: + fingerprint = 'client' + # Connect to the candidate host, on success call on_connect method + gajim.socks5queue.connect_to_hosts( + self.jft.session.connection.name, + self.jft.file_props['sid'], self.jft.on_connect, + self.jft._on_connect_error, fingerprint=fingerprint) + + +class StateCandSent(JingleFileTransferStates): + + ''' + This state sends our nominated candidate + ''' + + def _sendCand(self, args): + if 'candError' in args: + self.jft.nominated_cand['our-cand'] = False + self.jft.send_error_candidate() + return + # Send candidate used + streamhost = args['streamhost'] + self.jft.nominated_cand['our-cand'] = streamhost + + content = xmpp.Node('content') + content.setAttr('creator', 'initiator') + content.setAttr('name', self.jft.name) + + transport = xmpp.Node('transport') + transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM) + transport.setAttr('sid', self.jft.transport.sid) + + candidateused = xmpp.Node('candidate-used') + candidateused.setAttr('cid', streamhost['cid']) + + transport.addChild(node=candidateused) + content.addChild(node=transport) + + self.jft.session.send_transport_info(content) + + + def action(self, args=None): + self._sendCand(args) + +class StateCandReceived(JingleFileTransferStates): + + ''' + This state happens when we receive a candidate. + It takes the arguments: canError if we receive a candidate-error + ''' + + def _recvCand(self, args): + if 'candError' in args: + return + content = args['content'] + streamhost_cid = content.getTag('transport').getTag('candidate-used').\ + getAttr('cid') + streamhost_used = None + for cand in self.jft.transport.candidates: + if cand['candidate_id'] == streamhost_cid: + streamhost_used = cand + break + if streamhost_used == None: + log.info("unknow streamhost") + return + # We save the candidate nominated by peer + self.jft.nominated_cand['peer-cand'] = streamhost_used + + + + def action(self, args=None): + self._recvCand(args) + +class StateCandSentAndRecv( StateCandSent, StateCandReceived): + + ''' + This state happens when we have received and sent the candidates. + It takes the boolean argument: sendCand in order to decide whether + we should execute the action of when we receive or send a candidate. + ''' + + def action(self, args=None): + + if args['sendCand']: + self._sendCand(args) + else: + self._recvCand(args) + +class StateTransportReplace(JingleFileTransferStates): + + ''' + This state initiates transport replace + ''' + + def action(self, args=None): + self.jft.session.transport_replace() + +class StateTransfering(JingleFileTransferStates): + + ''' + This state will start the transfer depeding on the type of transport + we have. + ''' + + def __start_IBB_transfer(self, con): + con.files_props[self.jft.file_props['sid']] = \ + self.jft.file_props + fp = open(self.jft.file_props['file-name'], 'r') + con.OpenStream( self.jft.transport.sid, + self.jft.session.peerjid, fp, blocksize=4096) + + def __start_SOCK5_transfer(self): + # It tells wether we start the transfer as client or server + mode = None + + if self.jft.isOurCandUsed(): + mode = 'client' + streamhost_used = self.jft.nominated_cand['our-cand'] + else: + mode = 'server' + streamhost_used = self.jft.nominated_cand['peer-cand'] + + if streamhost_used['type'] == 'proxy': + self.jft.file_props['is_a_proxy'] = True + # This needs to be changed when requesting + if self.jft.weinitiate: + self.jft.file_props['proxy_sender'] = streamhost_used['initiator'] + self.jft.file_props['proxy_receiver'] = streamhost_used['target'] + else: + self.jft.file_props['proxy_sender'] = streamhost_used['target'] + self.jft.file_props['proxy_receiver'] = streamhost_used['initiator'] + + # This needs to be changed when requesting + if not self.jft.weinitiate and streamhost_used['type'] == 'proxy': + r = gajim.socks5queue.readers + for reader in r: + if r[reader].host == streamhost_used['host'] and \ + r[reader].connected: + return + + # This needs to be changed when requesting + if self.jft.weinitiate and streamhost_used['type'] == 'proxy': + s = gajim.socks5queue.senders + for sender in s: + if s[sender].host == streamhost_used['host'] and \ + s[sender].connected: + return + + if streamhost_used['type'] == 'proxy': + self.jft.file_props['streamhost-used'] = True + streamhost_used['sid'] = self.jft.file_props['sid'] + self.jft.file_props['streamhosts'] = [] + self.jft.file_props['streamhosts'].append(streamhost_used) + self.jft.file_props['proxyhosts'] = [] + self.jft.file_props['proxyhosts'].append(streamhost_used) + + # This needs to be changed when requesting + if self.jft.weinitiate: + gajim.socks5queue.idx += 1 + idx = gajim.socks5queue.idx + sockobj = Socks5SenderClient(gajim.idlequeue, idx, + gajim.socks5queue, _sock=None, + host=str(streamhost_used['host']), + port=int(streamhost_used['port']), fingerprint=None, + connected=False, file_props=self.jft.file_props) + else: + sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used, + sid=self.jft.file_props['sid'], + file_props=self.jft.file_props, fingerprint=None) + sockobj.proxy = True + sockobj.streamhost = streamhost_used + gajim.socks5queue.add_sockobj(self.jft.session.connection.name, + sockobj, 'sender') + streamhost_used['idx'] = sockobj.queue_idx + # If we offered the nominated candidate used, we activate + # the proxy + if not self.jft.isOurCandUsed(): + gajim.socks5queue.on_success[self.jft.file_props['sid']] = \ + self.jft.transport._on_proxy_auth_ok + # TODO: add on failure + else: + jid = gajim.get_jid_without_resource(self.jft.session.ourjid) + gajim.socks5queue.send_file(self.jft.file_props, + self.jft.session.connection.name, mode) + + def action(self, args=None): + if self.jft.transport.type == TransportType.IBB: + self.__start_IBB_transfer(self.jft.session.connection) + + elif self.jft.transport.type == TransportType.SOCKS5: + self.__start_SOCK5_transfer() diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index a83cb191a..b3faafe2d 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -381,7 +381,6 @@ class JingleSession(object): self.modify_content(creator, name, transport) cont = self.contents[(creator, name)] cont.transport = transport - cont.state = STATE_TRANSPORT_REPLACE stanza, jingle = self.__make_jingle('transport-replace') self.__append_contents(jingle) From 038ea0fe664980ed4dfed9ffb6ba2d8bb9df0858 Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 7 Apr 2012 00:56:44 -0400 Subject: [PATCH 120/121] minor fixes --- src/common/jingle_ft.py | 24 ++++++++++-------------- src/common/jingle_ftstates.py | 2 +- src/common/xmpp/protocol.py | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index b5f7ed948..1e0564409 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -33,17 +33,16 @@ log = logging.getLogger('gajim.c.jingle_ft') STATE_NOT_STARTED = 0 STATE_INITIALIZED = 1 -STATE_ACCEPTED = 2 # We send the candidates and we are waiting for a reply -STATE_CAND_SENT = 3 +STATE_CAND_SENT = 2 # We received the candidates and we are waiting to reply -STATE_CAND_RECEIVED = 4 +STATE_CAND_RECEIVED = 3 # We have sent and received the candidates # This also includes any candidate-error received or sent -STATE_CAND_SENT_AND_RECEIVED = 5 -STATE_TRANSPORT_REPLACE = 6 +STATE_CAND_SENT_AND_RECEIVED = 4 +STATE_TRANSPORT_REPLACE = 5 # We are transfering the file -STATE_TRANSFERING = 7 +STATE_TRANSFERING = 6 class JingleFileTransfer(JingleContent): @@ -264,11 +263,9 @@ class JingleFileTransfer(JingleContent): def __transport_setup(self, stanza=None, content=None, error=None, action=None): # Sets up a few transport specific things for the file transfer - # TODO: Do this inside of a state class - if self.transport.type == TransportType.SOCKS5: - self._listen_host() if self.transport.type == TransportType.IBB: + # No action required, just set the state to transfering self.state = STATE_TRANSFERING @@ -283,15 +280,14 @@ class JingleFileTransfer(JingleContent): 'sendCand' : True} self.nominated_cand['our-cand'] = streamhost - if self.state == STATE_CAND_RECEIVED: - self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args) - else: - self.__state_changed(STATE_CAND_SENT, args) - + self.__sendCand(args) def _on_connect_error(self, sid): log.info('connect error, sid=' + sid) args = {'candError' : True} + self.__sendCand(args) + + def __sendCand(self, args): if self.state == STATE_CAND_RECEIVED: self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args) else: diff --git a/src/common/jingle_ftstates.py b/src/common/jingle_ftstates.py index 72e5a4856..271f53927 100644 --- a/src/common/jingle_ftstates.py +++ b/src/common/jingle_ftstates.py @@ -37,12 +37,12 @@ class StateInitialized(JingleFileTransferStates): ''' def action(self, args=None): + self.jft._listen_host() if self.jft.weinitiate: # update connection's fileprops self.jft.session.connection.files_props[self.jft.file_props['sid']] = \ self.jft.file_props # Listen on configured port for file transfer - self.jft._listen_host() # TODO: Rename this to listen_host() else: # Add file_props to the queue if not gajim.socks5queue.get_file_props( diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 3a8f092a7..60a17d256 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -90,7 +90,7 @@ NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166 NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167 NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167 NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167 -NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234 +NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:3' # XEP-0234 NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 From 66df7c30ca8a655a078708d3dbd0623e292a69dd Mon Sep 17 00:00:00 2001 From: Jefry Lagrange Date: Sat, 7 Apr 2012 23:12:15 -0400 Subject: [PATCH 121/121] dont compare hashes on SI transfer --- src/filetransfers_window.py | 4 +++- src/gui_interface.py | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index bc96b43cd..efe7c787f 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -639,7 +639,9 @@ class FileTransfersWindow: status = 'stop' self.model.set(iter_, 0, self.get_icon(status)) if transfered_size == full_size: - if file_props['type'] == 'r': + # If we are receiver and this is a jingle session + if file_props['type'] == 'r' and 'session-sid' in file_props: + # Show that we are computing the hash self.set_status(typ, sid, 'computing') else: self.set_status(typ, sid, 'ok') diff --git a/src/gui_interface.py b/src/gui_interface.py index d16b4f7e6..a27c11914 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -34,7 +34,6 @@ ## You should have received a copy of the GNU General Public License ## along with Gajim. If not, see . ## - import os import sys import re @@ -954,11 +953,13 @@ class Interface: return if file_props['type'] == 'r': # we receive a file - jid = unicode(file_props['sender']) - # Compare hashes in a new thread - self.hashThread = Thread(target=self.__compare_hashes, - args=(account, file_props)) - self.hashThread.start() + # If we have a jingle session id, it is a jingle transfer + # we compare hashes + if 'session-sid' in file_props: + # Compare hashes in a new thread + self.hashThread = Thread(target=self.__compare_hashes, + args=(account, file_props)) + self.hashThread.start() gajim.socks5queue.remove_receiver(file_props['sid'], True, True) else: # we send a file jid = unicode(file_props['receiver'])