merge jingleFT branch! Thanks to Zhenchao Li and Jefry Lagrange for their work.
This commit is contained in:
commit
6d178205fd
26 changed files with 2970 additions and 594 deletions
|
@ -53,7 +53,7 @@ from common.logger import constants
|
||||||
from common.pep import MOODS, ACTIVITIES
|
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_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||||
from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
|
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.xmpp.protocol import NS_CHATSTATES
|
from common.xmpp.protocol import NS_CHATSTATES
|
||||||
from common.connection_handlers_events import MessageOutgoingEvent
|
from common.connection_handlers_events import MessageOutgoingEvent
|
||||||
from common.exceptions import GajimGeneralException
|
from common.exceptions import GajimGeneralException
|
||||||
|
@ -1722,13 +1722,13 @@ class ChatControl(ChatControlBase):
|
||||||
self._video_button.set_sensitive(self.video_available)
|
self._video_button.set_sensitive(self.video_available)
|
||||||
|
|
||||||
# Send file
|
# Send file
|
||||||
if self.contact.supports(NS_FILE) and (self.type_id == 'chat' or \
|
if (self.contact.supports(NS_FILE) or self.contact.supports(NS_JINGLE_FILE_TRANSFER)) and (self.type_id == 'chat' or \
|
||||||
self.gc_contact.resource):
|
self.gc_contact.resource):
|
||||||
self._send_file_button.set_sensitive(True)
|
self._send_file_button.set_sensitive(True)
|
||||||
self._send_file_button.set_tooltip_text('')
|
self._send_file_button.set_tooltip_text('')
|
||||||
else:
|
else:
|
||||||
self._send_file_button.set_sensitive(False)
|
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(_(
|
self._send_file_button.set_tooltip_text(_(
|
||||||
"This contact does not support file transfer."))
|
"This contact does not support file transfer."))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -38,10 +38,10 @@ import logging
|
||||||
log = logging.getLogger('gajim.c.caps_cache')
|
log = logging.getLogger('gajim.c.caps_cache')
|
||||||
|
|
||||||
from common.xmpp import (NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES,
|
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
|
# Features where we cannot safely assume that the other side supports them
|
||||||
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION,
|
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
|
# Query entry status codes
|
||||||
NEW = 0
|
NEW = 0
|
||||||
|
|
|
@ -30,6 +30,7 @@ import stat
|
||||||
|
|
||||||
from common import gajim
|
from common import gajim
|
||||||
import logger
|
import logger
|
||||||
|
from common import jingle_xtls
|
||||||
|
|
||||||
# DO NOT MOVE ABOVE OF import gajim
|
# DO NOT MOVE ABOVE OF import gajim
|
||||||
import sqlite3 as sqlite
|
import sqlite3 as sqlite
|
||||||
|
@ -266,6 +267,8 @@ def check_and_possibly_create_paths():
|
||||||
MY_DATA = configpaths.gajimpaths['MY_DATA']
|
MY_DATA = configpaths.gajimpaths['MY_DATA']
|
||||||
MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
|
MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
|
||||||
MY_CACHE = configpaths.gajimpaths['MY_CACHE']
|
MY_CACHE = configpaths.gajimpaths['MY_CACHE']
|
||||||
|
XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS']
|
||||||
|
LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT']
|
||||||
|
|
||||||
PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
|
PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
|
||||||
|
|
||||||
|
@ -342,6 +345,17 @@ def check_and_possibly_create_paths():
|
||||||
print _('%s is a directory but should be a file') % CACHE_DB_PATH
|
print _('%s is a directory but should be a file') % CACHE_DB_PATH
|
||||||
print _('Gajim will now exit')
|
print _('Gajim will now exit')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
if not os.path.exists(XTLS_CERTS):
|
||||||
|
create_path(XTLS_CERTS)
|
||||||
|
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):
|
def create_path(directory):
|
||||||
head, tail = os.path.split(directory)
|
head, tail = os.path.split(directory)
|
||||||
|
|
|
@ -141,7 +141,7 @@ class ConfigPaths:
|
||||||
d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
|
d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
|
||||||
'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets',
|
'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',
|
||||||
'PLUGINS_USER': u'plugins'}
|
'PLUGINS_USER': u'plugins', 'MY_PEER_CERTS': u'certs'}
|
||||||
for name in d:
|
for name in d:
|
||||||
self.add(name, TYPE_DATA, windowsify(d[name]))
|
self.add(name, TYPE_DATA, windowsify(d[name]))
|
||||||
|
|
||||||
|
@ -151,6 +151,7 @@ class ConfigPaths:
|
||||||
self.add(name, TYPE_CACHE, windowsify(d[name]))
|
self.add(name, TYPE_CACHE, windowsify(d[name]))
|
||||||
|
|
||||||
self.add('MY_CONFIG', TYPE_CONFIG, '')
|
self.add('MY_CONFIG', TYPE_CONFIG, '')
|
||||||
|
self.add('MY_CERT', TYPE_CONFIG, '')
|
||||||
|
|
||||||
basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir))
|
basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir))
|
||||||
self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
|
self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
|
||||||
|
|
|
@ -45,6 +45,7 @@ from common import helpers
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import exceptions
|
from common import exceptions
|
||||||
from common import dataforms
|
from common import dataforms
|
||||||
|
from common import jingle_xtls
|
||||||
from common.commands import ConnectionCommands
|
from common.commands import ConnectionCommands
|
||||||
from common.pubsub import ConnectionPubSub
|
from common.pubsub import ConnectionPubSub
|
||||||
from common.pep import ConnectionPEP
|
from common.pep import ConnectionPEP
|
||||||
|
@ -189,7 +190,10 @@ class ConnectionDisco:
|
||||||
query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[
|
query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[
|
||||||
0])
|
0])
|
||||||
for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI,
|
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, 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 = common.xmpp.Node('feature')
|
||||||
feature.setAttr('var', f)
|
feature.setAttr('var', f)
|
||||||
query.addChild(node=feature)
|
query.addChild(node=feature)
|
||||||
|
@ -1979,10 +1983,37 @@ ConnectionJingle, ConnectionIBBytestream):
|
||||||
gajim.nec.push_incoming_event(SearchFormReceivedEvent(None,
|
gajim.nec.push_incoming_event(SearchFormReceivedEvent(None,
|
||||||
conn=self, stanza=iq_obj))
|
conn=self, stanza=iq_obj))
|
||||||
|
|
||||||
def _StreamCB(self, con, iq_obj):
|
def _search_fields_received(self, con, iq_obj):
|
||||||
log.debug('StreamCB')
|
jid = jid = helpers.get_jid_from_iq(iq_obj)
|
||||||
gajim.nec.push_incoming_event(StreamReceivedEvent(None,
|
tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH)
|
||||||
conn=self, stanza=iq_obj))
|
if not tag:
|
||||||
|
self.dispatch('SEARCH_FORM', (jid, None, False))
|
||||||
|
return
|
||||||
|
df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
|
||||||
|
if df:
|
||||||
|
self.dispatch('SEARCH_FORM', (jid, df, True))
|
||||||
|
return
|
||||||
|
df = {}
|
||||||
|
for i in iq_obj.getQueryPayload():
|
||||||
|
df[i.getName()] = i.getData()
|
||||||
|
self.dispatch('SEARCH_FORM', (jid, df, False))
|
||||||
|
|
||||||
|
def _PubkeyGetCB(self, con, iq_obj):
|
||||||
|
log.info('PubkeyGetCB')
|
||||||
|
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 = 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'):
|
||||||
|
# disconnected because of a resource conflict
|
||||||
|
self.dispatch('RESOURCE_CONFLICT', ())
|
||||||
|
|
||||||
def _register_handlers(self, con, con_type):
|
def _register_handlers(self, con, con_type):
|
||||||
# try to find another way to register handlers in each class
|
# try to find another way to register handlers in each class
|
||||||
|
@ -2070,5 +2101,7 @@ ConnectionJingle, ConnectionIBBytestream):
|
||||||
con.RegisterHandler('iq', self._ResultCB, 'result')
|
con.RegisterHandler('iq', self._ResultCB, 'result')
|
||||||
con.RegisterHandler('presence', self._StanzaArrivedCB)
|
con.RegisterHandler('presence', self._StanzaArrivedCB)
|
||||||
con.RegisterHandler('message', self._StanzaArrivedCB)
|
con.RegisterHandler('message', self._StanzaArrivedCB)
|
||||||
con.RegisterHandler('unknown', self._StreamCB,
|
con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
|
||||||
common.xmpp.NS_XMPP_STREAMS, xmlns=common.xmpp.NS_STREAMS)
|
con.RegisterHandler('iq', self._PubkeyGetCB, 'get', common.xmpp.NS_PUBKEY_PUBKEY)
|
||||||
|
con.RegisterHandler('iq', self._PubkeyResultCB, 'result', common.xmpp.NS_PUBKEY_PUBKEY)
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ from common.zeroconf import zeroconf
|
||||||
from common.logger import LOG_DB_PATH
|
from common.logger import LOG_DB_PATH
|
||||||
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
|
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
|
||||||
from common.xmpp.protocol import NS_CHATSTATES
|
from common.xmpp.protocol import NS_CHATSTATES
|
||||||
|
from common.jingle_transport import JingleTransportSocks5
|
||||||
|
|
||||||
import gtkgui_helpers
|
import gtkgui_helpers
|
||||||
|
|
||||||
|
@ -1392,6 +1393,16 @@ class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
|
self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
|
||||||
self.sid = self.jingle_session.sid
|
self.sid = self.jingle_session.sid
|
||||||
return True
|
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):
|
class JingleErrorReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
name = 'jingle-error-received'
|
name = 'jingle-error-received'
|
||||||
|
@ -1901,6 +1912,10 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
name = 'file-request-received'
|
name = 'file-request-received'
|
||||||
base_network_events = []
|
base_network_events = []
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.jingle_content = None
|
||||||
|
self.FT_content = None
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.get_id()
|
self.get_id()
|
||||||
self.fjid = self.conn._ft_get_from(self.stanza)
|
self.fjid = self.conn._ft_get_from(self.stanza)
|
||||||
|
@ -1908,28 +1923,36 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
self.file_props = {'type': 'r'}
|
self.file_props = {'type': 'r'}
|
||||||
self.file_props['sender'] = self.fjid
|
self.file_props['sender'] = self.fjid
|
||||||
self.file_props['request-id'] = self.id_
|
self.file_props['request-id'] = self.id_
|
||||||
si = self.stanza.getTag('si')
|
if self.jingle_content:
|
||||||
profile = si.getAttr('profile')
|
self.file_props['session-type'] = 'jingle'
|
||||||
if profile != xmpp.NS_FILE:
|
self.file_props['stream-methods'] = xmpp.NS_BYTESTREAM
|
||||||
self.conn.send_file_rejection(self.file_props, code='400', typ='profile')
|
file_tag = self.jingle_content.getTag('description').getTag(
|
||||||
raise xmpp.NodeProcessed
|
'offer').getTag('file')
|
||||||
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:
|
else:
|
||||||
self.conn.send_file_rejection(self.file_props, code='400', typ='stream')
|
si = self.stanza.getTag('si')
|
||||||
raise xmpp.NodeProcessed
|
profile = si.getAttr('profile')
|
||||||
file_tag = si.getTag('file')
|
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():
|
for attribute in file_tag.getAttrs():
|
||||||
if attribute in ('name', 'size', 'hash', 'date'):
|
if attribute in ('name', 'size', 'hash', 'date'):
|
||||||
val = file_tag.getAttr(attribute)
|
val = file_tag.getAttr(attribute)
|
||||||
|
@ -1940,13 +1963,41 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
if file_desc_tag is not None:
|
if file_desc_tag is not None:
|
||||||
self.file_props['desc'] = file_desc_tag.getData()
|
self.file_props['desc'] = file_desc_tag.getData()
|
||||||
|
|
||||||
mime_type = si.getAttr('mime-type')
|
if not self.jingle_content:
|
||||||
if mime_type is not None:
|
mime_type = si.getAttr('mime-type')
|
||||||
self.file_props['mime-type'] = 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['receiver'] = self.conn._ft_get_our_jid()
|
||||||
self.file_props['sid'] = unicode(si.getAttr('id'))
|
|
||||||
self.file_props['transfered_size'] = []
|
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
|
return True
|
||||||
|
|
||||||
class FileRequestErrorEvent(nec.NetworkIncomingEvent):
|
class FileRequestErrorEvent(nec.NetworkIncomingEvent):
|
||||||
|
|
|
@ -62,6 +62,7 @@ MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
|
||||||
MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
|
MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
|
||||||
MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
|
MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
|
||||||
MY_CACERTS = gajimpaths['MY_CACERTS']
|
MY_CACERTS = gajimpaths['MY_CACERTS']
|
||||||
|
MY_PEER_CERTS_PATH = gajimpaths['MY_PEER_CERTS']
|
||||||
TMP = gajimpaths['TMP']
|
TMP = gajimpaths['TMP']
|
||||||
DATA_DIR = gajimpaths['DATA']
|
DATA_DIR = gajimpaths['DATA']
|
||||||
ICONS_DIR = gajimpaths['ICONS']
|
ICONS_DIR = gajimpaths['ICONS']
|
||||||
|
@ -69,6 +70,7 @@ HOME_DIR = gajimpaths['HOME']
|
||||||
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
|
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
|
||||||
gajimpaths['PLUGINS_USER']]
|
gajimpaths['PLUGINS_USER']]
|
||||||
PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
|
PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
|
||||||
|
MY_CERT_DIR = gajimpaths['MY_CERT']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
||||||
|
@ -206,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,
|
'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',
|
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,
|
'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
|
# Optional features gajim supports per account
|
||||||
gajim_optional_features = {}
|
gajim_optional_features = {}
|
||||||
|
|
|
@ -1336,6 +1336,10 @@ 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_AUDIO)
|
||||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
|
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_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.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
|
||||||
gajim.gajim_common_features + gajim.gajim_optional_features[a])
|
gajim.gajim_common_features + gajim.gajim_optional_features[a])
|
||||||
# re-send presence with new hash
|
# re-send presence with new hash
|
||||||
|
|
|
@ -37,7 +37,11 @@ import gajim
|
||||||
from jingle_session import JingleSession, JingleStates
|
from jingle_session import JingleSession, JingleStates
|
||||||
if gajim.HAVE_FARSTREAM:
|
if gajim.HAVE_FARSTREAM:
|
||||||
from jingle_rtp import JingleAudio, JingleVideo
|
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')
|
||||||
|
|
||||||
class ConnectionJingle(object):
|
class ConnectionJingle(object):
|
||||||
"""
|
"""
|
||||||
|
@ -75,27 +79,38 @@ class ConnectionJingle(object):
|
||||||
"""
|
"""
|
||||||
# get data
|
# get data
|
||||||
jid = helpers.get_full_jid_from_iq(stanza)
|
jid = helpers.get_full_jid_from_iq(stanza)
|
||||||
id = stanza.getID()
|
id_ = stanza.getID()
|
||||||
|
|
||||||
if (jid, id) in self.__iq_responses.keys():
|
if (jid, id_) in self.__iq_responses.keys():
|
||||||
self.__iq_responses[(jid, id)].on_stanza(stanza)
|
self.__iq_responses[(jid, id_)].on_stanza(stanza)
|
||||||
del self.__iq_responses[(jid, id)]
|
del self.__iq_responses[(jid, id_)]
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
|
||||||
jingle = stanza.getTag('jingle')
|
jingle = stanza.getTag('jingle')
|
||||||
if not jingle: return
|
# a jingle element is not necessary in iq-result stanza
|
||||||
sid = jingle.getAttr('sid')
|
# 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
|
# 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...
|
#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
|
self._sessions[sid] = newjingle
|
||||||
|
|
||||||
# we already have such session in dispatcher...
|
# we already have such session in dispatcher...
|
||||||
|
self._sessions[sid].collect_iq_id(id_)
|
||||||
self._sessions[sid].on_stanza(stanza)
|
self._sessions[sid].on_stanza(stanza)
|
||||||
# Delete invalid/unneeded sessions
|
# 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)
|
self.delete_jingle_session(sid)
|
||||||
|
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
@ -126,16 +141,55 @@ class ConnectionJingle(object):
|
||||||
jingle.start_session()
|
jingle.start_session()
|
||||||
return jingle.sid
|
return jingle.sid
|
||||||
|
|
||||||
|
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))
|
||||||
|
if contact is None:
|
||||||
|
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):
|
||||||
|
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.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):
|
def iter_jingle_sessions(self, jid, sid=None, media=None):
|
||||||
if sid:
|
if sid:
|
||||||
return (session for session in self._sessions.values() if session.sid == sid)
|
return (session for session in self._sessions.values() if \
|
||||||
sessions = (session for session in self._sessions.values() if session.peerjid == jid)
|
session.sid == sid)
|
||||||
|
sessions = (session for session in self._sessions.values() if \
|
||||||
|
session.peerjid == jid)
|
||||||
if media:
|
if media:
|
||||||
if media not in ('audio', 'video'):
|
if media not in ('audio', 'video', 'file'):
|
||||||
return tuple()
|
return tuple()
|
||||||
else:
|
else:
|
||||||
return (session for session in sessions if session.get_content(media))
|
return (session for session in sessions if \
|
||||||
|
session.get_content(media))
|
||||||
else:
|
else:
|
||||||
return sessions
|
return sessions
|
||||||
|
|
||||||
|
@ -147,6 +201,8 @@ class ConnectionJingle(object):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
elif media:
|
elif media:
|
||||||
|
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):
|
if session.peerjid == jid and session.get_content(media):
|
||||||
return session
|
return session
|
||||||
|
|
|
@ -16,6 +16,7 @@ Handles Jingle contents (XEP 0166)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import xmpp
|
import xmpp
|
||||||
|
from jingle_transport import JingleTransportIBB
|
||||||
|
|
||||||
contents = {}
|
contents = {}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ class JingleContent(object):
|
||||||
'session-initiate': [self.__on_transport_info],
|
'session-initiate': [self.__on_transport_info],
|
||||||
'session-terminate': [],
|
'session-terminate': [],
|
||||||
'transport-info': [self.__on_transport_info],
|
'transport-info': [self.__on_transport_info],
|
||||||
'transport-replace': [],
|
'transport-replace': [self.__on_transport_replace],
|
||||||
'transport-accept': [],
|
'transport-accept': [],
|
||||||
'transport-reject': [],
|
'transport-reject': [],
|
||||||
'iq-result': [],
|
'iq-result': [],
|
||||||
|
@ -99,7 +100,7 @@ class JingleContent(object):
|
||||||
"""
|
"""
|
||||||
Add a list of candidates to the list of remote candidates
|
Add a list of candidates to the list of remote candidates
|
||||||
"""
|
"""
|
||||||
pass
|
self.transport.remote_candidates = candidates
|
||||||
|
|
||||||
def on_stanza(self, stanza, content, error, action):
|
def on_stanza(self, stanza, content, error, action):
|
||||||
"""
|
"""
|
||||||
|
@ -109,12 +110,15 @@ class JingleContent(object):
|
||||||
for callback in self.callbacks[action]:
|
for callback in self.callbacks[action]:
|
||||||
callback(stanza, content, error, 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):
|
def __on_transport_info(self, stanza, content, error, action):
|
||||||
"""
|
"""
|
||||||
Got a new transport candidate
|
Got a new transport candidate
|
||||||
"""
|
"""
|
||||||
candidates = self.transport.parse_transport_stanza(
|
candidates = self.transport.parse_transport_stanza(
|
||||||
content.getTag('transport'))
|
content.getTag('transport'))
|
||||||
if candidates:
|
if candidates:
|
||||||
self.add_remote_candidates(candidates)
|
self.add_remote_candidates(candidates)
|
||||||
|
|
||||||
|
@ -134,6 +138,17 @@ class JingleContent(object):
|
||||||
content.addChild(node=self.transport.make_transport([candidate]))
|
content.addChild(node=self.transport.make_transport([candidate]))
|
||||||
self.session.send_transport_info(content)
|
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(add_candidates=False)
|
||||||
|
tp.addChild(name='candidate-error')
|
||||||
|
content.addChild(node=tp)
|
||||||
|
self.session.send_transport_info(content)
|
||||||
|
|
||||||
|
|
||||||
def send_description_info(self):
|
def send_description_info(self):
|
||||||
content = self.__content()
|
content = self.__content()
|
||||||
self._fill_content(content)
|
self._fill_content(content)
|
||||||
|
|
375
src/common/jingle_ft.py
Normal file
375
src/common/jingle_ft.py
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
# -*- 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 gajim
|
||||||
|
import xmpp
|
||||||
|
from jingle_content import contents, JingleContent
|
||||||
|
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
|
||||||
|
STATE_INITIALIZED = 1
|
||||||
|
# We send the candidates and we are waiting for a reply
|
||||||
|
STATE_CAND_SENT = 2
|
||||||
|
# We received the candidates and we are waiting to reply
|
||||||
|
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 = 4
|
||||||
|
STATE_TRANSPORT_REPLACE = 5
|
||||||
|
# We are transfering the file
|
||||||
|
STATE_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.file_props = file_props
|
||||||
|
if file_props is None:
|
||||||
|
self.weinitiate = False
|
||||||
|
else:
|
||||||
|
self.weinitiate = True
|
||||||
|
|
||||||
|
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['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)
|
||||||
|
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'
|
||||||
|
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,
|
||||||
|
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.session.hash_algo == None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
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_)
|
||||||
|
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',
|
||||||
|
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
|
||||||
|
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')
|
||||||
|
response.delChild(response.getQuery())
|
||||||
|
con.connection.send(response)
|
||||||
|
# We send the file
|
||||||
|
self.__state_changed(STATE_TRANSFERING)
|
||||||
|
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')
|
||||||
|
response.delChild(response.getQuery())
|
||||||
|
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'
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
self.__state_changed(STATE_TRANSFERING)
|
||||||
|
raise xmpp.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")
|
||||||
|
|
||||||
|
if content.getTag('transport').getTag('candidate-error'):
|
||||||
|
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 xmpp.NodeProcessed
|
||||||
|
else:
|
||||||
|
args = {'candError' : True}
|
||||||
|
self.__state_changed(STATE_CAND_RECEIVED, args)
|
||||||
|
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
|
||||||
|
|
||||||
|
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.__state_changed(STATE_TRANSFERING)
|
||||||
|
raise xmpp.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
|
||||||
|
|
||||||
|
|
||||||
|
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.__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:
|
||||||
|
self.__state_changed(STATE_CAND_SENT, args)
|
||||||
|
|
||||||
|
def _fill_content(self, content):
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
if self.weinitiate:
|
||||||
|
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||||
|
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')
|
||||||
|
|
||||||
|
if not listener:
|
||||||
|
# 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:
|
||||||
|
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 get_content(desc):
|
||||||
|
return JingleFileTransfer
|
||||||
|
|
||||||
|
contents[xmpp.NS_JINGLE_FILE_TRANSFER] = get_content
|
244
src/common/jingle_ftstates.py
Normal file
244
src/common/jingle_ftstates.py
Normal file
|
@ -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):
|
||||||
|
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
|
||||||
|
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()
|
|
@ -38,7 +38,7 @@ log = logging.getLogger('gajim.c.jingle_rtp')
|
||||||
class JingleRTPContent(JingleContent):
|
class JingleRTPContent(JingleContent):
|
||||||
def __init__(self, session, media, transport=None):
|
def __init__(self, session, media, transport=None):
|
||||||
if transport is None:
|
if transport is None:
|
||||||
transport = JingleTransportICEUDP()
|
transport = JingleTransportICEUDP(None)
|
||||||
JingleContent.__init__(self, session, transport)
|
JingleContent.__init__(self, session, transport)
|
||||||
self.media = media
|
self.media = media
|
||||||
self._dtmf_running = False
|
self._dtmf_running = False
|
||||||
|
|
|
@ -28,9 +28,13 @@ Handles Jingle sessions (XEP 0166)
|
||||||
|
|
||||||
import gajim #Get rid of that?
|
import gajim #Get rid of that?
|
||||||
import xmpp
|
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 get_jingle_content, JingleContentSetupException
|
||||||
|
from jingle_content import JingleContent
|
||||||
|
from jingle_ft import STATE_TRANSPORT_REPLACE
|
||||||
from common.connection_handlers_events import *
|
from common.connection_handlers_events import *
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger("gajim.c.jingle_session")
|
||||||
|
|
||||||
# FIXME: Move it to JingleSession.States?
|
# FIXME: Move it to JingleSession.States?
|
||||||
class JingleStates(object):
|
class JingleStates(object):
|
||||||
|
@ -59,7 +63,7 @@ class JingleSession(object):
|
||||||
negotiated between an initiator and a responder.
|
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,
|
con -- connection object,
|
||||||
weinitiate -- boolean, are we the initiator?
|
weinitiate -- boolean, are we the initiator?
|
||||||
|
@ -85,8 +89,21 @@ class JingleSession(object):
|
||||||
sid = con.connection.getAnID()
|
sid = con.connection.getAnID()
|
||||||
self.sid = sid # sessionid
|
self.sid = sid # sessionid
|
||||||
|
|
||||||
self.accepted = True # is this session accepted by user
|
# 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
|
||||||
|
# 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
|
||||||
|
# Tells whether this session is a file transfer or not
|
||||||
|
self.session_type_FT = False
|
||||||
# callbacks to call on proper contents
|
# callbacks to call on proper contents
|
||||||
# use .prepend() to add new callbacks, especially when you're going
|
# use .prepend() to add new callbacks, especially when you're going
|
||||||
# to send error instead of ack
|
# to send error instead of ack
|
||||||
|
@ -101,7 +118,7 @@ class JingleSession(object):
|
||||||
'description-info': [self.__broadcast, self.__ack], #TODO
|
'description-info': [self.__broadcast, self.__ack], #TODO
|
||||||
'security-info': [self.__ack], #TODO
|
'security-info': [self.__ack], #TODO
|
||||||
'session-accept': [self.__on_session_accept, self.__on_content_accept,
|
'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-info': [self.__broadcast, self.__on_session_info, self.__ack],
|
||||||
'session-initiate': [self.__on_session_initiate, self.__broadcast,
|
'session-initiate': [self.__on_session_initiate, self.__broadcast,
|
||||||
self.__ack],
|
self.__ack],
|
||||||
|
@ -111,9 +128,14 @@ class JingleSession(object):
|
||||||
'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO
|
'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO
|
||||||
'transport-accept': [self.__ack], #TODO
|
'transport-accept': [self.__ack], #TODO
|
||||||
'transport-reject': [self.__ack], #TODO
|
'transport-reject': [self.__ack], #TODO
|
||||||
'iq-result': [],
|
'iq-result': [self.__broadcast],
|
||||||
'iq-error': [self.__on_error],
|
'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):
|
def approve_session(self):
|
||||||
"""
|
"""
|
||||||
|
@ -128,15 +150,23 @@ class JingleSession(object):
|
||||||
reason = xmpp.Node('reason')
|
reason = xmpp.Node('reason')
|
||||||
reason.addChild('decline')
|
reason.addChild('decline')
|
||||||
self._session_terminate(reason)
|
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):
|
def approve_content(self, media, name=None):
|
||||||
content = self.get_content(media)
|
content = self.get_content(media, name)
|
||||||
if content:
|
if content:
|
||||||
content.accepted = True
|
content.accepted = True
|
||||||
self.on_session_state_changed(content)
|
self.on_session_state_changed(content)
|
||||||
|
|
||||||
def reject_content(self, media):
|
def reject_content(self, media, name=None):
|
||||||
content = self.get_content(media)
|
content = self.get_content(media, name)
|
||||||
if content:
|
if content:
|
||||||
if self.state == JingleStates.active:
|
if self.state == JingleStates.active:
|
||||||
self.__content_reject(content)
|
self.__content_reject(content)
|
||||||
|
@ -154,13 +184,14 @@ class JingleSession(object):
|
||||||
reason.addChild('cancel')
|
reason.addChild('cancel')
|
||||||
self._session_terminate(reason)
|
self._session_terminate(reason)
|
||||||
|
|
||||||
def get_content(self, media=None):
|
def get_content(self, media=None, name=None):
|
||||||
if media is None:
|
if media is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for content in self.contents.values():
|
for content in self.contents.values():
|
||||||
if content.media == media:
|
if content.media == media:
|
||||||
return content
|
if name is None or content.name == name:
|
||||||
|
return content
|
||||||
|
|
||||||
def add_content(self, name, content, creator='we'):
|
def add_content(self, name, content, creator='we'):
|
||||||
"""
|
"""
|
||||||
|
@ -195,12 +226,22 @@ class JingleSession(object):
|
||||||
content = self.contents[(creator, name)]
|
content = self.contents[(creator, name)]
|
||||||
self.__content_remove(content, reason)
|
self.__content_remove(content, reason)
|
||||||
self.contents[(creator, name)].destroy()
|
self.contents[(creator, name)].destroy()
|
||||||
|
if not self.contents:
|
||||||
|
self.end_session()
|
||||||
|
|
||||||
def modify_content(self, creator, name, *someother):
|
def modify_content(self, creator, name, transport = None):
|
||||||
"""
|
'''
|
||||||
We do not need this now
|
Currently used for transport replacement
|
||||||
"""
|
'''
|
||||||
pass
|
|
||||||
|
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):
|
def on_session_state_changed(self, content=None):
|
||||||
if self.state == JingleStates.ended:
|
if self.state == JingleStates.ended:
|
||||||
|
@ -216,10 +257,15 @@ class JingleSession(object):
|
||||||
elif content and self.weinitiate:
|
elif content and self.weinitiate:
|
||||||
self.__content_accept(content)
|
self.__content_accept(content)
|
||||||
elif self.state == JingleStates.active:
|
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:
|
if not content:
|
||||||
return
|
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.
|
# We initiated this content. It's a pending content-add.
|
||||||
self.__content_add(content)
|
self.__content_add(content)
|
||||||
else:
|
else:
|
||||||
|
@ -261,6 +307,7 @@ class JingleSession(object):
|
||||||
stanza, jingle = self.__make_jingle('transport-info')
|
stanza, jingle = self.__make_jingle('transport-info')
|
||||||
jingle.addChild(node=content)
|
jingle.addChild(node=content)
|
||||||
self.connection.connection.send(stanza)
|
self.connection.connection.send(stanza)
|
||||||
|
self.collect_iq_id(stanza.getID())
|
||||||
|
|
||||||
def send_description_info(self, content):
|
def send_description_info(self, content):
|
||||||
assert self.state != JingleStates.ended
|
assert self.state != JingleStates.ended
|
||||||
|
@ -286,7 +333,7 @@ class JingleSession(object):
|
||||||
self.__send_error(stanza, 'bad-request')
|
self.__send_error(stanza, 'bad-request')
|
||||||
return
|
return
|
||||||
# FIXME: If we aren't initiated and it's not a session-initiate...
|
# 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')
|
self.__send_error(stanza, 'item-not-found', 'unknown-session')
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -311,6 +358,7 @@ class JingleSession(object):
|
||||||
Default callback for action stanzas -- simple ack and stop processing
|
Default callback for action stanzas -- simple ack and stop processing
|
||||||
"""
|
"""
|
||||||
response = stanza.buildReply('result')
|
response = stanza.buildReply('result')
|
||||||
|
response.delChild(response.getQuery())
|
||||||
self.connection.connection.send(response)
|
self.connection.connection.send(response)
|
||||||
|
|
||||||
def __on_error(self, stanza, jingle, error, action):
|
def __on_error(self, stanza, jingle, error, action):
|
||||||
|
@ -325,18 +373,42 @@ class JingleSession(object):
|
||||||
error_name = child.getName()
|
error_name = child.getName()
|
||||||
self.__dispatch_error(error_name, text, error.getAttr('type'))
|
self.__dispatch_error(error_name, text, error.getAttr('type'))
|
||||||
# FIXME: Not sure when we would want to do that...
|
# 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
|
||||||
|
|
||||||
|
stanza, jingle = self.__make_jingle('transport-replace')
|
||||||
|
self.__append_contents(jingle)
|
||||||
|
self.__broadcast(stanza, jingle, None, 'transport-replace')
|
||||||
|
self.connection.connection.send(stanza)
|
||||||
|
self.state = JingleStates.pending
|
||||||
|
|
||||||
|
|
||||||
def __on_transport_replace(self, stanza, jingle, error, action):
|
def __on_transport_replace(self, stanza, jingle, error, action):
|
||||||
for content in jingle.iterTags('content'):
|
for content in jingle.iterTags('content'):
|
||||||
creator = content['creator']
|
creator = content['creator']
|
||||||
name = content['name']
|
name = content['name']
|
||||||
if (creator, name) in self.contents:
|
if (creator, name) in self.contents:
|
||||||
transport_ns = content.getTag('transport').getNamespace()
|
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...
|
# FIXME: We don't manage anything else than ICE-UDP now...
|
||||||
# What was the previous transport?!?
|
# What was the previous transport?!?
|
||||||
# Anyway, content's transport is not modifiable yet
|
# Anyway, content's transport is not modifiable yet
|
||||||
pass
|
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:
|
else:
|
||||||
stanza, jingle = self.__make_jingle('transport-reject')
|
stanza, jingle = self.__make_jingle('transport-reject')
|
||||||
content = jingle.setTag('content', attrs={'creator': creator,
|
content = jingle.setTag('content', attrs={'creator': creator,
|
||||||
|
@ -357,9 +429,22 @@ class JingleSession(object):
|
||||||
def __on_session_info(self, stanza, jingle, error, action):
|
def __on_session_info(self, stanza, jingle, error, action):
|
||||||
# TODO: ringing, active, (un)hold, (un)mute
|
# TODO: ringing, active, (un)hold, (un)mute
|
||||||
payload = jingle.getPayload()
|
payload = jingle.getPayload()
|
||||||
if payload:
|
for p in payload:
|
||||||
self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify')
|
if p.getName() == 'checksum':
|
||||||
raise xmpp.NodeProcessed
|
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:
|
||||||
|
self.hash_algo = algo
|
||||||
|
data = hash.getData()
|
||||||
|
# This only works because there is only one session
|
||||||
|
# per file in jingleFT
|
||||||
|
self.file_hash = 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):
|
def __on_content_remove(self, stanza, jingle, error, action):
|
||||||
for content in jingle.iterTags('content'):
|
for content in jingle.iterTags('content'):
|
||||||
|
@ -382,6 +467,7 @@ class JingleSession(object):
|
||||||
if self.state != JingleStates.pending:
|
if self.state != JingleStates.pending:
|
||||||
raise OutOfOrder
|
raise OutOfOrder
|
||||||
self.state = JingleStates.active
|
self.state = JingleStates.active
|
||||||
|
|
||||||
|
|
||||||
def __on_content_accept(self, stanza, jingle, error, action):
|
def __on_content_accept(self, stanza, jingle, error, action):
|
||||||
"""
|
"""
|
||||||
|
@ -429,19 +515,25 @@ class JingleSession(object):
|
||||||
# subscription) and the receiver has a policy of not communicating via
|
# subscription) and the receiver has a policy of not communicating via
|
||||||
# Jingle with unknown entities, it SHOULD return a <service-unavailable/>
|
# Jingle with unknown entities, it SHOULD return a <service-unavailable/>
|
||||||
# error.
|
# 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
|
# Lets check what kind of jingle session does the peer want
|
||||||
contents, contents_rejected, reason_txt = self.__parse_contents(jingle)
|
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 there's no content we understand...
|
||||||
if not contents:
|
if not contents:
|
||||||
|
@ -462,6 +554,17 @@ class JingleSession(object):
|
||||||
"""
|
"""
|
||||||
Broadcast the stanza contents to proper content handlers
|
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'):
|
for content in jingle.iterTags('content'):
|
||||||
name = content['name']
|
name = content['name']
|
||||||
creator = content['creator']
|
creator = content['creator']
|
||||||
|
@ -483,9 +586,11 @@ class JingleSession(object):
|
||||||
else:
|
else:
|
||||||
# TODO
|
# TODO
|
||||||
text = reason
|
text = reason
|
||||||
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
|
|
||||||
conn=self.connection, jingle_session=self, media=None,
|
if reason == 'cancel' and self.session_type_FT:
|
||||||
reason=text))
|
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):
|
def __broadcast_all(self, stanza, jingle, error, action):
|
||||||
"""
|
"""
|
||||||
|
@ -502,6 +607,8 @@ class JingleSession(object):
|
||||||
|
|
||||||
for element in jingle.iterTags('content'):
|
for element in jingle.iterTags('content'):
|
||||||
transport = get_jingle_transport(element.getTag('transport'))
|
transport = get_jingle_transport(element.getTag('transport'))
|
||||||
|
if transport:
|
||||||
|
transport.ourjid = self.ourjid
|
||||||
content_type = get_jingle_content(element.getTag('description'))
|
content_type = get_jingle_content(element.getTag('description'))
|
||||||
if content_type:
|
if content_type:
|
||||||
try:
|
try:
|
||||||
|
@ -531,12 +638,15 @@ class JingleSession(object):
|
||||||
return (contents, contents_rejected, failure_reason)
|
return (contents, contents_rejected, failure_reason)
|
||||||
|
|
||||||
def __dispatch_error(self, error=None, text=None, type_=None):
|
def __dispatch_error(self, error=None, text=None, type_=None):
|
||||||
|
|
||||||
if text:
|
if text:
|
||||||
text = '%s (%s)' % (error, text)
|
text = '%s (%s)' % (error, text)
|
||||||
if type_ != 'modify':
|
if type_ != 'modify':
|
||||||
gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
|
gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
|
||||||
conn=self.connection, jingle_session=self,
|
conn=self.connection, jingle_session=self,
|
||||||
reason=text or error))
|
reason=text or error))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __reason_from_stanza(self, stanza):
|
def __reason_from_stanza(self, stanza):
|
||||||
# TODO: Move to GUI?
|
# TODO: Move to GUI?
|
||||||
|
@ -557,13 +667,12 @@ class JingleSession(object):
|
||||||
return (reason, text)
|
return (reason, text)
|
||||||
|
|
||||||
def __make_jingle(self, action, reason=None):
|
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.ourjid)
|
||||||
attrs = {'action': action,
|
attrs = {'action': action,
|
||||||
'sid': self.sid}
|
'sid': self.sid,
|
||||||
if action == 'session-initiate':
|
'initiator' : self.initiator}
|
||||||
attrs['initiator'] = self.initiator
|
|
||||||
elif action == 'session-accept':
|
|
||||||
attrs['responder'] = self.responder
|
|
||||||
jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
|
jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
|
||||||
if reason is not None:
|
if reason is not None:
|
||||||
jingle.addChild(node=reason)
|
jingle.addChild(node=reason)
|
||||||
|
@ -605,6 +714,7 @@ class JingleSession(object):
|
||||||
self.__append_contents(jingle)
|
self.__append_contents(jingle)
|
||||||
self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
|
self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
|
||||||
self.connection.connection.send(stanza)
|
self.connection.connection.send(stanza)
|
||||||
|
self.collect_iq_id(stanza.getID())
|
||||||
self.state = JingleStates.pending
|
self.state = JingleStates.pending
|
||||||
|
|
||||||
def __session_accept(self):
|
def __session_accept(self):
|
||||||
|
@ -613,6 +723,7 @@ class JingleSession(object):
|
||||||
self.__append_contents(jingle)
|
self.__append_contents(jingle)
|
||||||
self.__broadcast(stanza, jingle, None, 'session-accept-sent')
|
self.__broadcast(stanza, jingle, None, 'session-accept-sent')
|
||||||
self.connection.connection.send(stanza)
|
self.connection.connection.send(stanza)
|
||||||
|
self.collect_iq_id(stanza.getID())
|
||||||
self.state = JingleStates.active
|
self.state = JingleStates.active
|
||||||
|
|
||||||
def __session_info(self, payload=None):
|
def __session_info(self, payload=None):
|
||||||
|
@ -621,9 +732,15 @@ class JingleSession(object):
|
||||||
if payload:
|
if payload:
|
||||||
jingle.addChild(node=payload)
|
jingle.addChild(node=payload)
|
||||||
self.connection.connection.send(stanza)
|
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):
|
def _session_terminate(self, reason=None):
|
||||||
assert self.state != JingleStates.ended
|
|
||||||
stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
|
stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
|
||||||
self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
|
self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
|
||||||
if self.connection.connection and self.connection.connected >= 2:
|
if self.connection.connection and self.connection.connected >= 2:
|
||||||
|
@ -647,7 +764,8 @@ class JingleSession(object):
|
||||||
stanza, jingle = self.__make_jingle('content-add')
|
stanza, jingle = self.__make_jingle('content-add')
|
||||||
self.__append_content(jingle, content)
|
self.__append_content(jingle, content)
|
||||||
self.__broadcast(stanza, jingle, None, 'content-add-sent')
|
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):
|
def __content_accept(self, content):
|
||||||
# TODO: test
|
# TODO: test
|
||||||
|
@ -655,7 +773,8 @@ class JingleSession(object):
|
||||||
stanza, jingle = self.__make_jingle('content-accept')
|
stanza, jingle = self.__make_jingle('content-accept')
|
||||||
self.__append_content(jingle, content)
|
self.__append_content(jingle, content)
|
||||||
self.__broadcast(stanza, jingle, None, 'content-accept-sent')
|
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):
|
def __content_reject(self, content):
|
||||||
assert self.state != JingleStates.ended
|
assert self.state != JingleStates.ended
|
||||||
|
|
|
@ -16,21 +16,29 @@ Handles Jingle Transports (currently only ICE-UDP)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import xmpp
|
import xmpp
|
||||||
|
import socket
|
||||||
|
from common import gajim
|
||||||
|
from common.protocol.bytestream import ConnectionSocks5Bytestream
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger('gajim.c.jingle_transport')
|
||||||
|
|
||||||
|
|
||||||
transports = {}
|
transports = {}
|
||||||
|
|
||||||
def get_jingle_transport(node):
|
def get_jingle_transport(node):
|
||||||
namespace = node.getNamespace()
|
namespace = node.getNamespace()
|
||||||
if namespace in transports:
|
if namespace in transports:
|
||||||
return transports[namespace]()
|
return transports[namespace](node)
|
||||||
|
|
||||||
|
|
||||||
class TransportType(object):
|
class TransportType(object):
|
||||||
"""
|
"""
|
||||||
Possible types of a JingleTransport
|
Possible types of a JingleTransport
|
||||||
"""
|
"""
|
||||||
datagram = 1
|
ICEUDP = 1
|
||||||
streaming = 2
|
SOCKS5 = 2
|
||||||
|
IBB = 3
|
||||||
|
|
||||||
|
|
||||||
class JingleTransport(object):
|
class JingleTransport(object):
|
||||||
|
@ -70,16 +78,258 @@ class JingleTransport(object):
|
||||||
Return the list of transport candidates from a transport stanza
|
Return the list of transport candidates from a transport stanza
|
||||||
"""
|
"""
|
||||||
return []
|
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):
|
||||||
|
"""
|
||||||
|
Socks5 transport in jingle scenario
|
||||||
|
Note: Don't forget to call set_file_props after initialization
|
||||||
|
"""
|
||||||
|
def __init__(self, node=None):
|
||||||
|
JingleTransport.__init__(self, TransportType.SOCKS5)
|
||||||
|
self.connection = None
|
||||||
|
self.remote_candidates = []
|
||||||
|
self.sid = None
|
||||||
|
if node and node.getAttr('sid'):
|
||||||
|
self.sid = node.getAttr('sid')
|
||||||
|
|
||||||
|
|
||||||
|
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, 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
|
||||||
|
|
||||||
|
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': int(candidate['port']),
|
||||||
|
'cid': candidate['cid'],
|
||||||
|
'type': typ,
|
||||||
|
'priority': candidate['priority']
|
||||||
|
}
|
||||||
|
candidates.append(cand)
|
||||||
|
|
||||||
|
# we need this when we construct file_props on session-initiation
|
||||||
|
self.remote_candidates = candidates
|
||||||
|
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 = 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()
|
||||||
|
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'] = self.connection.connection.getAnID()
|
||||||
|
c['port'] = port
|
||||||
|
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._add_candidates(local_ip_cand)
|
||||||
|
|
||||||
|
def _add_additional_candidates(self):
|
||||||
|
if not self.connection:
|
||||||
|
return
|
||||||
|
type_preference = 126
|
||||||
|
additional_ip_cand = []
|
||||||
|
port = int(gajim.config.get('file_transfers_port'))
|
||||||
|
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:
|
||||||
|
c = {'host': h}
|
||||||
|
c['candidate_id'] = self.connection.connection.getAnID()
|
||||||
|
c['port'] = port
|
||||||
|
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._add_candidates(additional_ip_cand)
|
||||||
|
|
||||||
|
def _add_proxy_candidates(self):
|
||||||
|
if not self.connection:
|
||||||
|
return
|
||||||
|
type_preference = 10
|
||||||
|
proxy_cand = []
|
||||||
|
socks5conn = self.connection
|
||||||
|
proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props)
|
||||||
|
|
||||||
|
if proxyhosts:
|
||||||
|
self.file_props['proxyhosts'] = proxyhosts
|
||||||
|
|
||||||
|
for proxyhost in proxyhosts:
|
||||||
|
c = {'host': proxyhost['host']}
|
||||||
|
c['candidate_id'] = self.connection.connection.getAnID()
|
||||||
|
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._add_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
|
||||||
|
if not self.connection:
|
||||||
|
return
|
||||||
|
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(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
|
||||||
|
|
||||||
|
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:
|
||||||
|
raise Exception, 'cid is missing'
|
||||||
|
activated.setAttr('cid', cid)
|
||||||
|
transport.addChild(node=activated)
|
||||||
|
content.addChild(node=transport)
|
||||||
|
sesn.send_transport_info(content)
|
||||||
|
|
||||||
|
|
||||||
|
class JingleTransportIBB(JingleTransport):
|
||||||
|
|
||||||
|
def __init__(self, node=None, block_sz=None):
|
||||||
|
|
||||||
|
JingleTransport.__init__(self, TransportType.IBB)
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import farstream
|
import farstream
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class JingleTransportICEUDP(JingleTransport):
|
class JingleTransportICEUDP(JingleTransport):
|
||||||
def __init__(self):
|
def __init__(self, node):
|
||||||
JingleTransport.__init__(self, TransportType.datagram)
|
JingleTransport.__init__(self, TransportType.ICEUDP)
|
||||||
|
|
||||||
def make_candidate(self, candidate):
|
def make_candidate(self, candidate):
|
||||||
types = {farstream.CANDIDATE_TYPE_HOST: 'host',
|
types = {farstream.CANDIDATE_TYPE_HOST: 'host',
|
||||||
|
@ -149,3 +399,5 @@ class JingleTransportICEUDP(JingleTransport):
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP
|
transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP
|
||||||
|
transports[xmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5
|
||||||
|
transports[xmpp.NS_JINGLE_IBB] = JingleTransportIBB
|
||||||
|
|
239
src/common/jingle_xtls.py
Normal file
239
src/common/jingle_xtls.py
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import common
|
||||||
|
from common import gajim
|
||||||
|
log = logging.getLogger('gajim.c.jingle_xtls')
|
||||||
|
|
||||||
|
PYOPENSSL_PRESENT = False
|
||||||
|
|
||||||
|
pending_contents = {} # key-exchange id -> session, accept that session once key-exchange completes
|
||||||
|
|
||||||
|
def key_exchange_pend(id_, content):
|
||||||
|
pending_contents[id_] = content
|
||||||
|
|
||||||
|
def approve_pending_content(id_):
|
||||||
|
content = pending_contents[id_]
|
||||||
|
content.session.approve_session()
|
||||||
|
content.session.approve_content('file', name=content.name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import OpenSSL
|
||||||
|
PYOPENSSL_PRESENT = True
|
||||||
|
except ImportError:
|
||||||
|
log.info("PyOpenSSL not available")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
||||||
|
|
||||||
|
if fingerprint == 'server': # for testing purposes only
|
||||||
|
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')
|
||||||
|
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)
|
||||||
|
log.debug('certificate file ' + f + ' loaded fingerprint ' + \
|
||||||
|
fingerprint)
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def send_cert(con, jid_from, sid):
|
||||||
|
certpath = os.path.join(gajim.MY_CERT_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(gajim.MY_PEER_CERTS_PATH), jid)
|
||||||
|
certpath += '.cert'
|
||||||
|
|
||||||
|
id_ = obj.getAttr('id')
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
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_)
|
||||||
|
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
|
||||||
|
|
||||||
|
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')
|
|
@ -37,9 +37,8 @@ from common import xmpp
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import helpers
|
from common import helpers
|
||||||
from common import dataforms
|
from common import dataforms
|
||||||
from common.connection_handlers_events import FileRequestReceivedEvent, \
|
|
||||||
FileRequestErrorEvent, InformationEvent
|
|
||||||
from common import ged
|
from common import ged
|
||||||
|
from common import jingle_xtls
|
||||||
|
|
||||||
from common.socks5 import Socks5Receiver
|
from common.socks5 import Socks5Receiver
|
||||||
|
|
||||||
|
@ -140,6 +139,34 @@ class ConnectionBytestream:
|
||||||
# user response to ConfirmationDialog may come after we've disconneted
|
# user response to ConfirmationDialog may come after we've disconneted
|
||||||
if not self.connection or self.connected < 2:
|
if not self.connection or self.connected < 2:
|
||||||
return
|
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['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', 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', content.name)
|
||||||
|
return
|
||||||
|
|
||||||
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
|
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
|
||||||
iq.setAttr('id', file_props['request-id'])
|
iq.setAttr('id', file_props['request-id'])
|
||||||
si = iq.setTag('si', namespace=xmpp.NS_SI)
|
si = iq.setTag('si', namespace=xmpp.NS_SI)
|
||||||
|
@ -168,6 +195,10 @@ class ConnectionBytestream:
|
||||||
# user response to ConfirmationDialog may come after we've disconneted
|
# user response to ConfirmationDialog may come after we've disconneted
|
||||||
if not self.connection or self.connected < 2:
|
if not self.connection or self.connected < 2:
|
||||||
return
|
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 = xmpp.Iq(to=unicode(file_props['sender']), typ='error')
|
||||||
iq.setAttr('id', file_props['request-id'])
|
iq.setAttr('id', file_props['request-id'])
|
||||||
if code == '400' and typ in ('stream', 'profile'):
|
if code == '400' and typ in ('stream', 'profile'):
|
||||||
|
@ -221,6 +252,7 @@ class ConnectionBytestream:
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
|
||||||
def _siSetCB(self, con, iq_obj):
|
def _siSetCB(self, con, iq_obj):
|
||||||
|
from common.connection_handlers_events import FileRequestReceivedEvent
|
||||||
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self,
|
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self,
|
||||||
stanza=iq_obj))
|
stanza=iq_obj))
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
@ -240,6 +272,7 @@ class ConnectionBytestream:
|
||||||
return
|
return
|
||||||
jid = self._ft_get_from(iq_obj)
|
jid = self._ft_get_from(iq_obj)
|
||||||
file_props['error'] = -3
|
file_props['error'] = -3
|
||||||
|
from common.connection_handlers_events import FileRequestErrorEvent
|
||||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
||||||
jid=jid, file_props=file_props, error_msg=''))
|
jid=jid, file_props=file_props, error_msg=''))
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
@ -273,6 +306,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
||||||
if contact.get_full_jid() == receiver_jid:
|
if contact.get_full_jid() == receiver_jid:
|
||||||
file_props['error'] = -5
|
file_props['error'] = -5
|
||||||
self.remove_transfer(file_props)
|
self.remove_transfer(file_props)
|
||||||
|
from common.connection_handlers_events import \
|
||||||
|
FileRequestErrorEvent
|
||||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
|
gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
|
||||||
conn=self, jid=contact.jid, file_props=file_props,
|
conn=self, jid=contact.jid, file_props=file_props,
|
||||||
error_msg=''))
|
error_msg=''))
|
||||||
|
@ -332,9 +367,10 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
||||||
|
|
||||||
port = gajim.config.get('file_transfers_port')
|
port = gajim.config.get('file_transfers_port')
|
||||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||||
self._result_socks5_sid, file_props['sid'])
|
self._result_socks5_sid, file_props)
|
||||||
if not listener:
|
if not listener:
|
||||||
file_props['error'] = -5
|
file_props['error'] = -5
|
||||||
|
from common.connection_handlers_events import FileRequestErrorEvent
|
||||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
||||||
jid=unicode(receiver), file_props=file_props, error_msg=''))
|
jid=unicode(receiver), file_props=file_props, error_msg=''))
|
||||||
self._connect_error(unicode(receiver), file_props['sid'],
|
self._connect_error(unicode(receiver), file_props['sid'],
|
||||||
|
@ -374,6 +410,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
||||||
port = gajim.config.get('file_transfers_port')
|
port = gajim.config.get('file_transfers_port')
|
||||||
self._add_streamhosts_to_query(query, sender, port, my_ips)
|
self._add_streamhosts_to_query(query, sender, port, my_ips)
|
||||||
except socket.gaierror:
|
except socket.gaierror:
|
||||||
|
from common.connection_handlers_events import InformationEvent
|
||||||
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
|
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
|
||||||
level='error', pri_txt=_('Wrong host'),
|
level='error', pri_txt=_('Wrong host'),
|
||||||
sec_txt=_('Invalid local address? :-O')))
|
sec_txt=_('Invalid local address? :-O')))
|
||||||
|
@ -546,6 +583,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
||||||
if file_props is not None:
|
if file_props is not None:
|
||||||
self.disconnect_transfer(file_props)
|
self.disconnect_transfer(file_props)
|
||||||
file_props['error'] = -3
|
file_props['error'] = -3
|
||||||
|
from common.connection_handlers_events import \
|
||||||
|
FileRequestErrorEvent
|
||||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
|
gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
|
||||||
conn=self, jid=to, file_props=file_props, error_msg=msg))
|
conn=self, jid=to, file_props=file_props, error_msg=msg))
|
||||||
|
|
||||||
|
@ -578,6 +617,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
||||||
return
|
return
|
||||||
file_props = self.files_props[id_]
|
file_props = self.files_props[id_]
|
||||||
file_props['error'] = -4
|
file_props['error'] = -4
|
||||||
|
from common.connection_handlers_events import FileRequestErrorEvent
|
||||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
||||||
jid=jid, file_props=file_props, error_msg=''))
|
jid=jid, file_props=file_props, error_msg=''))
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
@ -713,7 +753,10 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
|
||||||
else:
|
else:
|
||||||
gajim.socks5queue.send_file(file_props, self.name)
|
if 'stopped' in file_props and file_props['stopped']:
|
||||||
|
self.remove_transfer(file_props)
|
||||||
|
else:
|
||||||
|
gajim.socks5queue.send_file(file_props, self.name, 'client')
|
||||||
if 'fast' in file_props:
|
if 'fast' in file_props:
|
||||||
fasts = file_props['fast']
|
fasts = file_props['fast']
|
||||||
if len(fasts) > 0:
|
if len(fasts) > 0:
|
||||||
|
@ -741,9 +784,9 @@ class ConnectionIBBytestream(ConnectionBytestream):
|
||||||
elif typ == 'set' and stanza.getTag('close', namespace=xmpp.NS_IBB):
|
elif typ == 'set' and stanza.getTag('close', namespace=xmpp.NS_IBB):
|
||||||
self.StreamCloseHandler(conn, stanza)
|
self.StreamCloseHandler(conn, stanza)
|
||||||
elif typ == 'result':
|
elif typ == 'result':
|
||||||
self.StreamCommitHandler(conn, stanza)
|
self.SendHandler()
|
||||||
elif typ == 'error':
|
elif typ == 'error':
|
||||||
self.StreamOpenReplyHandler(conn, stanza)
|
gajim.socks5queue.error_cb()
|
||||||
else:
|
else:
|
||||||
conn.send(xmpp.Error(stanza, xmpp.ERR_BAD_REQUEST))
|
conn.send(xmpp.Error(stanza, xmpp.ERR_BAD_REQUEST))
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
@ -918,13 +961,17 @@ class ConnectionIBBytestream(ConnectionBytestream):
|
||||||
log.debug('StreamCloseHandler called sid->%s' % sid)
|
log.debug('StreamCloseHandler called sid->%s' % sid)
|
||||||
# look in sending files
|
# look in sending files
|
||||||
if sid in self.files_props.keys():
|
if sid in self.files_props.keys():
|
||||||
conn.send(stanza.buildReply('result'))
|
reply = stanza.buildReply('result')
|
||||||
gajim.socks5queue.complete_transfer_cb(self.name, file_props)
|
reply.delChild('close')
|
||||||
|
conn.send(reply)
|
||||||
|
gajim.socks5queue.complete_transfer_cb(self.name, self.files_props[sid])
|
||||||
del self.files_props[sid]
|
del self.files_props[sid]
|
||||||
# look in receiving files
|
# look in receiving files
|
||||||
elif gajim.socks5queue.get_file_props(self.name, sid):
|
elif gajim.socks5queue.get_file_props(self.name, sid):
|
||||||
file_props = 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()
|
file_props['fp'].close()
|
||||||
gajim.socks5queue.complete_transfer_cb(self.name, file_props)
|
gajim.socks5queue.complete_transfer_cb(self.name, file_props)
|
||||||
gajim.socks5queue.remove_file_props(self.name, sid)
|
gajim.socks5queue.remove_file_props(self.name, sid)
|
||||||
|
@ -964,6 +1011,7 @@ class ConnectionIBBytestream(ConnectionBytestream):
|
||||||
if stanza.getTag('data'):
|
if stanza.getTag('data'):
|
||||||
if self.IBBMessageHandler(conn, stanza):
|
if self.IBBMessageHandler(conn, stanza):
|
||||||
reply = stanza.buildReply('result')
|
reply = stanza.buildReply('result')
|
||||||
|
reply.delChild('data')
|
||||||
conn.send(reply)
|
conn.send(reply)
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
elif syn_id == self.last_sent_ibb_id:
|
elif syn_id == self.last_sent_ibb_id:
|
||||||
|
|
1258
src/common/socks5.py
1258
src/common/socks5.py
File diff suppressed because it is too large
Load diff
|
@ -23,6 +23,7 @@ sub- stanzas) handling routines
|
||||||
from simplexml import Node, NodeBuilder
|
from simplexml import Node, NodeBuilder
|
||||||
import time
|
import time
|
||||||
import string
|
import string
|
||||||
|
import hashlib
|
||||||
|
|
||||||
def ascii_upper(s):
|
def ascii_upper(s):
|
||||||
trans_table = string.maketrans(string.ascii_lowercase,
|
trans_table = string.maketrans(string.ascii_lowercase,
|
||||||
|
@ -90,8 +91,12 @@ NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166
|
||||||
NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167
|
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_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167
|
||||||
NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167
|
NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167
|
||||||
|
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_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_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_LAST = 'jabber:iq:last'
|
||||||
NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080
|
NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080
|
||||||
NS_MESSAGE = 'message' # Jabberd2
|
NS_MESSAGE = 'message' # Jabberd2
|
||||||
|
@ -153,8 +158,16 @@ NS_DATA_LAYOUT = 'http://jabber.org/protocol/xdata-layout' # XEP-0141
|
||||||
NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate' # XEP-0122
|
NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate' # XEP-0122
|
||||||
NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
|
NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
|
||||||
NS_RECEIPTS = 'urn:xmpp:receipts'
|
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'
|
||||||
NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198
|
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 = '''
|
xmpp_stream_error_conditions = '''
|
||||||
bad-format -- -- -- The entity has sent XML that cannot be processed.
|
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.
|
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.
|
||||||
|
@ -1024,13 +1037,84 @@ class Iq(Protocol):
|
||||||
attrs={'id': self.getID()})
|
attrs={'id': self.getID()})
|
||||||
iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
|
iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
|
||||||
return iq
|
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:
|
||||||
|
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:
|
||||||
|
for line in file_string:
|
||||||
|
hl.update(line)
|
||||||
|
hash_ = hl.hexdigest()
|
||||||
|
|
||||||
|
return hash_
|
||||||
|
|
||||||
|
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):
|
class Acks(Node):
|
||||||
"""
|
"""
|
||||||
Acknowledgement elements for Stream Management
|
Acknowledgement elements for Stream Management
|
||||||
"""
|
"""
|
||||||
def __init__(self, nsp=NS_STREAM_MGMT):
|
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)
|
self.setNamespace(nsp)
|
||||||
|
|
||||||
def buildAnswer(self, handled):
|
def buildAnswer(self, handled):
|
||||||
|
@ -1407,3 +1491,4 @@ class DataForm(Node):
|
||||||
Simple dictionary interface for setting datafields values by their names
|
Simple dictionary interface for setting datafields values by their names
|
||||||
"""
|
"""
|
||||||
return self.setField(name).setValue(val)
|
return self.setField(name).setValue(val)
|
||||||
|
|
||||||
|
|
|
@ -1615,16 +1615,15 @@ class YesNoDialog(HigDialog):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
|
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_yes = on_response_yes
|
||||||
self.user_response_no = on_response_no
|
self.user_response_no = on_response_no
|
||||||
if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
|
if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
|
||||||
parent = gajim.interface.roster.window
|
parent = gajim.interface.roster.window
|
||||||
else:
|
else:
|
||||||
parent = None
|
parent = None
|
||||||
HigDialog.__init__(self, parent, gtk.MESSAGE_QUESTION,
|
HigDialog.__init__(self, parent, type_, gtk.BUTTONS_YES_NO, pritext,
|
||||||
gtk.BUTTONS_YES_NO, pritext, sectext,
|
sectext, on_response_yes=self.on_response_yes,
|
||||||
on_response_yes=self.on_response_yes,
|
|
||||||
on_response_no=self.on_response_no)
|
on_response_no=self.on_response_no)
|
||||||
|
|
||||||
if checktext:
|
if checktext:
|
||||||
|
|
|
@ -35,6 +35,10 @@ from common import gajim
|
||||||
from common import helpers
|
from common import helpers
|
||||||
from common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
|
from common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
|
||||||
is_transfer_stopped)
|
is_transfer_stopped)
|
||||||
|
from common.xmpp.protocol import NS_JINGLE_FILE_TRANSFER
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger('gajim.filetransfer_window')
|
||||||
|
|
||||||
C_IMAGE = 0
|
C_IMAGE = 0
|
||||||
C_LABELS = 1
|
C_LABELS = 1
|
||||||
|
@ -42,7 +46,8 @@ C_FILE = 2
|
||||||
C_TIME = 3
|
C_TIME = 3
|
||||||
C_PROGRESS = 4
|
C_PROGRESS = 4
|
||||||
C_PERCENT = 5
|
C_PERCENT = 5
|
||||||
C_SID = 6
|
C_PULSE = 6
|
||||||
|
C_SID = 7
|
||||||
|
|
||||||
|
|
||||||
class FileTransfersWindow:
|
class FileTransfersWindow:
|
||||||
|
@ -61,7 +66,8 @@ class FileTransfersWindow:
|
||||||
shall_notify = gajim.config.get('notify_on_file_complete')
|
shall_notify = gajim.config.get('notify_on_file_complete')
|
||||||
self.notify_ft_checkbox.set_active(shall_notify
|
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)
|
self.tree.set_model(self.model)
|
||||||
col = gtk.TreeViewColumn()
|
col = gtk.TreeViewColumn()
|
||||||
|
|
||||||
|
@ -108,6 +114,7 @@ class FileTransfersWindow:
|
||||||
col.pack_start(renderer, expand=False)
|
col.pack_start(renderer, expand=False)
|
||||||
col.add_attribute(renderer, 'text', C_PROGRESS)
|
col.add_attribute(renderer, 'text', C_PROGRESS)
|
||||||
col.add_attribute(renderer, 'value', C_PERCENT)
|
col.add_attribute(renderer, 'value', C_PERCENT)
|
||||||
|
col.add_attribute(renderer, 'pulse', C_PULSE)
|
||||||
col.set_resizable(True)
|
col.set_resizable(True)
|
||||||
col.set_expand(False)
|
col.set_expand(False)
|
||||||
self.tree.append_column(col)
|
self.tree.append_column(col)
|
||||||
|
@ -121,6 +128,8 @@ class FileTransfersWindow:
|
||||||
'pause': gtk.STOCK_MEDIA_PAUSE,
|
'pause': gtk.STOCK_MEDIA_PAUSE,
|
||||||
'continue': gtk.STOCK_MEDIA_PLAY,
|
'continue': gtk.STOCK_MEDIA_PLAY,
|
||||||
'ok': gtk.STOCK_APPLY,
|
'ok': gtk.STOCK_APPLY,
|
||||||
|
'computing': gtk.STOCK_EXECUTE,
|
||||||
|
'hash_error': gtk.STOCK_STOP,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
|
self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
|
||||||
|
@ -240,6 +249,21 @@ class FileTransfersWindow:
|
||||||
dialogs.ErrorDialog(_('File transfer stopped'), sectext)
|
dialogs.ErrorDialog(_('File transfer stopped'), sectext)
|
||||||
self.tree.get_selection().unselect_all()
|
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):
|
def show_file_send_request(self, account, contact):
|
||||||
win = gtk.ScrolledWindow()
|
win = gtk.ScrolledWindow()
|
||||||
win.set_shadow_type(gtk.SHADOW_IN)
|
win.set_shadow_type(gtk.SHADOW_IN)
|
||||||
|
@ -310,8 +334,17 @@ class FileTransfersWindow:
|
||||||
file_path, file_name, file_desc)
|
file_path, file_name, file_desc)
|
||||||
if file_props is None:
|
if file_props is None:
|
||||||
return False
|
return False
|
||||||
self.add_transfer(account, contact, file_props)
|
if contact.supports(NS_JINGLE_FILE_TRANSFER):
|
||||||
gajim.connections[account].send_file_request(file_props)
|
log.info("contact %s supports jingle file transfer"%(contact.get_full_jid()))
|
||||||
|
# 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
|
return True
|
||||||
|
|
||||||
def _start_receive(self, file_path, account, contact, file_props):
|
def _start_receive(self, file_path, account, contact, file_props):
|
||||||
|
@ -436,6 +469,36 @@ class FileTransfersWindow:
|
||||||
file_props['stopped'] = True
|
file_props['stopped'] = True
|
||||||
elif status == 'ok':
|
elif status == 'ok':
|
||||||
file_props['completed'] = True
|
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))
|
self.model.set(iter_, C_IMAGE, self.get_icon(status))
|
||||||
path = self.model.get_path(iter_)
|
path = self.model.get_path(iter_)
|
||||||
self.select_func(path)
|
self.select_func(path)
|
||||||
|
@ -576,7 +639,12 @@ class FileTransfersWindow:
|
||||||
status = 'stop'
|
status = 'stop'
|
||||||
self.model.set(iter_, 0, self.get_icon(status))
|
self.model.set(iter_, 0, self.get_icon(status))
|
||||||
if transfered_size == full_size:
|
if transfered_size == full_size:
|
||||||
self.set_status(typ, sid, 'ok')
|
# 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')
|
||||||
elif just_began:
|
elif just_began:
|
||||||
path = self.model.get_path(iter_)
|
path = self.model.get_path(iter_)
|
||||||
self.select_func(path)
|
self.select_func(path)
|
||||||
|
@ -642,7 +710,7 @@ class FileTransfersWindow:
|
||||||
file_name = file_props['name']
|
file_name = file_props['name']
|
||||||
text_props = gobject.markup_escape_text(file_name) + '\n'
|
text_props = gobject.markup_escape_text(file_name) + '\n'
|
||||||
text_props += contact.get_shown_name()
|
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'])
|
file_props['type'] + file_props['sid'])
|
||||||
self.set_progress(file_props['type'], file_props['sid'], 0, iter_)
|
self.set_progress(file_props['type'], file_props['sid'], 0, iter_)
|
||||||
if 'started' in file_props and file_props['started'] is False:
|
if 'started' in file_props and file_props['started'] is False:
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
## You should have received a copy of the GNU General Public License
|
## You should have received a copy of the GNU General Public License
|
||||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
##
|
##
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
@ -71,6 +70,7 @@ from session import ChatControlSession
|
||||||
import common.sleepy
|
import common.sleepy
|
||||||
|
|
||||||
from common.xmpp import idlequeue
|
from common.xmpp import idlequeue
|
||||||
|
from common.xmpp import Hashes
|
||||||
from common.zeroconf import connection_zeroconf
|
from common.zeroconf import connection_zeroconf
|
||||||
from common import resolver
|
from common import resolver
|
||||||
from common import caps_cache
|
from common import caps_cache
|
||||||
|
@ -83,6 +83,7 @@ from common import logging_helpers
|
||||||
from common.connection_handlers_events import OurShowEvent, \
|
from common.connection_handlers_events import OurShowEvent, \
|
||||||
FileRequestErrorEvent, InformationEvent
|
FileRequestErrorEvent, InformationEvent
|
||||||
from common.connection import Connection
|
from common.connection import Connection
|
||||||
|
from common import jingle
|
||||||
|
|
||||||
import roster_window
|
import roster_window
|
||||||
import profile_window
|
import profile_window
|
||||||
|
@ -918,21 +919,65 @@ class Interface:
|
||||||
self.instances['file_transfers'].set_progress(file_props['type'],
|
self.instances['file_transfers'].set_progress(file_props['type'],
|
||||||
file_props['sid'], file_props['received-len'])
|
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'])
|
||||||
|
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')
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
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
|
||||||
|
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()
|
||||||
|
|
||||||
def handle_event_file_rcv_completed(self, account, file_props):
|
def handle_event_file_rcv_completed(self, account, file_props):
|
||||||
ft = self.instances['file_transfers']
|
ft = self.instances['file_transfers']
|
||||||
if file_props['error'] == 0:
|
if file_props['error'] == 0:
|
||||||
ft.set_progress(file_props['type'], file_props['sid'],
|
ft.set_progress(file_props['type'], file_props['sid'],
|
||||||
file_props['received-len'])
|
file_props['received-len'])
|
||||||
else:
|
else:
|
||||||
ft.set_status(file_props['type'], file_props['sid'], 'stop')
|
ft.set_status(file_props['type'], file_props['sid'], 'stop')
|
||||||
if 'stalled' in file_props and file_props['stalled'] or \
|
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
|
return
|
||||||
|
|
||||||
if file_props['type'] == 'r': # we receive a file
|
if file_props['type'] == 'r': # we receive a file
|
||||||
jid = unicode(file_props['sender'])
|
# 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
|
else: # we send a file
|
||||||
jid = unicode(file_props['receiver'])
|
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 helpers.allow_popup_window(account):
|
||||||
if file_props['error'] == 0:
|
if file_props['error'] == 0:
|
||||||
if gajim.config.get('notify_on_file_complete'):
|
if gajim.config.get('notify_on_file_complete'):
|
||||||
|
@ -943,6 +988,8 @@ class Interface:
|
||||||
elif file_props['error'] == -6:
|
elif file_props['error'] == -6:
|
||||||
ft.show_stopped(jid, file_props,
|
ft.show_stopped(jid, file_props,
|
||||||
error_msg=_('Error opening file'))
|
error_msg=_('Error opening file'))
|
||||||
|
elif file_props['error'] == -10:
|
||||||
|
ft.show_hash_error(jid, file_props)
|
||||||
return
|
return
|
||||||
|
|
||||||
msg_type = ''
|
msg_type = ''
|
||||||
|
@ -954,6 +1001,9 @@ class Interface:
|
||||||
elif file_props['error'] in (-1, -6):
|
elif file_props['error'] in (-1, -6):
|
||||||
msg_type = 'file-stopped'
|
msg_type = 'file-stopped'
|
||||||
event_type = _('File Transfer Stopped')
|
event_type = _('File Transfer Stopped')
|
||||||
|
elif file_props['error'] == -10:
|
||||||
|
msg_type = 'file-hash-error'
|
||||||
|
event_type = _('File Transfer Failed')
|
||||||
|
|
||||||
if event_type == '':
|
if event_type == '':
|
||||||
# FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
|
# FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
|
||||||
|
@ -971,16 +1021,20 @@ class Interface:
|
||||||
# get the name of the sender, as it is in the roster
|
# get the name of the sender, as it is in the roster
|
||||||
sender = unicode(file_props['sender']).split('/')[0]
|
sender = unicode(file_props['sender']).split('/')[0]
|
||||||
name = gajim.contacts.get_first_contact_from_jid(account,
|
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'])
|
filename = os.path.basename(file_props['file-name'])
|
||||||
if event_type == _('File Transfer Completed'):
|
if event_type == _('File Transfer Completed'):
|
||||||
txt = _('You successfully received %(filename)s from '
|
txt = _('You successfully received %(filename)s from '
|
||||||
'%(name)s.') % {'filename': filename, 'name': name}
|
'%(name)s.') % {'filename': filename, 'name': name}
|
||||||
img_name = 'gajim-ft_done'
|
img_name = 'gajim-ft_done'
|
||||||
else: # ft stopped
|
elif event_type == _('File Transfer Stopped'):
|
||||||
txt = _('File transfer of %(filename)s from %(name)s '
|
txt = _('File transfer of %(filename)s from %(name)s '
|
||||||
'stopped.') % {'filename': filename, 'name': name}
|
'stopped.') % {'filename': filename, 'name': name}
|
||||||
img_name = 'gajim-ft_stopped'
|
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:
|
else:
|
||||||
receiver = file_props['receiver']
|
receiver = file_props['receiver']
|
||||||
if hasattr(receiver, 'jid'):
|
if hasattr(receiver, 'jid'):
|
||||||
|
@ -988,24 +1042,28 @@ class Interface:
|
||||||
receiver = receiver.split('/')[0]
|
receiver = receiver.split('/')[0]
|
||||||
# get the name of the contact, as it is in the roster
|
# get the name of the contact, as it is in the roster
|
||||||
name = gajim.contacts.get_first_contact_from_jid(account,
|
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'])
|
filename = os.path.basename(file_props['file-name'])
|
||||||
if event_type == _('File Transfer Completed'):
|
if event_type == _('File Transfer Completed'):
|
||||||
txt = _('You successfully sent %(filename)s to %(name)s.')\
|
txt = _('You successfully sent %(filename)s to %(name)s.')\
|
||||||
% {'filename': filename, 'name': name}
|
% {'filename': filename, 'name': name}
|
||||||
img_name = 'gajim-ft_done'
|
img_name = 'gajim-ft_done'
|
||||||
else: # ft stopped
|
elif event_type == _('File Transfer Stopped'):
|
||||||
txt = _('File transfer of %(filename)s to %(name)s '
|
txt = _('File transfer of %(filename)s to %(name)s '
|
||||||
'stopped.') % {'filename': filename, 'name': name}
|
'stopped.') % {'filename': filename, 'name': name}
|
||||||
img_name = 'gajim-ft_stopped'
|
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)
|
path = gtkgui_helpers.get_icon_path(img_name, 48)
|
||||||
else:
|
else:
|
||||||
txt = ''
|
txt = ''
|
||||||
path = ''
|
path = ''
|
||||||
|
|
||||||
if gajim.config.get('notify_on_file_complete') and \
|
if gajim.config.get('notify_on_file_complete') and \
|
||||||
(gajim.config.get('autopopupaway') or \
|
(gajim.config.get('autopopupaway') or \
|
||||||
gajim.connections[account].connected in (2, 3)):
|
gajim.connections[account].connected in (2, 3)):
|
||||||
# we want to be notified and we are online/chat or we don't mind
|
# we want to be notified and we are online/chat or we don't mind
|
||||||
# bugged when away/na/busy
|
# bugged when away/na/busy
|
||||||
notify.popup(event_type, jid, account, msg_type, path_to_image=path,
|
notify.popup(event_type, jid, account, msg_type, path_to_image=path,
|
||||||
|
@ -1103,6 +1161,23 @@ class Interface:
|
||||||
'resource. Please type a new one'), resource=proposed_resource,
|
'resource. Please type a new one'), resource=proposed_resource,
|
||||||
ok_handler=on_ok)
|
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')
|
||||||
|
|
||||||
def handle_event_jingle_incoming(self, obj):
|
def handle_event_jingle_incoming(self, obj):
|
||||||
# ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
|
# ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
|
||||||
# data...))
|
# data...))
|
||||||
|
@ -1440,6 +1515,7 @@ class Interface:
|
||||||
self.handle_event_jingle_disconnected],
|
self.handle_event_jingle_disconnected],
|
||||||
'jingle-error-received': [self.handle_event_jingle_error],
|
'jingle-error-received': [self.handle_event_jingle_error],
|
||||||
'jingle-request-received': [self.handle_event_jingle_incoming],
|
'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],
|
'last-result-received': [self.handle_event_last_status_time],
|
||||||
'message-error': [self.handle_event_msgerror],
|
'message-error': [self.handle_event_msgerror],
|
||||||
'message-not-sent': [self.handle_event_msgnotsent],
|
'message-not-sent': [self.handle_event_msgnotsent],
|
||||||
|
@ -1498,7 +1574,7 @@ class Interface:
|
||||||
no_queue = len(gajim.events.get_events(account, jid)) == 0
|
no_queue = len(gajim.events.get_events(account, jid)) == 0
|
||||||
# type_ can be gc-invitation file-send-error file-error
|
# type_ can be gc-invitation file-send-error file-error
|
||||||
# file-request-error file-request file-completed file-stopped
|
# 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_type can be in advancedNotificationWindow.events_list
|
||||||
event_types = {'file-request': 'ft_request',
|
event_types = {'file-request': 'ft_request',
|
||||||
'file-completed': 'ft_finished'}
|
'file-completed': 'ft_finished'}
|
||||||
|
@ -1627,7 +1703,7 @@ class Interface:
|
||||||
w = ctrl.parent_win
|
w = ctrl.parent_win
|
||||||
elif type_ in ('normal', 'file-request', 'file-request-error',
|
elif type_ in ('normal', 'file-request', 'file-request-error',
|
||||||
'file-send-error', 'file-error', 'file-stopped', 'file-completed',
|
'file-send-error', 'file-error', 'file-stopped', 'file-completed',
|
||||||
'jingle-incoming'):
|
'file-hash-error', 'jingle-incoming'):
|
||||||
# Get the first single message event
|
# Get the first single message event
|
||||||
event = gajim.events.get_first_event(account, fjid, type_)
|
event = gajim.events.get_first_event(account, fjid, type_)
|
||||||
if not event:
|
if not event:
|
||||||
|
|
|
@ -25,7 +25,7 @@ import message_control
|
||||||
|
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import helpers
|
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,
|
def build_resources_submenu(contacts, account, action, room_jid=None,
|
||||||
room_account=None, cap=None):
|
room_account=None, cap=None):
|
||||||
|
@ -227,7 +227,7 @@ control=None, gc_contact=None):
|
||||||
else:
|
else:
|
||||||
start_chat_menuitem.connect('activate',
|
start_chat_menuitem.connect('activate',
|
||||||
gajim.interface.on_open_chat_window, contact, account)
|
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.set_sensitive(True)
|
||||||
send_file_menuitem.connect('activate',
|
send_file_menuitem.connect('activate',
|
||||||
roster.on_send_file_menuitem_activate, contact, account)
|
roster.on_send_file_menuitem_activate, contact, account)
|
||||||
|
|
|
@ -1926,6 +1926,9 @@ class RosterWindow:
|
||||||
ft.show_stopped(jid, data, error_msg=msg_err)
|
ft.show_stopped(jid, data, error_msg=msg_err)
|
||||||
gajim.events.remove_events(account, jid, event)
|
gajim.events.remove_events(account, jid, event)
|
||||||
return True
|
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':
|
elif event.type_ == 'file-completed':
|
||||||
ft.show_completed(jid, data)
|
ft.show_completed(jid, data)
|
||||||
gajim.events.remove_events(account, jid, event)
|
gajim.events.remove_events(account, jid, event)
|
||||||
|
|
157
test/unit/test_jingle.py
Normal file
157
test/unit/test_jingle.py
Normal file
|
@ -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 = '''
|
||||||
|
<iq xmlns="jabber:client" to="jingleft@thiessen.im/Gajim" type="set" id="43">
|
||||||
|
<jingle xmlns="urn:xmpp:jingle:1" action="session-initiate" initiator="jtest@thiessen.im/Gajim" sid="38">
|
||||||
|
<content name="fileWL1Y2JIPTM5RAD68" creator="initiator">
|
||||||
|
<security xmlns="urn:xmpp:jingle:security:xtls:0">
|
||||||
|
<method name="x509" />
|
||||||
|
</security>
|
||||||
|
<description xmlns="urn:xmpp:jingle:apps:file-transfer:1">
|
||||||
|
<offer>
|
||||||
|
<file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="to" size="2273">
|
||||||
|
<desc />
|
||||||
|
</file>
|
||||||
|
</offer>
|
||||||
|
</description>
|
||||||
|
<transport xmlns="urn:xmpp:jingle:transports:s5b:1" sid="39">
|
||||||
|
<candidate jid="jtest@thiessen.im/Gajim" cid="40" priority="8257536" host="192.168.2.100" type="direct" port="28011" />
|
||||||
|
<candidate jid="proxy.thiessen.im" cid="41" priority="655360" host="192.168.2.100" type="proxy" port="5000" />
|
||||||
|
<candidate jid="proxy.jabbim.cz" cid="42" priority="655360" host="192.168.2.100" type="proxy" port="7777" />
|
||||||
|
</transport>
|
||||||
|
</content>
|
||||||
|
</jingle>
|
||||||
|
</iq>
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
transport_info = '''
|
||||||
|
<iq from='jtest@thiessen.im/Gajim'
|
||||||
|
id='hjdi8'
|
||||||
|
to='jingleft@thiessen.im/Gajim'
|
||||||
|
type='set'>
|
||||||
|
<jingle xmlns='urn:xmpp:jingle:1'
|
||||||
|
action='transport-info'
|
||||||
|
initiator='jtest@thiessen.im/Gajim'
|
||||||
|
sid='38'>
|
||||||
|
<content creator='initiator' name='fileWL1Y2JIPTM5RAD68'>
|
||||||
|
<transport xmlns='urn:xmpp:jingle:transports:s5b:1'
|
||||||
|
sid='vj3hs98y'>
|
||||||
|
<candidate-used cid='hr65dqyd'/>
|
||||||
|
</transport>
|
||||||
|
</content>
|
||||||
|
</jingle>
|
||||||
|
</iq>
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
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("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'>")
|
||||||
|
|
||||||
|
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()
|
177
test/unit/test_socks5.py
Normal file
177
test/unit/test_socks5.py
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
'''
|
||||||
|
Tests for dispatcher_nb.py
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import lib
|
||||||
|
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):
|
||||||
|
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}
|
||||||
|
queue = Mock()
|
||||||
|
queue.file_props = {}
|
||||||
|
#self.sockobj = Socks5Receiver(fake_idlequeue(), streamhost, None)
|
||||||
|
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
|
||||||
|
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.file_props['paused'] = ''
|
||||||
|
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_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()
|
||||||
|
try:
|
||||||
|
self.sockobj.pollout()
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._check_inout()
|
||||||
|
|
||||||
|
def test_server_negoc(self):
|
||||||
|
return
|
||||||
|
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()
|
Loading…
Add table
Reference in a new issue