diff --git a/ChangeLog b/ChangeLog index 27c2374a5..98386a68f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2004-07-30 Alexey Shchepin + + * examples/extauth/check_pass_null.pl: A reference "null" + implementation of external authentification script (thanks to Leif + Johansson) + + * src/extauth.erl: Support for external authentification + (thanks to Leif Johansson) + * src/ejabberd_auth.erl: Likewise + + * src/mod_vcard_ldap.erl: A drop-in replacement for mod_vcard.erl + which uses ldap for JUD and vCard (thanks to Leif Johansson) + 2004-07-28 Alexey Shchepin * src/tls/tls_drv.c: Added freeing of SSL stuff diff --git a/examples/extauth/check_pass_null.pl b/examples/extauth/check_pass_null.pl new file mode 100644 index 000000000..7291abf0f --- /dev/null +++ b/examples/extauth/check_pass_null.pl @@ -0,0 +1,50 @@ +#!/usr/local/bin/perl + +use Unix::Syslog qw(:macros :subs); + +my $domain = $ARGV[0] || "example.com"; + +while(1) + { + # my $rin = '',$rout; + # vec($rin,fileno(STDIN),1) = 1; + # $ein = $rin; + # my $nfound = select($rout=$rin,undef,undef,undef); + + my $buf = ""; + syslog LOG_INFO,"waiting for packet"; + my $nread = sysread STDIN,$buf,2; + do { syslog LOG_INFO,"port closed"; exit; } unless $nread == 2; + my $len = unpack "n",$buf; + my $nread = sysread STDIN,$buf,$len; + + my ($op,$user,$password) = split /:/,$buf; + #$user =~ s/\./\//og; + my $jid = "$user\@$domain"; + my $result; + + syslog(LOG_INFO,"request (%s)", $op); + + SWITCH: + { + $op eq 'auth' and do + { + $result = 1; + },last SWITCH; + + $op eq 'setpass' and do + { + $result = 1; + },last SWITCH; + + $op eq 'isuser' and do + { + # password is null. Return 1 if the user $user\@$domain exitst. + $result = 1; + },last SWITCH; + }; + my $out = pack "nn",2,$result ? 1 : 0; + syswrite STDOUT,$out; + } + +closelog; diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index c697e8b73..d078223e1 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -47,7 +47,14 @@ %%% API %%%---------------------------------------------------------------------- start() -> + case auth_method() of + external -> + extauth:start(ejabberd_config:get_local_option(extauth_program)); + _ -> + ok + end, gen_server:start({local, ejabberd_auth}, ejabberd_auth, [], []). + start_link() -> gen_server:start_link({local, ejabberd_auth}, ejabberd_auth, [], []). @@ -68,6 +75,8 @@ init([]) -> case auth_method() of internal -> ok; + external -> + ok; ldap -> LDAPServers = ejabberd_config:get_local_option(ldap_servers), eldap:start_link("ejabberd", LDAPServers, 389, "", ""), @@ -124,6 +133,16 @@ terminate(_Reason, _State) -> auth_method() -> case ejabberd_config:get_local_option(auth_method) of + external -> + external; + ldap -> + ldap; + _ -> + internal + end. + +user_method() -> + case ejabberd_config:get_local_option(user_method) of ldap -> ldap; _ -> @@ -134,19 +153,31 @@ plain_password_required() -> case auth_method() of internal -> false; + external -> + true; ldap -> true end. - check_password(User, Password) -> case auth_method() of internal -> check_password_internal(User, Password); + external -> + check_password_external(User, Password); ldap -> check_password_ldap(User, Password) end. +check_password_external(User, Password) -> + extauth:check_password(User, Password). + +set_password_external(User, Password) -> + extauth:set_password(User, Password). + +is_user_exists_external(User) -> + extauth:is_user_exists(User). + check_password_internal(User, Password) -> LUser = jlib:nodeprep(User), case catch mnesia:dirty_read({passwd, LUser}) of @@ -160,6 +191,8 @@ check_password(User, Password, StreamID, Digest) -> case auth_method() of internal -> check_password_internal(User, Password, StreamID, Digest); + external -> + check_password_external(User, Password, StreamID, Digest); ldap -> check_password_ldap(User, Password, StreamID, Digest) end. @@ -183,8 +216,16 @@ check_password_internal(User, Password, StreamID, Digest) -> false end. - set_password(User, Password) -> + case auth_method() of + internal -> + set_password_internal(User,Password); + external -> + set_password_external(User,Password); + ldap -> {error, not_allowed} + end. + +set_password_internal(User, Password) -> case jlib:nodeprep(User) of error -> {error, invalid_jid}; LUser -> @@ -200,6 +241,8 @@ try_register(User, Password) -> case auth_method() of internal -> try_register_internal(User, Password); + external -> + {error, not_allowed}; ldap -> {error, not_allowed} end. @@ -246,6 +289,8 @@ is_user_exists(User) -> case auth_method() of internal -> is_user_exists_internal(User); + external -> + is_user_exists_external(User); ldap -> is_user_exists_ldap(User) end. @@ -262,13 +307,13 @@ is_user_exists_internal(User) -> end. remove_user(User) -> - case auth_method() of + case user_method() of internal -> remove_user_internal(User); ldap -> {error, not_allowed} end. - + remove_user_internal(User) -> LUser = jlib:nodeprep(User), F = fun() -> @@ -282,7 +327,7 @@ remove_user_internal(User) -> catch mod_private:remove_user(User). remove_user(User, Password) -> - case auth_method() of + case user_method() of internal -> remove_user_internal(User, Password); ldap -> @@ -322,6 +367,9 @@ remove_user_internal(User, Password) -> check_password_ldap(User, Password, StreamID, Digest) -> check_password_ldap(User, Password). +check_password_external(User, Password, StreamID, Digest) -> + check_password_external(User, Password). + check_password_ldap(User, Password) -> case find_user_dn(User) of false -> diff --git a/src/extauth.erl b/src/extauth.erl new file mode 100644 index 000000000..a7ac59fae --- /dev/null +++ b/src/extauth.erl @@ -0,0 +1,76 @@ +%%%---------------------------------------------------------------------- +%%% File : extauth.erl +%%% Author : Leif Johansson +%%% Purpose : External authentication using a simple port-driver +%%% Created : 30 Jul 2004 by Leif Johansson +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(extauth). +-author('leifj@it.su.se'). + +-export([start/1, stop/0, init/1, + check_password/2, set_password/2, is_user_exists/1 ]). + + +start(ExtPrg) -> + spawn(?MODULE, init, [ExtPrg]). + +init(ExtPrg) -> + register(eauth,self()), + process_flag(trap_exit,true), + Port = open_port({spawn, ExtPrg}, [{packet,2}]), + loop(Port). + +stop() -> + eauth ! stop. + +check_password(User,Password) -> + call_port(["auth",User,Password]). + +is_user_exists(User) -> + call_port(["isuser",User]). + +set_password(User,Password) -> + call_port(["setpass",User,Password]). + +call_port(Msg) -> + eauth ! {call, self(), Msg}, + receive + {eauth,Result}-> + Result + end. + +loop(Port) -> + receive + {call, Caller, Msg} -> + Port ! {self(), {command, encode(Msg)}}, + receive + {Port, {data, Data}} -> + Caller ! {eauth, decode(Data)} + end, + loop(Port); + stop -> + Port ! {self(), close}, + receive + {Port, closed} -> + exit(normal) + end; + {'EXIT', Port, Reason} -> + io:format("~p ~n", [Reason]), + exit(port_terminated) + end. + +join(List, Sep) -> + lists:foldl(fun(A, "") -> A; + (A, Acc) -> Acc ++ Sep ++ A + end, "", List). + +encode(L) -> + join(L,":"). + +decode([0,0]) -> + false; +decode([0,1]) -> + true. + diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl new file mode 100644 index 000000000..75c8a298d --- /dev/null +++ b/src/mod_vcard_ldap.erl @@ -0,0 +1,572 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_vcard_ldap.erl +%%% Author : Alexey Shchepin +%%% Purpose : +%%% Created : 2 Jan 2003 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(mod_vcard_ldap). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +-behaviour(gen_mod). + +-export([start/1, init/2, stop/0, + process_local_iq/3, + process_sm_iq/3, + remove_user/1]). + +-include("ejabberd.hrl"). +-include("eldap/eldap.hrl"). +-include("jlib.hrl"). + + +start(Opts) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), + gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_VCARD, + ?MODULE, process_local_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_VCARD, + ?MODULE, process_sm_iq, IQDisc), + LDAPServers = ejabberd_config:get_local_option(ldap_servers), + eldap:start_link("mod_vcard_ldap", LDAPServers, 389, "", ""), + Host = gen_mod:get_opt(host, Opts, "vjud." ++ ?MYNAME), + Search = gen_mod:get_opt(search, Opts, true), + register(ejabberd_mod_vcard_ldap, spawn(?MODULE, init, [Host, Search])). + +init(Host, Search) -> + case Search of + false -> + loop(Host); + _ -> + ejabberd_router:register_route(Host), + loop(Host) + end. + +loop(Host) -> + receive + {route, From, To, Packet} -> + case catch do_route(From, To, Packet) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p", [Reason]); + _ -> + ok + end, + loop(Host); + stop -> + catch ejabberd_router:unregister_route(Host), + ok; + _ -> + loop(Host) + end. + +stop() -> + gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_VCARD), + ejabberd_mod_vcard_ldap ! stop, + ok. + +process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> + case Type of + set -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + get -> + IQ#iq{type = result, + sub_el = [{xmlelement, "vCard", + [{"xmlns", ?NS_VCARD}], + [{xmlelement, "FN", [], + [{xmlcdata, "ejabberd"}]}, + {xmlelement, "URL", [], + [{xmlcdata, + "http://ejabberd.jabberstudio.org/"}]}, + {xmlelement, "DESC", [], + [{xmlcdata, + translate:translate( + Lang, + "Erlang Jabber Server\n" + "Copyright (c) 2002-2004 Alexey Shchepin")}]}, + {xmlelement, "BDAY", [], + [{xmlcdata, "2002-11-16"}]} + ]}]} + end. + +find_ldap_user(User) -> + Attr = ejabberd_config:get_local_option(ldap_uidattr), + Filter = eldap:equalityMatch(Attr, User), + Base = ejabberd_config:get_local_option(ldap_base), + case eldap:search("mod_vcard_ldap", [{base, Base}, + {filter, Filter}, + {attributes, []}]) of + #eldap_search_result{entries = [E | _]} -> + E; + _ -> + false + 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); + _ -> + none + end. + +ldap_lca_to_vcard(vCard,"displayname",[Value|_]) -> + {xmlelement,"FN",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCard,"uid",[Value|_]) -> + {xmlelement,"NICKNAME",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCard,"title",[Value|_]) -> + {xmlelement,"TITLE",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCard,"labeleduri",[Value|_]) -> + {xmlelement,"URL",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCard,"description",[Value|_]) -> + {xmlelement,"DESC",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCard,"telephonenumber",[Value|_]) -> + {xmlelement,"TEL",[],[{xmlelement,"VOICE",[],[]}, + {xmlelement,"WORK",[],[]}, + {xmlelement,"NUMBER",[],[{xmlcdata,Value}]}]}; + +ldap_lca_to_vcard(vCard,"mail",[Value|_]) -> + {xmlelement,"EMAIL",[],[{xmlelement,"INTERNET",[],[]}, + {xmlelement,"PREF",[],[]}, + {xmlelement,"USERID",[],[{xmlcdata,Value}]}]}; + +ldap_lca_to_vcard(vCardN,"sn",[Value|_]) -> + {xmlelement,"FAMILY",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCardN,"givenname",[Value|_]) -> + {xmlelement,"GIVEN",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCardN,"initials",[Value|_]) -> + {xmlelement,"MIDDLE",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCardO,"o",[Value|_]) -> + {xmlelement,"ORGNAME",[],[{xmlcdata,Value}]}; + +ldap_lca_to_vcard(vCardO,"ou",[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}]) + }]. + +is_self_request(From,To) -> + #jid{luser = RUser, lserver = RServer } = From, + #jid{luser = LUser} = To, + case RServer == ?MYNAME of + true -> + LUser == RUser; + _ -> + false + end. + +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} = To, + case find_ldap_user(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. + +-define(TLFIELD(Type, Label, Var), + {xmlelement, "field", [{"type", Type}, + {"label", translate:translate(Lang, Label)}, + {"var", Var}], []}). + + +-define(FORM(JID), + [{xmlelement, "instructions", [], + [{xmlcdata, translate:translate(Lang, "You need an x:data capable client to search")}]}, + {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}], + [{xmlelement, "title", [], + [{xmlcdata, translate:translate(Lang, "Search users in ") ++ + 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") + ]}]). + + + + +do_route(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}; + 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 -> + Err = jlib:make_error_reply( + Packet, ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, Err); + _ -> + XData = jlib:parse_xdata_submit(XDataEl), + case XData of + invalid -> + Err = jlib:make_error_reply( + Packet, + ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, + Err); + _ -> + ResIQ = + IQ#iq{ + type = result, + sub_el = + [{xmlelement, + "query", + [{"xmlns", ?NS_SEARCH}], + [{xmlelement, "x", + [{"xmlns", ?NS_XDATA}, + {"type", "result"}], + search_result(Lang, To, XData) + }]}]}, + ejabberd_router:route( + To, From, jlib:iq_to_xml(ResIQ)) + end + end; + get -> + ResIQ = IQ#iq{type = result, + sub_el = [{xmlelement, + "query", + [{"xmlns", ?NS_SEARCH}], + ?FORM(To) + }]}, + ejabberd_router:route(To, + From, + jlib:iq_to_xml(ResIQ)) + end; + #iq{type = Type, xmlns = ?NS_DISCO_INFO, sub_el = SubEl} -> + case Type of + set -> + Err = jlib:make_error_reply( + Packet, ?ERR_NOT_ALLOWED), + ejabberd_router:route(To, From, Err); + get -> + ResIQ = + IQ#iq{type = result, + sub_el = [{xmlelement, + "query", + [{"xmlns", ?NS_DISCO_INFO}], + [{xmlelement, "identity", + [{"category", "directory"}, + {"type", "user"}, + {"name", + "vCard User Search"}], + []}, + {xmlelement, "feature", + [{"var", ?NS_SEARCH}], []}, + {xmlelement, "feature", + [{"var", ?NS_VCARD}], []} + ] + }]}, + ejabberd_router:route(To, + From, + jlib:iq_to_xml(ResIQ)) + end; + #iq{type = Type, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} -> + case Type of + set -> + Err = jlib:make_error_reply( + Packet, ?ERR_NOT_ALLOWED), + ejabberd_router:route(To, From, Err); + get -> + ResIQ = + IQ#iq{type = result, + sub_el = [{xmlelement, + "query", + [{"xmlns", ?NS_DISCO_INFO}], + []}]}, + 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 = [{xmlelement, + "vCard", + [{"xmlns", ?NS_VCARD}], + 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) -> + [{xmlelement, "FN", [], + [{xmlcdata, "ejabberd/mod_vcard"}]}, + {xmlelement, "URL", [], + [{xmlcdata, + "http://ejabberd.jabberstudio.org/"}]}, + {xmlelement, "DESC", [], + [{xmlcdata, translate:translate( + Lang, + "ejabberd vCard module\n" + "Copyright (c) 2003-2004 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, Data) -> + [{xmlelement, "title", [], + [{xmlcdata, translate:translate(Lang, "Results of search in ") ++ + jlib:jid_to_string(JID)}]}, + {xmlelement, "reported", [], + [?LFIELD("JID", "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(Data)). + +-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(Data) -> + Filter = make_filter(Data), + Base = ejabberd_config:get_local_option(ldap_base), + UIDAttr = ejabberd_config:get_local_option(ldap_uidattr), + case eldap:search("mod_vcard_ldap",[{base,Base}, + {filter, Filter}, + {attributes, []}]) of + #eldap_search_result{entries = E} -> + [X || X <- E, ejabberd_auth:is_user_exists(ldap_get_value(X,UIDAttr)) ]; + _ -> + ?ERROR_MSG("~p", ["Bad search"]) + end. + + +make_filter(Data) -> + Filter = [X || X <- lists:map(fun(R) -> + make_assertion(R) + end,Data), + X /= none ], + case Filter of + [F] -> + F; + _ -> + eldap:'and'(Filter) + end. + + +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 + 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"; + +ldap_attribute("orgname") -> + "o"; + +ldap_attribute("orgunit") -> + "ou"; + +ldap_attribute(_) -> + none. + +remove_user(User) -> + true. +