You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-overlay/net-im/calculate-ejabberd/files/mod_shared_roster_ldap.patch

558 lines
16 KiB

diff -uNr ejabberd-2.0.2-beta1.ORIG/src/mod_shared_roster_ldap.erl ejabberd-2.0.2-beta1/src/mod_shared_roster_ldap.erl
--- mod_shared_roster_ldap.erl
+++ mod_shared_roster_ldap.erl
@@ -0,0 +1,553 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_shared_roster_ldap.erl
+%%% Author : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose : LDAP shared roster management
+%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id : $Id: mod_shared_roster.erl 24 2005-04-14 01:15:31Z alexey $
+%%%----------------------------------------------------------------------
+
+%%%----------------------------------------------------------------------
+%%% Some changes to make it AD friendly and more usable :-)
+%%% realloc@realloc.spb.ru
+%%%----------------------------------------------------------------------
+
+
+-module(mod_shared_roster_ldap).
+-author('alexey@sevcom.net').
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+%% gen_server callbacks
+-export([
+ init/1,
+ handle_info/2,
+ handle_call/3,
+ handle_cast/2,
+ terminate/2,
+ code_change/3
+]).
+
+-export([
+ start/2,
+ start_link/2,
+ stop/1,
+ get_user_roster/2,
+ get_subscription_lists/3,
+ get_jid_info/4,
+ process_item/2,
+ in_subscription/6,
+ out_subscription/4
+]).
+
+-include("ejabberd.hrl").
+-include("eldap/eldap.hrl").
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+
+-record(state, {
+ host,
+ eldap_id,
+ servers,
+ port,
+ dn,
+ base,
+ password,
+ uid,
+ group_attr,
+ group_desc,
+ user_desc,
+ uid_format,
+ filter,
+ ufilter,
+ rfilter,
+ gfilter
+}).
+
+-define(LDAP_REQUEST_TIMEOUT, 10000).
+
+%% Unused callbacks.
+handle_cast(_Request, State) ->
+ {noreply, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+handle_info(_Info, State) ->
+ {noreply, State}.
+%% -----
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ ChildSpec = {
+ Proc, {?MODULE, start_link, [Host, Opts]},
+ permanent, 1000, worker, [?MODULE]
+ },
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:call(Proc, stop),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+terminate(_Reason, State) ->
+ Host = State#state.host,
+ ejabberd_hooks:delete(roster_get, Host,
+ ?MODULE, get_user_roster, 70),
+ ejabberd_hooks:delete(roster_in_subscription, Host,
+ ?MODULE, in_subscription, 30),
+ ejabberd_hooks:delete(roster_out_subscription, Host,
+ ?MODULE, out_subscription, 30),
+ ejabberd_hooks:delete(roster_get_subscription_lists, Host,
+ ?MODULE, get_subscription_lists, 70),
+ ejabberd_hooks:delete(roster_get_jid_info, Host,
+ ?MODULE, get_jid_info, 70),
+ ejabberd_hooks:delete(roster_process_item, Host,
+ ?MODULE, process_item, 50).
+
+init([Host, Opts]) ->
+ State = parse_options(Host, Opts),
+ ejabberd_hooks:add(roster_get, Host,
+ ?MODULE, get_user_roster, 70),
+ ejabberd_hooks:add(roster_in_subscription, Host,
+ ?MODULE, in_subscription, 30),
+ ejabberd_hooks:add(roster_out_subscription, Host,
+ ?MODULE, out_subscription, 30),
+ ejabberd_hooks:add(roster_get_subscription_lists, Host,
+ ?MODULE, get_subscription_lists, 70),
+ ejabberd_hooks:add(roster_get_jid_info, Host,
+ ?MODULE, get_jid_info, 70),
+ ejabberd_hooks:add(roster_process_item, Host,
+ ?MODULE, process_item, 50),
+ eldap:start_link(State#state.eldap_id,
+ State#state.servers,
+ State#state.port,
+ State#state.dn,
+ State#state.password),
+ {ok, State}.
+
+get_user_roster(Items, US) ->
+ {U, S} = US,
+ DisplayedGroups = get_user_displayed_groups(US),
+ %% Get shared roster users in all groups and remove self:
+ SRUsers =
+ lists:foldl(
+ fun(Group, Acc1) ->
+ lists:foldl(
+ fun(User, Acc2) ->
+ if User == US -> Acc2;
+ true -> dict:append(User,
+ get_group_name(S, Group),
+ Acc2)
+ end
+ end, Acc1, get_group_users(S, Group))
+ end, dict:new(), DisplayedGroups),
+
+ %% If partially subscribed users are also in shared roster, show them as
+ %% totally subscribed:
+ {NewItems1, SRUsersRest} =
+ lists:mapfoldl(
+ fun(Item, SRUsers1) ->
+ {_, _, {U1, S1, _}} = Item#roster.usj,
+ US1 = {U1, S1},
+ case dict:find(US1, SRUsers1) of
+ {ok, _GroupNames} ->
+ {Item#roster{subscription = both, ask = none},
+ dict:erase(US1, SRUsers1)};
+ error ->
+ {Item, SRUsers1}
+ end
+ end, SRUsers, Items),
+
+ %% Export items in roster format:
+ SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
+ us = US,
+ jid = {U1, S1, ""},
+ name = get_user_name(U1,S1),
+ subscription = both,
+ ask = none,
+ groups = GroupNames} ||
+ {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
+ SRItems ++ NewItems1.
+
+%% This function in use to rewrite the roster entries when moving or renaming
+%% them in the user contact list.
+process_item(RosterItem, Host) ->
+ USFrom = RosterItem#roster.us,
+ {User,Server,_Resource} = RosterItem#roster.jid,
+ USTo = {User,Server},
+ DisplayedGroups = get_user_displayed_groups(USFrom),
+ CommonGroups = lists:filter(fun(Group) ->
+ is_user_in_group(USTo, Group, Server)
+ end, DisplayedGroups),
+ case CommonGroups of
+ [] -> RosterItem;
+ %% Roster item cannot be removed: We simply reset the original groups:
+ _ when RosterItem#roster.subscription == remove ->
+ GroupNames = lists:map(fun(Group) ->
+ get_group_name(Host, Group)
+ end, CommonGroups),
+ RosterItem#roster{subscription = both, ask = none,
+ groups=[GroupNames]};
+ _ -> RosterItem#roster{subscription = both, ask = none}
+ end.
+
+get_subscription_lists({F, T}, User, Server) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ US = {LUser, LServer},
+ DisplayedGroups = get_user_displayed_groups(US),
+ SRUsers =
+ lists:usort(
+ lists:flatmap(
+ fun(Group) ->
+ get_group_users(LServer, Group)
+ end, DisplayedGroups)),
+ SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers],
+ {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
+
+get_jid_info({Subscription, Groups}, User, Server, JID) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ US = {LUser, LServer},
+ {U1, S1, _} = jlib:jid_tolower(JID),
+ US1 = {U1, S1},
+ DisplayedGroups = get_user_displayed_groups(US),
+ SRUsers =
+ lists:foldl(
+ fun(Group, Acc1) ->
+ lists:foldl(
+ fun(User1, Acc2) ->
+ dict:append(
+ User1, get_group_name(LServer, Group), Acc2)
+ end, Acc1, get_group_users(LServer, Group))
+ end, dict:new(), DisplayedGroups),
+ case dict:find(US1, SRUsers) of
+ {ok, GroupNames} ->
+ NewGroups = if
+ Groups == [] -> GroupNames;
+ true -> Groups
+ end,
+ {both, NewGroups};
+ error ->
+ {Subscription, Groups}
+ end.
+
+in_subscription(Acc, User, Server, JID, Type, _Reason) ->
+ process_subscription(in, User, Server, JID, Type, Acc).
+
+out_subscription(User, Server, JID, Type) ->
+ process_subscription(out, User, Server, JID, Type, false).
+
+process_subscription(Direction, User, Server, JID, _Type, Acc) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ US = {LUser, LServer},
+ {U1, S1, _} = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ US1 = {U1, S1},
+ DisplayedGroups = get_user_displayed_groups(US),
+ SRUsers =
+ lists:usort(
+ lists:flatmap(
+ fun(Group) ->
+ get_group_users(LServer, Group)
+ end, DisplayedGroups)),
+ case lists:member(US1, SRUsers) of
+ true ->
+ case Direction of
+ in ->
+ {stop, false};
+ out ->
+ stop
+ end;
+ false ->
+ Acc
+ end.
+
+get_group_users(Host, Group) ->
+ make_request(Host, {get_group_users, Group}, []).
+
+get_group_name(Host, Group) ->
+ make_request(Host, {get_group_name, Group}, Group).
+
+get_user_displayed_groups({User, Host}) ->
+ make_request(Host, {get_user_displayed_groups, User}, []).
+
+is_user_in_group({User, _Server}, Group, Host) ->
+ make_request(Host, {is_user_in_group, User, Group}, false).
+
+get_user_name(User, Host) ->
+ make_request(Host, {get_user_name, User},[]).
+
+
+%%%-----------------------
+%%% Internal functions.
+%%%-----------------------
+handle_call({get_user_displayed_groups, User}, _From, State) ->
+ GroupAttr = State#state.group_attr,
+ Reply = case eldap_filter:parse(State#state.rfilter) of
+ {ok, EldapFilter} ->
+ case eldap:search(State#state.eldap_id, [
+ {base, State#state.base},
+ {filter, EldapFilter},
+ {attributes, [GroupAttr]}]) of
+ #eldap_search_result{entries = Es} ->
+ lists:usort(lists:flatmap(
+ fun(#eldap_entry{attributes = Attrs}) ->
+ case Attrs of
+ [{GroupAttr, ValuesList}] ->
+ ValuesList;
+ _ ->
+ []
+ end
+ end, Es));
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ {reply, Reply, State};
+
+handle_call({get_group_name, Group}, _From, State) ->
+ GroupDescAttr = State#state.group_desc,
+ Reply = case eldap_filter:parse(State#state.gfilter, [{"%g", Group}]) of
+ {ok, EldapFilter} ->
+ case eldap:search(State#state.eldap_id, [
+ {base, State#state.base},
+ {filter, EldapFilter},
+ {attributes, [GroupDescAttr]}]) of
+ #eldap_search_result{entries = [
+ #eldap_entry{attributes =
+ [{GroupDescAttr, GroupName} | _]}
+ ]} ->
+ GroupName;
+ _ ->
+ Group
+ end;
+ _ ->
+ Group
+ end,
+ {reply, Reply, State};
+
+handle_call({get_user_name, User}, _From, State) ->
+UserDescAttr = State#state.user_desc,
+Reply = case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
+ {ok, EldapFilter} ->
+ case eldap:search(State#state.eldap_id, [
+ {base, State#state.base},
+ {filter, EldapFilter},
+ {attributes, [UserDescAttr]}]) of
+ #eldap_search_result{entries = [
+ #eldap_entry{attributes =
+ [{UserDescAttr, UserName} | _]}
+ ]} ->
+ UserName;
+ _ ->
+ User
+ end;
+ _ ->
+ User
+ end,
+ {reply, Reply, State};
+
+
+handle_call({get_group_users, Group}, _From, State) ->
+ UIDAttr = State#state.uid,
+ UAF = State#state.uid_format,
+ Host = State#state.host,
+ Reply = case eldap_filter:parse(State#state.gfilter, [{"%g", Group}]) of
+ {ok, EldapFilter} ->
+ case eldap:search(State#state.eldap_id, [
+ {base, State#state.base},
+ {filter, EldapFilter},
+ {attributes, [UIDAttr]}]) of
+ #eldap_search_result{entries = Es} ->
+ lists:usort(lists:flatmap(
+ fun(#eldap_entry{attributes = Attrs}) ->
+ case Attrs of
+ [{UIDAttr, UsersList}] ->
+ lists:foldl(fun(User, Acc) ->
+ case catch get_user_part(User, UAF) of
+ {ok, U} ->
+ case ejabberd_auth:is_user_exists(U, Host) of
+ true -> [{U, Host} | Acc];
+ _ -> Acc
+ end;
+ _ -> Acc
+ end
+ end, [], UsersList);
+ _ ->
+ []
+ end
+ end, Es));
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ {reply, Reply, State};
+
+handle_call({is_user_in_group, User, Group}, _From, State) ->
+ Reply = case eldap_filter:parse(State#state.filter,
+ [{"%u", User}, {"%g", Group}]) of
+ {ok, EldapFilter} ->
+ case eldap:search(State#state.eldap_id, [
+ {base, State#state.base},
+ {filter, EldapFilter},
+ {attributes, ["dn"]}]) of
+ #eldap_search_result{entries = [_|_]} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end,
+ {reply, Reply, State};
+
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+
+handle_call(_Request, _From, State) ->
+ {reply, bad_request, State}.
+
+%%%-----------------------
+%%% Auxiliary functions.
+%%%-----------------------
+parse_options(Host, Opts) ->
+ Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
+ LDAPServers = case gen_mod:get_opt(ldap_servers, Opts, undefined) of
+ undefined ->
+ ejabberd_config:get_local_option({ldap_servers, Host});
+ S -> S
+ end,
+ LDAPPort = case gen_mod:get_opt(ldap_port, Opts, undefined) of
+ undefined ->
+ case ejabberd_config:get_local_option({ldap_port, Host}) of
+ undefined -> 389;
+ P -> P
+ 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,
+ GroupAttr = case gen_mod:get_opt(ldap_groupattr, Opts, undefined) of
+ undefined -> "cn";
+ GA -> GA
+ end,
+ GroupDesc = case gen_mod:get_opt(ldap_groupdesc, Opts, undefined) of
+ undefined -> "cn";
+ GD -> GD
+ end,
+ UserDesc = case gen_mod:get_opt(ldap_userdesc, Opts, undefined) of
+ undefined -> "cn";
+ UD -> UD
+ end,
+ UIDAttr = case gen_mod:get_opt(ldap_memberattr, Opts, undefined) of
+ undefined -> "memberUid";
+ UA -> UA
+ end,
+ UIDAttrFormat = case gen_mod:get_opt(ldap_memberattr_format, Opts, undefined) of
+ undefined -> "%u";
+ UAF -> UAF
+ 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,
+ ConfigFilter = case gen_mod:get_opt(ldap_filter, Opts, undefined) of
+ undefined ->
+ ejabberd_config:get_local_option({ldap_filter, Host});
+ F ->
+ F
+ end,
+
+ RosterFilter = case gen_mod:get_opt(ldap_rfilter, Opts, undefined) of
+ undefined ->
+ ejabberd_config:get_local_option({ldap_rfilter, Host});
+ RF ->
+ RF
+ end,
+
+ SubFilter = "(&("++UIDAttr++"="++UIDAttrFormat++")("++GroupAttr++"=%g))",
+ UserSubFilter = eldap_filter:do_sub(SubFilter, [{"%g", "*"}]),
+ GroupSubFilter = eldap_filter:do_sub(SubFilter, [{"%u", "*"}]),
+ Filter = case ConfigFilter of
+ undefined -> SubFilter;
+ "" -> SubFilter;
+ _ -> "(&" ++ SubFilter ++ ConfigFilter ++ ")"
+ end,
+ UserFilter = case ConfigFilter of
+ undefined -> UserSubFilter;
+ "" -> UserSubFilter;
+ _ -> "(&" ++ UserSubFilter ++ ConfigFilter ++ ")"
+ end,
+ GroupFilter = case ConfigFilter of
+ undefined -> GroupSubFilter;
+ "" -> GroupSubFilter;
+ _ -> "(&" ++ GroupSubFilter ++ ConfigFilter ++ ")"
+ end,
+ #state{
+ host = Host,
+ eldap_id = Eldap_ID,
+ servers = LDAPServers,
+ port = LDAPPort,
+ dn = RootDN,
+ base = LDAPBase,
+ password = Password,
+ uid = UIDAttr,
+ group_attr = GroupAttr,
+ group_desc = GroupDesc,
+ user_desc = UserDesc,
+ uid_format = UIDAttrFormat,
+ filter = Filter,
+ ufilter = UserFilter,
+ rfilter = RosterFilter,
+ gfilter = GroupFilter
+ }.
+
+get_user_part(String, Pattern) ->
+ F = fun(S, P) ->
+ First = string:str(P, "%u"),
+ TailLength = length(P) - (First+1),
+ string:sub_string(S, First, length(S) - TailLength)
+ end,
+ case catch F(String, Pattern) of
+ {'EXIT', _} ->
+ {error, badmatch};
+ Result ->
+ case regexp:sub(Pattern, "%u", Result) of
+ {ok, String, _} -> {ok, Result};
+ _ -> {error, badmatch}
+ end
+ end.
+
+make_request(Host, Request, Fallback) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ case catch gen_server:call(Proc, Request, ?LDAP_REQUEST_TIMEOUT) of
+ {'EXIT', _} ->
+ Fallback;
+ Result ->
+ Result
+ end.
+