720 lines
27 KiB
Python
720 lines
27 KiB
Python
|
# Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
|
||
|
# Junglecow J <junglecow AT gmail.com>
|
||
|
# Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
|
||
|
# Travis Shirk <travis AT pobox.com>
|
||
|
# Nikos Kouremenos <kourem AT gmail.com>
|
||
|
# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||
|
# Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
|
||
|
# Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||
|
# Jean-Marie Traissard <jim AT lapin.org>
|
||
|
# Stephan Erb <steve-e AT h3c.de>
|
||
|
# Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||
|
#
|
||
|
# 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/>.
|
||
|
|
||
|
import socket
|
||
|
import logging
|
||
|
|
||
|
import nbxmpp
|
||
|
from nbxmpp.namespaces import Namespace
|
||
|
from nbxmpp.structs import StanzaHandler
|
||
|
from gi.repository import GLib
|
||
|
|
||
|
from gajim.common import app
|
||
|
from gajim.common import helpers
|
||
|
from gajim.common import jingle_xtls
|
||
|
from gajim.common.file_props import FilesProp
|
||
|
from gajim.common.socks5 import Socks5SenderClient
|
||
|
from gajim.common.nec import NetworkEvent
|
||
|
from gajim.common.modules.base import BaseModule
|
||
|
|
||
|
|
||
|
log = logging.getLogger('gajim.c.m.bytestream')
|
||
|
|
||
|
def is_transfer_paused(file_props):
|
||
|
if file_props.stopped:
|
||
|
return False
|
||
|
if file_props.completed:
|
||
|
return False
|
||
|
if file_props.disconnect_cb:
|
||
|
return False
|
||
|
return file_props.paused
|
||
|
|
||
|
def is_transfer_active(file_props):
|
||
|
if file_props.stopped:
|
||
|
return False
|
||
|
if file_props.completed:
|
||
|
return False
|
||
|
if not file_props.started:
|
||
|
return False
|
||
|
if file_props.paused:
|
||
|
return True
|
||
|
return not file_props.paused
|
||
|
|
||
|
def is_transfer_stopped(file_props):
|
||
|
if not file_props:
|
||
|
return True
|
||
|
if file_props.error:
|
||
|
return True
|
||
|
if file_props.completed:
|
||
|
return True
|
||
|
if not file_props.stopped:
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
class Bytestream(BaseModule):
|
||
|
def __init__(self, con):
|
||
|
BaseModule.__init__(self, con)
|
||
|
|
||
|
self.handlers = [
|
||
|
StanzaHandler(name='iq',
|
||
|
typ='result',
|
||
|
ns=Namespace.BYTESTREAM,
|
||
|
callback=self._on_bytestream_result),
|
||
|
StanzaHandler(name='iq',
|
||
|
typ='error',
|
||
|
ns=Namespace.BYTESTREAM,
|
||
|
callback=self._on_bytestream_error),
|
||
|
StanzaHandler(name='iq',
|
||
|
typ='set',
|
||
|
ns=Namespace.BYTESTREAM,
|
||
|
callback=self._on_bytestream_set),
|
||
|
StanzaHandler(name='iq',
|
||
|
typ='result',
|
||
|
callback=self._on_result),
|
||
|
]
|
||
|
|
||
|
self.no_gupnp_reply_id = None
|
||
|
self.ok_id = None
|
||
|
self.fail_id = None
|
||
|
|
||
|
def pass_disco(self, info):
|
||
|
if Namespace.BYTESTREAM not in info.features:
|
||
|
return
|
||
|
if app.settings.get_account_setting(self._account, 'use_ft_proxies'):
|
||
|
log.info('Discovered proxy: %s', info.jid)
|
||
|
our_fjid = self._con.get_own_jid()
|
||
|
testit = app.settings.get_account_setting(
|
||
|
self._account, 'test_ft_proxies_on_startup')
|
||
|
app.proxy65_manager.resolve(
|
||
|
info.jid, self._con.connection, str(our_fjid),
|
||
|
default=self._account, testit=testit)
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
def _ft_get_receiver_jid(self, file_props):
|
||
|
if self._account == 'Local':
|
||
|
return file_props.receiver.jid
|
||
|
return file_props.receiver.jid + '/' + file_props.receiver.resource
|
||
|
|
||
|
def _ft_get_from(self, iq_obj):
|
||
|
if self._account == 'Local':
|
||
|
return iq_obj.getFrom()
|
||
|
return helpers.get_full_jid_from_iq(iq_obj)
|
||
|
|
||
|
def _ft_get_streamhost_jid_attr(self, streamhost):
|
||
|
if self._account == 'Local':
|
||
|
return streamhost.getAttr('jid')
|
||
|
return helpers.parse_jid(streamhost.getAttr('jid'))
|
||
|
|
||
|
def send_file_approval(self, file_props):
|
||
|
"""
|
||
|
Send iq, confirming that we want to download the file
|
||
|
"""
|
||
|
# user response to ConfirmationDialog may come after we've disconnected
|
||
|
if not app.account_is_available(self._account):
|
||
|
return
|
||
|
|
||
|
# file transfer initiated by a jingle session
|
||
|
log.info("send_file_approval: jingle session accept")
|
||
|
|
||
|
session = self._con.get_module('Jingle').get_jingle_session(
|
||
|
file_props.sender, file_props.sid)
|
||
|
if not session:
|
||
|
return
|
||
|
content = None
|
||
|
for content_ in session.contents.values():
|
||
|
if content_.transport.sid == file_props.transport_sid:
|
||
|
content = content_
|
||
|
break
|
||
|
|
||
|
if not content:
|
||
|
return
|
||
|
|
||
|
if not session.accepted:
|
||
|
content = session.get_content('file', content.name)
|
||
|
if content.use_security:
|
||
|
fingerprint = content.x509_fingerprint
|
||
|
if not jingle_xtls.check_cert(
|
||
|
app.get_jid_without_resource(file_props.sender),
|
||
|
fingerprint):
|
||
|
id_ = jingle_xtls.send_cert_request(
|
||
|
self._con, file_props.sender)
|
||
|
jingle_xtls.key_exchange_pend(id_,
|
||
|
content.on_cert_received, [])
|
||
|
return
|
||
|
session.approve_session()
|
||
|
|
||
|
session.approve_content('file', content.name)
|
||
|
|
||
|
def send_file_rejection(self, file_props):
|
||
|
"""
|
||
|
Inform sender that we refuse to download the file
|
||
|
|
||
|
typ is used when code = '400', in this case typ can be 'stream' for
|
||
|
invalid stream or 'profile' for invalid profile
|
||
|
"""
|
||
|
# user response to ConfirmationDialog may come after we've disconnected
|
||
|
if not app.account_is_available(self._account):
|
||
|
return
|
||
|
|
||
|
for session in self._con.get_module('Jingle').get_jingle_sessions(
|
||
|
None, file_props.sid):
|
||
|
session.cancel_session()
|
||
|
|
||
|
def send_success_connect_reply(self, streamhost):
|
||
|
"""
|
||
|
Send reply to the initiator of FT that we made a connection
|
||
|
"""
|
||
|
if not app.account_is_available(self._account):
|
||
|
return
|
||
|
if streamhost is None:
|
||
|
return
|
||
|
iq = nbxmpp.Iq(to=streamhost['initiator'],
|
||
|
typ='result',
|
||
|
frm=streamhost['target'])
|
||
|
iq.setAttr('id', streamhost['id'])
|
||
|
query = iq.setTag('query', namespace=Namespace.BYTESTREAM)
|
||
|
stream_tag = query.setTag('streamhost-used')
|
||
|
stream_tag.setAttr('jid', streamhost['jid'])
|
||
|
self._con.connection.send(iq)
|
||
|
|
||
|
def stop_all_active_file_transfers(self, contact):
|
||
|
"""
|
||
|
Stop all active transfer to or from the given contact
|
||
|
"""
|
||
|
for file_props in FilesProp.getAllFileProp():
|
||
|
if is_transfer_stopped(file_props):
|
||
|
continue
|
||
|
receiver_jid = file_props.receiver
|
||
|
if contact.get_full_jid() == receiver_jid:
|
||
|
file_props.error = -5
|
||
|
self.remove_transfer(file_props)
|
||
|
app.nec.push_incoming_event(
|
||
|
NetworkEvent('file-request-error',
|
||
|
conn=self._con,
|
||
|
jid=app.get_jid_without_resource(contact.jid),
|
||
|
file_props=file_props,
|
||
|
error_msg=''))
|
||
|
sender_jid = file_props.sender
|
||
|
if contact.get_full_jid() == sender_jid:
|
||
|
file_props.error = -3
|
||
|
self.remove_transfer(file_props)
|
||
|
|
||
|
def remove_all_transfers(self):
|
||
|
"""
|
||
|
Stop and remove all active connections from the socks5 pool
|
||
|
"""
|
||
|
for file_props in FilesProp.getAllFileProp():
|
||
|
self.remove_transfer(file_props)
|
||
|
|
||
|
def remove_transfer(self, file_props):
|
||
|
if file_props is None:
|
||
|
return
|
||
|
self.disconnect_transfer(file_props)
|
||
|
|
||
|
@staticmethod
|
||
|
def disconnect_transfer(file_props):
|
||
|
if file_props is None:
|
||
|
return
|
||
|
if file_props.hash_:
|
||
|
app.socks5queue.remove_sender(file_props.hash_)
|
||
|
|
||
|
if file_props.streamhosts:
|
||
|
for host in file_props.streamhosts:
|
||
|
if 'idx' in host and host['idx'] > 0:
|
||
|
app.socks5queue.remove_receiver(host['idx'])
|
||
|
app.socks5queue.remove_sender(host['idx'])
|
||
|
|
||
|
def _send_socks5_info(self, file_props):
|
||
|
"""
|
||
|
Send iq for the present streamhosts and proxies
|
||
|
"""
|
||
|
if not app.account_is_available(self._account):
|
||
|
return
|
||
|
receiver = file_props.receiver
|
||
|
sender = file_props.sender
|
||
|
|
||
|
sha_str = helpers.get_auth_sha(file_props.sid, sender, receiver)
|
||
|
file_props.sha_str = sha_str
|
||
|
|
||
|
port = app.settings.get('file_transfers_port')
|
||
|
listener = app.socks5queue.start_listener(
|
||
|
port,
|
||
|
sha_str,
|
||
|
self._result_socks5_sid, file_props)
|
||
|
if not listener:
|
||
|
file_props.error = -5
|
||
|
app.nec.push_incoming_event(
|
||
|
NetworkEvent('file-request-error',
|
||
|
conn=self._con,
|
||
|
jid=app.get_jid_without_resource(receiver),
|
||
|
file_props=file_props,
|
||
|
error_msg=''))
|
||
|
self._connect_error(file_props.sid,
|
||
|
error='not-acceptable',
|
||
|
error_type='modify')
|
||
|
else:
|
||
|
iq = nbxmpp.Iq(to=receiver, typ='set')
|
||
|
file_props.request_id = 'id_' + file_props.sid
|
||
|
iq.setID(file_props.request_id)
|
||
|
query = iq.setTag('query', namespace=Namespace.BYTESTREAM)
|
||
|
query.setAttr('sid', file_props.sid)
|
||
|
|
||
|
self._add_addiditional_streamhosts_to_query(query, file_props)
|
||
|
self._add_local_ips_as_streamhosts_to_query(query, file_props)
|
||
|
self._add_proxy_streamhosts_to_query(query, file_props)
|
||
|
self._add_upnp_igd_as_streamhost_to_query(query, file_props, iq)
|
||
|
# Upnp-igd is asynchronous, so it will send the iq itself
|
||
|
|
||
|
@staticmethod
|
||
|
def _add_streamhosts_to_query(query, sender, port, hosts):
|
||
|
for host in hosts:
|
||
|
streamhost = nbxmpp.Node(tag='streamhost')
|
||
|
query.addChild(node=streamhost)
|
||
|
streamhost.setAttr('port', str(port))
|
||
|
streamhost.setAttr('host', host)
|
||
|
streamhost.setAttr('jid', sender)
|
||
|
|
||
|
def _add_local_ips_as_streamhosts_to_query(self, query, file_props):
|
||
|
if not app.settings.get_account_setting(self._account,
|
||
|
'ft_send_local_ips'):
|
||
|
return
|
||
|
|
||
|
my_ip = self._con.local_address
|
||
|
if my_ip is None:
|
||
|
log.warning('No local address available')
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
# The ip we're connected to server with
|
||
|
my_ips = [my_ip]
|
||
|
# all IPs from local DNS
|
||
|
for addr in socket.getaddrinfo(socket.gethostname(), None):
|
||
|
if (not addr[4][0] in my_ips and
|
||
|
not addr[4][0].startswith('127') and
|
||
|
not addr[4][0] == '::1'):
|
||
|
my_ips.append(addr[4][0])
|
||
|
|
||
|
sender = file_props.sender
|
||
|
port = app.settings.get('file_transfers_port')
|
||
|
self._add_streamhosts_to_query(query, sender, port, my_ips)
|
||
|
except socket.gaierror:
|
||
|
from gajim.common.connection_handlers_events import InformationEvent
|
||
|
app.nec.push_incoming_event(
|
||
|
InformationEvent(None, dialog_name='wrong-host'))
|
||
|
|
||
|
def _add_addiditional_streamhosts_to_query(self, query, file_props):
|
||
|
sender = file_props.sender
|
||
|
port = app.settings.get('file_transfers_port')
|
||
|
ft_add_hosts_to_send = app.settings.get('ft_add_hosts_to_send')
|
||
|
add_hosts = []
|
||
|
if ft_add_hosts_to_send:
|
||
|
add_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')]
|
||
|
else:
|
||
|
add_hosts = []
|
||
|
self._add_streamhosts_to_query(query, sender, port, add_hosts)
|
||
|
|
||
|
def _add_upnp_igd_as_streamhost_to_query(self, query, file_props, iq):
|
||
|
my_ip = self._con.local_address
|
||
|
if my_ip is None or not app.is_installed('UPNP'):
|
||
|
log.warning('No local address available')
|
||
|
self._con.connection.send(iq)
|
||
|
return
|
||
|
|
||
|
# check if we are connected with an IPv4 address
|
||
|
try:
|
||
|
socket.inet_aton(my_ip)
|
||
|
except socket.error:
|
||
|
self._con.connection.send(iq)
|
||
|
return
|
||
|
|
||
|
def ip_is_local(ip):
|
||
|
if '.' not in ip:
|
||
|
# it's an IPv6
|
||
|
return True
|
||
|
ip_s = ip.split('.')
|
||
|
ip_l = int(ip_s[0])<<24 | int(ip_s[1])<<16 | int(ip_s[2])<<8 | \
|
||
|
int(ip_s[3])
|
||
|
# 10/8
|
||
|
if ip_l & (255<<24) == 10<<24:
|
||
|
return True
|
||
|
# 172.16/12
|
||
|
if ip_l & (255<<24 | 240<<16) == (172<<24 | 16<<16):
|
||
|
return True
|
||
|
# 192.168
|
||
|
if ip_l & (255<<24 | 255<<16) == (192<<24 | 168<<16):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
if not ip_is_local(my_ip):
|
||
|
self.connection.send(iq)
|
||
|
return
|
||
|
|
||
|
self.no_gupnp_reply_id = 0
|
||
|
|
||
|
def cleanup_gupnp():
|
||
|
if self.no_gupnp_reply_id:
|
||
|
GLib.source_remove(self.no_gupnp_reply_id)
|
||
|
self.no_gupnp_reply_id = 0
|
||
|
app.gupnp_igd.disconnect(self.ok_id)
|
||
|
app.gupnp_igd.disconnect(self.fail_id)
|
||
|
|
||
|
def success(_gupnp, _proto, ext_ip, _re, ext_port,
|
||
|
local_ip, local_port, _desc):
|
||
|
log.debug('Got GUPnP-IGD answer: external: %s:%s, internal: %s:%s',
|
||
|
ext_ip, ext_port, local_ip, local_port)
|
||
|
if local_port != app.settings.get('file_transfers_port'):
|
||
|
sender = file_props.sender
|
||
|
receiver = file_props.receiver
|
||
|
sha_str = helpers.get_auth_sha(file_props.sid,
|
||
|
sender,
|
||
|
receiver)
|
||
|
listener = app.socks5queue.start_listener(
|
||
|
local_port,
|
||
|
sha_str,
|
||
|
self._result_socks5_sid,
|
||
|
file_props.sid)
|
||
|
if listener:
|
||
|
self._add_streamhosts_to_query(query,
|
||
|
sender,
|
||
|
ext_port,
|
||
|
[ext_ip])
|
||
|
else:
|
||
|
self._add_streamhosts_to_query(query,
|
||
|
file_props.sender,
|
||
|
ext_port,
|
||
|
[ext_ip])
|
||
|
self._con.connection.send(iq)
|
||
|
cleanup_gupnp()
|
||
|
|
||
|
def fail(_gupnp, error, _proto, _ext_ip, _local_ip, _local_port, _desc):
|
||
|
log.debug('Got GUPnP-IGD error: %s', error)
|
||
|
self._con.connection.send(iq)
|
||
|
cleanup_gupnp()
|
||
|
|
||
|
def no_upnp_reply():
|
||
|
log.debug('Got not GUPnP-IGD answer')
|
||
|
# stop trying to use it
|
||
|
app.disable_dependency('UPNP')
|
||
|
self.no_gupnp_reply_id = 0
|
||
|
self._con.connection.send(iq)
|
||
|
cleanup_gupnp()
|
||
|
return False
|
||
|
|
||
|
|
||
|
self.ok_id = app.gupnp_igd.connect('mapped-external-port', success)
|
||
|
self.fail_id = app.gupnp_igd.connect('error-mapping-port', fail)
|
||
|
|
||
|
port = app.settings.get('file_transfers_port')
|
||
|
self.no_gupnp_reply_id = GLib.timeout_add_seconds(10, no_upnp_reply)
|
||
|
app.gupnp_igd.add_port('TCP',
|
||
|
0,
|
||
|
my_ip,
|
||
|
port,
|
||
|
3600,
|
||
|
'Gajim file transfer')
|
||
|
|
||
|
def _add_proxy_streamhosts_to_query(self, query, file_props):
|
||
|
proxyhosts = self._get_file_transfer_proxies_from_config(file_props)
|
||
|
if proxyhosts:
|
||
|
file_props.proxy_receiver = file_props.receiver
|
||
|
file_props.proxy_sender = file_props.sender
|
||
|
file_props.proxyhosts = proxyhosts
|
||
|
|
||
|
for proxyhost in proxyhosts:
|
||
|
self._add_streamhosts_to_query(query,
|
||
|
proxyhost['jid'],
|
||
|
proxyhost['port'],
|
||
|
[proxyhost['host']])
|
||
|
|
||
|
def _get_file_transfer_proxies_from_config(self, file_props):
|
||
|
configured_proxies = app.settings.get_account_setting(
|
||
|
self._account, 'file_transfer_proxies')
|
||
|
shall_use_proxies = app.settings.get_account_setting(
|
||
|
self._account, 'use_ft_proxies')
|
||
|
if shall_use_proxies:
|
||
|
proxyhost_dicts = []
|
||
|
proxies = []
|
||
|
if configured_proxies:
|
||
|
proxies = [item.strip() for item in
|
||
|
configured_proxies.split(',')]
|
||
|
default_proxy = app.proxy65_manager.get_default_for_name(
|
||
|
self._account)
|
||
|
if default_proxy:
|
||
|
# add/move default proxy at top of the others
|
||
|
if default_proxy in proxies:
|
||
|
proxies.remove(default_proxy)
|
||
|
proxies.insert(0, default_proxy)
|
||
|
|
||
|
for proxy in proxies:
|
||
|
(host, _port, jid) = app.proxy65_manager.get_proxy(
|
||
|
proxy, self._account)
|
||
|
if not host:
|
||
|
continue
|
||
|
host_dict = {
|
||
|
'state': 0,
|
||
|
'target': file_props.receiver,
|
||
|
'id': file_props.sid,
|
||
|
'sid': file_props.sid,
|
||
|
'initiator': proxy,
|
||
|
'host': host,
|
||
|
'port': str(_port),
|
||
|
'jid': jid
|
||
|
}
|
||
|
proxyhost_dicts.append(host_dict)
|
||
|
return proxyhost_dicts
|
||
|
|
||
|
return []
|
||
|
|
||
|
@staticmethod
|
||
|
def _result_socks5_sid(sid, hash_id):
|
||
|
"""
|
||
|
Store the result of SHA message from auth
|
||
|
"""
|
||
|
file_props = FilesProp.getFilePropBySid(sid)
|
||
|
file_props.hash_ = hash_id
|
||
|
|
||
|
def _connect_error(self, sid, error, error_type, msg=None):
|
||
|
"""
|
||
|
Called when there is an error establishing BS connection, or when
|
||
|
connection is rejected
|
||
|
"""
|
||
|
if not app.account_is_available(self._account):
|
||
|
return
|
||
|
file_props = FilesProp.getFileProp(self._account, sid)
|
||
|
if file_props is None:
|
||
|
log.error('can not send iq error on failed transfer')
|
||
|
return
|
||
|
if file_props.type_ == 's':
|
||
|
to = file_props.receiver
|
||
|
else:
|
||
|
to = file_props.sender
|
||
|
iq = nbxmpp.Iq(to=to, typ='error')
|
||
|
iq.setAttr('id', file_props.request_id)
|
||
|
err = iq.setTag('error')
|
||
|
err.setAttr('type', error_type)
|
||
|
err.setTag(error, namespace=Namespace.STANZAS)
|
||
|
self._con.connection.send(iq)
|
||
|
if msg:
|
||
|
self.disconnect_transfer(file_props)
|
||
|
file_props.error = -3
|
||
|
app.nec.push_incoming_event(
|
||
|
NetworkEvent('file-request-error',
|
||
|
conn=self._con,
|
||
|
jid=app.get_jid_without_resource(to),
|
||
|
file_props=file_props,
|
||
|
error_msg=msg))
|
||
|
|
||
|
def _proxy_auth_ok(self, proxy):
|
||
|
"""
|
||
|
Called after authentication to proxy server
|
||
|
"""
|
||
|
if not app.account_is_available(self._account):
|
||
|
return
|
||
|
file_props = FilesProp.getFileProp(self._account, proxy['sid'])
|
||
|
iq = nbxmpp.Iq(to=proxy['initiator'], typ='set')
|
||
|
auth_id = "au_" + proxy['sid']
|
||
|
iq.setID(auth_id)
|
||
|
query = iq.setTag('query', namespace=Namespace.BYTESTREAM)
|
||
|
query.setAttr('sid', proxy['sid'])
|
||
|
activate = query.setTag('activate')
|
||
|
activate.setData(file_props.proxy_receiver)
|
||
|
iq.setID(auth_id)
|
||
|
self._con.connection.send(iq)
|
||
|
|
||
|
def _on_bytestream_error(self, _con, iq_obj, _properties):
|
||
|
id_ = iq_obj.getAttr('id')
|
||
|
frm = helpers.get_full_jid_from_iq(iq_obj)
|
||
|
query = iq_obj.getTag('query')
|
||
|
app.proxy65_manager.error_cb(frm, query)
|
||
|
jid = helpers.get_jid_from_iq(iq_obj)
|
||
|
id_ = id_[3:]
|
||
|
file_props = FilesProp.getFilePropBySid(id_)
|
||
|
if not file_props:
|
||
|
return
|
||
|
file_props.error = -4
|
||
|
app.nec.push_incoming_event(
|
||
|
NetworkEvent('file-request-error',
|
||
|
conn=self._con,
|
||
|
jid=app.get_jid_without_resource(jid),
|
||
|
file_props=file_props,
|
||
|
error_msg=''))
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
def _on_bytestream_set(self, con, iq_obj, _properties):
|
||
|
target = iq_obj.getAttr('to')
|
||
|
id_ = iq_obj.getAttr('id')
|
||
|
query = iq_obj.getTag('query')
|
||
|
sid = query.getAttr('sid')
|
||
|
file_props = FilesProp.getFileProp(self._account, sid)
|
||
|
streamhosts = []
|
||
|
for item in query.getChildren():
|
||
|
if item.getName() == 'streamhost':
|
||
|
host_dict = {
|
||
|
'state': 0,
|
||
|
'target': target,
|
||
|
'id': id_,
|
||
|
'sid': sid,
|
||
|
'initiator': self._ft_get_from(iq_obj)
|
||
|
}
|
||
|
for attr in item.getAttrs():
|
||
|
host_dict[attr] = item.getAttr(attr)
|
||
|
if 'host' not in host_dict:
|
||
|
continue
|
||
|
if 'jid' not in host_dict:
|
||
|
continue
|
||
|
if 'port' not in host_dict:
|
||
|
continue
|
||
|
streamhosts.append(host_dict)
|
||
|
file_props = FilesProp.getFilePropBySid(sid)
|
||
|
if file_props is not None:
|
||
|
if file_props.type_ == 's': # FIXME: remove fast xmlns
|
||
|
# only psi do this
|
||
|
if file_props.streamhosts:
|
||
|
file_props.streamhosts.extend(streamhosts)
|
||
|
else:
|
||
|
file_props.streamhosts = streamhosts
|
||
|
app.socks5queue.connect_to_hosts(
|
||
|
self._account,
|
||
|
sid,
|
||
|
self.send_success_connect_reply,
|
||
|
None)
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
else:
|
||
|
log.warning('Gajim got streamhosts for unknown transfer. '
|
||
|
'Ignoring it.')
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
file_props.streamhosts = streamhosts
|
||
|
def _connection_error(sid):
|
||
|
self._connect_error(sid,
|
||
|
'item-not-found',
|
||
|
'cancel',
|
||
|
msg='Could not connect to given hosts')
|
||
|
if file_props.type_ == 'r':
|
||
|
app.socks5queue.connect_to_hosts(
|
||
|
self._account,
|
||
|
sid,
|
||
|
self.send_success_connect_reply,
|
||
|
_connection_error)
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
def _on_result(self, _con, iq_obj, _properties):
|
||
|
# if we want to respect xep-0065 we have to check for proxy
|
||
|
# activation result in any result iq
|
||
|
real_id = iq_obj.getAttr('id')
|
||
|
if real_id is None:
|
||
|
log.warning('Invalid IQ without id attribute:\n%s', iq_obj)
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
if real_id is None or not real_id.startswith('au_'):
|
||
|
return
|
||
|
frm = self._ft_get_from(iq_obj)
|
||
|
id_ = real_id[3:]
|
||
|
file_props = FilesProp.getFilePropByTransportSid(self._account, id_)
|
||
|
if file_props.streamhost_used:
|
||
|
for host in file_props.proxyhosts:
|
||
|
if host['initiator'] == frm and 'idx' in host:
|
||
|
app.socks5queue.activate_proxy(host['idx'])
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
def _on_bytestream_result(self, con, iq_obj, _properties):
|
||
|
frm = self._ft_get_from(iq_obj)
|
||
|
real_id = iq_obj.getAttr('id')
|
||
|
query = iq_obj.getTag('query')
|
||
|
app.proxy65_manager.resolve_result(frm, query)
|
||
|
|
||
|
try:
|
||
|
streamhost = query.getTag('streamhost-used')
|
||
|
except Exception: # this bytestream result is not what we need
|
||
|
pass
|
||
|
id_ = real_id[3:]
|
||
|
file_props = FilesProp.getFileProp(self._account, id_)
|
||
|
if file_props is None:
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
if streamhost is None:
|
||
|
# proxy approves the activate query
|
||
|
if real_id.startswith('au_'):
|
||
|
if file_props.streamhost_used is False:
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
if not file_props.proxyhosts:
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
for host in file_props.proxyhosts:
|
||
|
if host['initiator'] == frm and \
|
||
|
query.getAttr('sid') == file_props.sid:
|
||
|
app.socks5queue.activate_proxy(host['idx'])
|
||
|
break
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
jid = self._ft_get_streamhost_jid_attr(streamhost)
|
||
|
if file_props.streamhost_used is True:
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
if real_id.startswith('au_'):
|
||
|
if file_props.stopped:
|
||
|
self.remove_transfer(file_props)
|
||
|
else:
|
||
|
app.socks5queue.send_file(file_props, self._account, 'server')
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
proxy = None
|
||
|
if file_props.proxyhosts:
|
||
|
for proxyhost in file_props.proxyhosts:
|
||
|
if proxyhost['jid'] == jid:
|
||
|
proxy = proxyhost
|
||
|
|
||
|
if file_props.stopped:
|
||
|
self.remove_transfer(file_props)
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
if proxy is not None:
|
||
|
file_props.streamhost_used = True
|
||
|
file_props.streamhosts.append(proxy)
|
||
|
file_props.is_a_proxy = True
|
||
|
idx = app.socks5queue.idx
|
||
|
sender = Socks5SenderClient(app.idlequeue,
|
||
|
idx,
|
||
|
app.socks5queue,
|
||
|
_sock=None,
|
||
|
host=str(proxy['host']),
|
||
|
port=int(proxy['port']),
|
||
|
fingerprint=None,
|
||
|
connected=False,
|
||
|
file_props=file_props)
|
||
|
sender.streamhost = proxy
|
||
|
app.socks5queue.add_sockobj(self._account, sender)
|
||
|
proxy['idx'] = sender.queue_idx
|
||
|
app.socks5queue.on_success[file_props.sid] = self._proxy_auth_ok
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
if file_props.stopped:
|
||
|
self.remove_transfer(file_props)
|
||
|
else:
|
||
|
app.socks5queue.send_file(file_props, self._account, 'server')
|
||
|
|
||
|
raise nbxmpp.NodeProcessed
|
||
|
|
||
|
|
||
|
def get_instance(*args, **kwargs):
|
||
|
return Bytestream(*args, **kwargs), 'Bytestream'
|