diff options
Diffstat (limited to 'src/common/jingle_ft.py')
-rw-r--r-- | src/common/jingle_ft.py | 413 |
1 files changed, 0 insertions, 413 deletions
diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py deleted file mode 100644 index 3f7eae168..000000000 --- a/src/common/jingle_ft.py +++ /dev/null @@ -1,413 +0,0 @@ -# -*- 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 <http://www.gnu.org/licenses/>. -## - - -""" -Handles Jingle File Transfer (XEP 0234) -""" - -import hashlib -import logging -import os -import threading -from enum import IntEnum, unique -import nbxmpp -from common import gajim -from common import configpaths -from common import jingle_xtls -from common.jingle_content import contents, JingleContent -from common.jingle_transport import JingleTransportSocks5, TransportType -from common import helpers -from common.connection_handlers_events import FileRequestReceivedEvent -from common.jingle_ftstates import ( - StateInitialized, StateCandSent, StateCandReceived, StateTransfering, - StateCandSentAndRecv, StateTransportReplace) - -log = logging.getLogger('gajim.c.jingle_ft') - - -@unique -class State(IntEnum): - NOT_STARTED = 0 - INITIALIZED = 1 - # We send the candidates and we are waiting for a reply - CAND_SENT = 2 - # We received the candidates and we are waiting to reply - CAND_RECEIVED = 3 - # We have sent and received the candidates - # This also includes any candidate-error received or sent - CAND_SENT_AND_RECEIVED = 4 - TRANSPORT_REPLACE = 5 - # We are transfering the file - TRANSFERING = 6 - - -class JingleFileTransfer(JingleContent): - - 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 - 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-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] - # 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.use_security = use_security - self.x509_fingerprint = None - self.file_props = file_props - self.weinitiate = self.session.weinitiate - self.werequest = self.session.werequest - if self.file_props is not None: - if self.session.werequest: - self.file_props.sender = self.session.peerjid - self.file_props.receiver = self.session.ourjid - else: - self.file_props.sender = self.session.ourjid - self.file_props.receiver = self.session.peerjid - self.file_props.session_type = 'jingle' - self.file_props.sid = session.sid - self.file_props.transfered_size = [] - self.file_props.transport_sid = self.transport.sid - 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) - log.info('ourjid: %s', session.ourjid) - self.session = session - self.media = 'file' - self.nominated_cand = {} - if gajim.contacts.is_gc_contact(session.connection.name, - session.peerjid): - roomjid = session.peerjid.split('/')[0] - dstaddr = hashlib.sha1(('%s%s%s' % (self.file_props.sid, - session.ourjid, roomjid)) - .encode('utf-8')).hexdigest() - self.file_props.dstaddr = dstaddr - 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) - } - - if jingle_xtls.PYOPENSSL_PRESENT: - cert_name = os.path.join(configpaths.gajimpaths['MY_CERT'], - 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 __state_changed(self, nextstate, args=None): - # Executes the next state action and sets the next state - current_state = self.state - st = self.states[nextstate] - st.action(args) - # state can have been changed during the action. Don't go back. - if self.state == current_state: - self.state = nextstate - - def __on_session_initiate(self, stanza, content, error, action): - log.debug("Jingle FT request received") - gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, - conn=self.session.connection, - stanza=stanza, - jingle_content=content, - FT_content=self)) - if self.session.request: - # accept the request - self.session.approve_content(self.media, self.name) - self.session.accept_session() - - def __on_session_initiate_sent(self, stanza, content, error, action): - pass - - def __send_hash(self): - # Send hash in a session info - checksum = nbxmpp.Node(tag='checksum', - payload=[nbxmpp.Node(tag='file', - payload=[self._compute_hash()])]) - checksum.setNamespace(nbxmpp.NS_JINGLE_FILE_TRANSFER_5) - self.session.__session_info(checksum) - pjid = gajim.get_jid_without_resource(self.session.peerjid) - file_info = {'name' : self.file_props.name, - 'file-name' : self.file_props.file_name, - 'hash' : self.file_props.hash_, - 'size' : self.file_props.size, - 'date' : self.file_props.date, - 'peerjid' : pjid - } - self.session.connection.set_file_info(file_info) - - def _compute_hash(self): - # Caculates the hash and returns a xep-300 hash stanza - if self.file_props.algo is None: - return - try: - file_ = open(self.file_props.file_name, 'rb') - except IOError: - # can't open file - return - h = nbxmpp.Hashes2() - hash_ = h.calculateHash(self.file_props.algo, file_) - file_.close() - # DEBUG - #hash_ = '1294809248109223' - if not hash_: - # Hash alogrithm not supported - return - self.file_props.hash_ = hash_ - h.addHash(hash_, self.file_props.algo) - return h - - def on_cert_received(self): - self.session.approve_session() - self.session.approve_content('file', name=self.name) - - 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 - else: - fingerprint = security.getTag('fingerprint') - if fingerprint: - fingerprint = fingerprint.getData() - self.x509_fingerprint = fingerprint - if not jingle_xtls.check_cert(gajim.get_jid_without_resource( - self.session.responder), fingerprint): - id_ = jingle_xtls.send_cert_request(con, - self.session.responder) - jingle_xtls.key_exchange_pend(id_, - self.continue_session_accept, - [stanza]) - raise nbxmpp.NodeProcessed - self.continue_session_accept(stanza) - - def continue_session_accept(self, stanza): - con = self.session.connection - if self.state == State.TRANSPORT_REPLACE: - # If we are requesting we don't have the file - if self.session.werequest: - raise nbxmpp.NodeProcessed - # We send the file - self.__state_changed(State.TRANSFERING) - raise nbxmpp.NodeProcessed - self.file_props.streamhosts = self.transport.remote_candidates - # Calculate file hash in a new thread - # if we haven't sent the hash already. - if self.file_props.hash_ is None and self.file_props.algo and \ - not self.werequest: - self.hash_thread = threading.Thread(target=self.__send_hash) - self.hash_thread.start() - for host in self.file_props.streamhosts: - host['initiator'] = self.session.initiator - host['target'] = self.session.responder - host['sid'] = self.file_props.sid - 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.on_connect, - self._on_connect_error, - fingerprint=fingerprint, - receiving=False) - raise nbxmpp.NodeProcessed - self.__state_changed(State.TRANSFERING) - raise nbxmpp.NodeProcessed - - 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") - - 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") - cand_error = content.getTag('transport').getTag('candidate-error') - cand_used = content.getTag('transport').getTag('candidate-used') - if (cand_error or cand_used) and \ - self.state >= State.CAND_SENT_AND_RECEIVED: - raise nbxmpp.NodeProcessed - if cand_error: - if not gajim.socks5queue.listener.connections: - gajim.socks5queue.listener.disconnect() - self.nominated_cand['peer-cand'] = False - if self.state == State.CAND_SENT: - if not self.nominated_cand['our-cand'] and \ - not self.nominated_cand['peer-cand']: - if not self.weinitiate: - return - self.__state_changed(State.TRANSPORT_REPLACE) - else: - response = stanza.buildReply('result') - response.delChild(response.getQuery()) - self.session.connection.connection.send(response) - self.__state_changed(State.TRANSFERING) - raise nbxmpp.NodeProcessed - else: - args = {'cand_error' : True} - self.__state_changed(State.CAND_RECEIVED, args) - return - if cand_used: - streamhost_cid = cand_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 is None or streamhost_used['type'] == 'proxy': - if gajim.socks5queue.listener and \ - not gajim.socks5queue.listener.connections: - gajim.socks5queue.listener.disconnect() - 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 - args = {'content': content, - 'sendCand': False} - if self.state == State.CAND_SENT: - self.__state_changed(State.CAND_SENT_AND_RECEIVED, args) - self.__state_changed(State.TRANSFERING) - raise nbxmpp.NodeProcessed - else: - self.__state_changed(State.CAND_RECEIVED, args) - - def __on_iq_result(self, stanza, content, error, action): - log.info("__on_iq_result") - - 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.__state_changed(State.TRANSPORT_REPLACE) - return - # initiate transfer - self.__state_changed(State.TRANSFERING) - - 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.IBB: - # No action required, just set the state to transfering - self.state = State.TRANSFERING - else: - self._listen_host() - - 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 - self.__send_candidate(args) - - def _on_connect_error(self, sid): - log.info('connect error, sid=' + sid) - args = {'candError' : True, - 'sendCand' : True} - self.__send_candidate(args) - - def __send_candidate(self, args): - if self.state == State.CAND_RECEIVED: - self.__state_changed(State.CAND_SENT_AND_RECEIVED, args) - else: - self.__state_changed(State.CAND_SENT, args) - - def _store_socks5_sid(self, sid, hash_id): - # callback from socsk5queue.start_listener - 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) - 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, - fingerprint=fingerprint, - typ='sender' if self.weinitiate else 'receiver') - if not listener: - # send error message, notify the user - return - - def is_our_candidate_used(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 not self.nominated_cand['peer-cand']: - return True - if not self.nominated_cand['our-cand']: - 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 - return self.weinitiate - - def start_ibb_transfer(self): - if self.file_props.type_ == 's': - self.__state_changed(State.TRANSFERING) - - -def get_content(desc): - return JingleFileTransfer - -contents[nbxmpp.NS_JINGLE_FILE_TRANSFER_5] = get_content |