Software /
code /
verse
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 |