-- 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);