489 lines
16 KiB
Erlang
489 lines
16 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : mod_xmlrpc.erl
|
|
%%% Author : Badlop <badlop@ono.com>
|
|
%%% Purpose : XML-RPC server
|
|
%%% Created : 21 Aug 2007 by Badlop <badlop@ono.com>
|
|
%%% Id : $Id: mod_xmlrpc.erl 1061 2010-02-19 14:17:13Z badlop $
|
|
%%%----------------------------------------------------------------------
|
|
|
|
-module(mod_xmlrpc).
|
|
-author('badlop@ono.com').
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
-export([start/2,
|
|
handler/2,
|
|
loop/1,
|
|
stop/1]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("mod_roster.hrl").
|
|
-include("jlib.hrl").
|
|
|
|
-define(PROCNAME, ejabberd_mod_xmlrpc).
|
|
|
|
%% -----------------------------
|
|
%% Module interface
|
|
%% -----------------------------
|
|
|
|
start(_Host, Opts) ->
|
|
case whereis(?PROCNAME) of
|
|
undefined ->
|
|
%% get options
|
|
Port = gen_mod:get_opt(port, Opts, 4560),
|
|
MaxSessions = gen_mod:get_opt(maxsessions, Opts, 10),
|
|
Timeout = gen_mod:get_opt(timeout, Opts, 5000),
|
|
Handler = {mod_xmlrpc, handler},
|
|
State = started,
|
|
|
|
Ip = gen_mod:get_opt(ip, Opts, all),
|
|
|
|
%% start the XML-RPC server
|
|
{ok, Pid} = xmlrpc:start_link(Ip, Port, MaxSessions, Timeout, Handler, State),
|
|
|
|
%% start the loop process
|
|
register(?PROCNAME, spawn(?MODULE, loop, [Pid]));
|
|
_ ->
|
|
ok
|
|
end.
|
|
|
|
loop(Pid) ->
|
|
receive
|
|
stop ->
|
|
xmlrpc:stop(Pid)
|
|
end.
|
|
|
|
stop(_Host) ->
|
|
?PROCNAME ! stop.
|
|
|
|
|
|
%% -----------------------------
|
|
%% Handlers
|
|
%% -----------------------------
|
|
|
|
%% Call: Arguments: Returns:
|
|
|
|
%% .............................
|
|
%% Debug
|
|
|
|
%% echothis String String
|
|
handler(_State, {call, echothis, [A]}) ->
|
|
{false, {response, [A]}};
|
|
|
|
%% multhis struct[{a, Integer}, {b, Integer}] Integer
|
|
handler(_State, {call, multhis, [{struct, [{a, A}, {b, B}]}]}) ->
|
|
{false, {response, [A*B]}};
|
|
|
|
%% .............................
|
|
%% Statistics
|
|
|
|
%% tellme_title String String
|
|
handler(_State, {call, tellme_title, [A]}) ->
|
|
{false, {response, [get_title(A)]}};
|
|
|
|
%% tellme_value String String
|
|
handler(_State, {call, tellme_value, [A]}) ->
|
|
N = node(),
|
|
{false, {response, [get_value(N, A)]}};
|
|
|
|
%% tellme String struct[{title, String}, {value. String}]
|
|
handler(_State, {call, tellme, [A]}) ->
|
|
N = node(),
|
|
T = {title, get_title(A)},
|
|
V = {value, get_value(N, A)},
|
|
R = {struct, [T, V]},
|
|
{false, {response, [R]}};
|
|
|
|
%% .............................
|
|
%% User administration
|
|
|
|
%% create_account struct[{user, String}, {host, String}, {password, String}] Integer
|
|
handler(_State, {call, create_account, [{struct, AttrL}]}) ->
|
|
[U, H, P] = get_attrs([user, host, password], AttrL),
|
|
R = case jlib:nodeprep(U) of
|
|
error ->
|
|
error;
|
|
"" ->
|
|
error;
|
|
_ ->
|
|
ejabberd_auth:try_register(U, H, P)
|
|
end,
|
|
case R of
|
|
{atomic, ok} ->
|
|
{false, {response, [0]}};
|
|
{atomic, exists} ->
|
|
{false, {response, [409]}};
|
|
_E ->
|
|
{false, {response, [1]}}
|
|
end;
|
|
|
|
%% delete_account struct[{user, String}, {host, String}, {password, String}] Integer
|
|
handler(_State, {call, delete_account, [{struct, AttrL}]}) ->
|
|
[U, H, P] = get_attrs([user, host, password], AttrL),
|
|
case jlib:nodeprep(U) of
|
|
error ->
|
|
error;
|
|
"" ->
|
|
error;
|
|
_ ->
|
|
ejabberd_auth:remove_user(U, H, P)
|
|
end,
|
|
{false, {response, [0]}};
|
|
|
|
%% check_account struct[{user, String}, {host, String}] Integer
|
|
handler(_State, {call, check_account, [{struct, AttrL}]}) ->
|
|
[U, H] = get_attrs([user, host], AttrL),
|
|
case ejabberd_auth:is_user_exists(U, H) of
|
|
true ->
|
|
{false, {response, [0]}};
|
|
false ->
|
|
{false, {response, [1]}}
|
|
end;
|
|
|
|
%% check_password struct[{user, String}, {host, String}, {password, String}] Integer
|
|
handler(_State, {call, check_password, [{struct, AttrL}]}) ->
|
|
[U, H, P] = get_attrs([user, host, password], AttrL),
|
|
case ejabberd_auth:check_password(U, H, P) of
|
|
true ->
|
|
{false, {response, [0]}};
|
|
false ->
|
|
{false, {response, [1]}}
|
|
end;
|
|
|
|
%% change_password struct[{user, String}, {host, String}, {newpass, String}] Integer
|
|
handler(_State, {call, change_password, [{struct, AttrL}]}) ->
|
|
[U, H, P] = get_attrs([user, host, newpass], AttrL),
|
|
case ejabberd_auth:set_password(U, H, P) of
|
|
ok ->
|
|
{false, {response, [0]}};
|
|
_ ->
|
|
{false, {response, [1]}}
|
|
end;
|
|
|
|
%% num_resources struct[{user, String}, {host, String}] Integer
|
|
handler(_State, {call, num_resources, [{struct, AttrL}]}) ->
|
|
[U, H] = get_attrs([user, host], AttrL),
|
|
R = length(ejabberd_sm:get_user_resources(U, H)),
|
|
{false, {response, [R]}};
|
|
|
|
%% resource_num struct[{user, String}, {host, String}, {num, Integer}] String
|
|
handler(_State, {call, resource_num, [{struct, AttrL}]}) ->
|
|
[U, H, N] = get_attrs([user, host, num], AttrL),
|
|
Resources = ejabberd_sm:get_user_resources(U, H),
|
|
case (0<N) and (N=<length(Resources)) of
|
|
true ->
|
|
R = lists:nth(N, Resources),
|
|
{false, {response, [R]}};
|
|
false ->
|
|
FaultString = lists:flatten(io_lib:format("Wrong resource number: ~p", [N])),
|
|
{false, {response, {fault, -1, FaultString}}}
|
|
end;
|
|
|
|
%%% set_nickname struct[{user, String}, {host, String}, {nickname, String}] Integer
|
|
handler(_State, {call, set_nickname, [{struct, AttrL}]}) ->
|
|
[U, H, N] = get_attrs([user, host, nickname], AttrL),
|
|
R = mod_vcard:process_sm_iq(
|
|
{jid, U, H, "", U, H, ""},
|
|
{jid, U, H, "", U, H, ""},
|
|
{iq, "", set, "", "en",
|
|
{xmlelement, "vCard",
|
|
[{"xmlns", "vcard-temp"}], [
|
|
{xmlelement, "NICKNAME", [], [{xmlcdata, N}]}
|
|
]
|
|
}}),
|
|
case R of
|
|
{iq, [], result, [], _L, []} ->
|
|
{false, {response, [0]}};
|
|
_ ->
|
|
{false, {response, [1]}}
|
|
end;
|
|
|
|
%% add_rosteritem struct[{localuser, String}, {localserver, String},
|
|
%% {user, String}, {server, String},
|
|
%% {nick, String}, {group, String},
|
|
%% {subs, String}] String
|
|
handler(_State, {call, add_rosteritem, [{struct, AttrL}]}) ->
|
|
[LocalUser, LocalServer, User, Server, Nick, Group, Subs] =
|
|
get_attrs([localuser, localserver, user, server, nick, group, subs], AttrL),
|
|
Node = node(),
|
|
R = case add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, list_to_atom(Subs), []) of
|
|
{atomic, ok} ->
|
|
push_roster_item(LocalUser, LocalServer, User, Server, {add, Nick, Subs, Group}),
|
|
0;
|
|
{error, Reason} ->
|
|
io:format("Can't add roster item to user ~p@~p on node ~p: ~p~n",
|
|
[LocalUser, LocalServer, Node, Reason]),
|
|
1;
|
|
{badrpc, Reason} ->
|
|
io:format("Can't add roster item to user ~p@~p on node ~p: ~p~n",
|
|
[LocalUser, LocalServer, Node, Reason]),
|
|
1
|
|
end,
|
|
{false, {response, [R]}};
|
|
|
|
|
|
%% delete_rosteritem struct[{localuser, String}, {localserver, String},
|
|
%% {user, String}, {server, String}] String
|
|
handler(_State, {call, delete_rosteritem, [{struct, AttrL}]}) ->
|
|
[LocalUser, LocalServer, User, Server] = get_attrs([localuser, localserver, user, server], AttrL),
|
|
Node = node(),
|
|
R = case delete_rosteritem(LocalUser, LocalServer, User, Server) of
|
|
{atomic, ok} ->
|
|
push_roster_item(LocalUser, LocalServer, User, Server, remove),
|
|
0;
|
|
{error, Reason} ->
|
|
io:format("Can't remove roster item from user ~p@~p on node ~p: ~p~n",
|
|
[LocalUser, LocalServer, Node, Reason]),
|
|
1;
|
|
{badrpc, Reason} ->
|
|
io:format("Can't remove roster item from user ~p@~p on node ~p: ~p~n",
|
|
[LocalUser, LocalServer, Node, Reason]),
|
|
1
|
|
end,
|
|
{false, {response, [R]}};
|
|
|
|
%% get_roster struct[{user, String}, {server, String}]
|
|
%% array[struct[{jid, String}, {nick, String}, {Group, String}]]
|
|
handler(_State, {call, get_roster, [{struct, AttrL}]}) ->
|
|
[User, Server] = get_attrs([user, server], AttrL),
|
|
Node = node(),
|
|
R = case get_roster(User, Server) of
|
|
{ok, Roster} ->
|
|
RosterXMLRPC = make_roster_xmlrpc(Roster),
|
|
{array, RosterXMLRPC};
|
|
{error, Reason} ->
|
|
io:format("Can't get roster of user ~p@~p on node ~p: ~p~n",
|
|
[User, Server, Node, Reason]),
|
|
1;
|
|
{badrpc, Reason} ->
|
|
io:format("Can't get roster of user ~p@~p on node ~p: ~p~n",
|
|
[User, Server, Node, Reason]),
|
|
1
|
|
end,
|
|
{false, {response, [R]}};
|
|
|
|
%% create_muc_room struct[{name, String}, {service, String}, {server, String}] Integer
|
|
handler(_State, {call, create_muc_room, [{struct, AttrL}]}) ->
|
|
[Name, Service, Server] = get_attrs([name, service, server], AttrL),
|
|
R = case mod_muc_admin:create_room(Name, Service, Server) of
|
|
ok ->
|
|
0;
|
|
{error, room_already_exists} ->
|
|
1
|
|
end,
|
|
{false, {response, [R]}};
|
|
|
|
%% delete_muc_room struct[{name, String}, {service, String}, {server, String}] Integer
|
|
handler(_State, {call, delete_muc_room, [{struct, AttrL}]}) ->
|
|
[Name, Service, Server] = get_attrs([name, service, server], AttrL),
|
|
R = case mod_muc_admin:destroy_room(Name, Service, Server) of
|
|
ok ->
|
|
0;
|
|
{error, room_not_exists} ->
|
|
?INFO_MSG("~n MUC: ~p does not exist on service ~p ~n", [Name, Service]),
|
|
1
|
|
end,
|
|
{false, {response, [R]}};
|
|
|
|
%% muc_room_change_option struct[{name, String}, {service, String}, {option, String}, {value String}] Integer
|
|
handler(_State, {call, muc_room_change_option, [{struct, AttrL}]}) ->
|
|
[Name, Service, OptionString, Value] = get_attrs([name, service, option, value], AttrL),
|
|
Option = list_to_atom(OptionString),
|
|
mod_muc_admin:change_room_option(Name, Service, Option, Value),
|
|
{false, {response, [0]}};
|
|
|
|
%% muc_room_set_affiliation struct[{name, String}, {service, String}, {jid, String}, {affiliation, String}] Integer
|
|
handler(_State, {call, muc_room_set_affiliation, [{struct, AttrL}]}) ->
|
|
[Name, Service, JID, Affiliation] = get_attrs([name, service, jid, affiliation], AttrL),
|
|
R = case mod_muc_admin:set_affiliation(Name, Service, JID, list_to_atom(Affiliation)) of
|
|
ok ->
|
|
0;
|
|
{error, room_not_exists} ->
|
|
?INFO_MSG("~n MUC: ~p does not exist on service ~p ~n", [Name, Service]),
|
|
1
|
|
end,
|
|
{false, {response, [R]}};
|
|
|
|
%% send_message struct[{from, String}, {to, String}, {subject, String}, {body, String}] Integer
|
|
handler(_State, {call, send_message, [{struct, AttrL}]}) ->
|
|
[FromJIDString, ToJIDString, Subject, Body] = get_attrs([from, to, subject, body], AttrL),
|
|
FromJID = jlib:string_to_jid(FromJIDString),
|
|
ToJID = jlib:string_to_jid(ToJIDString),
|
|
send_message(FromJID, ToJID, Subject, Body),
|
|
{false, {response, [0]}};
|
|
|
|
%% If no other guard matches
|
|
handler(_State, Payload) ->
|
|
FaultString = lists:flatten(io_lib:format("Unknown call: ~p", [Payload])),
|
|
?INFO_MSG("Unknown call: ~p", [Payload]),
|
|
{false, {response, {fault, -1, FaultString}}}.
|
|
|
|
|
|
%% -----------------------------
|
|
%% Roster items
|
|
%% -----------------------------
|
|
|
|
add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
|
|
subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs).
|
|
|
|
subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
|
|
mnesia:transaction(
|
|
fun() ->
|
|
mnesia:write({roster,
|
|
{LU,LS,{User,Server,[]}}, % uj
|
|
{LU,LS}, % user
|
|
{User,Server,[]}, % jid
|
|
Nick, % name: "Mom", []
|
|
Subscription, % subscription: none, to=you see him, from=he sees you, both
|
|
none, % ask: out=send request, in=somebody requests you, none
|
|
[Group], % groups: ["Family"]
|
|
Xattrs, % xattrs: [{"category","conference"}]
|
|
[] % xs: []
|
|
})
|
|
end).
|
|
|
|
delete_rosteritem(LU, LS, User, Server) ->
|
|
unsubscribe(LU, LS, User, Server).
|
|
|
|
unsubscribe(LU, LS, User, Server) ->
|
|
mnesia:transaction(
|
|
fun() ->
|
|
mnesia:delete({roster, {LU, LS, {User, Server, []}}})
|
|
end).
|
|
|
|
%% @spec(LU, LS, U, S, Action) -> ok
|
|
%% Action = {add, Nick, Subs, Group} | remove
|
|
%% @doc Push to the roster of account LU@LS the contact U@S.
|
|
%% The specific action to perform is defined in Action.
|
|
push_roster_item(LU, LS, U, S, Action) ->
|
|
lists:foreach(fun(R) ->
|
|
push_roster_item(LU, LS, R, U, S, Action)
|
|
end, ejabberd_sm:get_user_resources(LU, LS)).
|
|
|
|
push_roster_item(LU, LS, R, U, S, Action) ->
|
|
Item = build_roster_item(U, S, Action),
|
|
ResIQ = build_iq_roster_push(Item),
|
|
LJID = jlib:make_jid(LU, LS, R),
|
|
ejabberd_router:route(LJID, LJID, ResIQ).
|
|
|
|
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
|
|
{xmlelement, "item",
|
|
[{"jid", jlib:jid_to_string(jlib:make_jid(U, S, ""))},
|
|
{"name", Nick},
|
|
{"subscription", Subs}],
|
|
[{xmlelement, "group", [], [{xmlcdata, Group}]}]
|
|
};
|
|
build_roster_item(U, S, remove) ->
|
|
{xmlelement, "item",
|
|
[{"jid", jlib:jid_to_string(jlib:make_jid(U, S, ""))},
|
|
{"subscription", "remove"}],
|
|
[]
|
|
}.
|
|
|
|
build_iq_roster_push(Item) ->
|
|
{xmlelement, "iq",
|
|
[{"type", "set"}, {"id", "push"}],
|
|
[{xmlelement, "query",
|
|
[{"xmlns", ?NS_ROSTER}],
|
|
[Item]
|
|
}
|
|
]
|
|
}.
|
|
|
|
|
|
%% -----------------------------
|
|
%% Get Roster
|
|
%% -----------------------------
|
|
|
|
get_roster(User, Server) ->
|
|
LUser = jlib:nodeprep(User),
|
|
LServer = jlib:nameprep(Server),
|
|
Roster = ejabberd_hooks:run_fold(roster_get, LServer, [], [{LUser, LServer}]),
|
|
{ok, Roster}.
|
|
|
|
%% Note: if a contact is in several groups, the contact is returned
|
|
%% several times, each one in a different group.
|
|
make_roster_xmlrpc(Roster) ->
|
|
lists:foldl(
|
|
fun(Item, Res) ->
|
|
JIDS = jlib:jid_to_string(Item#roster.jid),
|
|
Nick = Item#roster.name,
|
|
Groups = case Item#roster.groups of
|
|
[] -> [""];
|
|
Gs -> Gs
|
|
end,
|
|
ItemsX = [{struct, [{jid, JIDS}, {nick, Nick}, {group, Group}]}
|
|
|| Group <- Groups],
|
|
ItemsX ++ Res
|
|
end,
|
|
[],
|
|
Roster).
|
|
|
|
|
|
%% -----------------------------
|
|
%% Sending messages to the user
|
|
%% -----------------------------
|
|
|
|
%% @doc Send a message to a Jabber account.
|
|
%% If a resource was specified in the JID,
|
|
%% the message is sent only to that specific resource.
|
|
%% If no resource was specified in the JID,
|
|
%% and the user is remote or local but offline,
|
|
%% the message is sent to the bare JID.
|
|
%% If the user is local and is online in several resources,
|
|
%% the message is sent to all its resources.
|
|
send_message(FromJID, ToJID, Subject, Body) ->
|
|
ToUser = ToJID#jid.user,
|
|
ToServer = ToJID#jid.server,
|
|
case ToJID#jid.resource of
|
|
"" ->
|
|
send_message(FromJID, ToUser, ToServer, Subject, Body);
|
|
Resource ->
|
|
send_message(FromJID, ToUser, ToServer, Resource, Subject, Body)
|
|
end.
|
|
|
|
send_message(FromJID, ToUser, ToServer, Subject, Body) ->
|
|
case ejabberd_sm:get_user_resources(ToUser, ToServer) of
|
|
[] ->
|
|
send_message(FromJID, ToUser, ToServer, "", Subject, Body);
|
|
ToResources ->
|
|
lists:foreach(
|
|
fun(ToResource) ->
|
|
send_message(FromJID, ToUser, ToServer, ToResource, Subject, Body)
|
|
end,
|
|
ToResources)
|
|
end.
|
|
|
|
send_message(FromJID, ToU, ToS, ToR, Subject, Body) ->
|
|
MPacket = build_send_message(Subject, Body),
|
|
ToJID = jlib:make_jid(ToU, ToS, ToR),
|
|
ejabberd_router:route(FromJID, ToJID, MPacket).
|
|
|
|
build_send_message(Subject, Body) ->
|
|
{xmlelement, "message",
|
|
[{"type", "headline"}],
|
|
[{xmlelement, "subject", [], [{xmlcdata, Subject}]},
|
|
{xmlelement, "body", [], [{xmlcdata, Body}]}
|
|
]
|
|
}.
|
|
|
|
|
|
%% -----------------------------
|
|
%% Internal
|
|
%% -----------------------------
|
|
|
|
get_title(A) -> mod_statsdx:get_title(A).
|
|
get_value(N, A) -> mod_statsdx:get(N, [A]).
|
|
|
|
get_attrs(Attribute_names, L) ->
|
|
[get_attr(A, L) || A <- Attribute_names].
|
|
get_attr(A, L) ->
|
|
case lists:keysearch(A, 1, L) of
|
|
{value, {A, Value}} -> Value;
|
|
false ->
|
|
%% Report the error and then force a crash
|
|
?ERROR_MSG("Attribute '~p' not found on the list of attributes provided on the call:~n ~p", [A, L]),
|
|
attribute_not_found = A
|
|
end.
|
|
|