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.
-- Wordpress authentication backend for Prosody
--
-- Copyright (C) 2011 Waqas Hussain
-- Copyright (C) 2011 Kim Alvefur
--
local log = require "util.logger".init("auth_sql");
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 table_prefix = module:get_option_string("wordpress_table_prefix", "wp_");
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 `user_pass` FROM `"..table_prefix.."users` WHERE `user_login`=?", username);
if stmt then
for row in stmt:rows(true) do
return row.user_pass;
end
end
end
local itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
local function hashEncode64(input, count)
local output = "";
local i, value = 0, 0;
while true do
value = input:byte(i+1)
i = i+1;
local idx = value % 0x40 + 1;
output = output .. itoa64:sub(idx, idx);
if i < count then
value = value + input:byte(i+1) * 256;
end
local _ = value % (2^6);
local idx = ((value - _) / (2^6)) % 0x40 + 1
output = output .. itoa64:sub(idx, idx);
if i >= count then break; end
i = i+1;
if i < count then
value = value + input:byte(i+1) * 256 * 256;
end
local _ = value % (2^12);
local idx = ((value - _) / (2^12)) % 0x40 + 1
output = output .. itoa64:sub(idx, idx);
if i >= count then break; end
i = i+1;
local _ = value % (2^18);
local idx = ((value - _) / (2^18)) % 0x40 + 1
output = output .. itoa64:sub(idx, idx);
if not(i < count) then break; end
end
return output;
end
local function hashCryptPrivate(password, genSalt)
local output = "*";
if not genSalt:match("^%$P%$") then return output; end
local count_log2 = itoa64:find(genSalt:sub(4,4)) - 1;
if count_log2 < 7 or count_log2 > 30 then return output; end
local count = 2 ^ count_log2;
local salt = genSalt:sub(5, 12);
if #salt ~= 8 then return output; end
local hash = md5(salt..password);
while true do
hash = md5(hash..password);
if not(count > 1) then break; end
count = count-1;
end
output = genSalt:sub(1, 12);
output = output .. hashEncode64(hash, 16);
return output;
end
local function hashGensaltPrivate(input)
local iteration_count_log2 = 6;
local output = "$P$";
local idx = math.min(iteration_count_log2 + 5, 30) + 1;
output = output .. itoa64:sub(idx, idx);
output = output .. hashEncode64(input, 6);
return output;
end
local function wordpressCheckHash(password, hash)
if #hash == 32 then return hash == md5(password, true); end
return #hash == 34 and hashCryptPrivate(password, hash) == hash;
end
local function wordpressCreateHash(password)
local random = uuid_gen():sub(-6);
local salt = hashGensaltPrivate(random);
local hash = hashCryptPrivate(password, salt);
if #hash == 34 then return hash; end
return md5(password, true);
end
provider = {};
function provider.test_password(username, password)
local hash = get_password(username);
return hash and wordpressCheckHash(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 = wordpressCreateHash(password);
local stmt, err = setsql("UPDATE `"..table_prefix.."users` SET `user_pass`=? WHERE `user_login`=?", 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);