diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/configmanager.lua | 121 | ||||
-rw-r--r-- | core/discomanager.lua | 39 | ||||
-rw-r--r-- | core/modulemanager.lua | 6 | ||||
-rw-r--r-- | core/presencemanager.lua | 121 | ||||
-rw-r--r-- | core/s2smanager.lua | 83 | ||||
-rw-r--r-- | core/servermanager.lua | 2 | ||||
-rw-r--r-- | core/sessionmanager.lua | 42 | ||||
-rw-r--r-- | core/stanza_router.lua | 132 | ||||
-rw-r--r-- | core/xmlhandlers.lua | 19 |
9 files changed, 394 insertions, 171 deletions
diff --git a/core/configmanager.lua b/core/configmanager.lua new file mode 100644 index 00000000..5f5648b9 --- /dev/null +++ b/core/configmanager.lua @@ -0,0 +1,121 @@ + +local _G = _G; +local setmetatable, loadfile, pcall, rawget, rawset, io = + setmetatable, loadfile, pcall, rawget, rawset, io; +module "configmanager" + +local parsers = {}; + +local config = { ["*"] = { core = {} } }; + +local global_config = config["*"]; + +-- When host not found, use global +setmetatable(config, { __index = function () return global_config; end}); +local host_mt = { __index = global_config }; + +-- When key not found in section, check key in global's section +function section_mt(section_name) + return { __index = function (t, k) + local section = rawget(global_config, section_name); + if not section then return nil; end + return section[k]; + end }; +end + +function getconfig() + return config; +end + +function get(host, section, key) + local sec = config[host][section]; + if sec then + return sec[key]; + end + return nil; +end + +function set(host, section, key, value) + if host and section and key then + local hostconfig = rawget(config, host); + if not hostconfig then + hostconfig = rawset(config, host, setmetatable({}, host_mt))[host]; + end + if not rawget(hostconfig, section) then + hostconfig[section] = setmetatable({}, section_mt(section)); + end + hostconfig[section][key] = value; + return true; + end + return false; +end + +function load(filename, format) + format = format or filename:match("%w+$"); + if parsers[format] and parsers[format].load then + local f = io.open(filename); + if f then + local ok, err = parsers[format].load(f:read("*a")); + f:close(); + return ok, err; + end + end + if not format then + return nil, "no parser specified"; + else + return false, "no parser"; + end +end + +function save(filename, format) +end + +function addparser(format, parser) + if format and parser then + parsers[format] = parser; + end +end + +-- Built-in Lua parser +do + local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable; + local setfenv, rawget, tostring = _G.setfenv, _G.rawget, _G.tostring; + parsers.lua = {}; + function parsers.lua.load(data) + local env; + env = setmetatable({ Host = true; host = true; }, { __index = function (t, k) + return rawget(_G, k) or + function (settings_table) + config[__currenthost or "*"][k] = settings_table; + end; + end, + __newindex = function (t, k, v) + set(env.__currenthost or "*", "core", k, v); + end}); + + function env.Host(name) + rawset(env, "__currenthost", name); + set(name or "*", "core", "defined", true); + end + env.host = env.Host; + + local chunk, err = loadstring(data); + + if not chunk then + return nil, err; + end + + setfenv(chunk, env); + + local ok, err = pcall(chunk); + + if not ok then + return nil, err; + end + + return true; + end + +end + +return _M;
\ No newline at end of file diff --git a/core/discomanager.lua b/core/discomanager.lua new file mode 100644 index 00000000..5f7b3c78 --- /dev/null +++ b/core/discomanager.lua @@ -0,0 +1,39 @@ +
+local helper = require "util.discohelper".new();
+local hosts = hosts;
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local usermanager_user_exists = require "core.usermanager".user_exists;
+local rostermanager_is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+
+do
+ helper:addDiscoInfoHandler("*host", function(reply, to, from, node)
+ if hosts[to] then
+ reply:tag("identity", {category="server", type="im", name="lxmppd"}):up();
+ return true;
+ end
+ end);
+ helper:addDiscoInfoHandler("*node", function(reply, to, from, node)
+ local node, host = jid_split(to);
+ if hosts[host] and rostermanager_is_contact_subscribed(node, host, jid_bare(from)) then
+ reply:tag("identity", {category="account", type="registered"}):up();
+ return true;
+ end
+ end);
+end
+
+module "discomanager"
+
+function handle(stanza)
+ return helper:handle(stanza);
+end
+
+function addDiscoItemsHandler(jid, func)
+ return helper:addDiscoItemsHandler(jid, func);
+end
+
+function addDiscoInfoHandler(jid, func)
+ return helper:addDiscoInfoHandler(jid, func);
+end
+
+return _M;
diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 783fea55..d313130c 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -78,7 +78,7 @@ function load(name) local success, ret = pcall(mod); if not success then log("error", "Error initialising module '%s': %s", name or "nil", ret or "nil"); - return nil, err; + return nil, ret; end return true; end @@ -92,8 +92,8 @@ function handle_stanza(origin, stanza) if child then local xmlns = child.attr.xmlns or xmlns; log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns); - local handler = handlers[origin_type][name][xmlns]; - if handler then + local handler = handlers[origin_type][name] and 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 diff --git a/core/presencemanager.lua b/core/presencemanager.lua new file mode 100644 index 00000000..c6619fea --- /dev/null +++ b/core/presencemanager.lua @@ -0,0 +1,121 @@ +
+local log = require "util.logger".init("presencemanager")
+
+local require = require;
+local pairs = pairs;
+
+local st = require "util.stanza";
+local jid_split = require "util.jid".split;
+local hosts = hosts;
+
+local rostermanager = require "core.rostermanager";
+local sessionmanager = require "core.sessionmanager";
+
+module "presencemanager"
+
+function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza)
+ local h = hosts[host];
+ local count = 0;
+ if h and h.type == "local" then
+ local u = h.sessions[user];
+ if u then
+ for k, session in pairs(u.sessions) do
+ local pres = session.presence;
+ if pres then
+ pres.attr.to = jid;
+ pres.attr.from = session.full_jid;
+ core_route_stanza(session, pres);
+ pres.attr.to = nil;
+ pres.attr.from = nil;
+ count = count + 1;
+ end
+ end
+ end
+ end
+ log("info", "broadcasted presence of "..count.." resources from "..user.."@"..host.." to "..jid);
+ return count;
+end
+
+function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+ local node, host = jid_split(from_bare);
+ local st_from, st_to = stanza.attr.from, stanza.attr.to;
+ stanza.attr.from, stanza.attr.to = from_bare, to_bare;
+ log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
+ if stanza.attr.type == "subscribe" then
+ -- 1. route stanza
+ -- 2. roster push (subscription = none, ask = subscribe)
+ if rostermanager.set_contact_pending_out(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare);
+ end -- else file error
+ core_route_stanza(origin, stanza);
+ elseif stanza.attr.type == "unsubscribe" then
+ -- 1. route stanza
+ -- 2. roster push (subscription = none or from)
+ if rostermanager.unsubscribe(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?
+ end -- else file error
+ core_route_stanza(origin, stanza);
+ elseif stanza.attr.type == "subscribed" then
+ -- 1. route stanza
+ -- 2. roster_push ()
+ -- 3. send_presence_of_available_resources
+ if rostermanager.subscribed(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare);
+ end
+ core_route_stanza(origin, stanza);
+ send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza);
+ elseif stanza.attr.type == "unsubscribed" then
+ -- 1. route stanza
+ -- 2. roster push (subscription = none or to)
+ if rostermanager.unsubscribed(node, host, to_bare) then
+ rostermanager.roster_push(node, host, to_bare);
+ end
+ core_route_stanza(origin, stanza);
+ end
+ stanza.attr.from, stanza.attr.to = st_from, st_to;
+end
+
+function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+ local node, host = jid_split(to_bare);
+ local st_from, st_to = stanza.attr.from, stanza.attr.to;
+ stanza.attr.from, stanza.attr.to = from_bare, to_bare;
+ log("debug", "inbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
+ if stanza.attr.type == "probe" then
+ if rostermanager.is_contact_subscribed(node, host, from_bare) then
+ if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
+ -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+ end
+ else
+ core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
+ end
+ elseif stanza.attr.type == "subscribe" then
+ if rostermanager.is_contact_subscribed(node, host, from_bare) then
+ core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
+ -- Sending presence is not clearly stated in the RFC, but it seems appropriate
+ if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
+ -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+ end
+ else
+ if not rostermanager.is_contact_pending_in(node, host, from_bare) then
+ if rostermanager.set_contact_pending_in(node, host, from_bare) then
+ sessionmanager.send_to_available_resources(node, host, stanza);
+ end -- TODO else return error, unable to save
+ end
+ end
+ elseif stanza.attr.type == "unsubscribe" then
+ if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then
+ rostermanager.roster_push(node, host, from_bare);
+ end
+ elseif stanza.attr.type == "subscribed" then
+ if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then
+ rostermanager.roster_push(node, host, from_bare);
+ end
+ elseif stanza.attr.type == "unsubscribed" then
+ if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then
+ rostermanager.roster_push(node, host, from_bare);
+ end
+ end -- discard any other type
+ stanza.attr.from, stanza.attr.to = st_from, st_to;
+end
+
+return _M;
diff --git a/core/s2smanager.lua b/core/s2smanager.lua index c3d9bdb4..6d8f3a00 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -3,7 +3,7 @@ local hosts = hosts; local sessions = sessions; local socket = require "socket"; local format = string.format; -local t_insert = table.insert; +local t_insert, t_sort = table.insert, table.sort; local get_traceback = debug.traceback; local tostring, pairs, ipairs, getmetatable, print, newproxy, error, tonumber = tostring, pairs, ipairs, getmetatable, print, newproxy, error, tonumber; @@ -24,16 +24,19 @@ local md5_hash = require "util.hashes".md5; local dialback_secret = "This is very secret!!! Ha!"; -local srvmap = { ["gmail.com"] = "talk.google.com", ["identi.ca"] = "hampton.controlezvous.ca", ["cdr.se"] = "jabber.cdr.se" }; +local dns = require "net.dns"; module "s2smanager" +local function compare_srv_priorities(a,b) return a.priority < b.priority or a.weight < b.weight; end + function send_to_host(from_host, to_host, data) + if data.name then data = tostring(data); end local host = hosts[from_host].s2sout[to_host]; if host then -- We have a connection to this host already - if host.type == "s2sout_unauthed" then - host.log("debug", "trying to send over unauthed s2sout to "..to_host..", authing it now..."); + if host.type == "s2sout_unauthed" and ((not data.xmlns) or data.xmlns == "jabber:client" or data.xmlns == "jabber:server") then + (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host..", authing it now..."); if not host.notopen and not host.dialback_key then host.log("debug", "dialback had not been initiated"); initiate_dialback(host); @@ -51,7 +54,7 @@ function send_to_host(from_host, to_host, data) -- FIXME if host.from_host ~= from_host then log("error", "WARNING! This might, possibly, be a bug, but it might not..."); - log("error", "We are going to send from %s instead of %s", host.from_host, from_host); + log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host)); end host.sends2s(data); host.log("debug", "stanza sent over "..host.type); @@ -73,8 +76,8 @@ function new_incoming(conn) getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; print("s2s session got collected, now "..open_sessions.." s2s sessions are allocated") end; end open_sessions = open_sessions + 1; - local w = conn.write; - session.sends2s = function (t) w(tostring(t)); end + local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$")); + session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end return session; end @@ -84,30 +87,49 @@ function new_outgoing(from_host, to_host) local cl = connlisteners_get("xmppserver"); local conn, handler = socket.tcp() + + local connect_host, connect_port = to_host, 5269; - --FIXME: Below parameters (ports/ip) are incorrect (use SRV) - to_host = srvmap[to_host] or to_host; + local answer = dns.lookup("_xmpp-server._tcp."..to_host..".", "SRV"); + + if answer then + log("debug", to_host.." has SRV records, handling..."); + local srv_hosts = {}; + host_session.srv_hosts = srv_hosts; + for _, record in ipairs(answer) do + t_insert(srv_hosts, record.srv); + end + t_sort(srv_hosts, compare_srv_priorities); + + local srv_choice = srv_hosts[1]; + if srv_choice then + connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port; + log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port); + end + end conn:settimeout(0); - local success, err = conn:connect(to_host, 5269); - if not success then + local success, err = conn:connect(connect_host, connect_port); + if not success and err ~= "timeout" then log("warn", "s2s connect() failed: %s", err); end - conn = wraptlsclient(cl, conn, to_host, 5269, 0, 1, hosts[from_host].ssl_ctx ); + conn = wraptlsclient(cl, conn, connect_host, connect_port, 0, 1, hosts[from_host].ssl_ctx ); host_session.conn = conn; -- Register this outgoing connection so that xmppserver_listener knows about it -- otherwise it will assume it is a new incoming connection cl.register_outgoing(conn, host_session); + local log; do local conn_name = "s2sout"..tostring(conn):match("[a-f0-9]*$"); - host_session.log = logger_init(conn_name); + log = logger_init(conn_name); + host_session.log = log; end local w = conn.write; - host_session.sends2s = function (t) w(tostring(t)); end + host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end conn.write(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host)); @@ -119,23 +141,31 @@ function streamopened(session, attr) session.version = tonumber(attr.version) or 0; if session.version >= 1.0 and not (attr.to and attr.from) then - print("to: "..tostring(attr.to).." from: "..tostring(attr.from)); - --error(session.to_host.." failed to specify 'to' or 'from' hostname as per RFC"); + --print("to: "..tostring(attr.to).." from: "..tostring(attr.from)); log("warn", (session.to_host or "(unknown)").." failed to specify 'to' or 'from' hostname as per RFC"); end if session.direction == "incoming" then -- Send a reply stream header - for k,v in pairs(attr) do print("", tostring(k), ":::", tostring(v)); end + --for k,v in pairs(attr) do print("", tostring(k), ":::", tostring(v)); end session.to_host = attr.to; session.from_host = attr.from; session.streamid = uuid_gen(); - print(session, session.from_host, "incoming s2s stream opened"); + (session.log or log)("debug", "incoming s2s received <stream:stream>"); send("<?xml version='1.0'?>"); send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host }):top_tag()); + if session.to_host and not hosts[session.to_host] then + -- Attempting to connect to a host we don't serve + session:close("host-unknown"); + return; + end + if session.version >= 1.0 then + send(st.stanza("stream:features") + :tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up()); + end elseif session.direction == "outgoing" then -- If we are just using the connection for verifying dialback keys, we won't try and auth it if not attr.id then error("stream response did not give us a streamid!!!"); end @@ -147,17 +177,6 @@ function streamopened(session, attr) mark_connected(session); end end - --[[ - local features = {}; - modulemanager.fire_event("stream-features-s2s", session, features); - - send("<stream:features>"); - - for _, feature in ipairs(features) do - send(tostring(feature)); - end - - send("</stream:features>");]] session.notopen = nil; end @@ -217,11 +236,13 @@ end function destroy_session(session) (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)); + + -- FIXME: Flush sendq here/report errors to originators + if session.direction == "outgoing" then hosts[session.from_host].s2sout[session.to_host] = nil; end - session.conn = nil; - session.disconnect = nil; + for k in pairs(session) do if k ~= "trace" then session[k] = nil; diff --git a/core/servermanager.lua b/core/servermanager.lua index 99eb4c23..8cbf2f12 100644 --- a/core/servermanager.lua +++ b/core/servermanager.lua @@ -2,7 +2,7 @@ local st = require "util.stanza"; local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas'; -require "modulemanager" +local modulemanager = require "core.modulemanager"; -- Handle stanzas that were addressed to the server (whether they came from c2s, s2s, etc.) function handle_stanza(origin, stanza) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 0d65f6d6..e83b7c23 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -14,6 +14,8 @@ local error = error; local uuid_generate = require "util.uuid".generate; local rm_load_roster = require "core.rostermanager".load_roster; +local st = require "util.stanza"; + local newproxy = newproxy; local getmetatable = getmetatable; @@ -28,13 +30,24 @@ function new_session(conn) getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; print("Session got collected, now "..open_sessions.." sessions are allocated") end; end open_sessions = open_sessions + 1; + log("info", "open sessions now: ".. open_sessions); local w = conn.write; session.send = function (t) w(tostring(t)); end return session; end -function destroy_session(session) +function destroy_session(session, err) (session.log or log)("info", "Destroying session"); + + -- Send unavailable presence + if session.presence then + local pres = st.presence{ type = "unavailable" }; + if (not err) or err == "closed" then err = "connection closed"; end + pres:tag("status"):text("Disconnected: "..err); + session.stanza_dispatch(pres); + end + + -- Remove session/resource from user's session list if session.host and session.username then if session.resource then hosts[session.host].sessions[session.username].sessions[session.resource] = nil; @@ -46,8 +59,7 @@ function destroy_session(session) end end end - session.conn = nil; - session.disconnect = nil; + for k in pairs(session) do if k ~= "trace" then session[k] = nil; @@ -96,21 +108,25 @@ function streamopened(session, attr) session.host = attr.to or error("Client failed to specify destination hostname"); session.version = tonumber(attr.version) or 0; session.streamid = m_random(1000000, 99999999); - print(session, session.host, "Client opened stream"); - send("<?xml version='1.0'?>"); + (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host); + + + send("<?xml version='1.0'?>"); send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host)); - local features = {}; + if not hosts[session.host] then + -- We don't serve this host... + session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)}; + return; + end + + + local features = st.stanza("stream:features"); modulemanager.fire_event("stream-features", session, features); - send("<stream:features>"); + send(features); - for _, feature in ipairs(features) do - send(tostring(feature)); - end - - send("</stream:features>"); - log("info", "Stream opened successfully"); + (session.log or log)("info", "Sent reply <stream:stream> to client"); session.notopen = nil; end diff --git a/core/stanza_router.lua b/core/stanza_router.lua index 2b0e1f4b..2505fca3 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -21,6 +21,9 @@ local s2s_make_authenticated = require "core.s2smanager".make_authenticated; local modules_handle_stanza = require "core.modulemanager".handle_stanza; local component_handle_stanza = require "core.componentmanager".handle_stanza; +local handle_outbound_presence_subscriptions_and_probes = require "core.presencemanager".handle_outbound_presence_subscriptions_and_probes; +local handle_inbound_presence_subscriptions_and_probes = require "core.presencemanager".handle_inbound_presence_subscriptions_and_probes; + local format = string.format; local tostring = tostring; local t_concat = table.concat; @@ -32,7 +35,7 @@ local jid_split = require "util.jid".split; local print = print; function core_process_stanza(origin, stanza) - log("debug", "Received[%s]: %s", origin.type, stanza:pretty_top_tag()) + (origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:pretty_print()) --top_tag()) if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end -- FIXME Hack. This should be removed when we fix namespace handling. -- TODO verify validity of stanza (as well as JID validity) @@ -87,7 +90,7 @@ function core_process_stanza(origin, stanza) elseif hosts[host] and hosts[host].type == "component" then -- directed at a component component_handle_stanza(origin, stanza); elseif origin.type == "c2s" and stanza.name == "presence" and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then - handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare); + handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza); elseif origin.type ~= "c2s" and stanza.name == "iq" and not resource then -- directed at bare JID core_handle_stanza(origin, stanza); else @@ -174,130 +177,23 @@ function core_handle_stanza(origin, stanza) stanza.attr.to = nil; -- reset it else log("warn", "Unhandled c2s presence: %s", tostring(stanza)); - if stanza.attr.type ~= "error" then + if (stanza.attr.xmlns == "jabber:client" or stanza.attr.xmlns == "jabber:server") and stanza.attr.type ~= "error" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? end end else log("warn", "Unhandled c2s stanza: %s", tostring(stanza)); - if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then + if (stanza.attr.xmlns == "jabber:client" or stanza.attr.xmlns == "jabber:server") and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? end end -- TODO handle other stanzas else log("warn", "Unhandled origin: %s", origin.type); - if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then + if (stanza.attr.xmlns == "jabber:client" or stanza.attr.xmlns == "jabber:server") and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then -- s2s stanzas can get here - (origin.sends2s or origin.send)(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? - end - end -end - -function send_presence_of_available_resources(user, host, jid, recipient_session) - local h = hosts[host]; - local count = 0; - if h and h.type == "local" then - local u = h.sessions[user]; - if u then - for k, session in pairs(u.sessions) do - local pres = session.presence; - if pres then - pres.attr.to = jid; - pres.attr.from = session.full_jid; - recipient_session.send(pres); - pres.attr.to = nil; - pres.attr.from = nil; - count = count + 1; - end - end + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? end end - return count; -end - -function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare) - local node, host = jid_split(from_bare); - local st_from, st_to = stanza.attr.from, stanza.attr.to; - stanza.attr.from, stanza.attr.to = from_bare, to_bare; - if stanza.attr.type == "subscribe" then - log("debug", "outbound subscribe from "..from_bare.." for "..to_bare); - -- 1. route stanza - -- 2. roster push (subscription = none, ask = subscribe) - if rostermanager.set_contact_pending_out(node, host, to_bare) then - rostermanager.roster_push(node, host, to_bare); - end -- else file error - core_route_stanza(origin, stanza); - elseif stanza.attr.type == "unsubscribe" then - log("debug", "outbound unsubscribe from "..from_bare.." for "..to_bare); - -- 1. route stanza - -- 2. roster push (subscription = none or from) - if rostermanager.unsubscribe(node, host, to_bare) then - rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed? - end -- else file error - core_route_stanza(origin, stanza); - elseif stanza.attr.type == "subscribed" then - log("debug", "outbound subscribed from "..from_bare.." for "..to_bare); - -- 1. route stanza - -- 2. roster_push () - -- 3. send_presence_of_available_resources - if rostermanager.subscribed(node, host, to_bare) then - rostermanager.roster_push(node, host, to_bare); - core_route_stanza(origin, stanza); - send_presence_of_available_resources(node, host, to_bare, origin); - end - elseif stanza.attr.type == "unsubscribed" then - log("debug", "outbound unsubscribed from "..from_bare.." for "..to_bare); - -- 1. route stanza - -- 2. roster push (subscription = none or to) - if rostermanager.unsubscribed(node, host, to_bare) then - rostermanager.roster_push(node, host, to_bare); - core_route_stanza(origin, stanza); - end - end - stanza.attr.from, stanza.attr.to = st_from, st_to; -end - -function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare) - local node, host = jid_split(to_bare); - local st_from, st_to = stanza.attr.from, stanza.attr.to; - stanza.attr.from, stanza.attr.to = from_bare, to_bare; - if stanza.attr.type == "probe" then - log("debug", "inbound probe from "..from_bare.." for "..to_bare); - if rostermanager.is_contact_subscribed(node, host, from_bare) then - if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then - -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too) - end - else - core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"})); - end - elseif stanza.attr.type == "subscribe" then - log("debug", "inbound subscribe from "..from_bare.." for "..to_bare); - if rostermanager.is_contact_subscribed(node, host, from_bare) then - core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed - else - if not rostermanager.is_contact_pending_in(node, host, from_bare) then - if rostermanager.set_contact_pending_in(node, host, from_bare) then - sessionmanager.send_to_available_resources(node, host, stanza); - end -- TODO else return error, unable to save - end - end - elseif stanza.attr.type == "unsubscribe" then - log("debug", "inbound unsubscribe from "..from_bare.." for "..to_bare); - if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then - rostermanager.roster_push(node, host, from_bare); - end - elseif stanza.attr.type == "subscribed" then - log("debug", "inbound subscribed from "..from_bare.." for "..to_bare); - if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then - rostermanager.roster_push(node, host, from_bare); - end - elseif stanza.attr.type == "unsubscribed" then - log("debug", "inbound unsubscribed from "..from_bare.." for "..to_bare); - if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then - rostermanager.roster_push(node, host, from_bare); - end - end -- discard any other type - stanza.attr.from, stanza.attr.to = st_from, st_to; end function core_route_stanza(origin, stanza) @@ -312,6 +208,10 @@ function core_route_stanza(origin, stanza) local from_node, from_host, from_resource = jid_split(from); local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID + -- Auto-detect origin if not specified + origin = origin or hosts[from_host]; + if not origin then return false; end + if stanza.name == "presence" and (stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable") then resource = nil; end local host_session = hosts[host] @@ -324,7 +224,7 @@ function core_route_stanza(origin, stanza) -- if we get here, resource was not specified or was unavailable if stanza.name == "presence" then if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then - handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare); + handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza); else -- sender is available or unavailable for _, session in pairs(user.sessions) do -- presence broadcast to all user resources. if session.full_jid then -- FIXME should this be just for available resources? Do we need to check subscription? @@ -367,7 +267,7 @@ function core_route_stanza(origin, stanza) if user_exists(node, host) then if stanza.name == "presence" then if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then - handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare); + handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza); else -- TODO send unavailable presence or unsubscribed end @@ -404,8 +304,6 @@ function core_route_stanza(origin, stanza) elseif origin.type == "component" or origin.type == "local" then -- Route via s2s for components and modules log("debug", "Routing outgoing stanza for %s to %s", origin.host, host); - for k,v in pairs(origin) do print("origin:", tostring(k), tostring(v)); end - print(tostring(host), tostring(from_host)) send_s2s(origin.host, host, stanza); else log("warn", "received stanza from unhandled connection type: %s", origin.type); diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua index 3037a848..a97db8e9 100644 --- a/core/xmlhandlers.lua +++ b/core/xmlhandlers.lua @@ -25,7 +25,7 @@ local ns_prefixes = { ["http://www.w3.org/XML/1998/namespace"] = "xml"; } -function init_xmlhandlers(session, streamopened) +function init_xmlhandlers(session, stream_callbacks) local ns_stack = { "" }; local curr_ns = ""; local curr_tag; @@ -36,6 +36,9 @@ function init_xmlhandlers(session, streamopened) local send = session.send; + local cb_streamopened = stream_callbacks.streamopened; + local cb_streamclosed = stream_callbacks.streamclosed; + local stanza function xml_handlers:StartElement(name, attr) if stanza and #chardata > 0 then @@ -66,7 +69,9 @@ function init_xmlhandlers(session, streamopened) if not stanza then --if we are not currently inside a stanza if session.notopen then if name == "stream" then - streamopened(session, attr); + if cb_streamopened then + cb_streamopened(session, attr); + end return; end error("Client failed to open stream successfully"); @@ -75,7 +80,7 @@ function init_xmlhandlers(session, streamopened) error("Client sent invalid top-level stanza"); end - stanza = st.stanza(name, attr); --{ to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns }); + stanza = st.stanza(name, attr); curr_tag = stanza; else -- we are inside a stanza, so add a tag attr.xmlns = nil; @@ -92,15 +97,17 @@ function init_xmlhandlers(session, streamopened) end function xml_handlers:EndElement(name) curr_ns,name = name:match("^(.+)|([%w%-]+)$"); - if (not stanza) or #stanza.last_add < 0 or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then + if (not stanza) or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then if name == "stream" then log("debug", "Stream closed"); - sm_destroy_session(session); + if cb_streamclosed then + cb_streamclosed(session); + end return; elseif name == "error" then error("Stream error: "..tostring(name)..": "..tostring(stanza)); else - error("XML parse error in client stream"); + error("XML parse error in client stream with element: "..name); end end if stanza and #chardata > 0 then |