Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/processone/ejabberd.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog13
-rw-r--r--examples/extauth/check_pass_null.pl50
-rw-r--r--src/ejabberd_auth.erl58
-rw-r--r--src/extauth.erl76
-rw-r--r--src/mod_vcard_ldap.erl572
5 files changed, 764 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 27c2374a5..98386a68f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2004-07-30 Alexey Shchepin <alexey@sevcom.net>
+
+ * 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 <alexey@sevcom.net>
* 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 <leifj@it.su.se>
+%%% Purpose : External authentication using a simple port-driver
+%%% Created : 30 Jul 2004 by Leif Johansson <leifj@it.su.se>
+%%% 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 <alexey@sevcom.net>
+%%% Purpose :
+%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
+%%% 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.
+