--help This help.
--ostype=OS Use one of the OS presets.
- May be one of: debian, macosx
- May be one of: debian, macosx, linux, freebsd
++ May be one of: debian, macosx, linux
--prefix=DIR Prefix where Prosody should be installed.
Default is $PREFIX
--sysconfdir=DIR Location where the config file should be installed.
shift
done
- LUA_INCDIR_SET=yes
+if [ "$OSTYPE_SET" = "yes" ]
+then
+ if [ "$OSTYPE" = "debian" ]
+ then LUA_SUFFIX="5.1";
+ LUA_SUFFIX_SET=yes
+ LUA_INCDIR=/usr/include/lua5.1;
+ LUA_INCDIR_SET=yes
+ fi
+ if [ "$OSTYPE" = "macosx" ]
+ then LUA_INCDIR=/usr/local/include;
- LFLAGS="-bundle -undefined dynamic_lookup"
- fi
++ LUA_INCDIR_SET=yes
+ LUA_LIBDIR=/usr/local/lib
+ LUA_LIBDIR_SET=yes
+ CFLAGS="-Wall"
++ LDFLAGS="-bundle -undefined dynamic_lookup"
++ fi
++ if [ "$OSTYPE" = "linux" ]
++ then LUA_INCDIR=/usr/local/include;
++ LUA_INCDIR_SET=yes
++ LUA_LIBDIR=/usr/local/lib
++ LUA_LIBDIR_SET=yes
++ CFLAGS="-Wall -fPIC"
++ LDFLAGS="-shared"
++ fi
+fi
+
if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
-- 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, 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)
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)
- local roster = load_roster(username, host);
- 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");
+ return item and (item.subscription == "from" or item.subscription == "both"), err;
end
function is_contact_pending_in(username, host, jid)
end
end, "_xmpp-server._tcp."..connect_host..".", "SRV");
- log("debug", "DNS lookup for %s sent, waiting for response before we can connect", to_host);
+ -- Set handler for DNS timeout
+ add_task(dns_timeout, function ()
+ if handle then
+ adns.cancel(handle, true);
+ end
+ end);
+
return true; -- Attempt in progress
elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
host_session.srv_choice = host_session.srv_choice + 1;
local config_get = require "core.configmanager".get;
local nameprep = require "util.encodings".stringprep.nameprep;
local resourceprep = require "util.encodings".stringprep.resourceprep;
+ local nodeprep = require "util.encodings".stringprep.nodeprep;
-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;
local jid_bare = require "util.jid".bare;
local config = require "core.configmanager";
local hosts = hosts;
-local sasl_new = require "util.sasl".new;
- local prosody = _G.prosody;
+ local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
-local prosody = _G.prosody;
-
-local setmetatable = setmetatable;
-
-local default_provider = "internal_plain";
-
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
-function new_null_provider()
- local function dummy() end;
- local function dummy_get_sasl_handler() return sasl_new(nil, {}); end
- return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, { __index = function() return dummy; end });
--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 initialize_host(host)
- local host_session = hosts[host];
- host_session.events.add_handler("item-added/auth-provider", function (event)
- local provider = event.item;
- local auth_provider = config.get(host, "core", "authentication") or default_provider;
- if provider.name == auth_provider then
- host_session.users = provider;
- end
- if host_session.users ~= nil and host_session.users.name ~= nil then
- log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name);
- end
- end);
- host_session.events.add_handler("item-removed/auth-provider", function (event)
- local provider = event.item;
- if host_session.users == provider then
- host_session.users = new_null_provider();
- end
- end);
- host_session.users = new_null_provider(); -- Start with the default usermanager provider
- local auth_provider = config.get(host, "core", "authentication") or default_provider;
- if auth_provider ~= "null" then
- modulemanager.load(host, "auth_"..auth_provider);
- end
-end;
-prosody.events.add_handler("host-activated", initialize_host, 100);
-prosody.events.add_handler("component-activated", initialize_host, 100);
++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 {};
+
-function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
-
-function test_password(username, host, password)
- return hosts[host].users.test_password(username, password);
++ 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 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 is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
- return datamanager.store(username, host, "accounts", {password = password});
++ -- compare
++ if password == pwd then
++ return true;
++ else
++ return nil, "Auth failed. Invalid username or 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)
-- 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, password, host)
- return hosts[host].users.set_password(username, password);
+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
++ local account, err = datamanager.load(username, host, "accounts");
++ return (account or err) ~= 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_sasl_handler(host)
- return hosts[host].users.get_sasl_handler();
-end
-
-function get_provider(host)
- return hosts[host].users;
+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
- local is_admin;
- jid = jid_bare(jid);
+ host = host or "*";
-
- local host_admins = config.get(host, "core", "admins");
- local global_admins = config.get("*", "core", "admins");
-
- if host_admins and host_admins ~= global_admins then
- if type(host_admins) == "table" then
- for _,admin in ipairs(host_admins) do
- if admin == jid then
- is_admin = true;
- break;
- end
- end
- elseif admins then
- log("error", "Option 'admins' for host '%s' is not a list", host);
- end
++ local admins = config.get(host, "core", "admins");
++ if host ~= "*" and admins == config.get("*", "core", "admins") then
+ return nil;
end
-
- if not is_admin and global_admins then
- if type(global_admins) == "table" then
- for _,admin in ipairs(global_admins) do
- if admin == jid then
- is_admin = true;
- break;
- end
- end
- elseif admins then
- log("error", "Global option 'admins' is not a list");
++ if type(admins) == "table" then
++ jid = jid_bare(jid);
++ for _,admin in ipairs(admins) do
++ if admin == jid then return true; end
+ end
- end
-
- -- Still not an admin, check with auth provider
- if not is_admin and host ~= "*" and hosts[host].users.is_admin then
- is_admin = hosts[host].users.is_admin(jid);
- end
- return is_admin or false;
++ 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;
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
- local function restricted_handler()
- cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
- end
-
- if lxp_supports_doctype then
- xml_handlers.StartDoctypeDecl = restricted_handler;
- end
- xml_handlers.Comment = restricted_handler;
- xml_handlers.StartCdataSection = restricted_handler;
- xml_handlers.ProcessingInstruction = restricted_handler;
-
+ end
++
++ local function restricted_handler()
++ cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
++ end
++
++ if lxp_supports_doctype then
++ xml_handlers.StartDoctypeDecl = restricted_handler;
++ end
++ xml_handlers.Comment = restricted_handler;
++ xml_handlers.StartCdataSection = restricted_handler;
++ xml_handlers.ProcessingInstruction = restricted_handler;
+
return xml_handlers;
end
if drain then
drain(handler)
end
- _ = needtls and handler:starttls(nil)
++ _ = needtls and handler:starttls(nil, true)
_ = toclose and handler:close( )
return true
elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write
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;
-- 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)
-- End of session methods --
-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
- end
-
- local handlestanza = stream_callbacks.handlestanza;
- function session.dispatch_stanza(session, stanza)
- return handlestanza(session, stanza);
- end
-end
-
function xmppclient.onincoming(conn, data)
local session = sessions[conn];
- if session then
- session.data(data);
+ 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;
+ end
+
+ if opt_keepalives ~= nil then
+ 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
if not is_admin(stanza.attr.from) then
-- Not an admin? Not allowed!
- module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from);
+ module:log("warn", "Non-admin %s tried to send server announcement", tostring(jid.bare(stanza.attr.from)));
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
return;
end
-- setup compression for a stream
local function setup_compression(session, deflate_stream)
- add_filter(session, "bytes/out", function(t)
- local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
- if status == false then
- module:log("warn", "%s", tostring(compressed));
- session:close({
- condition = "undefined-condition";
- text = compressed;
- extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
- });
- return;
- end
- return compressed;
- end);
+ 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
end
-- setup decompression for a stream
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;
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();
local sessionmanager = require "core.sessionmanager";
local offlinemanager = require "core.offlinemanager";
- 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 _core_route_stanza = core_route_stanza;
+local core_route_stanza;
+function core_route_stanza(origin, stanza)
+ if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+ local node, host = jid_split(stanza.attr.to);
+ host = hosts[host];
+ if node and host and host.type == "local" then
+ handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+ return;
+ end
+ end
+ _core_route_stanza(origin, stanza);
+end
+
+ 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
end
end
-function handle_normal_presence(origin, stanza)
+ local ignore_presence_priority = module:get_option("ignore_presence_priority");
+
+function handle_normal_presence(origin, stanza, core_route_stanza)
+ if ignore_presence_priority then
+ local priority = stanza:child_with_name("priority");
+ if priority and priority[1] ~= "0" then
+ for i=#priority.tags,1,-1 do priority.tags[i] = nil; end
+ for i=#priority,1,-1 do priority[i] = nil; end
+ priority[1] = "0";
+ end
+ end
if full_sessions[origin.full_jid] then -- if user is still connected
origin.send(stanza); -- reflect their presence back to them
end
stanza.attr.from, stanza.attr.to = from_bare, to_bare;
log("debug", "inbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
- if not node then
- log("debug", "dropping presence sent to host or invalid address '%s'", tostring(to_bare));
- end
-
if stanza.attr.type == "probe" then
- if rostermanager.is_contact_subscribed(node, host, from_bare) then
+ local result, err = rostermanager.is_contact_subscribed(node, host, from_bare);
+ if result then
- if 0 == send_presence_of_available_resources(node, host, st_from, origin) then
- core_post_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"}), true); -- TODO send last activity
+ if 0 == send_presence_of_available_resources(node, host, st_from, origin, core_route_stanza) then
- core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
++ core_route_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"})); -- TODO send last activity
end
- else
+ elseif not err then
- core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}), true);
+ core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
end
elseif stanza.attr.type == "subscribe" then
if rostermanager.is_contact_subscribed(node, host, from_bare) then
local username, host = session.username, session.host;
--session.send(st.error_reply(stanza, "cancel", "not-allowed"));
--return;
- usermanager_set_password(username, host, nil); -- Disable account
- usermanager_set_password(username, nil, host); -- Disable account
++ --usermanager_set_password(username, host, nil); -- Disable account
-- FIXME the disabling currently allows a different user to recreate the account
-- we should add an in-memory account block mode when we have threading
session.send(st.reply(stanza));
local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
local sasl_backend = module:get_option("sasl_backend") or "builtin";
-local anonymous_login = module:get_option("anonymous_login");
+ -- Cyrus config options
+ local require_provisioning = module:get_option("cyrus_require_provisioning") or false;
+ local cyrus_service_realm = module:get_option("cyrus_service_realm");
+ local cyrus_service_name = module:get_option("cyrus_service_name");
+ local cyrus_application_name = module:get_option("cyrus_application_name");
+
local log = module._log;
local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
session.sasl_handler = session.sasl_handler:clean_clone();
elseif status == "success" then
local username = nodeprep(session.sasl_handler.username);
- if not username then -- TODO move this to sessionmanager
- module:log("warn", "SASL succeeded but we didn't get a username!");
- session.sasl_handler = nil;
- session:reset_stream();
- return;
+
+ if not(require_provisioning) or usermanager_user_exists(username, session.host) then
- local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
- if ok then
++ local aret, err = sm_make_authenticated(session, session.sasl_handler.username);
++ if aret then
+ session.sasl_handler = nil;
+ session:reset_stream();
+ else
+ module:log("warn", "SASL succeeded but username was invalid");
+ session.sasl_handler = session.sasl_handler:clean_clone();
+ return "failure", "not-authorized", "User authenticated successfully, but username was invalid";
+ end
+ else
+ module:log("warn", "SASL succeeded but we don't have an account provisioned for %s", username);
+ session.sasl_handler = session.sasl_handler:clean_clone();
+ return "failure", "not-authorized", "User authenticated successfully, but not provisioned for XMPP";
end
- sm_make_authenticated(session, session.sasl_handler.username);
- session.sasl_handler = nil;
- session:reset_stream();
end
+ return status, ret, err_msg;
end
local function sasl_handler(session, stanza)
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);
- while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
++ t_insert(history, st.preserialize(stanza));
+ while #history > history_length do t_remove(history, 1) end
end
end
function room_mt:broadcast_except_nick(stanza, nick)
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";
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
- local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role);
- if not allowed then return allowed, err_type, err_condition; 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
local p = st.presence({from = occupant_jid})
:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
:tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
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
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"
full_sessions = {};
hosts = {};
++ -- Global 'prosody' object
++ prosody = {};
++ local prosody = prosody;
++
prosody.bare_sessions = bare_sessions;
prosody.full_sessions = full_sessions;
prosody.hosts = hosts;
prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR,
plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
- local path_sep = package.config:sub(1,1);
- local rel_path_start = ".."..path_sep;
- function prosody.resolve_relative_path(path)
- if path then
- local is_relative;
- if path_sep == "/" and path:sub(1,1) ~= "/" then
- is_relative = true;
- elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\") then
- is_relative = true;
- end
- if is_relative then
- return CFG_CONFIGDIR..path_sep..path;
- end
- end
- return path;
- end
-
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"
end
function prepare_to_start()
- log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
+ log("info", "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_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) };
- return { events = prosody.events, users = require "core.usermanager".new_null_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;
--=========================
--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
return password, state;
end
- plain-test:
+ plain_test:
- function(username, password, realm)
+ function(username, realm, password)
return true or false, state;
end
-
- plain-hashed:
- function(username, realm)
- return hashed_password, hash_function, state;
- end
]]
local function plain(self, message)
if self.profile.plain then
local correct_password;
correct_password, state = self.profile.plain(authentication, self.realm);
- correct = (correct_password == password);
+ if correct_password == password then correct = true; else correct = false; end
elseif self.profile.plain_test then
- correct, state = self.profile.plain_test(authentication, password, self.realm);
+ correct, state = self.profile.plain_test(authentication, self.realm, password);
- elseif self.profile.plain_hashed then
- local hashed_password, hash_f;
- hashed_password, hash_f, state = self.profile.plain_hashed(authentication, self.realm);
- if hashed_password == hash_f(password) then correct = true; else correct = false; end
end
self.username = authentication
--[[
Supported Authentication Backends
- scram-{MECH}:
+ scram_{MECH}:
+ -- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_'
function(username, realm)
- return stored_key, server_key, iteration_count, salt, state;
+ return salted_password, iteration_count, salt, state;
end
]]
return username;
end
-local function hashprep(hashname)
- return hashname:lower():gsub("-", "_");
++local function hashprep( hashname )
++ local hash = hashname:lower()
++ hash = hash:gsub("-", "_")
++ return hash
+ end
+
-function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
++function saltedPasswordSHA1(password, salt, iteration_count)
++ local salted_password
+ if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then
+ return false, "inappropriate argument types"
+ end
+ if iteration_count < 4096 then
+ log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.")
+ end
- local salted_password = Hi(hmac_sha1, password, salt, iteration_count);
- local stored_key = sha1(hmac_sha1(salted_password, "Client Key"))
- local server_key = hmac_sha1(salted_password, "Server Key");
- return true, stored_key, server_key
++
++ return true, Hi(hmac_sha1, password, salt, iteration_count);
+ 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
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);
+
+ local succ = false;
- succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count);
++ succ, self.state.salted_password = saltedPasswordSHA1(password, self.state.salt, default_i, self.state.iteration_count);
+ if not succ then
- log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key);
++ log("error", "Generating salted password failed. Reason: %s", self.state.salted_password);
+ return "failure", "temporary-auth-failure";
+ end
+ elseif self.profile["scram_"..hashprep(hash_name)] then
- local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm);
++ local salted_password, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm);
if state == nil then return "failure", "not-authorized"
elseif state == false then return "failure", "account-disabled" end
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
-
+
+ if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then
+ return "failure", "malformed-request", "Wrong nonce in client-final-message.";
+ end
+
- local ServerKey = self.state.server_key;
- local StoredKey = self.state.stored_key;
-
+ 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 ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof))
+ local ClientProof = binaryXOR(ClientKey, ClientSignature)
local ServerSignature = HMAC_f(ServerKey, AuthMessage)
-
+
- if StoredKey == H_f(ClientKey) then
+ 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;