]> granicus.if.org Git - ejabberd/commitdiff
Improve LDAP shared roster support (EJAB-1480)
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 26 Feb 2016 14:27:12 +0000 (17:27 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 26 Feb 2016 14:27:12 +0000 (17:27 +0300)
src/mod_shared_roster_ldap.erl

index a4ac65c10e09d431b431593141632d8ddbfe08de..3275394be609c219bc52b88e5f3abcde718a28f2 100644 (file)
@@ -3,6 +3,7 @@
 %%% Author  : Realloc <realloc@realloc.spb.ru>
 %%%           Marcin Owsiany <marcin@owsiany.pl>
 %%%           Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Contributor : Mike Kaganski <mikekaganski@hotmail.com>
 %%% Description : LDAP shared roster management
 %%% Created :  5 Mar 2005 by Alexey Shchepin <alexey@process-one.net>
 %%%
@@ -26,8 +27,6 @@
 %%%-------------------------------------------------------------------
 -module(mod_shared_roster_ldap).
 
--behaviour(ejabberd_config).
-
 -behaviour(gen_server).
 
 -behaviour(gen_mod).
         out_subscription/4, mod_opt_type/1, opt_type/1]).
 
 -include("ejabberd.hrl").
--include("logger.hrl").
 -include("jlib.hrl").
 -include("mod_roster.hrl").
-
 -include("eldap.hrl").
+-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)).
 
--define(CACHE_SIZE, 1000).
-
--define(USER_CACHE_VALIDITY, 300).
-
--define(GROUP_CACHE_VALIDITY, 300).
-
--define(LDAP_SEARCH_TIMEOUT, 5).
+-define(CACHE_SIZE, 1).
+-define(CACHE_VALIDITY, 300). %% in seconds
+-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
+-define(INVALID_SETTING_MSG, "~s is not properly set! ~s will not function.").
 
 -record(state,
        {host = <<"">>                                :: binary(),
@@ -74,7 +69,6 @@
          group_attr = <<"">>                          :: binary(),
         group_desc = <<"">>                          :: binary(),
          user_desc = <<"">>                           :: binary(),
-         user_uid = <<"">>                            :: binary(),
          uid_format = <<"">>                          :: binary(),
         uid_format_re = <<"">>                       :: binary(),
          filter = <<"">>                              :: binary(),
          rfilter = <<"">>                             :: binary(),
          gfilter = <<"">>                             :: binary(),
         auth_check = true                            :: boolean(),
-         user_cache_size = ?CACHE_SIZE                :: non_neg_integer(),
-         group_cache_size = ?CACHE_SIZE               :: non_neg_integer(),
-        user_cache_validity = ?USER_CACHE_VALIDITY   :: non_neg_integer(),
-         group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}).
-
+         %% Group data parameters
+        group_base = <<"">>                          :: binary(),
+         %% - Subgroup of roster filter
+         %% This filter defines which groups are displayed in the shared roster
+         %% Valid values are 'all' or "LDAP filter string" or "LDAP filter string containing %g"
+         shgfilter = <<"">>                           :: binary(),
+         shg_attr = <<"">>                            :: binary(),
+         %% - Subgroup of group filter
+         group_is_dn = true                           :: boolean(),
+         member_attr = <<"">>                          :: binary(),
+         %% User data parameters
+         member_selection_mode = memberattr_dn        :: memberattr_normal | memberattr_dn | 
+                                                        group_children,
+         %% Algorithm control parameters
+         subscribe_all = false                        :: binary(),
+         roster_cache_size = ?CACHE_SIZE              :: non_neg_integer(),
+         roster_cache_validity = ?CACHE_VALIDITY      :: non_neg_integer()}).
+
+%% If #state.member_selection_mode is memberattr_normal or memberattr_dn,
+%% then members is list of member_attr values;
+%% if #state.member_selection_mode is group_children,
+%% then members is dn of the group (to make it possible to search for its subtree)
 -record(group_info, {desc, members}).
 
+-record(user_info, {us, name}).
+
+-record(shared_roster_item, {us, name, groups}).
+
+% Groups visible to this group
+% grp may be atom 'all' or a group name string.
+% shgrps is a list containing one or more grp
+-record(shg_data, {grp, shgrps}).
+
 %%====================================================================
 %% API
 %%====================================================================
 start_link(Host, Opts) ->
     Proc = gen_mod:get_module_proc(Host, ?MODULE),
     gen_server:start_link({local, Proc}, ?MODULE,
-                         [Host, Opts], []).
+                          [Host, Opts], []).
 
 start(Host, Opts) ->
     Proc = gen_mod:get_module_proc(Host, ?MODULE),
     ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
-                permanent, 1000, worker, [?MODULE]},
+                 permanent, 1000, worker, [?MODULE]},
     supervisor:start_child(ejabberd_sup, ChildSpec).
 
 stop(Host) ->
@@ -112,109 +132,92 @@ stop(Host) ->
 %% Hooks
 %%--------------------------------------------------------------------
 get_user_roster(Items, {U, S} = US) ->
-    SRUsers = get_user_to_groups_map(US, true),
-    {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),
+    {ok, State} = eldap_utils:get_state(S, ?MODULE),
+    SRUsers = get_shared_roster(State, US),
+    %%?ERROR_MSG("XXXXXX get_user_roster: SRUsers=~p", [SRUsers]),
+    %% 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 lists:keytake(US1, #shared_roster_item.us, SRUsers1) of
+            %%case dict:find(US1, SRUsers1) of
+                {value, _, SRUsers2} -> {Item#roster{subscription = both, ask = none}, SRUsers2};
+                %%{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)],
+                       us = US,
+                       jid = {U1, S1, <<"">>},
+                       name = Name,
+                       subscription = both,
+                       ask = none,
+                       groups = Groups} ||
+              #shared_roster_item{us = {U1, S1}, name = Name, groups = Groups} <- SRUsersRest],
+    %% 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},
-    Map = get_user_to_groups_map(USFrom, false),
-    case dict:find(USTo, Map) of
-      error -> RosterItem;
-      {ok, []} -> RosterItem;
-      {ok, GroupNames}
-         when RosterItem#roster.subscription == remove ->
-         RosterItem#roster{subscription = both, ask = none,
-                           groups = GroupNames};
-      _ -> RosterItem#roster{subscription = both, ask = none}
+    {ok, State} = eldap_utils:get_state(_Host, ?MODULE),
+    {User,Server,_Resource} = RosterItem#roster.jid,
+    USTo = {User,Server},
+    SR = get_shared_roster(State, RosterItem#roster.us),
+    case lists:keysearch(USTo, #shared_roster_item.us, SR) of
+        false ->
+            RosterItem;
+        {value, #shared_roster_item{groups = Groups}} when RosterItem#roster.subscription == remove ->
+            %% Roster item cannot be removed:
+            %% We simply reset the original groups:
+            RosterItem#roster{subscription = both, ask = none,
+                              groups=Groups};
+        _ ->
+            RosterItem#roster{subscription = both, ask = none}
     end.
 
 get_subscription_lists({F, T}, User, Server) ->
-    LUser = jid:nodeprep(User),
-    LServer = jid: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],
+    U = jlib:nodeprep(User),
+    S = jlib:nameprep(Server),
+    {ok, State} = eldap_utils:get_state(S, ?MODULE),
+    SRJIDs = get_presense_subscribers(State, {U, S}),
+%?INFO_MSG("SRJIDs: ~p", [SRJIDs]),
     {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
 
-get_jid_info({Subscription, Groups}, User, Server,
-            JID) ->
-    LUser = jid:nodeprep(User),
-    LServer = jid:nameprep(Server),
-    US = {LUser, LServer},
-    {U1, S1, _} = jid:tolower(JID),
+get_jid_info({Subscription, Groups}, User, Server, JID) ->
+    {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+    {U1, S1, _} = jlib:jid_tolower(JID),
     US1 = {U1, S1},
-    SRUsers = get_user_to_groups_map(US, false),
-    case dict:find(US1, SRUsers) of
-      {ok, GroupNames} ->
-         NewGroups = if Groups == [] -> GroupNames;
-                        true -> Groups
-                     end,
-         {both, NewGroups};
-      error -> {Subscription, Groups}
+    SR = get_shared_roster(State, {User, Server}),
+    case lists:keysearch(US1, #shared_roster_item.us, SR) of
+        false -> {Subscription, Groups};
+        {value, #shared_roster_item{groups = GroupNames}} when Groups == [] -> {both, GroupNames};
+        _ -> {both, Groups}
     end.
 
-in_subscription(Acc, User, Server, JID, Type,
-               _Reason) ->
+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 = jid:nodeprep(User),
-    LServer = jid:nameprep(Server),
-    US = {LUser, LServer},
-    {U1, S1, _} =
-       jid:tolower(jid:remove_resource(JID)),
+    process_subscription(out, User, Server, JID, Type, false).
+
+process_subscription(Direction, User, Server, JID, _Type, Acc) ->
+    {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+    {U1, S1, _} = jlib:jid_tolower(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
+    SR = get_shared_roster(State, {User, Server}),
+    case lists:keysearch(US1, #shared_roster_item.us, SR) of
+        false -> Acc;
+        _ when Direction == in -> {stop, false};
+        _ -> stop
     end.
 
 %%====================================================================
@@ -222,267 +225,509 @@ process_subscription(Direction, User, Server, JID,
 %%====================================================================
 init([Host, Opts]) ->
     State = parse_options(Host, Opts),
-    cache_tab:new(shared_roster_ldap_user,
-                 [{max_size, State#state.user_cache_size}, {lru, false},
-                  {life_time, State#state.user_cache_validity}]),
-    cache_tab:new(shared_roster_ldap_group,
-                 [{max_size, State#state.group_cache_size}, {lru, false},
-                  {life_time, State#state.group_cache_validity}]),
-    ejabberd_hooks:add(roster_get, Host, ?MODULE,
-                      get_user_roster, 70),
+    if
+        State#state.roster_cache_size > 0 ->
+            cache_tab:new(shared_roster_ldap_sr,
+                          [{max_size, State#state.roster_cache_size},
+                           {lru, false}, % We don't need LRU algorithm
+                           {life_time, State#state.roster_cache_validity}]);
+        true ->
+            false
+    end,
+    ejabberd_hooks:add(roster_get, Host,
+                       ?MODULE, get_user_roster, 70),
     ejabberd_hooks:add(roster_in_subscription, Host,
-                      ?MODULE, in_subscription, 30),
+                       ?MODULE, in_subscription, 30),
     ejabberd_hooks:add(roster_out_subscription, Host,
-                      ?MODULE, out_subscription, 30),
+                       ?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),
+                       ?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_pool:start_link(State#state.eldap_id,
-                         State#state.servers, State#state.backups,
-                         State#state.port, State#state.dn,
-                         State#state.password, State#state.tls_options),
+                          State#state.servers,
+                          State#state.backups,
+                          State#state.port,
+                          State#state.dn,
+                          State#state.password,
+                          State#state.tls_options),
     {ok, State}.
 
 handle_call(get_state, _From, State) ->
     {reply, {ok, State}, State};
+
 handle_call(_Request, _From, State) ->
     {reply, {error, badarg}, State}.
 
-handle_cast(_Msg, State) -> {noreply, State}.
+handle_cast(_Msg, State) ->
+    {noreply, State}.
 
-handle_info(_Info, State) -> {noreply, State}.
+handle_info(_Info, State) ->
+    {noreply, State}.
 
 terminate(_Reason, State) ->
     Host = State#state.host,
-    ejabberd_hooks:delete(roster_get, Host, ?MODULE,
-                         get_user_roster, 70),
+    ejabberd_hooks:delete(roster_get, Host,
+                          ?MODULE, get_user_roster, 70),
     ejabberd_hooks:delete(roster_in_subscription, Host,
-                         ?MODULE, in_subscription, 30),
+                          ?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),
+                          ?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),
+                          ?MODULE, get_jid_info, 70),
     ejabberd_hooks:delete(roster_process_item, Host,
-                         ?MODULE, process_item, 50).
+                          ?MODULE, process_item, 50).
 
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
 
 %%--------------------------------------------------------------------
 %%% Internal functions
 %%--------------------------------------------------------------------
 
-get_user_to_groups_map({_, Server} = US, SkipUS) ->
-    DisplayedGroups = get_user_displayed_groups(US),
-    lists:foldl(fun (Group, Dict1) ->
-                       GroupName = get_group_name(Server, Group),
-                       lists:foldl(fun (Contact, Dict) ->
-                                           if SkipUS, Contact == US -> Dict;
-                                              true ->
-                                                  dict:append(Contact,
-                                                              GroupName, Dict)
-                                           end
-                                   end,
-                                   Dict1, get_group_users(Server, Group))
-               end,
-               dict:new(), DisplayedGroups).
-
-eldap_search(State, FilterParseArgs, AttributesList) ->
-    case apply(eldap_filter, parse, FilterParseArgs) of
-      {ok, EldapFilter} ->
-         case eldap_pool:search(State#state.eldap_id,
-                                [{base, State#state.base},
-                                 {filter, EldapFilter},
-                                 {timeout, ?LDAP_SEARCH_TIMEOUT},
-                                 {deref_aliases, State#state.deref_aliases},
-                                 {attributes, AttributesList}])
-             of
-           #eldap_search_result{entries = Es} ->
-               %% A result with entries. Return their list.
-               Es;
-           _ ->
-               %% Something else. Pretend we got no results.
-               []
-         end;
-      _ ->
-         %% Filter parsing failed. Pretend we got no results.
-         []
+do_eldap_search(PoolName, Opts) ->
+    case eldap_pool:search(PoolName, Opts) of
+        #eldap_search_result{entries = Es} ->
+            %% A result with entries. Return their list.
+            Es;
+        Err ->
+            %% Something else. Pretend we got no results.
+            ?ERROR_MSG("Error searching: ~p ~p", [Err, Opts]),
+            []
     end.
 
-get_user_displayed_groups({User, Host}) ->
-    {ok, State} = eldap_utils:get_state(Host, ?MODULE),
-    GroupAttr = State#state.group_attr,
-    Entries = eldap_search(State,
-                          [eldap_filter:do_sub(State#state.rfilter,
-                                               [{<<"%u">>, User}])],
-                          [GroupAttr]),
-    Reply = lists:flatmap(fun (#eldap_entry{attributes =
-                                               Attrs}) ->
-                                 case Attrs of
-                                   [{GroupAttr, ValuesList}] -> ValuesList;
-                                   _ -> []
-                                 end
-                         end,
-                         Entries),
-    lists:usort(Reply).
-
-get_group_users(Host, Group) ->
-    {ok, State} = eldap_utils:get_state(Host, ?MODULE),
-    case cache_tab:dirty_lookup(shared_roster_ldap_group,
-                               {Group, Host},
-                               fun () -> search_group_info(State, Group) end)
-       of
-      {ok, #group_info{members = Members}}
-         when Members /= undefined ->
-         Members;
-      _ -> []
+%% Pass given Filter or FilterTemplate and SubstList to eldap_filter:parse,
+%% and if successful, run LDAP search on the whole subtree of Base, using
+%% resulting filter, retrieving given AttributesList. Return the result entries.
+%% On any error, print an error message and return an empty list of results.
+eldap_search(State, Base, EldapFilter, AttributesList) when is_tuple(EldapFilter) ->
+    do_eldap_search(State#state.eldap_id,
+                    [{base, Base},
+%%                     {scope, wholeSubtree} %% This is the default
+                     {filter, EldapFilter},
+                     {timeout, ?LDAP_SEARCH_TIMEOUT},
+                     {deref_aliases, State#state.deref_aliases},
+                     {attributes, AttributesList}]);
+%% Filter is string
+eldap_search(State, Base, Filter, AttributesList) ->
+    eldap_search(State, Base, Filter, [], AttributesList).
+
+eldap_search(State, Base, FilterTemplate, SubstList, AttributesList) ->
+    case apply(eldap_filter, parse, [eldap_filter:do_sub(FilterTemplate, SubstList)]) of
+        {ok, EldapFilter} ->
+            %% Filter parsing succeeded
+            eldap_search(State, Base, EldapFilter, AttributesList);
+        Err ->
+            %% Filter parsing failed. Pretend we got no results.
+            ?ERROR_MSG("Error parsing filter: ~p", [Err]),
+            []
     end.
 
-get_group_name(Host, Group) ->
-    {ok, State} = eldap_utils:get_state(Host, ?MODULE),
-    case cache_tab:dirty_lookup(shared_roster_ldap_group,
-                               {Group, Host},
-                               fun () -> search_group_info(State, Group) end)
-       of
-      {ok, #group_info{desc = GroupName}}
-         when GroupName /= undefined ->
-         GroupName;
-      _ -> Group
+%% The same as above, but gets the Attributes for the specified DN.
+%% Note that this function doesn't honor the State's base DN;
+%% TODO: fix this (create a custom check?)
+eldap_search_dn(State, DN, EldapFilter, AttributesList) when is_tuple(EldapFilter) ->
+    do_eldap_search(State#state.eldap_id,
+                    [{scope, baseObject},
+                     {base, DN},
+                     {filter, EldapFilter},
+                     {timeout, ?LDAP_SEARCH_TIMEOUT},
+                     {deref_aliases, State#state.deref_aliases},
+                     {attributes, AttributesList}]);
+%% Filter is string.
+eldap_search_dn(State, DN, Filter, AttributesList) ->
+    case eldap_filter:parse(Filter) of
+        {ok, EldapFilter} ->
+            %% Filter parsing succeeded
+            eldap_search_dn(State, DN, EldapFilter, AttributesList);
+        Err ->
+            %% Filter parsing failed. Pretend we got no results.
+            ?ERROR_MSG("Error parsing filter: ~p", [Err]),
+            []
     end.
 
-get_user_name(User, Host) ->
-    {ok, State} = eldap_utils:get_state(Host, ?MODULE),
-    case cache_tab:dirty_lookup(shared_roster_ldap_user,
-                               {User, Host},
-                               fun () -> search_user_name(State, User) end)
-       of
-      {ok, UserName} -> UserName;
-      error -> User
+intersection(L1,L2) -> lists:filter(fun(X) -> lists:member(X,L1) end, L2).
+
+filter_roster(Roster, all) -> Roster;
+filter_roster(_, []) -> [];
+filter_roster(Roster, IncludeGroups) when is_list(IncludeGroups) ->
+    lists:foldl(
+        fun(RosterItem, Acc) ->
+            case intersection(IncludeGroups, RosterItem#shared_roster_item.groups) of
+                [] -> Acc;
+                CommonGroups -> [RosterItem#shared_roster_item{groups=CommonGroups} | Acc]
+            end
+        end,
+        [], Roster).
+
+get_user_visible_groups(UserGroups, VisibilityMap) ->
+    lists:foldl(
+        fun(Group, Acc) ->
+            case (lists:keysearch(Group, #shg_data.grp, VisibilityMap)) of
+                {value, #shg_data{shgrps=Gs}} when is_list(Gs) -> Gs ++ Acc;
+                _ -> Acc
+            end
+        end,
+        UserGroups, UserGroups).
+
+%% Returns [#shared_roster_item];
+%% Removes the US from returned data
+%% If State#state.user_groups_only is 'true', then it removes all users that are not in US's groups,
+%% and also removes the groups from the users that the US is not member of.
+get_shared_roster(State, {_, Server} = US) ->
+    case (catch get_full_roster(State, Server)) of
+        {ok, {VisibilityMap, FullRoster}} ->
+           %%?ERROR_MSG("XXXXXX get_shared_roster: VMap=~p FullRoster=~p", [VisibilityMap, FullRoster]),
+            CommonRosterGroups = lists:foldl(
+                fun(_, all) -> all;
+                   (#shg_data{grp=all, shgrps=all}, _) -> all;
+                   (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) -> Gs ++ Acc;
+                   (_, Acc) -> Acc
+                end,
+                [], VisibilityMap),
+            case lists:keytake(US, #shared_roster_item.us, FullRoster) of
+                false -> filter_roster(FullRoster, CommonRosterGroups);
+                {value, #shared_roster_item{groups=UserGroups}, Roster2} ->
+                    VisibleGroups = case (CommonRosterGroups) of
+                        all -> all;
+                        CRG -> get_user_visible_groups(UserGroups, VisibilityMap) ++ CRG
+                    end,
+                    filter_roster(Roster2, VisibleGroups)
+            end;
+        {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), [];
+        _Unexpected -> []
     end.
 
+%% 1. If user is not a member of shared roster -> no additional subscriptions
+%% 2. Else if ldap_subscribe_all is set AND this user is member of a group published to all ->
+%%                                      add all registered users of this vhost
+%% 3. Else add only those groups this user' groups are published to
+get_presense_subscribers(State, {_, Server} = US) ->
+    case (catch get_full_roster(State, Server)) of
+        {ok, {VisibilityMap, FullRoster}} ->
+            case lists:keytake(US, #shared_roster_item.us, FullRoster) of
+                false -> []; % Case #1
+                {value, #shared_roster_item{groups=UserGroups}, Roster2} ->
+                    AllGroups = lists:usort(lists:foldl(
+                        fun(#shared_roster_item{groups=Gs}, Acc) -> Gs ++ Acc end,
+                        [], FullRoster)),
+                    Fun = case (State#state.subscribe_all) of
+                        true -> % Possible case 2
+                            fun(_, all) -> all;
+                               (#shg_data{grp=all, shgrps=all}, _) -> all;
+                               (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) ->
+                                    case intersection(Gs, UserGroups) of
+                                        [] -> Acc;
+                                        _SomeCommon -> all
+                                    end;
+                               (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) ->
+                                    case intersection(Gs, UserGroups) of
+                                        [] -> Acc;
+                                        _SomeCommon -> [G | Acc]
+                                    end;
+                               (_, Acc) -> Acc
+                            end;
+                        _False -> % Case 3
+                            fun(#shg_data{grp=all}, Acc) -> AllGroups ++ Acc;
+                               (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) ->
+                                    case intersection(Gs, UserGroups) of
+                                        [] -> Acc;
+                                        _SomeCommon -> [G | Acc]
+                                    end;
+                               (_, Acc) -> Acc
+                            end
+                    end,
+                    PublishTo = lists:foldl(Fun, [], VisibilityMap),
+                    case (PublishTo) of
+                        all ->
+                            [{U1, S1, <<"">>} || {U1, S1} <- ejabberd_auth:get_vh_registered_users(Server)];
+                        Groups ->
+                            [{U1, S1, <<"">>} || #shared_roster_item{us = {U1, S1}} <- filter_roster(Roster2, UserGroups ++ Groups)]
+                    end
+            end;
+        {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), [];
+        _Unexpected -> []
+    end.
+
+get_full_roster(State, Server) when State#state.roster_cache_size > 0 ->
+    cache_tab:dirty_lookup(shared_roster_ldap_sr,
+                           {Server},
+                           fun() -> search_roster_info(State, Server) end);
+get_full_roster(State, Server) ->
+    search_roster_info(State, Server).
+
+search_visible_groups(State, _) when State#state.shgfilter == all ->
+    [{all, all}];
+search_visible_groups(State, _) when State#state.shgfilter == none ->
+    [{all, none}];
+search_visible_groups(State, Groups) ->
+    case (string:str(State#state.shgfilter, "%g")) of
+        0 -> [{all, search_group_visible_groups(State, "")}];
+        _ -> lists:map(
+            fun(Group) -> {Group, search_group_visible_groups(State, Group)} end,
+            Groups)
+    end.
+
+search_group_visible_groups(State, Group) ->
+    Entries = eldap_search(State, State#state.group_base, State#state.shgfilter, [{<<"%g">>, Group}], [State#state.shg_attr]),
+    lists:usort(lists:flatmap(
+        fun(#eldap_entry{attributes = Attrs}) ->
+            case Attrs of
+                [{_GroupAttr, ValuesList}] ->
+                    ValuesList;
+                _ ->
+                    []
+            end
+        end, Entries)).
+
+group2name(all, _) -> all;
+group2name(none, _) -> none;
+group2name(Group, GroupNames) ->
+    case (lists:keysearch(Group, 1, GroupNames)) of
+        {value, {_, Name}} -> Name;
+        _ -> false
+    end.
+
+groups2names(all, _) -> all;
+groups2names(none, _) -> none;
+groups2names(GroupList, GroupNames) ->
+    lists:foldl(
+        fun(G, Acc) ->
+            case (group2name(G, GroupNames)) of
+                false -> Acc;
+                Name -> [Name | Acc]
+            end
+        end,
+        [], GroupList).
+
+prep_vis_map(VisGroups, GroupNames) ->
+    lists:foldl(
+        fun({G, Gs}, Acc) ->
+            case (group2name(G, GroupNames)) of
+                false -> Acc;
+                Name -> [#shg_data{grp=Name, shgrps=groups2names(Gs, GroupNames)} | Acc]
+            end
+        end,
+        [], VisGroups).
+
+search_roster_info(State, _Host) ->
+    Entries = eldap_search(State, State#state.group_base, State#state.rfilter, [State#state.group_attr]),
+    AllGroupIds = lists:usort(lists:flatmap(
+        fun(#eldap_entry{attributes = Attrs}) ->
+            case Attrs of
+                [{_GroupAttr, ValuesList}] ->
+                    ValuesList;
+                _ ->
+                    []
+            end
+        end, Entries)),
+    VisGroups = search_visible_groups(State, AllGroupIds),
+    %%?ERROR_MSG("XXXXXX search_roster_info: VisGroups=~p", [VisGroups]),
+
+    {GroupNames, RosterItems} = case State#state.member_selection_mode of
+        group_children ->
+            {GroupNames0, UsersDict0} = lists:foldl(
+                fun(Group, {GrNAcc, Dict1} = Acc) ->
+                    case search_group_info(State, Group) of
+                        {ok, #group_info{desc = GroupName, members = GroupDN}} ->
+                            {[{Group, GroupName} | GrNAcc], search_users_info(State, GroupDN, GroupName, Dict1)};
+                        _ -> Acc             %% Error getting group data -> No users!
+                    end
+                end,
+                {[], dict:new()}, AllGroupIds),
+
+            {GroupNames0, dict:fold(
+                fun(#user_info{us=US, name=UserName}, Groups, AccIn) ->
+                    [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn]
+                end,
+                [], UsersDict0)};
+        _ ->
+            {GroupNames1, UsersDict1} = lists:foldl(
+                fun(Group, {GrNAcc, Dict1} = Acc) ->
+                    case search_group_info(State, Group) of
+                        {ok, #group_info{desc = GroupName, members = Members}} ->
+                            {[{Group, GroupName} | GrNAcc], lists:foldl(
+                                fun(Member, Dict) -> dict:append(Member, GroupName, Dict) end,
+                                Dict1, Members)};
+                        _ -> Acc             %% Error getting group data -> No users!
+                    end
+                end,
+                {[], dict:new()}, AllGroupIds),
+
+            %%?ERROR_MSG("UsersDict1: ~p", [UsersDict1]),
+            %%?ERROR_MSG("GroupNames1: ~p", [GroupNames1]),
+
+            {GroupNames1, dict:fold(
+                fun(Member, Groups, AccIn) ->
+                    case search_user_info(State, Member) of
+                        {ok, #user_info{us=US, name=UserName}} ->
+                            %%?ERROR_MSG("XXXX found user: ~p ~p ~p", [UserName, Groups, US]),
+                            [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn];
+                        _ -> AccIn
+                    end
+                end,
+                [], UsersDict1)}
+    end,
+
+    VisibilityMap = prep_vis_map(VisGroups, GroupNames),
+    {ok, {VisibilityMap, RosterItems}}.
+
 search_group_info(State, Group) ->
+    AttList = case State#state.member_selection_mode of
+        group_children -> [State#state.group_desc];
+        _              -> [State#state.group_desc, State#state.member_attr]
+    end,
+    SearchResult = case State#state.group_is_dn of
+        true -> eldap_search_dn(State,
+                                Group,
+                                State#state.gfilter,
+                                AttList);
+        _    -> eldap_search(State,
+                             State#state.group_base,
+                             State#state.gfilter,
+                             [{<<"%g">>, Group}],
+                             AttList)
+    end,
+    case SearchResult of
+        [] ->
+            error;
+        LDAPEntries ->
+            case State#state.member_selection_mode of
+                group_children ->
+                    [#eldap_entry{object_name=Name, attributes=Attrs} | _] = LDAPEntries,
+                    {ok, #group_info{desc = eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+                                     members = Name}};
+                _ ->
+                    {GroupDesc, MembersLists} = lists:foldl(
+                        fun(#eldap_entry{attributes=Attrs}, {DescAcc, MembersAcc}) ->
+                            case {eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+                                  lists:keysearch(State#state.member_attr, 1, Attrs)} of
+                                {Desc, {value, {GroupMemberAttr, Members}}}
+                                when GroupMemberAttr == State#state.member_attr ->
+                                    {Desc, lists:usort(Members ++ MembersAcc)};
+                                _ ->
+                                    {DescAcc, MembersAcc}
+                            end
+                        end,
+                        {Group, []}, LDAPEntries),
+                    {ok, #group_info{desc = GroupDesc,
+                                     members = lists:usort(MembersLists)}}
+            end
+    end.
+
+%% Takes the attributes from LDAP user search;
+%% returns error or {ok, #user_info}
+construct_user(State, Attrs) ->
     Extractor = case State#state.uid_format_re of
-                 <<"">> ->
-                     fun (UID) ->
-                             catch eldap_utils:get_user_part(UID,
-                                                             State#state.uid_format)
-                     end;
-                 _ ->
-                     fun (UID) ->
-                             catch get_user_part_re(UID,
-                                                    State#state.uid_format_re)
-                     end
-               end,
+        <<"">> -> fun(UID) ->
+                catch eldap_utils:get_user_part(UID, State#state.uid_format)
+              end;
+        _  -> fun(UID) ->
+                catch get_user_part_re(UID, State#state.uid_format_re)
+              end
+    end,
     AuthChecker = case State#state.auth_check of
-                   true -> fun ejabberd_auth:is_user_exists/2;
-                   _ -> fun (_U, _S) -> true end
-                 end,
+                  true -> fun ejabberd_auth:is_user_exists/2;
+                  _ -> fun(_U, _S) -> true end
+    end,
     Host = State#state.host,
-    case eldap_search(State,
-                     [eldap_filter:do_sub(State#state.gfilter,
-                                          [{<<"%g">>, Group}])],
-                     [State#state.group_attr, State#state.group_desc,
-                      State#state.uid])
-       of
-      [] -> error;
-      LDAPEntries ->
-         {GroupDesc, MembersLists} = lists:foldl(fun
-                                                   (#eldap_entry{attributes =
-                                                                     Attrs},
-                                                    {DescAcc, JIDsAcc}) ->
-                                                       case
-                                                         {eldap_utils:get_ldap_attr(State#state.group_attr,
-                                                                                    Attrs),
-                                                          eldap_utils:get_ldap_attr(State#state.group_desc,
-                                                                                    Attrs),
-                                                          lists:keysearch(State#state.uid,
-                                                                          1,
-                                                                          Attrs)}
-                                                           of
-                                                         {ID, Desc,
-                                                          {value,
-                                                           {GroupMemberAttr,
-                                                            Members}}}
-                                                             when ID /= <<"">>,
-                                                                  GroupMemberAttr
-                                                                    ==
-                                                                    State#state.uid ->
-                                                             JIDs =
-                                                                 lists:foldl(fun
-                                                                               ({ok,
-                                                                                 UID},
-                                                                                L) ->
-                                                                                   PUID =
-                                                                                       jid:nodeprep(UID),
-                                                                                   case
-                                                                                     PUID
-                                                                                       of
-                                                                                     error ->
-                                                                                         L;
-                                                                                     _ ->
-                                                                                         case
-                                                                                           AuthChecker(PUID,
-                                                                                                       Host)
-                                                                                             of
-                                                                                           true ->
-                                                                                               [{PUID,
-                                                                                                 Host}
-                                                                                                | L];
-                                                                                           _ ->
-                                                                                               L
-                                                                                         end
-                                                                                   end;
-                                                                               (_,
-                                                                                L) ->
-                                                                                   L
-                                                                             end,
-                                                                             [],
-                                                                             lists:map(Extractor,
-                                                                                       Members)),
-                                                             {Desc,
-                                                              [JIDs
-                                                               | JIDsAcc]};
-                                                         _ ->
-                                                             {DescAcc, JIDsAcc}
-                                                       end
-                                                 end,
-                                                 {Group, []}, LDAPEntries),
-         {ok,
-          #group_info{desc = GroupDesc,
-                      members = lists:usort(lists:flatten(MembersLists))}}
+
+    case {eldap_utils:get_ldap_attr(State#state.uid, Attrs),
+          eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} of
+        {UID, Desc} when UID /= "" ->
+            %% By returning "" get_ldap_attr means "not found"
+            case Extractor(UID) of
+                {ok, UID1} ->
+                    UID2 = jlib:nodeprep(UID1),
+                    case UID2 of
+                        error -> error;
+                        _ ->
+                            case AuthChecker(UID2, Host) of
+                                true -> {ok, #user_info{us={UID2, Host}, name=Desc}};
+                                _ -> error
+                            end
+                    end;
+                _ -> error
+            end;
+        _ ->
+            error
     end.
 
-search_user_name(State, User) ->
-    case eldap_search(State,
-                     [eldap_filter:do_sub(State#state.ufilter,
-                                          [{<<"%u">>, User}])],
-                     [State#state.user_desc, State#state.user_uid])
-       of
-      [#eldap_entry{attributes = Attrs} | _] ->
-         case {eldap_utils:get_ldap_attr(State#state.user_uid,
-                                         Attrs),
-               eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)}
-             of
-           {UID, Desc} when UID /= <<"">> -> {ok, Desc};
-           _ -> error
-         end;
-      [] -> error
+%% This function is used when State#state.member_selection_mode is group_children
+%% Returns UsersDict to which the users (#user_info) of this group are added
+%%search_users_info(State, GroupInfo) ->
+search_users_info(State, GroupDN, GroupName, UsersDict) ->
+    SearchResult = eldap_search(State,
+                                GroupDN,
+                                State#state.ufilter,
+                                [State#state.user_desc, State#state.uid]),
+    lists:foldl(
+        fun(#eldap_entry{attributes=Attrs}, Dict1) ->
+            case construct_user(State, Attrs) of
+                {ok, UserInfo} ->
+                    dict:append(UserInfo, GroupName, Dict1);
+                _ -> Dict1
+            end
+        end, UsersDict, SearchResult).
+
+%% This function is used when State#state.member_selection_mode is either memberattr_normal or memberattr_dn
+search_user_info(State, User) ->
+            %%?ERROR_MSG("XXX search_user_info: searching for ~p", [User]),
+    SearchResult = case State#state.member_selection_mode of
+        memberattr_dn -> eldap_search_dn(State,
+                                         User,
+                                         State#state.ufilter,
+                                         [State#state.user_desc, State#state.uid]);
+        memberattr_normal -> eldap_search(State,
+                                          State#state.base,
+                                          State#state.ufilter,
+                                          [{<<"%u">>, User}],
+                                          [State#state.user_desc, State#state.uid])
+    end,
+    case SearchResult of
+        [#eldap_entry{attributes=Attrs}|_] ->
+            construct_user(State, Attrs);
+        [] ->
+            %%?ERROR_MSG("XX not found", []),
+            error
     end.
 
 %% Getting User ID part by regex pattern
 get_user_part_re(String, Pattern) ->
     case catch re:run(String, Pattern) of
-      {match, Captured} ->
-         {First, Len} = lists:nth(2, Captured),
-         Result = str:sub_string(String, First + 1, First + Len),
-         {ok, Result};
-      _ -> {error, badmatch}
+        {match, Captured} ->
+            {First, Len} = lists:nth(2,Captured),
+            Result = string:sub_string(String, First+1, First+Len),
+            {ok,Result};
+        _ -> {error, badmatch}
     end.
 
+% select(SelectFirst, First, Second) ->
+%     case SelectFirst of
+%         true -> First;
+%         _    -> Second
+%     end.
+
+% prepare_filter(Opts, Name, Default, ReturnParsed) ->
+%     F = gen_mod:get_opt(Name, Opts, Default),
+%     prepare_filter(F, Name, ReturnParsed).
+
+% prepare_filter(F, Name, ReturnParsed) ->
+%     case eldap_filter:parse(F) of
+%         {ok, EldapFilter} ->
+%             case ReturnParsed of
+%                 true -> EldapFilter;
+%                 _    -> F
+%             end;
+%         _ ->
+%             ?ERROR_MSG(?INVALID_SETTING_MSG, [atom_to_list(Name), ?MODULE]),
+%             []
+%     end.
+
 parse_options(Host, Opts) ->
     Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
     Cfg = eldap_utils:get_config(Host, Opts),
@@ -516,84 +761,114 @@ parse_options(Host, Opts) ->
                                    (false) -> false;
                                    (true) -> true
                                 end, true),
-    UserCacheValidity = gen_mod:get_opt(
-                          {ldap_user_cache_validity, Host}, Opts,
-                          fun(I) when is_integer(I), I>0 -> I end,
-                          ?USER_CACHE_VALIDITY),
-    GroupCacheValidity = gen_mod:get_opt(
+    RosterCacheValidity = eldap_utils:get_opt(
                            {ldap_group_cache_validity, Host}, Opts,
                            fun(I) when is_integer(I), I>0 -> I end,
-                           ?GROUP_CACHE_VALIDITY),
-    UserCacheSize = gen_mod:get_opt(
-                      {ldap_user_cache_size, Host}, Opts,
-                      fun(I) when is_integer(I), I>0 -> I end,
-                      ?CACHE_SIZE),
-    GroupCacheSize = gen_mod:get_opt(
-                       {ldap_group_cache_size, Host}, Opts,
+                           ?CACHE_VALIDITY),
+    RosterCacheSize = eldap_utils:get_opt(
+                       {ldap_roster_cache_size, Host}, Opts,
                        fun(I) when is_integer(I), I>0 -> I end,
                        ?CACHE_SIZE),
-    ConfigFilter = gen_mod:get_opt({ldap_filter, Host}, Opts,
+    ConfigFilter = eldap_utils:get_opt({ldap_filter, Host}, Opts,
                                        fun check_filter/1, <<"">>),
-    ConfigUserFilter = gen_mod:get_opt({ldap_ufilter, Host}, Opts,
+    ConfigUserFilter = eldap_utils:get_opt({ldap_ufilter, Host}, Opts,
                                            fun check_filter/1, <<"">>),
-    ConfigGroupFilter = gen_mod:get_opt({ldap_gfilter, Host}, Opts,
+    ConfigGroupFilter = eldap_utils:get_opt({ldap_gfilter, Host}, Opts,
                                             fun check_filter/1, <<"">>),
-    RosterFilter = gen_mod:get_opt({ldap_rfilter, Host}, Opts,
+    RosterFilter = eldap_utils:get_opt({ldap_rfilter, Host}, Opts,
                                        fun check_filter/1, <<"">>),
     SubFilter = <<"(&(", UIDAttr/binary, "=",
-                 UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
+               UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
     UserSubFilter = case ConfigUserFilter of
-                     <<"">> ->
-                         eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
-                     UString -> UString
-                   end,
+                       <<"">> ->
+                                 eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
+                                 UString -> UString
+                          end,
     GroupSubFilter = case ConfigGroupFilter of
-                      <<"">> ->
-                          eldap_filter:do_sub(SubFilter,
-                                              [{<<"%u">>, <<"*">>}]);
-                      GString -> GString
+                         <<"">> ->
+                                   eldap_filter:do_sub(SubFilter,
+                                                       [{<<"%u">>, <<"*">>}]);
+                                   GString -> GString
                     end,
     Filter = case ConfigFilter of
-              <<"">> -> SubFilter;
-              _ ->
-                  <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
-            end,
+           <<"">> -> SubFilter;
+                 _ ->
+                      <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
+                 end,
     UserFilter = case ConfigFilter of
-                  <<"">> -> UserSubFilter;
-                  _ ->
-                      <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
-                end,
+                 <<"">> -> UserSubFilter;
+                           _ ->
+                                    <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
+                           end,
     GroupFilter = case ConfigFilter of
-                   <<"">> -> GroupSubFilter;
-                   _ ->
-                       <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
-                         ")">>
-                 end,
+                   <<"">> -> GroupSubFilter;
+                              _ ->
+                                       <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
+                                                                        ")">>
+                              end,
+%%%%%%%%%%%%%
+    GroupBase = gen_mod:get_opt(ldap_group_base, Opts, fun iolist_to_binary/1,
+                               Cfg#eldap_config.base),
+    GroupIsDN = gen_mod:get_opt(ldap_group_is_dn, Opts,
+                                fun(on) -> true;
+                                   (off) -> false;
+                                   (false) -> false;
+                                   (true) -> true
+                                end, true),
+    MemberSelMode = gen_mod:get_opt(ldap_member_selection_mode, Opts,
+                               fun(memberattr_normal) -> memberattr_normal;
+                                  (memberattr_dn)     -> memberattr_dn;
+                                  (group_children)    -> group_children;
+                                  (Invalid) ->
+            ?ERROR_MSG("Invalid ldap_member_selection_mode '~p'. "
+                       "Value 'memberattr_normal' will be used instead.",
+                       [Invalid])
+       end, memberattr_normal),
+    SubscribeAll = gen_mod:get_opt(ldap_subscribe_all, Opts,
+                                fun(on) -> true;
+                                   (off) -> false;
+                                   (false) -> false;
+                                   (true) -> true
+                                end, false),
+    % MemberIsDN = (MemberSelMode == member_attr_dn) or (MemberSelMode == group_children),
+    ShGFilter = gen_mod:get_opt(ldap_shgfilter, Opts, 
+                               fun(all) -> all;
+                                  (none) -> none;
+                                  (S) -> check_filter(S)
+                             end, all),
+    ShGAttr = gen_mod:get_opt(ldap_shgattr, Opts,
+                              fun iolist_to_binary/1,
+                             << GroupAttr/binary >>),
+%%%%%%
     #state{host = Host, eldap_id = Eldap_ID,
-          servers = Cfg#eldap_config.servers,
-          backups = Cfg#eldap_config.backups,
+       servers = Cfg#eldap_config.servers,
+          backups = Cfg#eldap_config.backups,
            port = Cfg#eldap_config.port,
-          tls_options = Cfg#eldap_config.tls_options,
-          dn = Cfg#eldap_config.dn,
+             tls_options = Cfg#eldap_config.tls_options,
+                dn = Cfg#eldap_config.dn,
            password = Cfg#eldap_config.password,
            base = Cfg#eldap_config.base,
            deref_aliases = Cfg#eldap_config.deref_aliases,
-          uid = UIDAttr,
-          group_attr = GroupAttr, group_desc = GroupDesc,
-          user_desc = UserDesc, user_uid = UserUID,
-          uid_format = UIDAttrFormat,
-          uid_format_re = UIDAttrFormatRe, filter = Filter,
-          ufilter = UserFilter, rfilter = RosterFilter,
-          gfilter = GroupFilter, auth_check = AuthCheck,
-          user_cache_size = UserCacheSize,
-          user_cache_validity = UserCacheValidity,
-          group_cache_size = GroupCacheSize,
-          group_cache_validity = GroupCacheValidity}.
+             group_attr = GroupAttr, group_desc = GroupDesc,
+             user_desc = UserDesc, uid = UserUID,
+             uid_format = UIDAttrFormat,
+             uid_format_re = UIDAttrFormatRe, filter = Filter,
+             ufilter = UserFilter, rfilter = RosterFilter,
+             gfilter = GroupFilter, auth_check = AuthCheck,
+        group_base = GroupBase,
+        member_attr = UIDAttr,
+        member_selection_mode = MemberSelMode,
+        group_is_dn = GroupIsDN,
+        shgfilter = ShGFilter,
+        shg_attr = ShGAttr,
+        subscribe_all = SubscribeAll,
+             roster_cache_size = RosterCacheSize,
+             roster_cache_validity = RosterCacheValidity}.
 
 check_filter(F) ->
-    NewF = iolist_to_binary(F),
-    {ok, _} = eldap_filter:parse(NewF),
-    NewF.
+  NewF = iolist_to_binary(F),
+  {ok, _} = eldap_filter:parse(NewF),
+  NewF.
 
 mod_opt_type(deref_aliases) ->
     fun (never) -> never;
@@ -661,6 +936,20 @@ mod_opt_type(ldap_user_cache_validity) ->
     fun (I) when is_integer(I), I > 0 -> I end;
 mod_opt_type(ldap_userdesc) -> fun iolist_to_binary/1;
 mod_opt_type(ldap_useruid) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_group_base) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_group_is_dn) -> fun(B) when is_boolean(B) -> B end;
+mod_opt_type(ldap_member_selection_mode) ->
+    fun(memberattr_normal) -> memberattr_normal;
+       (memberattr_dn)     -> memberattr_dn;
+       (group_children)    -> group_children
+    end;
+mod_opt_type(ldap_subscribe_all) -> fun(B) when is_boolean(B) -> B end;
+mod_opt_type(ldap_shgfilter) ->
+    fun(all) -> all;
+       (none) -> none;
+       (S) -> check_filter(S)
+    end;
+mod_opt_type(ldap_shgattr) -> fun iolist_to_binary/1;
 mod_opt_type(_) ->
     [ldap_auth_check, ldap_filter, ldap_gfilter,
      ldap_group_cache_size, ldap_group_cache_validity,
@@ -672,7 +961,9 @@ mod_opt_type(_) ->
      ldap_deref_aliases, ldap_encrypt, ldap_password,
      ldap_port, ldap_rootdn, ldap_servers,
      ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,
-     ldap_tls_verify].
+     ldap_tls_verify, ldap_group_base, ldap_group_is_dn,
+     ldap_member_selection_mode, ldap_subscribe_all,
+     ldap_shgfilter, ldap_shgattr].
 
 opt_type(ldap_filter) -> fun check_filter/1;
 opt_type(ldap_gfilter) -> fun check_filter/1;