From 7d2a1af9d972e0b37eddafb86e5920956fd5334d Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Thu, 14 Sep 2006 02:54:21 +0000 Subject: * 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 --- src/mod_vcard_ldap.erl | 877 ++++++++++++++++++++++++++++++------------------- 1 file changed, 547 insertions(+), 330 deletions(-) (limited to 'src/mod_vcard_ldap.erl') 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 -%%% Purpose : +%%% Purpose : Support for VCards from LDAP storage. %%% Created : 2 Jan 2003 by Alexey Shchepin %%% 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 + }. -- cgit v1.2.3