Comparison

plugins/proxy65.lua @ 56:014bdb4154e9

verse.plugins.proxy65: XEP-0065 plugin for file transfer through a proxy
author Matthew Wild <mwild1@gmail.com>
date Thu, 06 May 2010 10:34:27 +0100
child 103:6cc0ca4aa664
comparison
equal deleted inserted replaced
55:163beb198646 56:014bdb4154e9
1 local events = require "util.events";
2 local uuid = require "util.uuid";
3 local sha1 = require "util.sha1";
4
5 local proxy65_mt = {};
6 proxy65_mt.__index = proxy65_mt;
7
8 local xmlns_bytestreams = "http://jabber.org/protocol/bytestreams";
9
10 local negotiate_socks5;
11
12 function verse.plugins.proxy65(stream)
13 stream.proxy65 = setmetatable({ stream = stream }, proxy65_mt);
14 stream:hook("disco-result", function (result)
15 -- Fill list with available proxies
16 end);
17 stream:hook("iq/"..xmlns_bytestreams, function (request)
18 local conn = verse.new(nil, {
19 initiator_jid = request.attr.from,
20 streamhosts = {},
21 current_host = 0;
22 });
23
24 -- Parse hosts from request
25 for tag in request.tags[1]:childtags() do
26 if tag.name == "streamhost" then
27 table.insert(conn.streamhosts, tag.attr);
28 end
29 end
30
31 --Attempt to connect to the next host
32 local function attempt_next_streamhost()
33 -- First connect, or the last connect failed
34 if conn.current_host < #conn.streamhosts then
35 conn.current_host = conn.current_host + 1;
36 conn:connect(
37 conn.streamhosts[conn.current_host].host,
38 conn.streamhosts[conn.current_host].port
39 );
40 negotiate_socks5(stream, conn, request.tags[1].attr.sid, request.attr.from, stream.jid);
41 return true; -- Halt processing of disconnected event
42 end
43 -- All streamhosts tried, none successful
44 conn:unhook("disconnected", attempt_next_streamhost);
45 stream:send(verse.error_reply(request, "cancel", "item-not-found"));
46 -- Let disconnected event fall through to user handlers...
47 end
48
49 function conn:accept()
50 conn:hook("disconnected", attempt_next_streamhost, 100);
51 -- When this event fires, we're connected to a streamhost
52 conn:hook("connected", function ()
53 conn:unhook("disconnected", attempt_next_streamhost);
54 -- Send XMPP success notification
55 local reply = verse.reply(request)
56 :tag("query", request.tags[1].attr)
57 :tag("streamhost-used", { jid = conn.streamhosts[conn.current_host].jid });
58 stream:send(reply);
59 end, 100);
60 attempt_next_streamhost();
61 end
62 function conn:refuse()
63 -- FIXME: XMPP refused reply
64 end
65 stream:event("proxy65/request", conn);
66 end);
67 end
68
69 function proxy65_mt:new(target_jid, proxies)
70 local conn = verse.new(nil, {
71 target_jid = target_jid;
72 bytestream_sid = uuid.generate();
73 });
74
75 local request = verse.iq{type="set", to = target_jid}
76 :tag("query", { xmlns = xmlns_bytestreams, mode = "tcp", sid = conn.bytestream_sid });
77 for _, proxy in ipairs(proxies or self.proxies) do
78 request:tag("streamhost", proxy):up();
79 end
80
81
82 self.stream:send_iq(request, function (reply)
83 if reply.attr.type == "error" then
84 local type, condition, text = reply:get_error();
85 conn:event("connection-failed", { conn = conn, type = type, condition = condition, text = text });
86 else
87 -- Target connected to streamhost, connect ourselves
88 local streamhost_used = reply.tags[1]:get_child("streamhost-used");
89 if not streamhost_used then
90 --FIXME: Emit error
91 end
92 conn.streamhost_jid = streamhost_used.attr.jid;
93 local host, port;
94 for _, proxy in ipairs(proxies or self.proxies) do
95 if proxy.jid == conn.streamhost_jid then
96 host, port = proxy.host, proxy.port;
97 break;
98 end
99 end
100 if not (host and port) then
101 --FIXME: Emit error
102 end
103
104 conn:connect(host, port);
105
106 local function handle_proxy_connected()
107 conn:unhook("connected", handle_proxy_connected);
108 -- Both of us connected, tell proxy to activate connection
109 local request = verse.iq{to = conn.streamhost_jid, type="set"}
110 :tag("query", { xmlns = xmlns_bytestreams, sid = conn.bytestream_sid })
111 :tag("activate"):text(target_jid);
112 self.stream:send_iq(request, function (reply)
113 if reply.attr.type == "result" then
114 -- Connection activated, ready to use
115 conn:event("connected", conn);
116 else
117 --FIXME: Emit error
118 end
119 end);
120 return true;
121 end
122 conn:hook("connected", handle_proxy_connected, 100);
123
124 negotiate_socks5(self.stream, conn, conn.bytestream_sid, self.stream.jid, target_jid);
125 end
126 end);
127 return conn;
128 end
129
130 function negotiate_socks5(stream, conn, sid, requester_jid, target_jid)
131 local hash = sha1.sha1(sid..requester_jid..target_jid);
132 local function suppress_connected()
133 conn:unhook("connected", suppress_connected);
134 return true;
135 end
136 local function receive_connection_response(data)
137 conn:unhook("incoming-raw", receive_connection_response);
138
139 if data:sub(1, 2) ~= "\005\000" then
140 return conn:event("error", "connection-failure");
141 end
142 conn:event("connected");
143 return true;
144 end
145 local function receive_auth_response(data)
146 conn:unhook("incoming-raw", receive_auth_response);
147 if data ~= "\005\000" then -- SOCKSv5; "NO AUTHENTICATION"
148 -- Server is not SOCKSv5, or does not allow no auth
149 local err = "version-mismatch";
150 if data:sub(1,1) == "\005" then
151 err = "authentication-failure";
152 end
153 return conn:event("error", err);
154 end
155 -- Request SOCKS5 connection
156 conn:send(string.char(0x05, 0x01, 0x00, 0x03, #hash)..hash.."\0\0"); --FIXME: Move to "connected"?
157 conn:hook("incoming-raw", receive_connection_response, 100);
158 return true;
159 end
160 conn:hook("connected", suppress_connected, 200);
161 conn:hook("incoming-raw", receive_auth_response, 100);
162 conn:send("\005\001\000"); -- SOCKSv5; 1 mechanism; "NO AUTHENTICATION"
163 end