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 = load_roster(username, host);
+ 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)
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 config = require "core.configmanager";
local hosts = hosts;
-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";
-
module "usermanager"
- function new_null_provider()
- local function dummy() end;
- return setmetatable({name = "null"}, { __index = function() return dummy; end });
- end
-local new_default_provider;
-
-prosody.events.add_handler("host-activated", function (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);
-
+ local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
- 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 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 {};
+
- function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
-
- function test_password(username, password, host)
- 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});
- 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)
- 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
- 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)
- local is_admin;
- jid = jid_bare(jid);
- return hosts[host].users.is_admin(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
return _M;
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
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
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_null_provider(hostname) };
- end
+ hosts = prosody.hosts;
for hostname, config in pairs(config.getconfig()) do
- hosts[hostname] = make_host(hostname);
- hosts[hostname] = { events = prosody.events };
++ hosts[hostname] = { events = events };
end
require "core.modulemanager"
--=========================
--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;