From a53395e6b72ac0abad00123e1d07b3504f585e08 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 30 Sep 2008 19:52:00 +0100 Subject: Huge commit to: * Break stanza routing (to be restored in a future commit) * Remove the old stanza_dispatcher code, which was never going to be maintainable nor extendable :) * Bring us plugins, starting with mod_legacyauth and mod_roster * Sessions are now created/destroyed using a standard sessionmanager interface --- core/modulemanager.lua | 79 +++++++++++++ core/servermanager.lua | 8 ++ core/sessionmanager.lua | 20 ++++ core/stanza_router.lua | 292 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 399 insertions(+) create mode 100644 core/modulemanager.lua create mode 100644 core/servermanager.lua create mode 100644 core/sessionmanager.lua create mode 100644 core/stanza_router.lua (limited to 'core') diff --git a/core/modulemanager.lua b/core/modulemanager.lua new file mode 100644 index 00000000..ed70b75b --- /dev/null +++ b/core/modulemanager.lua @@ -0,0 +1,79 @@ + +local log = require "util.logger".init("modulemanager") + +local loadfile, pcall = loadfile, pcall; +local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv; +local pairs, ipairs = pairs, ipairs; +local type = type; + +local tostring, print = tostring, print; + +local _G = _G; + +module "modulemanager" + +local handler_info = {}; +local handlers = {}; + +local modulehelpers = setmetatable({}, { __index = _G }); + +function modulehelpers.add_iq_handler(origin_type, xmlns, handler) + handlers[origin_type] = handlers[origin_type] or {}; + handlers[origin_type].iq = handlers[origin_type].iq or {}; + if not handlers[origin_type].iq[xmlns] then + handlers[origin_type].iq[xmlns]= handler; + handler_info[handler] = getfenv(2).module; + log("debug", "mod_%s now handles iq,%s", getfenv(2).module.name, xmlns); + else + log("warning", "mod_%s wants to handle iq,%s but mod_%s already handles that", getfenv(2).module.name, xmlns, handler_info[handlers[origin_type].iq[xmlns]].module.name); + end +end + +function modulehelpers.add_presence_handler(origin_type, handler) +end + +function modulehelpers.add_message_handler(origin_type, handler) +end + +function loadall() + load("legacyauth"); + load("roster"); +end + +function load(name) + local mod, err = loadfile("plugins/mod_"..name..".lua"); + if not mod then + log("error", "Unable to load module '%s': %s", name or "nil", err or "nil"); + return; + end + + local pluginenv = setmetatable({ module = { name = name } }, { __index = modulehelpers }); + + setfenv(mod, pluginenv); + local success, ret = pcall(mod); + if not success then + log("error", "Error initialising module '%s': %s", name or "nil", ret or "nil"); + return; + end +end + +function handle_stanza(origin, stanza) + local name, origin_type = stanza.name, origin.type; + + if name == "iq" then + log("debug", "Stanza is an "); + local child = stanza.tags[1]; + if child then + local xmlns = child.attr.xmlns; + log("debug", "Stanza has xmlns: %s", xmlns); + local handler = handlers[origin_type][name][xmlns]; + if handler then + log("debug", "Passing stanza to mod_%s", handler_info[handler].name); + return handler(origin, stanza) or true; + end + + end + end + log("debug", "Stanza unhandled by any modules"); + return false; -- we didn't handle it +end diff --git a/core/servermanager.lua b/core/servermanager.lua new file mode 100644 index 00000000..02ccab53 --- /dev/null +++ b/core/servermanager.lua @@ -0,0 +1,8 @@ + +require "modulemanager" + +-- Handle stanzas that were addressed to the server (whether they came from c2s, s2s, etc.) +function handle_stanza(origin, stanza) + -- Use plugins + return modulemanager.handle_stanza(origin, stanza); +end diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua new file mode 100644 index 00000000..47f47ba9 --- /dev/null +++ b/core/sessionmanager.lua @@ -0,0 +1,20 @@ + +local tostring = tostring; + +local log = require "util.logger".init("sessionmanager"); + +module "sessionmanager" + +function new_session(conn) + local session = { conn = conn, notopen = true, priority = 0, type = "c2s_unauthed" }; + local w = conn.write; + session.send = function (t) w(tostring(t)); end + return session; +end + +function send_to_session(session, data) + log("debug", "Sending...", tostring(data)); + session.conn.write(tostring(data)); +end + +return _M; \ No newline at end of file diff --git a/core/stanza_router.lua b/core/stanza_router.lua new file mode 100644 index 00000000..0ca534ce --- /dev/null +++ b/core/stanza_router.lua @@ -0,0 +1,292 @@ + +-- The code in this file should be self-explanatory, though the logic is horrible +-- for more info on that, see doc/stanza_routing.txt, which attempts to condense +-- the rules from the RFCs (mainly 3921) + +require "core.servermanager" + +local log = require "util.logger".init("stanzarouter") + +require "util.jid" +local jid_split = jid.split; + +function core_process_stanza(origin, stanza) + local to = stanza.attr.to; + + if not to or (hosts[to] and hosts[to].type == "local") then + core_handle_stanza(origin, stanza); + elseif origin.type == "c2s" then + core_route_stanza(origin, stanza); + end + +end + +function core_handle_stanza(origin, stanza) + -- Handlers + if origin.type == "c2s" or origin.type == "c2s_unauthed" then + local session = origin; + stanza.attr.from = session.full_jid; + + log("debug", "Routing stanza"); + -- Stanza has no to attribute + --local to_node, to_host, to_resource = jid_split(stanza.attr.to); + --if not to_host then error("Invalid destination JID: "..string.format("{ %q, %q, %q } == %q", to_node or "", to_host or "", to_resource or "", stanza.attr.to or "nil")); end + + -- Stanza is to this server, or a user on this server + log("debug", "Routing stanza to local"); + print(type(origin), type(stanza)); + handle_stanza(session, stanza); + end +end + +function core_route_stanza(origin, stanza) + -- Hooks + -- Deliver +end + +function handle_stanza_nodest(stanza) + if stanza.name == "iq" then + handle_stanza_iq_no_to(session, stanza); + elseif stanza.name == "presence" then + -- Broadcast to this user's contacts + handle_stanza_presence_broadcast(session, stanza); + -- also, if it is initial presence, send out presence probes + if not session.last_presence then + handle_stanza_presence_probe_broadcast(session, stanza); + end + session.last_presence = stanza; + elseif stanza.name == "message" then + -- Treat as if message was sent to bare JID of the sender + handle_stanza_to_local_user(stanza); + end +end + +function handle_stanza_tolocal(stanza) + local node, host, resource = jid.split(stanza.attr.to); + if host and hosts[host] and hosts[host].type == "local" then + -- Is a local host, handle internally + if node then + -- Is a local user, send to their session + log("debug", "Routing stanza to %s@%s", node, host); + if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what? + handle_stanza_to_local_user(stanza); + else + -- Is sent to this server, let's handle it... + log("debug", "Routing stanza to %s", host); + handle_stanza_to_server(stanza, session); + end + end +end + +function handle_stanza_toremote(stanza) + log("error", "Stanza bound for remote host, but s2s is not implemented"); +end + + +--[[ +local function route_c2s_stanza(session, stanza) + stanza.attr.from = session.full_jid; + if not stanza.attr.to and session.username then + -- Has no 'to' attribute, handle internally + if stanza.name == "iq" then + handle_stanza_iq_no_to(session, stanza); + elseif stanza.name == "presence" then + -- Broadcast to this user's contacts + handle_stanza_presence_broadcast(session, stanza); + -- also, if it is initial presence, send out presence probes + if not session.last_presence then + handle_stanza_presence_probe_broadcast(session, stanza); + end + session.last_presence = stanza; + elseif stanza.name == "message" then + -- Treat as if message was sent to bare JID of the sender + handle_stanza_to_local_user(stanza); + end + end + local node, host, resource = jid.split(stanza.attr.to); + if host and hosts[host] and hosts[host].type == "local" then + -- Is a local host, handle internally + if node then + -- Is a local user, send to their session + if not session.username then return; end --FIXME: Correct response when trying to use unauthed stream is what? + handle_stanza_to_local_user(stanza); + else + -- Is sent to this server, let's handle it... + handle_stanza_to_server(stanza, session); + end + else + -- Is not for us or a local user, route accordingly + route_s2s_stanza(stanza); + end +end + +function handle_stanza_no_to(session, stanza) + if not stanza.attr.id then log("warn", " without id attribute is invalid"); end + local xmlns = (stanza.tags[1].attr and stanza.tags[1].attr.xmlns); + if stanza.attr.type == "get" or stanza.attr.type == "set" then + if iq_handlers[xmlns] then + if iq_handlers[xmlns](stanza) then return; end; -- If handler returns true, it handled it + end + -- Oh, handler didn't handle it. Need to send service-unavailable now. + log("warn", "Unhandled namespace: "..xmlns); + session:send(format("", stanza.attr.id)); + return; -- All done! + end +end + +function handle_stanza_to_local_user(stanza) + if stanza.name == "message" then + handle_stanza_message_to_local_user(stanza); + elseif stanza.name == "presence" then + handle_stanza_presence_to_local_user(stanza); + elseif stanza.name == "iq" then + handle_stanza_iq_to_local_user(stanza); + end +end + +function handle_stanza_message_to_local_user(stanza) + local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource; + local destuser = hosts[host].sessions[node]; + if destuser then + if resource and destuser[resource] then + destuser[resource]:send(stanza); + else + -- Bare JID, or resource offline + local best_session; + for resource, session in pairs(destuser.sessions) do + if not best_session then best_session = session; + elseif session.priority >= best_session.priority and session.priority >= 0 then + best_session = session; + end + end + if not best_session then + offlinemessage.new(node, host, stanza); + else + print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'"); + destuser[best_session]:send(stanza); + end + end + else + -- User is offline + offlinemessage.new(node, host, stanza); + end +end + +function handle_stanza_presence_to_local_user(stanza) + local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource; + local destuser = hosts[host].sessions[node]; + if destuser then + if resource then + if destuser[resource] then + destuser[resource]:send(stanza); + else + return; + end + else + -- Broadcast to all user's resources + for resource, session in pairs(destuser.sessions) do + session:send(stanza); + end + end + end +end + +function handle_stanza_iq_to_local_user(stanza) + +end + +function foo() + local node, host, resource = stanza.to.node, stanza.to.host, stanza.to.resource; + local destuser = hosts[host].sessions[node]; + if destuser and destuser.sessions then + -- User online + if resource and destuser.sessions[resource] then + stanza.to:send(stanza); + else + --User is online, but specified resource isn't (or no resource specified) + local best_session; + for resource, session in pairs(destuser.sessions) do + if not best_session then best_session = session; + elseif session.priority >= best_session.priority and session.priority >= 0 then + best_session = session; + end + end + if not best_session then + offlinemessage.new(node, host, stanza); + else + print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'"); + resource = best_session.resource; + end + end + if destuser.sessions[resource] == session then + log("warn", "core", "Attempt to send stanza to self, dropping..."); + else + print("...sending...", tostring(stanza)); + --destuser.sessions[resource].conn.write(tostring(data)); + print(" to conn ", destuser.sessions[resource].conn); + destuser.sessions[resource].conn.write(tostring(stanza)); + print("...sent") + end + elseif stanza.name == "message" then + print(" ...will be stored offline"); + offlinemessage.new(node, host, stanza); + elseif stanza.name == "iq" then + print(" ...is an iq"); + stanza.from:send(st.reply(stanza) + :tag("error", { type = "cancel" }) + :tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" })); + end +end + +-- Broadcast a presence stanza to all of a user's contacts +function handle_stanza_presence_broadcast(session, stanza) + if session.roster then + local initial_presence = not session.last_presence; + session.last_presence = stanza; + + -- Broadcast presence and probes + local broadcast = st.presence({ from = session.full_jid, type = stanza.attr.type }); + + for child in stanza:childtags() do + broadcast:add_child(child); + end + for contact_jid in pairs(session.roster) do + broadcast.attr.to = contact_jid; + send_to(contact_jid, broadcast); + if initial_presence then + local node, host = jid.split(contact_jid); + if hosts[host] and hosts[host].type == "local" then + local contact = hosts[host].sessions[node] + if contact then + local pres = st.presence { to = session.full_jid }; + for resource, contact_session in pairs(contact.sessions) do + if contact_session.last_presence then + pres.tags = contact_session.last_presence.tags; + pres.attr.from = contact_session.full_jid; + send(pres); + end + end + end + --FIXME: Do we send unavailable if they are offline? + else + probe.attr.to = contact; + send_to(contact, probe); + end + end + end + + -- Probe for our contacts' presence + end +end + +-- Broadcast presence probes to all of a user's contacts +function handle_stanza_presence_probe_broadcast(session, stanza) +end + +-- +function handle_stanza_to_server(stanza) +end + +function handle_stanza_iq_no_to(session, stanza) +end +]] \ No newline at end of file -- cgit v1.2.3