mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-26 17:38:45 +01:00
added p1 modules
This commit is contained in:
parent
7be707f7bc
commit
59ae9bea76
337
src/http_p1.erl
Normal file
337
src/http_p1.erl
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : http_p1.erl
|
||||||
|
%%% Author : Emilio Bustos <ebustos@process-one.net>
|
||||||
|
%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
|
||||||
|
%%% Created : 29 Jul 2010 by Emilio Bustos <ebustos@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This program is free software; you can redistribute it and/or
|
||||||
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
%%% published by the Free Software Foundation; either version 2 of the
|
||||||
|
%%% License, or (at your option) any later version.
|
||||||
|
%%%
|
||||||
|
%%% This program is distributed in the hope that it will be useful,
|
||||||
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
%%% General Public License for more details.
|
||||||
|
%%%
|
||||||
|
%%% You should have received a copy of the GNU General Public License
|
||||||
|
%%% along with this program; if not, write to the Free Software
|
||||||
|
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
%%% 02111-1307 USA
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(http_p1).
|
||||||
|
-author('ebustos@process-one.net').
|
||||||
|
|
||||||
|
-export([
|
||||||
|
start/0,
|
||||||
|
stop/0,
|
||||||
|
get/1,
|
||||||
|
get/2,
|
||||||
|
post/2,
|
||||||
|
post/3,
|
||||||
|
request/3,
|
||||||
|
request/4,
|
||||||
|
request/5
|
||||||
|
]).
|
||||||
|
|
||||||
|
% -define(USE_INETS, 1).
|
||||||
|
% -define(USE_LHTTPC, 1).
|
||||||
|
% -define(USE_IBROWSE, 1).
|
||||||
|
% inets used as default if none specified
|
||||||
|
|
||||||
|
-ifdef(USE_IBROWSE).
|
||||||
|
-define(start(), start_ibrowse()).
|
||||||
|
-define(request(M, U, H, B, O), request_ibrowse(M, U, H, B, O)).
|
||||||
|
-define(stop(), stop_ibrowse()).
|
||||||
|
-else.
|
||||||
|
-ifdef(USE_LHTTPC).
|
||||||
|
-define(start(), start_lhttpc()).
|
||||||
|
-define(request(M, U, H, B, O), request_lhttpc(M, U, H, B, O)).
|
||||||
|
-define(stop(), stop_lhttpc()).
|
||||||
|
-else.
|
||||||
|
-define(start(), start_inets()).
|
||||||
|
-define(request(M, U, H, B, O), request_inets(M, U, H, B, O)).
|
||||||
|
-define(stop(), stop_inets()).
|
||||||
|
-endif.
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-type header() :: {string() | atom(), string()}.
|
||||||
|
-type headers() :: [header()].
|
||||||
|
|
||||||
|
-type option() ::
|
||||||
|
{connect_timeout, timeout()} |
|
||||||
|
{timeout, timeout()} |
|
||||||
|
|
||||||
|
{send_retry, non_neg_integer()} |
|
||||||
|
{partial_upload, non_neg_integer() | infinity} |
|
||||||
|
{partial_download, pid(), non_neg_integer() | infinity}.
|
||||||
|
|
||||||
|
-type options() :: [option()].
|
||||||
|
|
||||||
|
-type result() :: {ok, {{pos_integer(), string()}, headers(), string()}} |
|
||||||
|
{error, atom()}.
|
||||||
|
|
||||||
|
%% @spec () -> ok | {error, Reason}
|
||||||
|
%% Reason = term()
|
||||||
|
%% @doc
|
||||||
|
%% Start the application.
|
||||||
|
%% This is a helper function that will start the corresponding backend.
|
||||||
|
%% It allows the library to be started using the `-s' flag.
|
||||||
|
%% For instance:
|
||||||
|
%% `$ erl -s http_p1'
|
||||||
|
%%
|
||||||
|
%% @end
|
||||||
|
-spec start() -> ok | {error, any()}.
|
||||||
|
start() ->
|
||||||
|
?start().
|
||||||
|
|
||||||
|
start_inets()->
|
||||||
|
inets:start(),
|
||||||
|
ssl:start().
|
||||||
|
|
||||||
|
start_lhttpc()->
|
||||||
|
application:start(crypto),
|
||||||
|
application:start(ssl),
|
||||||
|
lhttpc:start().
|
||||||
|
|
||||||
|
start_ibrowse()->
|
||||||
|
ibrowse:start(),
|
||||||
|
ssl:start().
|
||||||
|
|
||||||
|
%% @spec () -> ok | {error, Reason}
|
||||||
|
%% Reason = term()
|
||||||
|
%% @doc
|
||||||
|
%% Stops the application.
|
||||||
|
%% This is a helper function that will stop the corresponding backend.
|
||||||
|
%%
|
||||||
|
%% @end
|
||||||
|
-spec stop() -> ok | {error, any()}.
|
||||||
|
stop() ->
|
||||||
|
?stop().
|
||||||
|
|
||||||
|
stop_inets()->
|
||||||
|
inets:stop(),
|
||||||
|
ssl:stop().
|
||||||
|
|
||||||
|
stop_lhttpc()->
|
||||||
|
lhttpc:stop(),
|
||||||
|
application:stop(ssl).
|
||||||
|
|
||||||
|
stop_ibrowse()->
|
||||||
|
ibrowse:stop().
|
||||||
|
|
||||||
|
%% @spec (URL) -> Result
|
||||||
|
%% URL = string()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a GET request.
|
||||||
|
%% Would be the same as calling `request(get, URL, [])',
|
||||||
|
%% that is {@link request/3} with an empty header list.
|
||||||
|
%% @end
|
||||||
|
%% @see request/3
|
||||||
|
-spec get(string()) -> result().
|
||||||
|
get(URL) ->
|
||||||
|
request(get, URL, []).
|
||||||
|
|
||||||
|
%% @spec (URL, Hdrs) -> Result
|
||||||
|
%% URL = string()
|
||||||
|
%% Hdrs = [{Header, Value}]
|
||||||
|
%% Header = string()
|
||||||
|
%% Value = string()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a GET request.
|
||||||
|
%% Would be the same as calling `request(get, URL, Hdrs)'.
|
||||||
|
%% @end
|
||||||
|
%% @see request/3
|
||||||
|
-spec get(string(), headers()) -> result().
|
||||||
|
get(URL, Hdrs) ->
|
||||||
|
request(get, URL, Hdrs).
|
||||||
|
|
||||||
|
%% @spec (URL, RequestBody) -> Result
|
||||||
|
%% URL = string()
|
||||||
|
%% RequestBody = string()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a POST request with form data.
|
||||||
|
%% Would be the same as calling
|
||||||
|
%% `request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body)'.
|
||||||
|
%% @end
|
||||||
|
%% @see request/4
|
||||||
|
-spec post(string(), string()) -> result().
|
||||||
|
post(URL, Body) ->
|
||||||
|
request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body).
|
||||||
|
|
||||||
|
%% @spec (URL, Hdrs, RequestBody) -> Result
|
||||||
|
%% URL = string()
|
||||||
|
%% Hdrs = [{Header, Value}]
|
||||||
|
%% Header = string()
|
||||||
|
%% Value = string()
|
||||||
|
%% RequestBody = string()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a POST request.
|
||||||
|
%% Would be the same as calling
|
||||||
|
%% `request(post, URL, Hdrs, Body)'.
|
||||||
|
%% @end
|
||||||
|
%% @see request/4
|
||||||
|
-spec post(string(), headers(), string()) -> result().
|
||||||
|
post(URL, Hdrs, Body) ->
|
||||||
|
NewHdrs = case [X || {X,_}<-Hdrs, string:to_lower(X) == "content-type"] of
|
||||||
|
[] ->
|
||||||
|
[{"content-type", "x-www-form-urlencoded"} | Hdrs];
|
||||||
|
_ ->
|
||||||
|
Hdrs
|
||||||
|
end,
|
||||||
|
request(post, URL, NewHdrs, Body).
|
||||||
|
|
||||||
|
%% @spec (Method, URL, Hdrs) -> Result
|
||||||
|
%% Method = atom()
|
||||||
|
%% URL = string()
|
||||||
|
%% Hdrs = [{Header, Value}]
|
||||||
|
%% Header = string()
|
||||||
|
%% Value = string()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a request without a body.
|
||||||
|
%% Would be the same as calling `request(Method, URL, Hdrs, [], [])',
|
||||||
|
%% that is {@link request/5} with an empty body.
|
||||||
|
%% @end
|
||||||
|
%% @see request/5
|
||||||
|
-spec request(atom(), string(), headers()) -> result().
|
||||||
|
request(Method, URL, Hdrs) ->
|
||||||
|
request(Method, URL, Hdrs, [], []).
|
||||||
|
|
||||||
|
%% @spec (Method, URL, Hdrs, RequestBody) -> Result
|
||||||
|
%% Method = atom()
|
||||||
|
%% URL = string()
|
||||||
|
%% Hdrs = [{Header, Value}]
|
||||||
|
%% Header = string()
|
||||||
|
%% Value = string()
|
||||||
|
%% RequestBody = string()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a request with a body.
|
||||||
|
%% Would be the same as calling
|
||||||
|
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||||
|
%% with no options.
|
||||||
|
%% @end
|
||||||
|
%% @see request/5
|
||||||
|
-spec request(atom(), string(), headers(), string()) -> result().
|
||||||
|
request(Method, URL, Hdrs, Body) ->
|
||||||
|
request(Method, URL, Hdrs, Body, []).
|
||||||
|
|
||||||
|
%% @spec (Method, URL, Hdrs, RequestBody, Options) -> Result
|
||||||
|
%% Method = atom()
|
||||||
|
%% URL = string()
|
||||||
|
%% Hdrs = [{Header, Value}]
|
||||||
|
%% Header = string()
|
||||||
|
%% Value = string()
|
||||||
|
%% RequestBody = string()
|
||||||
|
%% Options = [Option]
|
||||||
|
%% Option = {timeout, Milliseconds | infinity} |
|
||||||
|
%% {connect_timeout, Milliseconds | infinity} |
|
||||||
|
%% {socket_options, [term()]} |
|
||||||
|
|
||||||
|
%% Milliseconds = integer()
|
||||||
|
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||||
|
%% | {error, Reason}
|
||||||
|
%% StatusCode = integer()
|
||||||
|
%% ResponseBody = string()
|
||||||
|
%% Reason = connection_closed | connect_timeout | timeout
|
||||||
|
%% @doc Sends a request with a body.
|
||||||
|
%% Would be the same as calling
|
||||||
|
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||||
|
%% with no options.
|
||||||
|
%% @end
|
||||||
|
%% @see request/5
|
||||||
|
-spec request(atom(), string(), headers(), string(), options()) -> result().
|
||||||
|
request(Method, URL, Hdrs, Body, Opts) ->
|
||||||
|
% ?DEBUG("Making request with headers: ~p~n~n", [Hdrs]),
|
||||||
|
% Headers = lists:map(fun({H, V}) ->
|
||||||
|
% H2 = if
|
||||||
|
% is_atom(H) ->
|
||||||
|
% string:to_lower(atom_to_list(H));
|
||||||
|
% is_list(H) ->
|
||||||
|
% string:to_lower(H);
|
||||||
|
% true ->
|
||||||
|
% H
|
||||||
|
% end,
|
||||||
|
% {H2, V}
|
||||||
|
% end, Hdrs),
|
||||||
|
?request(Method, URL, Hdrs, Body, Opts).
|
||||||
|
|
||||||
|
request_inets(Method, URL, Hdrs, Body, Opts) ->
|
||||||
|
Request = case Method of
|
||||||
|
get ->
|
||||||
|
{URL, Hdrs};
|
||||||
|
head ->
|
||||||
|
{URL, Hdrs};
|
||||||
|
_ -> % post, etc.
|
||||||
|
{URL, Hdrs, proplists:get_value("content-type", Hdrs, []), Body}
|
||||||
|
end,
|
||||||
|
Options = case proplists:get_value(timeout, Opts, infinity) of
|
||||||
|
infinity ->
|
||||||
|
proplists:delete(timeout, Opts);
|
||||||
|
_ ->
|
||||||
|
Opts
|
||||||
|
end,
|
||||||
|
case http:request(Method, Request, Options, []) of
|
||||||
|
{ok, {{_, Status, _}, Headers, Response}} ->
|
||||||
|
{ok, Status, Headers, Response};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
request_lhttpc(Method, URL, Hdrs, Body, Opts) ->
|
||||||
|
TimeOut = proplists:get_value(timeout, Opts, infinity),
|
||||||
|
SockOpt = proplists:get_value(socket_options, Opts, []),
|
||||||
|
Options = [{connect_options, SockOpt} | proplists:delete(timeout, Opts)],
|
||||||
|
case lhttpc:request(URL, Method, Hdrs, Body, TimeOut, Options) of
|
||||||
|
{ok, {{Status, _Reason}, Headers, Response}} ->
|
||||||
|
{ok, Status, Headers, binary_to_list(Response)};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
request_ibrowse(Method, URL, Hdrs, Body, Opts) ->
|
||||||
|
TimeOut = proplists:get_value(timeout, Opts, infinity),
|
||||||
|
Options = [{inactivity_timeout, TimeOut} | proplists:delete(timeout, Opts)],
|
||||||
|
case ibrowse:send_req(URL, Hdrs, Method, Body, Options) of
|
||||||
|
{ok, Status, Headers, Response} ->
|
||||||
|
{ok, list_to_integer(Status), Headers, Response};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
% ibrowse {response_format, response_format()} |
|
||||||
|
% Options - [option()]
|
||||||
|
% Option - {sync, boolean()} | {stream, StreamTo} | {body_format, body_format()} | {full_result,
|
||||||
|
% boolean()} | {headers_as_is, boolean()}
|
||||||
|
%body_format() = string() | binary()
|
||||||
|
% The body_format option is only valid for the synchronous request and the default is string.
|
||||||
|
% When making an asynchronous request the body will always be received as a binary.
|
||||||
|
% lhttpc: always binary
|
1166
src/mod_admin_p1.erl
Normal file
1166
src/mod_admin_p1.erl
Normal file
File diff suppressed because it is too large
Load Diff
@ -85,7 +85,7 @@ loop(_State) ->
|
|||||||
%% TODO: Support comment lines starting by %
|
%% TODO: Support comment lines starting by %
|
||||||
update_bl_c2s() ->
|
update_bl_c2s() ->
|
||||||
?INFO_MSG("Updating C2S Blacklist", []),
|
?INFO_MSG("Updating C2S Blacklist", []),
|
||||||
case http:request(?BLC2S) of
|
case http_p1:request(?BLC2S) of
|
||||||
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
|
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
|
||||||
IPs = string:tokens(Body,"\n"),
|
IPs = string:tokens(Body,"\n"),
|
||||||
ets:delete_all_objects(bl_c2s),
|
ets:delete_all_objects(bl_c2s),
|
||||||
|
875
src/mod_xmlrpc.erl
Normal file
875
src/mod_xmlrpc.erl
Normal file
@ -0,0 +1,875 @@
|
|||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : mod_xmlrpc.erl
|
||||||
|
%%% Author : Badlop / Mickael Remond / Christophe Romain
|
||||||
|
%%% Purpose : XML-RPC server
|
||||||
|
%%% Created :
|
||||||
|
%%% Id :
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%%/***************************************************************************
|
||||||
|
%%% * *
|
||||||
|
%%% * This program is free software; you can redistribute it and/or modify *
|
||||||
|
%%% * it under the terms of the GNU General Public License as published by *
|
||||||
|
%%% * the Free Software Foundation; either version 2 of the License, or *
|
||||||
|
%%% * (at your option) any later version. *
|
||||||
|
%%% * *
|
||||||
|
%%% ***************************************************************************/
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% MOD_XMLRPC - an XML-RPC server module for ejabberd
|
||||||
|
%%%
|
||||||
|
%%% v0.5 - 17 March 2008
|
||||||
|
%%%
|
||||||
|
%%% http://ejabberd.jabber.ru/mod_xmlrpc
|
||||||
|
%%%
|
||||||
|
%%% (C) 2005, Badlop
|
||||||
|
%%% 2006, Process-one
|
||||||
|
%%% 2007, Process-one
|
||||||
|
%%% 2008, Process-one
|
||||||
|
%%%
|
||||||
|
%%% Changelog:
|
||||||
|
%%%
|
||||||
|
%%% 0.7 - 02 April 2009 - cromain
|
||||||
|
%%% - add user nick change
|
||||||
|
%%%
|
||||||
|
%%% 0.6 - 02 June 2008 - cromain
|
||||||
|
%%% - add user existance checking
|
||||||
|
%%% - improve parameter checking
|
||||||
|
%%% - allow orderless parameter
|
||||||
|
%%%
|
||||||
|
%%% 0.5 - 17 March 2008 - cromain
|
||||||
|
%%% - add user changing and higher level methods
|
||||||
|
%%%
|
||||||
|
%%% 0.4 - 18 February 2008 - cromain
|
||||||
|
%%% - add roster handling
|
||||||
|
%%% - add message sending
|
||||||
|
%%% - code and api clean-up
|
||||||
|
%%%
|
||||||
|
%%% 0.3 - 18 October 2007 - cromain
|
||||||
|
%%% - presence improvement
|
||||||
|
%%% - add new functionality
|
||||||
|
%%%
|
||||||
|
%%% 0.2 - 4 March 2006 - mremond
|
||||||
|
%%% - Code clean-up
|
||||||
|
%%% - Made it compatible with current ejabberd SVN version
|
||||||
|
%%%
|
||||||
|
%%% 0.1.2 - 28 December 2005
|
||||||
|
%%% - Now compatible with ejabberd 1.0.0
|
||||||
|
%%% - The XMLRPC server is started only once, not once for every virtual host
|
||||||
|
%%% - Added comments for handlers. Every available handler must be explained
|
||||||
|
%%%
|
||||||
|
|
||||||
|
-module(mod_xmlrpc).
|
||||||
|
-author('Process-one').
|
||||||
|
-vsn('0.6').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
-export([start/2,
|
||||||
|
handler/2,
|
||||||
|
link_contacts/5, unlink_contacts/3, %% used by Nimbuzz
|
||||||
|
loop/1,
|
||||||
|
stop/1]).
|
||||||
|
|
||||||
|
-export([add_rosteritem/6]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("jlib.hrl").
|
||||||
|
-include("mod_roster.hrl").
|
||||||
|
|
||||||
|
-ifdef(EJABBERD1).
|
||||||
|
-record(session, {sid, usr, us, priority}). %% ejabberd 1.1.x
|
||||||
|
-else.
|
||||||
|
-record(session, {sid, usr, us, priority, info}). %% ejabberd 2.0.x
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
|
||||||
|
-define(PROCNAME, ejabberd_mod_xmlrpc).
|
||||||
|
-define(PORT, 4560).
|
||||||
|
-define(TIMEOUT, 5000).
|
||||||
|
|
||||||
|
%% -----------------------------
|
||||||
|
%% Module interface
|
||||||
|
%% -----------------------------
|
||||||
|
|
||||||
|
start(_Host, Opts) ->
|
||||||
|
case whereis(?PROCNAME) of
|
||||||
|
undefined ->
|
||||||
|
%% get options
|
||||||
|
Port = gen_mod:get_opt(port, Opts, ?PORT),
|
||||||
|
MaxSessions = 10,
|
||||||
|
Timeout = gen_mod:get_opt(timeout, Opts, ?TIMEOUT),
|
||||||
|
Handler = {mod_xmlrpc, handler},
|
||||||
|
State = tryit,
|
||||||
|
|
||||||
|
%% TODO: this option gives
|
||||||
|
%% error_info: {function_clause,[{gen_tcp,mod,[{ip,{127,0,0,1}}]},
|
||||||
|
%%case gen_mod:get_opt(listen_all, Opts, false) of
|
||||||
|
%% true -> Ip = all;
|
||||||
|
%% false -> Ip = {127, 0, 0, 1}
|
||||||
|
%%end,
|
||||||
|
Ip = 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;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
loop(Pid) ->
|
||||||
|
receive
|
||||||
|
stop ->
|
||||||
|
xmlrpc:stop(Pid)
|
||||||
|
end.
|
||||||
|
|
||||||
|
stop(_Host) ->
|
||||||
|
case whereis(?PROCNAME) of
|
||||||
|
undefined ->
|
||||||
|
ok;
|
||||||
|
_Pid ->
|
||||||
|
?PROCNAME ! stop,
|
||||||
|
unregister(?PROCNAME)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% -----------------------------
|
||||||
|
%% Handlers
|
||||||
|
%% -----------------------------
|
||||||
|
|
||||||
|
handler(tryit, Call) ->
|
||||||
|
try handler(notry, Call) of
|
||||||
|
Result -> Result
|
||||||
|
catch
|
||||||
|
A:B ->
|
||||||
|
?ERROR_MSG("Problem '~p' in~nCall: ~p~nError: ~p", [A, Call, B]),
|
||||||
|
{false, {response, [-100]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
% 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, Struct}]}) ->
|
||||||
|
[{a, A}, {b, B}] = lists:sort(Struct),
|
||||||
|
{false, {response, [A*B]}};
|
||||||
|
|
||||||
|
%% .............................
|
||||||
|
%% User administration
|
||||||
|
|
||||||
|
%% create_account struct[{user, String}, {server, Server}, {password, String}] Integer
|
||||||
|
handler(_State, {call, create_account, [{struct, Struct}]}) ->
|
||||||
|
[{password, P}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:try_register(U, S, P) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
{false, {response, [0]}};
|
||||||
|
{atomic, exists} ->
|
||||||
|
{false, {response, [409]}};
|
||||||
|
_ ->
|
||||||
|
{false, {response, [1]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% delete_account struct[{user, String}, {server, Server}] Integer
|
||||||
|
handler(_State, {call, delete_account, [{struct, Struct}]}) ->
|
||||||
|
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
Fun = fun() -> ejabberd_auth:remove_user(U, S) end,
|
||||||
|
user_action(U, S, Fun, ok);
|
||||||
|
|
||||||
|
%% change_password struct[{user, String}, {server, String}, {newpass, String}] Integer
|
||||||
|
handler(_State, {call, change_password, [{struct, Struct}]}) ->
|
||||||
|
[{newpass, P}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
Fun = fun() -> ejabberd_auth:set_password(U, S, P) end,
|
||||||
|
user_action(U, S, Fun, ok);
|
||||||
|
|
||||||
|
%% set_nickname struct[{user, String}, {server, String}, {nick, String}] Integer
|
||||||
|
handler(_State, {call, set_nickname, [{struct, Struct}]}) ->
|
||||||
|
[{nick, N}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
Fun = fun() -> case mod_vcard:process_sm_iq(
|
||||||
|
{jid, U, S, "", U, S, ""},
|
||||||
|
{jid, U, S, "", U, S, ""},
|
||||||
|
{iq, "", set, "", "en",
|
||||||
|
{xmlelement, "vCard",
|
||||||
|
[{"xmlns", "vcard-temp"}], [
|
||||||
|
{xmlelement, "NICKNAME", [], [{xmlcdata, N}]}
|
||||||
|
]
|
||||||
|
}}) of
|
||||||
|
{iq, [], result, [], _, []} -> ok;
|
||||||
|
_ -> error
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
user_action(U, S, Fun, ok);
|
||||||
|
|
||||||
|
%% set_rosternick struct[{user, String}, {server, String}, {nick, String}] Integer
|
||||||
|
handler(_State, {call, set_rosternick, [{struct, Struct}]}) ->
|
||||||
|
[{nick, N}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
Fun = fun() -> change_rosternick(U, S, N) end,
|
||||||
|
user_action(U, S, Fun, ok);
|
||||||
|
|
||||||
|
%% add_rosteritem struct[{user, String}, {server, String},
|
||||||
|
%% {jid, String}, {group, String}, {nick, String}, {subs, String}] Integer
|
||||||
|
handler(_State, {call, add_rosteritem, [{struct, Struct}]}) ->
|
||||||
|
[{group, G},{jid, JID},{nick, N},{server, S},{subs, Subs},{user, U}] = lists:sort(Struct),
|
||||||
|
Fun = fun() -> add_rosteritem(U, S, JID, N, G, Subs) end,
|
||||||
|
user_action(U, S, Fun, {atomic, ok});
|
||||||
|
|
||||||
|
%% link_contacts struct[{jid1, String}, {nick1, String}, {jid2, String}, {nick2, String}] Integer
|
||||||
|
handler(_State, {call, link_contacts, [{struct, Struct}]}) ->
|
||||||
|
[{jid1, JID1}, {jid2, JID2}, {nick1, Nick1}, {nick2, Nick2}] = lists:sort(Struct),
|
||||||
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||||
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||||
|
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
|
||||||
|
{true, true} ->
|
||||||
|
case link_contacts(JID1, Nick1, JID2, Nick2) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
{false, {response, [0]}};
|
||||||
|
_ ->
|
||||||
|
{false, {response, [1]}}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% delete_rosteritem struct[{user, String}, {server, String}, {jid, String}] Integer
|
||||||
|
handler(_State, {call, delete_rosteritem, [{struct, Struct}]}) ->
|
||||||
|
[{jid, JID}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
Fun = fun() -> del_rosteritem(U, S, JID) end,
|
||||||
|
user_action(U, S, Fun, {atomic, ok});
|
||||||
|
|
||||||
|
%% unlink_contacts struct[{jid1, String}, {jid2, String}] Integer
|
||||||
|
handler(_State, {call, unlink_contacts, [{struct, Struct}]}) ->
|
||||||
|
[{jid1, JID1}, {jid2, JID2}] = lists:sort(Struct),
|
||||||
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||||
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||||
|
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
|
||||||
|
{true, true} ->
|
||||||
|
case unlink_contacts(JID1, JID2) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
{false, {response, [0]}};
|
||||||
|
_ ->
|
||||||
|
{false, {response, [1]}}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% get_roster struct[{user, String}, {server, String}]
|
||||||
|
%% array[struct[{jid, String}, {group, String}, {nick, String},
|
||||||
|
%% {subscription, String}, {pending, String}]]
|
||||||
|
handler(_State, {call, get_roster, [{struct, Struct}]}) ->
|
||||||
|
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
Roster = format_roster(get_roster(U, S)),
|
||||||
|
{false, {response, [{array, Roster}]}};
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% get_roster_with_presence struct[{user, String}, {server, String}]
|
||||||
|
%% array[struct[{jid, String}, {resource, String}, {group, String}, {nick, String},
|
||||||
|
%% {subscription, String}, {pending, String},
|
||||||
|
%% {show, String}, {status, String}]]
|
||||||
|
handler(_State, {call, get_roster_with_presence, [{struct, Struct}]}) ->
|
||||||
|
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
Roster = format_roster_with_presence(get_roster(U, S)),
|
||||||
|
{false, {response, [{array, Roster}]}};
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% get_presence struct[{user, String}, {server, String}]
|
||||||
|
%% array[struct[{jid, String}, {show, String}, {status, String}]]
|
||||||
|
handler(_State, {call, get_presence, [{struct, Struct}]}) ->
|
||||||
|
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
{Resource, Show, Status} = get_presence(U, S),
|
||||||
|
FullJID = case Resource of
|
||||||
|
[] ->
|
||||||
|
lists:flatten([U,"@",S]);
|
||||||
|
_ ->
|
||||||
|
lists:flatten([U,"@",S,"/",Resource])
|
||||||
|
end,
|
||||||
|
R = {struct, [{jid, FullJID}, {show, Show}, {status, Status} ]},
|
||||||
|
{false, {response, [R]}};
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% get_resources struct[{user, String}, {server, String}]
|
||||||
|
%% array[String]
|
||||||
|
handler(_State, {call, get_resources, [{struct, Struct}]}) ->
|
||||||
|
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
Resources = get_resources(U, S),
|
||||||
|
{false, {response, [{array, Resources}]}};
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% send_chat struct[{from, String}, {to, String}, {body, String}]
|
||||||
|
%% Integer
|
||||||
|
handler(_State, {call, send_chat, [{struct, Struct}]}) ->
|
||||||
|
[{body, Msg}, {from, FromJID}, {to, ToJID}] = lists:sort(Struct),
|
||||||
|
From = jlib:string_to_jid(FromJID),
|
||||||
|
To = jlib:string_to_jid(ToJID),
|
||||||
|
Stanza = {xmlelement, "message", [{"type", "chat"}],
|
||||||
|
[{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
|
||||||
|
ejabberd_router:route(From, To, Stanza),
|
||||||
|
{false, {response, [0]}};
|
||||||
|
|
||||||
|
%% send_message struct[{from, String}, {to, String}, {subject, String}, {body, String}]
|
||||||
|
%% Integer
|
||||||
|
handler(_State, {call, send_message, [{struct, Struct}]}) ->
|
||||||
|
[{body, Msg}, {from, FromJID}, {subject, Sub}, {to, ToJID}] = lists:sort(Struct),
|
||||||
|
From = jlib:string_to_jid(FromJID),
|
||||||
|
To = jlib:string_to_jid(ToJID),
|
||||||
|
Stanza = {xmlelement, "message", [{"type", "normal"}],
|
||||||
|
[{xmlelement, "subject", [], [{xmlcdata, Sub}]},
|
||||||
|
{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
|
||||||
|
ejabberd_router:route(From, To, Stanza),
|
||||||
|
{false, {response, [0]}};
|
||||||
|
|
||||||
|
%% send_stanza struct[{from, String}, {to, String}, {stanza, String}]
|
||||||
|
%% Integer
|
||||||
|
handler(_State, {call, send_stanza, [{struct, Struct}]}) ->
|
||||||
|
[{from, FromJID}, {stanza, StanzaStr}, {to, ToJID}] = lists:sort(Struct),
|
||||||
|
case xml_stream:parse_element(StanzaStr) of
|
||||||
|
{error, _} ->
|
||||||
|
{false, {response, [1]}};
|
||||||
|
Stanza ->
|
||||||
|
{xmlelement, _, Attrs, _} = Stanza,
|
||||||
|
From = jlib:string_to_jid(proplists:get_value("from", Attrs, FromJID)),
|
||||||
|
To = jlib:string_to_jid(proplists:get_value("to", Attrs, ToJID)),
|
||||||
|
ejabberd_router:route(From, To, Stanza),
|
||||||
|
{false, {response, [0]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% rename_account struct[{user, String}, {server, String}, {newuser, String}, {newserver, String}]
|
||||||
|
%% Integer
|
||||||
|
handler(_State, {call, rename_account, [{struct, Struct}]}) ->
|
||||||
|
[{newserver, NS}, {newuser, NU}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
case ejabberd_auth:get_password(U, S) of
|
||||||
|
false ->
|
||||||
|
{false, {response, [1]}};
|
||||||
|
Password ->
|
||||||
|
case ejabberd_auth:try_register(NU, NS, Password) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
OldJID = jlib:jid_to_string({U, S, ""}),
|
||||||
|
NewJID = jlib:jid_to_string({NU, NS, ""}),
|
||||||
|
Roster = get_roster(U, S),
|
||||||
|
lists:foreach(fun(#roster{jid={RU, RS, RE}, name=Nick, groups=Groups}) ->
|
||||||
|
NewGroup = extract_group(Groups),
|
||||||
|
{NewNick, Group} = case lists:filter(fun(#roster{jid={PU, PS, _}}) ->
|
||||||
|
(PU == U) and (PS == S)
|
||||||
|
end, get_roster(RU, RS)) of
|
||||||
|
[#roster{name=OldNick, groups=OldGroups}|_] -> {OldNick, extract_group(OldGroups)};
|
||||||
|
[] -> {NU, []}
|
||||||
|
end,
|
||||||
|
JIDStr = jlib:jid_to_string({RU, RS, RE}),
|
||||||
|
link_contacts(NewJID, NewNick, NewGroup, JIDStr, Nick, Group),
|
||||||
|
unlink_contacts(OldJID, JIDStr)
|
||||||
|
end, Roster),
|
||||||
|
ejabberd_auth:remove_user(U, S),
|
||||||
|
{false, {response, [0]}};
|
||||||
|
{atomic, exists} ->
|
||||||
|
{false, {response, [409]}};
|
||||||
|
_ ->
|
||||||
|
{false, {response, [1]}}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% add_contacts struct[{user, String}, {server, String},
|
||||||
|
%% array[struct[{jid, String}, {group, String}, {nick, String}]]]
|
||||||
|
%% Integer
|
||||||
|
handler(_State, {call, add_contacts, [{struct, Struct}]}) ->
|
||||||
|
[{array, Contacts}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
JID1 = jlib:jid_to_string({U, S, ""}),
|
||||||
|
Response = lists:foldl(fun({struct, Struct2}, Acc) ->
|
||||||
|
[{group, Group}, {jid, JID2}, {nick, Nick}] = lists:sort(Struct2),
|
||||||
|
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||||
|
case ejabberd_auth:is_user_exists(PU, PS) of
|
||||||
|
true ->
|
||||||
|
case link_contacts(JID1, "", "", JID2, Nick, Group) of
|
||||||
|
{atomic, ok} -> Acc;
|
||||||
|
_ -> 1
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end
|
||||||
|
end, 0, element(2, Contacts)),
|
||||||
|
{false, {response, [Response]}};
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% remove_contacts struct[{user, String}, {server, String}, array[String]]
|
||||||
|
%% Integer
|
||||||
|
handler(_State, {call, remove_contacts, [{struct, Struct}]}) ->
|
||||||
|
[{array, Contacts}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true ->
|
||||||
|
JID1 = jlib:jid_to_string({U, S, ""}),
|
||||||
|
Response = lists:foldl(fun(JID2, Acc) ->
|
||||||
|
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||||
|
case ejabberd_auth:is_user_exists(PU, PS) of
|
||||||
|
true ->
|
||||||
|
case unlink_contacts(JID1, JID2) of
|
||||||
|
{atomic, ok} -> Acc;
|
||||||
|
_ -> 1
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end
|
||||||
|
end, 0, element(2, Contacts)),
|
||||||
|
{false, {response, [Response]}};
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% check_users_registration array[struct[{user, String}, {server, String}]]
|
||||||
|
%% array[struct[{user, String}, {server, String}, {status, Integer}]]
|
||||||
|
handler(_State, {call, check_users_registration, [{array, Users}]}) ->
|
||||||
|
Response = lists:map(fun({struct, Struct}) ->
|
||||||
|
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||||
|
Registered = case ejabberd_auth:is_user_exists(U, S) of
|
||||||
|
true -> 1;
|
||||||
|
false -> 0
|
||||||
|
end,
|
||||||
|
{struct, [{user, U}, {server, S}, {status, Registered}]}
|
||||||
|
end, Users),
|
||||||
|
{false, {response, [{array, Response}]}};
|
||||||
|
|
||||||
|
|
||||||
|
%% If no other guard matches
|
||||||
|
handler(_State, Payload) ->
|
||||||
|
FaultString = lists:flatten(io_lib:format("Unknown call: ~p", [Payload])),
|
||||||
|
{false, {response, {fault, -1, FaultString}}}.
|
||||||
|
|
||||||
|
|
||||||
|
%% -----------------------------
|
||||||
|
%% Internal roster handling
|
||||||
|
%% -----------------------------
|
||||||
|
|
||||||
|
get_roster(User, Server) ->
|
||||||
|
LUser = jlib:nodeprep(User),
|
||||||
|
LServer = jlib:nameprep(Server),
|
||||||
|
ejabberd_hooks:run_fold(roster_get, LServer, [], [{LUser, LServer}]).
|
||||||
|
|
||||||
|
change_rosternick(User, Server, Nick) ->
|
||||||
|
LUser = jlib:nodeprep(User),
|
||||||
|
LServer = jlib:nameprep(Server),
|
||||||
|
LJID = {LUser, LServer, []},
|
||||||
|
JID = jlib:jid_to_string(LJID),
|
||||||
|
Push = fun(Subscription) ->
|
||||||
|
jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
|
||||||
|
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
|
||||||
|
[{xmlelement, "item", [{"jid", JID}, {"name", Nick}, {"subscription", atom_to_list(Subscription)}],
|
||||||
|
[]}]}]})
|
||||||
|
end,
|
||||||
|
Result = case roster_backend(Server) of
|
||||||
|
mnesia ->
|
||||||
|
%% XXX This way of doing can not work with s2s
|
||||||
|
mnesia:transaction(
|
||||||
|
fun() ->
|
||||||
|
lists:foreach(fun(Roster) ->
|
||||||
|
{U, S} = Roster#roster.us,
|
||||||
|
mnesia:write(Roster#roster{name = Nick}),
|
||||||
|
lists:foreach(fun(R) ->
|
||||||
|
UJID = jlib:make_jid(U, S, R),
|
||||||
|
ejabberd_router:route(UJID, UJID, Push(Roster#roster.subscription))
|
||||||
|
end, get_resources(U, S))
|
||||||
|
end, mnesia:match_object(#roster{jid = LJID, _ = '_'}))
|
||||||
|
end);
|
||||||
|
odbc ->
|
||||||
|
%%% XXX This way of doing does not work with several domains
|
||||||
|
ejabberd_odbc:sql_transaction(Server,
|
||||||
|
fun() ->
|
||||||
|
SNick = ejabberd_odbc:escape(Nick),
|
||||||
|
SJID = ejabberd_odbc:escape(JID),
|
||||||
|
ejabberd_odbc:sql_query_t(
|
||||||
|
["update rosterusers"
|
||||||
|
" set nick='", SNick, "'"
|
||||||
|
" where jid='", SJID, "';"]),
|
||||||
|
case ejabberd_odbc:sql_query_t(
|
||||||
|
["select username from rosterusers"
|
||||||
|
" where jid='", SJID, "'"
|
||||||
|
" and subscription = 'B';"]) of
|
||||||
|
{selected, ["username"], Users} ->
|
||||||
|
lists:foreach(fun({RU}) ->
|
||||||
|
lists:foreach(fun(R) ->
|
||||||
|
UJID = jlib:make_jid(RU, Server, R),
|
||||||
|
ejabberd_router:route(UJID, UJID, Push(both))
|
||||||
|
end, get_resources(RU, Server))
|
||||||
|
end, Users);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
none ->
|
||||||
|
{error, no_roster}
|
||||||
|
end,
|
||||||
|
case Result of
|
||||||
|
{atomic, ok} -> ok;
|
||||||
|
_ -> error
|
||||||
|
end.
|
||||||
|
|
||||||
|
add_rosteritem(User, Server, JID, Nick, Group, Subscription) ->
|
||||||
|
add_rosteritem(User, Server, JID, Nick, Group, Subscription, true).
|
||||||
|
add_rosteritem(User, Server, JID, Nick, Group, Subscription, Push) ->
|
||||||
|
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
||||||
|
LJID = {RU,RS,[]},
|
||||||
|
Groups = case Group of
|
||||||
|
[] -> [];
|
||||||
|
_ -> [Group]
|
||||||
|
end,
|
||||||
|
Roster = #roster{
|
||||||
|
usj = {User,Server,LJID},
|
||||||
|
us = {User,Server},
|
||||||
|
jid = LJID,
|
||||||
|
name = Nick,
|
||||||
|
ask = none,
|
||||||
|
subscription = list_to_atom(Subscription),
|
||||||
|
groups = Groups},
|
||||||
|
Result =
|
||||||
|
case roster_backend(Server) of
|
||||||
|
mnesia ->
|
||||||
|
mnesia:transaction(fun() ->
|
||||||
|
case mnesia:read({roster,{User,Server,LJID}}) of
|
||||||
|
[#roster{subscription=both}] ->
|
||||||
|
already_added;
|
||||||
|
_ ->
|
||||||
|
mnesia:write(Roster)
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
odbc ->
|
||||||
|
%% MREMOND: TODO: check if already_added
|
||||||
|
case ejabberd_odbc:sql_transaction(Server,
|
||||||
|
fun() ->
|
||||||
|
Username = ejabberd_odbc:escape(User),
|
||||||
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||||
|
case ejabberd_odbc:sql_query_t(
|
||||||
|
["select username from rosterusers "
|
||||||
|
" where username='", Username, "' "
|
||||||
|
" and jid='", SJID,
|
||||||
|
"' and subscription = 'B';"]) of
|
||||||
|
{selected, ["username"],[]} ->
|
||||||
|
ItemVals = record_to_string(Roster),
|
||||||
|
ItemGroups = groups_to_string(Roster),
|
||||||
|
odbc_queries:update_roster(Server, Username,
|
||||||
|
SJID, ItemVals,
|
||||||
|
ItemGroups);
|
||||||
|
_ ->
|
||||||
|
already_added
|
||||||
|
end
|
||||||
|
end) of
|
||||||
|
{atomic, already_added} -> {atomic, already_added};
|
||||||
|
{atomic, _} -> {atomic, ok};
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
none ->
|
||||||
|
{error, no_roster}
|
||||||
|
end,
|
||||||
|
case {Result, Push} of
|
||||||
|
{{atomic, already_added}, _} -> ok; %% No need for roster push
|
||||||
|
{{atomic, ok}, true} -> roster_push(User, Server, JID, Nick, Subscription);
|
||||||
|
{{error, no_roster}, true} -> roster_push(User, Server, JID, Nick, Subscription);
|
||||||
|
{{atomic, ok}, false} -> ok;
|
||||||
|
_ -> error
|
||||||
|
end,
|
||||||
|
Result.
|
||||||
|
|
||||||
|
del_rosteritem(User, Server, JID) ->
|
||||||
|
del_rosteritem(User, Server, JID, true).
|
||||||
|
del_rosteritem(User, Server, JID, Push) ->
|
||||||
|
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
||||||
|
LJID = {RU,RS,[]},
|
||||||
|
Result = case roster_backend(Server) of
|
||||||
|
mnesia ->
|
||||||
|
mnesia:transaction(fun() ->
|
||||||
|
mnesia:delete({roster, {User,Server,LJID}})
|
||||||
|
end);
|
||||||
|
odbc ->
|
||||||
|
case ejabberd_odbc:sql_transaction(Server, fun() ->
|
||||||
|
Username = ejabberd_odbc:escape(User),
|
||||||
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||||
|
odbc_queries:del_roster(Server, Username, SJID)
|
||||||
|
end) of
|
||||||
|
{atomic, _} -> {atomic, ok};
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
none ->
|
||||||
|
{error, no_roster}
|
||||||
|
end,
|
||||||
|
case {Result, Push} of
|
||||||
|
{{atomic, ok}, true} -> roster_push(User, Server, JID, "", "remove");
|
||||||
|
{{error, no_roster}, true} -> roster_push(User, Server, JID, "", "remove");
|
||||||
|
{{atomic, ok}, false} -> ok;
|
||||||
|
_ -> error
|
||||||
|
end,
|
||||||
|
Result.
|
||||||
|
|
||||||
|
link_contacts(JID1, Nick1, JID2, Nick2) ->
|
||||||
|
link_contacts(JID1, Nick1, JID2, Nick2, true).
|
||||||
|
link_contacts(JID1, Nick1, JID2, Nick2, Push) ->
|
||||||
|
link_contacts(JID1, Nick1, [], JID2, Nick2, [], Push).
|
||||||
|
|
||||||
|
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2) ->
|
||||||
|
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, true).
|
||||||
|
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, Push) ->
|
||||||
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||||
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||||
|
case add_rosteritem(U1, S1, JID2, Nick2, Group1, "both", Push) of
|
||||||
|
{atomic, ok} -> add_rosteritem(U2, S2, JID1, Nick1, Group2, "both", Push);
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
unlink_contacts(JID1, JID2) ->
|
||||||
|
unlink_contacts(JID1, JID2, true).
|
||||||
|
unlink_contacts(JID1, JID2, Push) ->
|
||||||
|
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||||
|
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||||
|
case del_rosteritem(U1, S1, JID2, Push) of
|
||||||
|
{atomic, ok} -> del_rosteritem(U2, S2, JID1, Push);
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
roster_push(User, Server, JID, Nick, Subscription) ->
|
||||||
|
LJID = jlib:make_jid(User, Server, ""),
|
||||||
|
TJID = jlib:string_to_jid(JID),
|
||||||
|
{TU, TS, _} = jlib:jid_tolower(TJID),
|
||||||
|
Presence = {xmlelement, "presence", [{"type",
|
||||||
|
case Subscription of
|
||||||
|
"remove" -> "unsubscribed";
|
||||||
|
"none" -> "unsubscribe";
|
||||||
|
"both" -> "subscribed";
|
||||||
|
_ -> "subscribe"
|
||||||
|
end}], []},
|
||||||
|
Item = case Nick of
|
||||||
|
"" -> [{"jid", JID}, {"subscription", Subscription}];
|
||||||
|
_ -> [{"jid", JID}, {"name", Nick}, {"subscription", Subscription}]
|
||||||
|
end,
|
||||||
|
Result = jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
|
||||||
|
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
|
||||||
|
[{xmlelement, "item", Item, []}]}]}),
|
||||||
|
ejabberd_router:route(TJID, LJID, Presence),
|
||||||
|
ejabberd_router:route(LJID, LJID, Result),
|
||||||
|
lists:foreach(fun(Resource) ->
|
||||||
|
UJID = jlib:make_jid(User, Server, Resource),
|
||||||
|
ejabberd_router:route(TJID, UJID, Presence),
|
||||||
|
ejabberd_router:route(UJID, UJID, Result),
|
||||||
|
case Subscription of
|
||||||
|
"remove" -> none;
|
||||||
|
_ ->
|
||||||
|
lists:foreach(fun(TR) ->
|
||||||
|
ejabberd_router:route(jlib:make_jid(TU, TS, TR), UJID,
|
||||||
|
{xmlelement, "presence", [], []})
|
||||||
|
end, get_resources(TU, TS))
|
||||||
|
end
|
||||||
|
end, [R || R <- get_resources(User, Server), Subscription =/= "remove"]).
|
||||||
|
|
||||||
|
roster_backend(Server) ->
|
||||||
|
Modules = gen_mod:loaded_modules(Server),
|
||||||
|
Mnesia = lists:member(mod_roster, Modules),
|
||||||
|
Odbc = lists:member(mod_roster_odbc, Modules),
|
||||||
|
if Mnesia -> mnesia;
|
||||||
|
true ->
|
||||||
|
if Odbc -> odbc;
|
||||||
|
true -> none
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
record_to_string(#roster{us = {User, _Server},
|
||||||
|
jid = JID,
|
||||||
|
name = Name,
|
||||||
|
subscription = Subscription,
|
||||||
|
ask = Ask,
|
||||||
|
askmessage = AskMessage}) ->
|
||||||
|
Username = ejabberd_odbc:escape(User),
|
||||||
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
||||||
|
Nick = ejabberd_odbc:escape(Name),
|
||||||
|
SSubscription = case Subscription of
|
||||||
|
both -> "B";
|
||||||
|
to -> "T";
|
||||||
|
from -> "F";
|
||||||
|
none -> "N"
|
||||||
|
end,
|
||||||
|
SAsk = case Ask of
|
||||||
|
subscribe -> "S";
|
||||||
|
unsubscribe -> "U";
|
||||||
|
both -> "B";
|
||||||
|
out -> "O";
|
||||||
|
in -> "I";
|
||||||
|
none -> "N"
|
||||||
|
end,
|
||||||
|
SAskMessage = ejabberd_odbc:escape(AskMessage),
|
||||||
|
["'", Username, "',"
|
||||||
|
"'", SJID, "',"
|
||||||
|
"'", Nick, "',"
|
||||||
|
"'", SSubscription, "',"
|
||||||
|
"'", SAsk, "',"
|
||||||
|
"'", SAskMessage, "',"
|
||||||
|
"'N', '', 'item'"].
|
||||||
|
|
||||||
|
groups_to_string(#roster{us = {User, _Server},
|
||||||
|
jid = JID,
|
||||||
|
groups = Groups}) ->
|
||||||
|
Username = ejabberd_odbc:escape(User),
|
||||||
|
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
||||||
|
%% Empty groups do not need to be converted to string to be inserted in
|
||||||
|
%% the database
|
||||||
|
lists:foldl(fun([], Acc) -> Acc;
|
||||||
|
(Group, Acc) ->
|
||||||
|
String = ["'", Username, "',"
|
||||||
|
"'", SJID, "',"
|
||||||
|
"'", ejabberd_odbc:escape(Group), "'"],
|
||||||
|
[String|Acc]
|
||||||
|
end, [], Groups).
|
||||||
|
|
||||||
|
%% Format roster items as a list of:
|
||||||
|
%% [{struct, [{jid, "test@localhost"},{group, "Friends"},{nick, "Nicktest"}]}]
|
||||||
|
format_roster([]) ->
|
||||||
|
[];
|
||||||
|
format_roster(Items) ->
|
||||||
|
format_roster(Items, []).
|
||||||
|
format_roster([], Structs) ->
|
||||||
|
Structs;
|
||||||
|
format_roster([#roster{jid=JID, name=Nick, groups=Group,
|
||||||
|
subscription=Subs, ask=Ask}|Items], Structs) ->
|
||||||
|
{User,Server,_Resource} = JID,
|
||||||
|
Struct = {struct, [{jid,lists:flatten([User,"@",Server])},
|
||||||
|
{group, extract_group(Group)},
|
||||||
|
{nick, Nick},
|
||||||
|
{subscription, atom_to_list(Subs)},
|
||||||
|
{pending, atom_to_list(Ask)}
|
||||||
|
]},
|
||||||
|
format_roster(Items, [Struct|Structs]).
|
||||||
|
|
||||||
|
%% Format roster items as a list of:
|
||||||
|
%% [{struct, [{jid, "test@localhost"}, {resource, "Messenger"}, {group, "Friends"},
|
||||||
|
%% {nick, "Nicktest"},{show, "available"}, {status, "Currently at office"}]}]
|
||||||
|
%% Note: If user is connected several times, only keep the resource with the
|
||||||
|
%% highest non-negative priority
|
||||||
|
format_roster_with_presence([]) ->
|
||||||
|
[];
|
||||||
|
format_roster_with_presence(Items) ->
|
||||||
|
format_roster_with_presence(Items, []).
|
||||||
|
format_roster_with_presence([], Structs) ->
|
||||||
|
Structs;
|
||||||
|
format_roster_with_presence([#roster{jid=JID, name=Nick, groups=Group,
|
||||||
|
subscription=Subs, ask=Ask}|Items], Structs) ->
|
||||||
|
{User,Server,_R} = JID,
|
||||||
|
Presence = case Subs of
|
||||||
|
both -> get_presence(User, Server);
|
||||||
|
from -> get_presence(User, Server);
|
||||||
|
_Other -> {"", "unavailable", ""}
|
||||||
|
end,
|
||||||
|
{Resource, Show, Status} =
|
||||||
|
case Presence of
|
||||||
|
{_R, "invisible", _S} -> {"", "unavailable", ""};
|
||||||
|
_Status -> Presence
|
||||||
|
end,
|
||||||
|
Struct = {struct, [{jid,lists:flatten([User,"@",Server])},
|
||||||
|
{resource, Resource},
|
||||||
|
{group, extract_group(Group)},
|
||||||
|
{nick, Nick},
|
||||||
|
{subscription, atom_to_list(Subs)},
|
||||||
|
{pending, atom_to_list(Ask)},
|
||||||
|
{show, Show},
|
||||||
|
{status, Status}
|
||||||
|
]},
|
||||||
|
format_roster_with_presence(Items, [Struct|Structs]).
|
||||||
|
|
||||||
|
extract_group([]) -> [];
|
||||||
|
extract_group([Group|_Groups]) -> Group.
|
||||||
|
|
||||||
|
%% -----------------------------
|
||||||
|
%% Internal session handling
|
||||||
|
%% -----------------------------
|
||||||
|
|
||||||
|
%% This is inspired from ejabberd_sm.erl
|
||||||
|
get_presence(User, Server) ->
|
||||||
|
case get_sessions(User, Server) of
|
||||||
|
[] ->
|
||||||
|
{"", "unavailable", ""};
|
||||||
|
Ss ->
|
||||||
|
Session = hd(Ss),
|
||||||
|
if Session#session.priority >= 0 ->
|
||||||
|
Pid = element(2, Session#session.sid),
|
||||||
|
%{_User, _Resource, Show, Status} = rpc:call(node(Pid), ejabberd_c2s, get_presence, [Pid]),
|
||||||
|
{_User, Resource, Show, Status} = ejabberd_c2s:get_presence(Pid),
|
||||||
|
{Resource, Show, Status};
|
||||||
|
true ->
|
||||||
|
{"", "unavailable", ""}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_resources(User, Server) ->
|
||||||
|
lists:map(fun(S) -> element(3, S#session.usr)
|
||||||
|
end, get_sessions(User, Server)).
|
||||||
|
|
||||||
|
get_sessions(User, Server) ->
|
||||||
|
LUser = jlib:nodeprep(User),
|
||||||
|
LServer = jlib:nameprep(Server),
|
||||||
|
case catch mnesia:dirty_index_read(session, {LUser, LServer}, #session.us) of
|
||||||
|
{'EXIT', _Reason} -> [];
|
||||||
|
[] -> [];
|
||||||
|
Result -> lists:reverse(lists:keysort(#session.priority, clean_session_list(Result)))
|
||||||
|
end.
|
||||||
|
|
||||||
|
clean_session_list(Ss) ->
|
||||||
|
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
||||||
|
|
||||||
|
clean_session_list([], Res) ->
|
||||||
|
Res;
|
||||||
|
clean_session_list([S], Res) ->
|
||||||
|
[S | Res];
|
||||||
|
clean_session_list([S1, S2 | Rest], Res) ->
|
||||||
|
if
|
||||||
|
S1#session.usr == S2#session.usr ->
|
||||||
|
if
|
||||||
|
S1#session.sid > S2#session.sid ->
|
||||||
|
clean_session_list([S1 | Rest], Res);
|
||||||
|
true ->
|
||||||
|
clean_session_list([S2 | Rest], Res)
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
clean_session_list([S2 | Rest], [S1 | Res])
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% -----------------------------
|
||||||
|
%% Internal function pattern
|
||||||
|
%% -----------------------------
|
||||||
|
|
||||||
|
user_action(User, Server, Fun, OK) ->
|
||||||
|
case ejabberd_auth:is_user_exists(User, Server) of
|
||||||
|
true ->
|
||||||
|
case catch Fun() of
|
||||||
|
OK ->
|
||||||
|
{false, {response, [0]}};
|
||||||
|
_ ->
|
||||||
|
{false, {response, [1]}}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{false, {response, [404]}}
|
||||||
|
end.
|
Loading…
Reference in New Issue
Block a user