Software /
code /
verse
File
plugins/jingle_s5b.lua @ 409:171c4a8cc3de
plugins.archive: Update to XEP-0313 v0.6
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 07 Apr 2017 19:38:52 +0200 |
parent | 392:cdea6a28369e |
child | 457:73d4eb93657b |
line wrap: on
line source
local verse = require "verse"; local xmlns_s5b = "urn:xmpp:jingle:transports:s5b:1"; local xmlns_bytestreams = "http://jabber.org/protocol/bytestreams"; local sha1 = require "util.hashes".sha1; local uuid_generate = require "util.uuid".generate; local function negotiate_socks5(conn, hash) local function suppress_connected() conn:unhook("connected", suppress_connected); return true; end local function receive_connection_response(data) conn:unhook("incoming-raw", receive_connection_response); if data:sub(1, 2) ~= "\005\000" then return conn:event("error", "connection-failure"); end conn:event("connected"); return true; end local function receive_auth_response(data) conn:unhook("incoming-raw", receive_auth_response); if data ~= "\005\000" then -- SOCKSv5; "NO AUTHENTICATION" -- Server is not SOCKSv5, or does not allow no auth local err = "version-mismatch"; if data:sub(1,1) == "\005" then err = "authentication-failure"; end return conn:event("error", err); end -- Request SOCKS5 connection conn:send(string.char(0x05, 0x01, 0x00, 0x03, #hash)..hash.."\0\0"); --FIXME: Move to "connected"? conn:hook("incoming-raw", receive_connection_response, 100); return true; end conn:hook("connected", suppress_connected, 200); conn:hook("incoming-raw", receive_auth_response, 100); conn:send("\005\001\000"); -- SOCKSv5; 1 mechanism; "NO AUTHENTICATION" end local function connect_to_usable_streamhost(callback, streamhosts, auth_token) local conn = verse.new(nil, { streamhosts = streamhosts, current_host = 0; }); --Attempt to connect to the next host local function attempt_next_streamhost(event) if event then return callback(nil, event.reason); end -- First connect, or the last connect failed if conn.current_host < #conn.streamhosts then conn.current_host = conn.current_host + 1; conn:debug("Attempting to connect to "..conn.streamhosts[conn.current_host].host..":"..conn.streamhosts[conn.current_host].port.."..."); local ok, err = conn:connect( conn.streamhosts[conn.current_host].host, conn.streamhosts[conn.current_host].port ); if not ok then conn:debug("Error connecting to proxy (%s:%s): %s", conn.streamhosts[conn.current_host].host, conn.streamhosts[conn.current_host].port, err ); else conn:debug("Connecting..."); end negotiate_socks5(conn, auth_token); return true; -- Halt processing of disconnected event end -- All streamhosts tried, none successful conn:unhook("disconnected", attempt_next_streamhost); return callback(nil); -- Let disconnected event fall through to user handlers... end conn:hook("disconnected", attempt_next_streamhost, 100); -- When this event fires, we're connected to a streamhost conn:hook("connected", function () conn:unhook("disconnected", attempt_next_streamhost); callback(conn.streamhosts[conn.current_host], conn); end, 100); attempt_next_streamhost(); -- Set it in motion return conn; end function verse.plugins.jingle_s5b(stream) stream:hook("ready", function () stream:add_disco_feature(xmlns_s5b); end, 10); local s5b = {}; function s5b:generate_initiate() self.s5b_sid = uuid_generate(); local transport = verse.stanza("transport", { xmlns = xmlns_s5b, mode = "tcp", sid = self.s5b_sid }); local p = 0; for jid, streamhost in pairs(stream.proxy65.available_streamhosts) do p = p + 1; transport:tag("candidate", { jid = jid, host = streamhost.host, port = streamhost.port, cid=jid, priority = p, type = "proxy" }):up(); end stream:debug("Have %d proxies", p) return transport; end function s5b:generate_accept(initiate_transport) local candidates = {}; self.s5b_peer_candidates = candidates; self.s5b_mode = initiate_transport.attr.mode or "tcp"; self.s5b_sid = initiate_transport.attr.sid or self.jingle.sid; -- Import the list of candidates the initiator offered us for candidate in initiate_transport:childtags() do --if candidate.attr.jid == "asterix4@jabber.lagaule.org/Gajim" --and candidate.attr.host == "82.246.25.239" then candidates[candidate.attr.cid] = { type = candidate.attr.type; jid = candidate.attr.jid; host = candidate.attr.host; port = tonumber(candidate.attr.port) or 0; priority = tonumber(candidate.attr.priority) or 0; cid = candidate.attr.cid; }; --end end -- Import our own candidates -- TODO ^ local transport = verse.stanza("transport", { xmlns = xmlns_s5b }); return transport; end function s5b:connect(callback) stream:warn("Connecting!"); local streamhost_array = {}; for cid, streamhost in pairs(self.s5b_peer_candidates or {}) do streamhost_array[#streamhost_array+1] = streamhost; end if #streamhost_array > 0 then self.connecting_peer_candidates = true; local function onconnect(streamhost, conn) self.jingle:send_command("transport-info", verse.stanza("content", { creator = self.creator, name = self.name }) :tag("transport", { xmlns = xmlns_s5b, sid = self.s5b_sid }) :tag("candidate-used", { cid = streamhost.cid })); self.onconnect_callback = callback; self.conn = conn; end local auth_token = sha1(self.s5b_sid..self.peer..stream.jid, true); connect_to_usable_streamhost(onconnect, streamhost_array, auth_token); else stream:warn("Actually, I'm going to wait for my peer to tell me its streamhost..."); self.onconnect_callback = callback; end end function s5b:info_received(jingle_tag) stream:warn("Info received"); local content_tag = jingle_tag:child_with_name("content"); local transport_tag = content_tag:child_with_name("transport"); if transport_tag:get_child("candidate-used") and not self.connecting_peer_candidates then local candidate_used = transport_tag:child_with_name("candidate-used"); if candidate_used then -- Connect straight away to candidate used, we weren't trying any anyway local function onconnect(streamhost, conn) if self.jingle.role == "initiator" then -- More correct would be - "is this a candidate we offered?" -- Activate the stream self.jingle.stream:send_iq(verse.iq({ to = streamhost.jid, type = "set" }) :tag("query", { xmlns = xmlns_bytestreams, sid = self.s5b_sid }) :tag("activate"):text(self.jingle.peer), function (result) if result.attr.type == "result" then self.jingle:send_command("transport-info", verse.stanza("content", content_tag.attr) :tag("transport", { xmlns = xmlns_s5b, sid = self.s5b_sid }) :tag("activated", { cid = candidate_used.attr.cid })); self.conn = conn; self.onconnect_callback(conn); else self.jingle.stream:error("Failed to activate bytestream"); end end); end end -- FIXME: Another assumption that cid==jid, and that it was our candidate self.jingle.stream:debug("CID: %s", self.jingle.stream.proxy65.available_streamhosts[candidate_used.attr.cid]); local streamhost_array = { self.jingle.stream.proxy65.available_streamhosts[candidate_used.attr.cid]; }; local auth_token = sha1(self.s5b_sid..stream.jid..self.peer, true); connect_to_usable_streamhost(onconnect, streamhost_array, auth_token); end elseif transport_tag:get_child("activated") then self.onconnect_callback(self.conn); end end function s5b:disconnect() if self.conn then self.conn:close(); end end function s5b:handle_accepted(jingle_tag) end local s5b_mt = { __index = s5b }; stream:hook("jingle/transport/"..xmlns_s5b, function (jingle) return setmetatable({ role = jingle.role, peer = jingle.peer, stream = jingle.stream, jingle = jingle, }, s5b_mt); end); end