Skip to content

Instantly share code, notes, and snippets.

@arekinath
Last active October 13, 2015 14:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arekinath/4212756 to your computer and use it in GitHub Desktop.
Save arekinath/4212756 to your computer and use it in GitHub Desktop.
ejabberd hook module that forces new roster entries with a blank alias to be given a default name from LDAP.
%%%----------------------------------------------------------------------
%%% mod_roster_ldapname
%%%
%%% Hook module that forces new roster entries with a blank alias to
%%% be given a default name from LDAP.
%%%
%%% This fixes the common problem where a newly added contact has only
%%% its jid as a name until you right-click and go to 'get info' (in
%%% Pidgin, similarly in other clients). Some clients never load the
%%% vcard info into aliases (eg, bitlbee), and this also fixes things up
%%% for them.
%%%
%%% To compile it, put this in the src/ folder of the ejabberd source
%%% and do ./configure && make as usual. Then you can either 'make install',
%%% OR just copy the mod_roster_ldapname.beam file into your installation
%%% on its own (this is the easiest way to do already deployed servers).
%%%
%%% I have also uploaded a pre-built copy of it at
%%% http://xylem.cooperi.net/~alex/mod_roster_ldapname.beam
%%% (BEAM bytecode is platform-independent, so this should work for you, too)
%%%
%%% In your config, use something like
%%% {modules, [
%%% ...
%%% {mod_roster_ldapname, [{name_format, {"~s", ["displayName"]}}] }
%%% ]}
%%% the first part of name_format is just passed to io_lib:format (see its
%%% docs for details)
%%%
%%%----------------------------------------------------------------------
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(mod_roster_ldapname).
-author('alex@uq.edu.au').
-behaviour(gen_mod).
-export([start/2,
loop/3,
stop/1,
process_item/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("eldap/eldap.hrl").
-include("mod_roster.hrl").
-define(PROCNAME, ejabberd_roster_ldapname).
-record(ldap_config, {eldap_id=none, servers,
backups,
port,
tls_options,
dn,
base,
password,
uids,
user_filter}).
start(Host, Opts) ->
InLdap = get_ldap_config(Host, Opts),
EldapID = atom_to_list(gen_mod:get_module_proc(Host, ?PROCNAME)),
Ldap = InLdap#ldap_config{eldap_id = EldapID},
Format = gen_mod:get_opt(name_format, Opts, {"~s", ["displayName"]}),
ejabberd_hooks:add(roster_process_item, Host,
?MODULE, process_item, 60),
eldap_pool:start_link(EldapID,
Ldap#ldap_config.servers,
Ldap#ldap_config.backups,
Ldap#ldap_config.port,
Ldap#ldap_config.dn,
Ldap#ldap_config.password,
Ldap#ldap_config.tls_options),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, loop, [Host, Ldap, Format])).
% yes, we have a server that does almost nothing! isn't it great
loop(Host, Ldap, Format) ->
receive
{Pid, get_state} ->
Pid ! {state, Ldap, Format},
loop(Host, Ldap, Format);
_ ->
loop(Host, Ldap, Format)
end.
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ejabberd_hooks:delete(roster_process_item, Host, ?MODULE, process_item, 60),
exit(whereis(Proc), stop),
{wait, Proc}.
% the main guts of the module
process_item(Item, Server) ->
% get our ldap parameters and format from the loop() process
Proc = gen_mod:get_module_proc(Server, ?PROCNAME),
Pid = whereis(Proc),
if is_pid(Pid) ->
Pid ! {self(), get_state},
{Ldap, Format} = receive {state, L, F} -> {L, F} end,
{Fmt, FmtArgs} = Format,
% if no name is set and we're saving the roster entry, rename it
case Item#roster.jid of
{LUser, Server, _} ->
Combined = LUser ++ "@" ++ Server,
DoRename = case Item#roster.name of
"" -> true;
LUser -> true;
Combined -> true;
_ -> false
end,
if DoRename ->
NewName = case find_ldap_user(LUser, Ldap, FmtArgs) of
#eldap_entry{attributes = Attributes} ->
Inserts = lists:map(fun(Attr) ->
proplists:get_value(Attr, Attributes)
end, FmtArgs),
lists:flatten(io_lib:format(Fmt, Inserts));
_ ->
Item#roster.name
end,
Item#roster{name = NewName};
true ->
Item
end;
_ ->
Item
end;
true ->
Item
end.
% the rest here is blatantly copied from mod_vcard_ldap and mod_shared_roster_ldap. ;)
find_ldap_user(User, State, Attrs) ->
Base = State#ldap_config.base,
RFC2254_Filter = State#ldap_config.user_filter,
Eldap_ID = State#ldap_config.eldap_id,
case eldap_filter:parse(RFC2254_Filter, [{"%u", User}]) of
{ok, EldapFilter} ->
case eldap_pool:search(Eldap_ID,
[{base, Base},
{filter, EldapFilter},
{attributes, Attrs}]) of
#eldap_search_result{entries = [E | _]} ->
E;
_ ->
false
end;
_ ->
false
end.
get_ldap_config(Host, Opts) ->
LDAPServers = case gen_mod:get_opt(ldap_servers, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_servers, Host});
S -> S
end,
LDAPBackups = case gen_mod:get_opt(ldap_backups, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_servers, Host});
Backups -> Backups
end,
LDAPEncrypt = case gen_mod:get_opt(ldap_encrypt, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_encrypt, Host});
E -> E
end,
LDAPTLSVerify = case gen_mod:get_opt(ldap_tls_verify, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_tls_verify, Host});
Verify -> Verify
end,
LDAPTLSCAFile = case gen_mod:get_opt(ldap_tls_cacertfile, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_tls_cacertfile, Host});
CAFile -> CAFile
end,
LDAPTLSDepth = case gen_mod:get_opt(ldap_tls_depth, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_tls_depth, Host});
Depth ->
Depth
end,
LDAPPortTemp = case gen_mod:get_opt(ldap_port, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_port, Host});
PT -> PT
end,
LDAPPort = case LDAPPortTemp of
undefined ->
case LDAPEncrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end;
P -> P
end,
LDAPBase = case gen_mod:get_opt(ldap_base, Opts, undefined) of
undefined ->
ejabberd_config:get_local_option({ldap_base, Host});
B -> B
end,
UIDs = case gen_mod:get_opt(ldap_uids, Opts, undefined) of
undefined ->
case ejabberd_config:get_local_option({ldap_uids, Host}) of
undefined -> [{"uid", "%u"}];
UI -> eldap_utils:uids_domain_subst(Host, UI)
end;
UI -> eldap_utils:uids_domain_subst(Host, UI)
end,
RootDN = case gen_mod:get_opt(ldap_rootdn, Opts, undefined) of
undefined ->
case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
undefined -> "";
RDN -> RDN
end;
RDN -> RDN
end,
Password = case gen_mod:get_opt(ldap_password, Opts, undefined) of
undefined ->
case ejabberd_config:get_local_option({ldap_password, Host}) of
undefined -> "";
Pass -> Pass
end;
Pass -> Pass
end,
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
UserFilter = case gen_mod:get_opt(ldap_filter, Opts, undefined) of
undefined ->
case ejabberd_config:get_local_option({ldap_filter, Host}) of
undefined -> SubFilter;
"" -> SubFilter;
F ->
eldap_utils:check_filter(F),
"(&" ++ SubFilter ++ F ++ ")"
end;
"" -> SubFilter;
F ->
eldap_utils:check_filter(F),
"(&" ++ SubFilter ++ F ++ ")"
end,
#ldap_config{
servers = LDAPServers,
backups = LDAPBackups,
port = LDAPPort,
tls_options = [{encrypt, LDAPEncrypt},
{tls_verify, LDAPTLSVerify},
{tls_cacertfile, LDAPTLSCAFile},
{tls_depth, LDAPTLSDepth}],
dn = RootDN,
base = LDAPBase,
password = Password,
uids = UIDs,
user_filter = UserFilter
}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment