Packages
ejabberd
16.2.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/ejabberd_service.erl
%%%----------------------------------------------------------------------
%%% File : ejabberd_service.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : External component management (XEP-0114)
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2016 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.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_service).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-protocol({xep, 114, '1.6'}).
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
%% External exports
-export([start/2, start_link/2, send_text/2,
send_element/2, socket_type/0, transform_listen_option/2]).
-export([init/1, wait_for_stream/2,
wait_for_handshake/2, stream_established/2,
handle_event/3, handle_sync_event/4, code_change/4,
handle_info/3, terminate/3, print_state/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-record(state,
{socket :: ejabberd_socket:socket_state(),
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
host_opts = dict:new() :: ?TDICT,
host = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean()}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(STREAM_HEADER,
<<"<?xml version='1.0'?><stream:stream "
"xmlns:stream='http://etherx.jabber.org/stream"
"s' xmlns='jabber:component:accept' id='~s' "
"from='~s'>">>).
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_HEADER_ERR,
<<"<stream:stream xmlns:stream='http://etherx.ja"
"bber.org/streams'><stream:error>Invalid "
"Stream Header</stream:error></stream:stream>">>).
-define(INVALID_HANDSHAKE_ERR,
<<"<stream:error><not-authorized xmlns='urn:ietf"
":params:xml:ns:xmpp-streams'/><text "
"xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
"xml:lang='en'>Invalid Handshake</text></strea"
"m:error></stream:stream>">>).
-define(INVALID_XML_ERR,
fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR,
fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(SockData, Opts) ->
supervisor:start_child(ejabberd_service_sup,
[SockData, Opts]).
start_link(SockData, Opts) ->
(?GEN_FSM):start_link(ejabberd_service,
[SockData, Opts], fsm_limit_opts(Opts) ++ (?FSMOPTS)).
socket_type() -> xml_stream.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("(~w) External service connected", [Socket]),
Access = case lists:keysearch(access, 1, Opts) of
{value, {_, A}} -> A;
_ -> all
end,
HostOpts = case lists:keyfind(hosts, 1, Opts) of
{hosts, HOpts} ->
lists:foldl(
fun({H, Os}, D) ->
P = proplists:get_value(
password, Os,
p1_sha:sha(crypto:rand_bytes(20))),
dict:store(H, P, D)
end, dict:new(), HOpts);
false ->
Pass = proplists:get_value(
password, Opts,
p1_sha:sha(crypto:rand_bytes(20))),
dict:from_list([{global, Pass}])
end,
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
end,
CheckFrom = case lists:keysearch(service_check_from, 1,
Opts)
of
{value, {_, CF}} -> CF;
_ -> true
end,
SockMod:change_shaper(Socket, Shaper),
{ok, wait_for_stream,
#state{socket = Socket, sockmod = SockMod,
streamid = new_id(), host_opts = HostOpts,
access = Access, check_from = CheckFrom}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"jabber:component:accept">> ->
To = fxml:get_attr_s(<<"to">>, Attrs),
Host = jid:nameprep(To),
if Host == error ->
Header = io_lib:format(?STREAM_HEADER,
[<<"none">>, ?MYNAME]),
send_text(StateData,
<<(list_to_binary(Header))/binary,
(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
true ->
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid, fxml:crypt(To)]),
send_text(StateData, Header),
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
true ->
StateData#state.host_opts;
false ->
case dict:find(global, StateData#state.host_opts) of
{ok, GlobalPass} ->
dict:from_list([{Host, GlobalPass}]);
error ->
StateData#state.host_opts
end
end,
{next_state, wait_for_handshake,
StateData#state{host = Host, host_opts = HostOpts}}
end;
_ ->
send_text(StateData, ?INVALID_HEADER_ERR),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
Header = io_lib:format(?STREAM_HEADER,
[<<"none">>, ?MYNAME]),
send_text(StateData,
<<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
wait_for_handshake({xmlstreamelement, El}, StateData) ->
#xmlel{name = Name, children = Els} = El,
case {Name, fxml:get_cdata(Els)} of
{<<"handshake">>, Digest} ->
case dict:find(StateData#state.host, StateData#state.host_opts) of
{ok, Password} ->
case p1_sha:sha(<<(StateData#state.streamid)/binary,
Password/binary>>) of
Digest ->
send_text(StateData, <<"<handshake/>">>),
lists:foreach(
fun (H) ->
ejabberd_router:register_route(H),
?INFO_MSG("Route registered for service ~p~n",
[H])
end, dict:fetch_keys(StateData#state.host_opts)),
{next_state, stream_established, StateData};
_ ->
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
{stop, normal, StateData}
end;
_ ->
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
{stop, normal, StateData}
end;
_ -> {next_state, wait_for_handshake, StateData}
end;
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_handshake({xmlstreamerror, _}, StateData) ->
send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
wait_for_handshake(closed, StateData) ->
{stop, normal, StateData}.
stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
From = fxml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field
%% when accept packets from any address.
%% In this case, the component can send packet of
%% behalf of the server users.
false -> jid:from_string(From);
%% The default is the standard behaviour in XEP-0114
_ ->
FromJID1 = jid:from_string(From),
case FromJID1 of
#jid{lserver = Server} ->
case dict:is_key(Server, StateData#state.host_opts) of
true -> FromJID1;
false -> error
end;
_ -> error
end
end,
To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> error;
_ -> jid:from_string(To)
end,
if ((Name == <<"iq">>) or (Name == <<"message">>) or
(Name == <<"presence">>))
and (ToJID /= error)
and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
true ->
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
send_element(StateData, Err),
error
end,
{next_state, stream_established, StateData};
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
stream_established(closed, StateData) ->
{stop, normal, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName,
StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
handle_info({send_element, El}, StateName, StateData) ->
send_element(StateData, El),
{next_state, StateName, StateData};
handle_info({route, From, To, Packet}, StateName,
StateData) ->
case acl:match_rule(global, StateData#state.access,
From)
of
allow ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
Attrs2 =
jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), Attrs),
Text = fxml:element_to_binary(#xmlel{name = Name,
attrs = Attrs2, children = Els}),
send_text(StateData, Text);
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
case StateName of
stream_established ->
lists:foreach(fun (H) ->
ejabberd_router:unregister_route(H)
end,
dict:fetch_keys(StateData#state.host_opts));
_ -> ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ok.
%%----------------------------------------------------------------------
%% Func: print_state/1
%% Purpose: Prepare the state to be printed on error log
%% Returns: State to print
%%----------------------------------------------------------------------
print_state(State) -> State.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket,
Text).
send_element(StateData, El) ->
send_text(StateData, fxml:element_to_binary(El)).
new_id() -> randoms:get_string().
transform_listen_option({hosts, Hosts, O}, Opts) ->
case lists:keyfind(hosts, 1, Opts) of
{_, PrevHostOpts} ->
NewHostOpts =
lists:foldl(
fun(H, Acc) ->
dict:append_list(H, O, Acc)
end, dict:from_list(PrevHostOpts), Hosts),
[{hosts, dict:to_list(NewHostOpts)}|
lists:keydelete(hosts, 1, Opts)];
_ ->
[{hosts, [{H, O} || H <- Hosts]}|Opts]
end;
transform_listen_option({host, Host, Os}, Opts) ->
transform_listen_option({hosts, [Host], Os}, Opts);
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) ->
[{max_queue, N}];
_ ->
case ejabberd_config:get_option(
max_fsm_queue,
fun(I) when is_integer(I), I > 0 -> I end) of
undefined -> [];
N -> [{max_queue, N}]
end
end.
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) -> [max_fsm_queue].