mod_log_ringbuffer: Detach event handlers on logging reload (thanks Menel)
Otherwise the global event handlers accumulate, one added each time
logging is reoladed, and each invocation of the signal or event triggers
one dump of each created ringbuffer.
-- Joomla authentication backend for Prosody
--
-- Copyright (C) 2011 Waqas Hussain
--
local new_sasl = require "util.sasl".new;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local saslprep = require "util.encodings".stringprep.saslprep;
local DBI = require "DBI"
local md5 = require "util.hashes".md5;
local uuid_gen = require "util.uuid".generate;
local connection;
local params = module:get_option("sql");
local prefix = params and params.prefix or "jos_";
local resolve_relative_path = require "core.configmanager".resolve_relative_path;
local function test_connection()
if not connection then return nil; end
if connection:ping() then
return true;
else
module:log("debug", "Database connection closed");
connection = nil;
end
end
local function connect()
if not test_connection() then
prosody.unlock_globals();
local dbh, err = DBI.Connect(
params.driver, params.database,
params.username, params.password,
params.host, params.port
);
prosody.lock_globals();
if not dbh then
module:log("debug", "Database connection failed: %s", tostring(err));
return nil, err;
end
module:log("debug", "Successfully connected to database");
dbh:autocommit(true); -- don't run in transaction
connection = dbh;
return connection;
end
end
do -- process options to get a db connection
params = params or { driver = "SQLite3" };
if params.driver == "SQLite3" then
params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
end
assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
assert(connect());
end
local function getsql(sql, ...)
if params.driver == "PostgreSQL" then
sql = sql:gsub("`", "\"");
end
if not test_connection() then connect(); end
-- do prepared statement stuff
local stmt, err = connection:prepare(sql);
if not stmt and not test_connection() then error("connection failed"); end
if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
-- run query
local ok, err = stmt:execute(...);
if not ok and not test_connection() then error("connection failed"); end
if not ok then return nil, err; end
return stmt;
end
local function setsql(sql, ...)
local stmt, err = getsql(sql, ...);
if not stmt then return stmt, err; end
return stmt:affected();
end
local function get_password(username)
local stmt, err = getsql("SELECT `password` FROM `"..prefix.."users` WHERE `username`=?", username);
if stmt then
for row in stmt:rows(true) do
return row.password;
end
end
end
local function getCryptedPassword(plaintext, salt)
local salted = plaintext..salt;
return md5(salted, true);
end
local function joomlaCheckHash(password, hash)
local crypt, salt = hash:match("^([^:]*):(.*)$");
return (crypt or hash) == getCryptedPassword(password, salt or '');
end
local function joomlaCreateHash(password)
local salt = uuid_gen():gsub("%-", "");
local crypt = getCryptedPassword(password, salt);
return crypt..':'..salt;
end
provider = {};
function provider.test_password(username, password)
local hash = get_password(username);
return hash and joomlaCheckHash(password, hash);
end
function provider.user_exists(username)
module:log("debug", "test user %s existence", username);
return get_password(username) and true;
end
function provider.get_password(username)
return nil, "Getting password is not supported.";
end
function provider.set_password(username, password)
local hash = joomlaCreateHash(password);
local stmt, err = setsql("UPDATE `"..prefix.."users` SET `password`=? WHERE `username`=?", hash, username);
return stmt and true, err;
end
function provider.create_user(username, password)
return nil, "Account creation/modification not supported.";
end
local escapes = {
[" "] = "\\20";
['"'] = "\\22";
["&"] = "\\26";
["'"] = "\\27";
["/"] = "\\2f";
[":"] = "\\3a";
["<"] = "\\3c";
[">"] = "\\3e";
["@"] = "\\40";
["\\"] = "\\5c";
};
local unescapes = {};
for k,v in pairs(escapes) do unescapes[v] = k; end
local function jid_escape(s) return s and (s:gsub(".", escapes)); end
local function jid_unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end
function provider.get_sasl_handler()
local sasl = {};
function sasl:clean_clone() return provider.get_sasl_handler(); end
function sasl:mechanisms() return { PLAIN = true; }; end
function sasl:select(mechanism)
if not self.selected and mechanism == "PLAIN" then
self.selected = mechanism;
return true;
end
end
function sasl:process(message)
if not message then return "failure", "malformed-request"; end
local authorization, authentication, password = message:match("^([^%z]*)%z([^%z]+)%z([^%z]+)");
if not authorization then return "failure", "malformed-request"; end
authentication = saslprep(authentication);
password = saslprep(password);
if (not password) or (password == "") or (not authentication) or (authentication == "") then
return "failure", "malformed-request", "Invalid username or password.";
end
local function test(authentication)
local prepped = nodeprep(authentication);
local normalized = jid_unescape(prepped);
return normalized and provider.test_password(normalized, password) and prepped;
end
local username = test(authentication) or test(jid_escape(authentication));
if username then
self.username = username;
return "success";
end
return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent.";
end
return sasl;
end
module:provides("auth", provider);