| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | local log = require "util.logger".init("stanzarouter") |
| 10 | |
| 11 | local hosts = _G.prosody.hosts; |
| 12 | local tostring = tostring; |
| 13 | local st = require "util.stanza"; |
| 14 | local send_s2s = require "core.s2smanager".send_to_host; |
| 15 | local jid_split = require "util.jid".split; |
| 16 | local jid_prepped_split = require "util.jid".prepped_split; |
| 17 | |
| 18 | local full_sessions = _G.prosody.full_sessions; |
| 19 | local bare_sessions = _G.prosody.bare_sessions; |
| 20 | |
| 21 | local function handle_unhandled_stanza(host, origin, stanza) |
| 22 | local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type; |
| 23 | if name == "iq" and xmlns == "jabber:client" then |
| 24 | if stanza.attr.type == "get" or stanza.attr.type == "set" then |
| 25 | xmlns = stanza.tags[1].attr.xmlns or "jabber:client"; |
| 26 | log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns); |
| 27 | else |
| 28 | log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type); |
| 29 | return true; |
| 30 | end |
| 31 | end |
| 32 | if not stanza.attr.xmlns then |
| 33 | log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it |
| 34 | if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then |
| 35 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| 36 | end |
| 37 | elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features |
| 38 | log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it |
| 39 | origin:close("unsupported-stanza-type"); |
| 40 | end |
| 41 | end |
| 42 | |
| 43 | function core_process_stanza(origin, stanza) |
| 44 | (origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag()) |
| 45 | |
| 46 | -- TODO verify validity of stanza (as well as JID validity) |
| 47 | if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log |
| 48 | if stanza.name == "iq" then |
| 49 | if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests |
| 50 | if (stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1) then |
| 51 | origin.send(st.error_reply(stanza, "modify", "bad-request")); |
| 52 | return; |
| 53 | end |
| 54 | end |
| 55 | |
| 56 | if origin.type == "c2s" and not stanza.attr.xmlns then |
| 57 | if not origin.full_jid |
| 58 | and not(stanza.name == "iq" and stanza.attr.type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind" |
| 59 | and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then |
| 60 | -- authenticated client isn't bound and current stanza is not a bind request |
| 61 | if stanza.attr.type ~= "result" and stanza.attr.type ~= "error" then |
| 62 | origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server |
| 63 | end |
| 64 | return; |
| 65 | end |
| 66 | |
| 67 | -- TODO also, stanzas should be returned to their original state before the function ends |
| 68 | stanza.attr.from = origin.full_jid; |
| 69 | end |
| 70 | local to, xmlns = stanza.attr.to, stanza.attr.xmlns; |
| 71 | local from = stanza.attr.from; |
| 72 | local node, host, resource; |
| 73 | local from_node, from_host, from_resource; |
| 74 | local to_bare, from_bare; |
| 75 | if to then |
| 76 | if full_sessions[to] or bare_sessions[to] or hosts[to] then |
| 77 | node, host = jid_split(to); -- TODO only the host is needed, optimize |
| 78 | else |
| 79 | node, host, resource = jid_prepped_split(to); |
| 80 | if not host then |
| 81 | log("warn", "Received stanza with invalid destination JID: %s", to); |
| 82 | if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then |
| 83 | origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The destination address is invalid: "..to)); |
| 84 | end |
| 85 | return; |
| 86 | end |
| 87 | to_bare = node and (node.."@"..host) or host; -- bare JID |
| 88 | if resource then to = to_bare.."/"..resource; else to = to_bare; end |
| 89 | stanza.attr.to = to; |
| 90 | end |
| 91 | end |
| 92 | if from and not origin.full_jid then |
| 93 | -- We only stamp the 'from' on c2s stanzas, so we still need to check validity |
| 94 | from_node, from_host, from_resource = jid_prepped_split(from); |
| 95 | if not from_host then |
| 96 | log("warn", "Received stanza with invalid source JID: %s", from); |
| 97 | if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then |
| 98 | origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The source address is invalid: "..from)); |
| 99 | end |
| 100 | return; |
| 101 | end |
| 102 | from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID |
| 103 | if from_resource then from = from_bare.."/"..from_resource; else from = from_bare; end |
| 104 | stanza.attr.from = from; |
| 105 | end |
| 106 | |
| 107 | --[[if to and not(hosts[to]) and not(hosts[to_bare]) and (hosts[host] and hosts[host].type ~= "local") then -- not for us? |
| 108 | log("warn", "stanza recieved for a non-local server"); |
| 109 | return; -- FIXME what should we do here? |
| 110 | end]] -- FIXME |
| 111 | |
| 112 | if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and not xmlns then |
| 113 | if origin.type == "s2sin" and not origin.dummy then |
| 114 | local host_status = origin.hosts[from_host]; |
| 115 | if not host_status or not host_status.authed then -- remote server trying to impersonate some other server? |
| 116 | log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host); |
| 117 | return; -- FIXME what should we do here? does this work with subdomains? |
| 118 | end |
| 119 | end |
| 120 | core_post_stanza(origin, stanza, origin.full_jid); |
| 121 | else |
| 122 | local h = hosts[stanza.attr.to or origin.host or origin.to_host]; |
| 123 | if h then |
| 124 | local event; |
| 125 | if not xmlns then |
| 126 | if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get") then |
| 127 | event = "stanza/iq/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name; |
| 128 | else |
| 129 | event = "stanza/"..stanza.name; |
| 130 | end |
| 131 | else |
| 132 | event = "stanza/"..xmlns..":"..stanza.name; |
| 133 | end |
| 134 | if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end |
| 135 | end |
| 136 | if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result |
| 137 | handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza); |
| 138 | end |
| 139 | end |
| 140 | |
| 141 | function core_post_stanza(origin, stanza, preevents) |
| 142 | local to = stanza.attr.to; |
| 143 | local node, host, resource = jid_split(to); |
| 144 | local to_bare = node and (node.."@"..host) or host; -- bare JID |
| 145 | |
| 146 | local to_type, to_self; |
| 147 | if node then |
| 148 | if resource then |
| 149 | to_type = '/full'; |
| 150 | else |
| 151 | to_type = '/bare'; |
| 152 | if node == origin.username and host == origin.host then |
| 153 | stanza.attr.to = nil; |
| 154 | to_self = true; |
| 155 | end |
| 156 | end |
| 157 | else |
| 158 | if host then |
| 159 | to_type = '/host'; |
| 160 | else |
| 161 | to_type = '/bare'; |
| 162 | to_self = true; |
| 163 | end |
| 164 | end |
| 165 | |
| 166 | local event_data = {origin=origin, stanza=stanza}; |
| 167 | if preevents then -- c2s connection |
| 168 | if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing |
| 169 | end |
| 170 | local h = hosts[to_bare] or hosts[host or origin.host]; |
| 171 | if h then |
| 172 | if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing |
| 173 | if to_self and h.events.fire_event(stanza.name..'/self', event_data) then return; end -- do processing |
| 174 | handle_unhandled_stanza(h.host, origin, stanza); |
| 175 | else |
| 176 | core_route_stanza(origin, stanza); |
| 177 | end |
| 178 | end |
| 179 | |
| 180 | function core_route_stanza(origin, stanza) |
| 181 | local node, host, resource = jid_split(stanza.attr.to); |
| 182 | local from_node, from_host, from_resource = jid_split(stanza.attr.from); |
| 183 | |
| 184 | -- Auto-detect origin if not specified |
| 185 | origin = origin or hosts[from_host]; |
| 186 | if not origin then return false; end |
| 187 | |
| 188 | if hosts[host] then |
| 189 | -- old stanza routing code removed |
| 190 | core_post_stanza(origin, stanza); |
| 191 | elseif origin.type == "c2s" then |
| 192 | -- Remote host |
| 193 | if not hosts[from_host] then |
| 194 | log("error", "No hosts[from_host] (please report): %s", tostring(stanza)); |
| 195 | end |
| 196 | if (not hosts[from_host]) or (not hosts[from_host].disallow_s2s) then |
| 197 | local xmlns = stanza.attr.xmlns; |
| 198 | --stanza.attr.xmlns = "jabber:server"; |
| 199 | stanza.attr.xmlns = nil; |
| 200 | log("debug", "sending s2s stanza: %s", tostring(stanza.top_tag and stanza:top_tag()) or stanza); |
| 201 | send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors |
| 202 | stanza.attr.xmlns = xmlns; -- reset |
| 203 | else |
| 204 | core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote servers is not allowed")); |
| 205 | end |
| 206 | elseif origin.type == "component" or origin.type == "local" then |
| 207 | -- Route via s2s for components and modules |
| 208 | log("debug", "Routing outgoing stanza for %s to %s", from_host, host); |
| 209 | send_s2s(from_host, host, stanza); |
| 210 | else |
| 211 | log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type)); |
| 212 | end |
| 213 | end |