aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/modulemanager.lua29
-rw-r--r--core/sessionmanager.lua39
-rw-r--r--core/usermanager.lua2
-rw-r--r--core/xmlhandlers.lua20
-rw-r--r--main.lua8
-rw-r--r--net/connhandlers.lua16
-rw-r--r--plugins/mod_legacyauth.lua29
-rw-r--r--plugins/mod_saslauth.lua53
-rw-r--r--util/sasl.lua43
9 files changed, 194 insertions, 45 deletions
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index ed70b75b..ad92b41b 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -23,19 +23,25 @@ function modulehelpers.add_iq_handler(origin_type, xmlns, handler)
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);
+ log("debug", "mod_%s now handles tag 'iq' with query namespace '%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);
+ log("warning", "mod_%s wants to handle tag 'iq' with query namespace '%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)
+function modulehelpers.add_handler(origin_type, tag, handler)
+ handlers[origin_type] = handlers[origin_type] or {};
+ if not handlers[origin_type][tag] then
+ handlers[origin_type][tag]= 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");
load("roster");
end
@@ -58,9 +64,9 @@ function load(name)
end
function handle_stanza(origin, stanza)
- local name, origin_type = stanza.name, origin.type;
+ local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
- if name == "iq" then
+ if name == "iq" and xmlns == "jabber:client" and handlers[origin_type] then
log("debug", "Stanza is an <iq/>");
local child = stanza.tags[1];
if child then
@@ -73,6 +79,13 @@ 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;
+ end
end
log("debug", "Stanza unhandled by any modules");
return false; -- we didn't handle it
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 47f47ba9..e89de262 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -1,6 +1,10 @@
local tostring = tostring;
+local print = print;
+
+local hosts = hosts;
+
local log = require "util.logger".init("sessionmanager");
module "sessionmanager"
@@ -12,9 +16,42 @@ function new_session(conn)
return session;
end
+function destroy_session(session)
+end
+
function send_to_session(session, data)
- log("debug", "Sending...", tostring(data));
+ log("debug", "Sending: %s", tostring(data));
session.conn.write(tostring(data));
end
+function make_authenticated(session, username)
+ session.username = username;
+ session.resource = resource;
+ if session.type == "c2s_unauthed" then
+ session.type = "c2s";
+ end
+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 :)
+ --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
+
+ if not hosts[session.host].sessions[session.username] then
+ hosts[session.host].sessions[session.username] = { sessions = {} };
+ else
+ if hosts[session.host].sessions[session.username].sessions[resource] then
+ -- Resource conflict
+ return false, "conflict";
+ end
+ end
+
+ session.resource = resource;
+ session.full_jid = session.username .. '@' .. session.host .. '/' .. resource;
+ hosts[session.host].sessions[session.username].sessions[resource] = session;
+
+ return true;
+end
+
return _M; \ No newline at end of file
diff --git a/core/usermanager.lua b/core/usermanager.lua
index c98a1918..a67ad368 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -9,3 +9,5 @@ function validate_credentials(host, username, password)
if password == credentials.password then return true; end
return false;
end
+
+return _M; \ No newline at end of file
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
index 96e3f3ac..b6050c5a 100644
--- a/core/xmlhandlers.lua
+++ b/core/xmlhandlers.lua
@@ -27,6 +27,7 @@ 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));
@@ -41,21 +42,28 @@ function init_xmlhandlers(session)
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'>", session.streamid, session.host));
- --send("<stream:features>");
- --send("<mechanism>PLAIN</mechanism>");
+ 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>");
+ send("</stream:features>");
log("info", "core", "Stream opened successfully");
session.notopen = nil;
return;
end
error("Client failed to open stream successfully");
end
- if name ~= "iq" and name ~= "presence" and name ~= "message" then
+ if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
error("Client sent invalid top-level stanza");
end
- stanza = st.stanza(name, { to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns });
+ attr.xmlns = curr_ns;
+ stanza = st.stanza(name, attr); --{ to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns });
curr_tag = stanza;
else
attr.xmlns = curr_ns;
diff --git a/main.lua b/main.lua
index b8a15adf..6712694d 100644
--- a/main.lua
+++ b/main.lua
@@ -21,6 +21,7 @@ require "core.modulemanager"
require "core.usermanager"
require "core.sessionmanager"
require "core.stanza_router"
+require "net.connhandlers"
require "util.stanza"
require "util.jid"
@@ -31,7 +32,6 @@ local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_
local m_random = math.random;
local format = string.format;
local st = stanza;
-local init_xmlhandlers = xmlhandlers.init_xmlhandlers;
------------------------------
@@ -63,8 +63,8 @@ function handler(conn, data, err)
print("Client connected");
session.stanza_dispatch = function (stanza) return core_process_stanza(session, stanza); end
- session.xml_handlers = init_xmlhandlers(session);
- session.parser = lxp.new(session.xml_handlers, ":");
+
+ session.connhandler = connhandlers.new("xmpp-client", session);
function session.disconnect(err)
if session.last_presence and session.last_presence.attr.type ~= "unavailable" then
@@ -82,7 +82,7 @@ function handler(conn, data, err)
end
end
if data then
- session.parser:parse(data);
+ session.connhandler:data(data);
end
--log("info", "core", "Client disconnected, connection closed");
diff --git a/net/connhandlers.lua b/net/connhandlers.lua
new file mode 100644
index 00000000..493f1946
--- /dev/null
+++ b/net/connhandlers.lua
@@ -0,0 +1,16 @@
+
+local lxp = require "lxp"
+local init_xmlhandlers = require "core.xmlhandlers"
+
+module "connhandlers"
+
+
+function new(name, session)
+ if name == "xmpp-client" then
+ local parser = lxp.new(init_xmlhandlers(session), ":");
+ local parse = parser.parse;
+ return { data = function (self, data) return parse(parser, data); end, parser = parser }
+ end
+end
+
+return _M; \ No newline at end of file
diff --git a/plugins/mod_legacyauth.lua b/plugins/mod_legacyauth.lua
index 276842b1..7a205e5b 100644
--- a/plugins/mod_legacyauth.lua
+++ b/plugins/mod_legacyauth.lua
@@ -21,16 +21,27 @@ add_iq_handler("c2s_unauthed", "jabber:iq:auth",
require "core.usermanager"
if usermanager.validate_credentials(session.host, username, password) then
-- Authentication successful!
- session.username = username;
- session.resource = resource;
- session.full_jid = username.."@"..session.host.."/"..session.resource;
- if session.type == "c2s_unauthed" then
- session.type = "c2s";
+ local success, err = sessionmanager.make_authenticated(session, username);
+ if success then
+ success, err = sessionmanager.bind_resource(session, resource);
+ --FIXME: Reply with error
+ if not success then
+ local reply = st.reply(stanza);
+ reply.attr.type = "error";
+ if err == "conflict" then
+ reply:tag("error", { code = "409", type = "cancel" })
+ :tag("conflict", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" });
+ elseif err == "constraint" then
+ reply:tag("error", { code = "409", type = "cancel" })
+ :tag("already-bound", { xmlns = "x-lxmppd:extensions:legacyauth" });
+ elseif err == "auth" then
+ reply:tag("error", { code = "401", type = "auth" })
+ :tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" });
+ end
+ dispatch_stanza(reply);
+ return true;
+ end
end
- if not hosts[session.host].sessions[username] then
- hosts[session.host].sessions[username] = { sessions = {} };
- end
- hosts[session.host].sessions[username].sessions[resource] = session;
send(session, st.reply(stanza));
return true;
else
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
new file mode 100644
index 00000000..dc6f3645
--- /dev/null
+++ b/plugins/mod_saslauth.lua
@@ -0,0 +1,53 @@
+
+local st = require "util.stanza";
+local send = require "core.sessionmanager".send_to_session;
+
+local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
+local t_concat = table.concat;
+local tostring = tostring;
+
+local log = require "util.logger".init("mod_saslauth");
+
+local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
+
+local new_connhandler = require "net.connhandlers".new;
+local new_sasl = require "util.sasl".new;
+
+add_handler("c2s_unauthed", "auth",
+ function (session, stanza)
+ if not session.sasl_handler then
+ session.sasl_handler = new_sasl(stanza.attr.mechanism,
+ function (username, password)
+ -- onAuth
+ require "core.usermanager"
+ if usermanager_validate_credentials(session.host, username, password) then
+ return true;
+ end
+ return false;
+ end,
+ function (username)
+ -- onSuccess
+ local success, err = sessionmanager.make_authenticated(session, username);
+ if not success then
+ sessionmanager.destroy_session(session);
+ end
+ session.sasl_handler = nil;
+ session.connhandler = new_connhandler("xmpp-client", session);
+ session.notopen = true;
+ end,
+ function (reason)
+ -- onFail
+ log("debug", "SASL failure, reason: %s", reason);
+ end,
+ function (stanza)
+ -- onWrite
+ log("debug", "SASL writes: %s", tostring(stanza));
+ send(session, stanza);
+ end
+ );
+ session.sasl_handler:feed(stanza);
+ else
+ error("Client tried to negotiate SASL again", 0);
+ end
+
+ end); \ No newline at end of file
diff --git a/util/sasl.lua b/util/sasl.lua
index 0d7740c8..515bcf8a 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -1,34 +1,43 @@
-require "base64"
-sasl = {}
-function sasl:new_plain(onAuth, onSuccess, onFail, onWrite)
+local base64 = require "base64"
+local log = require "util.logger".init("sasl");
+local tostring = tostring;
+local st = require "util.stanza";
+local s_match = string.match;
+module "sasl"
+
+
+local function new_plain(onAuth, onSuccess, onFail, onWrite)
local object = { mechanism = "PLAIN", onAuth = onAuth, onSuccess = onSuccess, onFail = onFail,
onWrite = onWrite}
- local challenge = base64.encode("");
- onWrite(stanza.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge))
+ --local challenge = base64.encode("");
+ --onWrite(st.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge))
object.feed = function(self, stanza)
- if (stanza.name ~= "response") then self.onFail() end
- if (stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl") then self.onFail() end
- local response = base64.decode(stanza.tag[1])
- local authorization = string.match(response, "([^&\0]+)")
- local authentication = string.match(response, "\0([^&\0]+)\0")
- local password = string.match(response, "\0[^&\0]+\0([^&\0]+)")
+ if stanza.name ~= "response" and stanza.name ~= "auth" then self.onFail("invalid-stanza-tag") end
+ if stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl" then self.onFail("invalid-stanza-namespace") end
+ local response = base64.decode(stanza[1])
+ 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
- self.onWrite(stanza.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}))
- self.onSuccess()
+ self.onWrite(st.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}))
+ self.onSuccess(authentication)
else
- self.onWrite(stanza.stanza("failure", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):tag("temporary-auth-failure"));
+ self.onWrite(st.stanza("failure", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):tag("temporary-auth-failure"));
end
end
return object
end
-function sasl:new(mechanism, onAuth, onSuccess, onFail, onWrite)
+
+function new(mechanism, onAuth, onSuccess, onFail, onWrite)
local object
if mechanism == "PLAIN" then object = new_plain(onAuth, onSuccess, onFail, onWrite)
- else onFail()
+ else
+ log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
+ onFail("unsupported-mechanism")
end
return object
end
-module "sasl"
+return _M; \ No newline at end of file