mirror of
https://github.com/processone/ejabberd.git
synced 2024-06-14 22:00:16 +02:00
2d43c07c62
ejabberd receivers were meant to serve connections from frontends to backends. However, this approach was not popular and frontend related code was removed in previous releases. Now, ejabberd receiver's code was also removed, making the code shorter and cleaner. Also, in stress tests ejabberd now handles load more robustly, without c2s processes overload (even with disabled shapers). ejabberd_socket.erl is renamed to xmpp_socket.erl: it's supposed to be finally moved into stand-alone xmpp library.
1045 lines
36 KiB
Erlang
1045 lines
36 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% Created : 8 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2002-2017 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_c2s).
|
|
-behaviour(xmpp_stream_in).
|
|
-behaviour(ejabberd_config).
|
|
-behaviour(xmpp_socket).
|
|
|
|
-protocol({rfc, 6121}).
|
|
|
|
%% xmpp_socket callbacks
|
|
-export([start/2, start_link/2, socket_type/0]).
|
|
%% ejabberd_config callbacks
|
|
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
|
|
%% xmpp_stream_in callbacks
|
|
-export([init/1, handle_call/3, handle_cast/2,
|
|
handle_info/2, terminate/2, code_change/3]).
|
|
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
|
|
compress_methods/1, bind/2, sasl_mechanisms/2,
|
|
get_password_fun/1, check_password_fun/1, check_password_digest_fun/1,
|
|
unauthenticated_stream_features/1, authenticated_stream_features/1,
|
|
handle_stream_start/2, handle_stream_end/2,
|
|
handle_unauthenticated_packet/2, handle_authenticated_packet/2,
|
|
handle_auth_success/4, handle_auth_failure/4, handle_send/3,
|
|
handle_recv/3, handle_cdata/2, handle_unbinded_packet/2]).
|
|
%% Hooks
|
|
-export([handle_unexpected_cast/2,
|
|
reject_unauthenticated_packet/2, process_closed/2,
|
|
process_terminated/2, process_info/2]).
|
|
%% API
|
|
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
|
|
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
|
|
reply/2, copy_state/2, set_timeout/2, route/2,
|
|
host_up/1, host_down/1]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("xmpp.hrl").
|
|
-include("logger.hrl").
|
|
-include("mod_roster.hrl").
|
|
|
|
-define(SETS, gb_sets).
|
|
|
|
-type state() :: map().
|
|
-export_type([state/0]).
|
|
|
|
%%%===================================================================
|
|
%%% xmpp_socket API
|
|
%%%===================================================================
|
|
start(SockData, Opts) ->
|
|
case proplists:get_value(supervisor, Opts, true) of
|
|
true ->
|
|
case supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]) of
|
|
{ok, undefined} -> ignore;
|
|
Res -> Res
|
|
end;
|
|
_ ->
|
|
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
|
ejabberd_config:fsm_limit_opts(Opts))
|
|
end.
|
|
|
|
start_link(SockData, Opts) ->
|
|
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
|
|
ejabberd_config:fsm_limit_opts(Opts)).
|
|
|
|
socket_type() ->
|
|
xml_stream.
|
|
|
|
%%%===================================================================
|
|
%%% Common API
|
|
%%%===================================================================
|
|
-spec call(pid(), term(), non_neg_integer() | infinity) -> term().
|
|
call(Ref, Msg, Timeout) ->
|
|
xmpp_stream_in:call(Ref, Msg, Timeout).
|
|
|
|
-spec cast(pid(), term()) -> ok.
|
|
cast(Ref, Msg) ->
|
|
xmpp_stream_in:cast(Ref, Msg).
|
|
|
|
reply(Ref, Reply) ->
|
|
xmpp_stream_in:reply(Ref, Reply).
|
|
|
|
-spec get_presence(pid()) -> presence().
|
|
get_presence(Ref) ->
|
|
call(Ref, get_presence, 1000).
|
|
|
|
-spec set_presence(pid(), presence()) -> ok.
|
|
set_presence(Ref, Pres) ->
|
|
call(Ref, {set_presence, Pres}, 1000).
|
|
|
|
-spec resend_presence(pid()) -> ok.
|
|
resend_presence(Pid) ->
|
|
resend_presence(Pid, undefined).
|
|
|
|
-spec resend_presence(pid(), jid() | undefined) -> ok.
|
|
resend_presence(Pid, To) ->
|
|
route(Pid, {resend_presence, To}).
|
|
|
|
-spec close(pid()) -> ok;
|
|
(state()) -> state().
|
|
close(Ref) ->
|
|
xmpp_stream_in:close(Ref).
|
|
|
|
-spec close(pid(), atom()) -> ok;
|
|
(state(), atom()) -> state().
|
|
close(Ref, Reason) ->
|
|
xmpp_stream_in:close(Ref, Reason).
|
|
|
|
-spec stop(pid()) -> ok;
|
|
(state()) -> no_return().
|
|
stop(Ref) ->
|
|
xmpp_stream_in:stop(Ref).
|
|
|
|
-spec send(pid(), xmpp_element()) -> ok;
|
|
(state(), xmpp_element()) -> state().
|
|
send(Pid, Pkt) when is_pid(Pid) ->
|
|
xmpp_stream_in:send(Pid, Pkt);
|
|
send(#{lserver := LServer} = State, Pkt) ->
|
|
Pkt1 = fix_from_to(Pkt, State),
|
|
case ejabberd_hooks:run_fold(c2s_filter_send, LServer, {Pkt1, State}, []) of
|
|
{drop, State1} -> State1;
|
|
{Pkt2, State1} -> xmpp_stream_in:send(State1, Pkt2)
|
|
end.
|
|
|
|
-spec send_error(state(), xmpp_element(), stanza_error()) -> state().
|
|
send_error(#{lserver := LServer} = State, Pkt, Err) ->
|
|
case ejabberd_hooks:run_fold(c2s_filter_send, LServer, {Pkt, State}, []) of
|
|
{drop, State1} -> State1;
|
|
{Pkt1, State1} -> xmpp_stream_in:send_error(State1, Pkt1, Err)
|
|
end.
|
|
|
|
-spec route(pid(), term()) -> boolean().
|
|
route(Pid, Term) ->
|
|
ejabberd_cluster:send(Pid, Term).
|
|
|
|
-spec set_timeout(state(), timeout()) -> state().
|
|
set_timeout(State, Timeout) ->
|
|
xmpp_stream_in:set_timeout(State, Timeout).
|
|
|
|
-spec host_up(binary()) -> ok.
|
|
host_up(Host) ->
|
|
ejabberd_hooks:add(c2s_closed, Host, ?MODULE, process_closed, 100),
|
|
ejabberd_hooks:add(c2s_terminated, Host, ?MODULE,
|
|
process_terminated, 100),
|
|
ejabberd_hooks:add(c2s_unauthenticated_packet, Host, ?MODULE,
|
|
reject_unauthenticated_packet, 100),
|
|
ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
|
|
process_info, 100),
|
|
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
|
|
handle_unexpected_cast, 100).
|
|
|
|
-spec host_down(binary()) -> ok.
|
|
host_down(Host) ->
|
|
ejabberd_hooks:delete(c2s_closed, Host, ?MODULE, process_closed, 100),
|
|
ejabberd_hooks:delete(c2s_terminated, Host, ?MODULE,
|
|
process_terminated, 100),
|
|
ejabberd_hooks:delete(c2s_unauthenticated_packet, Host, ?MODULE,
|
|
reject_unauthenticated_packet, 100),
|
|
ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
|
|
process_info, 100),
|
|
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
|
|
handle_unexpected_cast, 100).
|
|
|
|
%% Copies content of one c2s state to another.
|
|
%% This is needed for session migration from one pid to another.
|
|
-spec copy_state(state(), state()) -> state().
|
|
copy_state(#{owner := Owner} = NewState,
|
|
#{jid := JID, resource := Resource, sid := {Time, _},
|
|
auth_module := AuthModule, lserver := LServer,
|
|
pres_a := PresA} = OldState) ->
|
|
State1 = case OldState of
|
|
#{pres_last := Pres, pres_timestamp := PresTS} ->
|
|
NewState#{pres_last => Pres, pres_timestamp => PresTS};
|
|
_ ->
|
|
NewState
|
|
end,
|
|
Conn = get_conn_type(State1),
|
|
State2 = State1#{jid => JID, resource => Resource,
|
|
conn => Conn,
|
|
sid => {Time, Owner},
|
|
auth_module => AuthModule,
|
|
pres_a => PresA},
|
|
ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]).
|
|
|
|
-spec open_session(state()) -> {ok, state()} | state().
|
|
open_session(#{user := U, server := S, resource := R,
|
|
sid := SID, ip := IP, auth_module := AuthModule} = State) ->
|
|
JID = jid:make(U, S, R),
|
|
State1 = change_shaper(State),
|
|
Conn = get_conn_type(State1),
|
|
State2 = State1#{conn => Conn, resource => R, jid => JID},
|
|
Prio = case maps:get(pres_last, State, undefined) of
|
|
undefined -> undefined;
|
|
Pres -> get_priority_from_presence(Pres)
|
|
end,
|
|
Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthModule}],
|
|
ejabberd_sm:open_session(SID, U, S, R, Prio, Info),
|
|
xmpp_stream_in:establish(State2).
|
|
|
|
%%%===================================================================
|
|
%%% Hooks
|
|
%%%===================================================================
|
|
process_info(#{lserver := LServer} = State, {route, Packet}) ->
|
|
{Pass, State1} = case Packet of
|
|
#presence{} ->
|
|
process_presence_in(State, Packet);
|
|
#message{} ->
|
|
process_message_in(State, Packet);
|
|
#iq{} ->
|
|
process_iq_in(State, Packet)
|
|
end,
|
|
if Pass ->
|
|
{Packet1, State2} = ejabberd_hooks:run_fold(
|
|
user_receive_packet, LServer,
|
|
{Packet, State1}, []),
|
|
case Packet1 of
|
|
drop -> State2;
|
|
_ -> send(State2, Packet1)
|
|
end;
|
|
true ->
|
|
State1
|
|
end;
|
|
process_info(#{jid := JID} = State, {resend_presence, To}) ->
|
|
case maps:get(pres_last, State, error) of
|
|
error -> State;
|
|
Pres when To == undefined ->
|
|
process_self_presence(State, Pres);
|
|
Pres when To#jid.luser == JID#jid.luser andalso
|
|
To#jid.lserver == JID#jid.lserver andalso
|
|
To#jid.lresource == <<"">> ->
|
|
process_self_presence(State, Pres);
|
|
Pres ->
|
|
process_presence_out(State, xmpp:set_to(Pres, To))
|
|
end;
|
|
process_info(State, Info) ->
|
|
?WARNING_MSG("got unexpected info: ~p", [Info]),
|
|
State.
|
|
|
|
handle_unexpected_cast(State, Msg) ->
|
|
?WARNING_MSG("got unexpected cast: ~p", [Msg]),
|
|
State.
|
|
|
|
reject_unauthenticated_packet(State, _Pkt) ->
|
|
Err = xmpp:serr_not_authorized(),
|
|
send(State, Err).
|
|
|
|
process_closed(State, Reason) ->
|
|
stop(State#{stop_reason => Reason}).
|
|
|
|
process_terminated(#{sid := SID, socket := Socket,
|
|
jid := JID, user := U, server := S, resource := R} = State,
|
|
Reason) ->
|
|
Status = format_reason(State, Reason),
|
|
?INFO_MSG("(~s) Closing c2s session for ~s: ~s",
|
|
[xmpp_socket:pp(Socket), jid:encode(JID), Status]),
|
|
State1 = case maps:is_key(pres_last, State) of
|
|
true ->
|
|
Pres = #presence{type = unavailable,
|
|
status = xmpp:mk_text(Status),
|
|
from = JID,
|
|
to = jid:remove_resource(JID)},
|
|
ejabberd_sm:close_session_unset_presence(SID, U, S, R,
|
|
Status),
|
|
broadcast_presence_unavailable(State, Pres);
|
|
false ->
|
|
ejabberd_sm:close_session(SID, U, S, R),
|
|
State
|
|
end,
|
|
bounce_message_queue(),
|
|
State1;
|
|
process_terminated(#{socket := Socket,
|
|
stop_reason := {tls, _}} = State, Reason) ->
|
|
?WARNING_MSG("(~s) Failed to secure c2s connection: ~s",
|
|
[xmpp_socket:pp(Socket), format_reason(State, Reason)]),
|
|
State;
|
|
process_terminated(State, _Reason) ->
|
|
State.
|
|
|
|
%%%===================================================================
|
|
%%% xmpp_stream_in callbacks
|
|
%%%===================================================================
|
|
tls_options(#{lserver := LServer, tls_options := DefaultOpts,
|
|
stream_encrypted := Encrypted}) ->
|
|
TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
|
|
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
|
|
{_, _} ->
|
|
case get_certfile(LServer) of
|
|
undefined -> DefaultOpts;
|
|
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
|
{certfile, CertFile})
|
|
end
|
|
end,
|
|
TLSOpts2 = case ejabberd_config:get_option(
|
|
{c2s_ciphers, LServer}) of
|
|
undefined -> TLSOpts1;
|
|
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
|
|
{ciphers, Ciphers})
|
|
end,
|
|
TLSOpts3 = case ejabberd_config:get_option(
|
|
{c2s_protocol_options, LServer}) of
|
|
undefined -> TLSOpts2;
|
|
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
|
|
{protocol_options, ProtoOpts})
|
|
end,
|
|
TLSOpts4 = case ejabberd_config:get_option(
|
|
{c2s_dhfile, LServer}) of
|
|
undefined -> TLSOpts3;
|
|
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
|
|
{dhfile, DHFile})
|
|
end,
|
|
TLSOpts5 = case ejabberd_config:get_option(
|
|
{c2s_cafile, LServer}) of
|
|
undefined -> TLSOpts4;
|
|
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
|
|
{cafile, CAFile})
|
|
end,
|
|
case ejabberd_config:get_option({c2s_tls_compression, LServer}) of
|
|
undefined -> TLSOpts5;
|
|
false -> [compression_none | TLSOpts5];
|
|
true -> lists:delete(compression_none, TLSOpts5)
|
|
end.
|
|
|
|
tls_required(#{tls_required := TLSRequired}) ->
|
|
TLSRequired.
|
|
|
|
tls_verify(#{tls_verify := TLSVerify}) ->
|
|
TLSVerify.
|
|
|
|
tls_enabled(#{tls_enabled := TLSEnabled,
|
|
tls_required := TLSRequired,
|
|
tls_verify := TLSVerify}) ->
|
|
TLSEnabled or TLSRequired or TLSVerify.
|
|
|
|
compress_methods(#{zlib := true}) ->
|
|
[<<"zlib">>];
|
|
compress_methods(_) ->
|
|
[].
|
|
|
|
unauthenticated_stream_features(#{lserver := LServer}) ->
|
|
ejabberd_hooks:run_fold(c2s_pre_auth_features, LServer, [], [LServer]).
|
|
|
|
authenticated_stream_features(#{lserver := LServer}) ->
|
|
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
|
|
|
|
sasl_mechanisms(Mechs, #{lserver := LServer}) ->
|
|
Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
|
|
Mechs2 = case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer) of
|
|
true -> Mechs1;
|
|
false -> [<<"ANONYMOUS">>|Mechs1]
|
|
end,
|
|
Mechs -- Mechs2.
|
|
|
|
get_password_fun(#{lserver := LServer}) ->
|
|
fun(U) ->
|
|
ejabberd_auth:get_password_with_authmodule(U, LServer)
|
|
end.
|
|
|
|
check_password_fun(#{lserver := LServer}) ->
|
|
fun(U, AuthzId, P) ->
|
|
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P)
|
|
end.
|
|
|
|
check_password_digest_fun(#{lserver := LServer}) ->
|
|
fun(U, AuthzId, P, D, DG) ->
|
|
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P, D, DG)
|
|
end.
|
|
|
|
bind(<<"">>, State) ->
|
|
bind(new_uniq_id(), State);
|
|
bind(R, #{user := U, server := S, access := Access, lang := Lang,
|
|
lserver := LServer, socket := Socket,
|
|
ip := IP} = State) ->
|
|
case resource_conflict_action(U, S, R) of
|
|
closenew ->
|
|
{error, xmpp:err_conflict(), State};
|
|
{accept_resource, Resource} ->
|
|
JID = jid:make(U, S, Resource),
|
|
case acl:access_matches(Access,
|
|
#{usr => jid:split(JID), ip => IP},
|
|
LServer) of
|
|
allow ->
|
|
State1 = open_session(State#{resource => Resource,
|
|
sid => ejabberd_sm:make_sid()}),
|
|
State2 = ejabberd_hooks:run_fold(
|
|
c2s_session_opened, LServer, State1, []),
|
|
?INFO_MSG("(~s) Opened c2s session for ~s",
|
|
[xmpp_socket:pp(Socket), jid:encode(JID)]),
|
|
{ok, State2};
|
|
deny ->
|
|
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
|
|
?INFO_MSG("(~s) Forbidden c2s session for ~s",
|
|
[xmpp_socket:pp(Socket), jid:encode(JID)]),
|
|
Txt = <<"Access denied by service policy">>,
|
|
{error, xmpp:err_not_allowed(Txt, Lang), State}
|
|
end
|
|
end.
|
|
|
|
handle_stream_start(StreamStart, #{lserver := LServer} = State) ->
|
|
case ejabberd_router:is_my_host(LServer) of
|
|
false ->
|
|
send(State#{lserver => ?MYNAME}, xmpp:serr_host_unknown());
|
|
true ->
|
|
State1 = change_shaper(State),
|
|
ejabberd_hooks:run_fold(
|
|
c2s_stream_started, LServer, State1, [StreamStart])
|
|
end.
|
|
|
|
handle_stream_end(Reason, #{lserver := LServer} = State) ->
|
|
State1 = State#{stop_reason => Reason},
|
|
ejabberd_hooks:run_fold(c2s_closed, LServer, State1, [Reason]).
|
|
|
|
handle_auth_success(User, Mech, AuthModule,
|
|
#{socket := Socket,
|
|
ip := IP, lserver := LServer} = State) ->
|
|
?INFO_MSG("(~s) Accepted c2s ~s authentication for ~s@~s by ~s backend from ~s",
|
|
[xmpp_socket:pp(Socket), Mech, User, LServer,
|
|
ejabberd_auth:backend_type(AuthModule),
|
|
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
|
|
State1 = State#{auth_module => AuthModule},
|
|
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State1, [true, User]).
|
|
|
|
handle_auth_failure(User, Mech, Reason,
|
|
#{socket := Socket,
|
|
ip := IP, lserver := LServer} = State) ->
|
|
?INFO_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
|
|
[xmpp_socket:pp(Socket), Mech,
|
|
if User /= <<"">> -> ["for ", User, "@", LServer, " "];
|
|
true -> ""
|
|
end,
|
|
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
|
|
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State, [false, User]).
|
|
|
|
handle_unbinded_packet(Pkt, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_unbinded_packet, LServer, State, [Pkt]).
|
|
|
|
handle_unauthenticated_packet(Pkt, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_unauthenticated_packet, LServer, State, [Pkt]).
|
|
|
|
handle_authenticated_packet(Pkt, #{lserver := LServer} = State) when not ?is_stanza(Pkt) ->
|
|
ejabberd_hooks:run_fold(c2s_authenticated_packet,
|
|
LServer, State, [Pkt]);
|
|
handle_authenticated_packet(Pkt, #{lserver := LServer, jid := JID,
|
|
ip := {IP, _}} = State) ->
|
|
Pkt1 = xmpp:put_meta(Pkt, ip, IP),
|
|
State1 = ejabberd_hooks:run_fold(c2s_authenticated_packet,
|
|
LServer, State, [Pkt1]),
|
|
#jid{luser = LUser} = JID,
|
|
{Pkt2, State2} = ejabberd_hooks:run_fold(
|
|
user_send_packet, LServer, {Pkt1, State1}, []),
|
|
case Pkt2 of
|
|
drop ->
|
|
State2;
|
|
#iq{type = set, sub_els = [_]} ->
|
|
try xmpp:try_subtag(Pkt2, #xmpp_session{}) of
|
|
#xmpp_session{} ->
|
|
send(State2, xmpp:make_iq_result(Pkt2));
|
|
_ ->
|
|
check_privacy_then_route(State2, Pkt2)
|
|
catch _:{xmpp_codec, Why} ->
|
|
Txt = xmpp:io_format_error(Why),
|
|
Lang = maps:get(lang, State),
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
|
send_error(State2, Pkt2, Err)
|
|
end;
|
|
#presence{to = #jid{luser = LUser, lserver = LServer,
|
|
lresource = <<"">>}} ->
|
|
process_self_presence(State2, Pkt2);
|
|
#presence{} ->
|
|
process_presence_out(State2, Pkt2);
|
|
_ ->
|
|
check_privacy_then_route(State2, Pkt2)
|
|
end.
|
|
|
|
handle_cdata(Data, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_handle_cdata, LServer,
|
|
State, [Data]).
|
|
|
|
handle_recv(El, Pkt, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_handle_recv, LServer, State, [El, Pkt]).
|
|
|
|
handle_send(Pkt, Result, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_handle_send, LServer, State, [Pkt, Result]).
|
|
|
|
init([State, Opts]) ->
|
|
Access = proplists:get_value(access, Opts, all),
|
|
Shaper = proplists:get_value(shaper, Opts, none),
|
|
TLSOpts1 = lists:filter(
|
|
fun({certfile, _}) -> true;
|
|
({ciphers, _}) -> true;
|
|
({dhfile, _}) -> true;
|
|
({cafile, _}) -> true;
|
|
({protocol_options, _}) -> true;
|
|
(_) -> false
|
|
end, Opts),
|
|
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
|
false -> [compression_none | TLSOpts1];
|
|
true -> TLSOpts1
|
|
end,
|
|
TLSEnabled = proplists:get_bool(starttls, Opts),
|
|
TLSRequired = proplists:get_bool(starttls_required, Opts),
|
|
TLSVerify = proplists:get_bool(tls_verify, Opts),
|
|
Zlib = proplists:get_bool(zlib, Opts),
|
|
State1 = State#{tls_options => TLSOpts2,
|
|
tls_required => TLSRequired,
|
|
tls_enabled => TLSEnabled,
|
|
tls_verify => TLSVerify,
|
|
pres_a => ?SETS:new(),
|
|
zlib => Zlib,
|
|
lang => ?MYLANG,
|
|
server => ?MYNAME,
|
|
lserver => ?MYNAME,
|
|
access => Access,
|
|
shaper => Shaper},
|
|
ejabberd_hooks:run_fold(c2s_init, {ok, State1}, [Opts]).
|
|
|
|
handle_call(get_presence, From, #{jid := JID} = State) ->
|
|
Pres = case maps:get(pres_last, State, error) of
|
|
error ->
|
|
BareJID = jid:remove_resource(JID),
|
|
#presence{from = JID, to = BareJID, type = unavailable};
|
|
P -> P
|
|
end,
|
|
reply(From, Pres),
|
|
State;
|
|
handle_call({set_presence, Pres}, From, State) ->
|
|
reply(From, ok),
|
|
process_self_presence(State, Pres);
|
|
handle_call(Request, From, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(
|
|
c2s_handle_call, LServer, State, [Request, From]).
|
|
|
|
handle_cast(Msg, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_handle_cast, LServer, State, [Msg]).
|
|
|
|
handle_info(Info, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_handle_info, LServer, State, [Info]).
|
|
|
|
terminate(Reason, #{lserver := LServer} = State) ->
|
|
ejabberd_hooks:run_fold(c2s_terminated, LServer, State, [Reason]).
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
{ok, State}.
|
|
|
|
%%%===================================================================
|
|
%%% Internal functions
|
|
%%%===================================================================
|
|
-spec process_iq_in(state(), iq()) -> {boolean(), state()}.
|
|
process_iq_in(State, #iq{} = IQ) ->
|
|
case privacy_check_packet(State, IQ, in) of
|
|
allow ->
|
|
{true, State};
|
|
deny ->
|
|
ejabberd_router:route_error(IQ, xmpp:err_service_unavailable()),
|
|
{false, State}
|
|
end.
|
|
|
|
-spec process_message_in(state(), message()) -> {boolean(), state()}.
|
|
process_message_in(State, #message{type = T} = Msg) ->
|
|
%% This function should be as simple as process_iq_in/2,
|
|
%% however, we don't route errors to MUC rooms in order
|
|
%% to avoid kicking us, because having a MUC room's JID blocked
|
|
%% most likely means having only some particular participant
|
|
%% blocked, i.e. room@conference.server.org/participant.
|
|
case privacy_check_packet(State, Msg, in) of
|
|
allow ->
|
|
{true, State};
|
|
deny when T == groupchat; T == headline ->
|
|
{false, State};
|
|
deny ->
|
|
case xmpp:has_subtag(Msg, #muc_user{}) of
|
|
true ->
|
|
ok;
|
|
false ->
|
|
ejabberd_router:route_error(
|
|
Msg, xmpp:err_service_unavailable())
|
|
end,
|
|
{false, State}
|
|
end.
|
|
|
|
-spec process_presence_in(state(), presence()) -> {boolean(), state()}.
|
|
process_presence_in(#{lserver := LServer, pres_a := PresA} = State0,
|
|
#presence{from = From, type = T} = Pres) ->
|
|
State = ejabberd_hooks:run_fold(c2s_presence_in, LServer, State0, [Pres]),
|
|
case T of
|
|
probe ->
|
|
route_probe_reply(From, State),
|
|
{false, State};
|
|
error ->
|
|
A = ?SETS:del_element(jid:tolower(From), PresA),
|
|
{true, State#{pres_a => A}};
|
|
_ ->
|
|
case privacy_check_packet(State, Pres, in) of
|
|
allow ->
|
|
{true, State};
|
|
deny ->
|
|
{false, State}
|
|
end
|
|
end.
|
|
|
|
-spec route_probe_reply(jid(), state()) -> ok.
|
|
route_probe_reply(From, #{jid := To,
|
|
pres_last := LastPres,
|
|
pres_timestamp := TS} = State) ->
|
|
{LUser, LServer, LResource} = jid:tolower(To),
|
|
IsAnotherResource = case jid:tolower(From) of
|
|
{LUser, LServer, R} when R /= LResource -> true;
|
|
_ -> false
|
|
end,
|
|
Subscription = get_subscription(To, From),
|
|
if IsAnotherResource orelse
|
|
Subscription == both orelse Subscription == from ->
|
|
Packet = xmpp_util:add_delay_info(LastPres, To, TS),
|
|
case privacy_check_packet(State, Packet, out) of
|
|
deny ->
|
|
ok;
|
|
allow ->
|
|
ejabberd_hooks:run(presence_probe_hook,
|
|
LServer,
|
|
[From, To, self()]),
|
|
ejabberd_router:route(xmpp:set_from_to(Packet, To, From))
|
|
end;
|
|
true ->
|
|
ok
|
|
end;
|
|
route_probe_reply(_, _) ->
|
|
ok.
|
|
|
|
-spec process_presence_out(state(), presence()) -> state().
|
|
process_presence_out(#{user := User, server := Server, lserver := LServer,
|
|
jid := JID, lang := Lang, pres_a := PresA} = State,
|
|
#presence{from = From, to = To, type = Type} = Pres) ->
|
|
if Type == subscribe; Type == subscribed;
|
|
Type == unsubscribe; Type == unsubscribed ->
|
|
Access = gen_mod:get_module_opt(LServer, mod_roster, access, all),
|
|
MyBareJID = jid:remove_resource(JID),
|
|
case acl:match_rule(LServer, Access, MyBareJID) of
|
|
deny ->
|
|
AccessErrTxt = <<"Access denied by service policy">>,
|
|
AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang),
|
|
send_error(State, Pres, AccessErr);
|
|
allow ->
|
|
ejabberd_hooks:run(roster_out_subscription,
|
|
LServer,
|
|
[User, Server, To, Type])
|
|
end;
|
|
true -> ok
|
|
end,
|
|
case privacy_check_packet(State, Pres, out) of
|
|
deny ->
|
|
PrivErrTxt = <<"Your active privacy list has denied "
|
|
"the routing of this stanza.">>,
|
|
PrivErr = xmpp:err_not_acceptable(PrivErrTxt, Lang),
|
|
send_error(State, Pres, PrivErr);
|
|
allow when Type == subscribe; Type == subscribed;
|
|
Type == unsubscribe; Type == unsubscribed ->
|
|
BareFrom = jid:remove_resource(From),
|
|
ejabberd_router:route(xmpp:set_from_to(Pres, BareFrom, To)),
|
|
State;
|
|
allow when Type == error; Type == probe ->
|
|
ejabberd_router:route(Pres),
|
|
State;
|
|
allow ->
|
|
ejabberd_router:route(Pres),
|
|
LTo = jid:tolower(To),
|
|
LBareTo = jid:remove_resource(LTo),
|
|
LBareFrom = jid:remove_resource(jid:tolower(From)),
|
|
if LBareTo /= LBareFrom ->
|
|
Subscription = get_subscription(From, To),
|
|
if Subscription /= both andalso Subscription /= from ->
|
|
A = case Type of
|
|
available -> ?SETS:add_element(LTo, PresA);
|
|
unavailable -> ?SETS:del_element(LTo, PresA)
|
|
end,
|
|
State#{pres_a => A};
|
|
true ->
|
|
State
|
|
end;
|
|
true ->
|
|
State
|
|
end
|
|
end.
|
|
|
|
-spec process_self_presence(state(), presence()) -> state().
|
|
process_self_presence(#{ip := IP, conn := Conn, lserver := LServer,
|
|
auth_module := AuthMod, sid := SID,
|
|
user := U, server := S, resource := R} = State,
|
|
#presence{type = unavailable} = Pres) ->
|
|
Status = xmpp:get_text(Pres#presence.status),
|
|
Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
|
|
ejabberd_sm:unset_presence(SID, U, S, R, Status, Info),
|
|
{Pres1, State1} = ejabberd_hooks:run_fold(
|
|
c2s_self_presence, LServer, {Pres, State}, []),
|
|
State2 = broadcast_presence_unavailable(State1, Pres1),
|
|
maps:remove(pres_last, maps:remove(pres_timestamp, State2));
|
|
process_self_presence(#{lserver := LServer} = State,
|
|
#presence{type = available} = Pres) ->
|
|
PreviousPres = maps:get(pres_last, State, undefined),
|
|
update_priority(State, Pres),
|
|
{Pres1, State1} = ejabberd_hooks:run_fold(
|
|
c2s_self_presence, LServer, {Pres, State}, []),
|
|
State2 = State1#{pres_last => Pres1,
|
|
pres_timestamp => p1_time_compat:timestamp()},
|
|
FromUnavailable = PreviousPres == undefined,
|
|
broadcast_presence_available(State2, Pres1, FromUnavailable);
|
|
process_self_presence(State, _Pres) ->
|
|
State.
|
|
|
|
-spec update_priority(state(), presence()) -> ok.
|
|
update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
|
|
sid := SID, user := U, server := S, resource := R},
|
|
Pres) ->
|
|
Priority = get_priority_from_presence(Pres),
|
|
Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
|
|
ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
|
|
|
|
-spec broadcast_presence_unavailable(state(), presence()) -> state().
|
|
broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
|
|
#jid{luser = LUser, lserver = LServer} = JID,
|
|
BareJID = jid:remove_resource(JID),
|
|
Items1 = ejabberd_hooks:run_fold(roster_get, LServer,
|
|
[], [{LUser, LServer}]),
|
|
Items2 = ?SETS:fold(
|
|
fun(LJID, Items) ->
|
|
[#roster{jid = LJID, subscription = from}|Items]
|
|
end, Items1, PresA),
|
|
JIDs = lists:foldl(
|
|
fun(#roster{jid = LJID, subscription = Sub}, Tos)
|
|
when Sub == both orelse Sub == from ->
|
|
To = jid:make(LJID),
|
|
P = xmpp:set_to(Pres, jid:make(LJID)),
|
|
case privacy_check_packet(State, P, out) of
|
|
allow -> [To|Tos];
|
|
deny -> Tos
|
|
end;
|
|
(_, Tos) ->
|
|
Tos
|
|
end, [BareJID], Items2),
|
|
route_multiple(State, JIDs, Pres),
|
|
State#{pres_a => ?SETS:new()}.
|
|
|
|
-spec broadcast_presence_available(state(), presence(), boolean()) -> state().
|
|
broadcast_presence_available(#{jid := JID} = State,
|
|
Pres, _FromUnavailable = true) ->
|
|
Probe = #presence{from = JID, type = probe},
|
|
#jid{luser = LUser, lserver = LServer} = JID,
|
|
BareJID = jid:remove_resource(JID),
|
|
Items = ejabberd_hooks:run_fold(roster_get, LServer,
|
|
[], [{LUser, LServer}]),
|
|
{FJIDs, TJIDs} =
|
|
lists:foldl(
|
|
fun(#roster{jid = LJID, subscription = Sub}, {F, T}) ->
|
|
To = jid:make(LJID),
|
|
F1 = if Sub == both orelse Sub == from ->
|
|
Pres1 = xmpp:set_to(Pres, To),
|
|
case privacy_check_packet(State, Pres1, out) of
|
|
allow -> [To|F];
|
|
deny -> F
|
|
end;
|
|
true -> F
|
|
end,
|
|
T1 = if Sub == both orelse Sub == to ->
|
|
Probe1 = xmpp:set_to(Probe, To),
|
|
case privacy_check_packet(State, Probe1, out) of
|
|
allow -> [To|T];
|
|
deny -> T
|
|
end;
|
|
true -> T
|
|
end,
|
|
{F1, T1}
|
|
end, {[BareJID], [BareJID]}, Items),
|
|
route_multiple(State, TJIDs, Probe),
|
|
route_multiple(State, FJIDs, Pres),
|
|
State;
|
|
broadcast_presence_available(#{jid := JID} = State,
|
|
Pres, _FromUnavailable = false) ->
|
|
#jid{luser = LUser, lserver = LServer} = JID,
|
|
BareJID = jid:remove_resource(JID),
|
|
Items = ejabberd_hooks:run_fold(
|
|
roster_get, LServer, [], [{LUser, LServer}]),
|
|
JIDs = lists:foldl(
|
|
fun(#roster{jid = LJID, subscription = Sub}, Tos)
|
|
when Sub == both orelse Sub == from ->
|
|
To = jid:make(LJID),
|
|
P = xmpp:set_to(Pres, jid:make(LJID)),
|
|
case privacy_check_packet(State, P, out) of
|
|
allow -> [To|Tos];
|
|
deny -> Tos
|
|
end;
|
|
(_, Tos) ->
|
|
Tos
|
|
end, [BareJID], Items),
|
|
route_multiple(State, JIDs, Pres),
|
|
State.
|
|
|
|
-spec check_privacy_then_route(state(), stanza()) -> state().
|
|
check_privacy_then_route(#{lang := Lang} = State, Pkt) ->
|
|
case privacy_check_packet(State, Pkt, out) of
|
|
deny ->
|
|
ErrText = <<"Your active privacy list has denied "
|
|
"the routing of this stanza.">>,
|
|
Err = xmpp:err_not_acceptable(ErrText, Lang),
|
|
send_error(State, Pkt, Err);
|
|
allow ->
|
|
ejabberd_router:route(Pkt),
|
|
State
|
|
end.
|
|
|
|
-spec privacy_check_packet(state(), stanza(), in | out) -> allow | deny.
|
|
privacy_check_packet(#{lserver := LServer} = State, Pkt, Dir) ->
|
|
ejabberd_hooks:run_fold(privacy_check_packet, LServer, allow, [State, Pkt, Dir]).
|
|
|
|
-spec get_priority_from_presence(presence()) -> integer().
|
|
get_priority_from_presence(#presence{priority = Prio}) ->
|
|
case Prio of
|
|
undefined -> 0;
|
|
_ -> Prio
|
|
end.
|
|
|
|
-spec route_multiple(state(), [jid()], stanza()) -> ok.
|
|
route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
|
|
From = xmpp:get_from(Pkt),
|
|
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
|
|
|
|
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
|
|
{Subscription, _} = ejabberd_hooks:run_fold(
|
|
roster_get_jid_info, LServer, {none, []},
|
|
[LUser, LServer, JID]),
|
|
Subscription.
|
|
|
|
-spec resource_conflict_action(binary(), binary(), binary()) ->
|
|
{accept_resource, binary()} | closenew.
|
|
resource_conflict_action(U, S, R) ->
|
|
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
|
|
true ->
|
|
ejabberd_config:get_option(
|
|
{resource_conflict, S}, acceptnew);
|
|
false ->
|
|
acceptnew
|
|
end,
|
|
Option = case OptionRaw of
|
|
setresource -> setresource;
|
|
closeold -> acceptnew; %% ejabberd_sm will close old session
|
|
closenew -> closenew;
|
|
acceptnew -> acceptnew
|
|
end,
|
|
case Option of
|
|
acceptnew -> {accept_resource, R};
|
|
closenew -> closenew;
|
|
setresource ->
|
|
Rnew = new_uniq_id(),
|
|
{accept_resource, Rnew}
|
|
end.
|
|
|
|
-spec bounce_message_queue() -> ok.
|
|
bounce_message_queue() ->
|
|
receive {route, Pkt} ->
|
|
ejabberd_router:route(Pkt),
|
|
bounce_message_queue()
|
|
after 0 ->
|
|
ok
|
|
end.
|
|
|
|
-spec new_uniq_id() -> binary().
|
|
new_uniq_id() ->
|
|
iolist_to_binary(
|
|
[randoms:get_string(),
|
|
integer_to_binary(p1_time_compat:unique_integer([positive]))]).
|
|
|
|
-spec get_conn_type(state()) -> c2s | c2s_tls | c2s_compressed | websocket |
|
|
c2s_compressed_tls | http_bind.
|
|
get_conn_type(State) ->
|
|
case xmpp_stream_in:get_transport(State) of
|
|
tcp -> c2s;
|
|
tls -> c2s_tls;
|
|
tcp_zlib -> c2s_compressed;
|
|
tls_zlib -> c2s_compressed_tls;
|
|
http_bind -> http_bind;
|
|
websocket -> websocket
|
|
end.
|
|
|
|
-spec fix_from_to(xmpp_element(), state()) -> stanza().
|
|
fix_from_to(Pkt, #{jid := JID}) when ?is_stanza(Pkt) ->
|
|
#jid{luser = U, lserver = S, lresource = R} = JID,
|
|
case xmpp:get_from(Pkt) of
|
|
undefined ->
|
|
Pkt;
|
|
From ->
|
|
From1 = case jid:tolower(From) of
|
|
{U, S, R} -> JID;
|
|
{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
|
|
_ -> From
|
|
end,
|
|
xmpp:set_from_to(Pkt, From1, JID)
|
|
end;
|
|
fix_from_to(Pkt, _State) ->
|
|
Pkt.
|
|
|
|
-spec change_shaper(state()) -> state().
|
|
change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
|
|
user := U, server := S, resource := R} = State) ->
|
|
JID = jid:make(U, S, R),
|
|
Shaper = acl:access_matches(ShaperName,
|
|
#{usr => jid:split(JID), ip => IP},
|
|
LServer),
|
|
xmpp_stream_in:change_shaper(State, Shaper).
|
|
|
|
-spec format_reason(state(), term()) -> binary().
|
|
format_reason(#{stop_reason := Reason}, _) ->
|
|
xmpp_stream_in:format_error(Reason);
|
|
format_reason(_, normal) ->
|
|
<<"unknown reason">>;
|
|
format_reason(_, shutdown) ->
|
|
<<"stopped by supervisor">>;
|
|
format_reason(_, {shutdown, _}) ->
|
|
<<"stopped by supervisor">>;
|
|
format_reason(_, _) ->
|
|
<<"internal server error">>.
|
|
|
|
-spec get_certfile(binary()) -> file:filename_all().
|
|
get_certfile(LServer) ->
|
|
case ejabberd_pkix:get_certfile(LServer) of
|
|
{ok, CertFile} ->
|
|
CertFile;
|
|
error ->
|
|
ejabberd_config:get_option(
|
|
{domain_certfile, LServer},
|
|
ejabberd_config:get_option({c2s_certfile, LServer}))
|
|
end.
|
|
|
|
transform_listen_option(Opt, Opts) ->
|
|
[Opt|Opts].
|
|
|
|
-type resource_conflict() :: setresource | closeold | closenew | acceptnew.
|
|
-spec opt_type(c2s_ciphers) -> fun((binary()) -> binary());
|
|
(c2s_dhfile) -> fun((binary()) -> binary());
|
|
(c2s_cafile) -> fun((binary()) -> binary());
|
|
(c2s_protocol_options) -> fun(([binary()]) -> binary());
|
|
(c2s_tls_compression) -> fun((boolean()) -> boolean());
|
|
(resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
|
|
(disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
|
|
(atom()) -> [atom()].
|
|
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
|
|
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
|
|
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
|
|
opt_type(c2s_protocol_options) ->
|
|
fun (Options) -> str:join(Options, <<"|">>) end;
|
|
opt_type(c2s_tls_compression) ->
|
|
fun (true) -> true;
|
|
(false) -> false
|
|
end;
|
|
opt_type(resource_conflict) ->
|
|
fun (setresource) -> setresource;
|
|
(closeold) -> closeold;
|
|
(closenew) -> closenew;
|
|
(acceptnew) -> acceptnew
|
|
end;
|
|
opt_type(disable_sasl_mechanisms) ->
|
|
fun (V) when is_list(V) ->
|
|
lists:map(fun (M) -> str:to_upper(M) end, V);
|
|
(V) -> [str:to_upper(V)]
|
|
end;
|
|
opt_type(_) ->
|
|
[c2s_ciphers, c2s_cafile, c2s_dhfile,
|
|
c2s_protocol_options, c2s_tls_compression, resource_conflict,
|
|
disable_sasl_mechanisms].
|
|
|
|
-spec listen_opt_type(access) -> fun((any()) -> any());
|
|
(shaper) -> fun((any()) -> any());
|
|
(certfile) -> fun((binary()) -> binary());
|
|
(ciphers) -> fun((binary()) -> binary());
|
|
(dhfile) -> fun((binary()) -> binary());
|
|
(cafile) -> fun((binary()) -> binary());
|
|
(protocol_options) -> fun(([binary()]) -> binary());
|
|
(tls_compression) -> fun((boolean()) -> boolean());
|
|
(tls) -> fun((boolean()) -> boolean());
|
|
(starttls) -> fun((boolean()) -> boolean());
|
|
(tls_verify) -> fun((boolean()) -> boolean());
|
|
(zlib) -> fun((boolean()) -> boolean());
|
|
(supervisor) -> fun((boolean()) -> boolean());
|
|
(max_stanza_size) -> fun((timeout()) -> timeout());
|
|
(max_fsm_queue) -> fun((timeout()) -> timeout());
|
|
(stream_management) -> fun((boolean()) -> boolean());
|
|
(atom()) -> [atom()].
|
|
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
|
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
|
listen_opt_type(certfile = Opt) ->
|
|
fun(S) ->
|
|
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
|
|
"'certfiles' global option instead", [Opt, ?MODULE]),
|
|
ejabberd_pkix:add_certfile(S),
|
|
iolist_to_binary(S)
|
|
end;
|
|
listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
|
|
listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
|
|
listen_opt_type(cafile) -> opt_type(c2s_cafile);
|
|
listen_opt_type(protocol_options) -> opt_type(c2s_protocol_options);
|
|
listen_opt_type(tls_compression) -> opt_type(c2s_tls_compression);
|
|
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(max_stanza_size) ->
|
|
fun(I) when is_integer(I), I>0 -> I;
|
|
(unlimited) -> infinity;
|
|
(infinity) -> infinity
|
|
end;
|
|
listen_opt_type(max_fsm_queue) ->
|
|
fun(I) when is_integer(I), I>0 -> I end;
|
|
%% The following hack should be removed in future releases: it is intended
|
|
%% for backward compatibility with ejabberd 17.01 or older
|
|
listen_opt_type(stream_management) ->
|
|
?WARNING_MSG("listening option 'stream_management' is deprecated: "
|
|
"use mod_stream_mgmt module", []),
|
|
fun(B) when is_boolean(B) -> B end;
|
|
listen_opt_type(O) ->
|
|
case mod_stream_mgmt:mod_opt_type(O) of
|
|
L when is_list(L) ->
|
|
[access, shaper, certfile, ciphers, dhfile, cafile,
|
|
protocol_options, tls, tls_compression, starttls,
|
|
starttls_required, tls_verify, zlib, max_fsm_queue];
|
|
VFun ->
|
|
?WARNING_MSG("listening option '~s' is deprecated: use '~s' "
|
|
"option from mod_stream_mgmt module", [O, O]),
|
|
VFun
|
|
end.
|