aboutsummaryrefslogtreecommitdiff
path: root/src/mod_vcard_ldap.erl
diff options
context:
space:
mode:
authorAlexey Shchepin <alexey@process-one.net>2006-09-14 02:54:21 +0000
committerAlexey Shchepin <alexey@process-one.net>2006-09-14 02:54:21 +0000
commit7d2a1af9d972e0b37eddafb86e5920956fd5334d (patch)
tree99206bb934f30d34d239ab68aa61448915a8f6cf /src/mod_vcard_ldap.erl
parent* src/odbc/mssql.sql: Removed unused fields. (diff)
* doc/guide.tex: Updated (thanks to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Better LDAP support (thanks to Evgeniy Khramtsov) * src/mod_vcard_ldap.erl: Likewise * src/eldap/eldap_filter.erl: Likewise SVN Revision: 606
Diffstat (limited to 'src/mod_vcard_ldap.erl')
-rw-r--r--src/mod_vcard_ldap.erl877
1 files changed, 547 insertions, 330 deletions
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index 68e1fc8a2..4389e4bce 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -1,8 +1,7 @@
-
%%%----------------------------------------------------------------------
%%% File : mod_vcard_ldap.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
-%%% Purpose :
+%%% Purpose : Support for VCards from LDAP storage.
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
@@ -11,13 +10,26 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
+-behaviour(gen_server).
-behaviour(gen_mod).
--export([start/2, init/3, stop/1,
- get_sm_features/5,
+%% 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_sm_features/5,
process_local_iq/3,
process_sm_iq/3,
- remove_user/1]).
+ remove_user/1
+ ]).
-include("ejabberd.hrl").
-include("eldap/eldap.hrl").
@@ -25,69 +37,164 @@
-define(PROCNAME, ejabberd_mod_vcard_ldap).
+-record(state, {serverhost,
+ myhost,
+ eldap_id,
+ search,
+ servers,
+ port,
+ dn,
+ base,
+ password,
+ uid,
+ uid_format,
+ vcard_map,
+ vcard_map_attrs,
+ user_filter,
+ search_filter,
+ search_fields,
+ search_reported,
+ search_reported_attrs
+ }).
+
+-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", "GIVEN"},
+ {"Middle Name", "MIDDLE"},
+ {"Family Name", "FAMILY"},
+ {"Nickname", "NICKNAME"},
+ {"Birthday", "BDAY"},
+ {"Country", "CTRY"},
+ {"City", "LOCALITY"},
+ {"Email", "EMAIL"},
+ {"Organization Name", "ORGNAME"},
+ {"Organization Unit", "ORGUNIT"}
+ ]).
+
+%% Unused callbacks.
+handle_cast(_Request, State) ->
+ {noreply, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+%% -----
+
+
start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ 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, ?PROCNAME),
+ gen_server:call(Proc, stop),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+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
+ end.
+
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+init([Host, Opts]) ->
+ State = parse_options(Host, Opts),
IQDisc = gen_mod:get_opt(iqdisc, Opts, 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),
- LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
- RootDN = ejabberd_config:get_local_option({ldap_rootdn, Host}),
- Password = ejabberd_config:get_local_option({ldap_password, Host}),
- eldap:start_link("mod_vcard_ldap", LDAPServers, 389, RootDN, Password),
- MyHost = gen_mod:get_opt(host, Opts, "vjud." ++ Host),
- Search = gen_mod:get_opt(search, Opts, true),
- register(gen_mod:get_module_proc(Host, ?PROCNAME),
- spawn(?MODULE, init, [MyHost, Host, Search])).
-
-init(Host, ServerHost, Search) ->
- case Search of
- false ->
- loop(Host, ServerHost);
+ eldap:start_link(State#state.eldap_id,
+ State#state.servers,
+ State#state.port,
+ State#state.dn,
+ State#state.password),
+ case State#state.search of
+ true ->
+ ejabberd_router:register_route(State#state.myhost);
_ ->
- ejabberd_router:register_route(Host),
- loop(Host, ServerHost)
- end.
-
-loop(Host, ServerHost) ->
- receive
- {route, From, To, Packet} ->
- case catch do_route(ServerHost, From, To, Packet) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- _ ->
- ok
- end,
- loop(Host, ServerHost);
- stop ->
- ejabberd_router:unregister_route(Host),
- ok;
+ ok
+ end,
+ {ok, State}.
+
+handle_info({route, From, To, Packet}, State) ->
+ case catch do_route(State, From, To, Packet) of
+ {'EXIT', Reason} ->
+ Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route(To, From, Err),
+ %% Fail-Stop. Let the supervisor restarts us
+ {stop, Reason, State};
_ ->
- loop(Host, ServerHost)
- end.
+ {noreply, State}
+ end;
-stop(Host) ->
- 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),
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- Proc ! stop,
- {wait, Proc}.
+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
+ [] ->
+ case Acc of
+ {result, Features} ->
+ {result, [?NS_VCARD | Features]};
+ empty ->
+ {result, [?NS_VCARD]}
+ end;
+ _ ->
+ Acc
end.
process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
@@ -102,7 +209,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
[{xmlcdata, "ejabberd"}]},
{xmlelement, "URL", [],
[{xmlcdata,
- "http://ejabberd.jabberstudio.org/"}]},
+ "http://ejabberd.jabber.ru/"}]},
{xmlelement, "DESC", [],
[{xmlcdata,
translate:translate(
@@ -114,113 +221,165 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
]}]}
end.
-find_ldap_user(Host, User) ->
- Attr = ejabberd_config:get_local_option({ldap_uidattr, Host}),
- Filter = eldap:equalityMatch(Attr, User),
- Base = ejabberd_config:get_local_option({ldap_base, Host}),
- case eldap:search("mod_vcard_ldap", [{base, Base},
- {filter, Filter},
- {attributes, []}]) of
- #eldap_search_result{entries = [E | _]} ->
- E;
- _ ->
- false
+-define(SM_IQ_TIMEOUT, 20000).
+
+process_sm_iq(From, #jid{lserver=LServer} = To, #iq{sub_el = SubEl} = IQ) ->
+ Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
+ case catch gen_server:call(Proc,
+ {process_sm_iq, From, To, IQ}, ?SM_IQ_TIMEOUT) of
+ {'EXIT', Reason} ->
+ case Reason of
+ {timeout, _} ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_REMOTE_SERVER_TIMEOUT]};
+ _ ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ end;
+ Other ->
+ Other
end.
-is_attribute_read_allowed(Name,From,To) ->
- true.
-
-ldap_attribute_to_vcard(Prefix,{Name,Values},From,To) ->
- case is_attribute_read_allowed(Name,From,To) of
- true ->
- ldap_lca_to_vcard(Prefix,stringprep:tolower(Name),Values);
+handle_call({process_sm_iq, _From, To, IQ}, _FromPid, State) ->
+ #iq{type = Type, sub_el = SubEl} = IQ,
+ Reply = case Type of
+ set ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ 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,
+ {reply, Reply, State};
+
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+
+handle_call(_Request, _From, State) ->
+ {reply, bad_request, State}.
+
+find_ldap_user(User, State) ->
+ Base = State#state.base,
+ RFC2254_Filter = State#state.user_filter,
+ Eldap_ID = State#state.eldap_id,
+ VCardAttrs = State#state.vcard_map_attrs,
+ case eldap_filter:parse(RFC2254_Filter, [{"%u", User}]) of
+ {ok, EldapFilter} ->
+ case eldap:search(Eldap_ID, [{base, Base},
+ {filter, EldapFilter},
+ {attributes, VCardAttrs}]) of
+ #eldap_search_result{entries = [E | _]} ->
+ E;
+ _ ->
+ false
+ end;
_ ->
- none
+ false
end.
-ldap_lca_to_vcard(vCard,"displayname",[Value|_]) ->
+ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
+ Attrs = lists:map(
+ fun({VCardName, _, _}) ->
+ {stringprep:tolower(VCardName),
+ map_vcard_attr(VCardName, Attributes, VCardMap, UD)}
+ end, VCardMap),
+ Elts = [ldap_attribute_to_vcard(vCard, Attr) || Attr <- Attrs],
+ NElts = [ldap_attribute_to_vcard(vCardN, Attr) || Attr <- Attrs],
+ OElts = [ldap_attribute_to_vcard(vCardO, Attr) || Attr <- Attrs],
+ AElts = [ldap_attribute_to_vcard(vCardA, Attr) || Attr <- Attrs],
+ [{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}],
+ lists:append([X || X <- Elts, X /= none],
+ [{xmlelement,"N",[], [X || X <- NElts, X /= none]},
+ {xmlelement,"ORG",[], [X || X <- OElts, X /= none]},
+ {xmlelement,"ADR",[], [X || X <- AElts, X /= none]}])
+ }].
+
+ldap_attribute_to_vcard(vCard, {"fn", Value}) ->
{xmlelement,"FN",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCard,"uid",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"nickname", Value}) ->
{xmlelement,"NICKNAME",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCard,"title",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"title", Value}) ->
{xmlelement,"TITLE",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCard,"labeleduri",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"bday", Value}) ->
+ {xmlelement,"BDAY",[],[{xmlcdata,Value}]};
+
+ldap_attribute_to_vcard(vCard, {"url", Value}) ->
{xmlelement,"URL",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCard,"description",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"desc", Value}) ->
{xmlelement,"DESC",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCard,"telephonenumber",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"role", Value}) ->
+ {xmlelement,"ROLE",[],[{xmlcdata,Value}]};
+
+ldap_attribute_to_vcard(vCard, {"tel", Value}) ->
{xmlelement,"TEL",[],[{xmlelement,"VOICE",[],[]},
{xmlelement,"WORK",[],[]},
{xmlelement,"NUMBER",[],[{xmlcdata,Value}]}]};
-ldap_lca_to_vcard(vCard,"mail",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"email", Value}) ->
{xmlelement,"EMAIL",[],[{xmlelement,"INTERNET",[],[]},
{xmlelement,"PREF",[],[]},
{xmlelement,"USERID",[],[{xmlcdata,Value}]}]};
-ldap_lca_to_vcard(vCardN,"sn",[Value|_]) ->
+ldap_attribute_to_vcard(vCard, {"photo", Value}) ->
+ {xmlelement,"PHOTO",[],[
+ {xmlelement,"BINVAL",[],[{xmlcdata, jlib:encode_base64(Value)}]}]};
+
+ldap_attribute_to_vcard(vCardN, {"family", Value}) ->
{xmlelement,"FAMILY",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCardN,"givenname",[Value|_]) ->
+ldap_attribute_to_vcard(vCardN, {"given", Value}) ->
{xmlelement,"GIVEN",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCardN,"initials",[Value|_]) ->
+ldap_attribute_to_vcard(vCardN, {"middle", Value}) ->
{xmlelement,"MIDDLE",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCardO,"o",[Value|_]) ->
+ldap_attribute_to_vcard(vCardO, {"orgname", Value}) ->
{xmlelement,"ORGNAME",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(vCardO,"ou",[Value|_]) ->
+ldap_attribute_to_vcard(vCardO, {"orgunit", Value}) ->
{xmlelement,"ORGUNIT",[],[{xmlcdata,Value}]};
-ldap_lca_to_vcard(_,_,_) -> none.
-
-ldap_attributes_to_vcard(Attributes,From,To) ->
- Elts = lists:map(fun(Attr) ->
- ldap_attribute_to_vcard(vCard,Attr,From,To)
- end,Attributes),
- FElts = [ X || X <- Elts, X /= none ],
- NElts = lists:map(fun(Attr) ->
- ldap_attribute_to_vcard(vCardN,Attr,From,To)
- end,Attributes),
- FNElts = [ X || X <- NElts, X /= none ],
- OElts = lists:map(fun(Attr) ->
- ldap_attribute_to_vcard(vCardO,Attr,From,To)
- end,Attributes),
- FOElts = [ X || X <- OElts, X /= none ],
- [{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}],
- lists:append(FElts,
- [{xmlelement,"N",[],FNElts},
- {xmlelement,"ORG",[],FOElts}])
- }].
+ldap_attribute_to_vcard(vCardA, {"locality", Value}) ->
+ {xmlelement,"LOCALITY",[],[{xmlcdata,Value}]};
-process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- #jid{luser = LUser, lserver = LServer} = To,
- case find_ldap_user(LServer, LUser) of
- #eldap_entry{attributes = Attributes} ->
- Vcard = ldap_attributes_to_vcard(Attributes,From,To),
- IQ#iq{type = result, sub_el = Vcard};
- _ ->
- IQ#iq{type = result, sub_el = []}
- end
- end.
+ldap_attribute_to_vcard(vCardA, {"street", Value}) ->
+ {xmlelement,"STREET",[],[{xmlcdata,Value}]};
+
+ldap_attribute_to_vcard(vCardA, {"ctry", Value}) ->
+ {xmlelement,"CTRY",[],[{xmlcdata,Value}]};
+
+ldap_attribute_to_vcard(vCardA, {"region", Value}) ->
+ {xmlelement,"REGION",[],[{xmlcdata,Value}]};
+
+ldap_attribute_to_vcard(vCardA, {"pcode", Value}) ->
+ {xmlelement,"PCODE",[],[{xmlcdata,Value}]};
+
+ldap_attribute_to_vcard(_, _) ->
+ none.
-define(TLFIELD(Type, Label, Var),
{xmlelement, "field", [{"type", Type},
{"label", translate:translate(Lang, Label)},
{"var", Var}], []}).
-
--define(FORM(JID),
+-define(FORM(JID, SearchFields),
[{xmlelement, "instructions", [],
[{xmlcdata, translate:translate(Lang, "You need an x:data capable client to search")}]},
{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
@@ -229,30 +388,15 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
jlib:jid_to_string(JID)}]},
{xmlelement, "instructions", [],
[{xmlcdata, translate:translate(Lang, "Fill in fields to search "
- "for any matching Jabber User")}]},
- ?TLFIELD("text-single", "User", "user"),
- ?TLFIELD("text-single", "Full Name", "fn"),
- ?TLFIELD("text-single", "Given Name", "given"),
- ?TLFIELD("text-single", "Middle Name", "middle"),
- ?TLFIELD("text-single", "Family Name", "family"),
- ?TLFIELD("text-single", "Nickname", "nickname"),
- ?TLFIELD("text-single", "Birthday", "bday"),
- ?TLFIELD("text-single", "Country", "ctry"),
- ?TLFIELD("text-single", "City", "locality"),
- ?TLFIELD("text-single", "Email", "email"),
- ?TLFIELD("text-single", "Organization Name", "orgname"),
- ?TLFIELD("text-single", "Organization Unit", "orgunit")
- ]}]).
-
+ "for any matching Jabber User")}]}
+ ] ++ lists:map(fun({X,Y}) -> ?TLFIELD("text-single", X, Y) end, SearchFields)}]).
-
-
-do_route(ServerHost, From, To, Packet) ->
+do_route(State, From, To, Packet) ->
#jid{user = User, resource = Resource} = To,
if
(User /= "") or (Resource /= "") ->
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router ! {route, To, From, Err};
+ ejabberd_router:route(To, From, Err);
true ->
IQ = jlib:iq_query_info(Packet),
case IQ of
@@ -285,24 +429,25 @@ do_route(ServerHost, From, To, Packet) ->
[{xmlelement, "x",
[{"xmlns", ?NS_XDATA},
{"type", "result"}],
- search_result(Lang, To, ServerHost, XData)
+ 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 = [{xmlelement,
"query",
[{"xmlns", ?NS_SEARCH}],
- ?FORM(To)
+ ?FORM(To, SearchFields)
}]},
ejabberd_router:route(To,
From,
jlib:iq_to_xml(ResIQ))
end;
- #iq{type = Type, xmlns = ?NS_DISCO_INFO, sub_el = SubEl} ->
+ #iq{type = Type, xmlns = ?NS_DISCO_INFO} ->
case Type of
set ->
Err = jlib:make_error_reply(
@@ -330,7 +475,7 @@ do_route(ServerHost, From, To, Packet) ->
From,
jlib:iq_to_xml(ResIQ))
end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} ->
+ #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
case Type of
set ->
Err = jlib:make_error_reply(
@@ -369,223 +514,295 @@ iq_get_vcard(Lang) ->
[{xmlcdata, "ejabberd/mod_vcard"}]},
{xmlelement, "URL", [],
[{xmlcdata,
- "http://ejabberd.jabberstudio.org/"}]},
+ "http://ejabberd.jabber.ru/"}]},
{xmlelement, "DESC", [],
[{xmlcdata, translate:translate(
Lang,
"ejabberd vCard module\n"
"Copyright (c) 2003-2006 Alexey Shchepin")}]}].
-find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
- find_xdata_el1(SubEls).
-
-find_xdata_el1([]) ->
- false;
-find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_XDATA ->
- {xmlelement, Name, Attrs, SubEls};
- _ ->
- find_xdata_el1(Els)
- end;
-find_xdata_el1([_ | Els]) ->
- find_xdata_el1(Els).
-
-define(LFIELD(Label, Var),
{xmlelement, "field", [{"label", translate:translate(Lang, Label)},
{"var", Var}], []}).
-search_result(Lang, JID, ServerHost, Data) ->
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Search Results for ") ++
- jlib:jid_to_string(JID)}]},
- {xmlelement, "reported", [],
- [?LFIELD("Jabber ID", "jid"),
- ?LFIELD("Full Name", "fn"),
- ?LFIELD("Given Name", "given"),
- ?LFIELD("Middle Name", "middle"),
- ?LFIELD("Family Name", "family"),
- ?LFIELD("Nickname", "nickname"),
- ?LFIELD("Birthday", "bday"),
- ?LFIELD("Country", "ctry"),
- ?LFIELD("City", "locality"),
- ?LFIELD("Email", "email"),
- ?LFIELD("Organization Name", "orgname"),
- ?LFIELD("Organization Unit", "orgunit")
- ]}] ++ lists:map(fun(E) ->
- record_to_item(E#eldap_entry.attributes)
- end, search(ServerHost, Data)).
+search_result(Lang, JID, State, Data) ->
+ SearchReported = State#state.search_reported,
+ Header = [{xmlelement, "title", [],
+ [{xmlcdata, translate:translate(Lang, "Search Results for ") ++
+ jlib:jid_to_string(JID)}]},
+ {xmlelement, "reported", [],
+ [?LFIELD("Jabber ID", "jid")] ++
+ lists:map(
+ fun({Name, Value}) -> ?LFIELD(Name, Value) end,
+ SearchReported)
+ }],
+ case search(State, Data) of
+ error ->
+ Header;
+ Result ->
+ Header ++ Result
+ end.
-define(FIELD(Var, Val),
{xmlelement, "field", [{"var", Var}],
[{xmlelement, "value", [],
[{xmlcdata, Val}]}]}).
-case_exact_compare(none,_) ->
- false;
-case_exact_compare(_,none) ->
- false;
-case_exact_compare(X,Y) ->
- X > Y.
-
-ldap_sort_entries(L) ->
- lists:sort(fun(E1,E2) ->
- case_exact_compare(ldap_get_value(E1,"cn"),ldap_get_value(E2,"cn"))
- end,L).
-
-ldap_get_value(E,Attribute) ->
- #eldap_entry{attributes = Attributes} = E,
- case lists:filter(fun({A,_}) ->
- string:equal(A,Attribute)
- end,Attributes) of
- [{Attr,[Value|_]}] ->
- Value;
- _ ->
- none
- end.
-
-
-ldap_attribute_to_item("uid",Value) ->
- [
- ?FIELD("jid",Value ++ "@" ++ ?MYNAME),
- ?FIELD("uid",Value),
- ?FIELD("nickname",Value)
- ];
-
-ldap_attribute_to_item("displayname",Value) ->
- [
- ?FIELD("fn",Value)
- ];
-
-ldap_attribute_to_item("sn",Value) ->
- [
- ?FIELD("family",Value)
- ];
-
-ldap_attribute_to_item("displayname",Value) ->
- [
- ?FIELD("fn",Value)
- ];
-
-ldap_attribute_to_item("givenname",Value) ->
- [
- ?FIELD("given",Value)
- ];
-
-ldap_attribute_to_item("initials",Value) ->
- [
- ?FIELD("middle",Value)
- ];
-
-ldap_attribute_to_item("mail",Value) ->
- [
- ?FIELD("email",Value)
- ];
-
-ldap_attribute_to_item("o",Value) ->
- [
- ?FIELD("orgname",Value)
- ];
-
-ldap_attribute_to_item("ou",Value) ->
- [
- ?FIELD("orgunit",Value)
- ];
-
-ldap_attribute_to_item(_,_) ->
- [none].
-
-record_to_item(Attributes) ->
- List = lists:append(lists:map(fun({Attr,[Value|_]}) ->
- ldap_attribute_to_item(stringprep:tolower(Attr),Value)
- end,Attributes)),
- FList = [X || X <- List, X /= none],
- {xmlelement, "item", [],FList}.
-
-search(LServer, Data) ->
- Filter = make_filter(Data),
- Base = ejabberd_config:get_local_option({ldap_base, LServer}),
- UIDAttr = ejabberd_config:get_local_option({ldap_uidattr, LServer}),
- case eldap:search("mod_vcard_ldap",[{base, Base},
- {filter, Filter},
- {attributes, []}]) of
+search(State, Data) ->
+ Base = State#state.base,
+ SearchFilter = State#state.search_filter,
+ Eldap_ID = State#state.eldap_id,
+ UA = State#state.uid,
+ UAF = State#state.uid_format,
+ ReportedAttrs = State#state.search_reported_attrs,
+ Filter = eldap:'and'([SearchFilter, make_filter(Data, UA, UAF)]),
+ case eldap:search(Eldap_ID, [{base, Base},
+ {filter, Filter},
+ {attributes, ReportedAttrs}]) of
#eldap_search_result{entries = E} ->
- [X || X <- E,
- ejabberd_auth:is_user_exists(
- ldap_get_value(X, UIDAttr), LServer)];
- Err ->
- ?ERROR_MSG("Bad search: ~p", [[LServer, {base, Base},
- {filter, Filter},
- {attributes, []}]])
+ search_items(E, State);
+ _ ->
+ error
end.
-
-make_filter(Data) ->
- Filter = [X || X <- lists:map(fun(R) ->
- make_assertion(R)
- end, Data),
- X /= none ],
+search_items(Entries, State) ->
+ LServer = State#state.serverhost,
+ SearchReported = State#state.search_reported,
+ VCardMap = State#state.vcard_map,
+ UIDAttr = State#state.uid,
+ UIDAttrFormat = State#state.uid_format,
+ Attributes = lists:map(
+ fun(E) ->
+ #eldap_entry{attributes = Attrs} = E,
+ Attrs
+ end, Entries),
+ lists:flatmap(
+ fun(Attrs) ->
+ U = get_ldap_attr(UIDAttr, Attrs),
+ case 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 ++ "@" ++ LServer)] ++
+ [?FIELD(Name, Value) || {Name, Value} <- RFields],
+ [{xmlelement, "item", [], Result}];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end
+ end, Attributes).
+
+make_filter(Data, UAttr, UAttrFormat) ->
+ Filter = lists:flatmap(
+ fun({Name, [Value | _]}) ->
+ case Name of
+ "%u" when Value /= "" ->
+ {ok, UAF, _} = regexp:sub(UAttrFormat, "%u", "*%u*"),
+ case eldap_filter:parse(
+ "("++UAttr++"="++UAF++")", [{"%u", Value}]) of
+ {ok, F} -> [F];
+ _ -> []
+ end;
+ _ when Value /= "" ->
+ [eldap:substrings(Name, [{any, Value}])];
+ _ ->
+ []
+ end
+ end, Data),
case Filter of
- [F] ->
+ [F] ->
F;
_ ->
eldap:'and'(Filter)
end.
+remove_user(_User) ->
+ true.
-make_assertion("givenName",Value) ->
- eldap:substrings("givenName",[{any,Value}]);
-
-make_assertion("cn",Value) ->
- eldap:substrings("cn",[{any,Value}]);
-
-make_assertion("sn",Value) ->
- eldap:substrings("sn",[{any,Value}]);
-
-make_assertion(Attr, Value) ->
- eldap:equalityMatch(Attr,Value).
-
-make_assertion({SVar, [Val]}) ->
- LAttr = ldap_attribute(SVar),
- case LAttr of
- none ->
- none;
- _ ->
- if
- is_list(Val) and (Val /= "") ->
- make_assertion(LAttr,Val);
- true ->
- none
+%%%-----------------------
+%%% Auxiliary functions.
+%%%-----------------------
+
+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.
-ldap_attribute("user") ->
- "uid";
-
-ldap_attribute("fn") ->
- "cn";
-
-ldap_attribute("family") ->
- "sn";
-
-ldap_attribute("given") ->
- "givenName";
-
-ldap_attribute("middle") ->
- "initials";
-
-ldap_attribute("email") ->
- "mail";
+case_insensitive_match(X, Y) ->
+ X1 = stringprep:tolower(X),
+ Y1 = stringprep:tolower(Y),
+ if
+ X1 == Y1 -> true;
+ true -> false
+ end.
-ldap_attribute("orgname") ->
- "o";
+map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
+ Res = lists:filter(
+ fun({Name, _, _}) ->
+ case_insensitive_match(Name, VCardName)
+ end, Pattern),
+ case Res of
+ [{_, Str, Attrs}] ->
+ process_pattern(Str, UD,
+ [get_ldap_attr(X, Attributes) || X<-Attrs]);
+ _ -> ""
+ end.
-ldap_attribute("orgunit") ->
- "ou";
+process_pattern(Str, {User, Domain}, Attrs) ->
+ Res = lists:foldl(
+ fun(X, Acc) ->
+ {ok, NewStr, _} = regexp:sub(Acc, "%s", X),
+ NewStr
+ end,
+ Str, Attrs),
+ eldap_filter:do_sub(Res, [{"%u", User},{"%d", Domain}]).
+
+get_ldap_attr(LDAPAttr, Attributes) ->
+ Res = lists:filter(
+ fun({Name, _}) ->
+ case_insensitive_match(Name, LDAPAttr)
+ end, Attributes),
+ case Res of
+ [{_, [Value|_]}] -> Value;
+ _ -> ""
+ end.
-ldap_attribute(_) ->
- none.
+find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
+ find_xdata_el1(SubEls).
-remove_user(User) ->
- true.
+find_xdata_el1([]) ->
+ false;
+find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
+ case xml:get_attr_s("xmlns", Attrs) of
+ ?NS_XDATA ->
+ {xmlelement, Name, Attrs, SubEls};
+ _ ->
+ find_xdata_el1(Els)
+ end;
+find_xdata_el1([_ | Els]) ->
+ find_xdata_el1(Els).
+parse_options(Host, Opts) ->
+ MyHost = gen_mod:get_opt(host, Opts, "vjud." ++ Host),
+ Search = gen_mod:get_opt(search, Opts, true),
+ Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?PROCNAME)),
+ 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,
+ UIDAttr = case gen_mod:get_opt(ldap_uidattr, Opts, undefined) of
+ undefined ->
+ case ejabberd_config:get_local_option({ldap_uidattr, Host}) of
+ undefined -> "uid";
+ UA -> UA
+ end;
+ UA -> UA
+ end,
+ UIDAttrFormat = case gen_mod:get_opt(ldap_uidattr_format, Opts, undefined) of
+ undefined ->
+ case ejabberd_config:get_local_option({ldap_uidattr_format, Host}) of
+ undefined -> "%u";
+ UAF -> UAF
+ end;
+ 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,
+ SubFilter = "("++UIDAttr++"="++UIDAttrFormat++")",
+ UserFilter = case gen_mod:get_opt(ldap_filter, Opts, undefined) of
+ undefined ->
+ case ejabberd_config:get_local_option({ldap_filter, Host}) of
+ undefined -> SubFilter;
+ "" -> SubFilter;
+ F -> "(&" ++ SubFilter ++ F ++ ")"
+ end;
+ "" -> SubFilter;
+ F -> "(&" ++ SubFilter ++ F ++ ")"
+ end,
+ {ok, SearchFilter} = eldap_filter:parse(
+ eldap_filter:do_sub(UserFilter, [{"%u","*"}])),
+ VCardMap = gen_mod:get_opt(ldap_vcard_map, Opts, ?VCARD_MAP),
+ SearchFields = gen_mod:get_opt(ldap_search_fields, Opts, ?SEARCH_FIELDS),
+ SearchReported = gen_mod:get_opt(ldap_search_reported, Opts, ?SEARCH_REPORTED),
+ %% In search requests we need to fetch only attributes defined
+ %% in vcard-map and search-reported. In some cases,
+ %% this will essentially reduce network traffic from an LDAP server.
+ VCardMapAttrs = lists:usort(
+ lists:append([A || {_, _, A} <- VCardMap]) ++ [UIDAttr]),
+ SearchReportedAttrs =
+ lists:usort(lists:flatmap(
+ fun({_, N}) ->
+ case lists:keysearch(N, 1, VCardMap) of
+ {value, {_, _, L}} -> L;
+ _ -> []
+ end
+ end, SearchReported) ++ [UIDAttr]),
+ #state{serverhost = Host,
+ myhost = MyHost,
+ eldap_id = Eldap_ID,
+ search = Search,
+ servers = LDAPServers,
+ port = LDAPPort,
+ dn = RootDN,
+ base = LDAPBase,
+ password = Password,
+ uid = UIDAttr,
+ uid_format = UIDAttrFormat,
+ vcard_map = VCardMap,
+ vcard_map_attrs = VCardMapAttrs,
+ user_filter = UserFilter,
+ search_filter = SearchFilter,
+ search_fields = SearchFields,
+ search_reported = SearchReported,
+ search_reported_attrs = SearchReportedAttrs
+ }.