| 1 | -- Copyright (C) 2009 Thilo Cestonaro |
| 2 | -- |
| 3 | -- This project is MIT/X11 licensed. Please see the |
| 4 | -- COPYING file in the source package for more information. |
| 5 | -- |
| 6 | --[[ |
| 7 | * to restart the proxy in the console: e.g. |
| 8 | module:unload("proxy65"); |
| 9 | > server.removeserver(<proxy65_port>); |
| 10 | module:load("proxy65", <proxy65_jid>); |
| 11 | ]]-- |
| 12 | |
| 13 | |
| 14 | local module = module; |
| 15 | local tostring = tostring; |
| 16 | local jid_split, jid_join, jid_compare = require "util.jid".split, require "util.jid".join, require "util.jid".compare; |
| 17 | local st = require "util.stanza"; |
| 18 | local connlisteners = require "net.connlisteners"; |
| 19 | local sha1 = require "util.hashes".sha1; |
| 20 | local server = require "net.server"; |
| 21 | |
| 22 | local host, name = module:get_host(), "SOCKS5 Bytestreams Service"; |
| 23 | local sessions, transfers, replies_cache = {}, {}, {}; |
| 24 | |
| 25 | local proxy_port = module:get_option("proxy65_port") or 5000; |
| 26 | local proxy_interface = module:get_option("proxy65_interface") or "*"; |
| 27 | local proxy_address = module:get_option("proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host; |
| 28 | local proxy_acl = module:get_option("proxy65_acl"); |
| 29 | local max_buffer_size = 4096; |
| 30 | |
| 31 | local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" }; |
| 32 | |
| 33 | function connlistener.onincoming(conn, data) |
| 34 | local session = sessions[conn] or {}; |
| 35 | |
| 36 | if session.setup == nil and data ~= nil and data:byte(1) == 0x05 and #data > 2 then |
| 37 | local nmethods = data:byte(2); |
| 38 | local methods = data:sub(3); |
| 39 | local supported = false; |
| 40 | for i=1, nmethods, 1 do |
| 41 | if(methods:byte(i) == 0x00) then -- 0x00 == method: NO AUTH |
| 42 | supported = true; |
| 43 | break; |
| 44 | end |
| 45 | end |
| 46 | if(supported) then |
| 47 | module:log("debug", "new session found ... ") |
| 48 | session.setup = true; |
| 49 | sessions[conn] = session; |
| 50 | conn:write(string.char(5, 0)); |
| 51 | end |
| 52 | return; |
| 53 | end |
| 54 | if session.setup then |
| 55 | if session.sha ~= nil and transfers[session.sha] ~= nil then |
| 56 | local sha = session.sha; |
| 57 | if transfers[sha].activated == true and transfers[sha].target ~= nil then |
| 58 | if transfers[sha].initiator == conn then |
| 59 | transfers[sha].target:write(data); |
| 60 | else |
| 61 | transfers[sha].initiator:write(data); |
| 62 | end |
| 63 | return; |
| 64 | end |
| 65 | end |
| 66 | if data ~= nil and #data == 0x2F and -- 40 == length of SHA1 HASH, and 7 other bytes => 47 => 0x2F |
| 67 | data:byte(1) == 0x05 and -- SOCKS5 has 5 in first byte |
| 68 | data:byte(2) == 0x01 and -- CMD must be 1 |
| 69 | data:byte(3) == 0x00 and -- RSV must be 0 |
| 70 | data:byte(4) == 0x03 and -- ATYP must be 3 |
| 71 | data:byte(5) == 40 and -- SHA1 HASH length must be 40 (0x28) |
| 72 | data:byte(-2) == 0x00 and -- PORT must be 0, size 2 byte |
| 73 | data:byte(-1) == 0x00 |
| 74 | then |
| 75 | local sha = data:sub(6, 45); -- second param is not count! it's the ending index (included!) |
| 76 | if transfers[sha] == nil then |
| 77 | transfers[sha] = {}; |
| 78 | transfers[sha].activated = false; |
| 79 | transfers[sha].target = conn; |
| 80 | session.sha = sha; |
| 81 | module:log("debug", "target connected ... "); |
| 82 | elseif transfers[sha].target ~= nil then |
| 83 | transfers[sha].initiator = conn; |
| 84 | session.sha = sha; |
| 85 | module:log("debug", "initiator connected ... "); |
| 86 | server.link(conn, transfers[sha].target, max_buffer_size); |
| 87 | server.link(transfers[sha].target, conn, max_buffer_size); |
| 88 | end |
| 89 | conn:write(string.char(5, 0, 0, 3, #sha) .. sha .. string.char(0, 0)); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) |
| 90 | conn:lock_read(true) |
| 91 | else |
| 92 | module:log("warn", "Neither data transfer nor initial connect of a participator of a transfer.") |
| 93 | conn:close(); |
| 94 | end |
| 95 | else |
| 96 | if data ~= nil then |
| 97 | module:log("warn", "unknown connection with no authentication data -> closing it"); |
| 98 | conn:close(); |
| 99 | end |
| 100 | end |
| 101 | end |
| 102 | |
| 103 | function connlistener.ondisconnect(conn, err) |
| 104 | local session = sessions[conn]; |
| 105 | if session then |
| 106 | if session.sha and transfers[session.sha] then |
| 107 | local initiator, target = transfers[session.sha].initiator, transfers[session.sha].target; |
| 108 | if initiator == conn and target ~= nil then |
| 109 | target:close(); |
| 110 | elseif target == conn and initiator ~= nil then |
| 111 | initiator:close(); |
| 112 | end |
| 113 | transfers[session.sha] = nil; |
| 114 | end |
| 115 | -- Clean up any session-related stuff here |
| 116 | sessions[conn] = nil; |
| 117 | end |
| 118 | end |
| 119 | |
| 120 | module:add_identity("proxy", "bytestreams", name); |
| 121 | module:add_feature("http://jabber.org/protocol/bytestreams"); |
| 122 | |
| 123 | module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event) |
| 124 | local origin, stanza = event.origin, event.stanza; |
| 125 | local reply = replies_cache.disco_info; |
| 126 | if reply == nil then |
| 127 | reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#info") |
| 128 | :tag("identity", {category='proxy', type='bytestreams', name=name}):up() |
| 129 | :tag("feature", {var="http://jabber.org/protocol/bytestreams"}); |
| 130 | replies_cache.disco_info = reply; |
| 131 | end |
| 132 | |
| 133 | reply.attr.id = stanza.attr.id; |
| 134 | reply.attr.to = stanza.attr.from; |
| 135 | origin.send(reply); |
| 136 | return true; |
| 137 | end, -1); |
| 138 | |
| 139 | module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event) |
| 140 | local origin, stanza = event.origin, event.stanza; |
| 141 | local reply = replies_cache.disco_items; |
| 142 | if reply == nil then |
| 143 | reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#items"); |
| 144 | replies_cache.disco_items = reply; |
| 145 | end |
| 146 | |
| 147 | reply.attr.id = stanza.attr.id; |
| 148 | reply.attr.to = stanza.attr.from; |
| 149 | origin.send(reply); |
| 150 | return true; |
| 151 | end, -1); |
| 152 | |
| 153 | module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event) |
| 154 | local origin, stanza = event.origin, event.stanza; |
| 155 | local reply = replies_cache.stream_host; |
| 156 | local err_reply = replies_cache.stream_host_err; |
| 157 | local sid = stanza.tags[1].attr.sid; |
| 158 | local allow = false; |
| 159 | local jid = stanza.attr.from; |
| 160 | |
| 161 | if proxy_acl and #proxy_acl > 0 then |
| 162 | for _, acl in ipairs(proxy_acl) do |
| 163 | if jid_compare(jid, acl) then allow = true; end |
| 164 | end |
| 165 | else |
| 166 | allow = true; |
| 167 | end |
| 168 | if allow == true then |
| 169 | if reply == nil then |
| 170 | reply = st.iq({type="result", from=host}) |
| 171 | :query("http://jabber.org/protocol/bytestreams") |
| 172 | :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}); |
| 173 | replies_cache.stream_host = reply; |
| 174 | end |
| 175 | else |
| 176 | module:log("warn", "Denying use of proxy for %s", tostring(jid)); |
| 177 | if err_reply == nil then |
| 178 | err_reply = st.iq({type="error", from=host}) |
| 179 | :query("http://jabber.org/protocol/bytestreams") |
| 180 | :tag("error", {code='403', type='auth'}) |
| 181 | :tag("forbidden", {xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'}); |
| 182 | replies_cache.stream_host_err = err_reply; |
| 183 | end |
| 184 | reply = err_reply; |
| 185 | end |
| 186 | reply.attr.id = stanza.attr.id; |
| 187 | reply.attr.to = stanza.attr.from; |
| 188 | reply.tags[1].attr.sid = sid; |
| 189 | origin.send(reply); |
| 190 | return true; |
| 191 | end); |
| 192 | |
| 193 | module.unload = function() |
| 194 | connlisteners.deregister(module.host .. ':proxy65'); |
| 195 | end |
| 196 | |
| 197 | local function set_activation(stanza) |
| 198 | local to, reply; |
| 199 | local from = stanza.attr.from; |
| 200 | local query = stanza.tags[1]; |
| 201 | local sid = query.attr.sid; |
| 202 | if query.tags[1] and query.tags[1].name == "activate" then |
| 203 | to = query.tags[1][1]; |
| 204 | end |
| 205 | if from ~= nil and to ~= nil and sid ~= nil then |
| 206 | reply = st.iq({type="result", from=host, to=from}); |
| 207 | reply.attr.id = stanza.attr.id; |
| 208 | end |
| 209 | return reply, from, to, sid; |
| 210 | end |
| 211 | |
| 212 | module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event) |
| 213 | local origin, stanza = event.origin, event.stanza; |
| 214 | |
| 215 | module:log("debug", "Received activation request from %s", stanza.attr.from); |
| 216 | local reply, from, to, sid = set_activation(stanza); |
| 217 | if reply ~= nil and from ~= nil and to ~= nil and sid ~= nil then |
| 218 | local sha = sha1(sid .. from .. to, true); |
| 219 | if transfers[sha] == nil then |
| 220 | module:log("error", "transfers[sha]: nil"); |
| 221 | elseif(transfers[sha] ~= nil and transfers[sha].initiator ~= nil and transfers[sha].target ~= nil) then |
| 222 | origin.send(reply); |
| 223 | transfers[sha].activated = true; |
| 224 | transfers[sha].target:lock_read(false); |
| 225 | transfers[sha].initiator:lock_read(false); |
| 226 | else |
| 227 | module:log("debug", "Both parties were not yet connected"); |
| 228 | local message = "Neither party is connected to the proxy"; |
| 229 | if transfers[sha].initiator then |
| 230 | message = "The recipient is not connected to the proxy"; |
| 231 | elseif transfers[sha].target then |
| 232 | message = "The sender (you) is not connected to the proxy"; |
| 233 | end |
| 234 | origin.send(st.error_reply(stanza, "cancel", "not-allowed", message)); |
| 235 | end |
| 236 | return true; |
| 237 | else |
| 238 | module:log("error", "activation failed: sid: %s, initiator: %s, target: %s", tostring(sid), tostring(from), tostring(to)); |
| 239 | end |
| 240 | end); |
| 241 | |
| 242 | if not connlisteners.register(module.host .. ':proxy65', connlistener) then |
| 243 | module:log("error", "mod_proxy65: Could not establish a connection listener. Check your configuration please."); |
| 244 | module:log("error", "Possibly two proxy65 components are configured to share the same port."); |
| 245 | end |
| 246 | |
| 247 | connlisteners.start(module.host .. ':proxy65'); |