aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/modulemanager.lua42
-rw-r--r--core/servermanager.lua14
-rw-r--r--core/sessionmanager.lua72
-rw-r--r--core/usermanager.lua2
-rw-r--r--core/xmlhandlers.lua32
-rw-r--r--main.lua12
-rw-r--r--plugins/mod_legacyauth.lua2
-rw-r--r--plugins/mod_saslauth.lua66
-rw-r--r--util/import.lua13
-rw-r--r--util/logger.lua2
-rw-r--r--util/sasl.lua2
-rw-r--r--util/uuid.lua9
12 files changed, 224 insertions, 44 deletions
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index ad92b41b..6d803a26 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -4,6 +4,7 @@ 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 t_insert = table.insert;
local type = type;
local tostring, print = tostring, print;
@@ -18,6 +19,7 @@ local handlers = {};
local modulehelpers = setmetatable({}, { __index = _G });
function modulehelpers.add_iq_handler(origin_type, xmlns, handler)
+ if not (origin_type and handler and xmlns) then return false; end
handlers[origin_type] = handlers[origin_type] or {};
handlers[origin_type].iq = handlers[origin_type].iq or {};
if not handlers[origin_type].iq[xmlns] then
@@ -29,17 +31,19 @@ function modulehelpers.add_iq_handler(origin_type, xmlns, handler)
end
end
-function modulehelpers.add_handler(origin_type, tag, handler)
+function modulehelpers.add_handler(origin_type, tag, xmlns, handler)
+ if not (origin_type and tag and xmlns and handler) then return false; end
handlers[origin_type] = handlers[origin_type] or {};
if not handlers[origin_type][tag] then
- handlers[origin_type][tag]= handler;
+ handlers[origin_type][tag] = handlers[origin_type][tag] or {};
+ handlers[origin_type][tag][xmlns]= handler;
handler_info[handler] = getfenv(2).module;
log("debug", "mod_%s now handles tag '%s'", getfenv(2).module.name, tag);
elseif handler_info[handlers[origin_type][tag]] then
log("warning", "mod_%s wants to handle tag '%s' but mod_%s already handles that", getfenv(2).module.name, tag, handler_info[handlers[origin_type][tag]].module.name);
end
end
-
+
function loadall()
load("saslauth");
load("legacyauth");
@@ -79,14 +83,38 @@ function handle_stanza(origin, stanza)
end
end
- --FIXME: All iq's must be replied to, here we should return service-unavailable I think
elseif handlers[origin_type] then
local handler = handlers[origin_type][name];
if handler then
- log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
- return handler(origin, stanza) or true;
+ handler = handler[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");
+ log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns);
return false; -- we didn't handle it
end
+
+do
+ local event_handlers = {};
+
+ function modulehelpers.add_event_hook(name, handler)
+ if not event_handlers[name] then
+ event_handlers[name] = {};
+ end
+ t_insert(event_handlers[name] , handler);
+ end
+
+ function fire_event(name, ...)
+ local event_handlers = event_handlers[name];
+ if event_handlers then
+ for name, handler in ipairs(event_handlers) do
+ handler(...);
+ end
+ end
+ end
+end
+
+return _M;
diff --git a/core/servermanager.lua b/core/servermanager.lua
index 02ccab53..c1e075db 100644
--- a/core/servermanager.lua
+++ b/core/servermanager.lua
@@ -1,8 +1,20 @@
+local st = require "util.stanza";
+local send = require "core.sessionmanager".send_to_session;
+local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
+
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);
+ if not modulemanager.handle_stanza(origin, stanza) then
+ if stanza.name == "iq" then
+ local reply = st.reply(stanza);
+ reply.attr.type = "error";
+ reply:tag("error", { type = "cancel" })
+ :tag("service-unavailable", { xmlns = xmlns_stanzas });
+ send(origin, reply);
+ end
+ end
end
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index e89de262..3366120e 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -1,22 +1,62 @@
-local tostring = tostring;
-
-local print = print;
+local tonumber, tostring = tonumber, tostring;
+local ipairs, pairs, print= ipairs, pairs, print;
+local collectgarbage = collectgarbage;
+local m_random = import("math", "random");
+local format = import("string", "format");
local hosts = hosts;
+local sessions = sessions;
+local modulemanager = require "core.modulemanager";
local log = require "util.logger".init("sessionmanager");
+local error = error;
+local uuid_generate = require "util.uuid".uuid_generate;
+
+local newproxy = newproxy;
+local getmetatable = getmetatable;
module "sessionmanager"
function new_session(conn)
local session = { conn = conn, notopen = true, priority = 0, type = "c2s_unauthed" };
+ if true then
+ session.trace = newproxy(true);
+ getmetatable(session.trace).__gc = function () print("Session got collected") end;
+ end
local w = conn.write;
session.send = function (t) w(tostring(t)); end
return session;
end
function destroy_session(session)
+ if not (session and session.disconnect) then return; end
+ log("debug", "Destroying session...");
+ session.disconnect();
+ if session.username then
+ if session.resource then
+ hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
+ end
+ local nomore = true;
+ for res, ssn in pairs(hosts[session.host].sessions[session.username]) do
+ nomore = false;
+ end
+ if nomore then
+ hosts[session.host].sessions[session.username] = nil;
+ end
+ end
+ session.conn = nil;
+ session.disconnect = nil;
+ for k in pairs(session) do
+ if k ~= "trace" then
+ session[k] = nil;
+ end
+ end
+ collectgarbage("collect");
+ collectgarbage("collect");
+ collectgarbage("collect");
+ collectgarbage("collect");
+ collectgarbage("collect");
end
function send_to_session(session, data)
@@ -30,12 +70,13 @@ function make_authenticated(session, username)
if session.type == "c2s_unauthed" then
session.type = "c2s";
end
+ return true;
end
function bind_resource(session, resource)
if not session.username then return false, "auth"; end
if session.resource then return false, "constraint"; end -- We don't support binding multiple resources
- resource = resource or math.random(100000, 99999999); -- FIXME: Clearly we have issues :)
+ resource = resource or uuid_generate();
--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
if not hosts[session.host].sessions[session.username] then
@@ -54,4 +95,27 @@ function bind_resource(session, resource)
return true;
end
+function streamopened(session, attr)
+ local send = session.send;
+ 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'?>");
+ 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 = {};
+ modulemanager.fire_event("stream-features", session, features);
+
+ send("<stream:features>");
+
+ for _, feature in ipairs(features) do
+ send_to_session(session, tostring(feature));
+ end
+
+ send("</stream:features>");
+ log("info", "Stream opened successfully");
+ session.notopen = nil;
+end
+
return _M; \ No newline at end of file
diff --git a/core/usermanager.lua b/core/usermanager.lua
index a67ad368..0f303b24 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -1,10 +1,12 @@
require "util.datamanager"
local datamanager = datamanager;
+local log = require "util.logger".init("usermanager");
module "usermanager"
function validate_credentials(host, username, password)
+ log("debug", "User '%s' is being validated", username);
local credentials = datamanager.load(username, host, "accounts") or {};
if password == credentials.password then return true; end
return false;
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
index b6050c5a..ebc8f91d 100644
--- a/core/xmlhandlers.lua
+++ b/core/xmlhandlers.lua
@@ -1,4 +1,5 @@
+local sessionmanager_streamopened = require "core.sessionmanager".streamopened;
require "util.stanza"
local st = stanza;
@@ -9,6 +10,7 @@ local t_insert = table.insert;
local t_remove = table.remove;
local t_concat = table.concat;
local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
+local sm_destroy_session = import("core.sessionmanager", "destroy_session");
local error = error;
@@ -27,7 +29,6 @@ function init_xmlhandlers(session)
local stanza
function xml_handlers:StartElement(name, attr)
- log("info", "xmlhandlers", "Start element: " .. name);
if stanza and #chardata > 0 then
-- We have some character data in the buffer
stanza:text(t_concat(chardata));
@@ -37,24 +38,7 @@ function init_xmlhandlers(session)
if not stanza then
if session.notopen then
if name == "stream" then
- session.host = attr.to or error("Client failed to specify destination hostname");
- session.version = attr.version or 0;
- session.streamid = m_random(1000000, 99999999);
- print(session, session.host, "Client opened stream");
- 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));
- send("<stream:features>");
- if not session.username then
- send("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
- send("<mechanism>PLAIN</mechanism>");
- send("</mechanisms>");
- else
- send("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>");
- end
- --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]]
- send("</stream:features>");
- log("info", "core", "Stream opened successfully");
- session.notopen = nil;
+ sessionmanager_streamopened(session, attr);
return;
end
error("Client failed to open stream successfully");
@@ -77,7 +61,15 @@ function init_xmlhandlers(session)
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 error("XML parse error in client stream"); end
+ if (not stanza) or #stanza.last_add < 0 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);
+ return;
+ else
+ error("XML parse error in client stream");
+ end
+ end
if stanza and #chardata > 0 then
-- We have some character data in the buffer
stanza:text(t_concat(chardata));
diff --git a/main.lua b/main.lua
index 6712694d..821b3273 100644
--- a/main.lua
+++ b/main.lua
@@ -13,6 +13,7 @@ dofile "lxmppd.cfg"
sessions = {};
+require "util.import"
require "core.stanza_dispatch"
require "core.xmlhandlers"
require "core.rostermanager"
@@ -24,6 +25,7 @@ require "core.stanza_router"
require "net.connhandlers"
require "util.stanza"
require "util.jid"
+
-- Locals for faster access --
local t_insert = table.insert;
@@ -31,6 +33,7 @@ local t_concat = table.concat;
local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
local m_random = math.random;
local format = string.format;
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
local st = stanza;
------------------------------
@@ -46,7 +49,7 @@ function handler(conn, data, err)
local session = sessions[conn];
if not session then
- sessions[conn] = sessionmanager.new_session(conn);
+ sessions[conn] = sm_new_session(conn);
session = sessions[conn];
-- Logging functions --
@@ -73,11 +76,8 @@ function handler(conn, data, err)
pres:tag("status"):text("Disconnected: "..err);
session.stanza_dispatch(pres);
end
- if session.username then
- hosts[session.host].sessions[session.username] = nil;
- end
session = nil;
- print("Disconnected: "..err);
+ print("Disconnected: "..tostring(err));
collectgarbage("collect");
end
end
@@ -89,7 +89,7 @@ function handler(conn, data, err)
end
function disconnect(conn, err)
- sessions[conn].disconnect(err);
+ sm_destroy_session(sessions[conn]);
sessions[conn] = nil;
end
diff --git a/plugins/mod_legacyauth.lua b/plugins/mod_legacyauth.lua
index 7a205e5b..8ac10bd4 100644
--- a/plugins/mod_legacyauth.lua
+++ b/plugins/mod_legacyauth.lua
@@ -38,7 +38,7 @@ add_iq_handler("c2s_unauthed", "jabber:iq:auth",
reply:tag("error", { code = "401", type = "auth" })
:tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" });
end
- dispatch_stanza(reply);
+ send(session, reply);
return true;
end
end
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index dc6f3645..8ef1e09d 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -1,19 +1,22 @@
local st = require "util.stanza";
local send = require "core.sessionmanager".send_to_session;
+local sm_bind_resource = require "core.sessionmanager".bind_resource;
local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
-local t_concat = table.concat;
+local t_concat, t_insert = table.concat, table.insert;
local tostring = tostring;
local log = require "util.logger".init("mod_saslauth");
local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
+local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
+local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
local new_connhandler = require "net.connhandlers".new;
local new_sasl = require "util.sasl".new;
-add_handler("c2s_unauthed", "auth",
+add_handler("c2s_unauthed", "auth", xmlns_sasl,
function (session, stanza)
if not session.sasl_handler then
session.sasl_handler = new_sasl(stanza.attr.mechanism,
@@ -30,6 +33,7 @@ add_handler("c2s_unauthed", "auth",
local success, err = sessionmanager.make_authenticated(session, username);
if not success then
sessionmanager.destroy_session(session);
+ return;
end
session.sasl_handler = nil;
session.connhandler = new_connhandler("xmpp-client", session);
@@ -50,4 +54,60 @@ add_handler("c2s_unauthed", "auth",
error("Client tried to negotiate SASL again", 0);
end
- end); \ No newline at end of file
+ end);
+
+add_event_hook("stream-features",
+ function (session, features)
+ if not session.username then
+ t_insert(features, "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
+ t_insert(features, "<mechanism>PLAIN</mechanism>");
+ t_insert(features, "</mechanisms>");
+ else
+ t_insert(features, "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>");
+ t_insert(features, "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
+ end
+ --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]]
+ end);
+
+add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind",
+ function (session, stanza)
+ log("debug", "Client tried to bind to a resource");
+ local resource;
+ if stanza.attr.type == "set" then
+ local bind = stanza.tags[1];
+
+ if bind and bind.attr.xmlns == xmlns_bind then
+ resource = bind:child_with_name("resource");
+ if resource then
+ resource = resource[1];
+ end
+ end
+ end
+ local success, err = sm_bind_resource(session, resource);
+ if not success then
+ local reply = st.reply(stanza);
+ reply.attr.type = "error";
+ if err == "conflict" then
+ reply:tag("error", { type = "modify" })
+ :tag("conflict", { xmlns = xmlns_stanzas });
+ elseif err == "constraint" then
+ reply:tag("error", { type = "cancel" })
+ :tag("resource-constraint", { xmlns = xmlns_stanzas });
+ elseif err == "auth" then
+ reply:tag("error", { type = "cancel" })
+ :tag("not-allowed", { xmlns = xmlns_stanzas });
+ end
+ send(session, reply);
+ else
+ local reply = st.reply(stanza);
+ reply:tag("bind", { xmlns = xmlns_bind})
+ :tag("jid"):text(session.full_jid);
+ send(session, reply);
+ end
+ end);
+
+add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session",
+ function (session, stanza)
+ log("debug", "Client tried to bind to a resource");
+ send(session, st.reply(stanza));
+ end);
diff --git a/util/import.lua b/util/import.lua
new file mode 100644
index 00000000..6aab0d45
--- /dev/null
+++ b/util/import.lua
@@ -0,0 +1,13 @@
+
+local t_insert = table.insert;
+function import(module, ...)
+ local m = package.loaded[module] or require(module);
+ if type(m) == "table" and ... then
+ local ret = {};
+ for _, f in ipairs{...} do
+ t_insert(ret, m[f]);
+ end
+ return unpack(ret);
+ end
+ return m;
+end
diff --git a/util/logger.lua b/util/logger.lua
index 3d672e94..623ceb67 100644
--- a/util/logger.lua
+++ b/util/logger.lua
@@ -9,7 +9,7 @@ function init(name)
name = nil; -- While this line is not commented, will automatically fill in file/line number info
return function (level, message, ...)
if not name then
- local inf = debug.getinfo(2, 'Snl');
+ local inf = debug.getinfo(3, 'Snl');
level = level .. ","..tostring(inf.short_src):match("[^/]*$")..":"..inf.currentline;
end
if ... then
diff --git a/util/sasl.lua b/util/sasl.lua
index 515bcf8a..dbd6326a 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -19,7 +19,7 @@ local function new_plain(onAuth, onSuccess, onFail, onWrite)
local authorization = s_match(response, "([^&%z]+)")
local authentication = s_match(response, "%z([^&%z]+)%z")
local password = s_match(response, "%z[^&%z]+%z([^&%z]+)")
- if self.onAuth(authorization, password) == true then
+ if self.onAuth(authentication, password) == true then
self.onWrite(st.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}))
self.onSuccess(authentication)
else
diff --git a/util/uuid.lua b/util/uuid.lua
new file mode 100644
index 00000000..489522aa
--- /dev/null
+++ b/util/uuid.lua
@@ -0,0 +1,9 @@
+
+local m_random = math.random;
+module "uuid"
+
+function uuid_generate()
+ return m_random(0, 99999999);
+end
+
+return _M; \ No newline at end of file