Software /
code /
verse
File
plugins/jingle.lua @ 498:50d0bd035bb7
util.sasl.oauthbearer: Don't send authzid
It's not needed and not recommended in XMPP unless we want to act as
someone other than who we authenticate as. We find out the JID during
resource binding.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 23 Jun 2023 12:09:49 +0200 |
parent | 490:6b2f31da9610 |
line wrap: on
line source
local verse = require "verse"; local timer = require "prosody.util.timer"; local new_id = require "prosody.util.id".short; local xmlns_jingle = "urn:xmpp:jingle:1"; local xmlns_jingle_errors = "urn:xmpp:jingle:errors:1"; local jingle_mt = {}; jingle_mt.__index = jingle_mt; local registered_transports = {}; local registered_content_types = {}; function verse.plugins.jingle(stream) stream:hook("ready", function () stream:add_disco_feature(xmlns_jingle); end, 10); function stream:jingle(to) return verse.eventable(setmetatable(base or { role = "initiator"; peer = to; sid = new_id(); stream = stream; }, jingle_mt)); end function stream:register_jingle_transport(transport) -- transport is a function that receives a -- <transport> element, and returns a connection -- We wait for 'connected' on that connection, -- and use :send() and 'incoming-raw'. end function stream:register_jingle_content_type(content) -- Call content() for every 'incoming-raw'? -- I think content() returns the object we return -- on jingle:accept() end local function handle_incoming_jingle(stanza) local jingle_tag = stanza:get_child("jingle", xmlns_jingle); local sid = jingle_tag.attr.sid; local action = jingle_tag.attr.action; local result = stream:event("jingle/"..sid, stanza); if result == true then -- Ack stream:send(verse.reply(stanza)); return true; end -- No existing Jingle object handled this action, our turn... if action ~= "session-initiate" then -- Trying to send a command to a session we don't know local reply = verse.error_reply(stanza, "cancel", "item-not-found") :tag("unknown-session", { xmlns = xmlns_jingle_errors }):up(); stream:send(reply); return; end -- Ok, session-initiate, new session -- Create new Jingle object local sid = jingle_tag.attr.sid; local jingle = verse.eventable{ role = "receiver"; peer = stanza.attr.from; sid = sid; stream = stream; }; setmetatable(jingle, jingle_mt); local content_tag; local content, transport; for tag in jingle_tag:childtags() do if tag.name == "content" and tag.attr.xmlns == xmlns_jingle then local description_tag = tag:child_with_name("description"); local description_xmlns = description_tag.attr.xmlns; if description_xmlns then local desc_handler = stream:event("jingle/content/"..description_xmlns, jingle, description_tag); if desc_handler then content = desc_handler; end end local transport_tag = tag:child_with_name("transport"); local transport_xmlns = transport_tag.attr.xmlns; transport = stream:event("jingle/transport/"..transport_xmlns, jingle, transport_tag); if content and transport then content_tag = tag; break; end end end if not content then -- FIXME: Fail, no content stream:send(verse.error_reply(stanza, "cancel", "feature-not-implemented", "The specified content is not supported")); return true; end if not transport then -- FIXME: Refuse session, no transport stream:send(verse.error_reply(stanza, "cancel", "feature-not-implemented", "The specified transport is not supported")); return true; end stream:send(verse.reply(stanza)); jingle.content_tag = content_tag; jingle.creator, jingle.name = content_tag.attr.creator, content_tag.attr.name; jingle.content, jingle.transport = content, transport; function jingle:decline() -- FIXME: Decline session end stream:hook("jingle/"..sid, function (stanza) if stanza.attr.from ~= jingle.peer then return false; end local jingle_tag = stanza:get_child("jingle", xmlns_jingle); return jingle:handle_command(jingle_tag); end); stream:event("jingle", jingle); return true; end function jingle_mt:handle_command(jingle_tag) local action = jingle_tag.attr.action; stream:debug("Handling Jingle command: %s", action); if action == "session-terminate" then self:destroy(); elseif action == "session-accept" then -- Yay! self:handle_accepted(jingle_tag); elseif action == "transport-info" then stream:debug("Handling transport-info"); self.transport:info_received(jingle_tag); elseif action == "transport-replace" then -- FIXME: Used for IBB fallback stream:error("Peer wanted to swap transport, not implemented"); else -- FIXME: Reply unhandled command stream:warn("Unhandled Jingle command: %s", action); return nil; end return true; end function jingle_mt:send_command(command, element, callback) local stanza = verse.iq({ to = self.peer, type = "set" }) :tag("jingle", { xmlns = xmlns_jingle, sid = self.sid, action = command, initiator = self.role == "initiator" and self.stream.jid or nil, responder = self.role == "responder" and self.jid or nil, }):add_child(element); if not callback then self.stream:send(stanza); else self.stream:send_iq(stanza, callback); end end function jingle_mt:accept(options) local accept_stanza = verse.iq({ to = self.peer, type = "set" }) :tag("jingle", { xmlns = xmlns_jingle, sid = self.sid, action = "session-accept", responder = stream.jid, }) :tag("content", { creator = self.creator, name = self.name }); local content_accept_tag = self.content:generate_accept(self.content_tag:child_with_name("description"), options); accept_stanza:add_child(content_accept_tag); local transport_accept_tag = self.transport:generate_accept(self.content_tag:child_with_name("transport"), options); accept_stanza:add_child(transport_accept_tag); local jingle = self; stream:send_iq(accept_stanza, function (result) if result.attr.type == "error" then local type, condition, text = result:get_error(); stream:error("session-accept rejected: %s", condition); -- FIXME: Notify return false; end jingle.transport:connect(function (conn) stream:warn("CONNECTED (receiver)!!!"); jingle.state = "active"; jingle:event("connected", conn); end); end); end stream:hook("iq/"..xmlns_jingle, handle_incoming_jingle); return true; end function jingle_mt:offer(name, content) local session_initiate = verse.iq({ to = self.peer, type = "set" }) :tag("jingle", { xmlns = xmlns_jingle, action = "session-initiate", initiator = self.stream.jid, sid = self.sid }); -- Content tag session_initiate:tag("content", { creator = self.role, name = name }); -- Need description element from someone who can turn 'content' into XML local description = self.stream:event("jingle/describe/"..name, content); if not description then return false, "Unknown content type"; end session_initiate:add_child(description); -- FIXME: Sort transports by 1) recipient caps 2) priority (SOCKS vs IBB, etc.) -- Fixed to s5b in the meantime local transport = self.stream:event("jingle/transport/".."urn:xmpp:jingle:transports:s5b:1", self); self.transport = transport; session_initiate:add_child(transport:generate_initiate()); self.stream:debug("Hooking %s", "jingle/"..self.sid); self.stream:hook("jingle/"..self.sid, function (stanza) if stanza.attr.from ~= self.peer then return false; end local jingle_tag = stanza:get_child("jingle", xmlns_jingle); return self:handle_command(jingle_tag) end); self.stream:send_iq(session_initiate, function (result) if result.attr.type == "error" then self.state = "terminated"; local type, condition, text = result:get_error(); return self:event("error", { type = type, condition = condition, text = text }); end end); self.state = "pending"; end function jingle_mt:terminate(reason) local reason_tag = verse.stanza("reason"):tag(reason or "success"); self:send_command("session-terminate", reason_tag, function (result) self.state = "terminated"; self.transport:disconnect(); self:destroy(); end); end function jingle_mt:destroy() self:event("terminated"); self.stream:unhook("jingle/"..self.sid, self.handle_command); end function jingle_mt:handle_accepted(jingle_tag) local transport_tag = jingle_tag:child_with_name("transport"); self.transport:handle_accepted(transport_tag); self.transport:connect(function (conn) self.stream:debug("CONNECTED (initiator)!") -- Connected, send file self.state = "active"; self:event("connected", conn); end); end function jingle_mt:set_source(source, auto_close) local function pump() local chunk, err = source(); if chunk and chunk ~= "" then self.transport.conn:send(chunk); elseif chunk == "" then return pump(); -- We need some data! elseif chunk == nil then if auto_close then self:terminate(); end self.transport.conn:unhook("drained", pump); source = nil; end end self.transport.conn:hook("drained", pump); pump(); end function jingle_mt:set_sink(sink) self.transport.conn:hook("incoming-raw", sink); self.transport.conn:hook("disconnected", function (event) self.stream:debug("Closing sink..."); local reason = event.reason; if reason == "closed" then reason = nil; end sink(nil, reason); end); end