aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/configmanager.lua33
-rw-r--r--core/eventmanager.lua16
-rw-r--r--core/rostermanager.lua12
-rw-r--r--core/s2smanager.lua31
-rw-r--r--core/sessionmanager.lua15
-rw-r--r--core/usermanager.lua144
-rw-r--r--net/multiplex_listener.lua4
-rw-r--r--net/xmppclient_listener.lua90
-rw-r--r--net/xmppserver_listener.lua86
-rw-r--r--plugins/mod_compression.lua62
-rw-r--r--plugins/mod_iq.lua11
-rw-r--r--plugins/mod_offline.lua56
-rw-r--r--plugins/mod_pep.lua44
-rw-r--r--plugins/mod_posix.lua22
-rw-r--r--plugins/muc/muc.lib.lua62
-rw-r--r--plugins/storage/ejabberdstore.lib.lua190
-rw-r--r--plugins/storage/mod_storage.lua83
-rw-r--r--plugins/storage/sqlbasic.lib.lua97
-rw-r--r--plugins/storage/xep227store.lib.lua168
-rw-r--r--plugins/storage/xmlparse.lib.lua56
-rwxr-xr-xprosody19
-rwxr-xr-xprosodyctl59
-rw-r--r--util/filters.lua68
-rw-r--r--util/sasl/anonymous.lua10
-rw-r--r--util/xmppstream.lua168
25 files changed, 1338 insertions, 268 deletions
diff --git a/core/configmanager.lua b/core/configmanager.lua
index 54fb0a9a..9b03e1c7 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -30,10 +30,11 @@ 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 };
+ local section = rawget(global_config, section_name);
+ if not section then return nil; end
+ return section[k];
+ end
+ };
end
function getconfig()
@@ -112,16 +113,20 @@ do
function parsers.lua.load(data, filename)
local env;
-- The ' = true' are needed so as not to set off __newindex when we assign the functions below
- env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true,
- Include = true, include = true, RunScript = dofile }, { __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});
+ env = setmetatable({
+ Host = true, host = true, VirtualHost = true,
+ Component = true, component = true,
+ Include = true, include = true, RunScript = dofile }, {
+ __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
+ });
rawset(env, "__currenthost", "*") -- Default is global
function env.VirtualHost(name)
diff --git a/core/eventmanager.lua b/core/eventmanager.lua
index 0e766c30..1f69c8e1 100644
--- a/core/eventmanager.lua
+++ b/core/eventmanager.lua
@@ -10,24 +10,18 @@
local t_insert = table.insert;
local ipairs = ipairs;
+local events = _G.prosody.events;
+
module "eventmanager"
local event_handlers = {};
function add_event_hook(name, handler)
- if not event_handlers[name] then
- event_handlers[name] = {};
- end
- t_insert(event_handlers[name] , handler);
+ return events.add_handler(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
+ return events.fire_event(name, ...);
end
-return _M; \ No newline at end of file
+return _M;
diff --git a/core/rostermanager.lua b/core/rostermanager.lua
index 506cf205..59ba6579 100644
--- a/core/rostermanager.lua
+++ b/core/rostermanager.lua
@@ -190,7 +190,19 @@ function process_inbound_unsubscribe(username, host, jid)
end
end
+local function _get_online_roster_subscription(jidA, jidB)
+ local user = bare_sessions[jidA];
+ local item = user and (user.roster[jidB] or { subscription = "none" });
+ return item and item.subscription;
+end
function is_contact_subscribed(username, host, jid)
+ do
+ local selfjid = username.."@"..host;
+ local subscription = _get_online_roster_subscription(selfjid, jid);
+ if subscription then return (subscription == "both" or subscription == "from"); end
+ local subscription = _get_online_roster_subscription(jid, selfjid);
+ if subscription then return (subscription == "both" or subscription == "to"); end
+ end
local roster, err = load_roster(username, host);
local item = roster[jid];
return item and (item.subscription == "from" or item.subscription == "both"), err;
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index ca87670a..7d39240f 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -23,6 +23,7 @@ local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local connlisteners_get = require "net.connlisteners".get;
+local initialize_filters = require "util.filters".initialize;
local wrapclient = require "net.server".wrapclient;
local modulemanager = require "core.modulemanager";
local st = require "stanza";
@@ -137,7 +138,19 @@ function new_incoming(conn)
open_sessions = open_sessions + 1;
local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
session.log = log;
- session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end
+ local filter = initialize_filters(session);
+ session.sends2s = function (t)
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
+ return w(conn, t);
+ end
+ end
+ end
incoming_s2s[session] = true;
add_task(connect_timeout, function ()
if session.conn ~= conn or
@@ -166,6 +179,8 @@ function new_outgoing(from_host, to_host, connect)
host_session.log = log;
end
+ initialize_filters(host_session);
+
if connect ~= false then
-- Kick the connection attempting machine into life
attempt_connection(host_session);
@@ -332,8 +347,20 @@ function make_connect(host_session, connect_host, connect_port)
-- otherwise it will assume it is a new incoming connection
cl.register_outgoing(conn, host_session);
+ local filter = initialize_filters(host_session);
local w, log = conn.write, host_session.log;
- host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end
+ host_session.sends2s = function (t)
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
+ return w(conn, tostring(t));
+ end
+ end
+ end
host_session:open_stream(from_host, to_host);
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index fd6ed96e..ac07a793 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -26,6 +26,7 @@ local config_get = require "core.configmanager".get;
local nameprep = require "util.encodings".stringprep.nameprep;
local resourceprep = require "util.encodings".stringprep.resourceprep;
+local initialize_filters = require "util.filters".initialize;
local fire_event = require "core.eventmanager".fire_event;
local add_task = require "util.timer".add_task;
local gettime = require "socket".gettime;
@@ -49,8 +50,20 @@ function new_session(conn)
end
open_sessions = open_sessions + 1;
log("debug", "open sessions now: ".. open_sessions);
+
+ local filter = initialize_filters(session);
local w = conn.write;
- session.send = function (t) w(conn, tostring(t)); end
+ session.send = function (t)
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ return w(conn, t);
+ end
+ end
+ end
session.ip = conn:ip();
local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 698d2f10..07097dc1 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -18,83 +18,133 @@ local hosts = hosts;
local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
+local prosody = _G.prosody;
+
module "usermanager"
-local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
+local new_default_provider;
-function validate_credentials(host, username, password, method)
- log("debug", "User '%s' is being validated", username);
- if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
- local credentials = datamanager.load(username, host, "accounts") or {};
+local function host_handler(host)
+ local host_session = hosts[host];
+ host_session.events.add_handler("item-added/auth-provider", function (provider)
+ if config.get(host, "core", "authentication") == provider.name then
+ host_session.users = provider;
+ end
+ end);
+ host_session.events.add_handler("item-removed/auth-provider", function (provider)
+ if host_session.users == provider then
+ host_session.users = new_default_provider(host);
+ end
+ end);
+ host_session.users = new_default_provider(host); -- Start with the default usermanager provider
+end
+prosody.events.add_handler("host-activated", host_handler);
+prosody.events.add_handler("component-activated", host_handler);
+
+local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
- if method == nil then method = "PLAIN"; end
- if method == "PLAIN" and credentials.password then -- PLAIN, do directly
+function new_default_provider(host)
+ local provider = { name = "default" };
+
+ function provider:test_password(username, password)
+ if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
+ local credentials = datamanager.load(username, host, "accounts") or {};
+
if password == credentials.password then
return true;
else
return nil, "Auth failed. Invalid username or password.";
end
- end
- -- must do md5
- -- make credentials md5
- local pwd = credentials.password;
- if not pwd then pwd = credentials.md5; else pwd = hashes.md5(pwd, true); end
- -- make password md5
- if method == "PLAIN" then
- password = hashes.md5(password or "", true);
- elseif method ~= "DIGEST-MD5" then
- return nil, "Unsupported auth method";
end
- -- compare
- if password == pwd then
- return true;
- else
- return nil, "Auth failed. Invalid username or password.";
+
+ function provider:get_password(username)
+ if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+ return (datamanager.load(username, host, "accounts") or {}).password;
+ end
+
+ function provider:set_password(username, password)
+ if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+ local account = datamanager.load(username, host, "accounts");
+ if account then
+ account.password = password;
+ return datamanager.store(username, host, "accounts", account);
+ end
+ return nil, "Account not available.";
+ end
+
+ function provider:user_exists(username)
+ if not(require_provisioning) and is_cyrus(host) then return true; end
+ local account, err = datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
+ return (account or err) ~= nil; -- FIXME also check for empty credentials
+ end
+
+ function provider:create_user(username, password)
+ if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
+ return datamanager.store(username, host, "accounts", {password = password});
+ end
+
+ function provider:get_supported_methods()
+ return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
end
+
+ function provider:is_admin(jid)
+ local admins = config.get(host, "core", "admins");
+ if admins ~= config.get("*", "core", "admins") then
+ if type(admins) == "table" then
+ jid = jid_bare(jid);
+ for _,admin in ipairs(admins) do
+ if admin == jid then return true; end
+ end
+ elseif admins then
+ log("error", "Option 'admins' for host '%s' is not a table", host);
+ end
+ end
+ return is_admin(jid); -- Test whether it's a global admin instead
+ end
+ return provider;
+end
+
+function validate_credentials(host, username, password, method)
+ return hosts[host].users:test_password(username, password);
end
function get_password(username, host)
- if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
- return (datamanager.load(username, host, "accounts") or {}).password
+ return hosts[host].users:get_password(username);
end
+
function set_password(username, host, password)
- if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
- local account = datamanager.load(username, host, "accounts");
- if account then
- account.password = password;
- return datamanager.store(username, host, "accounts", account);
- end
- return nil, "Account not available.";
+ return hosts[host].users:set_password(username, password);
end
function user_exists(username, host)
- if not(require_provisioning) and is_cyrus(host) then return true; end
- local account, err = datamanager.load(username, host, "accounts");
- return (account or err) ~= nil; -- FIXME also check for empty credentials
+ return hosts[host].users:user_exists(username);
end
function create_user(username, password, host)
- if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
- return datamanager.store(username, host, "accounts", {password = password});
+ return hosts[host].users:create_user(username, password);
end
function get_supported_methods(host)
- return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
+ return hosts[host].users:get_supported_methods();
end
function is_admin(jid, host)
- host = host or "*";
- local admins = config.get(host, "core", "admins");
- if host ~= "*" and admins == config.get("*", "core", "admins") then
+ if host and host ~= "*" then
+ return hosts[host].users:is_admin(jid);
+ else -- Test only whether this JID is a global admin
+ local admins = config.get("*", "core", "admins");
+ if type(admins) == "table" then
+ jid = jid_bare(jid);
+ for _,admin in ipairs(admins) do
+ if admin == jid then return true; end
+ end
+ elseif admins then
+ log("error", "Option 'admins' for host '%s' is not a table", host);
+ end
return nil;
end
- if type(admins) == "table" then
- jid = jid_bare(jid);
- for _,admin in ipairs(admins) do
- if admin == jid then return true; end
- end
- elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
- return nil;
end
+_M.new_default_provider = new_default_provider;
+
return _M;
diff --git a/net/multiplex_listener.lua b/net/multiplex_listener.lua
index bf193ad8..b515ccce 100644
--- a/net/multiplex_listener.lua
+++ b/net/multiplex_listener.lua
@@ -19,6 +19,8 @@ function server.onincoming(conn, data)
if buf:match("^[a-zA-Z]") then
local listener = httpserver_listener;
conn:setlistener(listener);
+ local onconnect = listener.onconnect;
+ if onconnect then onconnect(conn) end
listener.onincoming(conn, buf);
elseif buf:match(">") then
local listener;
@@ -31,6 +33,8 @@ function server.onincoming(conn, data)
listener = xmppclient_listener;
end
conn:setlistener(listener);
+ local onconnect = listener.onconnect;
+ if onconnect then onconnect(conn) end
listener.onincoming(conn, buf);
elseif #buf > 1024 then
conn:close();
diff --git a/net/xmppclient_listener.lua b/net/xmppclient_listener.lua
index 94daa2b2..623a98c8 100644
--- a/net/xmppclient_listener.lua
+++ b/net/xmppclient_listener.lua
@@ -11,7 +11,7 @@
local logger = require "logger";
local log = logger.init("xmppclient_listener");
local lxp = require "lxp"
-local init_xmlhandlers = require "core.xmlhandlers"
+local new_xmpp_stream = require "util.xmppstream".new;
local sm_new_session = require "core.sessionmanager".new_session;
local connlisteners_register = require "net.connlisteners".register;
@@ -72,23 +72,6 @@ local xmppclient = { default_port = 5222, default_mode = "*a" };
-- These are session methods --
-local function session_reset_stream(session)
- -- Reset stream
- local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
- session.parser = parser;
-
- session.notopen = true;
-
- function session.data(conn, data)
- local ok, err = parser:parse(data);
- if ok then return; end
- log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
- session:close("xml-not-well-formed");
- end
-
- return true;
-end
-
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
local function session_close(session, reason)
@@ -128,32 +111,57 @@ end
-- End of session methods --
-function xmppclient.onincoming(conn, data)
- local session = sessions[conn];
- if not session then
- session = sm_new_session(conn);
- sessions[conn] = session;
-
- session.log("info", "Client connected");
-
- -- Client is using legacy SSL (otherwise mod_tls sets this flag)
- if conn:ssl() then
- session.secure = true;
+function xmppclient.onconnect(conn)
+ local session = sm_new_session(conn);
+ sessions[conn] = session;
+
+ session.log("info", "Client connected");
+
+ -- Client is using legacy SSL (otherwise mod_tls sets this flag)
+ if conn:ssl() then
+ session.secure = true;
+ end
+
+ if opt_keepalives ~= nil then
+ conn:setoption("keepalive", opt_keepalives);
+ end
+
+ session.close = session_close;
+
+ local stream = new_xmpp_stream(session, stream_callbacks);
+ session.stream = stream;
+
+ session.notopen = true;
+
+ function session.reset_stream()
+ session.notopen = true;
+ session.stream:reset();
+ end
+
+ local filter = session.filter;
+ function session.data(data)
+ data = filter("bytes/in", data);
+ if data then
+ local ok, err = stream:feed(data);
+ if ok then return; end
+ log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+ session:close("xml-not-well-formed");
end
-
- if opt_keepalives ~= nil then
- conn:setoption("keepalive", opt_keepalives);
+ end
+
+ local handlestanza = stream_callbacks.handlestanza;
+ function session.dispatch_stanza(session, stanza)
+ stanza = filter("stanzas/in", stanza);
+ if stanza then
+ return handlestanza(session, stanza);
end
-
- session.reset_stream = session_reset_stream;
- session.close = session_close;
-
- session_reset_stream(session); -- Initialise, ready for use
-
- session.dispatch_stanza = stream_callbacks.handlestanza;
end
- if data then
- session.data(conn, data);
+end
+
+function xmppclient.onincoming(conn, data)
+ local session = sessions[conn];
+ if session then
+ session.data(data);
end
end
diff --git a/net/xmppserver_listener.lua b/net/xmppserver_listener.lua
index d1272edb..8700e6f2 100644
--- a/net/xmppserver_listener.lua
+++ b/net/xmppserver_listener.lua
@@ -11,7 +11,7 @@
local logger = require "logger";
local log = logger.init("xmppserver_listener");
local lxp = require "lxp"
-local init_xmlhandlers = require "core.xmlhandlers"
+local new_xmpp_stream = require "util.xmppstream".new;
local s2s_new_incoming = require "core.s2smanager".new_incoming;
local s2s_streamopened = require "core.s2smanager".streamopened;
local s2s_streamclosed = require "core.s2smanager".streamclosed;
@@ -72,24 +72,6 @@ local xmppserver = { default_port = 5269, default_mode = "*a" };
-- These are session methods --
-local function session_reset_stream(session)
- -- Reset stream
- local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
- session.parser = parser;
-
- session.notopen = true;
-
- function session.data(conn, data)
- local ok, err = parser:parse(data);
- if ok then return; end
- (session.log or log)("warn", "Received invalid XML: %s", data);
- (session.log or log)("warn", "Problem was: %s", err);
- session:close("xml-not-well-formed");
- end
-
- return true;
-end
-
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
local function session_close(session, reason, remote_reason)
@@ -132,29 +114,58 @@ end
-- End of session methods --
-function xmppserver.onincoming(conn, data)
- local session = sessions[conn];
- if not session then
- session = s2s_new_incoming(conn);
- sessions[conn] = session;
+local function initialize_session(session)
+ local stream = new_xmpp_stream(session, stream_callbacks);
+ session.stream = stream;
+
+ session.notopen = true;
+
+ function session.reset_stream()
+ session.notopen = true;
+ session.stream:reset();
+ end
+
+ local filter = session.filter;
+ function session.data(data)
+ data = filter("bytes/in", data);
+ if data then
+ local ok, err = stream:feed(data);
+ if ok then return; end
+ (session.log or log)("warn", "Received invalid XML: %s", data);
+ (session.log or log)("warn", "Problem was: %s", err);
+ session:close("xml-not-well-formed");
+ end
+ end
- -- Logging functions --
+ session.close = session_close;
+ local handlestanza = stream_callbacks.handlestanza;
+ function session.dispatch_stanza(session, stanza)
+ stanza = filters("stanzas/in", stanza);
+ if stanza then
+ return handlestanza(session, stanza);
+ end
+ end
+end
-
+function xmppserver.onconnect(conn)
+ if not sessions[conn] then -- May be an existing outgoing session
+ local session = s2s_new_incoming(conn);
+ sessions[conn] = session;
+
+ -- Logging functions --
local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
session.log("info", "Incoming s2s connection");
- session.reset_stream = session_reset_stream;
- session.close = session_close;
-
- session_reset_stream(session); -- Initialise, ready for use
-
- session.dispatch_stanza = stream_callbacks.handlestanza;
+ initialize_session(session);
end
- if data then
- session.data(conn, data);
+end
+
+function xmppserver.onincoming(conn, data)
+ local session = sessions[conn];
+ if session then
+ session.data(data);
end
end
@@ -190,12 +201,7 @@ function xmppserver.register_outgoing(conn, session)
session.direction = "outgoing";
sessions[conn] = session;
- session.reset_stream = session_reset_stream;
- session.close = session_close;
- session_reset_stream(session); -- Initialise, ready for use
-
- --local function handleerr(err) print("Traceback:", err, debug.traceback()); end
- --session.stanza_dispatch = function (stanza) return select(2, xpcall(function () return core_process_stanza(session, stanza); end, handleerr)); end
+ initialize_session(session);
end
connlisteners_register("xmppserver", xmppserver);
diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua
index c2e84f2b..0e1aab8c 100644
--- a/plugins/mod_compression.lua
+++ b/plugins/mod_compression.lua
@@ -14,6 +14,7 @@ local xmlns_compression_feature = "http://jabber.org/features/compress"
local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
local xmlns_stream = "http://etherx.jabber.org/streams";
local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
+local add_filter = require "util.filters".add_filter;
local compression_level = module:get_option("compression_level");
-- if not defined assume admin wants best compression
@@ -94,44 +95,37 @@ end
-- setup compression for a stream
local function setup_compression(session, deflate_stream)
- local old_send = (session.sends2s or session.send);
-
- local new_send = function(t)
- --TODO: Better code injection in the sending process
- session.log(t)
- local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
- if status == false then
- session:close({
- condition = "undefined-condition";
- text = compressed;
- extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
- });
- module:log("warn", "%s", tostring(compressed));
- return;
- end
- session.conn:write(compressed);
- end;
-
- if session.sends2s then session.sends2s = new_send
- elseif session.send then session.send = new_send end
+ add_filter(session, "bytes/out", function(t)
+ session.log(t)
+ local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
+ if status == false then
+ session:close({
+ condition = "undefined-condition";
+ text = compressed;
+ extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+ });
+ module:log("warn", "%s", tostring(compressed));
+ return;
+ end
+ return compressed;
+ end);
end
-- setup decompression for a stream
local function setup_decompression(session, inflate_stream)
- local old_data = session.data
- session.data = function(conn, data)
- local status, decompressed, eof = pcall(inflate_stream, data);
- if status == false then
- session:close({
- condition = "undefined-condition";
- text = decompressed;
- extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
- });
- module:log("warn", "%s", tostring(decompressed));
- return;
- end
- old_data(conn, decompressed);
- end;
+ add_filter(session, "bytes/in", function(data)
+ local status, decompressed, eof = pcall(inflate_stream, data);
+ if status == false then
+ session:close({
+ condition = "undefined-condition";
+ text = decompressed;
+ extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+ });
+ module:log("warn", "%s", tostring(decompressed));
+ return;
+ end
+ return decompressed;
+ end);
end
module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol,
diff --git a/plugins/mod_iq.lua b/plugins/mod_iq.lua
index b3001fe5..e90af781 100644
--- a/plugins/mod_iq.lua
+++ b/plugins/mod_iq.lua
@@ -9,7 +9,6 @@
local st = require "util.stanza";
local jid_split = require "util.jid".split;
-local user_exists = require "core.usermanager".user_exists;
local full_sessions = full_sessions;
local bare_sessions = bare_sessions;
@@ -34,16 +33,6 @@ module:hook("iq/bare", function(data)
-- IQ to bare JID recieved
local origin, stanza = data.origin, data.stanza;
- local to = stanza.attr.to;
- if to and not bare_sessions[to] then -- quick check for account existance
- local node, host = jid_split(to);
- if not user_exists(node, host) then -- full check for account existance
- if stanza.attr.type == "get" or stanza.attr.type == "set" then
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
- end
- return true;
- end
- end
-- TODO fire post processing events
if stanza.attr.type == "get" or stanza.attr.type == "set" then
return module:fire_event("iq/bare/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data);
diff --git a/plugins/mod_offline.lua b/plugins/mod_offline.lua
new file mode 100644
index 00000000..24aef9ed
--- /dev/null
+++ b/plugins/mod_offline.lua
@@ -0,0 +1,56 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local datamanager = require "util.datamanager";
+local st = require "util.stanza";
+local datetime = require "util.datetime";
+local ipairs = ipairs;
+local jid_split = require "util.jid".split;
+
+module:add_feature("msgoffline");
+
+module:hook("message/offline/store", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ local to = stanza.attr.to;
+ local node, host;
+ if to then
+ node, host = jid_split(to)
+ else
+ node, host = origin.username, origin.host;
+ end
+
+ stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
+ local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
+ stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+
+ return true;
+end);
+
+module:hook("message/offline/broadcast", function(event)
+ local origin = event.origin;
+ local node, host = origin.username, origin.host;
+
+ local data = datamanager.list_load(node, host, "offline");
+ if not data then return true; end
+ for _, stanza in ipairs(data) do
+ stanza = st.deserialize(stanza);
+ stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
+ stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
+ stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+ origin.send(stanza);
+ end
+ return true;
+end);
+
+module:hook("message/offline/delete", function(event)
+ local origin = event.origin;
+ local node, host = origin.username, origin.host;
+
+ return datamanager.list_store(node, host, "offline", nil);
+end);
diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
index aa46d2d3..31546dff 100644
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -16,7 +16,6 @@ local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed
local pairs, ipairs = pairs, ipairs;
local next = next;
local type = type;
-local load_roster = require "core.rostermanager".load_roster;
local sha1 = require "util.hashes".sha1;
local base64 = require "util.encodings".base64.encode;
@@ -40,8 +39,8 @@ module:add_feature("http://jabber.org/protocol/pubsub#publish");
local function subscription_presence(user_bare, recipient)
local recipient_bare = jid_bare(recipient);
if (recipient_bare == user_bare) then return true end
- local item = load_roster(jid_split(user_bare))[recipient_bare];
- return item and (item.subscription == 'from' or item.subscription == 'both');
+ local username, host = jid_split(user_bare);
+ return is_contact_subscribed(username, host, recipient_bare);
end
local function publish(session, node, id, item)
@@ -118,27 +117,32 @@ module:hook("presence/bare", function(event)
-- inbound presence to bare JID recieved
local origin, stanza = event.origin, event.stanza;
local user = stanza.attr.to or (origin.username..'@'..origin.host);
+ local t = stanza.attr.type;
- if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
- local recipient = stanza.attr.from;
- local current = recipients[user] and recipients[user][recipient];
- local hash = get_caps_hash_from_presence(stanza, current);
- if current == hash then return; end
- if not hash then
- if recipients[user] then recipients[user][recipient] = nil; end
- else
- recipients[user] = recipients[user] or {};
- if hash_map[hash] then
- recipients[user][recipient] = hash_map[hash];
- publish_all(user, recipient, origin);
+ if not t then -- available presence
+ if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
+ local recipient = stanza.attr.from;
+ local current = recipients[user] and recipients[user][recipient];
+ local hash = get_caps_hash_from_presence(stanza, current);
+ if current == hash then return; end
+ if not hash then
+ if recipients[user] then recipients[user][recipient] = nil; end
else
- recipients[user][recipient] = hash;
- origin.send(
- st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"})
- :query("http://jabber.org/protocol/disco#info")
- );
+ recipients[user] = recipients[user] or {};
+ if hash_map[hash] then
+ recipients[user][recipient] = hash_map[hash];
+ publish_all(user, recipient, origin);
+ else
+ recipients[user][recipient] = hash;
+ origin.send(
+ st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"})
+ :query("http://jabber.org/protocol/disco#info")
+ );
+ end
end
end
+ elseif t == "unavailable" then
+ if recipients[user] then recipients[user][stanza.attr.from] = nil; end
end
end, 10);
diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua
index c38f7eba..52b1e0e6 100644
--- a/plugins/mod_posix.lua
+++ b/plugins/mod_posix.lua
@@ -54,16 +54,16 @@ module:add_event_hook("server-started", function ()
end);
-- Don't even think about it!
-module:add_event_hook("server-starting", function ()
- local suid = module:get_option("setuid");
- if not suid or suid == 0 or suid == "root" then
- if pposix.getuid() == 0 and not module:get_option("run_as_root") then
- module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
- module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
- prosody.shutdown("Refusing to run as root");
- end
+if not prosody.start_time then -- server-starting
+ local suid = module:get_option("setuid");
+ if not suid or suid == 0 or suid == "root" then
+ if pposix.getuid() == 0 and not module:get_option("run_as_root") then
+ module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
+ module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
+ prosody.shutdown("Refusing to run as root");
end
- end);
+ end
+end
local pidfile;
local pidfile_handle;
@@ -141,7 +141,9 @@ if daemonize then
write_pidfile();
end
end
- module:add_event_hook("server-starting", daemonize_server);
+ if not prosody.start_time then -- server-starting
+ daemonize_server();
+ end
else
-- Not going to daemonize, so write the pid of this process
write_pidfile();
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 18c80325..273e21ce 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -122,9 +122,13 @@ function room_mt:broadcast_message(stanza, historic)
local history = self._data['history'];
if not history then history = {}; self._data['history'] = history; end
stanza = st.clone(stanza);
- stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
+ stanza.attr.to = "";
+ local stamp = datetime.datetime();
+ local chars = #tostring(stanza);
+ stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
- t_insert(history, st.preserialize(stanza));
+ local entry = { stanza = stanza, stamp = stamp };
+ t_insert(history, entry);
while #history > history_length do t_remove(history, 1) end
end
end
@@ -151,12 +155,46 @@ function room_mt:send_occupant_list(to)
end
end
end
-function room_mt:send_history(to)
+function room_mt:send_history(to, stanza)
local history = self._data['history']; -- send discussion history
if history then
- for _, msg in ipairs(history) do
- msg = st.deserialize(msg);
- msg.attr.to=to;
+ local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
+ local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
+
+ local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
+ if maxchars then maxchars = math.floor(maxchars); end
+
+ local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
+ if not history_tag then maxstanzas = 20; end
+
+ local seconds = history_tag and tonumber(history_tag.attr.seconds);
+ if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
+
+ local since = history_tag and history_tag.attr.since;
+ if since and not since:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ$") then since = nil; end -- FIXME timezone support
+ if seconds and (not since or since < seconds) then since = seconds; end
+
+ local n = 0;
+ local charcount = 0;
+ local stanzacount = 0;
+
+ for i=#history,1,-1 do
+ local entry = history[i];
+ if maxchars then
+ if not entry.chars then
+ entry.stanza.attr.to = "";
+ entry.chars = #tostring(entry.stanza);
+ end
+ charcount = charcount + entry.chars + #to;
+ if charcount > maxchars then break; end
+ end
+ if since and since > entry.stamp then break; end
+ if n + 1 > maxstanzas then break; end
+ n = n + 1;
+ end
+ for i=#history-n+1,#history do
+ local msg = history[i].stanza;
+ msg.attr.to = to;
self:_route_stanza(msg);
end
end
@@ -319,12 +357,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
:tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
:tag("status", {code='110'}));
end
- if self._data.whois == 'anyone' then -- non-anonymous?
- self:_route_stanza(st.stanza("message", {from=to, to=from, type='groupchat'})
- :tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
- :tag("status", {code='100'}));
- end
- self:send_history(from);
+ self:send_history(from, stanza);
else -- banned
local reply = st.error_reply(stanza, "auth", "forbidden"):up();
reply.tags[1].attr.code = "403";
@@ -751,7 +784,7 @@ end
function room_mt:set_role(actor, occupant_jid, role, callback, reason)
if role == "none" then role = nil; end
if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
- if self:get_role(self._jid_nick[actor]) ~= "moderator" then return nil, "cancel", "not-allowed"; end
+ if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
local occupant = self._occupants[occupant_jid];
if not occupant then return nil, "modify", "not-acceptable"; end
if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
@@ -804,6 +837,9 @@ function room_mt:_route_stanza(stanza)
end
end
end
+ if self._data.whois == 'anyone' then
+ muc_child:tag('status', { code = '100' });
+ end
end
self:route_stanza(stanza);
if muc_child then
diff --git a/plugins/storage/ejabberdstore.lib.lua b/plugins/storage/ejabberdstore.lib.lua
new file mode 100644
index 00000000..7e8592a8
--- /dev/null
+++ b/plugins/storage/ejabberdstore.lib.lua
@@ -0,0 +1,190 @@
+
+local handlers = {};
+
+handlers.accounts = {
+ get = function(self, user)
+ local select = self:query("select password from users where username=?", user);
+ local row = select and select:fetch();
+ if row then return { password = row[1] }; end
+ end;
+ set = function(self, user, data)
+ if data and data.password then
+ return self:modify("update users set password=? where username=?", data.password, user)
+ or self:modify("insert into users (username, password) values (?, ?)", user, data.password);
+ else
+ return self:modify("delete from users where username=?", user);
+ end
+ end;
+};
+handlers.vcard = {
+ get = function(self, user)
+ local select = self:query("select vcard from vcard where username=?", user);
+ local row = select and select:fetch();
+ if row then return parse_xml(row[1]); end
+ end;
+ set = function(self, user, data)
+ if data then
+ data = unparse_xml(data);
+ return self:modify("update vcard set vcard=? where username=?", data, user)
+ or self:modify("insert into vcard (username, vcard) values (?, ?)", user, data);
+ else
+ return self:modify("delete from vcard where username=?", user);
+ end
+ end;
+};
+handlers.private = {
+ get = function(self, user)
+ local select = self:query("select namespace,data from private_storage where username=?", user);
+ if select then
+ local data = {};
+ for row in select:rows() do
+ data[row[1]] = parse_xml(row[2]);
+ end
+ return data;
+ end
+ end;
+ set = function(self, user, data)
+ if data then
+ self:modify("delete from private_storage where username=?", user);
+ for namespace,text in pairs(data) do
+ self:modify("insert into private_storage (username, namespace, data) values (?, ?, ?)", user, namespace, unparse_xml(text));
+ end
+ return true;
+ else
+ return self:modify("delete from private_storage where username=?", user);
+ end
+ end;
+ -- TODO map_set, map_get
+};
+local subscription_map = { N = "none", B = "both", F = "from", T = "to" };
+local subscription_map_reverse = { none = "N", both = "B", from = "F", to = "T" };
+handlers.roster = {
+ get = function(self, user)
+ local select = self:query("select jid,nick,subscription,ask,server,subscribe,type from rosterusers where username=?", user);
+ if select then
+ local roster = { pending = {} };
+ for row in select:rows() do
+ local jid,nick,subscription,ask,server,subscribe,typ = unpack(row);
+ local item = { groups = {} };
+ if nick == "" then nick = nil; end
+ item.nick = nick;
+ item.subscription = subscription_map[subscription];
+ if ask == "N" then ask = nil;
+ elseif ask == "O" then ask = "subscribe"
+ elseif ask == "I" then roster.pending[jid] = true; ask = nil;
+ elseif ask == "B" then roster.pending[jid] = true; ask = "subscribe";
+ else module:log("debug", "bad roster_item.ask: %s", ask); ask = nil; end
+ item.ask = ask;
+ roster[jid] = item;
+ end
+
+ select = self:query("select jid,grp from rostergroups where username=?", user);
+ if select then
+ for row in select:rows() do
+ local jid,grp = unpack(rows);
+ if roster[jid] then roster[jid].groups[grp] = true; end
+ end
+ end
+ select = self:query("select version from roster_version where username=?", user);
+ local row = select and select:fetch();
+ if row then
+ roster[false] = { version = row[1]; };
+ end
+ return roster;
+ end
+ end;
+ set = function(self, user, data)
+ if data and next(data) ~= nil then
+ self:modify("delete from rosterusers where username=?", user);
+ self:modify("delete from rostergroups where username=?", user);
+ self:modify("delete from roster_version where username=?", user);
+ local done = {};
+ local pending = data.pending or {};
+ for jid,item in pairs(data) do
+ if jid and jid ~= "pending" then
+ local subscription = subscription_map_reverse[item.subscription];
+ local ask;
+ if pending[jid] then
+ if item.ask then ask = "B"; else ask = "I"; end
+ else
+ if item.ask then ask = "O"; else ask = "N"; end
+ end
+ local r = self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?, '', '', '')", user, jid, item.nick or "", subscription, ask);
+ if not r then module:log("debug", "--- :( %s", tostring(r)); end
+ done[jid] = true;
+ for group in pairs(item.groups) do
+ self:modify("insert into rostergroups (username,jid,grp) values (?, ?, ?)", user, jid, group);
+ end
+ end
+ end
+ for jid in pairs(pending) do
+ if not done[jid] then
+ self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?. ''. ''. '')", user, jid, "", "N", "I");
+ end
+ end
+ local version = data[false] and data[false].version;
+ if version then
+ self:modify("insert into roster_version (username,version) values (?, ?)", user, version);
+ end
+ return true;
+ else
+ self:modify("delete from rosterusers where username=?", user);
+ self:modify("delete from rostergroups where username=?", user);
+ self:modify("delete from roster_version where username=?", user);
+ end
+ end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:prepare(sql)
+ module:log("debug", "query: %s", sql);
+ local err;
+ if not self.sqlcache then self.sqlcache = {}; end
+ local r = self.sqlcache[sql];
+ if r then return r; end
+ r, err = self.database:prepare(sql);
+ if not r then error("Unable to prepare SQL statement: "..err); end
+ self.sqlcache[sql] = r;
+ return r;
+end
+
+function driver:query(sql, ...)
+ local stmt = self:prepare(sql);
+ if stmt:execute(...) then return stmt; end
+end
+function driver:modify(sql, ...)
+ local stmt = self:query(sql, ...);
+ if stmt and stmt:affected() > 0 then return stmt; end
+end
+
+function driver:open(host, datastore, typ)
+ local cache_key = host.." "..datastore;
+ if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+ local instance = setmetatable({}, self);
+ instance.host = host;
+ instance.datastore = datastore;
+ local handler = handlers[datastore];
+ if not handler then return nil; end
+ for key,val in pairs(handler) do
+ instance[key] = val;
+ end
+ if instance.init then instance:init(); end
+ self.ds_cache[cache_key] = instance;
+ return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new(dbtype, dbname, ...)
+ local instance = setmetatable({}, driver);
+ instance.__index = instance;
+ instance.database = get_database(dbtype, dbname, ...);
+ instance.ds_cache = {};
+ return instance;
+end
+
+return _M;
diff --git a/plugins/storage/mod_storage.lua b/plugins/storage/mod_storage.lua
new file mode 100644
index 00000000..e22de82c
--- /dev/null
+++ b/plugins/storage/mod_storage.lua
@@ -0,0 +1,83 @@
+
+module:set_global();
+
+local cache = { data = {} };
+function cache:get(key) return self.data[key]; end
+function cache:set(key, val) self.data[key] = val; return val; end
+
+local DBI = require "DBI";
+function get_database(driver, db, ...)
+ local uri = "dbi:"..driver..":"..db;
+ return cache:get(uri) or cache:set(uri, (function(...)
+ module:log("debug", "Opening database: %s", uri);
+ prosody.unlock_globals();
+ local dbh = assert(DBI.Connect(...));
+ prosody.lock_globals();
+ dbh:autocommit(true)
+ return dbh;
+ end)(driver, db, ...));
+end
+
+local st = require "util.stanza";
+local _parse_xml = module:require("xmlparse");
+parse_xml_real = _parse_xml;
+function parse_xml(str)
+ local s = _parse_xml(str);
+ if s and not s.gsub then
+ return st.preserialize(s);
+ end
+end
+function unparse_xml(s)
+ return tostring(st.deserialize(s));
+end
+
+local drivers = {};
+
+--local driver = module:require("sqlbasic").new("SQLite3", "hello.sqlite");
+local option_datastore = module:get_option("datastore");
+local option_datastore_params = module:get_option("datastore_params") or {};
+if option_datastore then
+ local driver = module:require(option_datastore).new(unpack(option_datastore_params));
+ table.insert(drivers, driver);
+end
+
+local datamanager = require "util.datamanager";
+local olddm = {};
+local dm = {};
+for key,val in pairs(datamanager) do olddm[key] = val; end
+
+do -- driver based on old datamanager
+ local dmd = {};
+ dmd.__index = dmd;
+ function dmd:open(host, datastore)
+ return setmetatable({ host = host, datastore = datastore }, dmd);
+ end
+ function dmd:get(user) return olddm.load(user, self.host, self.datastore); end
+ function dmd:set(user, data) return olddm.store(user, self.host, self.datastore, data); end
+ table.insert(drivers, dmd);
+end
+
+local function open(...)
+ for _,driver in pairs(drivers) do
+ local ds = driver:open(...);
+ if ds then return ds; end
+ end
+end
+
+local _data_path;
+--function dm.set_data_path(path) _data_path = path; end
+--function dm.add_callback(...) end
+--function dm.remove_callback(...) end
+--function dm.getpath(...) end
+function dm.load(username, host, datastore)
+ local x = open(host, datastore);
+ return x:get(username);
+end
+function dm.store(username, host, datastore, data)
+ return open(host, datastore):set(username, data);
+end
+--function dm.list_append(...) return driver:list_append(...); end
+--function dm.list_store(...) return driver:list_store(...); end
+--function dm.list_load(...) return driver:list_load(...); end
+
+for key,val in pairs(dm) do datamanager[key] = val; end
diff --git a/plugins/storage/sqlbasic.lib.lua b/plugins/storage/sqlbasic.lib.lua
new file mode 100644
index 00000000..f1202287
--- /dev/null
+++ b/plugins/storage/sqlbasic.lib.lua
@@ -0,0 +1,97 @@
+
+-- Basic SQL driver
+-- This driver stores data as simple key-values
+
+local ser = require "util.serialization".serialize;
+local deser = function(data)
+ module:log("debug", "deser: %s", tostring(data));
+ if not data then return nil; end
+ local f = loadstring("return "..data);
+ if not f then return nil; end
+ setfenv(f, {});
+ local s, d = pcall(f);
+ if not s then return nil; end
+ return d;
+end;
+
+local driver = {};
+driver.__index = driver;
+
+driver.item_table = "item";
+driver.list_table = "list";
+
+function driver:prepare(sql)
+ module:log("debug", "query: %s", sql);
+ local err;
+ if not self.sqlcache then self.sqlcache = {}; end
+ local r = self.sqlcache[sql];
+ if r then return r; end
+ r, err = self.connection:prepare(sql);
+ if not r then error("Unable to prepare SQL statement: "..err); end
+ self.sqlcache[sql] = r;
+ return r;
+end
+
+function driver:load(username, host, datastore)
+ local select = self:prepare("select data from "..self.item_table.." where username=? and host=? and datastore=?");
+ select:execute(username, host, datastore);
+ local row = select:fetch();
+ return row and deser(row[1]) or nil;
+end
+
+function driver:store(username, host, datastore, data)
+ if not data or next(data) == nil then
+ local delete = self:prepare("delete from "..self.item_table.." where username=? and host=? and datastore=?");
+ delete:execute(username, host, datastore);
+ return true;
+ else
+ local d = self:load(username, host, datastore);
+ if d then -- update
+ local update = self:prepare("update "..self.item_table.." set data=? where username=? and host=? and datastore=?");
+ return update:execute(ser(data), username, host, datastore);
+ else -- insert
+ local insert = self:prepare("insert into "..self.item_table.." values (?, ?, ?, ?)");
+ return insert:execute(username, host, datastore, ser(data));
+ end
+ end
+end
+
+function driver:list_append(username, host, datastore, data)
+ if not data then return; end
+ local insert = self:prepare("insert into "..self.list_table.." values (?, ?, ?, ?)");
+ return insert:execute(username, host, datastore, ser(data));
+end
+
+function driver:list_store(username, host, datastore, data)
+ -- remove existing data
+ local delete = self:prepare("delete from "..self.list_table.." where username=? and host=? and datastore=?");
+ delete:execute(username, host, datastore);
+ if data and next(data) ~= nil then
+ -- add data
+ for _, d in ipairs(data) do
+ self:list_append(username, host, datastore, ser(d));
+ end
+ end
+ return true;
+end
+
+function driver:list_load(username, host, datastore)
+ local select = self:prepare("select data from "..self.list_table.." where username=? and host=? and datastore=?");
+ select:execute(username, host, datastore);
+ local r = {};
+ for row in select:rows() do
+ table.insert(r, deser(row[1]));
+ end
+ return r;
+end
+
+local _M = {};
+function _M.new(dbtype, dbname, ...)
+ local d = {};
+ setmetatable(d, driver);
+ local dbh = get_database(dbtype, dbname, ...);
+ --d:set_connection(dbh);
+ d.connection = dbh;
+ return d;
+end
+return _M;
diff --git a/plugins/storage/xep227store.lib.lua b/plugins/storage/xep227store.lib.lua
new file mode 100644
index 00000000..5ef8df54
--- /dev/null
+++ b/plugins/storage/xep227store.lib.lua
@@ -0,0 +1,168 @@
+
+local st = require "util.stanza";
+
+local function getXml(user, host)
+ local jid = user.."@"..host;
+ local path = "data/"..jid..".xml";
+ local f = io.open(path);
+ if not f then return; end
+ local s = f:read("*a");
+ return parse_xml_real(s);
+end
+local function setXml(user, host, xml)
+ local jid = user.."@"..host;
+ local path = "data/"..jid..".xml";
+ if xml then
+ local f = io.open(path, "w");
+ if not f then return; end
+ local s = tostring(xml);
+ f:write(s);
+ f:close();
+ return true;
+ else
+ return os.remove(path);
+ end
+end
+local function getUserElement(xml)
+ if xml and xml.name == "server-data" then
+ local host = xml.tags[1];
+ if host and host.name == "host" then
+ local user = host.tags[1];
+ if user and user.name == "user" then
+ return user;
+ end
+ end
+ end
+end
+local function createOuterXml(user, host)
+ return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'})
+ :tag("host", {jid=host})
+ :tag("user", {name = user});
+end
+local function removeFromArray(array, value)
+ for i,item in ipairs(array) do
+ if item == value then
+ table.remove(array, i);
+ return;
+ end
+ end
+end
+local function removeStanzaChild(s, child)
+ removeFromArray(s.tags, child);
+ removeFromArray(s, child);
+end
+
+local handlers = {};
+
+handlers.accounts = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user and user.attr.password then
+ return { password = user.attr.password };
+ end
+ end;
+ set = function(self, user, data)
+ if data and data.password then
+ local xml = getXml(user, self.host);
+ if not xml then xml = createOuterXml(user, self.host); end
+ local usere = getUserElement(xml);
+ usere.attr.password = data.password;
+ return setXml(user, self.host, xml);
+ else
+ return setXml(user, self.host, nil);
+ end
+ end;
+};
+handlers.vcard = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user then
+ local vcard = user:get_child("vCard", 'vcard-temp');
+ if vcard then
+ return st.preserialize(vcard);
+ end
+ end
+ end;
+ set = function(self, user, data)
+ local xml = getXml(user, self.host);
+ local usere = xml and getUserElement(xml);
+ if usere then
+ local vcard = usere:get_child("vCard", 'vcard-temp');
+ if vcard then
+ removeStanzaChild(usere, vcard);
+ elseif not data then
+ return true;
+ end
+ if data then
+ vcard = st.deserialize(data);
+ usere:add_child(vcard);
+ end
+ return setXml(user, self.host, xml);
+ end
+ return true;
+ end;
+};
+handlers.private = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user then
+ local private = user:get_child("query", "jabber:iq:private");
+ if private then
+ local r = {};
+ for _, tag in ipairs(private.tags) do
+ r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag);
+ end
+ return r;
+ end
+ end
+ end;
+ set = function(self, user, data)
+ local xml = getXml(user, self.host);
+ local usere = xml and getUserElement(xml);
+ if usere then
+ local private = usere:get_child("query", 'jabber:iq:private');
+ if private then removeStanzaChild(usere, private); end
+ if data and next(data) ~= nil then
+ private = st.stanza("query", {xmlns='jabber:iq:private'});
+ for _,tag in pairs(data) do
+ private:add_child(st.deserialize(tag));
+ end
+ usere:add_child(private);
+ end
+ return setXml(user, self.host, xml);
+ end
+ return true;
+ end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:open(host, datastore, typ)
+ local cache_key = host.." "..datastore;
+ if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+ local instance = setmetatable({}, self);
+ instance.host = host;
+ instance.datastore = datastore;
+ local handler = handlers[datastore];
+ if not handler then return nil; end
+ for key,val in pairs(handler) do
+ instance[key] = val;
+ end
+ if instance.init then instance:init(); end
+ self.ds_cache[cache_key] = instance;
+ return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new()
+ local instance = setmetatable({}, driver);
+ instance.__index = instance;
+ instance.ds_cache = {};
+ return instance;
+end
+
+return _M;
diff --git a/plugins/storage/xmlparse.lib.lua b/plugins/storage/xmlparse.lib.lua
new file mode 100644
index 00000000..91063995
--- /dev/null
+++ b/plugins/storage/xmlparse.lib.lua
@@ -0,0 +1,56 @@
+
+local st = require "util.stanza";
+
+-- XML parser
+local parse_xml = (function()
+ local entity_map = setmetatable({
+ ["amp"] = "&";
+ ["gt"] = ">";
+ ["lt"] = "<";
+ ["apos"] = "'";
+ ["quot"] = "\"";
+ }, {__index = function(_, s)
+ if s:sub(1,1) == "#" then
+ if s:sub(2,2) == "x" then
+ return string.char(tonumber(s:sub(3), 16));
+ else
+ return string.char(tonumber(s:sub(2)));
+ end
+ end
+ end
+ });
+ local function xml_unescape(str)
+ return (str:gsub("&(.-);", entity_map));
+ end
+ local function parse_tag(s)
+ local name,sattr=(s):gmatch("([^%s]+)(.*)")();
+ local attr = {};
+ for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end
+ return name, attr;
+ end
+ return function(xml)
+ local stanza = st.stanza("root");
+ local regexp = "<([^>]*)>([^<]*)";
+ for elem, text in xml:gmatch(regexp) do
+ if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
+ elseif elem:sub(1,1) == "/" then -- end tag
+ elem = elem:sub(2);
+ stanza:up(); -- TODO check for start-end tag name match
+ elseif elem:sub(-1,-1) == "/" then -- empty tag
+ elem = elem:sub(1,-2);
+ local name,attr = parse_tag(elem);
+ stanza:tag(name, attr):up();
+ else -- start tag
+ local name,attr = parse_tag(elem);
+ stanza:tag(name, attr);
+ end
+ if #text ~= 0 then -- text
+ stanza:text(xml_unescape(text));
+ end
+ end
+ return stanza.tags[1];
+ end
+end)();
+-- end of XML parser
+
+return parse_xml;
diff --git a/prosody b/prosody
index e4e82105..517762f3 100755
--- a/prosody
+++ b/prosody
@@ -22,9 +22,6 @@ if CFG_SOURCEDIR then
package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
end
-package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
-package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
-
-- Substitute ~ with path to home directory in data path
if CFG_DATADIR then
if os.getenv("HOME") then
@@ -32,6 +29,10 @@ if CFG_DATADIR then
end
end
+-- Global 'prosody' object
+prosody = { events = require "util.events".new(); };
+local prosody = prosody;
+
-- Load the config-parsing module
config = require "core.configmanager"
@@ -155,10 +156,6 @@ function init_global_state()
full_sessions = {};
hosts = {};
- -- Global 'prosody' object
- prosody = {};
- local prosody = prosody;
-
prosody.bare_sessions = bare_sessions;
prosody.full_sessions = full_sessions;
prosody.hosts = hosts;
@@ -168,8 +165,6 @@ function init_global_state()
prosody.arg = _G.arg;
- prosody.events = require "util.events".new();
-
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
@@ -200,7 +195,6 @@ function init_global_state()
-- Function to reopen logfiles
function prosody.reopen_logfiles()
log("info", "Re-opening log files");
- eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
prosody.events.fire_event("reopen-log-files");
end
@@ -291,9 +285,9 @@ end
function load_secondary_libraries()
--- Load and initialise core modules
require "util.import"
+ require "util.xmppstream"
require "core.xmlhandlers"
require "core.rostermanager"
- require "core.eventmanager"
require "core.hostmanager"
require "core.modulemanager"
require "core.usermanager"
@@ -337,7 +331,6 @@ end
function prepare_to_start()
log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
-- Signal to modules that we are ready to start
- eventmanager.fire_event("server-starting");
prosody.events.fire_event("server-starting");
-- start listening on sockets
@@ -455,14 +448,12 @@ init_data_store();
init_global_protection();
prepare_to_start();
-eventmanager.fire_event("server-started");
prosody.events.fire_event("server-started");
loop();
log("info", "Shutting down...");
cleanup();
-eventmanager.fire_event("server-stopped");
prosody.events.fire_event("server-stopped");
log("info", "Shutdown complete");
diff --git a/prosodyctl b/prosodyctl
index ccc1e2f9..c0cd89a0 100755
--- a/prosodyctl
+++ b/prosodyctl
@@ -29,6 +29,14 @@ if CFG_DATADIR then
end
end
+-- Global 'prosody' object
+prosody = {
+ hosts = {},
+ events = require "util.events".new(),
+ platform = "posix"
+};
+local prosody = prosody;
+
config = require "core.configmanager"
do
@@ -63,8 +71,6 @@ if not require "util.dependencies".check_dependencies() then
os.exit(1);
end
-prosody = { hosts = {}, events = events, platform = "posix" };
-
local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
require "util.datamanager".set_data_path(data_path);
@@ -114,12 +120,14 @@ local error_messages = setmetatable({
["not-running"] = "Prosody is not running";
}, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
-local events = require "util.events".new();
-
hosts = prosody.hosts;
+local function make_host(hostname)
+ return { events = prosody.events, users = require "core.usermanager".new_default_provider(hostname) };
+end
+
for hostname, config in pairs(config.getconfig()) do
- hosts[hostname] = { events = events };
+ hosts[hostname] = make_host(hostname);
end
require "core.modulemanager"
@@ -231,14 +239,21 @@ function commands.adduser(arg)
return 1;
end
- if prosodyctl.user_exists{ user = user, host = host } then
- show_message [[That user already exists]];
- return 1;
- end
-
if not hosts[host] then
show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
show_warning("The user will not be able to log in until this is changed.");
+ hosts[host] = make_host(host);
+ elseif config.get(host, "core", "authentication")
+ and config.get(host, "core", "authentication") ~= "default" then
+ show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
+ config.get(host, "core", "authentication"));
+ show_warning("prosodyctl currently only supports the default provider, sorry :(");
+ return 1;
+ end
+
+ if prosodyctl.user_exists{ user = user, host = host } then
+ show_message [[That user already exists]];
+ return 1;
end
local password = read_password();
@@ -269,6 +284,18 @@ function commands.passwd(arg)
return 1;
end
+ if not hosts[host] then
+ show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
+ show_warning("The user will not be able to log in until this is changed.");
+ hosts[host] = make_host(host);
+ elseif config.get(host, "core", "authentication")
+ and config.get(host, "core", "authentication") ~= "default" then
+ show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
+ config.get(host, "core", "authentication"));
+ show_warning("prosodyctl currently only supports the default provider, sorry :(");
+ return 1;
+ end
+
if not prosodyctl.user_exists { user = user, host = host } then
show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
return 1;
@@ -302,6 +329,18 @@ function commands.deluser(arg)
return 1;
end
+ if not hosts[host] then
+ show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
+ show_warning("The user will not be able to log in until this is changed.");
+ hosts[host] = make_host(host);
+ elseif config.get(host, "core", "authentication")
+ and config.get(host, "core", "authentication") ~= "default" then
+ show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
+ config.get(host, "core", "authentication"));
+ show_warning("prosodyctl currently only supports the default provider, sorry :(");
+ return 1;
+ end
+
if not prosodyctl.user_exists { user = user, host = host } then
show_message [[That user does not exist on this server]]
return 1;
diff --git a/util/filters.lua b/util/filters.lua
new file mode 100644
index 00000000..0ac4cb56
--- /dev/null
+++ b/util/filters.lua
@@ -0,0 +1,68 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local t_insert, t_remove = table.insert, table.remove;
+
+module "filters"
+
+function initialize(session)
+ if not session.filters then
+ local filters = {};
+ session.filters = filters;
+
+ function session.filter(type, data)
+ local filter_list = filters[type];
+ if filter_list then
+ for i = 1, #filter_list do
+ data = filter_list[i](data);
+ if data == nil then break; end
+ end
+ end
+ return data;
+ end
+ end
+ return session.filter;
+end
+
+function add_filter(session, type, callback, priority)
+ if not session.filters then
+ initialize(session);
+ end
+
+ local filter_list = session.filters[type];
+ if not filter_list then
+ filter_list = {};
+ session.filters[type] = filter_list;
+ end
+
+ priority = priority or 0;
+
+ local i = 0;
+ repeat
+ i = i + 1;
+ until not filter_list[i] or filter_list[filter_list[i]] >= priority;
+
+ t_insert(filter_list, i, callback);
+ filter_list[callback] = priority;
+end
+
+function remove_filter(session, type, callback)
+ if not session.filters then return; end
+ local filter_list = session.filters[type];
+ if filter_list and filter_list[callback] then
+ for i=1, #filter_list do
+ if filter_list[i] == callback then
+ t_remove(filter_list, i);
+ filter_list[callback] = nil;
+ return true;
+ end
+ end
+ end
+end
+
+return _M; \ No newline at end of file
diff --git a/util/sasl/anonymous.lua b/util/sasl/anonymous.lua
index 7b5a5081..f3e31a7f 100644
--- a/util/sasl/anonymous.lua
+++ b/util/sasl/anonymous.lua
@@ -20,6 +20,16 @@ module "anonymous"
--=========================
--SASL ANONYMOUS according to RFC 4505
+
+--[[
+Supported Authentication Backends
+
+anonymous:
+ function(username, realm)
+ return true; --for normal usage just return true; if you don't like the supplied username you can return false.
+ end
+]]
+
local function anonymous(self, message)
local username;
repeat
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
new file mode 100644
index 00000000..ed5395b5
--- /dev/null
+++ b/util/xmppstream.lua
@@ -0,0 +1,168 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local lxp = require "lxp";
+local st = require "util.stanza";
+
+local tostring = tostring;
+local t_insert = table.insert;
+local t_concat = table.concat;
+
+local default_log = require "util.logger".init("xmlhandlers");
+
+local error = error;
+
+module "xmppstream"
+
+local new_parser = lxp.new;
+
+local ns_prefixes = {
+ ["http://www.w3.org/XML/1998/namespace"] = "xml";
+};
+
+local xmlns_streams = "http://etherx.jabber.org/streams";
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+
+function new_sax_handlers(session, stream_callbacks)
+ local xml_handlers = {};
+
+ local log = session.log or default_log;
+
+ local cb_streamopened = stream_callbacks.streamopened;
+ local cb_streamclosed = stream_callbacks.streamclosed;
+ local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
+ local cb_handlestanza = stream_callbacks.handlestanza;
+
+ local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
+ local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
+ local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
+
+ local stream_default_ns = stream_callbacks.default_ns;
+
+ local chardata, stanza = {};
+ function xml_handlers:StartElement(tagname, attr)
+ if stanza and #chardata > 0 then
+ -- We have some character data in the buffer
+ stanza:text(t_concat(chardata));
+ chardata = {};
+ end
+ local curr_ns,name = tagname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+
+ if curr_ns ~= stream_default_ns then
+ attr.xmlns = curr_ns;
+ end
+
+ -- FIXME !!!!!
+ for i=1,#attr do
+ local k = attr[i];
+ attr[i] = nil;
+ local ns, nm = k:match(ns_pattern);
+ if nm ~= "" then
+ ns = ns_prefixes[ns];
+ if ns then
+ attr[ns..":"..nm] = attr[k];
+ attr[k] = nil;
+ end
+ end
+ end
+
+ if not stanza then --if we are not currently inside a stanza
+ if session.notopen then
+ if tagname == stream_tag then
+ if cb_streamopened then
+ cb_streamopened(session, attr);
+ end
+ else
+ -- Garbage before stream?
+ cb_error(session, "no-stream");
+ end
+ return;
+ end
+ if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
+ cb_error(session, "invalid-top-level-element");
+ end
+
+ stanza = st.stanza(name, attr);
+ else -- we are inside a stanza, so add a tag
+ attr.xmlns = nil;
+ if curr_ns ~= stream_default_ns then
+ attr.xmlns = curr_ns;
+ end
+ stanza:tag(name, attr);
+ end
+ end
+ function xml_handlers:CharacterData(data)
+ if stanza then
+ t_insert(chardata, data);
+ end
+ end
+ function xml_handlers:EndElement(tagname)
+ if stanza then
+ if #chardata > 0 then
+ -- We have some character data in the buffer
+ stanza:text(t_concat(chardata));
+ chardata = {};
+ end
+ -- Complete stanza
+ if #stanza.last_add == 0 then
+ if tagname ~= stream_error_tag then
+ cb_handlestanza(session, stanza);
+ else
+ cb_error(session, "stream-error", stanza);
+ end
+ stanza = nil;
+ else
+ stanza:up();
+ end
+ else
+ if tagname == stream_tag then
+ if cb_streamclosed then
+ cb_streamclosed(session);
+ end
+ else
+ local curr_ns,name = tagname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+ cb_error(session, "parse-error", "unexpected-element-close", name);
+ end
+ stanza, chardata = nil, {};
+ end
+ end
+
+ local function reset()
+ stanza, chardata = nil, {};
+ end
+
+ return xml_handlers, { reset = reset };
+end
+
+function new(session, stream_callbacks)
+ local handlers, meta = new_sax_handlers(session, stream_callbacks);
+ local parser = new_parser(handlers, ns_separator);
+ local parse = parser.parse;
+
+ return {
+ reset = function ()
+ parser = new_parser(handlers, ns_separator);
+ parse = parser.parse;
+ meta.reset();
+ end,
+ feed = function (self, data)
+ return parse(parser, data);
+ end
+ };
+end
+
+return _M;