Software /
code /
prosody-modules
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); |