# HG changeset patch # User Waqas Hussain # Date 1315767500 -18000 # Node ID 1082856e4612829e9f61c7ef2d5a6f7488412330 # Parent 816d8e3e83a3242578b3af0f210445e23ba48055 mod_auth_joomla: Initial commit. diff -r 816d8e3e83a3 -r 1082856e4612 mod_auth_joomla/mod_auth_joomla.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_joomla/mod_auth_joomla.lua Sun Sep 11 23:58:20 2011 +0500 @@ -0,0 +1,180 @@ +-- 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 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 `jos_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) + return md5(plaintext..salt); +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 = { name = "joomla" }; + +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 `jos_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:add_item("auth-provider", provider); +