changeset 423:524dda2ecb6a

mod_auth_wordpress: Initial commit.
author Kim Alvefur <zash@zash.se>
date Sun, 11 Sep 2011 21:42:16 +0200
parents 1082856e4612
children 22935ef37284
files mod_auth_wordpress/mod_auth_wordpress.lua
diffstat 1 files changed, 252 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_auth_wordpress/mod_auth_wordpress.lua	Sun Sep 11 21:42:16 2011 +0200
@@ -0,0 +1,252 @@
+-- Wordpress authentication backend for Prosody
+--
+-- Copyright (C) 2011 Waqas Hussain
+--
+
+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 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 `wp_users` WHERE `user_login`=?", username);
+	if stmt then
+		for row in stmt:rows(true) do
+			return row.user_password;
+		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 = "$H$";
+	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 phpbbCheckHash(password, hash)
+	if #hash == 32 then return hash == md5(password, true); end -- legacy PHPBB2 hash
+	return #hash == 34 and hashCryptPrivate(password, hash) == hash;
+end
+local function phpbbCreateHash(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 = { name = "wordpress" };
+
+function provider.test_password(username, password)
+	local hash = get_password(username);
+	return hash and phpbbCheckHash(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 = phpbbCreateHash(password);
+	local stmt, err = setsql("UPDATE `wp_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:add_item("auth-provider", provider);
+