-- GNU social Authentication module for Prosody IM -- Copyright (C) 2011 Tomasz Sterna -- Copyright (C) 2011 Waqas Hussain -- Copyright (C) 2013 Kim Alvefur -- Copyright (C) 2015 Mikael Nordfeldth local new_sasl = require "util.sasl".new; local nodeprep = require "util.encodings".stringprep.nodeprep; local DBI = require "DBI" local md5 = require "util.hashes".md5; local posix = require "posix" local connection; local params = module:get_option("auth_sql", 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 get_password(username) -- local stmt, err = getsql("SELECT `password`, `sessionid` FROM `user` WHERE `nickname`=?", username); local stmt, err = getsql("SELECT `password` FROM `user` WHERE `nickname`=?", username); if stmt then for row in stmt:rows(true) do -- return row.password, row.sessionid; return row.password; end else return nil, err; end end provider = { name = "gnusocial" }; function provider.test_password(username, password) -- local hash, sessionid = get_password(username); local hash = get_password(username); module:log("debug", "mod_auth_gnusocial username: %s hash: %s", username, hash); if not hash or #password == 0 then return nil; end -- return hash == posix.crypt(password, hash) or password == sessionid; return hash == posix.crypt(password, hash); end function provider.get_password(username) return get_password(username); end function provider.set_password() return nil, "Setting password is not supported."; end function provider.user_exists(username) return get_password(username) and true; end function provider.create_user() return nil, "Account creation/modification not supported."; end function provider.users() local stmt, err = getsql("SELECT `nickname` FROM `user` WHERE `password` IS NOT NULL", module.host); if stmt then local next, state = stmt:rows(true) return function() for row in next, state do return row.nickname; end end end return stmt, err; end function provider.get_sasl_handler() local profile = { plain_test = function(sasl, username, password, realm) local prepped_username = nodeprep(username); if not prepped_username then module:log("debug", "NODEprep failed on username: %s", username); return nil; end if provider.test_password(prepped_username, password) then return true, true end end }; return new_sasl(module.host, profile); end module:add_item("auth-provider", provider);