summaryrefslogblamecommitdiff
path: root/src/mod_disco.erl
blob: 3a7578ddf0dfc229ad6629e1f35ed1ccb2bb2eb2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12


                                                                         
                                                  
                                                                






                                                                         

                    
                 
                



                                  
                            

                                 




                                   

                         
                     

                          



                                                    
 
              

                                         
                                                      
                                                                  
                                                                           
                                                                 
                                                                          
                                                               
                                                                        
                                                              
                                                                       

                                                                      


                                           
 


                                                                           

                                                                         

       



                                                                      



                                                                  

 



                                                                      



                                                                      







                                                                           
                                                                                       

                
                                                                    
              

                                                      
 

                                                   
                                                                            
                       




                                                                  


                                                                               





                                                                         
                                         
                                                                        
                       



               

                                                                

                
                                                                    
              



                                                           

                                                                       




                                                                 
                                                               



                                                                      
                            
                                                                            







                                                                       











                                                                 
                                                          




                                                       
                                                                                


                                                                  



                                                         
                                                                                


                                                                 



                                                         
                                                                                


                                                                 



                                                         
                                                                                
                                         



                                                         
                                                                                
                    
                                                                              






                                                    

                                                


                                                
                         





                                                    

                                  

                                                                          
 


                                      

                                                                          
            










                                                         



                                                       



                                               






                                                  


                                                            
                                       













                                                                            

                                                    




                                 


                                                  


                                                      





                                                   

                                                          
                                                      
                                                                
                                                                         

                                                       
        

                                                                

                 








                                                                            










                                                                       








                                                                            
                           
                                          




 
















                                                                         



























                                                                              

        




                                                      
                                                         
                      
                       







                                                                         
                                       





















                                                                            
 





































                                                                               






















                                                                                    
                                                                    







































                                                                               


        

                                                            





                                                               
                                                                    
















                                                                               
                    







                                                                          











                                                                    




                                                          
%%%----------------------------------------------------------------------
%%% File    : mod_disco.erl
%%% Author  : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Service Discovery (JEP-0030) support
%%% Created :  1 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id      : $Id$
%%%----------------------------------------------------------------------

-module(mod_disco).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').

-behaviour(gen_mod).

-export([start/1,
	 stop/0,
	 process_local_iq_items/3,
	 process_local_iq_info/3,
	 process_sm_iq_items/3,
	 process_sm_iq_info/3,
	 register_feature/1,
	 unregister_feature/1,
	 register_extra_domain/1,
	 unregister_extra_domain/1,
	 register_sm_feature/1,
	 unregister_sm_feature/1,
	 register_sm_node/4,
	 unregister_sm_node/1]).

-include("ejabberd.hrl").
-include("jlib.hrl").

-define(EMPTY_INFO_RESULT,
	IQ#iq{type = result,
	      sub_el = [{xmlelement, "query",
			 [{"xmlns", ?NS_DISCO_INFO},
			  {"node", SNode}], []}]}).

start(Opts) ->
    ejabberd_local:refresh_iq_handlers(),

    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
    gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_DISCO_ITEMS,
				  ?MODULE, process_local_iq_items, IQDisc),
    gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_DISCO_INFO,
				  ?MODULE, process_local_iq_info, IQDisc),
    gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_DISCO_ITEMS,
				  ?MODULE, process_sm_iq_items, IQDisc),
    gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_DISCO_INFO,
				  ?MODULE, process_sm_iq_info, IQDisc),

    catch ets:new(disco_features, [named_table, ordered_set, public]),
    register_feature("iq"),
    register_feature("presence"),
    register_feature("presence-invisible"),

    catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
    ExtraDomains = gen_mod:get_opt(extra_domains, Opts, []),
    lists:foreach(fun register_extra_domain/1, ExtraDomains),
    catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
    catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
    ok.

stop() ->
    gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_DISCO_ITEMS),
    gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_DISCO_INFO),
    gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_DISCO_ITEMS),
    gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_DISCO_INFO),
    catch ets:delete(disco_features),
    catch ets:delete(disco_extra_domains),
    ok.


register_feature(Feature) ->
    catch ets:new(disco_features, [named_table, ordered_set, public]),
    ets:insert(disco_features, {Feature}).

unregister_feature(Feature) ->
    catch ets:new(disco_features, [named_table, ordered_set, public]),
    ets:delete(disco_features, Feature).

register_extra_domain(Domain) ->
    catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
    ets:insert(disco_extra_domains, {Domain}).

unregister_extra_domain(Domain) ->
    catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
    ets:delete(disco_extra_domains, Domain).

process_local_iq_items(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 ->
	    SNode = xml:get_tag_attr_s("node", SubEl),
	    Node = string:tokens(SNode, "/"),

	    case acl:match_rule(configure, From) of
		deny when Node /= [] ->
		    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
		deny ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement, "query",
				     [{"xmlns", ?NS_DISCO_ITEMS}],
				     get_services_only()
				    }]};
		_ ->
		    case get_local_items(Node, jlib:jid_to_string(To), Lang) of
			{result, Res} ->
			    IQ#iq{type = result,
				  sub_el = [{xmlelement, "query",
					     [{"xmlns", ?NS_DISCO_ITEMS},
					      {"node", SNode}],
					     Res
					    }]};
			{error, Error} ->
			    IQ#iq{type = error, sub_el = [SubEl, Error]}
		    end
	    end
    end.


process_local_iq_info(From, _To, #iq{type = Type, xmlns = XMLNS,
				     sub_el = SubEl} = IQ) ->
    case Type of
	set ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
	get ->
	    SNode = xml:get_tag_attr_s("node", SubEl),
	    Node = string:tokens(SNode, "/"),
	    case {acl:match_rule(configure, From), Node} of
		{_, []} ->
		    Features = lists:map(fun feature_to_xml/1,
					 ets:tab2list(disco_features)),
		    IQ#iq{type = result,
			  sub_el = [{xmlelement,
				     "query",
				     [{"xmlns", ?NS_DISCO_INFO}],
				     [{xmlelement, "identity",
				       [{"category", "server"},
					{"type", "im"},
					{"name", "ejabberd"}], []}] ++
				     Features
				    }]};
		{deny, _} ->
		    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
		{allow, ["config"]} -> ?EMPTY_INFO_RESULT;
		{allow, ["online users"]} -> ?EMPTY_INFO_RESULT;
		{allow, ["all users"]} -> ?EMPTY_INFO_RESULT;
		{allow, ["all users", [$@ | _]]} -> ?EMPTY_INFO_RESULT;
		{allow, ["outgoing s2s" | _]} -> ?EMPTY_INFO_RESULT;
		{allow, ["running nodes"]} -> ?EMPTY_INFO_RESULT;
		{allow, ["stopped nodes"]} -> ?EMPTY_INFO_RESULT;
		{allow, ["running nodes", ENode]} ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement,
				     "query",
				     [{"xmlns", XMLNS},
				      {"node", SNode}],
				     [{xmlelement, "identity",
				       [{"category", "ejabberd"},
					{"type", "node"},
					{"name", ENode}], []},
				      feature_to_xml({?NS_STATS})
				     ]
				    }]};
		{allow, ["running nodes", ENode, "DB"]} ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement,
				     "query",
				     [{"xmlns", XMLNS},
				      {"node", SNode}],
				     [feature_to_xml({?NS_EJABBERD_CONFIG})]}]};
		{allow, ["running nodes", ENode, "modules"]} ->
		    ?EMPTY_INFO_RESULT;
		{allow, ["running nodes", ENode, "modules", _]} ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement, "query",
				     [{"xmlns", XMLNS},
				      {"node", SNode}],
				     [feature_to_xml({?NS_EJABBERD_CONFIG})]}]};
		{allow, ["running nodes", ENode, "backup"]} ->
		    ?EMPTY_INFO_RESULT;
		{allow, ["running nodes", ENode, "backup", _]} ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement, "query",
				     [{"xmlns", XMLNS},
				      {"node", SNode}],
				     [feature_to_xml({?NS_EJABBERD_CONFIG})]}]};
		{allow, ["running nodes", ENode, "import"]} ->
		    ?EMPTY_INFO_RESULT;
		{allow, ["running nodes", ENode, "import", _]} ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement, "query",
				     [{"xmlns", XMLNS},
				      {"node", SNode}],
				     [feature_to_xml({?NS_EJABBERD_CONFIG})]}]};
		{allow, ["config", _]} ->
		    IQ#iq{type = result,
			  sub_el = [{xmlelement, "query",
				     [{"xmlns", XMLNS},
				      {"node", SNode}],
				     [feature_to_xml({?NS_EJABBERD_CONFIG})]}]};
		_ ->
		    IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
	    end
    end.


feature_to_xml({Feature}) ->
    {xmlelement, "feature", [{"var", Feature}], []}.

domain_to_xml({Domain}) ->
    {xmlelement, "item", [{"jid", Domain}], []};
domain_to_xml(Domain) ->
    {xmlelement, "item", [{"jid", Domain}], []}.

-define(NODE(Name, Node),
	{xmlelement, "item",
	 [{"jid", Server},
	  {"name", translate:translate(Lang, Name)},
	  {"node", Node}], []}).


get_services_only() ->
    lists:map(fun domain_to_xml/1,
	      ejabberd_router:dirty_get_all_routes()) ++
	lists:map(fun domain_to_xml/1, ets:tab2list(disco_extra_domains)).

get_local_items([], Server, Lang) ->
    Domains =
	lists:map(fun domain_to_xml/1,
		  ejabberd_router:dirty_get_all_routes()) ++
	lists:map(fun domain_to_xml/1, ets:tab2list(disco_extra_domains)),
    {result,
     Domains ++
     [?NODE("Configuration",            "config"),
      ?NODE("Online Users",             "online users"),
      ?NODE("All Users",                "all users"),
      ?NODE("Outgoing S2S connections", "outgoing s2s"),
      ?NODE("Running Nodes",            "running nodes"),
      ?NODE("Stopped Nodes",            "stopped nodes")
     ]};

get_local_items(["config"], Server, Lang) ->
    {result,
     [?NODE("Host Name",            "config/hostname"),
      ?NODE("Access Control Lists", "config/acls"),
      ?NODE("Access Rules",         "config/access"),
      ?NODE("Remove Users",         "config/remusers")
     ]};

get_local_items(["config", _], Server, Lang) ->
    {result, []};

get_local_items(["online users"], Server, Lang) ->
    {result, get_online_users()};

get_local_items(["all users"], Server, Lang) ->
    {result, get_all_users()};

get_local_items(["all users", [$@ | Diap]], Server, Lang) ->
    case catch ejabberd_auth:dirty_get_registered_users() of
	{'EXIT', Reason} ->
	    ?ERR_INTERNAL_SERVER_ERROR;
	Users ->
	    SUsers = lists:sort(Users),
	    case catch begin
			   {ok, [S1, S2]} = regexp:split(Diap, "-"),
			   N1 = list_to_integer(S1),
			   N2 = list_to_integer(S2),
			   Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
			   lists:map(fun(U) ->
					     {xmlelement, "item",
					      [{"jid", U ++ "@" ++ ?MYNAME},
					       {"name", U}], []}
				     end, Sub)
		       end of
		{'EXIT', Reason} ->
		    % TODO: must be "not acceptable"
		    ?ERR_BAD_REQUEST;
		Res ->
		    {result, Res}
	    end
    end;

get_local_items(["outgoing s2s"], Server, Lang) ->
    {result, get_outgoing_s2s(Lang)};

get_local_items(["outgoing s2s", To], Server, Lang) ->
    {result, get_outgoing_s2s(Lang, To)};

get_local_items(["running nodes"], Server, Lang) ->
    {result, get_running_nodes(Lang)};

get_local_items(["stopped nodes"], Server, Lang) ->
    {result, get_stopped_nodes(Lang)};

get_local_items(["running nodes", ENode], Server, Lang) ->
    {result,
     [?NODE("DB", "running nodes/" ++ ENode ++ "/DB"),
      ?NODE("Modules", "running nodes/" ++ ENode ++ "/modules"),
      ?NODE("Backup Management", "running nodes/" ++ ENode ++ "/backup"),
      ?NODE("Import users from jabberd1.4 spool files",
	    "running nodes/" ++ ENode ++ "/import")
     ]};

get_local_items(["running nodes", ENode, "DB"], Server, Lang) ->
    {result, []};

get_local_items(["running nodes", ENode, "modules"], Server, Lang) ->
    {result,
     [?NODE("Start Modules", "running nodes/" ++ ENode ++ "/modules/start"),
      ?NODE("Stop Modules",  "running nodes/" ++ ENode ++ "/modules/stop")
     ]};

get_local_items(["running nodes", ENode, "modules", _], Server, Lang) ->
    {result, []};

get_local_items(["running nodes", ENode, "backup"], Server, Lang) ->
    {result,
     [?NODE("Backup", "running nodes/" ++ ENode ++ "/backup/backup"),
      ?NODE("Restore", "running nodes/" ++ ENode ++ "/backup/restore"),
      ?NODE("Dump to Text File",
	    "running nodes/" ++ ENode ++ "/backup/textfile")
     ]};

get_local_items(["running nodes", ENode, "backup", _], Server, Lang) ->
    {result, []};

get_local_items(["running nodes", ENode, "import"], Server, Lang) ->
    {result,
     [?NODE("Import File", "running nodes/" ++ ENode ++ "/import/file"),
      ?NODE("Import Directory",  "running nodes/" ++ ENode ++ "/import/dir")
     ]};

get_local_items(["running nodes", ENode, "import", _], Server, Lang) ->
    {result, []};

get_local_items(_, _, _) ->
    {error, ?ERR_FEATURE_NOT_IMPLEMENTED}.





get_online_users() ->
    case catch ejabberd_sm:dirty_get_sessions_list() of
	{'EXIT', Reason} ->
	    [];
	URs ->
	    lists:map(fun({U, R}) ->
			      {xmlelement, "item",
			       [{"jid", U ++ "@" ++ ?MYNAME ++ "/" ++ R},
				{"name", U}], []}
		      end, lists:sort(URs))
    end.

get_all_users() ->
    case catch ejabberd_auth:dirty_get_registered_users() of
	{'EXIT', Reason} ->
	    [];
	Users ->
	    SUsers = lists:sort(Users),
	    case length(SUsers) of
		N when N =< 100 ->
		    lists:map(fun(U) ->
				      {xmlelement, "item",
				       [{"jid", U ++ "@" ++ ?MYNAME},
					{"name", U}], []}
			      end, SUsers);
		N ->
		    NParts = trunc(math:sqrt(N * 0.618)) + 1,
		    M = trunc(N / NParts) + 1,
		    lists:map(fun(K) ->
				      L = K + M - 1,
				      Node =
					  "@" ++ integer_to_list(K) ++
					  "-" ++ integer_to_list(L),
				      Last = if L < N -> lists:nth(L, SUsers);
						true -> lists:last(SUsers)
					     end,
				      Name = 
					  lists:nth(K, SUsers) ++ " -- " ++
					  Last,
				      {xmlelement, "item",
				       [{"jid", ?MYNAME},
					{"node", "all users/" ++ Node},
					{"name", Name}], []}
			      end, lists:seq(1, N, M))
	    end
    end.

get_outgoing_s2s(Lang) ->
    case catch ejabberd_s2s:dirty_get_connections() of
	{'EXIT', Reason} ->
	    [];
	Connections ->
	    TConns = [element(2, C) || C <- Connections],
	    lists:map(
	      fun(T) ->
		      {xmlelement, "item",
		       [{"jid", ?MYNAME},
			{"node", "outgoing s2s/" ++ T},
			{"name",
			 lists:flatten(
			   io_lib:format(
			     translate:translate(Lang, "To ~s"), [T]))}],
		       []}
	      end, lists:usort(TConns))
    end.

get_outgoing_s2s(Lang, To) ->
    case catch ejabberd_s2s:dirty_get_connections() of
	{'EXIT', Reason} ->
	    [];
	Connections ->
	    lists:map(
	      fun({F, T}) ->
		      {xmlelement, "item",
		       [{"jid", ?MYNAME},
			{"node", "outgoing s2s/" ++ To ++ "/" ++ F},
			{"name",
			 lists:flatten(
			   io_lib:format(
			     translate:translate(Lang, "From ~s"), [F]))}],
		       []}
	      end, lists:keysort(1, lists:filter(fun(E) ->
							 element(2, E) == To
						 end, Connections)))
    end.


get_running_nodes(Lang) ->
    case catch mnesia:system_info(running_db_nodes) of
	{'EXIT', Reason} ->
	    [];
	DBNodes ->
	    lists:map(
	      fun(N) ->
		      S = atom_to_list(N),
		      {xmlelement, "item",
		       [{"jid", ?MYNAME},
			{"node", "running nodes/" ++ S},
			{"name", S}],
		       []}
	      end, lists:sort(DBNodes))
    end.

get_stopped_nodes(Lang) ->
    case catch (lists:usort(mnesia:system_info(db_nodes) ++
			    mnesia:system_info(extra_db_nodes)) --
		mnesia:system_info(running_db_nodes)) of
	{'EXIT', Reason} ->
	    [];
	DBNodes ->
	    lists:map(
	      fun(N) ->
		      S = atom_to_list(N),
		      {xmlelement, "item",
		       [{"jid", ?MYNAME},
			{"node", "stopped nodes/" ++ S},
			{"name", S}],
		       []}
	      end, lists:sort(DBNodes))
    end.



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

register_sm_feature(Feature) ->
    catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
    ets:insert(disco_sm_features, {Feature}).

unregister_sm_feature(Feature) ->
    catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
    ets:delete(disco_sm_features, Feature).

register_sm_node(Node, Name, Module, Function) ->
    catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
    ets:insert(disco_sm_nodes, {Node, Name, Module, Function}).

unregister_sm_node(Node) ->
    catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
    ets:delete(disco_sm_nodes, Node).

process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
    #jid{user = User, luser = LTo} = To,
    #jid{luser = LFrom, lserver = LServer} = From,
    Self = (LTo == LFrom) andalso (LServer == ?MYNAME),
    Node = xml:get_tag_attr_s("node", SubEl),
    case {acl:match_rule(configure, From), Type, Self, Node} of
	{_, set, _, _} ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
	{_, get, true, []}  ->
	    Nodes = lists:map(fun({Nod, Name, _, _}) ->
					node_to_xml(User,
					Nod,
					translate:translate(Lang, Name))
			      end, ets:tab2list(disco_sm_nodes)),
	    IQ#iq{type = result,
		  sub_el = [{xmlelement, "query",
			     [{"xmlns", ?NS_DISCO_ITEMS}],
			     get_user_resources(User) ++ Nodes}]};
	{allow, get, _, []} ->
	    Nodes = lists:map(fun({Nod, Name, _, _}) ->
					node_to_xml(User,
					Nod,
					translate:translate(Lang, Name))
			      end, ets:tab2list(disco_sm_nodes)),
	    IQ#iq{type = result,
		  sub_el = [{xmlelement, "query",
			     [{"xmlns", ?NS_DISCO_ITEMS}],
			     get_user_resources(User) ++ Nodes}]};
	{A, get, S, _} when (A == allow) or (S == true) ->
	    case ets:lookup(disco_sm_nodes, Node) of
		[] ->
		    IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
		[{Node, _Name, Module, Function}] ->
		    case Module:Function(From, To, IQ) of
			{error, Err} ->
			    IQ#iq{type = error, sub_el = [SubEl, Err]};
			{result, Res} ->
			    IQ#iq{type = result,
				  sub_el = [{xmlelement, "query",
					     [{"xmlns", ?NS_DISCO_ITEMS},
					      {"node", Node}],
					     Res}]}
		    end
	    end;
	{_, get, _, _} ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
	_ ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
    end.


process_sm_iq_info(From, To, #iq{type = Type, xmlns = XMLNS,
				 sub_el = SubEl} = IQ) ->
    #jid{luser = LTo} = To,
    #jid{luser = LFrom, lserver = LServer} = From,
    Self = (LTo == LFrom) andalso (LServer == ?MYNAME),
    Node = xml:get_tag_attr_s("node", SubEl),
    case {acl:match_rule(configure, From), Type, Self, Node} of
	{_, set, _, _} ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
	{allow, get, _, []} ->
	    Features = lists:map(fun feature_to_xml/1,
				 ets:tab2list(disco_sm_features)),
	    IQ#iq{type = result,
		  sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}],
			     [feature_to_xml({?NS_EJABBERD_CONFIG})] ++
			     Features}]};
	{_, get, _, []} ->
	    Features = lists:map(fun feature_to_xml/1,
				 ets:tab2list(disco_sm_features)),
	    IQ#iq{type = result,
		  sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}],
			    Features}]};
	{A, get, S, _} when (A == allow) or (S == true) ->
	    case ets:lookup(disco_sm_nodes, Node) of
		[] ->
		    IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
		_ ->
		    IQ#iq{type = result, sub_el = [{xmlelement, "query",
						   [{"xmlns", XMLNS},
						    {"node", Node}], []}]}
	    end;
	{_, get, _, _} ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
	_ ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
    end.



get_user_resources(User) ->
    Rs = ejabberd_sm:get_user_resources(User),
    lists:map(fun(R) ->
		      {xmlelement, "item",
		       [{"jid", User ++ "@" ++ ?MYNAME ++ "/" ++ R},
			{"name", User}], []}
	      end, lists:sort(Rs)).

node_to_xml(User, Node, Name) ->
    {xmlelement, "item", [{"jid", User ++ "@" ++ ?MYNAME},
			  {"node", Node},
			  {"name", Name}], []}.