2009-06-20
stanza_router: Skip prepping 'from' on c2s origins - #optimization
| mwild1@896 | 1 | -- Prosody IM v0.4 |
| mwild1@760 | 2 | -- Copyright (C) 2008-2009 Matthew Wild |
| mwild1@760 | 3 | -- Copyright (C) 2008-2009 Waqas Hussain |
| mwild1@519 | 4 | -- |
| mwild1@758 | 5 | -- This project is MIT/X11 licensed. Please see the |
| mwild1@758 | 6 | -- COPYING file in the source package for more information. |
| mwild1@519 | 7 | -- |
| mwild1@519 | 8 | |
| mwild1@519 | 9 | |
| mwild1@30 | 10 | |
| mwild1@30 | 11 | local log = require "util.logger".init("stanzarouter") |
| mwild1@30 | 12 | |
| mwild1@1043 | 13 | local hosts = _G.hosts; |
| mwild1@1043 | 14 | |
| waqas20@71 | 15 | local st = require "util.stanza"; |
| mwild1@187 | 16 | local send_s2s = require "core.s2smanager".send_to_host; |
| waqas20@113 | 17 | local user_exists = require "core.usermanager".user_exists; |
| waqas20@71 | 18 | |
| waqas20@171 | 19 | local rostermanager = require "core.rostermanager"; |
| waqas20@249 | 20 | local offlinemanager = require "core.offlinemanager"; |
| waqas20@171 | 21 | |
| mwild1@191 | 22 | local modules_handle_stanza = require "core.modulemanager".handle_stanza; |
| waqas20@212 | 23 | local component_handle_stanza = require "core.componentmanager".handle_stanza; |
| mwild1@191 | 24 | |
| mwild1@145 | 25 | local tostring = tostring; |
| waqas20@200 | 26 | local t_insert = table.insert; |
| waqas20@783 | 27 | local pairs = pairs; |
| waqas20@783 | 28 | local ipairs = ipairs; |
| mwild1@145 | 29 | |
| waqas20@105 | 30 | local jid_split = require "util.jid".split; |
| waqas20@718 | 31 | local jid_prepped_split = require "util.jid".prepped_split; |
| waqas20@1244 | 32 | local fire_event = prosody.events.fire_event; |
| mwild1@30 | 33 | |
| mwild1@1212 | 34 | local select_best_resources; |
| mwild1@1212 | 35 | |
| mwild1@30 | 36 | function core_process_stanza(origin, stanza) |
| mwild1@726 | 37 | (origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag()) |
| waqas20@212 | 38 | |
| mwild1@1178 | 39 | -- Currently we guarantee every stanza to have an xmlns, should we keep this rule? |
| mwild1@1178 | 40 | if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end |
| mwild1@1178 | 41 | |
| waqas20@73 | 42 | -- TODO verify validity of stanza (as well as JID validity) |
| waqas20@1152 | 43 | if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log |
| waqas20@924 | 44 | if stanza.name == "iq" then |
| waqas20@924 | 45 | if (stanza.attr.type == "set" or stanza.attr.type == "get") and #stanza.tags ~= 1 then |
| waqas20@924 | 46 | origin.send(st.error_reply(stanza, "modify", "bad-request")); |
| waqas20@924 | 47 | return; |
| waqas20@113 | 48 | end |
| waqas20@83 | 49 | end |
| waqas20@78 | 50 | |
| waqas20@1367 | 51 | if origin.type == "c2s" then |
| waqas20@1367 | 52 | if not origin.full_jid |
| waqas20@1367 | 53 | and not(stanza.name == "iq" and stanza.attr.type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind" |
| waqas20@1367 | 54 | and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then |
| waqas20@1367 | 55 | -- authenticated client isn't bound and current stanza is not a bind request |
| waqas20@1367 | 56 | origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server |
| waqas20@1368 | 57 | return; |
| waqas20@1367 | 58 | end |
| waqas20@78 | 59 | |
| waqas20@1367 | 60 | -- TODO also, stanzas should be returned to their original state before the function ends |
| waqas20@212 | 61 | stanza.attr.from = origin.full_jid; |
| waqas20@212 | 62 | end |
| mwild1@621 | 63 | local to, xmlns = stanza.attr.to, stanza.attr.xmlns; |
| waqas20@212 | 64 | local from = stanza.attr.from; |
| waqas20@718 | 65 | local node, host, resource; |
| waqas20@718 | 66 | local from_node, from_host, from_resource; |
| waqas20@718 | 67 | local to_bare, from_bare; |
| waqas20@718 | 68 | if to then |
| waqas20@718 | 69 | node, host, resource = jid_prepped_split(to); |
| waqas20@718 | 70 | if not host then |
| mwild1@1143 | 71 | log("warn", "Received stanza with invalid destination JID: %s", to); |
| waqas20@1270 | 72 | if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then |
| waqas20@1270 | 73 | origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The destination address is invalid: "..to)); |
| waqas20@1270 | 74 | end |
| mwild1@1143 | 75 | return; |
| waqas20@718 | 76 | end |
| waqas20@718 | 77 | to_bare = node and (node.."@"..host) or host; -- bare JID |
| waqas20@718 | 78 | if resource then to = to_bare.."/"..resource; else to = to_bare; end |
| waqas20@718 | 79 | stanza.attr.to = to; |
| waqas20@718 | 80 | end |
| waqas20@1369 | 81 | if from and not origin.full_jid then |
| mwild1@1143 | 82 | -- We only stamp the 'from' on c2s stanzas, so we still need to check validity |
| waqas20@718 | 83 | from_node, from_host, from_resource = jid_prepped_split(from); |
| waqas20@718 | 84 | if not from_host then |
| mwild1@1143 | 85 | log("warn", "Received stanza with invalid source JID: %s", from); |
| waqas20@1270 | 86 | if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then |
| waqas20@1270 | 87 | origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The source address is invalid: "..from)); |
| waqas20@1270 | 88 | end |
| mwild1@1143 | 89 | return; |
| waqas20@718 | 90 | end |
| waqas20@718 | 91 | from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID |
| waqas20@718 | 92 | if from_resource then from = from_bare.."/"..from_resource; else from = from_bare; end |
| waqas20@718 | 93 | stanza.attr.from = from; |
| waqas20@718 | 94 | end |
| waqas20@212 | 95 | |
| waqas20@213 | 96 | --[[if to and not(hosts[to]) and not(hosts[to_bare]) and (hosts[host] and hosts[host].type ~= "local") then -- not for us? |
| waqas20@213 | 97 | log("warn", "stanza recieved for a non-local server"); |
| waqas20@212 | 98 | return; -- FIXME what should we do here? |
| waqas20@213 | 99 | end]] -- FIXME |
| waqas20@207 | 100 | |
| waqas20@1165 | 101 | if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == "jabber:client" then |
| waqas20@631 | 102 | if origin.type == "s2sin" and not origin.dummy then |
| mwild1@621 | 103 | local host_status = origin.hosts[from_host]; |
| mwild1@621 | 104 | if not host_status or not host_status.authed then -- remote server trying to impersonate some other server? |
| mwild1@1179 | 105 | log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host); |
| mwild1@621 | 106 | return; -- FIXME what should we do here? does this work with subdomains? |
| mwild1@621 | 107 | end |
| mwild1@621 | 108 | end |
| waqas20@1172 | 109 | core_post_stanza(origin, stanza); |
| waqas20@222 | 110 | else |
| waqas20@1254 | 111 | local h = hosts[stanza.attr.to or origin.host or origin.to_host]; |
| waqas20@1254 | 112 | if h then |
| waqas20@1254 | 113 | local event; |
| waqas20@1254 | 114 | if stanza.attr.xmlns == "jabber:client" then |
| waqas20@1254 | 115 | if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get") then |
| waqas20@1254 | 116 | event = "stanza/iq/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name; |
| waqas20@1254 | 117 | else |
| waqas20@1254 | 118 | event = "stanza/"..stanza.name; |
| waqas20@1254 | 119 | end |
| waqas20@1254 | 120 | else |
| waqas20@1254 | 121 | event = "stanza/"..stanza.attr.xmlns..":"..stanza.name; |
| waqas20@1254 | 122 | end |
| mwild1@1262 | 123 | if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end |
| waqas20@1254 | 124 | end |
| waqas20@1294 | 125 | if host and not hosts[host] then host = nil; end -- workaround for a Pidgin bug which sets 'to' to the SRV result |
| waqas20@1167 | 126 | modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza); |
| mwild1@119 | 127 | end |
| mwild1@119 | 128 | end |
| mwild1@119 | 129 | |
| waqas20@1172 | 130 | function core_post_stanza(origin, stanza) |
| waqas20@1172 | 131 | local to = stanza.attr.to; |
| waqas20@1172 | 132 | local node, host, resource = jid_split(to); |
| waqas20@1172 | 133 | local to_bare = node and (node.."@"..host) or host; -- bare JID |
| waqas20@1172 | 134 | |
| waqas20@1228 | 135 | local to_type; |
| waqas20@1228 | 136 | if node then |
| waqas20@1228 | 137 | if resource then |
| waqas20@1228 | 138 | to_type = '/full'; |
| waqas20@1228 | 139 | else |
| waqas20@1228 | 140 | to_type = '/bare'; |
| waqas20@1292 | 141 | if node == origin.username and host == origin.host then |
| waqas20@1292 | 142 | stanza.attr.to = nil; |
| waqas20@1292 | 143 | end |
| waqas20@1228 | 144 | end |
| waqas20@1228 | 145 | else |
| waqas20@1228 | 146 | if host then |
| waqas20@1228 | 147 | to_type = '/host'; |
| waqas20@1228 | 148 | else |
| waqas20@1228 | 149 | to_type = '/bare'; |
| waqas20@1228 | 150 | end |
| waqas20@1228 | 151 | end |
| waqas20@1228 | 152 | |
| waqas20@1172 | 153 | local event_data = {origin=origin, stanza=stanza}; |
| waqas20@1228 | 154 | if origin.full_jid then -- c2s connection |
| mwild1@1262 | 155 | if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing |
| waqas20@1228 | 156 | end |
| waqas20@1228 | 157 | local h = hosts[to_bare] or hosts[host or origin.host]; |
| waqas20@1228 | 158 | if h then |
| waqas20@1228 | 159 | if h.type == "component" then |
| waqas20@1228 | 160 | component_handle_stanza(origin, stanza); |
| waqas20@1228 | 161 | return; |
| waqas20@1228 | 162 | else |
| mwild1@1262 | 163 | if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing |
| waqas20@1228 | 164 | end |
| waqas20@1228 | 165 | end |
| waqas20@1228 | 166 | |
| waqas20@1182 | 167 | if host and fire_event(host.."/"..stanza.name, event_data) then |
| waqas20@1182 | 168 | -- event handled |
| waqas20@1182 | 169 | elseif stanza.name == "presence" and origin.host and fire_event(origin.host.."/"..stanza.name, event_data) then |
| waqas20@1172 | 170 | -- event handled |
| waqas20@1172 | 171 | elseif not to then |
| waqas20@1172 | 172 | modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza); |
| waqas20@1172 | 173 | elseif hosts[to] and hosts[to].type == "local" then -- directed at a local server |
| waqas20@1172 | 174 | modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza); |
| waqas20@1172 | 175 | elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server |
| waqas20@1172 | 176 | component_handle_stanza(origin, stanza); |
| waqas20@1172 | 177 | elseif hosts[host] and hosts[host].type == "component" then -- directed at a component |
| waqas20@1172 | 178 | component_handle_stanza(origin, stanza); |
| waqas20@1172 | 179 | elseif hosts[host] and hosts[host].type == "local" and stanza.name == "iq" and not resource then -- directed at bare JID |
| waqas20@1172 | 180 | modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza); |
| mwild1@145 | 181 | else |
| waqas20@1172 | 182 | core_route_stanza(origin, stanza); |
| waqas20@71 | 183 | end |
| mwild1@30 | 184 | end |
| mwild1@30 | 185 | |
| mwild1@30 | 186 | function core_route_stanza(origin, stanza) |
| mwild1@30 | 187 | -- Hooks |
| mwild1@68 | 188 | --- ...later |
| waqas20@207 | 189 | |
| mwild1@30 | 190 | -- Deliver |
| waqas20@113 | 191 | local to = stanza.attr.to; |
| waqas20@113 | 192 | local node, host, resource = jid_split(to); |
| waqas20@170 | 193 | local to_bare = node and (node.."@"..host) or host; -- bare JID |
| waqas20@170 | 194 | local from = stanza.attr.from; |
| waqas20@170 | 195 | local from_node, from_host, from_resource = jid_split(from); |
| waqas20@170 | 196 | local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID |
| waqas20@113 | 197 | |
| mwild1@372 | 198 | -- Auto-detect origin if not specified |
| mwild1@372 | 199 | origin = origin or hosts[from_host]; |
| mwild1@372 | 200 | if not origin then return false; end |
| mwild1@372 | 201 | |
| waqas20@1168 | 202 | if hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server |
| waqas20@781 | 203 | return component_handle_stanza(origin, stanza); |
| waqas20@781 | 204 | elseif hosts[host] and hosts[host].type == "component" then -- directed at a component |
| waqas20@781 | 205 | return component_handle_stanza(origin, stanza); |
| waqas20@781 | 206 | end |
| waqas20@781 | 207 | |
| waqas20@966 | 208 | if stanza.name == "presence" and (stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error") then resource = nil; end |
| waqas20@113 | 209 | |
| mwild1@68 | 210 | local host_session = hosts[host] |
| mwild1@68 | 211 | if host_session and host_session.type == "local" then |
| mwild1@68 | 212 | -- Local host |
| waqas20@71 | 213 | local user = host_session.sessions[node]; |
| waqas20@71 | 214 | if user then |
| waqas20@72 | 215 | local res = user.sessions[resource]; |
| waqas20@1169 | 216 | if res then -- resource is online... |
| waqas20@1169 | 217 | res.send(stanza); -- Yay \o/ |
| waqas20@1169 | 218 | else |
| waqas20@71 | 219 | -- if we get here, resource was not specified or was unavailable |
| waqas20@106 | 220 | if stanza.name == "presence" then |
| waqas20@966 | 221 | if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then |
| waqas20@1356 | 222 | -- inbound presence subscriptions and probes, already handled, so should never get here |
| waqas20@966 | 223 | elseif not resource then -- sender is available or unavailable or error |
| waqas20@200 | 224 | for _, session in pairs(user.sessions) do -- presence broadcast to all user resources. |
| waqas20@200 | 225 | if session.full_jid then -- FIXME should this be just for available resources? Do we need to check subscription? |
| waqas20@200 | 226 | stanza.attr.to = session.full_jid; -- reset at the end of function |
| waqas20@200 | 227 | session.send(stanza); |
| waqas20@113 | 228 | end |
| waqas20@106 | 229 | end |
| waqas20@106 | 230 | end |
| waqas20@113 | 231 | elseif stanza.name == "message" then -- select a resource to recieve message |
| waqas20@784 | 232 | stanza.attr.to = to_bare; |
| waqas20@784 | 233 | if stanza.attr.type == 'headline' then |
| waqas20@783 | 234 | for _, session in pairs(user.sessions) do -- find resource with greatest priority |
| waqas20@783 | 235 | if session.presence and session.priority >= 0 then |
| waqas20@783 | 236 | session.send(stanza); |
| waqas20@783 | 237 | end |
| waqas20@106 | 238 | end |
| waqas20@1273 | 239 | elseif stanza.attr.type == 'groupchat' then |
| mwild1@848 | 240 | -- Groupchat message sent to offline resource |
| mwild1@848 | 241 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| waqas20@783 | 242 | else |
| waqas20@783 | 243 | local count = 0; |
| mwild1@1212 | 244 | for _, session in ipairs(select_best_resources(user)) do |
| waqas20@783 | 245 | session.send(stanza); |
| waqas20@783 | 246 | count = count + 1; |
| waqas20@783 | 247 | end |
| waqas20@783 | 248 | if count == 0 and (stanza.attr.type == "chat" or stanza.attr.type == "normal" or not stanza.attr.type) then |
| waqas20@783 | 249 | offlinemanager.store(node, host, stanza); |
| waqas20@783 | 250 | -- TODO deal with storage errors |
| waqas20@783 | 251 | end |
| waqas20@200 | 252 | end |
| waqas20@967 | 253 | elseif stanza.attr.type == "get" or stanza.attr.type == "set" then |
| waqas20@967 | 254 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| waqas20@113 | 255 | end |
| waqas20@113 | 256 | end |
| waqas20@113 | 257 | else |
| waqas20@113 | 258 | -- user not online |
| waqas20@113 | 259 | if user_exists(node, host) then |
| waqas20@113 | 260 | if stanza.name == "presence" then |
| waqas20@966 | 261 | if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then |
| waqas20@1356 | 262 | -- inbound presence subscriptions and probes, already handled, so should never get here |
| waqas20@113 | 263 | else |
| waqas20@175 | 264 | -- TODO send unavailable presence or unsubscribed |
| waqas20@113 | 265 | end |
| waqas20@642 | 266 | elseif stanza.name == "message" then -- FIXME if full jid, then send out to resources with highest priority |
| waqas20@784 | 267 | stanza.attr.to = to_bare; -- TODO not in RFC, but seems obvious. Should discuss on the mailing list. |
| waqas20@249 | 268 | if stanza.attr.type == "chat" or stanza.attr.type == "normal" or not stanza.attr.type then |
| waqas20@249 | 269 | offlinemanager.store(node, host, stanza); |
| waqas20@249 | 270 | -- FIXME don't store messages with only chat state notifications |
| mwild1@848 | 271 | elseif stanza.attr.type == "groupchat" then |
| mwild1@848 | 272 | local reply = st.error_reply(stanza, "cancel", "service-unavailable"); |
| mwild1@848 | 273 | reply.attr.from = to; |
| mwild1@848 | 274 | origin.send(reply); |
| waqas20@249 | 275 | end |
| waqas20@249 | 276 | -- TODO allow configuration of offline storage |
| waqas20@249 | 277 | -- TODO send error if not storing offline |
| mwild1@854 | 278 | elseif stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then |
| waqas20@642 | 279 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| waqas20@113 | 280 | end |
| waqas20@113 | 281 | else -- user does not exist |
| waqas20@113 | 282 | -- TODO we would get here for nodeless JIDs too. Do something fun maybe? Echo service? Let plugins use xmpp:server/resource addresses? |
| waqas20@113 | 283 | if stanza.name == "presence" then |
| waqas20@642 | 284 | local t = stanza.attr.type; |
| waqas20@642 | 285 | if t == "subscribe" or t == "probe" then |
| mwild1@186 | 286 | origin.send(st.presence({from = to_bare, to = from_bare, type = "unsubscribed"})); |
| waqas20@113 | 287 | end |
| waqas20@113 | 288 | -- else ignore |
| mwild1@854 | 289 | elseif stanza.attr.type ~= "error" and (stanza.name ~= "iq" or stanza.attr.type ~= "result") then |
| mwild1@186 | 290 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| waqas20@71 | 291 | end |
| waqas20@71 | 292 | end |
| waqas20@71 | 293 | end |
| mwild1@148 | 294 | elseif origin.type == "c2s" then |
| mwild1@148 | 295 | -- Remote host |
| waqas20@170 | 296 | local xmlns = stanza.attr.xmlns; |
| mwild1@148 | 297 | --stanza.attr.xmlns = "jabber:server"; |
| mwild1@148 | 298 | stanza.attr.xmlns = nil; |
| mwild1@148 | 299 | log("debug", "sending s2s stanza: %s", tostring(stanza)); |
| waqas20@170 | 300 | send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors |
| waqas20@170 | 301 | stanza.attr.xmlns = xmlns; -- reset |
| mwild1@210 | 302 | elseif origin.type == "component" or origin.type == "local" then |
| mwild1@210 | 303 | -- Route via s2s for components and modules |
| waqas20@1348 | 304 | log("debug", "Routing outgoing stanza for %s to %s", from_host, host); |
| waqas20@1348 | 305 | send_s2s(from_host, host, stanza); |
| mwild1@68 | 306 | else |
| mwild1@148 | 307 | log("warn", "received stanza from unhandled connection type: %s", origin.type); |
| mwild1@68 | 308 | end |
| waqas20@113 | 309 | stanza.attr.to = to; -- reset |
| mwild1@30 | 310 | end |
| mwild1@1212 | 311 | |
| mwild1@1212 | 312 | function select_best_resources(user) |
| mwild1@1212 | 313 | local priority = 0; |
| mwild1@1212 | 314 | local recipients = {}; |
| mwild1@1212 | 315 | for _, session in pairs(user.sessions) do -- find resource with greatest priority |
| mwild1@1212 | 316 | if session.presence then |
| mwild1@1212 | 317 | local p = session.priority; |
| mwild1@1212 | 318 | if p > priority then |
| mwild1@1212 | 319 | priority = p; |
| mwild1@1212 | 320 | recipients = {session}; |
| mwild1@1212 | 321 | elseif p == priority then |
| mwild1@1212 | 322 | t_insert(recipients, session); |
| mwild1@1212 | 323 | end |
| mwild1@1212 | 324 | end |
| mwild1@1212 | 325 | end |
| mwild1@1212 | 326 | return recipients; |
| mwild1@1212 | 327 | end |