diff --git a/sat/plugins/plugin_xep_0353.py b/sat/plugins/plugin_xep_0353.py
index ecf8c2a7..8cc4986b 100644
--- a/sat/plugins/plugin_xep_0353.py
+++ b/sat/plugins/plugin_xep_0353.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# SàT plugin for Jingle Message Initiation (XEP-0353)
+# Libervia plugin for Jingle Message Initiation (XEP-0353)
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
@@ -16,17 +16,19 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from zope.interface import implementer
from twisted.internet import defer
from twisted.internet import reactor
-from twisted.words.protocols.jabber import xmlstream, jid, error
+from twisted.words.protocols.jabber import error, jid
+from twisted.words.protocols.jabber import xmlstream
from twisted.words.xish import domish
from wokkel import disco, iwokkel
-from sat.core.i18n import _, D_
-from sat.core.constants import Const as C
+from zope.interface import implementer
+
from sat.core import exceptions
+from sat.core.constants import Const as C
+from sat.core.core_types import SatXMPPEntity
+from sat.core.i18n import D_, _
from sat.core.log import getLogger
-from sat.tools import utils
from sat.tools import xml_tools
log = getLogger(__name__)
@@ -40,7 +42,7 @@ PLUGIN_INFO = {
C.PI_TYPE: "XEP",
C.PI_MODES: [C.PLUG_MODE_CLIENT],
C.PI_PROTOCOLS: ["XEP-0353"],
- C.PI_DEPENDENCIES: ["XEP-0166"],
+ C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0334"],
C.PI_MAIN: "XEP_0353",
C.PI_HANDLER: "yes",
C.PI_DESCRIPTION: _("""Implementation of Jingle Message Initiation"""),
@@ -48,13 +50,19 @@ PLUGIN_INFO = {
class XEP_0353:
-
def __init__(self, host):
log.info(_("plugin {name} initialization").format(name=PLUGIN_INFO[C.PI_NAME]))
self.host = host
host.register_namespace("jingle-message", NS_JINGLE_MESSAGE)
self._j = host.plugins["XEP-0166"]
- host.trigger.add("XEP-0166_initiate", self._on_initiate_trigger)
+ self._h = host.plugins["XEP-0334"]
+ host.trigger.add(
+ "XEP-0166_initiate_elt_built",
+ self._on_initiate_trigger,
+ # this plugin set the resource, we want it to happen first to other trigger
+ # can get the full peer JID
+ priority=host.trigger.MAX_PRIORITY,
+ )
host.trigger.add("message_received", self._on_message_received)
def get_handler(self, client):
@@ -66,22 +74,29 @@ class XEP_0353:
def build_message_data(self, client, peer_jid, verb, session_id):
mess_data = {
- 'from': client.jid,
- 'to': peer_jid,
- 'uid': '',
- 'message': {},
- 'type': C.MESS_TYPE_CHAT,
- 'subject': {},
- 'extra': {}
+ "from": client.jid,
+ "to": peer_jid,
+ "uid": "",
+ "message": {},
+ "type": C.MESS_TYPE_CHAT,
+ "subject": {},
+ "extra": {},
}
client.generate_message_xml(mess_data)
- verb_elt = mess_data["xml"].addElement((NS_JINGLE_MESSAGE, verb))
+ message_elt = mess_data["xml"]
+ verb_elt = message_elt.addElement((NS_JINGLE_MESSAGE, verb))
verb_elt["id"] = session_id
+ self._h.add_hint_elements(message_elt, [self._h.HINT_STORE])
return mess_data
- async def _on_initiate_trigger(self, client, session, contents):
- # FIXME: check that at least one resource of the peer_jid can handle the feature
- peer_jid = session['peer_jid']
+ async def _on_initiate_trigger(
+ self,
+ client: SatXMPPEntity,
+ session: dict,
+ iq_elt: domish.Element,
+ jingle_elt: domish.Element,
+ ) -> bool:
+ peer_jid = session["peer_jid"]
if peer_jid.resource:
return True
@@ -103,43 +118,43 @@ class XEP_0353:
# according to XEP-0353 §3.1
await client.presence.available(peer_jid)
- mess_data = self.build_message_data(client, peer_jid, "propose", session['id'])
- for content in contents:
- content_data = self._j.get_content_data(
- content)
- try:
- jingle_description_elt = (
- content_data.application.handler.jingle_description_elt
- )
- except AttributeError:
- log.debug(
- "no jingle_description_elt set for "
- f"{content_data.application.handler}"
- )
- description_elt = domish.Element((content["app_ns"], "description"))
- else:
- description_elt = await utils.as_deferred(
- jingle_description_elt,
- client, session, content_data.content_name, *content_data.app_args,
- **content_data.app_kwargs
- )
- mess_data["xml"].propose.addChild(description_elt)
+ mess_data = self.build_message_data(client, peer_jid, "propose", session["id"])
+ message_elt = mess_data["xml"]
+ for content_data in session["contents"].values():
+ # we get the full element build by the application plugin
+ jingle_description_elt = content_data["application_data"]["desc_elt"]
+ # and copy it to only keep the root element, no children
+ description_elt = domish.Element(
+ (jingle_description_elt.uri, jingle_description_elt.name),
+ defaultUri=jingle_description_elt.defaultUri,
+ attribs=jingle_description_elt.attributes,
+ localPrefixes=jingle_description_elt.localPrefixes,
+ )
+ message_elt.propose.addChild(description_elt)
response_d = defer.Deferred()
# we wait for 2 min before cancelling the session init
- # response_d.addTimeout(2*60, reactor)
# FIXME: let's application decide timeout?
- response_d.addTimeout(2, reactor)
- client._xep_0353_pending_sessions[session['id']] = response_d
+ response_d.addTimeout(2 * 60, reactor)
+ client._xep_0353_pending_sessions[session["id"]] = response_d
await client.send_message_data(mess_data)
try:
accepting_jid = await response_d
except defer.TimeoutError:
- log.warning(_(
- "Message initiation with {peer_jid} timed out"
- ).format(peer_jid=peer_jid))
+ log.warning(
+ _("Message initiation with {peer_jid} timed out").format(
+ peer_jid=peer_jid
+ )
+ )
else:
+ if iq_elt["to"] != accepting_jid.userhost():
+ raise exceptions.InternalError(
+ f" 'to' attribute ({iq_elt['to']!r}) must not differ "
+ f"from bare JID of the accepting entity ({accepting_jid!r}), this "
+ "may be a sign of an internal bug, a hack attempt, or a MITM attack!"
+ )
+ iq_elt["to"] = accepting_jid.full()
session["peer_jid"] = accepting_jid
- del client._xep_0353_pending_sessions[session['id']]
+ del client._xep_0353_pending_sessions[session["id"]]
return True
async def _on_message_received(self, client, message_elt, post_treat):
@@ -170,11 +185,11 @@ class XEP_0353:
human_name = getattr(application.handler, "human_name", application.name)
except (exceptions.NotFound, AttributeError):
if app_ns.startswith("urn:xmpp:jingle:apps:"):
- human_name = app_ns[21:].split(":", 1)[0].replace('-', ' ').title()
+ human_name = app_ns[21:].split(":", 1)[0].replace("-", " ").title()
else:
- splitted_ns = app_ns.split(':')
+ splitted_ns = app_ns.split(":")
if len(splitted_ns) > 1:
- human_name = splitted_ns[-2].replace('- ', ' ').title()
+ human_name = splitted_ns[-2].replace("- ", " ").title()
else:
human_name = app_ns
@@ -185,28 +200,27 @@ class XEP_0353:
).format(peer_jid=peer_jid, human_name=human_name)
confirm_title = D_("Invitation from an unknown contact")
accept = await xml_tools.defer_confirm(
- self.host, confirm_msg, confirm_title, profile=client.profile,
+ self.host,
+ confirm_msg,
+ confirm_title,
+ profile=client.profile,
action_extra={
"type": C.META_TYPE_NOT_IN_ROSTER_LEAK,
"session_id": session_id,
"from_jid": peer_jid.full(),
- }
+ },
)
if not accept:
mess_data = self.build_message_data(
- client, client.jid.userhostJID(), "reject", session_id)
+ client, client.jid.userhostJID(), "reject", session_id
+ )
await client.send_message_data(mess_data)
# we don't sent anything to sender, to avoid leaking presence
return False
else:
await client.presence.available(peer_jid)
session_id = elt["id"]
- # FIXME: accept is not used anymore in new specification, check it and remove it
- mess_data = self.build_message_data(
- client, client.jid.userhostJID(), "accept", session_id)
- await client.send_message_data(mess_data)
- mess_data = self.build_message_data(
- client, peer_jid, "proceed", session_id)
+ mess_data = self.build_message_data(client, peer_jid, "proceed", session_id)
await client.send_message_data(mess_data)
return False
@@ -224,8 +238,9 @@ class XEP_0353:
response_d = client._xep_0353_pending_sessions[session_id]
except KeyError:
log.warning(
- _("no pending session found with id {session_id}, did it timed out?")
- .format(session_id=session_id)
+ _(
+ "no pending session found with id {session_id}, did it timed out?"
+ ).format(session_id=session_id)
)
return True
@@ -241,7 +256,6 @@ class XEP_0353:
@implementer(iwokkel.IDisco)
class Handler(xmlstream.XMPPHandler):
-
def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
return [disco.DiscoFeature(NS_JINGLE_MESSAGE)]