Comparison

mod_onions/mod_onions.lua @ 1060:25b83ed7838a

mod_onions: Added mod_onions. This module allows Prosody to make s2s connections to Tor hidden services. * Requires a local install of Tor. * Does not require the initiating server to be a hidden service (though dialback will be tricky).
author Thijs Alkemade <me@thijsalkema.de>
date Mon, 10 Jun 2013 20:59:39 +0200
child 1061:f4031e7ccec1
comparison
equal deleted inserted replaced
1059:95ab35ef52ba 1060:25b83ed7838a
1 local wrapclient = require "net.server".wrapclient;
2 local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
3 local initialize_filters = require "util.filters".initialize;
4 local bit = require "bit32";
5 local st = require "util.stanza";
6 local portmanager = require "core.portmanager";
7 local byte = string.byte;
8 local c = string.char;
9
10 local proxy_ip = module:get_option("onions_socks5_host") or "127.0.0.1";
11 local proxy_port = module:get_option("onions_socks5_port") or "9050";
12 local forbid_else = module:get_option("onions_only") or false;
13
14 local sessions = module:shared("sessions");
15
16 -- The socks5listener handles connection while still connecting to the proxy,
17 -- then it hands them over to the normal listener (in mod_s2s)
18 local socks5listener = { default_port = tonumber(proxy_port), default_mode = "*a", default_interface = "*" };
19
20 local function socks5_connect_sent(conn, data)
21
22 local session = sessions[conn];
23
24 if #data < 5 then
25 session.socks5_buffer = data;
26 return;
27 end
28
29 request_status = byte(data, 2);
30
31 if not request_status == 0x00 then
32 module:log("debug", "Failed to connect to the SOCKS5 proxy. :(");
33 session:close(false);
34 return;
35 end
36
37 module:log("debug", "Succesfully connected to SOCKS5 proxy.");
38
39 local response = byte(data, 4);
40
41 if response == 0x01 then
42 if #data < 10 then
43 -- let's try again when we have enough
44 session.socks5_buffer = data;
45 return;
46 end
47
48 -- this means the server tells us to connect on an IPv4 address
49 local ip1 = byte(data, 5);
50 local ip2 = byte(data, 6);
51 local ip3 = byte(data, 7);
52 local ip4 = byte(data, 8);
53 local port = bit.band(byte(data, 9), bit.lshift(byte(data, 10), 8));
54 module:log("debug", "Should connect to: "..ip1.."."..ip2.."."..ip3.."."..ip4..":"..port);
55
56 if not (ip1 == 0 and ip2 == 0 and ip3 == 0 and ip4 == 0 and port == 0) then
57 module:log("debug", "The SOCKS5 proxy tells us to connect to a different IP, don't know how. :(");
58 session:close(false);
59 return;
60 end
61
62 -- Now the real s2s listener can take over the connection.
63 local listener = portmanager.get_service("s2s").listener;
64
65 module:log("debug", "SOCKS5 done, handing over listening to "..tostring(listener));
66
67 session.socks5_handler = nil;
68 session.socks5_buffer = nil;
69
70 local w, log = conn.send, session.log;
71
72 local filter = initialize_filters(session);
73
74 session.sends2s = function (t)
75 log("debug", "sending (s2s over socks5): %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
76 if t.name then
77 t = filter("stanzas/out", t);
78 end
79 if t then
80 t = filter("bytes/out", tostring(t));
81 if t then
82 return w(conn, tostring(t));
83 end
84 end
85 end
86
87 session.open_stream = function ()
88 session.sends2s(st.stanza("stream:stream", {
89 xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
90 ["xmlns:stream"]='http://etherx.jabber.org/streams',
91 from=session.from_host, to=session.to_host, version='1.0', ["xml:lang"]='en'}):top_tag());
92 end
93
94 conn.setlistener(conn, listener);
95
96 listener.register_outgoing(conn, session);
97
98 listener.onconnect(conn);
99 end
100 end
101
102 local function socks5_handshake_sent(conn, data)
103
104 local session = sessions[conn];
105
106 if #data < 2 then
107 session.socks5_buffer = data;
108 return;
109 end
110
111 -- version, method
112 local request_status = byte(data, 2);
113
114 module:log("debug", "SOCKS version: "..byte(data, 1));
115 module:log("debug", "Response: "..request_status);
116
117 if not request_status == 0x00 then
118 module:log("debug", "Failed to connect to the SOCKS5 proxy. :( It seems to require authentication.");
119 session:close(false);
120 return;
121 end
122
123 module:log("debug", "Sending connect message.");
124
125 -- version 5, connect, (reserved), type: domainname, (length, hostname), port
126 conn:send(c(5) .. c(1) .. c(0) .. c(3) .. c(#session.socks5_to) .. session.socks5_to);
127 conn:send(c(bit.rshift(session.socks5_port, 8)) .. c(bit.band(session.socks5_port, 0xff)));
128
129 session.socks5_handler = socks5_connect_sent;
130 end
131
132 function socks5listener.onconnect(conn)
133 module:log("debug", "Connected to SOCKS5 proxy, sending SOCKS5 handshake.");
134
135 -- Socks version 5, 1 method, no auth
136 conn:send(c(5) .. c(1) .. c(0));
137
138 sessions[conn].socks5_handler = socks5_handshake_sent;
139 end
140
141 function socks5listener.register_outgoing(conn, session)
142 session.direction = "outgoing";
143 sessions[conn] = session;
144 end
145
146 function socks5listener.ondisconnect(conn, err)
147 sessions[conn] = nil;
148 end
149
150 function socks5listener.onincoming(conn, data)
151 local session = sessions[conn];
152
153 if session.socks5_buffer then
154 data = session.socks5_buffer .. data;
155 end
156
157 if session.socks5_handler then
158 session.socks5_handler(conn, data);
159 end
160 end
161
162 local function connect_socks5(host_session, connect_host, connect_port)
163
164 local conn, handler = socket.tcp();
165
166 module:log("debug", "Connecting to " .. connect_host .. ":" .. connect_port);
167
168 -- this is not necessarily the same as .to_host (it can be that this is a SRV record)
169 host_session.socks5_to = connect_host;
170 host_session.socks5_port = connect_port;
171
172 conn:settimeout(0);
173
174 local success, err = conn:connect(proxy_ip, proxy_port);
175
176 conn = wrapclient(conn, connect_host, connect_port, socks5listener, "*a");
177
178 socks5listener.register_outgoing(conn, host_session);
179
180 host_session.conn = conn;
181 end
182
183 local function bounce_sendq(session, reason)
184 local sendq = session.sendq;
185 if not sendq then return; end
186 session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host));
187 local dummy = {
188 type = "s2sin";
189 send = function(s)
190 (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback());
191 end;
192 dummy = true;
193 };
194 for i, data in ipairs(sendq) do
195 local reply = data[2];
196 if reply and not(reply.attr.xmlns) then
197 reply.attr.type = "error";
198 reply:tag("error", {type = "cancel"})
199 :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
200 if reason then
201 reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
202 :text("Server-to-server connection failed: "..reason):up();
203 end
204 core_process_stanza(dummy, reply);
205 end
206 sendq[i] = nil;
207 end
208 session.sendq = nil;
209 end
210
211 -- Try to intercept anything to *.onion
212 local function route_to_onion(event)
213
214 if not event.to_host:find(".onion(.?)$") then
215 if forbid_else then
216 module:log("debug", event.to_host .. " is not an onion. Blocking it.");
217 return false;
218 else
219 return;
220 end
221 end
222
223 module:log("debug", "Onion routing something to ".. event.to_host);
224
225 if hosts[event.from_host].s2sout[event.to_host] then
226 return;
227 end
228
229 local host_session = s2s_new_outgoing(event.from_host, event.to_host);
230
231 host_session.bounce_sendq = bounce_sendq;
232 host_session.sendq = { {tostring(stanza), stanza.attr and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
233
234 hosts[event.from_host].s2sout[event.to_host] = host_session;
235
236 connect_socks5(host_session, event.to_host, 5269);
237
238 return true;
239 end
240
241 module:log("debug", "Onions ready and loaded");
242
243 hosts[module.host].events.add_handler("route/remote", route_to_onion, 200);