Comparison

plugins/jingle_s5b.lua @ 101:9c5362d393f0

verse.plugins.jingle_s5b: Jingle SOCKS5 Bytestreams transport
author Matthew Wild <mwild1@gmail.com>
date Sat, 21 Aug 2010 15:28:08 +0100
child 140:97bf22d6ff96
comparison
equal deleted inserted replaced
100:e45883a3f39a 101:9c5362d393f0
1
2 local xmlns_s5b = "urn:xmpp:jingle:transports:s5b:1";
3 local sha1 = require "util.sha1".sha1;
4 local uuid_generate = require "util.uuid".generate;
5
6 local function negotiate_socks5(conn, hash)
7 local function suppress_connected()
8 conn:unhook("connected", suppress_connected);
9 return true;
10 end
11 local function receive_connection_response(data)
12 conn:unhook("incoming-raw", receive_connection_response);
13
14 if data:sub(1, 2) ~= "\005\000" then
15 return conn:event("error", "connection-failure");
16 end
17 conn:event("connected");
18 return true;
19 end
20 local function receive_auth_response(data)
21 conn:unhook("incoming-raw", receive_auth_response);
22 if data ~= "\005\000" then -- SOCKSv5; "NO AUTHENTICATION"
23 -- Server is not SOCKSv5, or does not allow no auth
24 local err = "version-mismatch";
25 if data:sub(1,1) == "\005" then
26 err = "authentication-failure";
27 end
28 return conn:event("error", err);
29 end
30 -- Request SOCKS5 connection
31 conn:send(string.char(0x05, 0x01, 0x00, 0x03, #hash)..hash.."\0\0"); --FIXME: Move to "connected"?
32 conn:hook("incoming-raw", receive_connection_response, 100);
33 return true;
34 end
35 conn:hook("connected", suppress_connected, 200);
36 conn:hook("incoming-raw", receive_auth_response, 100);
37 conn:send("\005\001\000"); -- SOCKSv5; 1 mechanism; "NO AUTHENTICATION"
38 end
39
40 local function connect_to_usable_streamhost(callback, streamhosts, auth_token)
41 local conn = verse.new(nil, {
42 streamhosts = streamhosts,
43 current_host = 0;
44 });
45 --Attempt to connect to the next host
46 local function attempt_next_streamhost(event)
47 if event then
48 return callback(nil, event.reason);
49 end
50 -- First connect, or the last connect failed
51 if conn.current_host < #conn.streamhosts then
52 conn.current_host = conn.current_host + 1;
53 conn:debug("Attempting to connect to "..conn.streamhosts[conn.current_host].host..":"..conn.streamhosts[conn.current_host].port.."...");
54 local ok, err = conn:connect(
55 conn.streamhosts[conn.current_host].host,
56 conn.streamhosts[conn.current_host].port
57 );
58 if not ok then
59 conn:debug("Error connecting to proxy (%s:%s): %s",
60 conn.streamhosts[conn.current_host].host,
61 conn.streamhosts[conn.current_host].port,
62 err
63 );
64 else
65 conn:debug("Connecting...");
66 end
67 negotiate_socks5(conn, auth_token);
68 return true; -- Halt processing of disconnected event
69 end
70 -- All streamhosts tried, none successful
71 conn:unhook("disconnected", attempt_next_streamhost);
72 return callback(nil);
73 -- Let disconnected event fall through to user handlers...
74 end
75 conn:hook("disconnected", attempt_next_streamhost, 100);
76 -- When this event fires, we're connected to a streamhost
77 conn:hook("connected", function ()
78 conn:unhook("disconnected", attempt_next_streamhost);
79 callback(conn.streamhosts[conn.current_host], conn);
80 end, 100);
81 attempt_next_streamhost(); -- Set it in motion
82 return conn;
83 end
84
85 function verse.plugins.jingle_s5b(stream)
86 stream:hook("ready", function ()
87 stream:add_disco_feature(xmlns_s5b);
88 end, 10);
89
90 local s5b = {};
91
92 function s5b:generate_initiate()
93 self.s5b_sid = uuid_generate();
94 local transport = verse.stanza("transport", { xmlns = xmlns_s5b,
95 mode = "tcp", sid = self.s5b_sid });
96 local p = 0;
97 for jid, streamhost in pairs(stream.proxy65.available_streamhosts) do
98 p = p + 1;
99 transport:tag("candidate", { jid = jid, host = streamhost.host,
100 port = streamhost.port, cid=jid, priority = p, type = "proxy" }):up();
101 end
102 stream:debug("Have %d proxies", p)
103 return transport;
104 end
105
106 function s5b:generate_accept(initiate_transport)
107 local candidates = {};
108 self.s5b_peer_candidates = candidates;
109 self.s5b_mode = initiate_transport.attr.mode or "tcp";
110 self.s5b_sid = initiate_transport.attr.sid or self.jingle.sid;
111
112 -- Import the list of candidates the initiator offered us
113 for candidate in initiate_transport:childtags() do
114 --if candidate.attr.jid == "asterix4@jabber.lagaule.org/Gajim"
115 --and candidate.attr.host == "82.246.25.239" then
116 candidates[candidate.attr.cid] = {
117 type = candidate.attr.type;
118 jid = candidate.attr.jid;
119 host = candidate.attr.host;
120 port = tonumber(candidate.attr.port) or 0;
121 priority = tonumber(candidate.attr.priority) or 0;
122 cid = candidate.attr.cid;
123 };
124 --end
125 end
126
127 -- Import our own candidates
128 -- TODO ^
129 local transport = verse.stanza("transport", { xmlns = xmlns_s5b });
130 return transport;
131 end
132
133 function s5b:connect(callback)
134 stream:warn("Connecting!");
135
136 local streamhost_array = {};
137 for cid, streamhost in pairs(self.s5b_peer_candidates or {}) do
138 streamhost_array[#streamhost_array+1] = streamhost;
139 end
140
141 if #streamhost_array > 0 then
142 self.connecting_peer_candidates = true;
143 local function onconnect(streamhost, conn)
144 self.jingle:send_command("transport-info", verse.stanza("transport", { xmlns = xmlns_s5b, sid = self.s5b_sid })
145 :tag("candidate-used", { cid = streamhost.cid }));
146 self.onconnect_callback = callback;
147 self.conn = conn;
148 end
149 local auth_token = sha1(self.s5b_sid..self.peer..stream.jid, true);
150 connect_to_usable_streamhost(onconnect, streamhost_array, auth_token);
151 else
152 stream:warn("Actually, I'm going to wait for my peer to tell me its streamhost...");
153 self.onconnect_callback = callback;
154 end
155 end
156
157 function s5b:info_received(jingle_tag)
158 stream:warn("Info received");
159 local transport_tag = jingle_tag:child_with_name("content"):child_with_name("transport");
160 if transport_tag:get_child("candidate-used") and not self.connecting_peer_candidates then
161 local candidate_used = transport_tag:child_with_name("candidate-used");
162 if candidate_used then
163 -- Connect straight away to candidate used, we weren't trying any anyway
164 local function onconnect(streamhost, conn)
165 if self.jingle.role == "initiator" then -- More correct would be - "is this a candidate we offered?"
166 -- Activate the stream
167 self.jingle.stream:send_iq(verse.iq({ to = streamhost.jid, type = "set" })
168 :tag("query", { xmlns = xmlns_bytestreams, sid = self.s5b_sid })
169 :tag("activate"):text(self.jingle.peer), function (result)
170
171 if result.attr.type == "result" then
172 self.jingle:send_command("transport-info", verse.stanza("transport", { xmlns = xmlns_s5b, sid = self.s5b_sid })
173 :tag("activated", { cid = candidate_used.attr.cid }));
174 self.conn = conn;
175 self.onconnect_callback(conn);
176 else
177 self.jingle.stream:error("Failed to activate bytestream");
178 end
179 end);
180 end
181 end
182
183 -- FIXME: Another assumption that cid==jid, and that it was our candidate
184 self.jingle.stream:debug("CID: %s", self.jingle.stream.proxy65.available_streamhosts[candidate_used.attr.cid]);
185 local streamhost_array = {
186 self.jingle.stream.proxy65.available_streamhosts[candidate_used.attr.cid];
187 };
188
189 local auth_token = sha1(self.s5b_sid..stream.jid..self.peer, true);
190 connect_to_usable_streamhost(onconnect, streamhost_array, auth_token);
191 end
192 elseif transport_tag:get_child("activated") then
193 self.onconnect_callback(self.conn);
194 end
195 end
196
197 function s5b:disconnect()
198 if self.conn then
199 self.conn:close();
200 end
201 end
202
203 function s5b:handle_accepted(jingle_tag)
204 end
205
206 local s5b_mt = { __index = s5b };
207 stream:hook("jingle/transport/"..xmlns_s5b, function (jingle)
208 return setmetatable({
209 role = jingle.role,
210 peer = jingle.peer,
211 stream = jingle.stream,
212 jingle = jingle,
213 }, s5b_mt);
214 end);
215 end