Fix a bunch of issues in common.jingle*.

This commit is contained in:
Emmanuel Gil Peyrot 2017-02-08 02:49:33 +00:00
parent 57fb80f1fa
commit ad9370afa8
9 changed files with 527 additions and 440 deletions

View File

@ -142,6 +142,10 @@ SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
# zeroconf account name
ZEROCONF_ACC_NAME = 'Local'
# These will be set in gajim.gui_interface.
idlequeue = None
socks5queue = None
HAVE_ZEROCONF = True
try:
__import__('avahi')

View File

@ -28,19 +28,21 @@ Handles the jingle signalling protocol
# * config:
# - codecs
import logging
import nbxmpp
from common import helpers
from common import gajim
from common.jingle_session import JingleSession, JingleStates
if gajim.HAVE_FARSTREAM:
from common.jingle_rtp import JingleAudio, JingleVideo
from common.jingle_ft import JingleFileTransfer
from common.jingle_transport import JingleTransportSocks5, JingleTransportIBB
if gajim.HAVE_FARSTREAM:
from common.jingle_rtp import JingleAudio, JingleVideo
import logging
logger = logging.getLogger('gajim.c.jingle')
class ConnectionJingle(object):
"""
This object depends on that it is a part of Connection class.
@ -80,7 +82,7 @@ class ConnectionJingle(object):
try:
jid = helpers.get_full_jid_from_iq(stanza)
except helpers.InvalidFormat:
logger.warn('Invalid JID: %s, ignoring it' % stanza.getFrom())
logger.warning('Invalid JID: %s, ignoring it', stanza.getFrom())
return
id_ = stanza.getID()
if (jid, id_) in self.__iq_responses.keys():
@ -102,14 +104,14 @@ class ConnectionJingle(object):
if sid not in self._sessions:
#TODO: tie-breaking and other things...
newjingle = JingleSession(con=self, weinitiate=False, jid=jid,
iq_id=id_, sid=sid)
iq_id=id_, sid=sid)
self._sessions[sid] = newjingle
# we already have such session in dispatcher...
self._sessions[sid].collect_iq_id(id_)
self._sessions[sid].on_stanza(stanza)
# Delete invalid/unneeded sessions
if sid in self._sessions and \
self._sessions[sid].state == JingleStates.ended:
self._sessions[sid].state == JingleStates.ENDED:
self.delete_jingle_session(sid)
raise nbxmpp.NodeProcessed
@ -132,20 +134,20 @@ class ConnectionJingle(object):
jingle = self.get_jingle_session(jid, media='audio')
if jingle:
jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
out_xid=out_xid))
out_xid=out_xid))
else:
jingle = JingleSession(self, weinitiate=True, jid=jid)
self._sessions[jingle.sid] = jingle
jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
out_xid=out_xid))
out_xid=out_xid))
jingle.start_session()
return jingle.sid
def start_file_transfer(self, jid, file_props, request=False):
logger.info("start file transfer with file: %s" % 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 gajim.contacts.is_gc_contact(self.name,jid):
gajim.get_jid_without_resource(jid))
if gajim.contacts.is_gc_contact(self.name, jid):
gcc = jid.split('/')
if len(gcc) == 2:
contact = gajim.contacts.get_gc_contact(self.name, gcc[0], gcc[1])
@ -162,7 +164,8 @@ class ConnectionJingle(object):
elif contact.supports(nbxmpp.NS_JINGLE_IBB):
transport = JingleTransportIBB()
c = JingleFileTransfer(jingle, transport=transport,
file_props=file_props, use_security=use_security)
file_props=file_props,
use_security=use_security)
file_props.algo = self.__hash_support(contact)
jingle.add_content('file' + helpers.get_random_string_16(), c)
jingle.start_session()

View File

@ -37,7 +37,7 @@ class JingleContentSetupException(Exception):
"""
class JingleContent(object):
class JingleContent:
"""
An abstraction of content in Jingle sessions
"""
@ -48,8 +48,8 @@ class JingleContent(object):
# will be filled by JingleSession.add_content()
# don't uncomment these lines, we will catch more buggy code then
# (a JingleContent not added to session shouldn't send anything)
#self.creator = None
#self.name = None
self.creator = None
self.name = None
self.accepted = False
self.sent = False
self.negotiated = False
@ -59,35 +59,39 @@ class JingleContent(object):
self.senders = 'both' #FIXME
self.allow_sending = True # Used for stream direction, attribute 'senders'
# These were found by the Politie
self.file_props = None
self.use_security = None
self.callbacks = {
# these are called when *we* get stanzas
'content-accept': [self.__on_transport_info,
self.__on_content_accept],
'content-add': [self.__on_transport_info],
'content-modify': [],
'content-reject': [],
'content-remove': [],
'description-info': [],
'security-info': [],
'session-accept': [self.__on_transport_info,
self.__on_content_accept],
'session-info': [],
'session-initiate': [self.__on_transport_info],
'session-terminate': [],
'transport-info': [self.__on_transport_info],
'transport-replace': [self.__on_transport_replace],
'transport-accept': [],
'transport-reject': [],
'iq-result': [],
'iq-error': [],
# these are called when *we* sent these stanzas
'content-accept-sent': [self.__fill_jingle_stanza,
self.__on_content_accept],
'content-add-sent': [self.__fill_jingle_stanza],
'session-initiate-sent': [self.__fill_jingle_stanza],
'session-accept-sent': [self.__fill_jingle_stanza,
self.__on_content_accept],
'session-terminate-sent': [],
# these are called when *we* get stanzas
'content-accept': [self.__on_transport_info,
self.__on_content_accept],
'content-add': [self.__on_transport_info],
'content-modify': [],
'content-reject': [],
'content-remove': [],
'description-info': [],
'security-info': [],
'session-accept': [self.__on_transport_info,
self.__on_content_accept],
'session-info': [],
'session-initiate': [self.__on_transport_info],
'session-terminate': [],
'transport-info': [self.__on_transport_info],
'transport-replace': [self.__on_transport_replace],
'transport-accept': [],
'transport-reject': [],
'iq-result': [],
'iq-error': [],
# these are called when *we* sent these stanzas
'content-accept-sent': [self.__fill_jingle_stanza,
self.__on_content_accept],
'content-add-sent': [self.__fill_jingle_stanza],
'session-initiate-sent': [self.__fill_jingle_stanza],
'session-accept-sent': [self.__fill_jingle_stanza,
self.__on_content_accept],
'session-terminate-sent': [],
}
def is_ready(self):
@ -134,8 +138,8 @@ class JingleContent(object):
if payload is None:
payload = []
return nbxmpp.Node('content',
attrs={'name': self.name, 'creator': self.creator},
payload=payload)
attrs={'name': self.name, 'creator': self.creator},
payload=payload)
def send_candidate(self, candidate):
"""
@ -191,17 +195,16 @@ class JingleContent(object):
file_tag.addChild(node=node)
if self.file_props.type_ == 'r':
if self.file_props.hash_:
h = file_tag.addChild('hash', attrs={
'algo': self.file_props.algo}, namespace=nbxmpp.NS_HASHES,
payload=self.file_props.hash_)
file_tag.addChild('hash', attrs={'algo': self.file_props.algo},
namespace=nbxmpp.NS_HASHES,
payload=self.file_props.hash_)
else:
# if the file is less than 10 mb, then it is small
# lets calculate it right away
if self.file_props.size < 10000000 and not \
self.file_props.hash_:
h = self._calcHash()
if h:
file_tag.addChild(node=h)
if self.file_props.size < 10000000 and not self.file_props.hash_:
hash_data = content._compute_hash()
if hash_data:
file_tag.addChild(node=hash_data)
pjid = gajim.get_jid_without_resource(self.session.peerjid)
file_info = {'name' : self.file_props.name,
'file-name' : self.file_props.file_name,
@ -223,9 +226,9 @@ class JingleContent(object):
cert = load_cert_file(certpath)
if cert:
try:
digest_algo = cert.get_signature_algorithm().decode('utf-8'
).split('With')[0]
except AttributeError as e:
digest_algo = (cert.get_signature_algorithm()
.decode('utf-8').split('With')[0])
except AttributeError:
# Old py-OpenSSL is missing get_signature_algorithm
digest_algo = "sha256"
security.addChild('fingerprint').addData(cert.digest(
@ -240,5 +243,3 @@ class JingleContent(object):
def destroy(self):
self.callbacks = None
del self.session.contents[(self.creator, self.name)]

View File

@ -19,41 +19,47 @@
Handles Jingle File Transfer (XEP 0234)
"""
import os
import hashlib
from common import gajim
import logging
import os
import threading
from enum import IntEnum
import nbxmpp
from common import gajim
from common import configpaths
from . import jingle_xtls
from common import jingle_xtls
from common.jingle_content import contents, JingleContent
from common.jingle_transport import *
from common.jingle_transport import JingleTransportSocks5, TransportType
from common import helpers
from common.connection_handlers_events import FileRequestReceivedEvent
import threading
import logging
from common.jingle_ftstates import *
from common.jingle_ftstates import (
StateInitialized, StateCandSent, StateCandReceived, StateTransfering,
StateCandSentAndRecv, StateTransportReplace)
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 State(IntEnum):
NOT_STARTED = 0
INITIALIZED = 1
# We send the candidates and we are waiting for a reply
CAND_SENT = 2
# We received the candidates and we are waiting to reply
CAND_RECEIVED = 3
# We have sent and received the candidates
# This also includes any candidate-error received or sent
CAND_SENT_AND_RECEIVED = 4
TRANSPORT_REPLACE = 5
# We are transfering the file
TRANSFERING = 6
class JingleFileTransfer(JingleContent):
def __init__(self, session, transport=None, file_props=None,
use_security=False):
use_security=False):
JingleContent.__init__(self, session, transport)
log.info("transport value: %s" % 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'] += [
@ -85,29 +91,31 @@ class JingleFileTransfer(JingleContent):
self.file_props.sid = session.sid
self.file_props.transfered_size = []
self.file_props.transport_sid = self.transport.sid
log.info("FT request: %s" % file_props)
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)
log.info('ourjid: %s', session.ourjid)
self.session = session
self.media = 'file'
self.nominated_cand = {}
if gajim.contacts.is_gc_contact(session.connection.name,
session.peerjid):
session.peerjid):
roomjid = session.peerjid.split('/')[0]
dstaddr = hashlib.sha1(('%s%s%s' % (self.file_props.sid,
session.ourjid, roomjid)).encode('utf-8')).hexdigest()
session.ourjid, roomjid))
.encode('utf-8')).hexdigest()
self.file_props.dstaddr = dstaddr
self.state = STATE_NOT_STARTED
self.states = {STATE_INITIALIZED : StateInitialized(self),
STATE_CAND_SENT : StateCandSent(self),
STATE_CAND_RECEIVED : StateCandReceived(self),
STATE_TRANSFERING : StateTransfering(self),
STATE_TRANSPORT_REPLACE : StateTransportReplace(self),
STATE_CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
self.state = State.NOT_STARTED
self.states = {
State.INITIALIZED : StateInitialized(self),
State.CAND_SENT : StateCandSent(self),
State.CAND_RECEIVED : StateCandReceived(self),
State.TRANSFERING : StateTransfering(self),
State.TRANSPORT_REPLACE : StateTransportReplace(self),
State.CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
}
if jingle_xtls.PYOPENSSL_PRESENT:
@ -129,8 +137,10 @@ class JingleFileTransfer(JingleContent):
def __on_session_initiate(self, stanza, content, error, action):
log.debug("Jingle FT request received")
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
conn=self.session.connection, stanza=stanza, jingle_content=content,
FT_content=self))
conn=self.session.connection,
stanza=stanza,
jingle_content=content,
FT_content=self))
if self.session.request:
# accept the request
self.session.approve_content(self.media, self.name)
@ -141,10 +151,11 @@ class JingleFileTransfer(JingleContent):
def __send_hash(self):
# Send hash in a session info
checksum = nbxmpp.Node(tag='checksum', payload=[nbxmpp.Node(tag='file',
payload=[self._calcHash()])])
checksum = nbxmpp.Node(tag='checksum',
payload=[nbxmpp.Node(tag='file',
payload=[self._compute_hash()])])
checksum.setNamespace(nbxmpp.NS_JINGLE_FILE_TRANSFER)
self.session.__session_info(checksum )
self.session.__session_info(checksum)
pjid = gajim.get_jid_without_resource(self.session.peerjid)
file_info = {'name' : self.file_props.name,
'file-name' : self.file_props.file_name,
@ -155,13 +166,13 @@ class JingleFileTransfer(JingleContent):
}
self.session.connection.set_file_info(file_info)
def _calcHash(self):
def _compute_hash(self):
# Caculates the hash and returns a xep-300 hash stanza
if self.file_props.algo == None:
if self.file_props.algo is None:
return
try:
file_ = open(self.file_props.file_name, 'rb')
except:
except IOError:
# can't open file
return
h = nbxmpp.Hashes()
@ -192,30 +203,31 @@ class JingleFileTransfer(JingleContent):
fingerprint = fingerprint.getData()
self.x509_fingerprint = fingerprint
if not jingle_xtls.check_cert(gajim.get_jid_without_resource(
self.session.responder), fingerprint):
self.session.responder), fingerprint):
id_ = jingle_xtls.send_cert_request(con,
self.session.responder)
self.session.responder)
jingle_xtls.key_exchange_pend(id_,
self.continue_session_accept, [stanza])
self.continue_session_accept,
[stanza])
raise nbxmpp.NodeProcessed
self.continue_session_accept(stanza)
def continue_session_accept(self, stanza):
con = self.session.connection
if self.state == STATE_TRANSPORT_REPLACE:
if self.state == State.TRANSPORT_REPLACE:
# If we are requesting we don't have the file
if self.session.werequest:
raise nbxmpp.NodeProcessed
# We send the file
self.__state_changed(STATE_TRANSFERING)
self.__state_changed(State.TRANSFERING)
raise nbxmpp.NodeProcessed
self.file_props.streamhosts = self.transport.remote_candidates
# Calculate file hash in a new thread
# if we haven't sent the hash already.
if self.file_props.hash_ is None and self.file_props.algo and \
not self.werequest:
self.hashThread = threading.Thread(target=self.__send_hash)
self.hashThread.start()
not self.werequest:
self.hash_thread = threading.Thread(target=self.__send_hash)
self.hash_thread.start()
for host in self.file_props.streamhosts:
host['initiator'] = self.session.initiator
host['target'] = self.session.responder
@ -225,11 +237,13 @@ class JingleFileTransfer(JingleContent):
fingerprint = 'client'
if self.transport.type_ == TransportType.SOCKS5:
gajim.socks5queue.connect_to_hosts(self.session.connection.name,
self.file_props.sid, self.on_connect,
self._on_connect_error, fingerprint=fingerprint,
receiving=False)
self.file_props.sid,
self.on_connect,
self._on_connect_error,
fingerprint=fingerprint,
receiving=False)
raise nbxmpp.NodeProcessed
self.__state_changed(STATE_TRANSFERING)
self.__state_changed(State.TRANSFERING)
raise nbxmpp.NodeProcessed
def __on_session_terminate(self, stanza, content, error, action):
@ -249,78 +263,78 @@ class JingleFileTransfer(JingleContent):
def __on_transport_info(self, stanza, content, error, action):
log.info("__on_transport_info")
candError = content.getTag('transport').getTag('candidate-error')
candUsed = content.getTag('transport').getTag('candidate-used')
if (candError or candUsed) and \
self.state >= STATE_CAND_SENT_AND_RECEIVED:
cand_error = content.getTag('transport').getTag('candidate-error')
cand_used = content.getTag('transport').getTag('candidate-used')
if (cand_error or cand_used) and \
self.state >= State.CAND_SENT_AND_RECEIVED:
raise nbxmpp.NodeProcessed
if candError:
if cand_error:
if not gajim.socks5queue.listener.connections:
gajim.socks5queue.listener.disconnect()
self.nominated_cand['peer-cand'] = False
if self.state == STATE_CAND_SENT:
if 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)
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)
self.__state_changed(State.TRANSFERING)
raise nbxmpp.NodeProcessed
else:
args = {'candError' : True}
self.__state_changed(STATE_CAND_RECEIVED, args)
args = {'cand_error' : True}
self.__state_changed(State.CAND_RECEIVED, args)
return
if candUsed:
streamhost_cid = candUsed.getAttr('cid')
if cand_used:
streamhost_cid = cand_used.getAttr('cid')
streamhost_used = None
for cand in self.transport.candidates:
if cand['candidate_id'] == streamhost_cid:
streamhost_used = cand
break
if streamhost_used == None or streamhost_used['type'] == 'proxy':
if streamhost_used is None or streamhost_used['type'] == 'proxy':
if gajim.socks5queue.listener and \
not gajim.socks5queue.listener.connections:
gajim.socks5queue.listener.disconnect()
if content.getTag('transport').getTag('activated'):
self.state = STATE_TRANSFERING
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')
self.session.connection.name, 'client')
return
args = {'content' : content,
'sendCand' : False}
if self.state == STATE_CAND_SENT:
self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args)
self.__state_changed(STATE_TRANSFERING)
args = {'content': content,
'sendCand': False}
if self.state == State.CAND_SENT:
self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
self.__state_changed(State.TRANSFERING)
raise nbxmpp.NodeProcessed
else:
self.__state_changed(STATE_CAND_RECEIVED, args)
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 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)
self.__state_changed(State.TRANSPORT_REPLACE)
return
# initiate transfer
self.__state_changed(STATE_TRANSFERING)
self.__state_changed(State.TRANSFERING)
def __transport_setup(self, stanza=None, content=None, error=None,
action=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
self.state = State.TRANSFERING
else:
self._listen_host()
@ -334,19 +348,19 @@ class JingleFileTransfer(JingleContent):
args = {'streamhost' : streamhost,
'sendCand' : True}
self.nominated_cand['our-cand'] = streamhost
self.__sendCand(args)
self.__send_candidate(args)
def _on_connect_error(self, sid):
log.info('connect error, sid=' + sid)
args = {'candError' : True,
'sendCand' : True}
self.__sendCand(args)
self.__send_candidate(args)
def __sendCand(self, args):
if self.state == STATE_CAND_RECEIVED:
self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args)
def __send_candidate(self, args):
if self.state == State.CAND_RECEIVED:
self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
else:
self.__state_changed(STATE_CAND_SENT, args)
self.__state_changed(State.CAND_SENT, args)
def _store_socks5_sid(self, sid, hash_id):
# callback from socsk5queue.start_listener
@ -356,44 +370,40 @@ class JingleFileTransfer(JingleContent):
receiver = self.file_props.receiver
sender = self.file_props.sender
sha_str = helpers.get_auth_sha(self.file_props.sid, sender,
receiver)
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, typ='sender')
else:
listener = gajim.socks5queue.start_listener(port, sha_str,
self._store_socks5_sid, self.file_props,
fingerprint=fingerprint, typ='receiver')
listener = gajim.socks5queue.start_listener(port, sha_str,
self._store_socks5_sid,
self.file_props,
fingerprint=fingerprint,
typ='sender' if self.weinitiate else 'receiver')
if not listener:
# send error message, notify the user
return
def isOurCandUsed(self):
def is_our_candidate_used(self):
'''
If this method returns true then the candidate we nominated will be
used, if false, the candidate nominated by peer will be used
'''
if self.nominated_cand['peer-cand'] == False:
if not self.nominated_cand['peer-cand']:
return True
if self.nominated_cand['our-cand'] == False:
if not self.nominated_cand['our-cand']:
return False
peer_pr = int(self.nominated_cand['peer-cand']['priority'])
our_pr = int(self.nominated_cand['our-cand']['priority'])
if peer_pr != our_pr:
return our_pr > peer_pr
else:
return self.weinitiate
return self.weinitiate
def start_IBB_transfer(self):
def start_ibb_transfer(self):
if self.file_props.type_ == 's':
self.__state_changed(STATE_TRANSFERING)
self.__state_changed(State.TRANSFERING)
def get_content(desc):

View File

@ -13,11 +13,14 @@
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from common import gajim
import nbxmpp
from common.jingle_transport import *
from common import gajim
from common.jingle_transport import TransportType
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
import logging
log = logging.getLogger('gajim.c.jingle_ftstates')
class JingleFileTransferStates:
'''
@ -31,7 +34,7 @@ class JingleFileTransferStates:
'''
This method MUST be overriden by a subclass
'''
raise Exception('This is an abstract method!!')
raise NotImplementedError('This is an abstract method!')
class StateInitialized(JingleFileTransferStates):
@ -50,8 +53,10 @@ class StateInitialized(JingleFileTransferStates):
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)
self.jft.file_props.sid,
self.jft.on_connect,
self.jft._on_connect_error,
fingerprint=fingerprint)
class StateCandSent(JingleFileTransferStates):
@ -59,7 +64,7 @@ class StateCandSent(JingleFileTransferStates):
This state sends our nominated candidate
'''
def _sendCand(self, args):
def _send_candidate(self, args):
if 'candError' in args:
self.jft.nominated_cand['our-cand'] = False
self.jft.send_error_candidate()
@ -80,16 +85,16 @@ class StateCandSent(JingleFileTransferStates):
self.jft.session.send_transport_info(content)
def action(self, args=None):
self._sendCand(args)
self._send_candidate(args)
class StateCandReceived(JingleFileTransferStates):
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):
def _recv_candidate(self, args):
if 'candError' in args:
return
content = args['content']
@ -100,17 +105,17 @@ class StateCandReceived(JingleFileTransferStates):
if cand['candidate_id'] == streamhost_cid:
streamhost_used = cand
break
if streamhost_used == None:
if streamhost_used is 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)
self._recv_candidate(args)
class StateCandSentAndRecv( StateCandSent, StateCandReceived):
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
@ -119,9 +124,9 @@ class StateCandSentAndRecv( StateCandSent, StateCandReceived):
def action(self, args=None):
if args['sendCand']:
self._sendCand(args)
self._send_candidate(args)
else:
self._recvCand(args)
self._recv_candidate(args)
class StateTransportReplace(JingleFileTransferStates):
@ -139,16 +144,16 @@ class StateTransfering(JingleFileTransferStates):
we have.
'''
def __start_IBB_transfer(self, con):
def _start_ibb_transfer(self, con):
self.jft.file_props.transport_sid = self.jft.transport.sid
fp = open(self.jft.file_props.file_name, 'r')
con.OpenStream( self.jft.file_props.sid, self.jft.session.peerjid, fp,
blocksize=4096)
con.OpenStream(self.jft.file_props.sid, self.jft.session.peerjid, fp,
blocksize=4096)
def __start_SOCK5_transfer(self):
def _start_sock5_transfer(self):
# It tells wether we start the transfer as client or server
mode = None
if self.jft.isOurCandUsed():
if self.jft.is_our_candidate_used():
mode = 'client'
streamhost_used = self.jft.nominated_cand['our-cand']
gajim.socks5queue.remove_server(self.jft.file_props.sid)
@ -191,34 +196,34 @@ class StateTransfering(JingleFileTransferStates):
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)
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)
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)
sockobj)
streamhost_used['idx'] = sockobj.queue_idx
# If we offered the nominated candidate used, we activate
# the proxy
if not self.jft.isOurCandUsed():
if not self.jft.is_our_candidate_used():
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)
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)
self._start_ibb_transfer(self.jft.session.connection)
elif self.jft.transport.type_ == TransportType.SOCKS5:
self.__start_SOCK5_transfer()
self._start_sock5_transfer()

View File

@ -17,6 +17,7 @@
Handles Jingle RTP sessions (XEP 0167)
"""
import logging
import socket
import nbxmpp
import gi
@ -35,7 +36,6 @@ from common.jingle_session import FailedApplication
from collections import deque
import logging
log = logging.getLogger('gajim.c.jingle_rtp')
@ -46,7 +46,8 @@ class JingleRTPContent(JingleContent):
JingleContent.__init__(self, session, transport)
self.media = media
self._dtmf_running = False
self.farstream_media = {'audio': Farstream.MediaType.AUDIO,
self.farstream_media = {
'audio': Farstream.MediaType.AUDIO,
'video': Farstream.MediaType.VIDEO}[media]
self.pipeline = None
@ -55,6 +56,12 @@ class JingleRTPContent(JingleContent):
self.candidates_ready = False # True when local candidates are prepared
# TODO
self.conference = None
self.funnel = None
self.p2psession = None
self.p2pstream = None
self.callbacks['session-initiate'] += [self.__on_remote_codecs]
self.callbacks['content-add'] += [self.__on_remote_codecs]
self.callbacks['description-info'] += [self.__on_remote_codecs]
@ -90,32 +97,32 @@ class JingleRTPContent(JingleContent):
if stun_server:
try:
ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
socket.SOCK_STREAM)[0][4][0]
socket.SOCK_STREAM)[0][4][0]
except socket.gaierror as e:
log.warning('Lookup of stun ip failed: %s' % str(e))
log.warning('Lookup of stun ip failed: %s', str(e))
else:
params['stun-ip'] = ip
self.p2pstream = self.p2psession.new_stream(participant,
Farstream.StreamDirection.BOTH)
Farstream.StreamDirection.BOTH)
self.p2pstream.connect('src-pad-added', on_src_pad_added)
self.p2pstream.set_transmitter_ht('nice', params)
def is_ready(self):
return (JingleContent.is_ready(self) and self.candidates_ready)
return JingleContent.is_ready(self) and self.candidates_ready
def make_bin_from_config(self, config_key, pipeline, text):
pipeline = pipeline % gajim.config.get(config_key)
try:
bin = Gst.parse_bin_from_description(pipeline, True)
return bin
gst_bin = Gst.parse_bin_from_description(pipeline, True)
return gst_bin
except GLib.GError as e:
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.session.connection, level='error',
pri_txt=_('%s configuration error') % text.capitalize(),
sec_txt=_("Couldn't setup %s. Check your configuration.\n\n"
"Pipeline was:\n%s\n\nError was:\n%s") % (text, pipeline,
str(e))))
gajim.nec.push_incoming_event(
InformationEvent(
None, conn=self.session.connection, level='error',
pri_txt=_('%s configuration error') % text.capitalize(),
sec_txt=_('Couldnt setup %s. Check your configuration.\n\n'
'Pipeline was:\n%s\n\nError was:\n%s') % (text, pipeline, str(e))))
raise JingleContentSetupException
def add_remote_candidates(self, candidates):
@ -147,7 +154,7 @@ class JingleRTPContent(JingleContent):
def _start_dtmf(self, event):
if event in ('*', '#'):
event = {'*': Farstream.DTMFEvent.STAR,
'#': Farstream.DTMFEvent.POUND}[event]
'#': Farstream.DTMFEvent.POUND}[event]
else:
event = int(event)
self.p2psession.start_telephony_event(event, 2)
@ -157,7 +164,8 @@ class JingleRTPContent(JingleContent):
def _fill_content(self, content):
content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
attrs={'media': self.media}, payload=list(self.iter_codecs()))
attrs={'media': self.media},
payload=list(self.iter_codecs()))
def _setup_funnel(self):
self.funnel = Gst.ElementFactory.make('funnel', None)
@ -174,7 +182,7 @@ class JingleRTPContent(JingleContent):
def _on_gst_message(self, bus, message):
if message.type == Gst.MessageType.ELEMENT:
name = message.get_structure().get_name()
log.debug('gst element message: %s: %s' % (name, message))
log.debug('gst element message: %s: %s', name, message)
if name == 'farstream-new-active-candidate-pair':
pass
elif name == 'farstream-recv-codecs-changed':
@ -189,7 +197,7 @@ class JingleRTPContent(JingleContent):
self.transport.remote_candidates)
self.transport.remote_candidates = []
self.p2pstream.set_property('direction',
Farstream.StreamDirection.BOTH)
Farstream.StreamDirection.BOTH)
elif name == 'farstream-local-candidates-prepared':
self.candidates_ready = True
@ -208,19 +216,21 @@ class JingleRTPContent(JingleContent):
reason.setTag('failed-transport')
self.session.remove_content(self.creator, self.name, reason)
elif name == 'farstream-error':
log.error('Farstream error #%d!\nMessage: %s' % (
message.get_structure().get_value('error-no'),
message.get_structure().get_value('error-msg')))
log.error('Farstream error #%d!\nMessage: %s',
message.get_structure().get_value('error-no'),
message.get_structure().get_value('error-msg'))
elif message.type == Gst.MessageType.ERROR:
# TODO: Fix it to fallback to videotestsrc anytime an error occur,
# or raise an error, Jingle way
# or maybe one-sided stream?
if not self.stream_failed_once:
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.session.connection, level='error',
pri_txt=_('GStreamer error'), sec_txt=_('Error: %s\nDebug: '
'%s' % (message.get_structure().get_value('gerror'),
message.get_structure().get_value('debug')))))
gajim.nec.push_incoming_event(
InformationEvent(
None, conn=self.session.connection, level='error',
pri_txt=_('GStreamer error'),
sec_txt=_('Error: %s\nDebug: %s' %
(message.get_structure().get_value('gerror'),
message.get_structure().get_value('debug')))))
sink_pad = self.p2psession.get_property('sink-pad')
@ -243,7 +253,8 @@ class JingleRTPContent(JingleContent):
# Start playing again
self.pipeline.set_state(Gst.State.PLAYING)
def get_fallback_src(self):
@staticmethod
def get_fallback_src():
return Gst.ElementFactory.make('fakesrc', None)
def on_negotiated(self):
@ -270,7 +281,7 @@ class JingleRTPContent(JingleContent):
# ignore invalid payload-types
continue
c = Farstream.Codec.new(int(codec['id']), codec['name'],
self.farstream_media, int(codec['clockrate']))
self.farstream_media, int(codec['clockrate']))
if 'channels' in codec:
c.channels = int(codec['channels'])
else:
@ -288,14 +299,17 @@ class JingleRTPContent(JingleContent):
def iter_codecs(self):
codecs = self.p2psession.props.codecs_without_config
for codec in codecs:
attrs = {'name': codec.encoding_name,
attrs = {
'name': codec.encoding_name,
'id': codec.id,
'channels': codec.channels}
'channels': codec.channels
}
if codec.clock_rate:
attrs['clockrate'] = codec.clock_rate
if codec.optional_params:
payload = list(nbxmpp.Node('parameter', {'name': p.name,
'value': p.value}) for p in codec.optional_params)
payload = [nbxmpp.Node('parameter',
{'name': p.name, 'value': p.value})
for p in codec.optional_params]
else:
payload = []
yield nbxmpp.Node('payload-type', attrs, payload)
@ -343,19 +357,22 @@ class JingleAudio(JingleRTPContent):
# place 16kHz before 8kHz, as buggy psi versions will take in
# account only the first codec
codecs = [Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
Farstream.MediaType.AUDIO, 16000),
codecs = [
Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
Farstream.MediaType.AUDIO, 8000)]
Farstream.MediaType.AUDIO, 16000),
Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
Farstream.MediaType.AUDIO, 8000)]
self.p2psession.set_codec_preferences(codecs)
# the local parts
# TODO: Add queues?
self.src_bin = self.make_bin_from_config('audio_input_device',
'%s ! audioconvert', _("audio input"))
'%s ! audioconvert',
_("audio input"))
self.sink = self.make_bin_from_config('audio_output_device',
'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output"))
'audioconvert ! volume name=gajim_out_vol ! %s',
_("audio output"))
self.mic_volume = self.src_bin.get_by_name('gajim_vol')
self.out_volume = self.sink.get_by_name('gajim_out_vol')
@ -411,15 +428,16 @@ class JingleVideo(JingleRTPContent):
tee = ''
self.src_bin = self.make_bin_from_config('video_input_device',
'%%s %s! %svideoscale ! %svideoconvert' % (tee, framerate,
video_size), _("video input"))
'%%s %s! %svideoscale ! %svideoconvert' %
(tee, framerate, video_size),
_("video input"))
self.pipeline.add(self.src_bin)
self.pipeline.set_state(Gst.State.PLAYING)
self.sink = self.make_bin_from_config('video_output_device',
'videoscale ! videoconvert ! %s',
_("video output"))
'videoscale ! videoconvert ! %s',
_("video output"))
self.pipeline.add(self.sink)

View File

@ -28,24 +28,28 @@ Handles Jingle sessions (XEP 0166)
# - Tie-breaking
# * timeout
from common import gajim
import nbxmpp
from common.jingle_transport import get_jingle_transport, JingleTransportIBB
from common.jingle_content import get_jingle_content, JingleContentSetupException
from common.jingle_content import JingleContent
from common.jingle_ft import STATE_TRANSPORT_REPLACE
from common.connection_handlers_events import *
import logging
from enum import Enum
import nbxmpp
from common import gajim
from common.jingle_transport import get_jingle_transport, JingleTransportIBB
from common.jingle_content import get_jingle_content, JingleContentSetupException, JingleContent
from common.jingle_ft import State
from common.connection_handlers_events import (
FilesProp, JingleRequestReceivedEvent, JingleDisconnectedReceivedEvent,
JingleTransferCancelledEvent, JingleConnectedReceivedEvent,
JingleErrorReceivedEvent)
log = logging.getLogger("gajim.c.jingle_session")
# FIXME: Move it to JingleSession.States?
class JingleStates(object):
class JingleStates(Enum):
"""
States in which jingle session may exist
"""
ended = 0
pending = 1
active = 2
ENDED = 0
PENDING = 1
ACTIVE = 2
class OutOfOrder(Exception):
"""
@ -61,17 +65,18 @@ class TieBreak(Exception):
class FailedApplication(Exception):
"""
Exception that should be raised in case responder supports none of the payload-types offered by the initiator
Exception that should be raised in case responder supports none of the
payload-types offered by the initiator
"""
class JingleSession(object):
class JingleSession:
"""
This represents one jingle session, that is, one or more content types
negotiated between an initiator and a responder.
"""
def __init__(self, con, weinitiate, jid, iq_id=None, sid=None,
werequest=False):
werequest=False):
"""
con -- connection object,
weinitiate -- boolean, are we the initiator?
@ -85,16 +90,16 @@ class JingleSession(object):
self.ourjid = self.ourjid + '/' + con.server_resource
self.peerjid = jid # jid we connect to
# jid we use as the initiator
self.initiator = weinitiate and self.ourjid or self.peerjid
self.initiator = self.ourjid if weinitiate else self.peerjid
# jid we use as the responder
self.responder = weinitiate and self.peerjid or self.ourjid
self.responder = self.peerjid if weinitiate else self.ourjid
# are we an initiator?
self.weinitiate = weinitiate
# Are we requesting or offering a file?
self.werequest = werequest
self.request = False
# what state is session in? (one from JingleStates)
self.state = JingleStates.ended
self.state = JingleStates.ENDED
if not sid:
sid = con.connection.getAnID()
self.sid = sid # sessionid
@ -106,37 +111,37 @@ class JingleSession(object):
self.iq_ids = []
self.accepted = True # is this session accepted by user
# Tells whether this session is a file transfer or not
self.session_type_FT = False
self.session_type_ft = False
# callbacks to call on proper contents
# use .prepend() to add new callbacks, especially when you're going
# to send error instead of ack
self.callbacks = {
'content-accept': [self.__ack, self.__on_content_accept,
self.__broadcast],
'content-add': [self.__ack,
self.__on_content_add, self.__broadcast
], #TODO
'content-modify': [self.__ack], #TODO
'content-reject': [self.__ack, self.__on_content_remove],
'content-remove': [self.__ack, self.__on_content_remove],
'description-info': [self.__ack, self.__broadcast], #TODO
'security-info': [self.__ack], #TODO
'session-accept': [self.__ack, self.__on_session_accept,
self.__on_content_accept,
self.__broadcast],
'session-info': [self.__ack, self.__broadcast,
self.__on_session_info ],
'session-initiate': [self.__ack, self.__on_session_initiate,
self.__broadcast],
'session-terminate': [self.__ack,self.__on_session_terminate,
self.__broadcast_all],
'transport-info': [self.__ack, self.__broadcast],
'transport-replace': [self.__ack, self.__broadcast,
self.__on_transport_replace], #TODO
'transport-accept': [self.__ack], #TODO
'transport-reject': [self.__ack], #TODO
'iq-result': [self.__broadcast],
'iq-error': [self.__on_error],
'content-accept': [self.__ack, self.__on_content_accept,
self.__broadcast],
'content-add': [self.__ack,
self.__on_content_add, self.__broadcast
], #TODO
'content-modify': [self.__ack], #TODO
'content-reject': [self.__ack, self.__on_content_remove],
'content-remove': [self.__ack, self.__on_content_remove],
'description-info': [self.__ack, self.__broadcast], #TODO
'security-info': [self.__ack], #TODO
'session-accept': [self.__ack, self.__on_session_accept,
self.__on_content_accept,
self.__broadcast],
'session-info': [self.__ack, self.__broadcast,
self.__on_session_info],
'session-initiate': [self.__ack, self.__on_session_initiate,
self.__broadcast],
'session-terminate': [self.__ack, self.__on_session_terminate,
self.__broadcast_all],
'transport-info': [self.__ack, self.__broadcast],
'transport-replace': [self.__ack, self.__broadcast,
self.__on_transport_replace], #TODO
'transport-accept': [self.__ack], #TODO
'transport-reject': [self.__ack], #TODO
'iq-result': [self.__broadcast],
'iq-error': [self.__on_error],
}
def collect_iq_id(self, iq_id):
@ -174,7 +179,7 @@ class JingleSession(object):
def reject_content(self, media, name=None):
content = self.get_content(media, name)
if content:
if self.state == JingleStates.active:
if self.state == JingleStates.ACTIVE:
self.__content_reject(content)
content.destroy()
self.on_session_state_changed()
@ -184,7 +189,7 @@ class JingleSession(object):
Called when user stops or cancel session in UI
"""
reason = nbxmpp.Node('reason')
if self.state == JingleStates.active:
if self.state == JingleStates.ACTIVE:
reason.addChild('success')
else:
reason.addChild('cancel')
@ -232,11 +237,11 @@ class JingleSession(object):
if not self.contents:
self.end_session()
def modify_content(self, creator, name, transport = None):
def modify_content(self, creator, name, transport=None):
'''
Currently used for transport replacement
'''
content = self.contents[(creator,name)]
content = self.contents[(creator, name)]
transport.set_sid(content.transport.sid)
transport.set_file_props(content.transport.file_props)
content.transport = transport
@ -245,11 +250,11 @@ class JingleSession(object):
content.accepted = True
def on_session_state_changed(self, content=None):
if self.state == JingleStates.ended:
if self.state == JingleStates.ENDED:
# Session not yet started, only one action possible: session-initiate
if self.is_ready() and self.weinitiate:
self.__session_initiate()
elif self.state == JingleStates.pending:
elif self.state == JingleStates.PENDING:
# We can either send a session-accept or a content-add
if self.is_ready() and not self.weinitiate:
self.__session_accept()
@ -257,7 +262,7 @@ class JingleSession(object):
self.__content_add(content)
elif content and self.weinitiate:
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. However, if
# we are sending a file we can only use session_initiate.
if not content:
@ -278,7 +283,7 @@ class JingleSession(object):
Return True when all codecs and candidates are ready (for all contents)
"""
return (any((content.is_ready() for content in self.contents.values()))
and self.accepted)
and self.accepted)
def accept_session(self):
"""
@ -298,20 +303,20 @@ class JingleSession(object):
pass
def send_content_accept(self, content):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('content-accept')
jingle.addChild(node=content)
self.connection.connection.send(stanza)
def send_transport_info(self, content):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('transport-info')
jingle.addChild(node=content)
self.connection.connection.send(stanza)
self.collect_iq_id(stanza.getID())
def send_description_info(self, content):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('description-info')
jingle.addChild(node=content)
self.connection.connection.send(stanza)
@ -334,8 +339,8 @@ class JingleSession(object):
self.__send_error(stanza, 'bad-request')
return
# FIXME: If we aren't initiated and it's not a session-initiate...
if action not in ['session-initiate','session-terminate'] \
and self.state == JingleStates.ended:
if action not in ['session-initiate', 'session-terminate'] \
and self.state == JingleStates.ENDED:
self.__send_error(stanza, 'item-not-found', 'unknown-session')
return
else:
@ -381,7 +386,7 @@ class JingleSession(object):
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():
for creator, name in self.contents:
self.modify_content(creator, name, transport)
cont = self.contents[(creator, name)]
cont.transport = transport
@ -389,7 +394,7 @@ class JingleSession(object):
self.__append_contents(jingle)
self.__broadcast(stanza, jingle, None, 'transport-replace')
self.connection.connection.send(stanza)
self.state = JingleStates.pending
self.state = JingleStates.PENDING
def __on_transport_replace(self, stanza, jingle, error, action):
for content in jingle.iterTags('content'):
@ -405,15 +410,15 @@ class JingleSession(object):
elif transport_ns == nbxmpp.NS_JINGLE_IBB:
transport = JingleTransportIBB()
self.modify_content(creator, name, transport)
self.state = JingleStates.pending
self.contents[(creator,name)].state = STATE_TRANSPORT_REPLACE
self.state = JingleStates.PENDING
self.contents[(creator, name)].state = State.TRANSPORT_REPLACE
self.__ack(stanza, jingle, error, action)
self.__session_accept()
self.contents[(creator,name)].start_IBB_transfer()
self.contents[(creator, name)].start_IBB_transfer()
else:
stanza, jingle = self.__make_jingle('transport-reject')
content = jingle.setTag('content', attrs={'creator': creator,
'name': name})
'name': name})
content.setTag('transport', namespace=transport_ns)
self.connection.connection.send(stanza)
raise nbxmpp.NodeProcessed
@ -421,9 +426,9 @@ class JingleSession(object):
# FIXME: This ressource is unknown to us, what should we do?
# For now, reject the transport
stanza, jingle = self.__make_jingle('transport-reject')
c = jingle.setTag('content', attrs={'creator': creator,
'name': name})
c.setTag('transport', namespace=transport_ns)
content = jingle.setTag('content', attrs={'creator': creator,
'name': name})
content.setTag('transport', namespace=transport_ns)
self.connection.connection.send(stanza)
raise nbxmpp.NodeProcessed
@ -433,12 +438,12 @@ class JingleSession(object):
if payload[0].getName() == 'ringing':
# ignore ringing
raise nbxmpp.NodeProcessed
if self.state != JingleStates.active:
if self.state != JingleStates.ACTIVE:
raise OutOfOrder
for p in payload:
if p.getName() == 'checksum':
hash_ = p.getTag('file').getTag(name='hash',
namespace=nbxmpp.NS_HASHES)
for child in payload:
if child.getName() == 'checksum':
hash_ = child.getTag('file').getTag(name='hash',
namespace=nbxmpp.NS_HASHES)
algo = hash_.getAttr('algo')
if algo in nbxmpp.Hashes.supported:
file_props = FilesProp.getFileProp(self.connection.name,
@ -468,11 +473,12 @@ class JingleSession(object):
def __on_session_accept(self, stanza, jingle, error, action):
# FIXME
if self.state != JingleStates.pending:
if self.state != JingleStates.PENDING:
raise OutOfOrder
self.state = JingleStates.active
self.state = JingleStates.ACTIVE
def __on_content_accept(self, stanza, jingle, error, action):
@staticmethod
def __on_content_accept(stanza, jingle, error, action):
"""
Called when we get content-accept stanza or equivalent one (like
session-accept)
@ -484,7 +490,7 @@ class JingleSession(object):
name = content['name']
def __on_content_add(self, stanza, jingle, error, action):
if self.state == JingleStates.ended:
if self.state == JingleStates.ENDED:
raise OutOfOrder
parse_result = self.__parse_contents(jingle)
contents = parse_result[0]
@ -496,14 +502,16 @@ class JingleSession(object):
self.__content_reject(content)
self.contents[(content.creator, content.name)].destroy()
gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
conn=self.connection, jingle_session=self, contents=contents))
conn=self.connection,
jingle_session=self,
contents=contents))
def __on_session_initiate(self, stanza, jingle, error, action):
"""
We got a jingle session request from other entity, therefore we are the
receiver... Unpack the data, inform the user
"""
if self.state != JingleStates.ended:
if self.state != JingleStates.ENDED:
raise OutOfOrder
self.initiator = jingle['initiator']
self.responder = self.ourjid
@ -533,18 +541,17 @@ class JingleSession(object):
jingle.getTag('content').getTag('description').getTag('request')
if request:
self.request = True
h = request.getTag('file').getTag('hash')
h = h.getData() if h else None
hash_tag = request.getTag('file').getTag('hash')
hash_data = hash_tag.getData() if hash_tag else None
n = request.getTag('file').getTag('name')
n = n.getData() if n else None
pjid = gajim.get_jid_without_resource(self.peerjid)
file_info = self.connection.get_file_info(pjid, h, n,
self.connection.name)
file_info = self.connection.get_file_info(pjid, hash_data, n,
self.connection.name)
if not file_info:
log.warning('The peer ' + pjid + \
' is requesting a ' + \
'file that we dont have or ' + \
'it is not allowed to request')
log.warning('The peer %s is requesting a ' \
'file that we dont have or ' \
'it is not allowed to request', pjid)
self.decline_session()
raise nbxmpp.NodeProcessed
# If there's no content we understand...
@ -555,10 +562,12 @@ class JingleSession(object):
self.__ack(stanza, jingle, error, action)
self._session_terminate(reason)
raise nbxmpp.NodeProcessed
self.state = JingleStates.pending
self.state = JingleStates.PENDING
# Send event about starting a session
gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
conn=self.connection, jingle_session=self, contents=contents))
conn=self.connection,
jingle_session=self,
contents=contents))
def __broadcast(self, stanza, jingle, error, action):
"""
@ -594,10 +603,12 @@ class JingleSession(object):
else:
# TODO
text = reason
if reason == 'cancel' and self.session_type_FT:
if reason == 'cancel' and self.session_type_ft:
gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None,
conn=self.connection, jingle_session=self, media=None,
reason=text))
conn=self.connection,
jingle_session=self,
media=None,
reason=text))
def __broadcast_all(self, stanza, jingle, error, action):
"""
@ -621,7 +632,7 @@ class JingleSession(object):
if transport:
content = content_type(self, transport)
self.add_content(element['name'],
content, 'peer')
content, 'peer')
contents.append((content.media,))
else:
reasons.add('unsupported-transports')
@ -634,7 +645,7 @@ class JingleSession(object):
failure_reason = None
# Store the first reason of failure
for reason in ('failed-application', 'unsupported-transports',
'unsupported-applications'):
'unsupported-applications'):
if reason in reasons:
failure_reason = reason
break
@ -645,17 +656,21 @@ class JingleSession(object):
text = '%s (%s)' % (error, text)
if type_ != 'modify':
gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
conn=self.connection, jingle_session=self,
reason=text or error))
conn=self.connection,
jingle_session=self,
reason=text or error))
def __reason_from_stanza(self, stanza):
@staticmethod
def __reason_from_stanza(stanza):
# TODO: Move to GUI?
reason = 'success'
reasons = ['success', 'busy', 'cancel', 'connectivity-error',
'decline', 'expired', 'failed-application', 'failed-transport',
'general-error', 'gone', 'incompatible-parameters', 'media-error',
'security-error', 'timeout', 'unsupported-applications',
'unsupported-transports']
reasons = [
'success', 'busy', 'cancel', 'connectivity-error', 'decline',
'expired', 'failed-application', 'failed-transport',
'general-error', 'gone', 'incompatible-parameters', 'media-error',
'security-error', 'timeout', 'unsupported-applications',
'unsupported-transports'
]
tag = stanza.getTag('reason')
text = ''
if tag:
@ -668,12 +683,14 @@ class JingleSession(object):
def __make_jingle(self, action, reason=None):
stanza = nbxmpp.Iq(typ='set', to=nbxmpp.JID(self.peerjid),
frm=self.ourjid)
attrs = {'action': action,
'sid': self.sid,
'initiator' : self.initiator}
frm=self.ourjid)
attrs = {
'action': action,
'sid': self.sid,
'initiator' : self.initiator
}
jingle = stanza.addChild('jingle', attrs=attrs,
namespace=nbxmpp.NS_JINGLE)
namespace=nbxmpp.NS_JINGLE)
if reason is not None:
jingle.addChild(node=reason)
return stanza, jingle
@ -690,13 +707,15 @@ class JingleSession(object):
self.connection.connection.send(err_stanza)
self.__dispatch_error(jingle_error or error, text, type_)
def __append_content(self, jingle, content):
@staticmethod
def __append_content(jingle, content):
"""
Append <content/> element to <jingle/> element, with (full=True) or
without (full=False) <content/> children
"""
jingle.addChild('content',
attrs={'name': content.name, 'creator': content.creator})
attrs={'name': content.name,
'creator': content.creator})
def __append_contents(self, jingle):
"""
@ -709,36 +728,36 @@ class JingleSession(object):
self.__append_content(jingle, content)
def __session_initiate(self):
assert self.state == JingleStates.ended
assert self.state == JingleStates.ENDED
stanza, jingle = self.__make_jingle('session-initiate')
self.__append_contents(jingle)
self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
self.connection.connection.send(stanza)
self.collect_iq_id(stanza.getID())
self.state = JingleStates.pending
self.state = JingleStates.PENDING
def __session_accept(self):
assert self.state == JingleStates.pending
assert self.state == JingleStates.PENDING
stanza, jingle = self.__make_jingle('session-accept')
self.__append_contents(jingle)
self.__broadcast(stanza, jingle, None, 'session-accept-sent')
self.connection.connection.send(stanza)
self.collect_iq_id(stanza.getID())
self.state = JingleStates.active
self.state = JingleStates.ACTIVE
def __session_info(self, payload=None):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('session-info')
if payload:
jingle.addChild(node=payload)
self.connection.connection.send(stanza)
def _JingleFileTransfer__session_info(self, p):
def _JingleFileTransfer__session_info(self, payload):
# For some strange reason when I call
# self.session.__session_info(h) from the jingleFileTransfer object
# self.session.__session_info(payload) from the jingleFileTransfer object
# within a thread, this method gets called instead. Even though, it
# isn't being called explicitly.
self.__session_info(p)
self.__session_info(payload)
def _session_terminate(self, reason=None):
stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
@ -755,12 +774,14 @@ class JingleSession(object):
text = reason
self.connection.delete_jingle_session(self.sid)
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
conn=self.connection, jingle_session=self, media=None,
reason=text))
conn=self.connection,
jingle_session=self,
media=None,
reason=text))
def __content_add(self, content):
# TODO: test
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('content-add')
self.__append_content(jingle, content)
self.__broadcast(stanza, jingle, None, 'content-add-sent')
@ -769,7 +790,7 @@ class JingleSession(object):
def __content_accept(self, content):
# TODO: test
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('content-accept')
self.__append_content(jingle, content)
self.__broadcast(stanza, jingle, None, 'content-accept-sent')
@ -777,31 +798,35 @@ class JingleSession(object):
self.collect_iq_id(id_)
def __content_reject(self, content):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
stanza, jingle = self.__make_jingle('content-reject')
self.__append_content(jingle, content)
self.connection.connection.send(stanza)
# TODO: this will fail if content is not an RTP content
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
conn=self.connection, jingle_session=self, media=content.media,
reason='rejected'))
conn=self.connection,
jingle_session=self,
media=content.media,
reason='rejected'))
def __content_modify(self):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
def __content_remove(self, content, reason=None):
assert self.state != JingleStates.ended
assert self.state != JingleStates.ENDED
if self.connection.connection and self.connection.connected > 1:
stanza, jingle = self.__make_jingle('content-remove', reason=reason)
self.__append_content(jingle, content)
self.connection.connection.send(stanza)
# TODO: this will fail if content is not an RTP content
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
conn=self.connection, jingle_session=self, media=content.media,
reason='removed'))
conn=self.connection,
jingle_session=self,
media=content.media,
reason='removed'))
def content_negotiated(self, media):
gajim.nec.push_incoming_event(JingleConnectedReceivedEvent(None,
conn=self.connection, jingle_session=self, media=media))
conn=self.connection,
jingle_session=self,
media=media))

View File

@ -17,11 +17,11 @@
Handles Jingle Transports (currently only ICE-UDP)
"""
import nbxmpp
import socket
from common import gajim
import logging
import socket
from enum import IntEnum
import nbxmpp
from common import gajim
log = logging.getLogger('gajim.c.jingle_transport')
@ -43,16 +43,24 @@ class TransportType(IntEnum):
IBB = 3
class JingleTransport(object):
class JingleTransport:
"""
An abstraction of a transport in Jingle sessions
"""
__slots__ = ['type_', 'candidates', 'remote_candidates', 'connection',
'file_props', 'ourjid', 'sid']
def __init__(self, type_):
self.type_ = type_
self.candidates = []
self.remote_candidates = []
self.connection = None
self.file_props = None
self.ourjid = None
self.sid = None
def _iter_candidates(self):
for candidate in self.candidates:
yield self.make_candidate(candidate)
@ -110,9 +118,7 @@ class JingleTransportSocks5(JingleTransport):
def make_candidate(self, candidate):
import logging
log = logging.getLogger()
log.info('candidate dict, %s' % candidate)
log.info('candidate dict, %s', candidate)
attrs = {
'cid': candidate['candidate_id'],
'host': candidate['host'],
@ -124,8 +130,8 @@ class JingleTransportSocks5(JingleTransport):
return nbxmpp.Node('candidate', attrs=attrs)
def make_transport(self, candidates=None, add_candidates = True):
if add_candidates:
def make_transport(self, candidates=None, add_candidates=True):
if add_candidates:
self._add_local_ips_as_candidates()
self._add_additional_candidates()
self._add_proxy_candidates()
@ -174,7 +180,7 @@ class JingleTransportSocks5(JingleTransport):
def _add_local_ips_as_candidates(self):
if not gajim.config.get_per('accounts', self.connection.name,
'ft_send_local_ips'):
'ft_send_local_ips'):
return
if not self.connection:
return
@ -186,30 +192,34 @@ class JingleTransportSocks5(JingleTransport):
hosts = set()
local_ip_cand = []
c = {'host': self.connection.peerhost[0],
'candidate_id': self.connection.connection.getAnID(),
'port': port,
'type': 'direct',
'jid': self.ourjid,
'priority': priority}
candidate = {
'host': self.connection.peerhost[0],
'candidate_id': self.connection.connection.getAnID(),
'port': port,
'type': 'direct',
'jid': self.ourjid,
'priority': priority
}
hosts.add(self.connection.peerhost[0])
local_ip_cand.append(c)
local_ip_cand.append(candidate)
try:
for addrinfo in socket.getaddrinfo(socket.gethostname(), None):
addr = addrinfo[4][0]
if not addr in hosts and not addr.startswith('127.') and \
addr != '::1':
c = {'host': addr,
'candidate_id': self.connection.connection.getAnID(),
'port': port,
'type': 'direct',
'jid': self.ourjid,
'priority': priority,
'initiator': self.file_props.sender,
'target': self.file_props.receiver}
candidate = {
'host': addr,
'candidate_id': self.connection.connection.getAnID(),
'port': port,
'type': 'direct',
'jid': self.ourjid,
'priority': priority,
'initiator': self.file_props.sender,
'target': self.file_props.receiver
}
hosts.add(addr)
local_ip_cand.append(c)
local_ip_cand.append(candidate)
except socket.gaierror:
pass # ignore address-related errors for getaddrinfo
@ -226,16 +236,18 @@ class JingleTransportSocks5(JingleTransport):
if ft_add_hosts:
hosts = [e.strip() for e in ft_add_hosts.split(',')]
for h in hosts:
c = {'host': h,
'candidate_id': self.connection.connection.getAnID(),
'port': port,
'type': 'direct',
'jid': self.ourjid,
'priority': priority,
'initiator': self.file_props.sender,
'target': self.file_props.receiver}
additional_ip_cand.append(c)
for host in hosts:
candidate = {
'host': host,
'candidate_id': self.connection.connection.getAnID(),
'port': port,
'type': 'direct',
'jid': self.ourjid,
'priority': priority,
'initiator': self.file_props.sender,
'target': self.file_props.receiver
}
additional_ip_cand.append(candidate)
self._add_candidates(additional_ip_cand)
@ -252,21 +264,23 @@ class JingleTransportSocks5(JingleTransport):
self.file_props.proxyhosts = proxyhosts
for proxyhost in proxyhosts:
c = {'host': proxyhost['host'],
'candidate_id': self.connection.connection.getAnID(),
'port': int(proxyhost['port']),
'type': 'proxy',
'jid': proxyhost['jid'],
'priority': priority,
'initiator': self.file_props.sender,
'target': self.file_props.receiver}
proxy_cand.append(c)
candidate = {
'host': proxyhost['host'],
'candidate_id': self.connection.connection.getAnID(),
'port': int(proxyhost['port']),
'type': 'proxy',
'jid': proxyhost['jid'],
'priority': priority,
'initiator': self.file_props.sender,
'target': self.file_props.receiver
}
proxy_cand.append(candidate)
self._add_candidates(proxy_cand)
def get_content(self):
sesn = self.connection.get_jingle_session(self.ourjid,
self.file_props.sid)
self.file_props.sid)
for content in sesn.contents.values():
if content.transport == self:
return content
@ -277,7 +291,7 @@ class JingleTransportSocks5(JingleTransport):
if not self.connection:
return
sesn = self.connection.get_jingle_session(self.ourjid,
self.file_props.sid)
self.file_props.sid)
if sesn is None:
return
@ -294,8 +308,8 @@ class JingleTransportSocks5(JingleTransport):
content = nbxmpp.Node('content')
content.setAttr('creator', 'initiator')
c = self.get_content()
content.setAttr('name', c.name)
content_object = self.get_content()
content.setAttr('name', content_object.name)
transport = nbxmpp.Node('transport')
transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
transport.setAttr('sid', proxy['sid'])
@ -307,7 +321,7 @@ class JingleTransportSocks5(JingleTransport):
else:
for host in self.candidates:
if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \
and host['port'] == proxy['port']:
and host['port'] == proxy['port']:
cid = host['candidate_id']
break
if cid is None:
@ -345,7 +359,7 @@ class JingleTransportIBB(JingleTransport):
try:
from gi.repository import Farstream
except Exception:
except ImportError:
pass
class JingleTransportICEUDP(JingleTransport):
@ -353,11 +367,13 @@ class JingleTransportICEUDP(JingleTransport):
JingleTransport.__init__(self, TransportType.ICEUDP)
def make_candidate(self, candidate):
types = {Farstream.CandidateType.HOST: 'host',
types = {
Farstream.CandidateType.HOST: 'host',
Farstream.CandidateType.SRFLX: 'srflx',
Farstream.CandidateType.PRFLX: 'prflx',
Farstream.CandidateType.RELAY: 'relay',
Farstream.CandidateType.MULTICAST: 'multicast'}
Farstream.CandidateType.MULTICAST: 'multicast'
}
attrs = {
'component': candidate.component_id,
'foundation': '1', # hack
@ -402,23 +418,26 @@ class JingleTransportICEUDP(JingleTransport):
# jingle
proto = Farstream.NetworkProtocol.TCP
priority = int(candidate['priority'])
types = {'host': Farstream.CandidateType.HOST,
types = {
'host': Farstream.CandidateType.HOST,
'srflx': Farstream.CandidateType.SRFLX,
'prflx': Farstream.CandidateType.PRFLX,
'relay': Farstream.CandidateType.RELAY,
'multicast': Farstream.CandidateType.MULTICAST}
'multicast': Farstream.CandidateType.MULTICAST
}
if 'type' in candidate and candidate['type'] in types:
type_ = types[candidate['type']]
else:
log.warning('Unknown type %s' % candidate['type'])
log.warning('Unknown type %s', candidate['type'])
type_ = Farstream.CandidateType.HOST
username = str(transport['ufrag'])
password = str(transport['pwd'])
ttl = 0
cand = Farstream.Candidate.new_full(foundation, component_id, ip,
port, base_ip, base_port, proto, priority, type_, username,
password, ttl)
port, base_ip, base_port,
proto, priority, type_,
username, password, ttl)
candidates.append(cand)
self.remote_candidates.extend(candidates)

View File

@ -16,13 +16,15 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import os
import nbxmpp
import logging
import os
import nbxmpp
from common import gajim
log = logging.getLogger('gajim.c.jingle_xtls')
PYOPENSSL_PRESENT = False
# key-exchange id -> [callback, args], accept that session once key-exchange completes
@ -53,7 +55,7 @@ DH_PARAMS = 'dh_params.pem'
DEFAULT_DH_PARAMS = 'dh4096.pem'
def default_callback(connection, certificate, error_num, depth, return_code):
log.info("certificate: %s" % certificate)
log.info("certificate: %s", certificate)
return return_code
def load_cert_file(cert_path, cert_store=None):
@ -65,8 +67,8 @@ def load_cert_file(cert_path, cert_store=None):
try:
f = open(cert_path)
except IOError as e:
log.warning('Unable to open certificate file %s: %s' % (cert_path,
str(e)))
log.warning('Unable to open certificate file %s: %s', cert_path,
str(e))
return None
lines = f.readlines()
i = 0
@ -84,11 +86,11 @@ def load_cert_file(cert_path, cert_store=None):
f.close()
return x509cert
except OpenSSL.crypto.Error as exception_obj:
log.warning('Unable to load a certificate from file %s: %s' %\
(cert_path, exception_obj.args[0][0][2]))
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)
'%s', cert_path)
begin = -1
i += 1
f.close()
@ -105,7 +107,7 @@ def get_context(fingerprint, verify_cb=None, remote_jid=None):
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)
verify_cb or default_callback)
elif fingerprint == 'client':
ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback)
@ -121,23 +123,23 @@ def get_context(fingerprint, verify_cb=None, remote_jid=None):
ctx.load_tmp_dh(dh_params_name.encode('utf-8'))
except FileNotFoundError as err:
default_dh_params_name = os.path.join(gajim.DATA_DIR,
'other', DEFAULT_DH_PARAMS)
'other', DEFAULT_DH_PARAMS)
try:
with open(default_dh_params_name, "r") as default_dh_params_file:
ctx.load_tmp_dh(default_dh_params_name.encode('utf-8'))
except FileNotFoundError as err:
log.error('Unable to load default DH parameter file: %s , %s'
% (default_dh_params_name, err))
log.error('Unable to load default DH parameter file: %s, %s',
default_dh_params_name, err)
raise
if remote_jid:
store = ctx.get_cert_store()
path = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH),
remote_jid) + '.cert'
remote_jid) + '.cert'
if os.path.exists(path):
load_cert_file(path, cert_store=store)
log.debug('certificate file ' + path + ' loaded fingerprint ' + \
fingerprint)
log.debug('certificate file %s loaded fingerprint %s',
path, fingerprint)
return ctx
def read_cert(certpath):
@ -212,16 +214,16 @@ def send_cert_request(con, to_jid):
# the following code is partly due to pyopenssl examples
def createKeyPair(type, bits):
def createKeyPair(type_, bits):
"""
Create a public/private key pair.
Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA
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)
pkey.generate_key(type_, bits)
return pkey
def createCertRequest(pkey, digest="sha256", **name):
@ -244,7 +246,7 @@ def createCertRequest(pkey, digest="sha256", **name):
req = crypto.X509Req()
subj = req.get_subject()
for (key,value) in name.items():
for (key, value) in name.items():
setattr(subj, key, value)
req.set_pubkey(pkey)