[ejabberd] Updated external authentication patch and ldap-based vcard+jud

Leif Johansson leifj at it.su.se
Tue Jul 20 02:14:53 MSD 2004


About the files:

extauth.erl
ejabberd_auth.erl.diff
check_pass_null.pl
	
	Implements external authentication using a simple port-driver.
	The perl-script is a reference "null" implementation. This
	version adds support for is_user_exists which I incorrectly
	placed in the user-backend in the last version. The modifi-
	cation to ejabberd_auth includes a split of auth_method into
	auth_method and user_method to support (eg) ldap-user storage
	and PAM (external) authentication. TODO: an extauth supervisor
	is needed.

ejabberd_sm.erl.diff

	The code for routing messages to offline storage didn't look
	right to me... I am not 100% sure about this patch though. It
	works for me.

mod_vcard_ldap.erl

	A drop-in replacement for mod_vcard.erl which uses ldap for
	JUD and vCard. Some configuration is hard-wired into the code
	right now but the vCard to attribute mapping should be ok for
	most cases. I will fix this shortly. It works for me.


	Cheers Leif
-------------- next part --------------

-module(extauth).
-author('leifj at it.su.se').

-export([start/1, stop/0, init/1, check_password/2, set_password/2, is_user_exists/1 ]).

-include("ejabberd.hrl").

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.

-------------- next part --------------
Index: ejabberd_auth.erl
===================================================================
RCS file: /home/cvs/ejabberd/src/ejabberd_auth.erl,v
retrieving revision 1.20
diff -u -r1.20 ejabberd_auth.erl
--- ejabberd_auth.erl	5 Jul 2004 21:51:22 -0000	1.20
+++ ejabberd_auth.erl	19 Jul 2004 22:01:01 -0000
@@ -47,7 +47,12 @@
 %%% API
 %%%----------------------------------------------------------------------
 start() ->
+    case auth_method() of
+	external ->
+	    extauth:start(ejabberd_config:get_local_option(extauth_program))
+    end,
     gen_server:start({local, ejabberd_auth}, ejabberd_auth, [], []).
+
 start_link() ->
     gen_server:start_link({local, ejabberd_auth}, ejabberd_auth, [], []).
 
@@ -68,6 +73,8 @@
     case auth_method() of
 	internal ->
 	    ok;
+	external ->
+	    ok;
 	ldap ->
 	    LDAPServers = ejabberd_config:get_local_option(ldap_servers),
 	    eldap:start_link("ejabberd", LDAPServers, 389, "", "")
@@ -123,6 +130,16 @@
 
 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;
 	_ ->
@@ -133,19 +150,31 @@
     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
@@ -159,6 +188,8 @@
     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.
@@ -182,8 +213,16 @@
 	    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 ->
@@ -199,6 +238,8 @@
     case auth_method() of
 	internal ->
 	    try_register_internal(User, Password);
+	external ->
+	    {error, not_allowed};
 	ldap ->
 	    {error, not_allowed}
     end.
@@ -245,6 +286,8 @@
     case auth_method() of
 	internal ->
 	    is_user_exists_internal(User);
+	external ->
+	    is_user_exists_external(User);
 	ldap ->
 	    is_user_exists_ldap(User)
     end.
@@ -261,27 +304,22 @@
     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() ->
 		mnesia:delete({passwd, LUser})
         end,
-    mnesia:transaction(F),
-    catch mod_roster:remove_user(User),
-    catch mod_offline:remove_user(User),
-    catch mod_last:remove_user(User),
-    catch mod_vcard:remove_user(User),
-    catch mod_private:remove_user(User).
+    mnesia:transaction(F).
 
 remove_user(User, Password) ->
-    case auth_method() of
+    case user_method() of
 	internal ->
 	    remove_user_internal(User, Password);
 	ldap ->
@@ -321,6 +359,9 @@
 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 ->
-------------- next part --------------
A non-text attachment was scrubbed...
Name: check_pass_null.pl
Type: application/x-perl
Size: 1083 bytes
Desc: not available
Url : http://lists.jabber.ru/pipermail/ejabberd/attachments/20040720/bd81935d/check_pass_null.pl
-------------- next part --------------
Index: ejabberd_sm.erl
===================================================================
RCS file: /home/cvs/ejabberd/src/ejabberd_sm.erl,v
retrieving revision 1.40
diff -u -r1.40 ejabberd_sm.erl
--- ejabberd_sm.erl	11 Jul 2004 20:51:50 -0000	1.40
+++ ejabberd_sm.erl	19 Jul 2004 22:04:24 -0000
@@ -255,14 +255,18 @@
 
 route_message(From, To, Packet) ->
     LUser = To#jid.luser,
-    case catch lists:max(get_user_present_resources(LUser)) of
-	{'EXIT', _} ->
+    PResources = get_user_present_resources(LUser),
+    ?INFO_MSG("Resources for ~p: ~p",[LUser,PResources]),
+    if 
+	PResources == [] ->
+	    ?INFO_MSG("Type: ~p",[xml:get_tag_attr_s("type",Packet)]),
 	    case xml:get_tag_attr_s("type", Packet) of
 		"error" ->
 		    ok;
 		_ ->
 		    case ejabberd_auth:is_user_exists(LUser) of
 			true ->
+			    ?INFO_MSG("offline ~p -> ~p: ~p",[From,To,Packet]),
 			    case catch mod_offline:store_packet(
 					 From, To, Packet) of
 				{'EXIT', _} ->
@@ -278,16 +282,19 @@
 			    ejabberd_router:route(To, From, Err)
 		    end
 	    end;
-	{_, R} ->
-	    LResource = jlib:resourceprep(R),
-	    LUR = {LUser, LResource},
-	    case mnesia:dirty_read({session, LUR}) of
-		[] ->
-		    ok;				% Race condition
-		[Sess] ->
-		    Pid = Sess#session.pid,
-		    ?DEBUG("sending to process ~p~n", [Pid]),
-		    Pid ! {route, From, To, Packet}
+	true ->
+	    case lists:max(PResources) of
+		{_, R} ->
+		    LResource = jlib:resourceprep(R),
+		    LUR = {LUser, LResource},
+		    case mnesia:dirty_read({session, LUR}) of
+			[] ->
+			    ok;				% Race condition
+			[Sess] ->
+			    Pid = Sess#session.pid,
+			    ?DEBUG("sending to process ~p~n", [Pid]),
+			    Pid ! {route, From, To, Packet}
+		    end
 	    end
     end.
 
-------------- next part --------------
%%%----------------------------------------------------------------------
%%% File    : mod_vcard.erl
%%% Author  : Alexey Shchepin <alexey at sevcom.net>
%%% Purpose : 
%%% Created :  2 Jan 2003 by Alexey Shchepin <alexey at sevcom.net>
%%% Id      : $Id: mod_vcard.erl,v 1.26 2004/03/08 20:20:14 aleksey Exp $
%%%----------------------------------------------------------------------

-module(mod_vcard_ldap).
-author('alexey at sevcom.net').
-vsn('$Revision: 1.26 $ ').

-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.



More information about the ejabberd mailing list