-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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.
-- 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()
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; Component = true, component = true,
- 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.Host(name)
+ function env.VirtualHost(name)
if rawget(config, name) and rawget(config[name].core, "component_module") then
error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
name, config[name].core.component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
-- Needs at least one setting to logically exist :)
set(name or "*", "core", "defined", true);
end
- env.host = env.Host;
+ env.Host, env.host = env.VirtualHost, env.VirtualHost;
function env.Component(name)
if rawget(config, name) and rawget(config[name].core, "defined") and not rawget(config[name].core, "component_module") then
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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 = table.insert;
local ipairs = ipairs;
-local events = _G.prosody.events;
-
module "eventmanager"
local event_handlers = {};
function add_event_hook(name, handler)
- return events.add_handler(name, handler);
+ if not event_handlers[name] then
+ event_handlers[name] = {};
+ end
+ t_insert(event_handlers[name] , handler);
end
function fire_event(name, ...)
- return events.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
-return _M;
+return _M;
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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 hashes = require "util.hashes";
local jid_bare = require "util.jid".bare;
local config = require "core.configmanager";
+ local hosts = hosts;
+
+ local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
-local prosody = _G.prosody;
-
module "usermanager"
-local new_default_provider;
-
-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
+
-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 {};
-
+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 {};
+
+ if method == nil then method = "PLAIN"; end
+ if method == "PLAIN" and credentials.password then -- PLAIN, do directly
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
-
- 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
- return datamanager.load(username, host, "accounts") ~= 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
+ -- compare
+ if password == pwd then
+ return true;
+ else
+ return nil, "Auth failed. Invalid username or password.";
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)
- return (datamanager.load(username, host, "accounts") or {}).password
- return hosts[host].users: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 set_password(username, host, password)
- return hosts[host].users: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 user_exists(username, host)
- return hosts[host].users:user_exists(username);
++ if not(require_provisioning) and is_cyrus(host) then return true; end
+ return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
end
function create_user(username, password, host)
- return hosts[host].users: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 get_supported_methods(host)
- return hosts[host].users:get_supported_methods();
+ return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
end
function is_admin(jid, host)
- 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
+ host = host or "*";
+ local admins = config.get(host, "core", "admins");
+ if host ~= "*" and admins == config.get("*", "core", "admins") then
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;
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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 logger = require "logger";
local log = logger.init("xmppclient_listener");
local lxp = require "lxp"
-local new_xmpp_stream = require "util.xmppstream".new;
+local init_xmlhandlers = require "core.xmlhandlers"
local sm_new_session = require "core.sessionmanager".new_session;
local connlisteners_register = require "net.connlisteners".register;
local stream_callbacks = { default_ns = "jabber:client",
streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza };
+ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
function stream_callbacks.error(session, error, data)
if error == "no-stream" then
session.log("debug", "Invalid opening stream header");
session:close("invalid-namespace");
- elseif session.close then
- (session.log or log)("debug", "Client XML parse error: %s", tostring(error));
+ elseif error == "parse-error" then
+ (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
session:close("xml-not-well-formed");
+ elseif error == "stream-error" then
+ local condition, text = "undefined-condition";
+ for child in data:children() do
+ if child.attr.xmlns == xmlns_xmpp_streams then
+ if child.name ~= "text" then
+ condition = child.name;
+ else
+ text = child:get_text();
+ end
+ if condition ~= "undefined-condition" and text then
+ break;
+ end
+ end
+ end
+ text = condition .. (text and (" ("..text..")") or "");
+ session.log("info", "Session closed by remote with error: %s", text);
+ session:close(nil, text);
end
end
-- 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)
conn:setoption("keepalive", opt_keepalives);
end
+ session.reset_stream = session_reset_stream;
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
-
- function session.data(data)
- 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
+ session_reset_stream(session); -- Initialise, ready for use
session.dispatch_stanza = stream_callbacks.handlestanza;
end
if data then
- session.data(data);
+ session.data(conn, data);
end
end
+++ /dev/null
---- 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.
----
--
- \r
- local datamanager = require "util.datamanager";\r
- local st = require "util.stanza";\r
- local datetime = require "util.datetime";\r
-
-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;\r
- \r
- module:add_feature("msgoffline");\r
- \r
- module:hook("message/offline/store", function(event)\r
- local origin, stanza = event.origin, event.stanza;\r
- local to = stanza.attr.to;\r
- local node, host;\r
- if to then\r
- node, host = jid_split(to)\r
- else\r
- node, host = origin.username, origin.host;\r
- end\r
- \r
- stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();\r
- local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));\r
- stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;\r
- \r
- return true;\r
- end);\r
- \r
- module:hook("message/offline/broadcast", function(event)\r
- local origin = event.origin;\r
- local node, host = origin.username, origin.host;\r
- \r
- local data = datamanager.list_load(node, host, "offline");\r
- if not data then return true; end\r
- for _, stanza in ipairs(data) do\r
- stanza = st.deserialize(stanza);\r
- stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203\r
- stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)\r
- stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;\r
- origin.send(stanza);\r
- end\r
- return true;\r
- end);\r
- \r
- module:hook("message/offline/delete", function(event)\r
- local origin = event.origin;\r
- local node, host = origin.username, origin.host;\r
- \r
- return datamanager.list_store(node, host, "offline", nil);\r
- end);\r
-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);
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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.
end);
-- Don't even think about it!
-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");
+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
end
- end
-end
+ end);
local pidfile;
local pidfile_handle;
end
pidfile = module:get_option("pidfile");
if pidfile then
+ local err;
local mode = stat(pidfile) and "r+" or "w+";
pidfile_handle, err = io.open(pidfile, mode);
if not pidfile_handle then
write_pidfile();
end
end
- if not prosody.start_time then -- server-starting
- daemonize_server();
- end
+ module:add_event_hook("server-starting", daemonize_server);
else
-- Not going to daemonize, so write the pid of this process
write_pidfile();
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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.
_core_route_stanza(origin, stanza);
end
-local select_top_resources;
-local bare_message_delivery_policy = module:get_option("bare_message_delivery_policy") or "priority";
-if bare_message_delivery_policy == "broadcast" then
- function select_top_resources(user)
- local recipients = {};
- for _, session in pairs(user.sessions) do -- find resources with non-negative priority
+local function select_top_resources(user)
+ local priority = 0;
+ local recipients = {};
+ for _, session in pairs(user.sessions) do -- find resource with greatest priority
+ if session.presence then
+ -- TODO check active privacy list for session
local p = session.priority;
- if p and p >= 0 then
+ if p > priority then
+ priority = p;
+ recipients = {session};
+ elseif p == priority then
t_insert(recipients, session);
end
end
- return recipients;
- end
-else
- if bare_message_delivery_policy ~= "priority" then
- module:log("warn", "Invalid value for config option bare_message_delivery_policy");
- end
- function select_top_resources(user)
- local priority = 0;
- local recipients = {};
- for _, session in pairs(user.sessions) do -- find resource with greatest priority
- if session.presence then
- -- TODO check active privacy list for session
- local p = session.priority;
- if p > priority then
- priority = p;
- recipients = {session};
- elseif p == priority then
- t_insert(recipients, session);
- end
- end
- end
- return recipients;
end
+ return recipients;
end
-
local function recalc_resource_map(user)
if user then
user.top_resources = select_top_resources(user);
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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 history = self._data['history'];
if not history then history = {}; self._data['history'] = history; end
stanza = st.clone(stanza);
- 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("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
- local entry = { stanza = stanza, stamp = stamp };
- t_insert(history, entry);
+ t_insert(history, st.preserialize(stanza));
while #history > history_length do t_remove(history, 1) end
end
end
end
end
end
-function room_mt:send_history(to, stanza)
+function room_mt:send_history(to)
local history = self._data['history']; -- send discussion history
if history then
- 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;
+ for _, msg in ipairs(history) do
+ msg = st.deserialize(msg);
+ msg.attr.to=to;
self:_route_stanza(msg);
end
end
:tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
:tag("status", {code='110'}));
end
- self:send_history(from, stanza);
++ 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);
else -- banned
local reply = st.error_reply(stanza, "auth", "forbidden"):up();
reply.tags[1].attr.code = "403";
if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
local occupant = self._occupants[self.jid.."/"..item.attr.nick];
if occupant then item.attr.jid = occupant.jid; end
+ elseif not item.attr.nick and item.attr.jid then
+ local nick = self._jid_nick[item.attr.jid];
+ if nick then item.attr.nick = select(3, jid_split(nick)); end
end
local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
if item.attr.affiliation and item.attr.jid and not item.attr.role then
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_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
++ if self:get_role(self._jid_nick[actor]) ~= "moderator" 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
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
#!/usr/bin/env lua
-- Prosody IM
- -- Copyright (C) 2008-2009 Matthew Wild
- -- Copyright (C) 2008-2009 Waqas Hussain
+ -- 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.
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
end
end
--- Global 'prosody' object
-prosody = { events = require "util.events".new(); };
-local prosody = prosody;
-
-- Load the config-parsing module
config = require "core.configmanager"
end
end
+ function set_function_metatable()
+ local mt = {};
+ function mt.__index(f, upvalue)
+ local i, name, value = 0;
+ repeat
+ i = i + 1;
+ name, value = debug.getupvalue(f, i);
+ until name == upvalue or name == nil;
+ return value;
+ end
+ function mt.__newindex(f, upvalue, value)
+ local i, name = 0;
+ repeat
+ i = i + 1;
+ name = debug.getupvalue(f, i);
+ until name == upvalue or name == nil;
+ if name then
+ debug.setupvalue(f, i, value);
+ end
+ end
+ function mt.__tostring(f)
+ local info = debug.getinfo(f);
+ return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
+ end
+ debug.setmetatable(function() end, mt);
+ end
+
function init_global_state()
bare_sessions = {};
full_sessions = {};
hosts = {};
+ -- Global 'prosody' object
+ prosody = {};
+ local prosody = prosody;
+
prosody.bare_sessions = bare_sessions;
prosody.full_sessions = full_sessions;
prosody.hosts = hosts;
prosody.arg = _G.arg;
+ prosody.events = require "util.events".new();
+
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
-- 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
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"
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
init_logging();
check_dependencies();
sandbox_require();
+ set_function_metatable();
load_libraries();
init_global_state();
read_version();
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");
end
end
--- Global 'prosody' object
-prosody = {
- hosts = {},
- events = require "util.events".new(),
- platform = "posix"
-};
-local prosody = prosody;
-
config = require "core.configmanager"
do
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);
["not-running"] = "Prosody is not running";
}, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
-hosts = prosody.hosts;
+local events = require "util.events".new();
-local function make_host(hostname)
- return { events = prosody.events, users = require "core.usermanager".new_default_provider(hostname) };
-end
+hosts = prosody.hosts;
for hostname, config in pairs(config.getconfig()) do
- hosts[hostname] = make_host(hostname);
+ hosts[hostname] = { events = events };
end
require "core.modulemanager"
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
+ 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.");
+ end
+
local password = read_password();
if not password then return 1; end
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;
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;
return 1;
end
+ function commands.restart(arg)
+ if arg[1] == "--help" then
+ show_usage([[restart]], [[Restart a running Prosody server]]);
+ return 1;
+ end
+
+ local ret = commands.stop(arg);
+ if ret == 0 then
+ ret = commands.start(arg);
+ end
+ return ret;
+ end
+
-- ejabberdctl compatibility
function commands.register(arg)
};
function commands.addplugin(arg)
+ if not arg[1] or arg[1] == "--help" then
+ show_usage("addplugin URL", "Download and install a plugin from a URL");
+ return 1;
+ end
local url = arg[1];
if url:match("^http://") then
local http = require "socket.http";
print("");
print("Where COMMAND may be one of:\n");
- local hidden_commands = require "util.set".new{ "register", "unregister" };
- local commands_order = { "adduser", "passwd", "deluser" };
+ local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" };
+ local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart" };
local done = {};
-- sasl.lua v0.4
- -- Copyright (C) 2008-2009 Tobias Markmann
+ -- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
state = false : disabled
state = true : enabled
state = nil : non-existant
+
+plain:
+ function(username, realm)
+ return password, state;
+ end
+
+plain-test:
+ function(username, realm, password)
+ return true or false, state;
+ end
+
+digest-md5:
+ function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
+ -- implementations it's not
+ return digesthash, state;
+ end
+
+digest-md5-test:
+ function(username, domain, realm, encoding, digesthash)
+ return true or false, state;
+ end
]]
local method = {};
end
-- load the mechanisms
- load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
+ local load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
for _, mech in ipairs(load_mechs) do
local name = "util.sasl."..mech;
local m = require(name);
-- sasl.lua v0.4
--- Copyright (C) 2008-2010 Tobias Markmann
+-- Copyright (C) 2008-2009 Tobias Markmann
--
-- All rights reserved.
--
--=========================
--SASL DIGEST-MD5 according to RFC 2831
---[[
-Supported Authentication Backends
-
-digest-md5:
- function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
- -- implementations it's not
- return digesthash, state;
- end
-
-digest-md5-test:
- function(username, domain, realm, encoding, digesthash)
- return true or false, state;
- end
-]]
-
local function digest(self, message)
--TODO complete support for authzid
local function serialize(message)
local data = ""
- if type(message) ~= "table" then error("serialize needs an argument of type table.") end
-
-- testing all possible values
if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
-- sasl.lua v0.4
--- Copyright (C) 2008-2010 Tobias Markmann
+-- Copyright (C) 2008-2009 Tobias Markmann
--
-- All rights reserved.
--
local type = type
local string = string
local base64 = require "util.encodings".base64;
- local xor = require "bit".bxor
local hmac_sha1 = require "util.hmac".sha1;
local sha1 = require "util.hashes".sha1;
local generate_uuid = require "util.uuid".generate;
--=========================
--SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10
-
---[[
-Supported Authentication Backends
-
-scram-{MECH}:
- function(username, realm)
- return salted_password, iteration_count, salt, state;
- end
-]]
-
local default_i = 4096
local function bp( b )
return username;
end
-local function scram_gen(hash_name, H_f, HMAC_f)
- local function scram_hash(self, message)
- if not self.state then self["state"] = {} end
+local function scram_sha_1(self, message)
+ if not self.state then self["state"] = {} end
- if not self.state.name then
- -- we are processing client_first_message
- local client_first_message = message;
- self.state["client_first_message"] = client_first_message;
- self.state["name"] = client_first_message:match("n=(.+),r=")
- self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
+ if not self.state.name then
+ -- we are processing client_first_message
+ local client_first_message = message;
+ self.state["client_first_message"] = client_first_message;
+ self.state["name"] = client_first_message:match("n=(.+),r=")
+ self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
- if not self.state.name or not self.state.clientnonce then
- return "failure", "malformed-request";
- end
+ if not self.state.name or not self.state.clientnonce then
+ return "failure", "malformed-request";
+ end
- self.state.name = validate_username(self.state.name);
- if not self.state.name then
- log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
- return "failure", "malformed-request", "Invalid username.";
- end
+ self.state.name = validate_username(self.state.name);
+ if not self.state.name then
+ log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
+ return "failure", "malformed-request", "Invalid username.";
+ end
- self.state["servernonce"] = generate_uuid();
-
- -- retreive credentials
- if self.profile.plain then
- local password, state = self.profile.plain(self.state.name, self.realm)
- if state == nil then return "failure", "not-authorized"
- elseif state == false then return "failure", "account-disabled" end
-
- password = saslprep(password);
- if not password then
- log("debug", "Password violates SASLprep.");
- return "failure", "not-authorized", "Invalid password."
- end
- self.state.salt = generate_uuid();
- self.state.iteration_count = default_i;
- self.state.salted_password = Hi(HMAC_f, password, self.state.salt, default_i);
- elseif self.profile["scram_"..hash_name] then
- local salted_password, iteration_count, salt, state = self.profile["scram-"..hash_name](self.state.name, self.realm);
- if state == nil then return "failure", "not-authorized"
- elseif state == false then return "failure", "account-disabled" end
-
- self.state.salted_password = salted_password;
- self.state.iteration_count = iteration_count;
- self.state.salt = salt
- end
+ self.state["servernonce"] = generate_uuid();
+ self.state["salt"] = generate_uuid();
- local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count;
- self.state["server_first_message"] = server_first_message;
- return "challenge", server_first_message
- else
- if type(message) ~= "string" then return "failure", "malformed-request" end
- -- we are processing client_final_message
- local client_final_message = message;
+ local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i;
+ self.state["server_first_message"] = server_first_message;
+ return "challenge", server_first_message
+ else
+ if type(message) ~= "string" then return "failure", "malformed-request" end
+ -- we are processing client_final_message
+ local client_final_message = message;
- self.state["proof"] = client_final_message:match("p=(.+)");
- self.state["nonce"] = client_final_message:match("r=(.+),p=");
- self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
- if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
- return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
+ self.state["proof"] = client_final_message:match("p=(.+)");
+ self.state["nonce"] = client_final_message:match("r=(.+),p=");
+ self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
+ if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
+ return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
+ end
+
+ local password, state;
+ if self.profile.plain then
+ password, state = self.profile.plain(self.state.name, self.realm)
+ if state == nil then return "failure", "not-authorized"
+ elseif state == false then return "failure", "account-disabled" end
+ password = saslprep(password);
+ if not password then
+ log("debug", "Password violates SASLprep.");
+ return "failure", "not-authorized", "Invalid password."
end
+ end
- local SaltedPassword = self.state.salted_password;
- local ClientKey = HMAC_f(SaltedPassword, "Client Key")
- local ServerKey = HMAC_f(SaltedPassword, "Server Key")
- local StoredKey = H_f(ClientKey)
- local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
- local ClientSignature = HMAC_f(StoredKey, AuthMessage)
- local ClientProof = binaryXOR(ClientKey, ClientSignature)
- local ServerSignature = HMAC_f(ServerKey, AuthMessage)
+ local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i)
+ local ClientKey = hmac_sha1(SaltedPassword, "Client Key")
+ local ServerKey = hmac_sha1(SaltedPassword, "Server Key")
+ local StoredKey = sha1(ClientKey)
+ local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
+ local ClientSignature = hmac_sha1(StoredKey, AuthMessage)
+ local ClientProof = binaryXOR(ClientKey, ClientSignature)
+ local ServerSignature = hmac_sha1(ServerKey, AuthMessage)
- if base64.encode(ClientProof) == self.state.proof then
- local server_final_message = "v="..base64.encode(ServerSignature);
- self["username"] = self.state.name;
- return "success", server_final_message;
- else
- return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
- end
+ if base64.encode(ClientProof) == self.state.proof then
+ local server_final_message = "v="..base64.encode(ServerSignature);
+ self["username"] = self.state.name;
+ return "success", server_final_message;
+ else
+ return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
end
end
- return scram_hash;
end
function init(registerMechanism)
- local function registerSCRAMMechanism(hash_name, hash, hmac_hash)
- registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash));
- end
-
- registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
+ registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1);
end
return _M;