-define(PROCNAME, ejabberd_mod_vcard).
-callback init(binary(), gen_mod:opts()) -> any().
+-callback stop(binary()) -> any().
-callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass.
-callback get_vcard(binary(), binary()) -> [xmlel()] | error.
-callback set_vcard(binary(), binary(),
xmlel(), #vcard_search{}) -> {atomic, any()}.
+-callback search_fields(binary()) -> [{binary(), binary()}].
+-callback search_reported(binary()) -> [{binary(), binary()}].
-callback search(binary(), [{binary(), [binary()]}], boolean(),
- infinity | pos_integer()) -> [binary()].
+ infinity | pos_integer()) -> [{binary(), binary()}].
-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
?NS_VCARD),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
+ Mod = gen_mod:db_type(Host, ?MODULE),
+ Mod:stop(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
-spec process_search(iq()) -> iq().
process_search(#iq{type = get, to = To, lang = Lang} = IQ) ->
- xmpp:make_iq_result(IQ, mk_search_form(To, Lang));
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ xmpp:make_iq_result(IQ, mk_search_form(To, ServerHost, Lang));
process_search(#iq{type = set, to = To, lang = Lang,
sub_els = [#search{xdata = #xdata{type = submit,
fields = Fs}}]} = IQ) ->
mk_field(Var, Val) ->
#xdata_field{var = Var, values = [Val]}.
--spec mk_search_form(jid(), undefined | binary()) -> search().
-mk_search_form(JID, Lang) ->
+-spec mk_search_form(jid(), binary(), undefined | binary()) -> search().
+mk_search_form(JID, ServerHost, Lang) ->
Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
(jid:to_string(JID))/binary>>,
- Fs = [mk_tfield(<<"User">>, <<"user">>, Lang),
- mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
- mk_tfield(<<"Name">>, <<"first">>, Lang),
- mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
- mk_tfield(<<"Family Name">>, <<"last">>, Lang),
- mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
- mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
- mk_tfield(<<"Country">>, <<"ctry">>, Lang),
- mk_tfield(<<"City">>, <<"locality">>, Lang),
- mk_tfield(<<"Email">>, <<"email">>, Lang),
- mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
- mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
+ Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+ SearchFields = Mod:search_fields(ServerHost),
+ Fs = [mk_tfield(Label, Var, Lang) || {Label, Var} <- SearchFields],
X = #xdata{type = form,
title = Title,
instructions =
-spec search_result(undefined | binary(), jid(), binary(), [xdata_field()]) -> xdata().
search_result(Lang, JID, ServerHost, XFields) ->
+ Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+ Reported = [mk_tfield(Label, Var, Lang) ||
+ {Label, Var} <- Mod:search_reported(ServerHost)],
#xdata{type = result,
title = <<(translate:translate(Lang,
<<"Search Results for ">>))/binary,
(jid:to_string(JID))/binary>>,
- reported = [mk_tfield(<<"Jabber ID">>, <<"jid">>, Lang),
- mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
- mk_tfield(<<"Name">>, <<"first">>, Lang),
- mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
- mk_tfield(<<"Family Name">>, <<"last">>, Lang),
- mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
- mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
- mk_tfield(<<"Country">>, <<"ctry">>, Lang),
- mk_tfield(<<"City">>, <<"locality">>, Lang),
- mk_tfield(<<"Email">>, <<"email">>, Lang),
- mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
- mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
- items = lists:map(fun (R) -> record_to_item(ServerHost, R) end,
+ reported = Reported,
+ items = lists:map(fun (Item) -> item_to_field(Item) end,
search(ServerHost, XFields))}.
--spec record_to_item(binary(), [binary()] | #vcard_search{}) -> [xdata_field()].
-record_to_item(LServer,
- [Username, FN, Family, Given, Middle, Nickname, BDay,
- CTRY, Locality, EMail, OrgName, OrgUnit]) ->
- [mk_field(<<"jid">>, <<Username/binary, "@", LServer/binary>>),
- mk_field(<<"fn">>, FN),
- mk_field(<<"last">>, Family),
- mk_field(<<"first">>, Given),
- mk_field(<<"middle">>, Middle),
- mk_field(<<"nick">>, Nickname),
- mk_field(<<"bday">>, BDay),
- mk_field(<<"ctry">>, CTRY),
- mk_field(<<"locality">>, Locality),
- mk_field(<<"email">>, EMail),
- mk_field(<<"orgname">>, OrgName),
- mk_field(<<"orgunit">>, OrgUnit)];
-record_to_item(_LServer, #vcard_search{} = R) ->
- {User, Server} = R#vcard_search.user,
- [mk_field(<<"jid">>, <<User/binary, "@", Server/binary>>),
- mk_field(<<"fn">>, (R#vcard_search.fn)),
- mk_field(<<"last">>, (R#vcard_search.family)),
- mk_field(<<"first">>, (R#vcard_search.given)),
- mk_field(<<"middle">>, (R#vcard_search.middle)),
- mk_field(<<"nick">>, (R#vcard_search.nickname)),
- mk_field(<<"bday">>, (R#vcard_search.bday)),
- mk_field(<<"ctry">>, (R#vcard_search.ctry)),
- mk_field(<<"locality">>, (R#vcard_search.locality)),
- mk_field(<<"email">>, (R#vcard_search.email)),
- mk_field(<<"orgname">>, (R#vcard_search.orgname)),
- mk_field(<<"orgunit">>, (R#vcard_search.orgunit))].
+-spec item_to_field([{binary(), binary()}]) -> [xdata_field()].
+item_to_field(Items) ->
+ [mk_field(Var, Value) || {Var, Value} <- Items].
-spec search(binary(), [xdata_field()]) -> [binary()].
search(LServer, XFields) ->
-%%%----------------------------------------------------------------------
-%%% File : mod_vcard_ldap.erl
-%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose : Support for VCards from LDAP storage.
-%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
-%%%
-%%% 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.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%---------------------u-------------------------------------------------
-
+%%% @end
+%%% Created : 29 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
-module(mod_vcard_ldap).
--behaviour(ejabberd_config).
-
--author('alexey@process-one.net').
-
-behaviour(gen_server).
+-behaviour(mod_vcard).
--behaviour(gen_mod).
-
-%% gen_server callbacks.
--export([init/1, handle_info/2, handle_call/3,
- handle_cast/2, terminate/2, code_change/3]).
+%% API
+-export([start_link/2]).
+-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4,
+ remove_user/2, import/2, search_fields/1, search_reported/1,
+ mod_opt_type/1, opt_type/1]).
--export([start/2, start_link/2, stop/1,
- get_sm_features/5, process_local_iq/3, process_sm_iq/3,
- remove_user/1, route/4, transform_module_options/1,
- mod_opt_type/1, opt_type/1, depends/2]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
-include("eldap.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(PROCNAME, ejabberd_mod_vcard_ldap).
deref_aliases = never :: never | searching | finding | always,
matches = 0 :: non_neg_integer()}).
--define(VCARD_MAP,
- [{<<"NICKNAME">>, <<"%u">>, []},
- {<<"FN">>, <<"%s">>, [<<"displayName">>]},
- {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
- {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
- {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
- {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
- {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
- {<<"CTRY">>, <<"%s">>, [<<"c">>]},
- {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
- {<<"STREET">>, <<"%s">>, [<<"street">>]},
- {<<"REGION">>, <<"%s">>, [<<"st">>]},
- {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
- {<<"TITLE">>, <<"%s">>, [<<"title">>]},
- {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
- {<<"DESC">>, <<"%s">>, [<<"description">>]},
- {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
- {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
- {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
- {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
- {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]).
-
--define(SEARCH_FIELDS,
- [{<<"User">>, <<"%u">>},
- {<<"Full Name">>, <<"displayName">>},
- {<<"Given Name">>, <<"givenName">>},
- {<<"Middle Name">>, <<"initials">>},
- {<<"Family Name">>, <<"sn">>},
- {<<"Nickname">>, <<"%u">>},
- {<<"Birthday">>, <<"birthDay">>},
- {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>},
- {<<"Email">>, <<"mail">>},
- {<<"Organization Name">>, <<"o">>},
- {<<"Organization Unit">>, <<"ou">>}]).
-
--define(SEARCH_REPORTED,
- [{<<"Full Name">>, <<"FN">>},
- {<<"Given Name">>, <<"FIRST">>},
- {<<"Middle Name">>, <<"MIDDLE">>},
- {<<"Family Name">>, <<"LAST">>},
- {<<"Nickname">>, <<"NICK">>},
- {<<"Birthday">>, <<"BDAY">>},
- {<<"Country">>, <<"CTRY">>},
- {<<"City">>, <<"LOCALITY">>},
- {<<"Email">>, <<"EMAIL">>},
- {<<"Organization Name">>, <<"ORGNAME">>},
- {<<"Organization Unit">>, <<"ORGUNIT">>}]).
-
-handle_cast(_Request, State) -> {noreply, State}.
-
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
-
-start(Host, Opts) ->
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+init(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
transient, 1000, worker, [?MODULE]},
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
-depends(_Host, _Opts) ->
- [].
-
-terminate(_Reason, State) ->
- Host = State#state.serverhost,
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
- ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
- ?NS_VCARD),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
- get_sm_features, 50),
- case State#state.search of
- true ->
- ejabberd_router:unregister_route(State#state.myhost);
- _ -> ok
+get_vcard(LUser, LServer) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ VCardMap = State#state.vcard_map,
+ case find_ldap_user(LUser, State) of
+ #eldap_entry{attributes = Attributes} ->
+ ldap_attributes_to_vcard(Attributes, VCardMap,
+ {LUser, LServer});
+ _ ->
+ []
end.
-start_link(Host, Opts) ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE,
- [Host, Opts], []).
+set_vcard(_LUser, _LServer, _VCard, _VCardSearch) ->
+ {atomic, not_implemented}.
+
+search_fields(LServer) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ State#state.search_fields.
+
+search_reported(LServer) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ State#state.search_reported.
+
+search(LServer, Data, _AllowReturnAll, MaxMatch) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ Base = State#state.base,
+ SearchFilter = State#state.search_filter,
+ Eldap_ID = State#state.eldap_id,
+ UIDs = State#state.uids,
+ ReportedAttrs = State#state.search_reported_attrs,
+ Filter = eldap:'and'([SearchFilter,
+ eldap_utils:make_filter(Data, UIDs)]),
+ case eldap_pool:search(Eldap_ID,
+ [{base, Base}, {filter, Filter}, {limit, MaxMatch},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ReportedAttrs}])
+ of
+ #eldap_search_result{entries = E} ->
+ search_items(E, State);
+ _ ->
+ []
+ end.
+search_items(Entries, State) ->
+ LServer = State#state.serverhost,
+ SearchReported = State#state.search_reported,
+ VCardMap = State#state.vcard_map,
+ UIDs = State#state.uids,
+ Attributes = lists:map(fun (E) ->
+ #eldap_entry{attributes = Attrs} = E, Attrs
+ end,
+ Entries),
+ lists:flatmap(
+ fun(Attrs) ->
+ case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
+ {U, UIDAttrFormat} ->
+ case eldap_utils:get_user_part(U, UIDAttrFormat) of
+ {ok, Username} ->
+ case ejabberd_auth:is_user_exists(Username,
+ LServer) of
+ true ->
+ RFields = lists:map(
+ fun({_, VCardName}) ->
+ {VCardName,
+ map_vcard_attr(VCardName,
+ Attrs,
+ VCardMap,
+ {Username,
+ ?MYNAME})}
+ end,
+ SearchReported),
+ J = <<Username/binary, $@, LServer/binary>>,
+ [{<<"jid">>, J} | RFields];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end;
+ <<"">> ->
+ []
+ end
+ end, Attributes).
+
+remove_user(_User, _Server) ->
+ {atomic, not_implemented}.
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
- one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_VCARD, ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
- get_sm_features, 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),
- case State#state.search of
- true ->
- ejabberd_router:register_route(State#state.myhost, Host);
- _ -> ok
- end,
{ok, State}.
-handle_info({route, From, To, Packet}, State) ->
- case catch do_route(State, From, To, Packet) of
- Pid when is_pid(Pid) -> ok;
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err)
- end,
- {noreply, State};
-handle_info(_Info, State) -> {noreply, State}.
-
-get_sm_features({error, _Error} = Acc, _From, _To,
- _Node, _Lang) ->
- Acc;
-get_sm_features(Acc, _From, _To, Node, _Lang) ->
- case Node of
- <<"">> ->
- case Acc of
- {result, Features} -> {result, [?NS_VCARD | Features]};
- empty -> {result, [?NS_VCARD]}
- end;
- _ -> Acc
- end.
+handle_call(get_state, _From, State) ->
+ {reply, {ok, State}, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
-process_local_iq(_From, _To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children =
- [#xmlel{name = <<"FN">>, attrs = [],
- children =
- [{xmlcdata, <<"ejabberd">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Erlang Jabber Server">>))/binary,
- "\nCopyright (c) 2002-2016 ProcessOne">>}]},
- #xmlel{name = <<"BDAY">>, attrs = [],
- children =
- [{xmlcdata, <<"2002-11-16">>}]}]}]}
- end.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
-process_sm_iq(_From, #jid{lserver = LServer} = To,
- #iq{sub_el = SubEl} = IQ) ->
- case catch process_vcard_ldap(To, IQ, LServer) of
- {'EXIT', _} ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
- Other -> Other
- end.
+handle_info(_Info, State) ->
+ {noreply, State}.
-process_vcard_ldap(To, IQ, Server) ->
- {ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
- #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ,
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- #jid{luser = LUser} = To,
- LServer = State#state.serverhost,
- case ejabberd_auth:is_user_exists(LUser, LServer) of
- true ->
- VCardMap = State#state.vcard_map,
- case find_ldap_user(LUser, State) of
- #eldap_entry{attributes = Attributes} ->
- Vcard = ldap_attributes_to_vcard(Attributes, VCardMap,
- {LUser, LServer}),
- IQ#iq{type = result, sub_el = Vcard};
- _ -> IQ#iq{type = result, sub_el = []}
- end;
- _ -> IQ#iq{type = result, sub_el = []}
- end
- end.
+terminate(_Reason, _State) ->
+ ok.
-handle_call(get_state, _From, State) ->
- {reply, {ok, State}, State};
-handle_call(stop, _From, State) ->
- {stop, normal, ok, State};
-handle_call(_Request, _From, State) ->
- {reply, bad_request, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
find_ldap_user(User, State) ->
Base = State#state.base,
RFC2254_Filter = State#state.user_filter,
children = [{xmlcdata, Value}]};
ldap_attribute_to_vcard(_, _) -> none.
--define(TLFIELD(Type, Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
-
--define(FORM(JID, SearchFields),
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need an x:data capable client to "
- "search">>)}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Search users in ">>))/binary,
- (jid:to_string(JID))/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Fill in fields to search for any matching "
- "Jabber User">>)}]}]
- ++
- lists:map(fun ({X, Y}) ->
- ?TLFIELD(<<"text-single">>, X, Y)
- end,
- SearchFields)}]).
-
-do_route(State, From, To, Packet) ->
- spawn(?MODULE, route, [State, From, To, Packet]).
-
-route(State, From, To, Packet) ->
- #jid{user = User, resource = Resource} = To,
- ServerHost = State#state.serverhost,
- if (User /= <<"">>) or (Resource /= <<"">>) ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err);
- true ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
- sub_el = SubEl} ->
- case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- Txt = <<"Data form not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- Txt = <<"Incorrect data form">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_SEARCH}],
- children =
- [#xmlel{name =
- <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_XDATA},
- {<<"type">>,
- <<"result">>}],
- children
- =
- search_result(Lang,
- To,
- State,
- XData)}]}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ))
- end
- end;
- get ->
- SearchFields = State#state.search_fields,
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_SEARCH}],
- children =
- ?FORM(To, SearchFields)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- get ->
- Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
- [],
- [ServerHost, ?MODULE,
- <<"">>, <<"">>]),
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_DISCO_INFO}],
- children =
- [#xmlel{name =
- <<"identity">>,
- attrs =
- [{<<"category">>,
- <<"directory">>},
- {<<"type">>,
- <<"user">>},
- {<<"name">>,
- translate:translate(Lang,
- <<"vCard User Search">>)}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_SEARCH}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_VCARD}],
- children = []}]
- ++ Info}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- get ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_DISCO_ITEMS}],
- children = []}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
- end.
-
-iq_get_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd vCard module">>))/binary,
- "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
--define(LFIELD(Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
-
-search_result(Lang, JID, State, Data) ->
- SearchReported = State#state.search_reported,
- Header = [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Search Results for ">>))/binary,
- (jid:to_string(JID))/binary>>}]},
- #xmlel{name = <<"reported">>, attrs = [],
- children =
- [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
- <<"jid">>)]
- ++
- lists:map(fun ({Name, Value}) ->
- ?TLFIELD(<<"text-single">>, Name,
- Value)
- end,
- SearchReported)}],
- case search(State, Data) of
- error -> Header;
- Result -> Header ++ Result
- end.
-
--define(FIELD(Var, Val),
- #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
-search(State, Data) ->
- Base = State#state.base,
- SearchFilter = State#state.search_filter,
- Eldap_ID = State#state.eldap_id,
- UIDs = State#state.uids,
- Limit = State#state.matches,
- ReportedAttrs = State#state.search_reported_attrs,
- Filter = eldap:'and'([SearchFilter,
- eldap_utils:make_filter(Data, UIDs)]),
- case eldap_pool:search(Eldap_ID,
- [{base, Base}, {filter, Filter}, {limit, Limit},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ReportedAttrs}])
- of
- #eldap_search_result{entries = E} ->
- search_items(E, State);
- _ -> error
- end.
-
-search_items(Entries, State) ->
- LServer = State#state.serverhost,
- SearchReported = State#state.search_reported,
- VCardMap = State#state.vcard_map,
- UIDs = State#state.uids,
- Attributes = lists:map(fun (E) ->
- #eldap_entry{attributes = Attrs} = E, Attrs
- end,
- Entries),
- lists:flatmap(fun (Attrs) ->
- case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
- {U, UIDAttrFormat} ->
- case eldap_utils:get_user_part(U, UIDAttrFormat)
- of
- {ok, Username} ->
- case
- ejabberd_auth:is_user_exists(Username,
- LServer)
- of
- true ->
- RFields = lists:map(fun ({_,
- VCardName}) ->
- {VCardName,
- map_vcard_attr(VCardName,
- Attrs,
- VCardMap,
- {Username,
- ?MYNAME})}
- end,
- SearchReported),
- Result = [?FIELD(<<"jid">>,
- <<Username/binary,
- "@",
- LServer/binary>>)]
- ++
- [?FIELD(Name, Value)
- || {Name, Value}
- <- RFields],
- [#xmlel{name = <<"item">>,
- attrs = [],
- children = Result}];
- _ -> []
- end;
- _ -> []
- end;
- <<"">> -> []
- end
- end,
- Attributes).
-
-remove_user(_User) -> true.
-
-%%%-----------------------
-%%% Auxiliary functions.
-%%%-----------------------
-
map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
Res = lists:filter(fun ({Name, _, _}) ->
eldap_utils:case_insensitive_match(Name,
[{<<"%u">>, User}, {<<"%d">>, Domain}] ++
[{<<"%s">>, V, 1} || V <- AttrValues]).
-find_xdata_el(#xmlel{children = SubEls}) ->
- find_xdata_el1(SubEls).
-
-find_xdata_el1([]) -> false;
-find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
- children = SubEls}
- | Els]) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- #xmlel{name = Name, attrs = Attrs, children = SubEls};
- _ -> find_xdata_el1(Els)
- end;
-find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
+default_vcard_map() ->
+ [{<<"NICKNAME">>, <<"%u">>, []},
+ {<<"FN">>, <<"%s">>, [<<"displayName">>]},
+ {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
+ {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
+ {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
+ {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
+ {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
+ {<<"CTRY">>, <<"%s">>, [<<"c">>]},
+ {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
+ {<<"STREET">>, <<"%s">>, [<<"street">>]},
+ {<<"REGION">>, <<"%s">>, [<<"st">>]},
+ {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
+ {<<"TITLE">>, <<"%s">>, [<<"title">>]},
+ {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
+ {<<"DESC">>, <<"%s">>, [<<"description">>]},
+ {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
+ {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
+ {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
+ {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
+ {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
+
+default_search_fields() ->
+ [{<<"User">>, <<"%u">>},
+ {<<"Full Name">>, <<"displayName">>},
+ {<<"Given Name">>, <<"givenName">>},
+ {<<"Middle Name">>, <<"initials">>},
+ {<<"Family Name">>, <<"sn">>},
+ {<<"Nickname">>, <<"%u">>},
+ {<<"Birthday">>, <<"birthDay">>},
+ {<<"Country">>, <<"c">>},
+ {<<"City">>, <<"l">>},
+ {<<"Email">>, <<"mail">>},
+ {<<"Organization Name">>, <<"o">>},
+ {<<"Organization Unit">>, <<"ou">>}].
+
+default_search_reported() ->
+ [{<<"Full Name">>, <<"FN">>},
+ {<<"Given Name">>, <<"FIRST">>},
+ {<<"Middle Name">>, <<"MIDDLE">>},
+ {<<"Family Name">>, <<"LAST">>},
+ {<<"Nickname">>, <<"NICK">>},
+ {<<"Birthday">>, <<"BDAY">>},
+ {<<"Country">>, <<"CTRY">>},
+ {<<"City">>, <<"LOCALITY">>},
+ {<<"Email">>, <<"EMAIL">>},
+ {<<"Organization Name">>, <<"ORGNAME">>},
+ {<<"Organization Unit">>, <<"ORGUNIT">>}].
parse_options(Host, Opts) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
[iolist_to_binary(E)
|| E <- L]}
end, Ls)
- end, ?VCARD_MAP),
+ end, default_vcard_map()),
SearchFields = gen_mod:get_opt(ldap_search_fields, Opts,
fun(Ls) ->
[{iolist_to_binary(S),
iolist_to_binary(P)}
|| {S, P} <- Ls]
- end, ?SEARCH_FIELDS),
+ end, default_search_fields()),
SearchReported = gen_mod:get_opt(ldap_search_reported, Opts,
fun(Ls) ->
[{iolist_to_binary(S),
iolist_to_binary(P)}
|| {S, P} <- Ls]
- end, ?SEARCH_REPORTED),
+ end, default_search_reported()),
UIDAttrs = [UAttr || {UAttr, _} <- UIDs],
VCardMapAttrs = lists:usort(lists:append([A
|| {_, _, A} <- VCardMap])
search_reported_attrs = SearchReportedAttrs,
matches = Matches}.
-transform_module_options(Opts) ->
- lists:map(
- fun({ldap_vcard_map, Map}) ->
- NewMap = lists:map(
- fun({Field, Pattern, Attrs}) ->
- {Field, [{Pattern, Attrs}]};
- (Opt) ->
- Opt
- end, Map),
- {ldap_vcard_map, NewMap};
- (Opt) ->
- Opt
- end, Opts).
-
check_filter(F) ->
NewF = iolist_to_binary(F),
{ok, _} = eldap_filter:parse(NewF),
NewF.
-mod_opt_type(host) -> fun iolist_to_binary/1;
-mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(ldap_filter) -> fun check_filter/1;
mod_opt_type(ldap_search_fields) ->
fun (Ls) ->
end,
Ls)
end;
-mod_opt_type(matches) ->
- fun (infinity) -> 0;
- (I) when is_integer(I), I > 0 -> I
- end;
-mod_opt_type(search) ->
- fun (B) when is_boolean(B) -> B end;
mod_opt_type(deref_aliases) ->
fun (never) -> never;
(searching) -> searching;
(false) -> false
end;
mod_opt_type(_) ->
- [host, iqdisc, ldap_filter, ldap_search_fields,
+ [ldap_filter, ldap_search_fields,
ldap_search_reported, ldap_uids, ldap_vcard_map,
- matches, search, deref_aliases, ldap_backups, ldap_base,
+ deref_aliases, ldap_backups, ldap_base,
ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers,
ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,