diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 70fa64dd3..5b4d45c88 100644 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -18,17 +18,6 @@ function safe_copy () { cd /usr/share/yunohost/templates/metronome -# Copy additional modules -files="ldap.lib.lua -mod_auth_ldap2.lua -mod_legacyauth.lua -mod_storage_ldap.lua -vcard.lib.lua" - -for file in $files; do - safe_copy modules/$file /usr/lib/metronome/modules/$file -done - # Copy configuration files main_domain=$(cat /etc/yunohost/current_host) cat metronome.cfg.lua.sed \ diff --git a/data/templates/metronome/modules/mod_auth_ldap2.lua b/data/templates/metronome/modules/mod_auth_ldap2.lua deleted file mode 100644 index 8c50a99fd..000000000 --- a/data/templates/metronome/modules/mod_auth_ldap2.lua +++ /dev/null @@ -1,81 +0,0 @@ --- vim:sts=4 sw=4 - --- Prosody IM --- Copyright (C) 2008-2010 Matthew Wild --- Copyright (C) 2008-2010 Waqas Hussain --- Copyright (C) 2012 Rob Hoelz --- --- This project is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- --- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua --- adapted to use common LDAP store - -local ldap = module:require 'ldap'; -local new_sasl = require 'util.sasl'.new; -local jsplit = require 'util.jid'.split; - -if not ldap then - return; -end - -local provider = {} - -function provider.test_password(username, password) - return ldap.bind(username, password); -end - -function provider.user_exists(username) - local params = ldap.getparams() - - local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username); - if params.user.usernamefield == 'mail' then - filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*'); - end - - return ldap.singlematch { - base = params.user.basedn, - filter = filter, - }; -end - -function provider.get_password(username) - return nil, "Passwords unavailable for LDAP."; -end - -function provider.set_password(username, password) - return nil, "Passwords unavailable for LDAP."; -end - -function provider.create_user(username, password) - return nil, "Account creation/modification not available with LDAP."; -end - -function provider.get_sasl_handler() - local testpass_authentication_profile = { - plain_test = function(sasl, username, password, realm) - return provider.test_password(username, password), true; - end, - mechanisms = { PLAIN = true }, - }; - return new_sasl(module.host, testpass_authentication_profile); -end - -function provider.is_admin(jid) - local admin_config = ldap.getparams().admin; - - if not admin_config then - return; - end - - local ld = ldap:getconnection(); - local username = jsplit(jid); - local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username); - - return ldap.singlematch { - base = admin_config.basedn, - filter = filter, - }; -end - -module:provides("auth", provider); diff --git a/data/templates/metronome/modules/mod_storage_ldap.lua b/data/templates/metronome/modules/mod_storage_ldap.lua deleted file mode 100644 index 17850a217..000000000 --- a/data/templates/metronome/modules/mod_storage_ldap.lua +++ /dev/null @@ -1,180 +0,0 @@ --- vim:sts=4 sw=4 - --- Prosody IM --- Copyright (C) 2008-2010 Matthew Wild --- Copyright (C) 2008-2010 Waqas Hussain --- Copyright (C) 2012 Rob Hoelz --- --- This project is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- - ----------------------------------------- --- Constants and such -- ----------------------------------------- - -local setmetatable = setmetatable; -local ldap = module:require 'ldap'; -local vcardlib = module:require 'vcard'; -local st = require 'util.stanza'; -local gettime = require 'socket'.gettime; - -if not ldap then - return; -end - -local CACHE_EXPIRY = 300; -local params = module:get_option('ldap'); - ----------------------------------------- --- Utility Functions -- ----------------------------------------- - -local function ldap_record_to_vcard(record) - return vcardlib.create { - record = record, - format = params.vcard_format, - } -end - -local get_alias_for_user; - -do - local user_cache; - local last_fetch_time; - - local function populate_user_cache() - local ld = ldap.getconnection(); - - local usernamefield = params.user.usernamefield; - local namefield = params.user.namefield; - - user_cache = {}; - - for _, attrs in ld:search { base = params.user.basedn, scope = 'onelevel', filter = params.user.filter } do - user_cache[attrs[usernamefield]] = attrs[namefield]; - end - last_fetch_time = gettime(); - end - - function get_alias_for_user(user) - if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then - user_cache = nil; - end - if not user_cache then - populate_user_cache(); - end - return user_cache[user]; - end -end - ----------------------------------------- --- General Setup -- ----------------------------------------- - -local ldap_store = {}; -ldap_store.__index = ldap_store; - -local adapters = { - roster = {}, - vcard = {}, -} - -for k, v in pairs(adapters) do - setmetatable(v, ldap_store); - v.__index = v; - v.name = k; -end - -function ldap_store:get(username) - return nil, "get method unimplemented on store '" .. tostring(self.name) .. "'" -end - -function ldap_store:set(username, data) - return nil, "LDAP storage is currently read-only"; -end - ----------------------------------------- --- Roster Storage Implementation -- ----------------------------------------- - -function adapters.roster:get(username) - local ld = ldap.getconnection(); - local contacts = {}; - - local memberfield = params.groups.memberfield; - local namefield = params.groups.namefield; - local filter = memberfield .. '=' .. tostring(username); - - local groups = {}; - for _, config in ipairs(params.groups) do - groups[ config[namefield] ] = config.name; - end - - -- XXX this kind of relies on the way we do groups at INOC - for _, attrs in ld:search { base = params.groups.basedn, scope = 'onelevel', filter = filter } do - if groups[ attrs[namefield] ] then - local members = attrs[memberfield]; - - for _, user in ipairs(members) do - if user ~= username then - local jid = user .. '@' .. module.host; - local record = contacts[jid]; - - if not record then - record = { - subscription = 'both', - groups = {}, - name = get_alias_for_user(user), - }; - contacts[jid] = record; - end - - record.groups[ groups[ attrs[namefield] ] ] = true; - end - end - end - end - - return contacts; -end - ----------------------------------------- --- vCard Storage Implementation -- ----------------------------------------- - -function adapters.vcard:get(username) - if not params.vcard_format then - return nil, ''; - end - - local ld = ldap.getconnection(); - local filter = params.user.usernamefield .. '=' .. tostring(username); - - local match = ldap.singlematch { - base = params.user.basedn, - filter = filter, - }; - if match then - match.jid = username .. '@' .. module.host - return st.preserialize(ldap_record_to_vcard(match)); - else - return nil, 'not found'; - end -end - ----------------------------------------- --- Driver Definition -- ----------------------------------------- - -local driver = {}; - -function driver:open(store, typ) - local adapter = adapters[store]; - - if adapter and not typ then - return adapter; - end - return nil, "unsupported-store"; -end -module:provides("storage", driver); diff --git a/debian/install b/debian/install index 82cd04ad5..ca458b688 100644 --- a/debian/install +++ b/debian/install @@ -4,5 +4,6 @@ data/hooks/* /usr/share/yunohost/hooks/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/apps/* /usr/share/yunohost/apps/ +lib/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ src/yunohost/*.py /usr/lib/moulinette/yunohost/ diff --git a/data/templates/metronome/modules/ldap.lib.lua b/lib/metronome/modules/ldap.lib.lua similarity index 100% rename from data/templates/metronome/modules/ldap.lib.lua rename to lib/metronome/modules/ldap.lib.lua diff --git a/lib/metronome/modules/mod_auth_ldap2.lua b/lib/metronome/modules/mod_auth_ldap2.lua new file mode 100644 index 000000000..bb62ca546 --- /dev/null +++ b/lib/metronome/modules/mod_auth_ldap2.lua @@ -0,0 +1,89 @@ +-- vim:sts=4 sw=4 + +-- Metronome IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2012 Rob Hoelz +-- Copyright (C) 2015 YUNOHOST.ORG +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- +-- https://github.com/YunoHost/yunohost-config-metronome/blob/unstable/lib/modules/mod_auth_ldap2.lua +-- adapted to use common LDAP store on Metronome + +local ldap = module:require 'ldap'; +local new_sasl = require 'util.sasl'.new; +local jsplit = require 'util.jid'.split; + +local log = module._log + +if not ldap then + return; +end + +function new_default_provider(host) + local provider = { name = "ldap2" }; + log("debug", "initializing ldap2 authentication provider for host '%s'", host); + + function provider.test_password(username, password) + return ldap.bind(username, password); + end + + function provider.user_exists(username) + local params = ldap.getparams() + + local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username); + if params.user.usernamefield == 'mail' then + filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*'); + end + + return ldap.singlematch { + base = params.user.basedn, + filter = filter, + }; + end + + function provider.get_password(username) + return nil, "Passwords unavailable for LDAP."; + end + + function provider.set_password(username, password) + return nil, "Passwords unavailable for LDAP."; + end + + function provider.create_user(username, password) + return nil, "Account creation/modification not available with LDAP."; + end + + function provider.get_sasl_handler() + local testpass_authentication_profile = { + plain_test = function(sasl, username, password, realm) + return provider.test_password(username, password), true; + end, + order = { "plain_test" }, + }; + return new_sasl(module.host, testpass_authentication_profile); + end + + function provider.is_admin(jid) + local admin_config = ldap.getparams().admin; + + if not admin_config then + return; + end + + local ld = ldap:getconnection(); + local username = jsplit(jid); + local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username); + + return ldap.singlematch { + base = admin_config.basedn, + filter = filter, + }; + end + + return provider; +end + +module:add_item("auth-provider", new_default_provider(module.host)); diff --git a/data/templates/metronome/modules/mod_legacyauth.lua b/lib/metronome/modules/mod_legacyauth.lua similarity index 100% rename from data/templates/metronome/modules/mod_legacyauth.lua rename to lib/metronome/modules/mod_legacyauth.lua diff --git a/lib/metronome/modules/mod_storage_ldap.lua b/lib/metronome/modules/mod_storage_ldap.lua new file mode 100644 index 000000000..83fb4d003 --- /dev/null +++ b/lib/metronome/modules/mod_storage_ldap.lua @@ -0,0 +1,243 @@ +-- vim:sts=4 sw=4 + +-- Metronome IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2012 Rob Hoelz +-- Copyright (C) 2015 YUNOHOST.ORG +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. + +---------------------------------------- +-- Constants and such -- +---------------------------------------- + +local setmetatable = setmetatable; + +local get_config = require "core.configmanager".get; +local ldap = module:require 'ldap'; +local vcardlib = module:require 'vcard'; +local st = require 'util.stanza'; +local gettime = require 'socket'.gettime; + +local log = module._log + +if not ldap then + return; +end + +local CACHE_EXPIRY = 300; + +---------------------------------------- +-- Utility Functions -- +---------------------------------------- + +local function ldap_record_to_vcard(record, format) + return vcardlib.create { + record = record, + format = format, + } +end + +local get_alias_for_user; + +do + local user_cache; + local last_fetch_time; + + local function populate_user_cache() + local user_c = get_config(module.host, 'ldap').user; + if not user_c then return; end + + local ld = ldap.getconnection(); + + local usernamefield = user_c.usernamefield; + local namefield = user_c.namefield; + + user_cache = {}; + + for _, attrs in ld:search { base = user_c.basedn, scope = 'onelevel', filter = user_c.filter } do + user_cache[attrs[usernamefield]] = attrs[namefield]; + end + last_fetch_time = gettime(); + end + + function get_alias_for_user(user) + if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then + user_cache = nil; + end + if not user_cache then + populate_user_cache(); + end + return user_cache[user]; + end +end + +---------------------------------------- +-- Base LDAP store class -- +---------------------------------------- + +local function ldap_store(config) + local self = {}; + local config = config; + + function self:get(username) + return nil, "Data getting is not available for this storage backend"; + end + + function self:set(username, data) + return nil, "Data setting is not available for this storage backend"; + end + + return self; +end + +local adapters = {}; + +---------------------------------------- +-- Roster Storage Implementation -- +---------------------------------------- + +adapters.roster = function (config) + -- Validate configuration requirements + if not config.groups then return nil; end + + local self = ldap_store(config) + + function self:get(username) + local ld = ldap.getconnection(); + local contacts = {}; + + local memberfield = config.groups.memberfield; + local namefield = config.groups.namefield; + local filter = memberfield .. '=' .. tostring(username); + + local groups = {}; + for _, config in ipairs(config.groups) do + groups[ config[namefield] ] = config.name; + end + + log("debug", "Found %d group(s) for user %s", select('#', groups), username) + + -- XXX this kind of relies on the way we do groups at INOC + for _, attrs in ld:search { base = config.groups.basedn, scope = 'onelevel', filter = filter } do + if groups[ attrs[namefield] ] then + local members = attrs[memberfield]; + + for _, user in ipairs(members) do + if user ~= username then + local jid = user .. '@' .. module.host; + local record = contacts[jid]; + + if not record then + record = { + subscription = 'both', + groups = {}, + name = get_alias_for_user(user), + }; + contacts[jid] = record; + end + + record.groups[ groups[ attrs[namefield] ] ] = true; + end + end + end + end + + return contacts; + end + + function self:set(username, data) + log("warn", "Setting data in Roster LDAP storage is not supported yet") + return nil, "not supported"; + end + + return self; +end + +---------------------------------------- +-- vCard Storage Implementation -- +---------------------------------------- + +adapters.vcard = function (config) + -- Validate configuration requirements + if not config.vcard_format or not config.user then return nil; end + + local self = ldap_store(config) + + function self:get(username) + local ld = ldap.getconnection(); + local filter = config.user.usernamefield .. '=' .. tostring(username); + + log("debug", "Retrieving vCard for user '%s'", username); + + local match = ldap.singlematch { + base = config.user.basedn, + filter = filter, + }; + if match then + match.jid = username .. '@' .. module.host + return st.preserialize(ldap_record_to_vcard(match, config.vcard_format)); + else + return nil, "username not found"; + end + end + + function self:set(username, data) + log("warn", "Setting data in vCard LDAP storage is not supported yet") + return nil, "not supported"; + end + + return self; +end + +---------------------------------------- +-- Driver Definition -- +---------------------------------------- + +cache = {}; + +local driver = { name = "ldap" }; + +function driver:open(store) + log("debug", "Opening ldap storage backend for host '%s' and store '%s'", module.host, store); + + if not cache[module.host] then + log("debug", "Caching adapters for the host '%s'", module.host); + + local ad_config = get_config(module.host, "ldap"); + local ad_cache = {}; + for k, v in pairs(adapters) do + ad_cache[k] = v(ad_config); + end + + cache[module.host] = ad_cache; + end + + local adapter = cache[module.host][store]; + + if not adapter then + log("info", "Unavailable adapter for store '%s'", store); + return nil, "unsupported-store"; + end + return adapter; +end + +function driver:stores(username, type, pattern) + return nil, "not implemented"; +end + +function driver:store_exists(username, datastore, type) + return nil, "not implemented"; +end + +function driver:purge(username) + return nil, "not implemented"; +end + +function driver:users() + return nil, "not implemented"; +end + +module:add_item("data-driver", driver); diff --git a/data/templates/metronome/modules/vcard.lib.lua b/lib/metronome/modules/vcard.lib.lua similarity index 100% rename from data/templates/metronome/modules/vcard.lib.lua rename to lib/metronome/modules/vcard.lib.lua