1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-03 01:39:35 +02:00
This commit is contained in:
badlop 2025-09-03 14:27:18 +00:00 committed by GitHub
commit f07d080b37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
383 changed files with 87051 additions and 59713 deletions

11
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,11 @@
# .git-blame-ignore-revs
# Use this file when viewing blame:
# git blame --ignore-revs-file .git-blame-ignore-revs
# Or configure git to use always this file:
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# Accumulated patch to binarize and indent code
9deb294328bb3f9eb6bd2c0e7cd500732e9b5830
# Result of running "make format indent" for the first time
6b7d15f0271686b6902a0edc82f407e529b35a90

View file

@ -13,6 +13,7 @@ SED = @SED@
ERL = @ERL@ ERL = @ERL@
EPMD = @EPMD@ EPMD = @EPMD@
IEX = @IEX@ IEX = @IEX@
EMACS = @EMACS@
INSTALLUSER=@INSTALLUSER@ INSTALLUSER=@INSTALLUSER@
INSTALLGROUP=@INSTALLGROUP@ INSTALLGROUP=@INSTALLGROUP@
@ -266,11 +267,15 @@ _build/edoc/logo.png: edoc_compile
#' format / indent #' format / indent
# #
.SILENT: format indent
FORMAT_LOG=/tmp/ejabberd-format.log
format: format:
tools/rebar3-format.sh $(REBAR3) tools/rebar3-format.sh $(FORMAT_LOG) $(REBAR3)
indent: indent:
tools/emacs-indent.sh tools/emacs-indent.sh $(FORMAT_LOG) $(EMACS)
#. #.
#' copy-files #' copy-files
@ -714,7 +719,7 @@ help:
@echo " translations Extract translation files" @echo " translations Extract translation files"
@echo " TAGS Generate tags file for text editors" @echo " TAGS Generate tags file for text editors"
@echo "" @echo ""
@echo " format Format source code using rebar3_format" @echo " format Format source code using efmt [rebar3]"
@echo " indent Indent source code using erlang-mode [emacs]" @echo " indent Indent source code using erlang-mode [emacs]"
@echo "" @echo ""
@echo " dialyzer Run Dialyzer static analyzer" @echo " dialyzer Run Dialyzer static analyzer"

View file

@ -1,2 +1,2 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, []}. {deps, []}.

View file

@ -3,7 +3,6 @@
{vsn, "0.0.1"}, {vsn, "0.0.1"},
{registered, []}, {registered, []},
{applications, [kernel, stdlib]}, {applications, [kernel, stdlib]},
{env,[]}, {env, []},
{modules, []}, {modules, []},
{links, []} {links, []}]}.
]}.

View file

@ -2,6 +2,7 @@
-export([init/1]). -export([init/1]).
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) -> init(State) ->
{ok, State1} = configure_deps_prv:init(State), {ok, State1} = configure_deps_prv:init(State),

View file

@ -3,24 +3,24 @@
-export([init/1, do/1, format_error/1]). -export([init/1, do/1, format_error/1]).
-define(PROVIDER, 'configure-deps'). -define(PROVIDER, 'configure-deps').
-define(DEPS, [install_deps]). -define(DEPS, [install_deps]).
%% =================================================================== %% ===================================================================
%% Public API %% Public API
%% =================================================================== %% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) -> init(State) ->
Provider = providers:create([ Provider = providers:create(
{namespace, default}, [{namespace, default},
{name, ?PROVIDER}, % The 'user friendly' name of the task {name, ?PROVIDER}, % The 'user friendly' name of the task
{module, ?MODULE}, % The module implementation of the task {module, ?MODULE}, % The module implementation of the task
{bare, true}, % The task can be run by the user, always true {bare, true}, % The task can be run by the user, always true
{deps, ?DEPS}, % The list of dependencies {deps, ?DEPS}, % The list of dependencies
{example, "rebar3 configure-deps"}, % How to use the plugin {example, "rebar3 configure-deps"}, % How to use the plugin
{opts, []}, % list of options understood by the plugin {opts, []}, % list of options understood by the plugin
{short_desc, "Explicitly run ./configure for dependencies"}, {short_desc, "Explicitly run ./configure for dependencies"},
{desc, "A rebar plugin to allow explicitly running ./configure on dependencies. Useful if dependencies might change prior to compilation when configure is run."} {desc, "A rebar plugin to allow explicitly running ./configure on dependencies. Useful if dependencies might change prior to compilation when configure is run."}]),
]),
{ok, rebar_state:add_provider(State, Provider)}. {ok, rebar_state:add_provider(State, Provider)}.
@ -30,25 +30,30 @@ do(State) ->
lists:foreach(fun do_app/1, Apps), lists:foreach(fun do_app/1, Apps),
{ok, State}. {ok, State}.
exec_configure({'configure-deps', Cmd}, Dir) -> exec_configure({'configure-deps', Cmd}, Dir) ->
rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, true}]); rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, true}]);
exec_configure(_, Acc) -> Acc. exec_configure(_, Acc) -> Acc.
parse_pre_hooks({pre_hooks, PreHooks}, Acc) -> parse_pre_hooks({pre_hooks, PreHooks}, Acc) ->
lists:foldl(fun exec_configure/2, Acc, PreHooks); lists:foldl(fun exec_configure/2, Acc, PreHooks);
parse_pre_hooks(_, Acc) -> Acc. parse_pre_hooks(_, Acc) -> Acc.
parse_additions({add, App, Additions}, {MyApp, Dir}) when App == MyApp -> parse_additions({add, App, Additions}, {MyApp, Dir}) when App == MyApp ->
lists:foldl(fun parse_pre_hooks/2, Dir, Additions), lists:foldl(fun parse_pre_hooks/2, Dir, Additions),
{MyApp, Dir}; {MyApp, Dir};
parse_additions(_, Acc) -> Acc. parse_additions(_, Acc) -> Acc.
do_app(App) -> do_app(App) ->
Dir = rebar_app_info:dir(App), Dir = rebar_app_info:dir(App),
Opts = rebar_app_info:opts(App), Opts = rebar_app_info:opts(App),
Overrides = rebar_opts:get(Opts, overrides), Overrides = rebar_opts:get(Opts, overrides),
lists:foldl(fun parse_additions/2, {binary_to_atom(rebar_app_info:name(App), utf8), Dir}, Overrides). lists:foldl(fun parse_additions/2, {binary_to_atom(rebar_app_info:name(App), utf8), Dir}, Overrides).
-spec format_error(any()) -> iolist().
-spec format_error(any()) -> iolist().
format_error(Reason) -> format_error(Reason) ->
io_lib:format("~p", [Reason]). io_lib:format("~p", [Reason]).

View file

@ -88,6 +88,8 @@ AC_PATH_PROG([ESCRIPT], [escript], [], [$ERLANG_ROOT_DIR/bin])
#locating make #locating make
AC_CHECK_PROG([MAKE], [make], [make], []) AC_CHECK_PROG([MAKE], [make], [make], [])
AC_PATH_TOOL(EMACS, emacs, , [${extra_erl_path}$PATH])
if test "x$ESCRIPT" = "x"; then if test "x$ESCRIPT" = "x"; then
AC_MSG_ERROR(['escript' was not found]) AC_MSG_ERROR(['escript' was not found])
fi fi

View file

@ -3,79 +3,101 @@
%% SEQUENCE and SET, and macro definitions for each value %% SEQUENCE and SET, and macro definitions for each value
%% definition,in module ELDAPv3 %% definition,in module ELDAPv3
-record('LDAPMessage', {
messageID, protocolOp, controls = asn1_NOVALUE
}).
-record('AttributeValueAssertion', {
attributeDesc, assertionValue
}).
-record('LDAPMessage',{ -record('Attribute', {
messageID, protocolOp, controls = asn1_NOVALUE}). type, vals
}).
-record('AttributeValueAssertion',{ -record('LDAPResult', {
attributeDesc, assertionValue}). resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE
}).
-record('Attribute',{ -record('Control', {
type, vals}). controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE
}).
-record('LDAPResult',{ -record('BindRequest', {
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE}). version, name, authentication
}).
-record('Control',{ -record('SaslCredentials', {
controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE}). mechanism, credentials = asn1_NOVALUE
}).
-record('BindRequest',{ -record('BindResponse', {
version, name, authentication}). resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE
}).
-record('SaslCredentials',{ -record('SearchRequest', {
mechanism, credentials = asn1_NOVALUE}). baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes
}).
-record('BindResponse',{ -record('SubstringFilter', {
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE}). type, substrings
}).
-record('SearchRequest',{ -record('MatchingRuleAssertion', {
baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes}). matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT
}).
-record('SubstringFilter',{ -record('SearchResultEntry', {
type, substrings}). objectName, attributes
}).
-record('MatchingRuleAssertion',{ -record('PartialAttributeList_SEQOF', {
matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT}). type, vals
}).
-record('SearchResultEntry',{ -record('ModifyRequest', {
objectName, attributes}). object, modification
}).
-record('PartialAttributeList_SEQOF',{ -record('ModifyRequest_modification_SEQOF', {
type, vals}). operation, modification
}).
-record('ModifyRequest',{ -record('AttributeTypeAndValues', {
object, modification}). type, vals
}).
-record('ModifyRequest_modification_SEQOF',{ -record('AddRequest', {
operation, modification}). entry, attributes
}).
-record('AttributeTypeAndValues',{ -record('AttributeList_SEQOF', {
type, vals}). type, vals
}).
-record('AddRequest',{ -record('ModifyDNRequest', {
entry, attributes}). entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE
}).
-record('AttributeList_SEQOF',{ -record('CompareRequest', {
type, vals}). entry, ava
}).
-record('ModifyDNRequest',{ -record('ExtendedRequest', {
entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE}). requestName, requestValue = asn1_NOVALUE
}).
-record('CompareRequest',{ -record('ExtendedResponse', {
entry, ava}). resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE
}).
-record('ExtendedRequest',{ -record('PasswdModifyRequestValue', {
requestName, requestValue = asn1_NOVALUE}). userIdentity = asn1_NOVALUE, oldPasswd = asn1_NOVALUE, newPasswd = asn1_NOVALUE
}).
-record('ExtendedResponse',{ -record('PasswdModifyResponseValue', {
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE}). genPasswd = asn1_NOVALUE
}).
-record('PasswdModifyRequestValue',{ -define('maxInt', 2147483647).
userIdentity = asn1_NOVALUE, oldPasswd = asn1_NOVALUE, newPasswd = asn1_NOVALUE}). -define('passwdModifyOID', [49, 46, 51, 46, 54, 46, 49, 46, 52, 46, 49, 46, 52, 50, 48, 51, 46, 49, 46, 49, 49, 46, 49]).
-record('PasswdModifyResponseValue',{
genPasswd = asn1_NOVALUE}).
-define('maxInt', 2147483647).
-define('passwdModifyOID', [49,46,51,46,54,46,49,46,52,46,49,46,52,50,48,51,46,49,46,49,49,46,49]).

View file

@ -19,33 +19,36 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-define(CT_XML, -define(CT_XML,
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
-define(CT_PLAIN, -define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}). {<<"Content-Type">>, <<"text/plain">>}).
-define(CT_JSON, -define(CT_JSON,
{<<"Content-Type">>, <<"application/json">>}). {<<"Content-Type">>, <<"application/json">>}).
-define(AC_ALLOW_ORIGIN, -define(AC_ALLOW_ORIGIN,
{<<"Access-Control-Allow-Origin">>, <<"*">>}). {<<"Access-Control-Allow-Origin">>, <<"*">>}).
-define(AC_ALLOW_METHODS, -define(AC_ALLOW_METHODS,
{<<"Access-Control-Allow-Methods">>, {<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}). <<"GET, POST, OPTIONS">>}).
-define(AC_ALLOW_HEADERS, -define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>, {<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}). <<"Content-Type">>}).
-define(AC_MAX_AGE, -define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}). {<<"Access-Control-Max-Age">>, <<"86400">>}).
-define(OPTIONS_HEADER, -define(OPTIONS_HEADER,
[?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, [?CT_PLAIN,
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). ?AC_ALLOW_ORIGIN,
?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS,
?AC_MAX_AGE]).
-define(HEADER(CType), -define(HEADER(CType),
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]). [CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
-define(BOSH_CACHE, bosh_cache). -define(BOSH_CACHE, bosh_cache).

View file

@ -18,5 +18,7 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | {binary(), binary(), atom()} | '$1', -record(passwd, {
password = <<"">> :: binary() | scram() | '_'}). us = {<<"">>, <<"">>} :: {binary(), binary()} | {binary(), binary(), atom()} | '$1',
password = <<"">> :: binary() | scram() | '_'
}).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-type aterm() :: {atom(), atype()}. -type aterm() :: {atom(), atype()}.
-type atype() :: integer | string | binary | any | atom | -type atype() :: integer | string | binary | any | atom |
@ -104,4 +106,3 @@
args_example :: none | [any()] | '_', args_example :: none | [any()] | '_',
result_example :: any() result_example :: any()
}. }.

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(request, -record(request,
{method :: method(), {method :: method(),

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(oauth_token, { -record(oauth_token, {
token = <<"">> :: binary() | '_', token = <<"">> :: binary() | '_',

View file

@ -1,3 +1,6 @@
%% @efmt:off
%% @indent-begin
-define(ROUTES_CACHE, routes_cache). -define(ROUTES_CACHE, routes_cache).
-type local_hint() :: integer() | {apply, atom(), atom()}. -type local_hint() :: integer() | {apply, atom(), atom()}.

View file

@ -27,10 +27,13 @@
-record(session_counter, {vhost, count}). -record(session_counter, {vhost, count}).
-type sid() :: {erlang:timestamp(), pid()}. -type sid() :: {erlang:timestamp(), pid()}.
-type ip() :: {inet:ip_address(), inet:port_number()} | undefined. -type ip() :: {inet:ip_address(), inet:port_number()} | undefined.
-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()} -type info() :: [{conn, atom()} |
| {oor, boolean()} | {auth_module, atom()} {ip, ip()} |
| {num_stanzas_in, non_neg_integer()} {node, atom()} |
| {atom(), term()}]. {oor, boolean()} |
{auth_module, atom()} |
{num_stanzas_in, non_neg_integer()} |
{atom(), term()}].
-type prio() :: undefined | integer(). -type prio() :: undefined | integer().
-endif. -endif.

View file

@ -30,46 +30,62 @@
-define(SQL_INSERT(Table, Fields), ?SQL_INSERT_MARK(Table, Fields)). -define(SQL_INSERT(Table, Fields), ?SQL_INSERT_MARK(Table, Fields)).
-ifdef(COMPILER_REPORTS_ONLY_LINES). -ifdef(COMPILER_REPORTS_ONLY_LINES).
-record(sql_query, {hash :: binary(), -record(sql_query, {
format_query :: fun(), hash :: binary(),
format_res :: fun(), format_query :: fun(),
args :: fun(), format_res :: fun(),
flags :: non_neg_integer(), args :: fun(),
loc :: {module(), pos_integer()}}). flags :: non_neg_integer(),
loc :: {module(), pos_integer()}
}).
-else. -else.
-record(sql_query, {hash :: binary(), -record(sql_query, {
format_query :: fun(), hash :: binary(),
format_res :: fun(), format_query :: fun(),
args :: fun(), format_res :: fun(),
flags :: non_neg_integer(), args :: fun(),
loc :: {module(), {pos_integer(), pos_integer()}}}). flags :: non_neg_integer(),
loc :: {module(), {pos_integer(), pos_integer()}}
}).
-endif. -endif.
-record(sql_escape, {string :: fun((binary()) -> binary()), -record(sql_escape, {
integer :: fun((integer()) -> binary()), string :: fun((binary()) -> binary()),
boolean :: fun((boolean()) -> binary()), integer :: fun((integer()) -> binary()),
in_array_string :: fun((binary()) -> binary()), boolean :: fun((boolean()) -> binary()),
like_escape :: fun(() -> binary())}). in_array_string :: fun((binary()) -> binary()),
like_escape :: fun(() -> binary())
}).
-record(sql_index, {
columns,
unique = false :: boolean(),
meta = #{}
}).
-record(sql_column, {
name :: binary(),
type,
default = false,
opts = []
}).
-record(sql_table, {
name :: binary(),
columns :: [#sql_column{}],
indices = [] :: [#sql_index{}],
post_create
}).
-record(sql_schema, {
version :: integer(),
tables :: [#sql_table{}],
update = []
}).
-record(sql_references, {
table :: binary(),
column :: binary()
}).
-record(sql_index, {columns, -record(sql_schema_info, {
unique = false :: boolean(), db_type :: pgsql | mysql | sqlite,
meta = #{}}). db_version :: any(),
-record(sql_column, {name :: binary(), new_schema = true :: boolean()
type, }).
default = false,
opts = []}).
-record(sql_table, {name :: binary(),
columns :: [#sql_column{}],
indices = [] :: [#sql_index{}],
post_create}).
-record(sql_schema, {version :: integer(),
tables :: [#sql_table{}],
update = []}).
-record(sql_references, {table :: binary(),
column :: binary()}).
-record(sql_schema_info,
{db_type :: pgsql | mysql | sqlite,
db_version :: any(),
new_schema = true :: boolean()}).

View file

@ -19,35 +19,35 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-define(X(Name), -define(X(Name),
#xmlel{name = Name, attrs = [], children = []}). #xmlel{name = Name, attrs = [], children = []}).
-define(XA(Name, Attrs), -define(XA(Name, Attrs),
#xmlel{name = Name, attrs = Attrs, children = []}). #xmlel{name = Name, attrs = Attrs, children = []}).
-define(XE(Name, Els), -define(XE(Name, Els),
#xmlel{name = Name, attrs = [], children = Els}). #xmlel{name = Name, attrs = [], children = Els}).
-define(XAE(Name, Attrs, Els), -define(XAE(Name, Attrs, Els),
#xmlel{name = Name, attrs = Attrs, children = Els}). #xmlel{name = Name, attrs = Attrs, children = Els}).
-define(C(Text), {xmlcdata, Text}). -define(C(Text), {xmlcdata, Text}).
-define(XC(Name, Text), ?XE(Name, [?C(Text)])). -define(XC(Name, Text), ?XE(Name, [?C(Text)])).
-define(XAC(Name, Attrs, Text), -define(XAC(Name, Attrs, Text),
?XAE(Name, Attrs, [?C(Text)])). ?XAE(Name, Attrs, [?C(Text)])).
-define(CT(Text), ?C((translate:translate(Lang, Text)))). -define(CT(Text), ?C((translate:translate(Lang, Text)))).
-define(XCT(Name, Text), ?XC(Name, (translate:translate(Lang, Text)))). -define(XCT(Name, Text), ?XC(Name, (translate:translate(Lang, Text)))).
-define(XACT(Name, Attrs, Text), -define(XACT(Name, Attrs, Text),
?XAC(Name, Attrs, (translate:translate(Lang, Text)))). ?XAC(Name, Attrs, (translate:translate(Lang, Text)))).
-define(LI(Els), ?XE(<<"li">>, Els)). -define(LI(Els), ?XE(<<"li">>, Els)).
-define(A(URL, Els), -define(A(URL, Els),
?XAE(<<"a">>, [{<<"href">>, URL}], Els)). ?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
-define(AC(URL, Text), ?A(URL, [?C(Text)])). -define(AC(URL, Text), ?A(URL, [?C(Text)])).
@ -58,69 +58,79 @@
-define(BR, ?X(<<"br">>)). -define(BR, ?X(<<"br">>)).
-define(INPUT(Type, Name, Value), -define(INPUT(Type, Name, Value),
?XA(<<"input">>, ?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name}, [{<<"type">>, Type},
{<<"value">>, Value}])). {<<"name">>, Name},
{<<"value">>, Value}])).
-define(INPUTPH(Type, Name, Value, PlaceHolder), -define(INPUTPH(Type, Name, Value, PlaceHolder),
?XA(<<"input">>, ?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name}, [{<<"type">>, Type},
{<<"value">>, Value}, {<<"placeholder">>, PlaceHolder}])). {<<"name">>, Name},
{<<"value">>, Value},
{<<"placeholder">>, PlaceHolder}])).
-define(INPUTT(Type, Name, Value), -define(INPUTT(Type, Name, Value),
?INPUT(Type, Name, (translate:translate(Lang, Value)))). ?INPUT(Type, Name, (translate:translate(Lang, Value)))).
-define(INPUTD(Type, Name, Value), -define(INPUTD(Type, Name, Value),
?XA(<<"input">>, ?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name}, [{<<"type">>, Type},
{<<"class">>, <<"btn-danger">>}, {<<"value">>, Value}])). {<<"name">>, Name},
{<<"class">>, <<"btn-danger">>},
{<<"value">>, Value}])).
-define(INPUTTD(Type, Name, Value), -define(INPUTTD(Type, Name, Value),
?INPUTD(Type, Name, (translate:translate(Lang, Value)))). ?INPUTD(Type, Name, (translate:translate(Lang, Value)))).
-define(INPUTS(Type, Name, Value, Size), -define(INPUTS(Type, Name, Value, Size),
?XA(<<"input">>, ?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name}, [{<<"type">>, Type},
{<<"value">>, Value}, {<<"size">>, Size}])). {<<"name">>, Name},
{<<"value">>, Value},
{<<"size">>, Size}])).
-define(INPUTST(Type, Name, Value, Size), -define(INPUTST(Type, Name, Value, Size),
?INPUT(Type, Name, (translate:translate(Lang, Value)), Size)). ?INPUT(Type, Name, (translate:translate(Lang, Value)), Size)).
-define(ACLINPUT(Text), -define(ACLINPUT(Text),
?XE(<<"td">>, ?XE(<<"td">>,
[?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])). [?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
-define(TEXTAREA(Name, Rows, Cols, Value), -define(TEXTAREA(Name, Rows, Cols, Value),
?XAC(<<"textarea">>, ?XAC(<<"textarea">>,
[{<<"name">>, Name}, {<<"rows">>, Rows}, [{<<"name">>, Name},
{<<"cols">>, Cols}], {<<"rows">>, Rows},
Value)). {<<"cols">>, Cols}],
Value)).
%% Build an xmlelement for result %% Build an xmlelement for result
-define(XRES(Text), -define(XRES(Text),
?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)). ?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
-define(DIVRES(Elements), -define(DIVRES(Elements),
?XAE(<<"div">>, [{<<"class">>, <<"result">>}], Elements)). ?XAE(<<"div">>, [{<<"class">>, <<"result">>}], Elements)).
%% Guide Link %% Guide Link
-define(XREST(Text), ?XRES((translate:translate(Lang, Text)))). -define(XREST(Text), ?XRES((translate:translate(Lang, Text)))).
-define(GL(Ref, Title), -define(GL(Ref, Title),
?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}], ?XAE(<<"div">>,
[?XAE(<<"a">>, [{<<"class">>, <<"guidelink">>}],
[{<<"href">>, <<"https://docs.ejabberd.im/", Ref/binary>>}, [?XAE(<<"a">>,
{<<"target">>, <<"_blank">>}], [{<<"href">>, <<"https://docs.ejabberd.im/", Ref/binary>>},
[?C(<<"docs: ", Title/binary>>)])])). {<<"target">>, <<"_blank">>}],
[?C(<<"docs: ", Title/binary>>)])])).
%% h1 with a Guide Link %% h1 with a Guide Link
-define(H1GLraw(Name, Ref, Title), -define(H1GLraw(Name, Ref, Title),
[?XC(<<"h1">>, Name), ?GL(Ref, Title), ?BR, ?BR]). [?XC(<<"h1">>, Name), ?GL(Ref, Title), ?BR, ?BR]).
-define(H1GL(Name, RefConf, Title), -define(H1GL(Name, RefConf, Title),
?H1GLraw(Name, <<"admin/configuration/", RefConf/binary>>, Title)). ?H1GLraw(Name, <<"admin/configuration/", RefConf/binary>>, Title)).
-define(ANCHORL(Ref), -define(ANCHORL(Ref),
?XAE(<<"div">>, [{<<"class">>, <<"anchorlink">>}], ?XAE(<<"div">>,
[?XAE(<<"a">>, [{<<"class">>, <<"anchorlink">>}],
[{<<"href">>, <<"#", Ref/binary>>}], [?XAE(<<"a">>,
[?C(unicode:characters_to_binary(""))])])). [{<<"href">>, <<"#", Ref/binary>>}],
[?C(unicode:characters_to_binary(""))])])).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-define(LDAP_PORT, 389). -define(LDAP_PORT, 389).

View file

@ -19,31 +19,34 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-define(CT_XML, -define(CT_XML,
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
-define(CT_PLAIN, -define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}). {<<"Content-Type">>, <<"text/plain">>}).
-define(AC_ALLOW_ORIGIN, -define(AC_ALLOW_ORIGIN,
{<<"Access-Control-Allow-Origin">>, <<"*">>}). {<<"Access-Control-Allow-Origin">>, <<"*">>}).
-define(AC_ALLOW_METHODS, -define(AC_ALLOW_METHODS,
{<<"Access-Control-Allow-Methods">>, {<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}). <<"GET, POST, OPTIONS">>}).
-define(AC_ALLOW_HEADERS, -define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>, {<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}). <<"Content-Type">>}).
-define(AC_MAX_AGE, -define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}). {<<"Access-Control-Max-Age">>, <<"86400">>}).
-define(NO_CACHE, -define(NO_CACHE,
{<<"Cache-Control">>, <<"max-age=0, no-cache, no-store">>}). {<<"Cache-Control">>, <<"max-age=0, no-cache, no-store">>}).
-define(OPTIONS_HEADER, -define(OPTIONS_HEADER,
[?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, [?CT_PLAIN,
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). ?AC_ALLOW_ORIGIN,
?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS,
?AC_MAX_AGE]).
-define(HEADER, -define(HEADER,
[?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS, ?NO_CACHE]). [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS, ?NO_CACHE]).

View file

@ -23,62 +23,87 @@
-compile([{parse_transform, lager_transform}]). -compile([{parse_transform, lager_transform}]).
-define(DEBUG(Format, Args), -define(DEBUG(Format, Args),
begin lager:debug(Format, Args), ok end). begin lager:debug(Format, Args), ok end).
-define(INFO_MSG(Format, Args), -define(INFO_MSG(Format, Args),
begin lager:info(Format, Args), ok end). begin lager:info(Format, Args), ok end).
-define(WARNING_MSG(Format, Args), -define(WARNING_MSG(Format, Args),
begin lager:warning(Format, Args), ok end). begin lager:warning(Format, Args), ok end).
-define(ERROR_MSG(Format, Args), -define(ERROR_MSG(Format, Args),
begin lager:error(Format, Args), ok end). begin lager:error(Format, Args), ok end).
-define(CRITICAL_MSG(Format, Args), -define(CRITICAL_MSG(Format, Args),
begin lager:critical(Format, Args), ok end). begin lager:critical(Format, Args), ok end).
-else. -else.
-include_lib("kernel/include/logger.hrl"). -include_lib("kernel/include/logger.hrl").
-define(CLEAD, "\e[1"). % bold -define(CLEAD, "\e[1"). % bold
-define(CMID, "\e[0"). % normal -define(CMID, "\e[0"). % normal
-define(CCLEAN, "\e[0m"). % clean -define(CCLEAN, "\e[0m"). % clean
-define(CDEFAULT, ";49;95m"). % light magenta -define(CDEFAULT, ";49;95m"). % light magenta
-define(CDEBUG, ";49;90m"). % dark gray -define(CDEBUG, ";49;90m"). % dark gray
-define(CINFO, ";49;92m"). % green -define(CINFO, ";49;92m"). % green
-define(CWARNING, ";49;93m"). % light yellow -define(CWARNING, ";49;93m"). % light yellow
-define(CERROR, ";49;91m"). % light magenta -define(CERROR, ";49;91m"). % light magenta
-define(CCRITICAL,";49;31m"). % light red -define(CCRITICAL, ";49;31m"). % light red
-define(DEBUG(Format, Args), -define(DEBUG(Format, Args),
begin ?LOG_DEBUG(Format, Args, begin
#{clevel => ?CLEAD ++ ?CDEBUG, ?LOG_DEBUG(Format,
ctext => ?CMID ++ ?CDEBUG}), Args,
ok end). #{
clevel => ?CLEAD ++ ?CDEBUG,
ctext => ?CMID ++ ?CDEBUG
}),
ok
end).
-define(INFO_MSG(Format, Args), -define(INFO_MSG(Format, Args),
begin ?LOG_INFO(Format, Args, begin
#{clevel => ?CLEAD ++ ?CINFO, ?LOG_INFO(Format,
ctext => ?CCLEAN}), Args,
ok end). #{
clevel => ?CLEAD ++ ?CINFO,
ctext => ?CCLEAN
}),
ok
end).
-define(WARNING_MSG(Format, Args), -define(WARNING_MSG(Format, Args),
begin ?LOG_WARNING(Format, Args, begin
#{clevel => ?CLEAD ++ ?CWARNING, ?LOG_WARNING(Format,
ctext => ?CMID ++ ?CWARNING}), Args,
ok end). #{
clevel => ?CLEAD ++ ?CWARNING,
ctext => ?CMID ++ ?CWARNING
}),
ok
end).
-define(ERROR_MSG(Format, Args), -define(ERROR_MSG(Format, Args),
begin ?LOG_ERROR(Format, Args, begin
#{clevel => ?CLEAD ++ ?CERROR, ?LOG_ERROR(Format,
ctext => ?CMID ++ ?CERROR}), Args,
ok end). #{
clevel => ?CLEAD ++ ?CERROR,
ctext => ?CMID ++ ?CERROR
}),
ok
end).
-define(CRITICAL_MSG(Format, Args), -define(CRITICAL_MSG(Format, Args),
begin ?LOG_CRITICAL(Format, Args, begin
#{clevel => ?CLEAD++ ?CCRITICAL, ?LOG_CRITICAL(Format,
ctext => ?CMID ++ ?CCRITICAL}), Args,
ok end). #{
clevel => ?CLEAD ++ ?CCRITICAL,
ctext => ?CMID ++ ?CCRITICAL
}),
ok
end).
-endif. -endif.
%% Use only when trying to troubleshoot test problem with ExUnit %% Use only when trying to troubleshoot test problem with ExUnit

View file

@ -18,8 +18,12 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(motd, {server = <<"">> :: binary(), -record(motd, {
packet = #xmlel{} :: xmlel()}). server = <<"">> :: binary(),
packet = #xmlel{} :: xmlel()
}).
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', -record(motd_users, {
dummy = [] :: [] | '_'}). us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
dummy = [] :: [] | '_'
}).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-define(MODULE_ANTISPAM, mod_antispam). -define(MODULE_ANTISPAM, mod_antispam).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(caps_features, -record(caps_features,
{node_pair = {<<"">>, <<"">>} :: {binary(), binary()}, {node_pair = {<<"">>, <<"">>} :: {binary(), binary()},

View file

@ -18,6 +18,8 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()}, -record(last_activity, {
timestamp = 0 :: non_neg_integer(), us = {<<"">>, <<"">>} :: {binary(), binary()},
status = <<"">> :: binary()}). timestamp = 0 :: non_neg_integer(),
status = <<"">> :: binary()
}).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(archive_msg, -record(archive_msg,
{us = {<<"">>, <<"">>} :: {binary(), binary()}, {us = {<<"">>, <<"">>} :: {binary(), binary()},

View file

@ -18,19 +18,19 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(room_version, -record(room_version, {
{id :: binary(), id :: binary(),
%% use the same field names as in Synapse %% use the same field names as in Synapse
enforce_key_validity :: boolean(), enforce_key_validity :: boolean(),
special_case_aliases_auth :: boolean(), special_case_aliases_auth :: boolean(),
strict_canonicaljson :: boolean(), strict_canonicaljson :: boolean(),
limit_notifications_power_levels :: boolean(), limit_notifications_power_levels :: boolean(),
knock_join_rule :: boolean(), knock_join_rule :: boolean(),
restricted_join_rule :: boolean(), restricted_join_rule :: boolean(),
restricted_join_rule_fix :: boolean(), restricted_join_rule_fix :: boolean(),
knock_restricted_join_rule :: boolean(), knock_restricted_join_rule :: boolean(),
enforce_int_power_levels :: boolean(), enforce_int_power_levels :: boolean(),
implicit_room_creator :: boolean(), implicit_room_creator :: boolean(),
updated_redaction_rules :: boolean(), updated_redaction_rules :: boolean(),
hydra :: boolean() hydra :: boolean()
}). }).

View file

@ -18,19 +18,25 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | -record(muc_room, {
{'_', binary()}, name_host = {<<"">>, <<"">>} :: {binary(), binary()} |
opts = [] :: list() | '_'}). {'_', binary()},
opts = [] :: list() | '_'
}).
-record(muc_registered, -record(muc_registered, {
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1', us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
nick = <<"">> :: binary()}). nick = <<"">> :: binary()
}).
-record(muc_online_room, -record(muc_online_room, {
{name_host :: {binary(), binary()} | '$1' | {'_', binary()} | '_', name_host :: {binary(), binary()} | '$1' | {'_', binary()} | '_',
pid :: pid() | '$2' | '_' | '$1'}). pid :: pid() | '$2' | '_' | '$1'
}).
-record(muc_online_users, {us :: {binary(), binary()}, -record(muc_online_users, {
resource :: binary() | '_', us :: {binary(), binary()},
room :: binary() | '_' | '$1', resource :: binary() | '_',
host :: binary() | '_' | '$2'}). room :: binary() | '_' | '$1',
host :: binary() | '_' | '$2'
}).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-define(MAX_USERS_DEFAULT, 200). -define(MAX_USERS_DEFAULT, 200).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(offline_msg, -record(offline_msg,
{us = {<<"">>, <<"">>} :: {binary(), binary()}, {us = {<<"">>, <<"">>} :: {binary(), binary()},

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()}, -record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()},
default = none :: none | binary(), default = none :: none | binary(),

View file

@ -18,7 +18,11 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(private_storage, -record(private_storage, {
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() | usns = {<<"">>, <<"">>, <<"">>} :: {binary(),
'$1' | '_'}, binary(),
xml = #xmlel{} :: xmlel() | '_' | '$1'}). binary() |
'$1' |
'_'},
xml = #xmlel{} :: xmlel() | '_' | '$1'
}).

View file

@ -68,6 +68,8 @@
%% RFC 1928 defined timeout. %% RFC 1928 defined timeout.
-define(SOCKS5_REPLY_TIMEOUT, 10000). -define(SOCKS5_REPLY_TIMEOUT, 10000).
-record(s5_request, {rsv = 0 :: integer(), -record(s5_request, {
cmd = connect :: connect | udp, rsv = 0 :: integer(),
sha1 = <<"">> :: binary()}). cmd = connect :: connect | udp,
sha1 = <<"">> :: binary()
}).

View file

@ -16,6 +16,9 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(push_session, -record(push_session,
{us = {<<"">>, <<"">>} :: {binary(), binary()}, {us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = erlang:timestamp() :: erlang:timestamp(), timestamp = erlang:timestamp() :: erlang:timestamp(),

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-record(roster, -record(roster,
{ {

View file

@ -18,8 +18,12 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()}, -record(sr_group, {
opts = [] :: list() | '_' | '$2'}). group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
opts = [] :: list() | '_' | '$2'
}).
-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()}, -record(sr_user, {
group_host = {<<"">>, <<"">>} :: {binary(), binary()}}). us = {<<"">>, <<"">>} :: {binary(), binary()},
group_host = {<<"">>, <<"">>} :: {binary(), binary()}
}).

View file

@ -18,11 +18,35 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-record(vcard_search, -record(vcard_search, {
{us, user, luser, fn, lfn, family, lfamily, given, us,
lgiven, middle, lmiddle, nickname, lnickname, bday, user,
lbday, ctry, lctry, locality, llocality, email, lemail, luser,
orgname, lorgname, orgunit, lorgunit}). fn,
lfn,
family,
lfamily,
given,
lgiven,
middle,
lmiddle,
nickname,
lnickname,
bday,
lbday,
ctry,
lctry,
locality,
llocality,
email,
lemail,
orgname,
lorgname,
orgunit,
lorgunit
}).
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(), -record(vcard, {
vcard = #xmlel{} :: xmlel()}). us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
vcard = #xmlel{} :: xmlel()
}).

View file

@ -15,6 +15,9 @@
%%% limitations under the License. %%% limitations under the License.
%%% %%%
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-define(MQTT_VERSION_4, 4). -define(MQTT_VERSION_4, 4).
-define(MQTT_VERSION_5, 5). -define(MQTT_VERSION_5, 5).

View file

@ -17,6 +17,8 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
%% ------------------------------- %% -------------------------------
%% Pubsub constants %% Pubsub constants

View file

@ -1,5 +1,6 @@
-module(configure_deps). -module(configure_deps).
-export(['configure-deps'/2]). -export(['configure-deps'/2]).
'configure-deps'(Config, Vals) -> 'configure-deps'(Config, Vals) ->
{ok, Config}. {ok, Config}.

View file

@ -1,6 +1,7 @@
-module(deps_erl_opts). -module(deps_erl_opts).
-export([preprocess/2]). -export([preprocess/2]).
preprocess(Config, Dirs) -> preprocess(Config, Dirs) ->
ExtraOpts = rebar_config:get(Config, deps_erl_opts, []), ExtraOpts = rebar_config:get(Config, deps_erl_opts, []),
Opts = rebar_config:get(Config, erl_opts, []), Opts = rebar_config:get(Config, erl_opts, []),
@ -8,5 +9,7 @@ preprocess(Config, Dirs) ->
lists:keystore(element(1, Opt), 1, Acc, Opt); lists:keystore(element(1, Opt), 1, Acc, Opt);
(Opt, Acc) -> (Opt, Acc) ->
[Opt | lists:delete(Opt, Acc)] [Opt | lists:delete(Opt, Acc)]
end, Opts, ExtraOpts), end,
Opts,
ExtraOpts),
{ok, rebar_config:set(Config, erl_opts, NewOpts), []}. {ok, rebar_config:set(Config, erl_opts, NewOpts), []}.

View file

@ -1,22 +1,25 @@
-module(override_deps_versions2). -module(override_deps_versions2).
-export([preprocess/2, 'pre_update-deps'/2, new_replace/1, new_replace/0]). -export([preprocess/2, 'pre_update-deps'/2, new_replace/1, new_replace/0]).
preprocess(Config, _Dirs) -> preprocess(Config, _Dirs) ->
update_deps(Config). update_deps(Config).
update_deps(Config) -> update_deps(Config) ->
LocalDeps = rebar_config:get_local(Config, deps, []), LocalDeps = rebar_config:get_local(Config, deps, []),
TopDeps = case rebar_config:get_xconf(Config, top_deps, []) of TopDeps = case rebar_config:get_xconf(Config, top_deps, []) of
[] -> LocalDeps; [] -> LocalDeps;
Val -> Val Val -> Val
end, end,
Config2 = rebar_config:set_xconf(Config, top_deps, TopDeps), Config2 = rebar_config:set_xconf(Config, top_deps, TopDeps),
NewDeps = lists:map(fun({Name, _, _} = Dep) -> NewDeps = lists:map(fun({Name, _, _} = Dep) ->
case lists:keyfind(Name, 1, TopDeps) of case lists:keyfind(Name, 1, TopDeps) of
false -> Dep; false -> Dep;
TopDep -> TopDep TopDep -> TopDep
end end
end, LocalDeps), end,
LocalDeps),
%io:format("LD ~p~n", [LocalDeps]), %io:format("LD ~p~n", [LocalDeps]),
%io:format("TD ~p~n", [TopDeps]), %io:format("TD ~p~n", [TopDeps]),
@ -28,53 +31,59 @@ update_deps(Config) ->
{ok, Config2, _} = update_deps(Config), {ok, Config2, _} = update_deps(Config),
case code:is_loaded(old_rebar_config) of case code:is_loaded(old_rebar_config) of
false -> false ->
{_, Beam, _} = code:get_object_code(rebar_config), {_, Beam, _} = code:get_object_code(rebar_config),
NBeam = rename(Beam, old_rebar_config), NBeam = rename(Beam, old_rebar_config),
code:load_binary(old_rebar_config, "blank", NBeam), code:load_binary(old_rebar_config, "blank", NBeam),
replace_mod(Beam); replace_mod(Beam);
_ -> _ ->
ok ok
end, end,
{ok, Config2}. {ok, Config2}.
new_replace() -> new_replace() ->
old_rebar_config:new(). old_rebar_config:new().
new_replace(Config) -> new_replace(Config) ->
NC = old_rebar_config:new(Config), NC = old_rebar_config:new(Config),
{ok, Conf, _} = update_deps(NC), {ok, Conf, _} = update_deps(NC),
Conf. Conf.
replace_mod(Beam) -> replace_mod(Beam) ->
{ok, {_, [{exports, Exports}]}} = beam_lib:chunks(Beam, [exports]), {ok, {_, [{exports, Exports}]}} = beam_lib:chunks(Beam, [exports]),
Funcs = lists:filtermap( Funcs = lists:filtermap(
fun({module_info, _}) -> fun({module_info, _}) ->
false; false;
({Name, Arity}) -> ({Name, Arity}) ->
Args = args(Arity), Args = args(Arity),
Call = case Name of Call = case Name of
new -> new ->
[erl_syntax:application( [erl_syntax:application(
erl_syntax:abstract(override_deps_versions2), erl_syntax:abstract(override_deps_versions2),
erl_syntax:abstract(new_replace), erl_syntax:abstract(new_replace),
Args)]; Args)];
_ -> _ ->
[erl_syntax:application( [erl_syntax:application(
erl_syntax:abstract(old_rebar_config), erl_syntax:abstract(old_rebar_config),
erl_syntax:abstract(Name), erl_syntax:abstract(Name),
Args)] Args)]
end, end,
{true, erl_syntax:function(erl_syntax:abstract(Name), {true, erl_syntax:function(erl_syntax:abstract(Name),
[erl_syntax:clause(Args, none, [erl_syntax:clause(Args,
Call)])} none,
end, Exports), Call)])}
end,
Exports),
Forms0 = ([erl_syntax:attribute(erl_syntax:abstract(module), Forms0 = ([erl_syntax:attribute(erl_syntax:abstract(module),
[erl_syntax:abstract(rebar_config)])] [erl_syntax:abstract(rebar_config)])] ++
++ Funcs), Funcs),
Forms = [erl_syntax:revert(Form) || Form <- Forms0], Forms = [ erl_syntax:revert(Form) || Form <- Forms0 ],
%io:format("--------------------------------------------------~n" %io:format("--------------------------------------------------~n"
% "~s~n", % "~s~n",
% [[erl_pp:form(Form) || Form <- Forms]]), % [[erl_pp:form(Form) || Form <- Forms]]),
{ok, Mod, Bin} = compile:forms(Forms, [report, export_all]), {ok, Mod, Bin} = compile:forms(Forms, [report, export_all]),
code:purge(rebar_config), code:purge(rebar_config),
{module, Mod} = code:load_binary(rebar_config, "mock", Bin). {module, Mod} = code:load_binary(rebar_config, "mock", Bin).
@ -83,15 +92,18 @@ replace_mod(Beam) ->
args(0) -> args(0) ->
[]; [];
args(N) -> args(N) ->
[arg(N) | args(N-1)]. [arg(N) | args(N - 1)].
arg(N) -> arg(N) ->
erl_syntax:variable(list_to_atom("A"++integer_to_list(N))). erl_syntax:variable(list_to_atom("A" ++ integer_to_list(N))).
rename(BeamBin0, Name) -> rename(BeamBin0, Name) ->
BeamBin = replace_in_atab(BeamBin0, Name), BeamBin = replace_in_atab(BeamBin0, Name),
update_form_size(BeamBin). update_form_size(BeamBin).
%% Replace the first atom of the atom table with the new name %% Replace the first atom of the atom table with the new name
replace_in_atab(<<"Atom", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) -> replace_in_atab(<<"Atom", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
replace_first_atom(<<"Atom">>, Cnk, CnkSz0, Rest, latin1, Name); replace_first_atom(<<"Atom">>, Cnk, CnkSz0, Rest, latin1, Name);
@ -100,6 +112,7 @@ replace_in_atab(<<"AtU8", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
replace_in_atab(<<C, Rest/binary>>, Name) -> replace_in_atab(<<C, Rest/binary>>, Name) ->
<<C, (replace_in_atab(Rest, Name))/binary>>. <<C, (replace_in_atab(Rest, Name))/binary>>.
replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) -> replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) ->
<<NumAtoms:32, NameSz0:8, _Name0:NameSz0/binary, CnkRest/binary>> = Cnk, <<NumAtoms:32, NameSz0:8, _Name0:NameSz0/binary, CnkRest/binary>> = Cnk,
NumPad0 = num_pad_bytes(CnkSz0), NumPad0 = num_pad_bytes(CnkSz0),
@ -116,11 +129,12 @@ replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) ->
%% BinSize to be an even multiple of ?beam_num_bytes_alignment. %% BinSize to be an even multiple of ?beam_num_bytes_alignment.
num_pad_bytes(BinSize) -> num_pad_bytes(BinSize) ->
case 4 - (BinSize rem 4) of case 4 - (BinSize rem 4) of
4 -> 0; 4 -> 0;
N -> N N -> N
end. end.
%% Update the size within the top-level form %% Update the size within the top-level form
update_form_size(<<"FOR1", _OldSz:32, Rest/binary>> = Bin) -> update_form_size(<<"FOR1", _OldSz:32, Rest/binary>> = Bin) ->
Sz = size(Bin) - 8, Sz = size(Bin) - 8,
<<"FOR1", Sz:32, Rest/binary>>. <<"FOR1", Sz:32, Rest/binary>>.

View file

@ -1,43 +1,53 @@
-module(override_opts). -module(override_opts).
-export([preprocess/2]). -export([preprocess/2]).
override_opts(override, Config, Opts) -> override_opts(override, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) -> lists:foldl(fun({Opt, Value}, Conf) ->
rebar_config:set(Conf, Opt, Value) rebar_config:set(Conf, Opt, Value)
end, Config, Opts); end,
Config,
Opts);
override_opts(add, Config, Opts) -> override_opts(add, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) -> lists:foldl(fun({Opt, Value}, Conf) ->
V = rebar_config:get_local(Conf, Opt, []), V = rebar_config:get_local(Conf, Opt, []),
rebar_config:set(Conf, Opt, V ++ Value) rebar_config:set(Conf, Opt, V ++ Value)
end, Config, Opts); end,
Config,
Opts);
override_opts(del, Config, Opts) -> override_opts(del, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) -> lists:foldl(fun({Opt, Value}, Conf) ->
V = rebar_config:get_local(Conf, Opt, []), V = rebar_config:get_local(Conf, Opt, []),
rebar_config:set(Conf, Opt, V -- Value) rebar_config:set(Conf, Opt, V -- Value)
end, Config, Opts). end,
Config,
Opts).
preprocess(Config, _Dirs) -> preprocess(Config, _Dirs) ->
Overrides = rebar_config:get_local(Config, overrides, []), Overrides = rebar_config:get_local(Config, overrides, []),
TopOverrides = case rebar_config:get_xconf(Config, top_overrides, []) of TopOverrides = case rebar_config:get_xconf(Config, top_overrides, []) of
[] -> Overrides; [] -> Overrides;
Val -> Val Val -> Val
end, end,
Config2 = rebar_config:set_xconf(Config, top_overrides, TopOverrides), Config2 = rebar_config:set_xconf(Config, top_overrides, TopOverrides),
try try
Config3 = case rebar_app_utils:load_app_file(Config2, _Dirs) of Config3 = case rebar_app_utils:load_app_file(Config2, _Dirs) of
{ok, C, AppName, _AppData} -> {ok, C, AppName, _AppData} ->
lists:foldl(fun({Type, AppName2, Opts}, Conf1) when lists:foldl(fun({Type, AppName2, Opts}, Conf1)
AppName2 == AppName -> when AppName2 == AppName ->
override_opts(Type, Conf1, Opts); override_opts(Type, Conf1, Opts);
({Type, Opts}, Conf1a) -> ({Type, Opts}, Conf1a) ->
override_opts(Type, Conf1a, Opts); override_opts(Type, Conf1a, Opts);
(_, Conf2) -> (_, Conf2) ->
Conf2 Conf2
end, C, TopOverrides); end,
_ -> C,
Config2 TopOverrides);
end, _ ->
{ok, Config3, []} Config2
end,
{ok, Config3, []}
catch catch
error:badarg -> {ok, Config2, []} error:badarg -> {ok, Config2, []}
end. end.

View file

@ -23,87 +23,94 @@
%%% %%%
{deps, [{if_not_rebar3, {deps, [{if_not_rebar3,
{if_version_below, "24", {if_version_below,
{base64url, "~> 1.0", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}} "24",
}}, {base64url, "~> 1.0", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}}}},
{cache_tab, "~> 1.0.33", {git, "https://github.com/processone/cache_tab", {tag, "1.0.33"}}}, {cache_tab, "~> 1.0.33", {git, "https://github.com/processone/cache_tab", {tag, "1.0.33"}}},
{eimp, "~> 1.0.26", {git, "https://github.com/processone/eimp", {tag, "1.0.26"}}}, {eimp, "~> 1.0.26", {git, "https://github.com/processone/eimp", {tag, "1.0.26"}}},
{if_var_true, pam, {if_var_true,
pam,
{epam, "~> 1.0.14", {git, "https://github.com/processone/epam", {tag, "1.0.14"}}}}, {epam, "~> 1.0.14", {git, "https://github.com/processone/epam", {tag, "1.0.14"}}}},
{if_var_true, redis, {if_var_true,
redis,
{if_not_rebar3, {if_not_rebar3,
{eredis, "~> 1.2.0", {git, "https://github.com/wooga/eredis/", {tag, "v1.2.0"}}} {eredis, "~> 1.2.0", {git, "https://github.com/wooga/eredis/", {tag, "v1.2.0"}}}}},
}}, {if_var_true,
{if_var_true, redis, redis,
{if_rebar3, {if_rebar3,
{if_version_below, "21", {if_version_below,
"21",
{eredis, "1.2.0", {git, "https://github.com/wooga/eredis/", {tag, "v1.2.0"}}}, {eredis, "1.2.0", {git, "https://github.com/wooga/eredis/", {tag, "v1.2.0"}}},
{eredis, "~> 1.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}} {eredis, "~> 1.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}}}}},
}}}, {if_var_true,
{if_var_true, sip, sip,
{esip, "~> 1.0.59", {git, "https://github.com/processone/esip", {tag, "1.0.59"}}}}, {esip, "~> 1.0.59", {git, "https://github.com/processone/esip", {tag, "1.0.59"}}}},
{if_var_true, zlib, {if_var_true,
zlib,
{ezlib, "~> 1.0.15", {git, "https://github.com/processone/ezlib", {tag, "1.0.15"}}}}, {ezlib, "~> 1.0.15", {git, "https://github.com/processone/ezlib", {tag, "1.0.15"}}}},
{fast_tls, "~> 1.1.25", {git, "https://github.com/processone/fast_tls", {tag, "1.1.25"}}}, {fast_tls, "~> 1.1.25", {git, "https://github.com/processone/fast_tls", {tag, "1.1.25"}}},
{fast_xml, "~> 1.1.57", {git, "https://github.com/processone/fast_xml", {tag, "1.1.57"}}}, {fast_xml, "~> 1.1.57", {git, "https://github.com/processone/fast_xml", {tag, "1.1.57"}}},
{fast_yaml, "~> 1.0.39", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.39"}}}, {fast_yaml, "~> 1.0.39", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.39"}}},
{idna, "~> 6.0", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}}, {idna, "~> 6.0", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
{if_version_below, "27", {if_version_below,
{jiffy, "~> 1.1.1", {git, "https://github.com/davisp/jiffy", {tag, "1.1.1"}}} "27",
}, {jiffy, "~> 1.1.1", {git, "https://github.com/davisp/jiffy", {tag, "1.1.1"}}}},
{if_version_above, "23", {if_version_above,
"23",
{jose, "~> 1.11.10", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.10"}}}, {jose, "~> 1.11.10", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.10"}}},
{jose, "1.11.1", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}} {jose, "1.11.1", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}}},
}, {if_version_below,
{if_version_below, "22", "22",
{lager, "~> 3.9.1", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}} {lager, "~> 3.9.1", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}}},
}, {if_var_true,
{if_var_true, lua, lua,
{if_version_below, "21", {if_version_below, "21",
{luerl, "1.0.0", {git, "https://github.com/rvirding/luerl", {tag, "1.0"}}}, {luerl, "1.0.0", {git, "https://github.com/rvirding/luerl", {tag, "1.0"}}},
{luerl, "~> 1.2.0", {git, "https://github.com/rvirding/luerl", {tag, "1.2"}}} {luerl, "~> 1.2.0", {git, "https://github.com/rvirding/luerl", {tag, "1.2"}}}}},
}},
{mqtree, "~> 1.0.19", {git, "https://github.com/processone/mqtree", {tag, "1.0.19"}}}, {mqtree, "~> 1.0.19", {git, "https://github.com/processone/mqtree", {tag, "1.0.19"}}},
{p1_acme, "~> 1.0.28", {git, "https://github.com/processone/p1_acme", {tag, "1.0.28"}}}, {p1_acme, "~> 1.0.28", {git, "https://github.com/processone/p1_acme", {tag, "1.0.28"}}},
{if_var_true, mysql, {if_var_true,
mysql,
{p1_mysql, "~> 1.0.26", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.26"}}}}, {p1_mysql, "~> 1.0.26", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.26"}}}},
{p1_oauth2, "~> 0.6.14", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.14"}}}, {p1_oauth2, "~> 0.6.14", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.14"}}},
{if_var_true, pgsql, {if_var_true,
pgsql,
{p1_pgsql, "~> 1.1.35", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.35"}}}}, {p1_pgsql, "~> 1.1.35", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.35"}}}},
{p1_utils, "~> 1.0.28", {git, "https://github.com/processone/p1_utils", {tag, "1.0.28"}}}, {p1_utils, "~> 1.0.28", {git, "https://github.com/processone/p1_utils", {tag, "1.0.28"}}},
{pkix, "~> 1.0.10", {git, "https://github.com/processone/pkix", {tag, "1.0.10"}}}, {pkix, "~> 1.0.10", {git, "https://github.com/processone/pkix", {tag, "1.0.10"}}},
{if_var_true, sqlite, {if_var_true,
sqlite,
{sqlite3, "~> 1.1.15", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.15"}}}}, {sqlite3, "~> 1.1.15", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.15"}}}},
{stringprep, "~> 1.0.33", {git, "https://github.com/processone/stringprep", {tag, "1.0.33"}}}, {stringprep, "~> 1.0.33", {git, "https://github.com/processone/stringprep", {tag, "1.0.33"}}},
{if_var_true, stun, {if_var_true,
stun,
{stun, "~> 1.2.21", {git, "https://github.com/processone/stun", {tag, "1.2.21"}}}}, {stun, "~> 1.2.21", {git, "https://github.com/processone/stun", {tag, "1.2.21"}}}},
{xmpp, "~> 1.11.1", {git, "https://github.com/processone/xmpp", {tag, "1.11.1"}}}, {xmpp, "~> 1.11.1", {git, "https://github.com/processone/xmpp", {tag, "1.11.1"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", "95692795a8a8d950ba560e5b07e6b80660557259"}} {yconf, ".*", {git, "https://github.com/processone/yconf", "95692795a8a8d950ba560e5b07e6b80660557259"}}]}.
]}.
{gitonly_deps, [ejabberd_po]}. {gitonly_deps, [ejabberd_po]}.
{if_var_true, latest_deps, {if_var_true, latest_deps,
{floating_deps, [cache_tab, {floating_deps, [cache_tab,
eimp, eimp,
epam, epam,
esip, esip,
ezlib, ezlib,
fast_tls, fast_tls,
fast_xml, fast_xml,
fast_yaml, fast_yaml,
mqtree, mqtree,
p1_acme, p1_acme,
p1_mysql, p1_mysql,
p1_oauth2, p1_oauth2,
p1_pgsql, p1_pgsql,
p1_utils, p1_utils,
pkix, pkix,
sqlite3, sqlite3,
stringprep, stringprep,
stun, stun,
xmpp, xmpp,
yconf]}}. yconf]}}.
%%% %%%
%%% Compile %%% Compile
@ -121,8 +128,8 @@
{"stringprep", []}]}. {"stringprep", []}]}.
{erl_first_files, ["src/ejabberd_sql_pt.erl", "src/ejabberd_config.erl", {erl_first_files, ["src/ejabberd_sql_pt.erl", "src/ejabberd_config.erl",
"src/gen_mod.erl", "src/mod_muc_room.erl", "src/gen_mod.erl", "src/mod_muc_room.erl",
"src/mod_push.erl", "src/xmpp_socket.erl"]}. "src/mod_push.erl", "src/xmpp_socket.erl"]}.
{erl_opts, [nowarn_deprecated_function, {erl_opts, [nowarn_deprecated_function,
{i, "include"}, {i, "include"},
@ -159,22 +166,17 @@
%% https://github.com/Supersonido/rebar_mix/issues/27#issuecomment-894873335 %% https://github.com/Supersonido/rebar_mix/issues/27#issuecomment-894873335
%% Let's use this fixed rebar_mix fork, see its PR: %% Let's use this fixed rebar_mix fork, see its PR:
%% https://github.com/Supersonido/rebar_mix/pull/31 %% https://github.com/Supersonido/rebar_mix/pull/31
{if_var_true, elixir, {rebar_mix, ".*", {if_var_true, elixir,
{git, "https://github.com/bsanyi/rebar_mix.git", {rebar_mix, ".*",
{branch, "consolidation_fix"}}} {git, "https://github.com/bsanyi/rebar_mix.git",
}]}}. {branch, "consolidation_fix"}}}}]}}.
{if_rebar3, {project_plugins, [configure_deps, {if_rebar3, {project_plugins, [configure_deps,
{if_var_true, tools, rebar3_format}, {if_var_true, tools, rebar3_efmt},
{if_var_true, tools, {rebar3_lint, "4.1.1"}} {if_var_true, tools, {rebar3_lint, "4.1.1"}}]}}.
]}}. {if_not_rebar3, {plugins, [deps_erl_opts, override_deps_versions2, override_opts, configure_deps]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts, override_deps_versions2, override_opts, configure_deps
]}}.
{if_rebar3, {if_var_true, elixir, {if_rebar3, {if_var_true, elixir,
{provider_hooks, [ {provider_hooks, [{post, [{compile, {mix, consolidate_protocols}}]}]}}}.
{post, [{compile, {mix, consolidate_protocols}}]}
]}}}.
%% Compiling Jose 1.11.10 with Erlang/OTP 27.0 throws warnings on public_key deprecated functions %% Compiling Jose 1.11.10 with Erlang/OTP 27.0 throws warnings on public_key deprecated functions
{if_rebar3, {overrides, [{del, jose, [{erl_opts, [warnings_as_errors]}]}]}}. {if_rebar3, {overrides, [{del, jose, [{erl_opts, [warnings_as_errors]}]}]}}.
@ -192,16 +194,13 @@
{if_rebar3, {if_rebar3,
{xref_checks, {xref_checks,
[deprecated_function_calls, deprecated_functions, locals_not_used, [deprecated_function_calls, deprecated_functions, locals_not_used,
undefined_function_calls, undefined_functions]} undefined_function_calls, undefined_functions]}}.
}.
{if_not_rebar3, {if_not_rebar3,
{xref_checks, {xref_checks,
[deprecated_function_calls, deprecated_functions, [deprecated_function_calls, deprecated_functions,
undefined_function_calls, undefined_functions]} undefined_function_calls, undefined_functions]}}.
}.
{xref_exclusions, [ {xref_exclusions, ["(\"gen_transport\":_/_)",
"(\"gen_transport\":_/_)",
"(\"eprof\":_/_)", "(\"eprof\":_/_)",
{if_var_false, elixir, "(\"Elixir.*\":_/_)"}, {if_var_false, elixir, "(\"Elixir.*\":_/_)"},
{if_var_false, http, "(\"lhttpc\":_/_)"}, {if_var_false, http, "(\"lhttpc\":_/_)"},
@ -219,37 +218,62 @@
{eunit_compile_opts, [{i, "tools"}, {eunit_compile_opts, [{i, "tools"},
{i, "include"}]}. {i, "include"}]}.
{dialyzer, [{get_warnings, false}, % Show warnings of dependencies {dialyzer, [{get_warnings, false}, % Show warnings of dependencies
{if_version_above, "25", {if_version_above, "25",
{plt_extra_apps, {plt_extra_apps,
[asn1, odbc, public_key, stdlib, syntax_tools, [asn1,
idna, jose, odbc,
cache_tab, eimp, fast_tls, fast_xml, fast_yaml, public_key,
mqtree, p1_acme, p1_oauth2, p1_utils, pkix, stdlib,
stringprep, xmpp, yconf, syntax_tools,
{if_version_below, "27", jiffy}, idna,
{if_var_true, pam, epam}, jose,
{if_var_true, redis, eredis}, cache_tab,
{if_var_true, sip, esip}, eimp,
{if_var_true, zlib, ezlib}, fast_tls,
{if_var_true, lua, luerl}, fast_xml,
{if_var_true, mysql, p1_mysql}, fast_yaml,
{if_var_true, pgsql, p1_pgsql}, mqtree,
{if_var_true, stun, stun}, p1_acme,
{if_var_true, sqlite, sqlite3}]}, p1_oauth2,
{plt_extra_apps, % For Erlang/OTP 25 and older p1_utils,
[cache_tab, eimp, fast_tls, fast_xml, fast_yaml, pkix,
mqtree, p1_acme, p1_oauth2, p1_utils, pkix, stringprep, xmpp, yconf, stringprep,
{if_var_true, pam, epam}, xmpp,
{if_var_true, redis, eredis}, yconf,
{if_var_true, sip, esip}, {if_version_below, "27", jiffy},
{if_var_true, zlib, ezlib}, {if_var_true, pam, epam},
{if_var_true, lua, luerl}, {if_var_true, redis, eredis},
{if_var_true, mysql, p1_mysql}, {if_var_true, sip, esip},
{if_var_true, pgsql, p1_pgsql}, {if_var_true, zlib, ezlib},
{if_var_true, stun, stun}, {if_var_true, lua, luerl},
{if_var_true, sqlite, sqlite3}]} {if_var_true, mysql, p1_mysql},
} ]}. {if_var_true, pgsql, p1_pgsql},
{if_var_true, stun, stun},
{if_var_true, sqlite, sqlite3}]},
{plt_extra_apps, % For Erlang/OTP 25 and older
[cache_tab,
eimp,
fast_tls,
fast_xml,
fast_yaml,
mqtree,
p1_acme,
p1_oauth2,
p1_utils,
pkix,
stringprep,
xmpp,
yconf,
{if_var_true, pam, epam},
{if_var_true, redis, eredis},
{if_var_true, sip, esip},
{if_var_true, zlib, ezlib},
{if_var_true, lua, luerl},
{if_var_true, mysql, p1_mysql},
{if_var_true, pgsql, p1_pgsql},
{if_var_true, stun, stun},
{if_var_true, sqlite, sqlite3}]}}]}.
{ct_opts, [{keep_logs, 20}]}. {ct_opts, [{keep_logs, 20}]}.
@ -264,7 +288,7 @@
%%% %%%
{relx, [{release, {ejabberd, {cmd, "grep {vsn, vars.config | sed 's|{vsn, \"||;s|\"}.||' | tr -d '\012'"}}, {relx, [{release, {ejabberd, {cmd, "grep {vsn, vars.config | sed 's|{vsn, \"||;s|\"}.||' | tr -d '\012'"}},
[ejabberd]}, [ejabberd]},
{sys_config, "./rel/sys.config"}, {sys_config, "./rel/sys.config"},
{vm_args, "./rel/vm.args"}, {vm_args, "./rel/vm.args"},
{overlay_vars, "vars.config"}, {overlay_vars, "vars.config"},
@ -277,12 +301,10 @@
{copy, "{{base_dir}}/consolidated/*", "lib/ejabberd-{{release_version}}/ebin/"}, {copy, "{{base_dir}}/consolidated/*", "lib/ejabberd-{{release_version}}/ebin/"},
{copy, "rel/overlays/iex", "releases/{{release_version}}/"}, {copy, "rel/overlays/iex", "releases/{{release_version}}/"},
{if_var_true, elixir, {if_var_true, elixir,
{template, "rel/overlays/elixir", "releases/{{release_version}}/elixir"} {template, "rel/overlays/elixir", "releases/{{release_version}}/elixir"}},
},
{copy, "inetrc", "conf/inetrc"}, {copy, "inetrc", "conf/inetrc"},
{copy, "tools/captcha*.sh", "lib/ejabberd-\{\{release_version\}\}/priv/bin/"}, {copy, "tools/captcha*.sh", "lib/ejabberd-\{\{release_version\}\}/priv/bin/"},
{copy, "rel/files/install_upgrade.escript", "bin/install_upgrade.escript"}]} {copy, "rel/files/install_upgrade.escript", "bin/install_upgrade.escript"}]}]}.
]}.
{profiles, [{prod, [{relx, [{debug_info, strip}, {profiles, [{prod, [{relx, [{debug_info, strip},
{dev_mode, false}, {dev_mode, false},
@ -311,8 +333,7 @@
--config rel/relive.config \ --config rel/relive.config \
--eval sync:go(). \ --eval sync:go(). \
--script rel/relive.escript \ --script rel/relive.escript \
--name ejabberd@localhost"}]} --name ejabberd@localhost"}]}]}.
]}.
%% Local Variables: %% Local Variables:
%% mode: erlang %% mode: erlang

View file

@ -4,23 +4,34 @@
%% ex: ft=erlang ts=4 sw=4 et %% ex: ft=erlang ts=4 sw=4 et
-define(TIMEOUT, 60000). -define(TIMEOUT, 60000).
-define(INFO(Fmt,Args), io:format(Fmt,Args)). -define(INFO(Fmt, Args), io:format(Fmt, Args)).
main([NodeName, Cookie, ReleasePackage]) -> main([NodeName, Cookie, ReleasePackage]) ->
TargetNode = start_distribution(NodeName, Cookie), TargetNode = start_distribution(NodeName, Cookie),
{ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, {ok, Vsn} = rpc:call(TargetNode,
[ReleasePackage], ?TIMEOUT), release_handler,
unpack_release,
[ReleasePackage],
?TIMEOUT),
?INFO("Unpacked Release ~p~n", [Vsn]), ?INFO("Unpacked Release ~p~n", [Vsn]),
{ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, {ok, OtherVsn, Desc} = rpc:call(TargetNode,
check_install_release, [Vsn], ?TIMEOUT), release_handler,
{ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, check_install_release,
install_release, [Vsn], ?TIMEOUT), [Vsn],
?TIMEOUT),
{ok, OtherVsn, Desc} = rpc:call(TargetNode,
release_handler,
install_release,
[Vsn],
?TIMEOUT),
?INFO("Installed Release ~p~n", [Vsn]), ?INFO("Installed Release ~p~n", [Vsn]),
ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT),
?INFO("Made Release ~p Permanent~n", [Vsn]); ?INFO("Made Release ~p Permanent~n", [Vsn]);
main(_) -> main(_) ->
init:stop(1). init:stop(1).
start_distribution(NodeName, Cookie) -> start_distribution(NodeName, Cookie) ->
MyNode = make_script_node(NodeName), MyNode = make_script_node(NodeName),
{ok, _Pid} = net_kernel:start([MyNode, shortnames]), {ok, _Pid} = net_kernel:start([MyNode, shortnames]),
@ -36,9 +47,11 @@ start_distribution(NodeName, Cookie) ->
end, end,
TargetNode. TargetNode.
make_target_node(Node) -> make_target_node(Node) ->
[_, Host] = string:tokens(atom_to_list(node()), "@"), [_, Host] = string:tokens(atom_to_list(node()), "@"),
list_to_atom(lists:concat([Node, "@", Host])). list_to_atom(lists:concat([Node, "@", Host])).
make_script_node(Node) -> make_script_node(Node) ->
list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])). list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])).

View file

@ -1,5 +1,6 @@
#!/usr/bin/env escript #!/usr/bin/env escript
main(_) -> main(_) ->
Base = "_build/relive", Base = "_build/relive",
prepare(Base, "", none), prepare(Base, "", none),
@ -9,6 +10,7 @@ main(_) ->
c:erlangrc([os:cmd("echo -n $HOME")]), c:erlangrc([os:cmd("echo -n $HOME")]),
ok. ok.
prepare(BaseDir, SuffixDir, MFA) -> prepare(BaseDir, SuffixDir, MFA) ->
Dir = filename:join(BaseDir, SuffixDir), Dir = filename:join(BaseDir, SuffixDir),
case file:make_dir(Dir) of case file:make_dir(Dir) of

File diff suppressed because it is too large Load diff

View file

@ -27,8 +27,12 @@
-export([validator/1, validators/0]). -export([validator/1, validators/0]).
-export([loaded_shared_roster_module/1]). -export([loaded_shared_roster_module/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-include("logger.hrl"). -include("logger.hrl").
@ -37,48 +41,55 @@
-type ip_mask() :: {inet:ip4_address(), 0..32} | {inet:ip6_address(), 0..128}. -type ip_mask() :: {inet:ip4_address(), 0..32} | {inet:ip6_address(), 0..128}.
-type access_rule() :: {acl, atom()} | acl_rule(). -type access_rule() :: {acl, atom()} | acl_rule().
-type acl_rule() :: {user, {binary(), binary()} | binary()} | -type acl_rule() :: {user, {binary(), binary()} | binary()} |
{server, binary()} | {server, binary()} |
{resource, binary()} | {resource, binary()} |
{user_regexp, {misc:re_mp(), binary()} | misc:re_mp()} | {user_regexp, {misc:re_mp(), binary()} | misc:re_mp()} |
{server_regexp, misc:re_mp()} | {server_regexp, misc:re_mp()} |
{resource_regexp, misc:re_mp()} | {resource_regexp, misc:re_mp()} |
{node_regexp, {misc:re_mp(), misc:re_mp()}} | {node_regexp, {misc:re_mp(), misc:re_mp()}} |
{user_glob, {misc:re_mp(), binary()} | misc:re_mp()} | {user_glob, {misc:re_mp(), binary()} | misc:re_mp()} |
{server_glob, misc:re_mp()} | {server_glob, misc:re_mp()} |
{resource_glob, misc:re_mp()} | {resource_glob, misc:re_mp()} |
{node_glob, {misc:re_mp(), misc:re_mp()}} | {node_glob, {misc:re_mp(), misc:re_mp()}} |
{shared_group, {binary(), binary()} | binary()} | {shared_group, {binary(), binary()} | binary()} |
{ip, ip_mask()}. {ip, ip_mask()}.
-type access() :: [{action(), [access_rule()]}]. -type access() :: [{action(), [access_rule()]}].
-type acl() :: atom() | access(). -type acl() :: atom() | access().
-type match() :: #{ip => inet:ip_address(), -type match() :: #{
usr => jid:ljid(), ip => inet:ip_address(),
atom() => term()}. usr => jid:ljid(),
atom() => term()
}.
-export_type([acl/0, acl_rule/0, access/0, access_rule/0, match/0]). -export_type([acl/0, acl_rule/0, access/0, access_rule/0, match/0]).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec match_rule(global | binary(), atom() | access(),
-spec match_rule(global | binary(),
atom() | access(),
jid:jid() | jid:ljid() | inet:ip_address() | match()) -> action(). jid:jid() | jid:ljid() | inet:ip_address() | match()) -> action().
match_rule(_, all, _) -> match_rule(_, all, _) ->
allow; allow;
match_rule(_, none, _) -> match_rule(_, none, _) ->
deny; deny;
match_rule(Host, Access, Match) when is_map(Match) -> match_rule(Host, Access, Match) when is_map(Match) ->
Rules = if is_atom(Access) -> read_access(Access, Host); Rules = if
true -> Access is_atom(Access) -> read_access(Access, Host);
end, true -> Access
end,
match_rules(Host, Rules, Match, deny); match_rules(Host, Rules, Match, deny);
match_rule(Host, Access, IP) when tuple_size(IP) == 4; tuple_size(IP) == 8 -> match_rule(Host, Access, IP) when tuple_size(IP) == 4; tuple_size(IP) == 8 ->
match_rule(Host, Access, #{ip => IP}); match_rule(Host, Access, #{ip => IP});
match_rule(Host, Access, JID) -> match_rule(Host, Access, JID) ->
match_rule(Host, Access, #{usr => jid:tolower(JID)}). match_rule(Host, Access, #{usr => jid:tolower(JID)}).
-spec match_acl(global | binary(), access_rule(), match()) -> boolean(). -spec match_acl(global | binary(), access_rule(), match()) -> boolean().
match_acl(_Host, {acl, all}, _) -> match_acl(_Host, {acl, all}, _) ->
true; true;
@ -87,8 +98,9 @@ match_acl(_Host, {acl, none}, _) ->
match_acl(Host, {acl, ACLName}, Match) -> match_acl(Host, {acl, ACLName}, Match) ->
lists:any( lists:any(
fun(ACL) -> fun(ACL) ->
match_acl(Host, ACL, Match) match_acl(Host, ACL, Match)
end, read_acl(ACLName, Host)); end,
read_acl(ACLName, Host));
match_acl(_Host, {ip, {Net, Mask}}, #{ip := {IP, _Port}}) -> match_acl(_Host, {ip, {Net, Mask}}, #{ip := {IP, _Port}}) ->
misc:match_ip_mask(IP, Net, Mask); misc:match_ip_mask(IP, Net, Mask);
match_acl(_Host, {ip, {Net, Mask}}, #{ip := IP}) -> match_acl(_Host, {ip, {Net, Mask}}, #{ip := IP}) ->
@ -103,8 +115,8 @@ match_acl(_Host, {resource, R}, #{usr := {_, _, R}}) ->
true; true;
match_acl(_Host, {shared_group, {G, H}}, #{usr := {U, S, _}}) -> match_acl(_Host, {shared_group, {G, H}}, #{usr := {U, S, _}}) ->
case loaded_shared_roster_module(H) of case loaded_shared_roster_module(H) of
undefined -> false; undefined -> false;
Mod -> Mod:is_user_in_group({U, S}, G, H) Mod -> Mod:is_user_in_group({U, S}, G, H)
end; end;
match_acl(Host, {shared_group, G}, #{usr := {_, S, _}} = Map) -> match_acl(Host, {shared_group, G}, #{usr := {_, S, _}} = Map) ->
match_acl(Host, {shared_group, {G, S}}, Map); match_acl(Host, {shared_group, {G, S}}, Map);
@ -131,30 +143,35 @@ match_acl(_Host, {node_glob, {UR, SR}}, #{usr := {U, S, _}}) ->
match_acl(_, _, _) -> match_acl(_, _, _) ->
false. false.
-spec match_rules(global | binary(), [{T, [access_rule()]}], match(), T) -> T. -spec match_rules(global | binary(), [{T, [access_rule()]}], match(), T) -> T.
match_rules(Host, [{Return, Rules} | Rest], Match, Default) -> match_rules(Host, [{Return, Rules} | Rest], Match, Default) ->
case match_acls(Host, Rules, Match) of case match_acls(Host, Rules, Match) of
false -> false ->
match_rules(Host, Rest, Match, Default); match_rules(Host, Rest, Match, Default);
true -> true ->
Return Return
end; end;
match_rules(_Host, [], _Match, Default) -> match_rules(_Host, [], _Match, Default) ->
Default. Default.
-spec match_acls(global | binary(), [access_rule()], match()) -> boolean(). -spec match_acls(global | binary(), [access_rule()], match()) -> boolean().
match_acls(_Host, [], _Match) -> match_acls(_Host, [], _Match) ->
false; false;
match_acls(Host, Rules, Match) -> match_acls(Host, Rules, Match) ->
lists:all( lists:all(
fun(Rule) -> fun(Rule) ->
match_acl(Host, Rule, Match) match_acl(Host, Rule, Match)
end, Rules). end,
Rules).
-spec reload_from_config() -> ok. -spec reload_from_config() -> ok.
reload_from_config() -> reload_from_config() ->
gen_server:call(?MODULE, reload_from_config, timer:minutes(1)). gen_server:call(?MODULE, reload_from_config, timer:minutes(1)).
-spec validator(access_rules | acl) -> econf:validator(). -spec validator(access_rules | acl) -> econf:validator().
validator(access_rules) -> validator(access_rules) ->
econf:options( econf:options(
@ -165,6 +182,7 @@ validator(acl) ->
#{'_' => acl_validator()}, #{'_' => acl_validator()},
[{disallowed, [all, none]}, unique]). [{disallowed, [all, none]}, unique]).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
@ -177,6 +195,7 @@ init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20), ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20),
{ok, #{hosts => Hosts}}. {ok, #{hosts => Hosts}}.
-spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}. -spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}.
handle_call(reload_from_config, _, State) -> handle_call(reload_from_config, _, State) ->
NewHosts = ejabberd_option:hosts(), NewHosts = ejabberd_option:hosts(),
@ -186,24 +205,29 @@ handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
-spec handle_cast(term(), state()) -> {noreply, state()}. -spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
-spec handle_info(term(), state()) -> {noreply, state()}. -spec handle_info(term(), state()) -> {noreply, state()}.
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
-spec terminate(any(), state()) -> ok. -spec terminate(any(), state()) -> ok.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20). ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20).
-spec code_change(term(), state(), term()) -> {ok, state()}. -spec code_change(term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -217,18 +241,21 @@ load_from_config(NewHosts) ->
load_tab(access, NewHosts, fun ejabberd_option:access_rules/1), load_tab(access, NewHosts, fun ejabberd_option:access_rules/1),
?DEBUG("Access rules loaded successfully", []). ?DEBUG("Access rules loaded successfully", []).
-spec create_tab(atom()) -> atom(). -spec create_tab(atom()) -> atom().
create_tab(Tab) -> create_tab(Tab) ->
_ = mnesia:delete_table(Tab), _ = mnesia:delete_table(Tab),
ets:new(Tab, [named_table, set, {read_concurrency, true}]). ets:new(Tab, [named_table, set, {read_concurrency, true}]).
-spec load_tab(atom(), [binary()], fun((global | binary()) -> {atom(), list()})) -> ok. -spec load_tab(atom(), [binary()], fun((global | binary()) -> {atom(), list()})) -> ok.
load_tab(Tab, Hosts, Fun) -> load_tab(Tab, Hosts, Fun) ->
Old = ets:tab2list(Tab), Old = ets:tab2list(Tab),
New = lists:flatmap( New = lists:flatmap(
fun(Host) -> fun(Host) ->
[{{Name, Host}, List} || {Name, List} <- Fun(Host)] [ {{Name, Host}, List} || {Name, List} <- Fun(Host) ]
end, [global|Hosts]), end,
[global | Hosts]),
ets:insert(Tab, New), ets:insert(Tab, New),
lists:foreach( lists:foreach(
fun({Key, _}) -> fun({Key, _}) ->
@ -236,15 +263,18 @@ load_tab(Tab, Hosts, Fun) ->
false -> ets:delete(Tab, Key); false -> ets:delete(Tab, Key);
true -> ok true -> ok
end end
end, Old). end,
Old).
-spec read_access(atom(), global | binary()) -> access(). -spec read_access(atom(), global | binary()) -> access().
read_access(Name, Host) -> read_access(Name, Host) ->
case ets:lookup(access, {Name, Host}) of case ets:lookup(access, {Name, Host}) of
[{_, Access}] -> Access; [{_, Access}] -> Access;
[] -> [] [] -> []
end. end.
-spec read_acl(atom(), global | binary()) -> [acl_rule()]. -spec read_acl(atom(), global | binary()) -> [acl_rule()].
read_acl(Name, Host) -> read_acl(Name, Host) ->
case ets:lookup(acl, {Name, Host}) of case ets:lookup(acl, {Name, Host}) of
@ -252,11 +282,13 @@ read_acl(Name, Host) ->
[] -> [] [] -> []
end. end.
%%%=================================================================== %%%===================================================================
%%% Validators %%% Validators
%%%=================================================================== %%%===================================================================
validators() -> validators() ->
#{ip => econf:list_or_single(econf:ip_mask()), #{
ip => econf:list_or_single(econf:ip_mask()),
user => user_validator(econf:user(), econf:domain()), user => user_validator(econf:user(), econf:domain()),
user_regexp => user_validator(econf:re([unicode]), econf:domain()), user_regexp => user_validator(econf:re([unicode]), econf:domain()),
user_glob => user_validator(econf:glob([unicode]), econf:domain()), user_glob => user_validator(econf:glob([unicode]), econf:domain()),
@ -269,82 +301,96 @@ validators() ->
node_regexp => node_validator(econf:re([unicode]), econf:re([unicode])), node_regexp => node_validator(econf:re([unicode]), econf:re([unicode])),
node_glob => node_validator(econf:glob([unicode]), econf:glob([unicode])), node_glob => node_validator(econf:glob([unicode]), econf:glob([unicode])),
shared_group => user_validator(econf:binary(), econf:domain()), shared_group => user_validator(econf:binary(), econf:domain()),
acl => econf:atom()}. acl => econf:atom()
}.
rule_validator() -> rule_validator() ->
rule_validator(validators()). rule_validator(validators()).
rule_validator(RVs) -> rule_validator(RVs) ->
econf:and_then( econf:and_then(
econf:non_empty(econf:options(RVs, [])), econf:non_empty(econf:options(RVs, [])),
fun(Rules) -> fun(Rules) ->
lists:flatmap( lists:flatmap(
fun({Type, Rs}) when is_list(Rs) -> fun({Type, Rs}) when is_list(Rs) ->
[{Type, R} || R <- Rs]; [ {Type, R} || R <- Rs ];
(Other) -> (Other) ->
[Other] [Other]
end, Rules) end,
Rules)
end). end).
access_validator() -> access_validator() ->
econf:and_then( econf:and_then(
fun(L) when is_list(L) -> fun(L) when is_list(L) ->
lists:map( lists:map(
fun({K, V}) -> {(econf:atom())(K), V}; fun({K, V}) -> {(econf:atom())(K), V};
(A) -> {acl, (econf:atom())(A)} (A) -> {acl, (econf:atom())(A)}
end, lists:flatten(L)); end,
(A) -> lists:flatten(L));
[{acl, (econf:atom())(A)}] (A) ->
[{acl, (econf:atom())(A)}]
end, end,
rule_validator()). rule_validator()).
access_rules_validator() -> access_rules_validator() ->
econf:and_then( econf:and_then(
fun(L) when is_list(L) -> fun(L) when is_list(L) ->
lists:map( lists:map(
fun({K, V}) -> {(econf:atom())(K), V}; fun({K, V}) -> {(econf:atom())(K), V};
(A) -> {(econf:atom())(A), [{acl, all}]} (A) -> {(econf:atom())(A), [{acl, all}]}
end, lists:flatten(L)); end,
(Bad) -> lists:flatten(L));
Bad (Bad) ->
Bad
end, end,
econf:non_empty( econf:non_empty(
econf:options( econf:options(
#{allow => access_validator(), #{
deny => access_validator()}, allow => access_validator(),
[]))). deny => access_validator()
},
[]))).
acl_validator() -> acl_validator() ->
econf:and_then( econf:and_then(
fun(L) when is_list(L) -> lists:flatten(L); fun(L) when is_list(L) -> lists:flatten(L);
(Bad) -> Bad (Bad) -> Bad
end, end,
rule_validator(maps:remove(acl, validators()))). rule_validator(maps:remove(acl, validators()))).
user_validator(UV, SV) -> user_validator(UV, SV) ->
econf:and_then( econf:and_then(
econf:list_or_single( econf:list_or_single(
fun({U, S}) -> fun({U, S}) ->
{UV(U), SV(S)}; {UV(U), SV(S)};
(M) when is_list(M) -> (M) when is_list(M) ->
(econf:map(UV, SV))(M); (econf:map(UV, SV))(M);
(Val) -> (Val) ->
US = (econf:binary())(Val), US = (econf:binary())(Val),
case binary:split(US, <<"@">>, [global]) of case binary:split(US, <<"@">>, [global]) of
[U, S] -> {UV(U), SV(S)}; [U, S] -> {UV(U), SV(S)};
[U] -> UV(U); [U] -> UV(U);
_ -> econf:fail({bad_user, Val}) _ -> econf:fail({bad_user, Val})
end end
end), end),
fun lists:flatten/1). fun lists:flatten/1).
node_validator(UV, SV) -> node_validator(UV, SV) ->
econf:and_then( econf:and_then(
econf:and_then( econf:and_then(
econf:list(econf:any()), econf:list(econf:any()),
fun lists:flatten/1), fun lists:flatten/1),
econf:map(UV, SV)). econf:map(UV, SV)).
%%%=================================================================== %%%===================================================================
%%% Aux %%% Aux
%%%=================================================================== %%%===================================================================
@ -352,15 +398,16 @@ node_validator(UV, SV) ->
match_regexp(Data, RegExp) -> match_regexp(Data, RegExp) ->
re:run(Data, RegExp) /= nomatch. re:run(Data, RegExp) /= nomatch.
-spec loaded_shared_roster_module(global | binary()) -> atom(). -spec loaded_shared_roster_module(global | binary()) -> atom().
loaded_shared_roster_module(global) -> loaded_shared_roster_module(global) ->
loaded_shared_roster_module(ejabberd_config:get_myname()); loaded_shared_roster_module(ejabberd_config:get_myname());
loaded_shared_roster_module(Host) -> loaded_shared_roster_module(Host) ->
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
true -> mod_shared_roster_ldap; true -> mod_shared_roster_ldap;
false -> false ->
case gen_mod:is_loaded(Host, mod_shared_roster) of case gen_mod:is_loaded(Host, mod_shared_roster) of
true -> mod_shared_roster; true -> mod_shared_roster;
false -> undefined false -> undefined
end end
end. end.

View file

@ -65,52 +65,62 @@
-export_type([validator/0, validator/1, validators/0]). -export_type([validator/0, validator/1, validators/0]).
-export_type([error_reason/0, error_return/0]). -export_type([error_reason/0, error_return/0]).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
parse(File, Validators, Options) -> parse(File, Validators, Options) ->
try yconf:parse(File, Validators, Options) try
catch _:{?MODULE, Reason, Ctx} -> yconf:parse(File, Validators, Options)
{error, Reason, Ctx} catch
_:{?MODULE, Reason, Ctx} ->
{error, Reason, Ctx}
end. end.
validate(Validator, Y) -> validate(Validator, Y) ->
try yconf:validate(Validator, Y) try
catch _:{?MODULE, Reason, Ctx} -> yconf:validate(Validator, Y)
{error, Reason, Ctx} catch
_:{?MODULE, Reason, Ctx} ->
{error, Reason, Ctx}
end. end.
replace_macros(Y) -> replace_macros(Y) ->
yconf:replace_macros(Y). yconf:replace_macros(Y).
-spec fail(error_reason()) -> no_return(). -spec fail(error_reason()) -> no_return().
fail(Reason) -> fail(Reason) ->
yconf:fail(?MODULE, Reason). yconf:fail(?MODULE, Reason).
format_error({bad_module, Mod}, Ctx) format_error({bad_module, Mod}, Ctx)
when Ctx == [listen, module]; when Ctx == [listen, module];
Ctx == [listen, request_handlers] -> Ctx == [listen, request_handlers] ->
Mods = ejabberd_config:beams(all), Mods = ejabberd_config:beams(all),
format("~ts: unknown ~ts: ~ts. Did you mean ~ts?", format("~ts: unknown ~ts: ~ts. Did you mean ~ts?",
[yconf:format_ctx(Ctx), [yconf:format_ctx(Ctx),
format_module_type(Ctx), format_module_type(Ctx),
format_module(Mod), format_module(Mod),
format_module(misc:best_match(Mod, Mods))]); format_module(misc:best_match(Mod, Mods))]);
format_error({bad_module, Mod}, Ctx) format_error({bad_module, Mod}, Ctx)
when Ctx == [modules] -> when Ctx == [modules] ->
Mods = lists:filter( Mods = lists:filter(
fun(M) -> fun(M) ->
case atom_to_list(M) of case atom_to_list(M) of
"mod_" ++ _ -> true; "mod_" ++ _ -> true;
"Elixir.Mod" ++ _ -> true; "Elixir.Mod" ++ _ -> true;
_ -> false _ -> false
end end
end, ejabberd_config:beams(all)), end,
ejabberd_config:beams(all)),
format("~ts: unknown ~ts: ~ts. Did you mean ~ts?", format("~ts: unknown ~ts: ~ts. Did you mean ~ts?",
[yconf:format_ctx(Ctx), [yconf:format_ctx(Ctx),
format_module_type(Ctx), format_module_type(Ctx),
format_module(Mod), format_module(Mod),
format_module(misc:best_match(Mod, Mods))]); format_module(misc:best_match(Mod, Mods))]);
format_error({bad_export, {F, A}, Mod}, Ctx) format_error({bad_export, {F, A}, Mod}, Ctx)
when Ctx == [listen, module]; when Ctx == [listen, module];
Ctx == [listen, request_handlers]; Ctx == [listen, request_handlers];
@ -118,46 +128,47 @@ format_error({bad_export, {F, A}, Mod}, Ctx)
Type = format_module_type(Ctx), Type = format_module_type(Ctx),
Slogan = yconf:format_ctx(Ctx), Slogan = yconf:format_ctx(Ctx),
case lists:member(Mod, ejabberd_config:beams(local)) of case lists:member(Mod, ejabberd_config:beams(local)) of
true -> true ->
format("~ts: '~ts' is not a ~ts", format("~ts: '~ts' is not a ~ts",
[Slogan, format_module(Mod), Type]); [Slogan, format_module(Mod), Type]);
false -> false ->
case lists:member(Mod, ejabberd_config:beams(external)) of case lists:member(Mod, ejabberd_config:beams(external)) of
true -> true ->
format("~ts: third-party ~ts '~ts' doesn't export " format("~ts: third-party ~ts '~ts' doesn't export "
"function ~ts/~B. If it's really a ~ts, " "function ~ts/~B. If it's really a ~ts, "
"consider to upgrade it", "consider to upgrade it",
[Slogan, Type, format_module(Mod),F, A, Type]); [Slogan, Type, format_module(Mod), F, A, Type]);
false -> false ->
format("~ts: '~ts' doesn't match any known ~ts", format("~ts: '~ts' doesn't match any known ~ts",
[Slogan, format_module(Mod), Type]) [Slogan, format_module(Mod), Type])
end end
end; end;
format_error({unknown_option, [], _} = Why, Ctx) -> format_error({unknown_option, [], _} = Why, Ctx) ->
format("~ts. There are no available options", format("~ts. There are no available options",
[yconf:format_error(Why, Ctx)]); [yconf:format_error(Why, Ctx)]);
format_error({unknown_option, Known, Opt} = Why, Ctx) -> format_error({unknown_option, Known, Opt} = Why, Ctx) ->
format("~ts. Did you mean ~ts? ~ts", format("~ts. Did you mean ~ts? ~ts",
[yconf:format_error(Why, Ctx), [yconf:format_error(Why, Ctx),
misc:best_match(Opt, Known), misc:best_match(Opt, Known),
format_known("Available options", Known)]); format_known("Available options", Known)]);
format_error({bad_enum, Known, Bad} = Why, Ctx) -> format_error({bad_enum, Known, Bad} = Why, Ctx) ->
format("~ts. Did you mean ~ts? ~ts", format("~ts. Did you mean ~ts? ~ts",
[yconf:format_error(Why, Ctx), [yconf:format_error(Why, Ctx),
misc:best_match(Bad, Known), misc:best_match(Bad, Known),
format_known("Possible values", Known)]); format_known("Possible values", Known)]);
format_error({bad_yaml, _, _} = Why, _) -> format_error({bad_yaml, _, _} = Why, _) ->
format_error(Why); format_error(Why);
format_error(Reason, Ctx) -> format_error(Reason, Ctx) ->
yconf:format_ctx(Ctx) ++ ": " ++ format_error(Reason). yconf:format_ctx(Ctx) ++ ": " ++ format_error(Reason).
format_error({bad_db_type, _, Atom}) -> format_error({bad_db_type, _, Atom}) ->
format("unsupported database: ~ts", [Atom]); format("unsupported database: ~ts", [Atom]);
format_error({bad_lang, Lang}) -> format_error({bad_lang, Lang}) ->
format("Invalid language tag: ~ts", [Lang]); format("Invalid language tag: ~ts", [Lang]);
format_error({bad_pem, Why, Path}) -> format_error({bad_pem, Why, Path}) ->
format("Failed to read PEM file '~ts': ~ts", format("Failed to read PEM file '~ts': ~ts",
[Path, pkix:format_error(Why)]); [Path, pkix:format_error(Why)]);
format_error({bad_cert, Why, Path}) -> format_error({bad_cert, Why, Path}) ->
format_error({bad_pem, Why, Path}); format_error({bad_pem, Why, Path});
format_error({bad_jwt_key, Path}) -> format_error({bad_jwt_key, Path}) ->
@ -178,42 +189,46 @@ format_error({bad_sip_uri, Bad}) ->
format("Invalid SIP URI: ~ts", [Bad]); format("Invalid SIP URI: ~ts", [Bad]);
format_error({route_conflict, R}) -> format_error({route_conflict, R}) ->
format("Failed to reuse route '~ts' because it's " format("Failed to reuse route '~ts' because it's "
"already registered on a virtual host", "already registered on a virtual host",
[R]); [R]);
format_error({listener_dup, AddrPort}) -> format_error({listener_dup, AddrPort}) ->
format("Overlapping listeners found at ~ts", format("Overlapping listeners found at ~ts",
[format_addr_port(AddrPort)]); [format_addr_port(AddrPort)]);
format_error({listener_conflict, AddrPort1, AddrPort2}) -> format_error({listener_conflict, AddrPort1, AddrPort2}) ->
format("Overlapping listeners found at ~ts and ~ts", format("Overlapping listeners found at ~ts and ~ts",
[format_addr_port(AddrPort1), [format_addr_port(AddrPort1),
format_addr_port(AddrPort2)]); format_addr_port(AddrPort2)]);
format_error({invalid_syntax, Reason}) -> format_error({invalid_syntax, Reason}) ->
format("~ts", [Reason]); format("~ts", [Reason]);
format_error({missing_module_dep, Mod, DepMod}) -> format_error({missing_module_dep, Mod, DepMod}) ->
format("module ~ts depends on module ~ts, " format("module ~ts depends on module ~ts, "
"which is not found in the config", "which is not found in the config",
[Mod, DepMod]); [Mod, DepMod]);
format_error(eimp_error) -> format_error(eimp_error) ->
format("ejabberd is built without image converter support", []); format("ejabberd is built without image converter support", []);
format_error({mqtt_codec, Reason}) -> format_error({mqtt_codec, Reason}) ->
mqtt_codec:format_error(Reason); mqtt_codec:format_error(Reason);
format_error({external_module_error, Module, Error}) -> format_error({external_module_error, Module, Error}) ->
try Module:format_error(Error) try
catch _:_ -> Module:format_error(Error)
format("Invalid value", []) catch
_:_ ->
format("Invalid value", [])
end; end;
format_error(Reason) -> format_error(Reason) ->
yconf:format_error(Reason). yconf:format_error(Reason).
-spec format_module(atom() | string()) -> string(). -spec format_module(atom() | string()) -> string().
format_module(Mod) when is_atom(Mod) -> format_module(Mod) when is_atom(Mod) ->
format_module(atom_to_list(Mod)); format_module(atom_to_list(Mod));
format_module(Mod) -> format_module(Mod) ->
case Mod of case Mod of
"Elixir." ++ M -> M; "Elixir." ++ M -> M;
M -> M M -> M
end. end.
format_module_type([listen, module]) -> format_module_type([listen, module]) ->
"listening module"; "listening module";
format_module_type([listen, request_handlers]) -> format_module_type([listen, request_handlers]) ->
@ -221,18 +236,21 @@ format_module_type([listen, request_handlers]) ->
format_module_type([modules]) -> format_module_type([modules]) ->
"ejabberd module". "ejabberd module".
format_known(_, Known) when length(Known) > 20 -> format_known(_, Known) when length(Known) > 20 ->
""; "";
format_known(Prefix, Known) -> format_known(Prefix, Known) ->
[Prefix, " are: ", format_join(Known)]. [Prefix, " are: ", format_join(Known)].
format_join([]) -> format_join([]) ->
"(empty)"; "(empty)";
format_join([H|_] = L) when is_atom(H) -> format_join([H | _] = L) when is_atom(H) ->
format_join([atom_to_binary(A, utf8) || A <- L]); format_join([ atom_to_binary(A, utf8) || A <- L ]);
format_join(L) -> format_join(L) ->
str:join(lists:sort(L), <<", ">>). str:join(lists:sort(L), <<", ">>).
%% All duplicated options having list-values are grouped %% All duplicated options having list-values are grouped
%% into a single option with all list-values being concatenated %% into a single option with all list-values being concatenated
-spec group_dups(list(T)) -> list(T). -spec group_dups(list(T)) -> list(T).
@ -244,11 +262,14 @@ group_dups(Y1) ->
{Option, Vals} when is_list(Vals) -> {Option, Vals} when is_list(Vals) ->
lists:keyreplace(Option, 1, Acc, {Option, Vals ++ Values}); lists:keyreplace(Option, 1, Acc, {Option, Vals ++ Values});
_ -> _ ->
[{Option, Values}|Acc] [{Option, Values} | Acc]
end; end;
(Other, Acc) -> (Other, Acc) ->
[Other|Acc] [Other | Acc]
end, [], Y1)). end,
[],
Y1)).
%%%=================================================================== %%%===================================================================
%%% Validators from yconf %%% Validators from yconf
@ -256,371 +277,452 @@ group_dups(Y1) ->
pos_int() -> pos_int() ->
yconf:pos_int(). yconf:pos_int().
pos_int(Inf) -> pos_int(Inf) ->
yconf:pos_int(Inf). yconf:pos_int(Inf).
non_neg_int() -> non_neg_int() ->
yconf:non_neg_int(). yconf:non_neg_int().
non_neg_int(Inf) -> non_neg_int(Inf) ->
yconf:non_neg_int(Inf). yconf:non_neg_int(Inf).
int() -> int() ->
yconf:int(). yconf:int().
int(Min, Max) -> int(Min, Max) ->
yconf:int(Min, Max). yconf:int(Min, Max).
number(Min) -> number(Min) ->
yconf:number(Min). yconf:number(Min).
octal() -> octal() ->
yconf:octal(). yconf:octal().
binary() -> binary() ->
yconf:binary(). yconf:binary().
binary(Re) -> binary(Re) ->
yconf:binary(Re). yconf:binary(Re).
binary(Re, Opts) -> binary(Re, Opts) ->
yconf:binary(Re, Opts). yconf:binary(Re, Opts).
enum(L) -> enum(L) ->
yconf:enum(L). yconf:enum(L).
bool() -> bool() ->
yconf:bool(). yconf:bool().
atom() -> atom() ->
yconf:atom(). yconf:atom().
string() -> string() ->
yconf:string(). yconf:string().
string(Re) -> string(Re) ->
yconf:string(Re). yconf:string(Re).
string(Re, Opts) -> string(Re, Opts) ->
yconf:string(Re, Opts). yconf:string(Re, Opts).
any() -> any() ->
yconf:any(). yconf:any().
url() -> url() ->
yconf:url(). yconf:url().
url(Schemes) -> url(Schemes) ->
yconf:url(Schemes). yconf:url(Schemes).
file() -> file() ->
yconf:file(). yconf:file().
file(Type) -> file(Type) ->
yconf:file(Type). yconf:file(Type).
directory() -> directory() ->
yconf:directory(). yconf:directory().
directory(Type) -> directory(Type) ->
yconf:directory(Type). yconf:directory(Type).
ip() -> ip() ->
yconf:ip(). yconf:ip().
ipv4() -> ipv4() ->
yconf:ipv4(). yconf:ipv4().
ipv6() -> ipv6() ->
yconf:ipv6(). yconf:ipv6().
ip_mask() -> ip_mask() ->
yconf:ip_mask(). yconf:ip_mask().
port() -> port() ->
yconf:port(). yconf:port().
re() -> re() ->
yconf:re(). yconf:re().
re(Opts) -> re(Opts) ->
yconf:re(Opts). yconf:re(Opts).
glob() -> glob() ->
yconf:glob(). yconf:glob().
glob(Opts) -> glob(Opts) ->
yconf:glob(Opts). yconf:glob(Opts).
path() -> path() ->
yconf:path(). yconf:path().
binary_sep(Sep) -> binary_sep(Sep) ->
yconf:binary_sep(Sep). yconf:binary_sep(Sep).
timeout(Units) -> timeout(Units) ->
yconf:timeout(Units). yconf:timeout(Units).
timeout(Units, Inf) -> timeout(Units, Inf) ->
yconf:timeout(Units, Inf). yconf:timeout(Units, Inf).
base64() -> base64() ->
yconf:base64(). yconf:base64().
non_empty(F) -> non_empty(F) ->
yconf:non_empty(F). yconf:non_empty(F).
list(F) -> list(F) ->
yconf:list(F). yconf:list(F).
list(F, Opts) -> list(F, Opts) ->
yconf:list(F, Opts). yconf:list(F, Opts).
list_or_single(F) -> list_or_single(F) ->
yconf:list_or_single(F). yconf:list_or_single(F).
list_or_single(F, Opts) -> list_or_single(F, Opts) ->
yconf:list_or_single(F, Opts). yconf:list_or_single(F, Opts).
map(F1, F2) -> map(F1, F2) ->
yconf:map(F1, F2). yconf:map(F1, F2).
map(F1, F2, Opts) -> map(F1, F2, Opts) ->
yconf:map(F1, F2, Opts). yconf:map(F1, F2, Opts).
either(F1, F2) -> either(F1, F2) ->
yconf:either(F1, F2). yconf:either(F1, F2).
and_then(F1, F2) -> and_then(F1, F2) ->
yconf:and_then(F1, F2). yconf:and_then(F1, F2).
options(V) -> options(V) ->
yconf:options(V). yconf:options(V).
options(V, O) -> options(V, O) ->
yconf:options(V, O). yconf:options(V, O).
%%%=================================================================== %%%===================================================================
%%% Custom validators %%% Custom validators
%%%=================================================================== %%%===================================================================
beam() -> beam() ->
beam([]). beam([]).
beam(Exports) -> beam(Exports) ->
and_then( and_then(
non_empty(binary()), non_empty(binary()),
fun(<<"Elixir.", _/binary>> = Val) -> fun(<<"Elixir.", _/binary>> = Val) ->
(yconf:beam(Exports))(Val); (yconf:beam(Exports))(Val);
(<<C, _/binary>> = Val) when C >= $A, C =< $Z -> (<<C, _/binary>> = Val) when C >= $A, C =< $Z ->
(yconf:beam(Exports))(<<"Elixir.", Val/binary>>); (yconf:beam(Exports))(<<"Elixir.", Val/binary>>);
(Val) -> (Val) ->
(yconf:beam(Exports))(Val) (yconf:beam(Exports))(Val)
end). end).
acl() -> acl() ->
either( either(
atom(), atom(),
acl:access_rules_validator()). acl:access_rules_validator()).
shaper() -> shaper() ->
either( either(
atom(), atom(),
ejabberd_shaper:shaper_rules_validator()). ejabberd_shaper:shaper_rules_validator()).
-spec url_or_file() -> yconf:validator({file | url, binary()}). -spec url_or_file() -> yconf:validator({file | url, binary()}).
url_or_file() -> url_or_file() ->
either( either(
and_then(url(), fun(URL) -> {url, URL} end), and_then(url(), fun(URL) -> {url, URL} end),
and_then(file(), fun(File) -> {file, File} end)). and_then(file(), fun(File) -> {file, File} end)).
-spec lang() -> yconf:validator(binary()). -spec lang() -> yconf:validator(binary()).
lang() -> lang() ->
and_then( and_then(
binary(), binary(),
fun(Lang) -> fun(Lang) ->
try xmpp_lang:check(Lang) try
catch _:_ -> fail({bad_lang, Lang}) xmpp_lang:check(Lang)
end catch
_:_ -> fail({bad_lang, Lang})
end
end). end).
-spec pem() -> yconf:validator(binary()). -spec pem() -> yconf:validator(binary()).
pem() -> pem() ->
and_then( and_then(
path(), path(),
fun(Path) -> fun(Path) ->
case pkix:is_pem_file(Path) of case pkix:is_pem_file(Path) of
true -> Path; true -> Path;
{false, Reason} -> {false, Reason} ->
fail({bad_pem, Reason, Path}) fail({bad_pem, Reason, Path})
end end
end). end).
-spec jid() -> yconf:validator(jid:jid()). -spec jid() -> yconf:validator(jid:jid()).
jid() -> jid() ->
and_then( and_then(
binary(), binary(),
fun(Val) -> fun(Val) ->
try jid:decode(Val) try
catch _:{bad_jid, _} = Reason -> fail(Reason) jid:decode(Val)
end catch
_:{bad_jid, _} = Reason -> fail(Reason)
end
end). end).
-spec user() -> yconf:validator(binary()). -spec user() -> yconf:validator(binary()).
user() -> user() ->
and_then( and_then(
binary(), binary(),
fun(Val) -> fun(Val) ->
case jid:nodeprep(Val) of case jid:nodeprep(Val) of
error -> fail({bad_user, Val}); error -> fail({bad_user, Val});
U -> U U -> U
end end
end). end).
-spec domain() -> yconf:validator(binary()). -spec domain() -> yconf:validator(binary()).
domain() -> domain() ->
and_then( and_then(
non_empty(binary()), non_empty(binary()),
fun(Val) -> fun(Val) ->
try jid:tolower(jid:decode(Val)) of try jid:tolower(jid:decode(Val)) of
{<<"">>, <<"xn--", _/binary>> = Domain, <<"">>} -> {<<"">>, <<"xn--", _/binary>> = Domain, <<"">>} ->
unicode:characters_to_binary(idna:decode(binary_to_list(Domain)), utf8); unicode:characters_to_binary(idna:decode(binary_to_list(Domain)), utf8);
{<<"">>, Domain, <<"">>} -> Domain; {<<"">>, Domain, <<"">>} -> Domain;
_ -> fail({bad_domain, Val}) _ -> fail({bad_domain, Val})
catch _:{bad_jid, _} -> catch
fail({bad_domain, Val}) _:{bad_jid, _} ->
end fail({bad_domain, Val})
end
end). end).
-spec resource() -> yconf:validator(binary()). -spec resource() -> yconf:validator(binary()).
resource() -> resource() ->
and_then( and_then(
binary(), binary(),
fun(Val) -> fun(Val) ->
case jid:resourceprep(Val) of case jid:resourceprep(Val) of
error -> fail({bad_resource, Val}); error -> fail({bad_resource, Val});
R -> R R -> R
end end
end). end).
-spec db_type(module()) -> yconf:validator(atom()). -spec db_type(module()) -> yconf:validator(atom()).
db_type(M) -> db_type(M) ->
and_then( and_then(
atom(), atom(),
fun(T) -> fun(T) ->
case code:ensure_loaded(db_module(M, T)) of case code:ensure_loaded(db_module(M, T)) of
{module, _} -> T; {module, _} -> T;
{error, _} -> {error, _} ->
ElixirModule = "Elixir." ++ atom_to_list(T), ElixirModule = "Elixir." ++ atom_to_list(T),
case code:ensure_loaded(list_to_atom(ElixirModule)) of case code:ensure_loaded(list_to_atom(ElixirModule)) of
{module, _} -> list_to_atom(ElixirModule); {module, _} -> list_to_atom(ElixirModule);
{error, _} -> fail({bad_db_type, M, T}) {error, _} -> fail({bad_db_type, M, T})
end end
end end
end). end).
-spec queue_type() -> yconf:validator(ram | file). -spec queue_type() -> yconf:validator(ram | file).
queue_type() -> queue_type() ->
enum([ram, file]). enum([ram, file]).
-spec ldap_filter() -> yconf:validator(binary()). -spec ldap_filter() -> yconf:validator(binary()).
ldap_filter() -> ldap_filter() ->
and_then( and_then(
binary(), binary(),
fun(Val) -> fun(Val) ->
case eldap_filter:parse(Val) of case eldap_filter:parse(Val) of
{ok, _} -> Val; {ok, _} -> Val;
_ -> fail({bad_ldap_filter, Val}) _ -> fail({bad_ldap_filter, Val})
end end
end). end).
-ifdef(SIP). -ifdef(SIP).
sip_uri() -> sip_uri() ->
and_then( and_then(
binary(), binary(),
fun(Val) -> fun(Val) ->
case esip:decode_uri(Val) of case esip:decode_uri(Val) of
error -> fail({bad_sip_uri, Val}); error -> fail({bad_sip_uri, Val});
URI -> URI URI -> URI
end end
end). end).
-endif. -endif.
-spec host() -> yconf:validator(binary()). -spec host() -> yconf:validator(binary()).
host() -> host() ->
fun(Domain) -> fun(Domain) ->
Hosts = ejabberd_config:get_option(hosts), Hosts = ejabberd_config:get_option(hosts),
Domain3 = (domain())(Domain), Domain3 = (domain())(Domain),
case lists:member(Domain3, Hosts) of case lists:member(Domain3, Hosts) of
true -> fail({route_conflict, Domain}); true -> fail({route_conflict, Domain});
false -> Domain3 false -> Domain3
end end
end. end.
-spec hosts() -> yconf:validator([binary()]). -spec hosts() -> yconf:validator([binary()]).
hosts() -> hosts() ->
list(host(), [unique]). list(host(), [unique]).
-spec vcard_temp() -> yconf:validator(). -spec vcard_temp() -> yconf:validator().
vcard_temp() -> vcard_temp() ->
and_then( and_then(
vcard_validator( vcard_validator(
vcard_temp, undefined, vcard_temp,
[{version, undefined, binary()}, undefined,
{fn, undefined, binary()}, [{version, undefined, binary()},
{n, undefined, vcard_name()}, {fn, undefined, binary()},
{nickname, undefined, binary()}, {n, undefined, vcard_name()},
{photo, undefined, vcard_photo()}, {nickname, undefined, binary()},
{bday, undefined, binary()}, {photo, undefined, vcard_photo()},
{adr, [], list(vcard_adr())}, {bday, undefined, binary()},
{label, [], list(vcard_label())}, {adr, [], list(vcard_adr())},
{tel, [], list(vcard_tel())}, {label, [], list(vcard_label())},
{email, [], list(vcard_email())}, {tel, [], list(vcard_tel())},
{jabberid, undefined, binary()}, {email, [], list(vcard_email())},
{mailer, undefined, binary()}, {jabberid, undefined, binary()},
{tz, undefined, binary()}, {mailer, undefined, binary()},
{geo, undefined, vcard_geo()}, {tz, undefined, binary()},
{title, undefined, binary()}, {geo, undefined, vcard_geo()},
{role, undefined, binary()}, {title, undefined, binary()},
{logo, undefined, vcard_logo()}, {role, undefined, binary()},
{org, undefined, vcard_org()}, {logo, undefined, vcard_logo()},
{categories, [], list(binary())}, {org, undefined, vcard_org()},
{note, undefined, binary()}, {categories, [], list(binary())},
{prodid, undefined, binary()}, {note, undefined, binary()},
{rev, undefined, binary()}, {prodid, undefined, binary()},
{sort_string, undefined, binary()}, {rev, undefined, binary()},
{sound, undefined, vcard_sound()}, {sort_string, undefined, binary()},
{uid, undefined, binary()}, {sound, undefined, vcard_sound()},
{url, undefined, binary()}, {uid, undefined, binary()},
{class, undefined, enum([confidential, private, public])}, {url, undefined, binary()},
{key, undefined, vcard_key()}, {class, undefined, enum([confidential, private, public])},
{desc, undefined, binary()}]), {key, undefined, vcard_key()},
fun(Tuple) -> {desc, undefined, binary()}]),
list_to_tuple(tuple_to_list(Tuple) ++ [[]]) fun(Tuple) ->
end). list_to_tuple(tuple_to_list(Tuple) ++ [[]])
end).
-spec vcard_name() -> yconf:validator(). -spec vcard_name() -> yconf:validator().
vcard_name() -> vcard_name() ->
vcard_validator( vcard_validator(
vcard_name, undefined, vcard_name,
undefined,
[{family, undefined, binary()}, [{family, undefined, binary()},
{given, undefined, binary()}, {given, undefined, binary()},
{middle, undefined, binary()}, {middle, undefined, binary()},
{prefix, undefined, binary()}, {prefix, undefined, binary()},
{suffix, undefined, binary()}]). {suffix, undefined, binary()}]).
-spec vcard_photo() -> yconf:validator(). -spec vcard_photo() -> yconf:validator().
vcard_photo() -> vcard_photo() ->
vcard_validator( vcard_validator(
vcard_photo, undefined, vcard_photo,
undefined,
[{type, undefined, binary()}, [{type, undefined, binary()},
{binval, undefined, base64()}, {binval, undefined, base64()},
{extval, undefined, binary()}]). {extval, undefined, binary()}]).
-spec vcard_adr() -> yconf:validator(). -spec vcard_adr() -> yconf:validator().
vcard_adr() -> vcard_adr() ->
vcard_validator( vcard_validator(
vcard_adr, [], vcard_adr,
[],
[{home, false, bool()}, [{home, false, bool()},
{work, false, bool()}, {work, false, bool()},
{postal, false, bool()}, {postal, false, bool()},
@ -636,10 +738,12 @@ vcard_adr() ->
{pcode, undefined, binary()}, {pcode, undefined, binary()},
{ctry, undefined, binary()}]). {ctry, undefined, binary()}]).
-spec vcard_label() -> yconf:validator(). -spec vcard_label() -> yconf:validator().
vcard_label() -> vcard_label() ->
vcard_validator( vcard_validator(
vcard_label, [], vcard_label,
[],
[{home, false, bool()}, [{home, false, bool()},
{work, false, bool()}, {work, false, bool()},
{postal, false, bool()}, {postal, false, bool()},
@ -649,10 +753,12 @@ vcard_label() ->
{pref, false, bool()}, {pref, false, bool()},
{line, [], list(binary())}]). {line, [], list(binary())}]).
-spec vcard_tel() -> yconf:validator(). -spec vcard_tel() -> yconf:validator().
vcard_tel() -> vcard_tel() ->
vcard_validator( vcard_validator(
vcard_tel, [], vcard_tel,
[],
[{home, false, bool()}, [{home, false, bool()},
{work, false, bool()}, {work, false, bool()},
{voice, false, bool()}, {voice, false, bool()},
@ -668,10 +774,12 @@ vcard_tel() ->
{pref, false, bool()}, {pref, false, bool()},
{number, undefined, binary()}]). {number, undefined, binary()}]).
-spec vcard_email() -> yconf:validator(). -spec vcard_email() -> yconf:validator().
vcard_email() -> vcard_email() ->
vcard_validator( vcard_validator(
vcard_email, [], vcard_email,
[],
[{home, false, bool()}, [{home, false, bool()},
{work, false, bool()}, {work, false, bool()},
{internet, false, bool()}, {internet, false, bool()},
@ -679,77 +787,94 @@ vcard_email() ->
{x400, false, bool()}, {x400, false, bool()},
{userid, undefined, binary()}]). {userid, undefined, binary()}]).
-spec vcard_geo() -> yconf:validator(). -spec vcard_geo() -> yconf:validator().
vcard_geo() -> vcard_geo() ->
vcard_validator( vcard_validator(
vcard_geo, undefined, vcard_geo,
undefined,
[{lat, undefined, binary()}, [{lat, undefined, binary()},
{lon, undefined, binary()}]). {lon, undefined, binary()}]).
-spec vcard_logo() -> yconf:validator(). -spec vcard_logo() -> yconf:validator().
vcard_logo() -> vcard_logo() ->
vcard_validator( vcard_validator(
vcard_logo, undefined, vcard_logo,
undefined,
[{type, undefined, binary()}, [{type, undefined, binary()},
{binval, undefined, base64()}, {binval, undefined, base64()},
{extval, undefined, binary()}]). {extval, undefined, binary()}]).
-spec vcard_org() -> yconf:validator(). -spec vcard_org() -> yconf:validator().
vcard_org() -> vcard_org() ->
vcard_validator( vcard_validator(
vcard_org, undefined, vcard_org,
undefined,
[{name, undefined, binary()}, [{name, undefined, binary()},
{units, [], list(binary())}]). {units, [], list(binary())}]).
-spec vcard_sound() -> yconf:validator(). -spec vcard_sound() -> yconf:validator().
vcard_sound() -> vcard_sound() ->
vcard_validator( vcard_validator(
vcard_sound, undefined, vcard_sound,
undefined,
[{phonetic, undefined, binary()}, [{phonetic, undefined, binary()},
{binval, undefined, base64()}, {binval, undefined, base64()},
{extval, undefined, binary()}]). {extval, undefined, binary()}]).
-spec vcard_key() -> yconf:validator(). -spec vcard_key() -> yconf:validator().
vcard_key() -> vcard_key() ->
vcard_validator( vcard_validator(
vcard_key, undefined, vcard_key,
undefined,
[{type, undefined, binary()}, [{type, undefined, binary()},
{cred, undefined, binary()}]). {cred, undefined, binary()}]).
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
-spec db_module(module(), atom()) -> module(). -spec db_module(module(), atom()) -> module().
db_module(M, Type) -> db_module(M, Type) ->
try list_to_atom(atom_to_list(M) ++ "_" ++ atom_to_list(Type)) try
catch _:system_limit -> list_to_atom(atom_to_list(M) ++ "_" ++ atom_to_list(Type))
fail({bad_length, 255}) catch
_:system_limit ->
fail({bad_length, 255})
end. end.
format_addr_port({IP, Port}) -> format_addr_port({IP, Port}) ->
IPStr = case tuple_size(IP) of IPStr = case tuple_size(IP) of
4 -> inet:ntoa(IP); 4 -> inet:ntoa(IP);
8 -> "[" ++ inet:ntoa(IP) ++ "]" 8 -> "[" ++ inet:ntoa(IP) ++ "]"
end, end,
IPStr ++ ":" ++ integer_to_list(Port). IPStr ++ ":" ++ integer_to_list(Port).
-spec format(iolist(), list()) -> string(). -spec format(iolist(), list()) -> string().
format(Fmt, Args) -> format(Fmt, Args) ->
lists:flatten(io_lib:format(Fmt, Args)). lists:flatten(io_lib:format(Fmt, Args)).
-spec vcard_validator(atom(), term(), [{atom(), term(), validator()}]) -> validator(). -spec vcard_validator(atom(), term(), [{atom(), term(), validator()}]) -> validator().
vcard_validator(Name, Default, Schema) -> vcard_validator(Name, Default, Schema) ->
Defaults = [{Key, Val} || {Key, Val, _} <- Schema], Defaults = [ {Key, Val} || {Key, Val, _} <- Schema ],
and_then( and_then(
options( options(
maps:from_list([{Key, Fun} || {Key, _, Fun} <- Schema]), maps:from_list([ {Key, Fun} || {Key, _, Fun} <- Schema ]),
[{return, map}, {unique, true}]), [{return, map}, {unique, true}]),
fun(Options) -> fun(Options) ->
merge(Defaults, Options, Name, Default) merge(Defaults, Options, Name, Default)
end). end).
-spec merge([{atom(), term()}], #{atom() => term()}, atom(), T) -> tuple() | T. -spec merge([{atom(), term()}], #{atom() => term()}, atom(), T) -> tuple() | T.
merge(_, Options, _, Default) when Options == #{} -> merge(_, Options, _, Default) when Options == #{} ->
Default; Default;
merge(Defaults, Options, Name, _) -> merge(Defaults, Options, Name, _) ->
list_to_tuple([Name|[maps:get(Key, Options, Val) || {Key, Val} <- Defaults]]). list_to_tuple([Name | [ maps:get(Key, Options, Val) || {Key, Val} <- Defaults ]]).

View file

@ -43,56 +43,70 @@
-protocol({xep, 440, '0.4.0', '24.02', "complete", ""}). -protocol({xep, 440, '0.4.0', '24.02', "complete", ""}).
-protocol({xep, 474, '0.4.0', '24.02', "complete", "0.4.0 since 25.03"}). -protocol({xep, 474, '0.4.0', '24.02', "complete", "0.4.0 since 25.03"}).
-export([start/0, stop/0, halt/0, start_app/1, start_app/2, -export([start/0,
get_pid_file/0, check_apps/0, module_name/1, is_loaded/0]). stop/0,
halt/0,
start_app/1, start_app/2,
get_pid_file/0,
check_apps/0,
module_name/1,
is_loaded/0]).
-include("logger.hrl"). -include("logger.hrl").
start() -> start() ->
case application:ensure_all_started(ejabberd) of case application:ensure_all_started(ejabberd) of
{error, Err} -> error_logger:error_msg("Failed to start ejabberd application: ~p", [Err]); {error, Err} -> error_logger:error_msg("Failed to start ejabberd application: ~p", [Err]);
Ok -> Ok Ok -> Ok
end. end.
stop() -> stop() ->
application:stop(ejabberd). application:stop(ejabberd).
halt() -> halt() ->
ejabberd_logger:flush(), ejabberd_logger:flush(),
erlang:halt(1, [{flush, true}]). erlang:halt(1, [{flush, true}]).
-spec get_pid_file() -> false | string(). -spec get_pid_file() -> false | string().
get_pid_file() -> get_pid_file() ->
case os:getenv("EJABBERD_PID_PATH") of case os:getenv("EJABBERD_PID_PATH") of
false -> false ->
false; false;
"" -> "" ->
false; false;
Path -> Path ->
Path Path
end. end.
start_app(App) -> start_app(App) ->
start_app(App, temporary). start_app(App, temporary).
start_app(App, Type) -> start_app(App, Type) ->
StartFlag = not is_loaded(), StartFlag = not is_loaded(),
start_app(App, Type, StartFlag). start_app(App, Type, StartFlag).
is_loaded() -> is_loaded() ->
Apps = application:which_applications(), Apps = application:which_applications(),
lists:keymember(ejabberd, 1, Apps). lists:keymember(ejabberd, 1, Apps).
start_app(App, Type, StartFlag) when is_atom(App) -> start_app(App, Type, StartFlag) when is_atom(App) ->
start_app([App], Type, StartFlag); start_app([App], Type, StartFlag);
start_app([App|Apps], Type, StartFlag) -> start_app([App | Apps], Type, StartFlag) ->
case application:start(App,Type) of case application:start(App, Type) of
ok -> ok ->
start_app(Apps, Type, StartFlag); start_app(Apps, Type, StartFlag);
{error, {already_started, _}} -> {error, {already_started, _}} ->
start_app(Apps, Type, StartFlag); start_app(Apps, Type, StartFlag);
{error, {not_started, DepApp}} -> {error, {not_started, DepApp}} ->
case lists:member(DepApp, [App|Apps]) of case lists:member(DepApp, [App | Apps]) of
true -> true ->
Reason = io_lib:format( Reason = io_lib:format(
"Failed to start Erlang application '~ts': " "Failed to start Erlang application '~ts': "
@ -100,17 +114,18 @@ start_app([App|Apps], Type, StartFlag) ->
[App, DepApp]), [App, DepApp]),
exit_or_halt(Reason, StartFlag); exit_or_halt(Reason, StartFlag);
false -> false ->
start_app([DepApp,App|Apps], Type, StartFlag) start_app([DepApp, App | Apps], Type, StartFlag)
end; end;
{error, Why} -> {error, Why} ->
Reason = io_lib:format( Reason = io_lib:format(
"Failed to start Erlang application '~ts': ~ts. ~ts", "Failed to start Erlang application '~ts': ~ts. ~ts",
[App, format_error(Why), hint()]), [App, format_error(Why), hint()]),
exit_or_halt(Reason, StartFlag) exit_or_halt(Reason, StartFlag)
end; end;
start_app([], _Type, _StartFlag) -> start_app([], _Type, _StartFlag) ->
ok. ok.
check_app_modules(App, StartFlag) -> check_app_modules(App, StartFlag) ->
case application:get_key(App, modules) of case application:get_key(App, modules) of
{ok, Mods} -> {ok, Mods} ->
@ -121,44 +136,50 @@ check_app_modules(App, StartFlag) ->
File = get_module_file(App, Mod), File = get_module_file(App, Mod),
Reason = io_lib:format( Reason = io_lib:format(
"Couldn't find file ~ts needed " "Couldn't find file ~ts needed "
"for Erlang application '~ts'. ~ts", "for Erlang application '~ts'. ~ts",
[File, App, hint()]), [File, App, hint()]),
exit_or_halt(Reason, StartFlag); exit_or_halt(Reason, StartFlag);
_ -> _ ->
ok ok
end end
end, Mods); end,
Mods);
_ -> _ ->
%% No modules? This is strange %% No modules? This is strange
ok ok
end. end.
check_apps() -> check_apps() ->
spawn( spawn(
fun() -> fun() ->
Apps = [ejabberd | Apps = [ejabberd | [ App || {App, _, _} <- application:which_applications(),
[App || {App, _, _} <- application:which_applications(), App /= ejabberd,
App /= ejabberd, App /= hex]], App /= hex ]],
?DEBUG("Checking consistency of applications: ~ts", ?DEBUG("Checking consistency of applications: ~ts",
[misc:join_atoms(Apps, <<", ">>)]), [misc:join_atoms(Apps, <<", ">>)]),
misc:peach( misc:peach(
fun(App) -> fun(App) ->
check_app_modules(App, true) check_app_modules(App, true)
end, Apps), end,
?DEBUG("All applications are intact", []), Apps),
lists:foreach(fun erlang:garbage_collect/1, processes()) ?DEBUG("All applications are intact", []),
lists:foreach(fun erlang:garbage_collect/1, processes())
end). end).
-spec exit_or_halt(iodata(), boolean()) -> no_return(). -spec exit_or_halt(iodata(), boolean()) -> no_return().
exit_or_halt(Reason, StartFlag) -> exit_or_halt(Reason, StartFlag) ->
?CRITICAL_MSG(Reason, []), ?CRITICAL_MSG(Reason, []),
if StartFlag -> if
StartFlag ->
%% Wait for the critical message is written in the console/log %% Wait for the critical message is written in the console/log
halt(); halt();
true -> true ->
erlang:error(application_start_failed) erlang:error(application_start_failed)
end. end.
get_module_file(App, Mod) -> get_module_file(App, Mod) ->
BaseName = atom_to_list(Mod), BaseName = atom_to_list(Mod),
case code:lib_dir(App) of case code:lib_dir(App) of
@ -168,46 +189,51 @@ get_module_file(App, Mod) ->
filename:join([Dir, "ebin", BaseName ++ ".beam"]) filename:join([Dir, "ebin", BaseName ++ ".beam"])
end. end.
module_name([Dir, _, <<H,_/binary>> | _] = Mod) when H >= 65, H =< 90 ->
Module = str:join([elixir_name(M) || M<-tl(Mod)], <<>>), module_name([Dir, _, <<H, _/binary>> | _] = Mod) when H >= 65, H =< 90 ->
Module = str:join([ elixir_name(M) || M <- tl(Mod) ], <<>>),
Prefix = case elixir_name(Dir) of Prefix = case elixir_name(Dir) of
<<"Ejabberd">> -> <<"Elixir.Ejabberd.">>; <<"Ejabberd">> -> <<"Elixir.Ejabberd.">>;
Lib -> <<"Elixir.Ejabberd.", Lib/binary, ".">> Lib -> <<"Elixir.Ejabberd.", Lib/binary, ".">>
end, end,
misc:binary_to_atom(<<Prefix/binary, Module/binary>>); misc:binary_to_atom(<<Prefix/binary, Module/binary>>);
module_name([<<"auth">> | T] = Mod) -> module_name([<<"auth">> | T] = Mod) ->
case hd(T) of case hd(T) of
%% T already starts with "Elixir" if an Elixir module is %% T already starts with "Elixir" if an Elixir module is
%% loaded with that name, as per `econf:db_type/1` %% loaded with that name, as per `econf:db_type/1`
<<"Elixir", _/binary>> -> misc:binary_to_atom(hd(T)); <<"Elixir", _/binary>> -> misc:binary_to_atom(hd(T));
_ -> module_name([<<"ejabberd">>] ++ Mod) _ -> module_name([<<"ejabberd">>] ++ Mod)
end; end;
module_name([<<"ejabberd">> | _] = Mod) -> module_name([<<"ejabberd">> | _] = Mod) ->
Module = str:join([erlang_name(M) || M<-Mod], $_), Module = str:join([ erlang_name(M) || M <- Mod ], $_),
misc:binary_to_atom(Module); misc:binary_to_atom(Module);
module_name(Mod) when is_list(Mod) -> module_name(Mod) when is_list(Mod) ->
Module = str:join([erlang_name(M) || M<-tl(Mod)], $_), Module = str:join([ erlang_name(M) || M <- tl(Mod) ], $_),
misc:binary_to_atom(Module). misc:binary_to_atom(Module).
elixir_name(Atom) when is_atom(Atom) -> elixir_name(Atom) when is_atom(Atom) ->
elixir_name(misc:atom_to_binary(Atom)); elixir_name(misc:atom_to_binary(Atom));
elixir_name(<<H,T/binary>>) when H >= 65, H =< 90 -> elixir_name(<<H, T/binary>>) when H >= 65, H =< 90 ->
<<H, T/binary>>; <<H, T/binary>>;
elixir_name(<<H,T/binary>>) -> elixir_name(<<H, T/binary>>) ->
<<(H-32), T/binary>>. <<(H - 32), T/binary>>.
erlang_name(Atom) when is_atom(Atom) -> erlang_name(Atom) when is_atom(Atom) ->
misc:atom_to_binary(Atom); misc:atom_to_binary(Atom);
erlang_name(Bin) when is_binary(Bin) -> erlang_name(Bin) when is_binary(Bin) ->
Bin. Bin.
format_error({Reason, File}) when is_list(Reason), is_list(File) -> format_error({Reason, File}) when is_list(Reason), is_list(File) ->
Reason ++ ": " ++ File; Reason ++ ": " ++ File;
format_error(Term) -> format_error(Term) ->
io_lib:format("~p", [Term]). io_lib:format("~p", [Term]).
hint() -> hint() ->
"This usually means that ejabberd or Erlang " "This usually means that ejabberd or Erlang "
"was compiled/installed incorrectly.". "was compiled/installed incorrectly.".

View file

@ -32,41 +32,43 @@
%% API %% API
-export([start_link/0, -export([start_link/0,
can_access/2, can_access/2,
invalidate/0, invalidate/0,
validator/0, validator/0,
show_current_definitions/0]). show_current_definitions/0]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([init/1,
handle_call/3, handle_call/3,
handle_cast/2, handle_cast/2,
handle_info/2, handle_info/2,
terminate/2, terminate/2,
code_change/3]). code_change/3]).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-define(CACHE_TAB, access_permissions_cache). -define(CACHE_TAB, access_permissions_cache).
-record(state, -record(state, {definitions = none :: none | [definition()]}).
{definitions = none :: none | [definition()]}).
-type state() :: #state{}. -type state() :: #state{}.
-type rule() :: {access, acl:access()} | -type rule() :: {access, acl:access()} |
{acl, all | none | acl:acl_rule()}. {acl, all | none | acl:acl_rule()}.
-type what() :: all | none | [atom() | {tag, atom()}]. -type what() :: all | none | [atom() | {tag, atom()}].
-type who() :: rule() | {oauth, {[binary()], [rule()]}}. -type who() :: rule() | {oauth, {[binary()], [rule()]}}.
-type from() :: atom(). -type from() :: atom().
-type permission() :: {binary(), {[from()], [who()], {what(), what()}}}. -type permission() :: {binary(), {[from()], [who()], {what(), what()}}}.
-type definition() :: {binary(), {[from()], [who()], [atom()] | all}}. -type definition() :: {binary(), {[from()], [who()], [atom()] | all}}.
-type caller_info() :: #{caller_module => module(), -type caller_info() :: #{
caller_host => global | binary(), caller_module => module(),
tag => binary() | none, caller_host => global | binary(),
extra_permissions => [definition()], tag => binary() | none,
atom() => term()}. extra_permissions => [definition()],
atom() => term()
}.
-export_type([permission/0]). -export_type([permission/0]).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
@ -78,41 +80,51 @@ can_access(Cmd, CallerInfo) ->
Tag = maps:get(tag, CallerInfo, none), Tag = maps:get(tag, CallerInfo, none),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0, Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl( Res = lists:foldl(
fun({Name, _} = Def, none) -> fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true -> true ->
?DEBUG("Command '~p' execution allowed by rule " ?DEBUG("Command '~p' execution allowed by rule "
"'~ts'~n (CallerInfo=~p)", [Cmd, Name, CallerInfo]), "'~ts'~n (CallerInfo=~p)",
allow; [Cmd, Name, CallerInfo]),
_ -> allow;
none _ ->
end; none
(_, Val) -> end;
Val (_, Val) ->
end, none, Defs), Val
end,
none,
Defs),
case Res of case Res of
allow -> allow; allow -> allow;
_ -> _ ->
?DEBUG("Command '~p' execution denied~n " ?DEBUG("Command '~p' execution denied~n "
"(CallerInfo=~p)", [Cmd, CallerInfo]), "(CallerInfo=~p)",
deny [Cmd, CallerInfo]),
deny
end. end.
-spec invalidate() -> ok. -spec invalidate() -> ok.
invalidate() -> invalidate() ->
gen_server:cast(?MODULE, invalidate), gen_server:cast(?MODULE, invalidate),
ets_cache:delete(?CACHE_TAB, definitions). ets_cache:delete(?CACHE_TAB, definitions).
-spec show_current_definitions() -> [definition()]. -spec show_current_definitions() -> [definition()].
show_current_definitions() -> show_current_definitions() ->
ets_cache:lookup(?CACHE_TAB, definitions, ets_cache:lookup(?CACHE_TAB,
fun() -> definitions,
{cache, gen_server:call(?MODULE, show_current_definitions)} fun() ->
end). {cache, gen_server:call(?MODULE, show_current_definitions)}
end).
start_link() -> start_link() ->
ets_cache:new(?CACHE_TAB, [{max_size, 2}]), ets_cache:new(?CACHE_TAB, [{max_size, 2}]),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
@ -122,8 +134,10 @@ init([]) ->
ets_cache:new(access_permissions), ets_cache:new(access_permissions),
{ok, #state{}}. {ok, #state{}}.
-spec handle_call(show_current_definitions | term(), -spec handle_call(show_current_definitions | term(),
term(), state()) -> {reply, term(), state()}. term(),
state()) -> {reply, term(), state()}.
handle_call(show_current_definitions, _From, State) -> handle_call(show_current_definitions, _From, State) ->
{State2, Defs} = get_definitions(State), {State2, Defs} = get_definitions(State),
{reply, Defs, State2}; {reply, Defs, State2};
@ -131,6 +145,7 @@ handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
-spec handle_cast(invalidate | term(), state()) -> {noreply, state()}. -spec handle_cast(invalidate | term(), state()) -> {noreply, state()}.
handle_cast(invalidate, State) -> handle_cast(invalidate, State) ->
{noreply, State#state{definitions = none}}; {noreply, State#state{definitions = none}};
@ -138,16 +153,20 @@ handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, invalidate, 90). ejabberd_hooks:delete(config_reloaded, ?MODULE, invalidate, 90).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -158,81 +177,93 @@ get_definitions(#state{definitions = none} = State) ->
ApiPerms = ejabberd_option:api_permissions(), ApiPerms = ejabberd_option:api_permissions(),
AllCommands = ejabberd_commands:get_commands_definition(), AllCommands = ejabberd_commands:get_commands_definition(),
NDefs0 = lists:map( NDefs0 = lists:map(
fun({Name, {From, Who, {Add, Del}}}) -> fun({Name, {From, Who, {Add, Del}}}) ->
Cmds = filter_commands_with_permissions(AllCommands, Add, Del), Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
{Name, {From, Who, Cmds}} {Name, {From, Who, Cmds}}
end, ApiPerms), end,
ApiPerms),
NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of
false -> false ->
[{<<"console commands">>, [{<<"console commands">>,
{[ejabberd_ctl], {[ejabberd_ctl],
[{acl, all}], [{acl, all}],
filter_commands_with_permissions(AllCommands, all, none)}} | NDefs0]; filter_commands_with_permissions(AllCommands, all, none)}} | NDefs0];
_ -> _ ->
NDefs0 NDefs0
end, end,
{State#state{definitions = NDefs}, NDefs}. {State#state{definitions = NDefs}, NDefs}.
-spec matches_definition(definition(), atom(), module(),
atom(), global | binary(), caller_info()) -> boolean(). -spec matches_definition(definition(),
atom(),
module(),
atom(),
global | binary(),
caller_info()) -> boolean().
matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInfo) -> matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInfo) ->
case What == all orelse lists:member(Cmd, What) of case What == all orelse lists:member(Cmd, What) of
true -> true ->
{Tags, Modules} = lists:partition(fun({tag, _}) -> true; (_) -> false end, From), {Tags, Modules} = lists:partition(fun({tag, _}) -> true; (_) -> false end, From),
case (Modules == [] orelse lists:member(Module, Modules)) andalso case (Modules == [] orelse lists:member(Module, Modules)) andalso
(Tags == [] orelse lists:member({tag, Tag}, Tags)) of (Tags == [] orelse lists:member({tag, Tag}, Tags)) of
true -> true ->
Scope = maps:get(oauth_scope, CallerInfo, none), Scope = maps:get(oauth_scope, CallerInfo, none),
lists:any( lists:any(
fun({access, Access}) when Scope == none -> fun({access, Access}) when Scope == none ->
acl:match_rule(Host, Access, CallerInfo) == allow; acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when Scope == none, is_atom(Name) -> ({acl, Name} = Acl) when Scope == none, is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo); acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) when Scope == none -> ({acl, Acl}) when Scope == none ->
acl:match_acl(Host, Acl, CallerInfo); acl:match_acl(Host, Acl, CallerInfo);
({oauth, {Scopes, List}}) when Scope /= none -> ({oauth, {Scopes, List}}) when Scope /= none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
true -> true ->
lists:any( lists:any(
fun({access, Access}) -> fun({access, Access}) ->
acl:match_rule(Host, Access, CallerInfo) == allow; acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when is_atom(Name) -> ({acl, Name} = Acl) when is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo); acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) -> ({acl, Acl}) ->
acl:match_acl(Host, Acl, CallerInfo) acl:match_acl(Host, Acl, CallerInfo)
end, List); end,
_ -> List);
false _ ->
end; false
(_) -> end;
false (_) ->
end, Who); false
_ -> end,
false Who);
end; _ ->
_ -> false
false end;
_ ->
false
end. end.
-spec filter_commands_with_permissions([#ejabberd_commands{}], what(), what()) -> [atom()]. -spec filter_commands_with_permissions([#ejabberd_commands{}], what(), what()) -> [atom()].
filter_commands_with_permissions(AllCommands, Add, Del) -> filter_commands_with_permissions(AllCommands, Add, Del) ->
CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []), CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []),
CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []), CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []),
lists:map(fun(#ejabberd_commands{name = N}) -> N end, lists:map(fun(#ejabberd_commands{name = N}) -> N end,
CommandsAdd -- CommandsDel). CommandsAdd -- CommandsDel).
-spec filter_commands_with_patterns([#ejabberd_commands{}], what(),
[#ejabberd_commands{}]) -> [#ejabberd_commands{}]. -spec filter_commands_with_patterns([#ejabberd_commands{}],
what(),
[#ejabberd_commands{}]) -> [#ejabberd_commands{}].
filter_commands_with_patterns([], _Patterns, Acc) -> filter_commands_with_patterns([], _Patterns, Acc) ->
Acc; Acc;
filter_commands_with_patterns([C | CRest], Patterns, Acc) -> filter_commands_with_patterns([C | CRest], Patterns, Acc) ->
case command_matches_patterns(C, Patterns) of case command_matches_patterns(C, Patterns) of
true -> true ->
filter_commands_with_patterns(CRest, Patterns, [C | Acc]); filter_commands_with_patterns(CRest, Patterns, [C | Acc]);
_ -> _ ->
filter_commands_with_patterns(CRest, Patterns, Acc) filter_commands_with_patterns(CRest, Patterns, Acc)
end. end.
-spec command_matches_patterns(#ejabberd_commands{}, what()) -> boolean(). -spec command_matches_patterns(#ejabberd_commands{}, what()) -> boolean().
command_matches_patterns(_, all) -> command_matches_patterns(_, all) ->
true; true;
@ -242,46 +273,50 @@ command_matches_patterns(_, []) ->
false; false;
command_matches_patterns(#ejabberd_commands{tags = Tags} = C, [{tag, Tag} | Tail]) -> command_matches_patterns(#ejabberd_commands{tags = Tags} = C, [{tag, Tag} | Tail]) ->
case lists:member(Tag, Tags) of case lists:member(Tag, Tags) of
true -> true ->
true; true;
_ -> _ ->
command_matches_patterns(C, Tail) command_matches_patterns(C, Tail)
end; end;
command_matches_patterns(#ejabberd_commands{name = Name}, [Name | _Tail]) -> command_matches_patterns(#ejabberd_commands{name = Name}, [Name | _Tail]) ->
true; true;
command_matches_patterns(C, [_ | Tail]) -> command_matches_patterns(C, [_ | Tail]) ->
command_matches_patterns(C, Tail). command_matches_patterns(C, Tail).
%%%=================================================================== %%%===================================================================
%%% Validators %%% Validators
%%%=================================================================== %%%===================================================================
-spec parse_what([binary()]) -> {what(), what()}. -spec parse_what([binary()]) -> {what(), what()}.
parse_what(Defs) -> parse_what(Defs) ->
{A, D} = {A, D} =
lists:foldl( lists:foldl(
fun(Def, {Add, Del}) -> fun(Def, {Add, Del}) ->
case parse_single_what(Def) of case parse_single_what(Def) of
{error, Err} -> {error, Err} ->
econf:fail({invalid_syntax, [Err, ": ", Def]}); econf:fail({invalid_syntax, [Err, ": ", Def]});
all -> all ->
{case Add of none -> none; _ -> all end, Del}; {case Add of none -> none; _ -> all end, Del};
{neg, all} -> {neg, all} ->
{none, all}; {none, all};
{neg, Value} -> {neg, Value} ->
{Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end}; {Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
Value -> Value ->
{case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del} {case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
end end
end, {[], []}, Defs), end,
{[], []},
Defs),
case {A, D} of case {A, D} of
{[], _} -> {[], _} ->
{none, all}; {none, all};
{A2, []} -> {A2, []} ->
{A2, none}; {A2, none};
V -> V ->
V V
end. end.
-spec parse_single_what(binary()) -> atom() | {neg, atom()} | {tag, atom()} | {error, string()}. -spec parse_single_what(binary()) -> atom() | {neg, atom()} | {tag, atom()} | {error, string()}.
parse_single_what(<<"*">>) -> parse_single_what(<<"*">>) ->
all; all;
@ -289,70 +324,76 @@ parse_single_what(<<"!*">>) ->
{neg, all}; {neg, all};
parse_single_what(<<"!", Rest/binary>>) -> parse_single_what(<<"!", Rest/binary>>) ->
case parse_single_what(Rest) of case parse_single_what(Rest) of
{neg, _} -> {neg, _} ->
{error, "double negation"}; {error, "double negation"};
{error, _} = Err -> {error, _} = Err ->
Err; Err;
V -> V ->
{neg, V} {neg, V}
end; end;
parse_single_what(<<"[tag:", Rest/binary>>) -> parse_single_what(<<"[tag:", Rest/binary>>) ->
case binary:split(Rest, <<"]">>) of case binary:split(Rest, <<"]">>) of
[TagName, <<"">>] -> [TagName, <<"">>] ->
case parse_single_what(TagName) of case parse_single_what(TagName) of
{error, _} = Err -> {error, _} = Err ->
Err; Err;
V when is_atom(V) -> V when is_atom(V) ->
{tag, V}; {tag, V};
_ -> _ ->
{error, "invalid tag"} {error, "invalid tag"}
end; end;
_ -> _ ->
{error, "invalid tag"} {error, "invalid tag"}
end; end;
parse_single_what(B) -> parse_single_what(B) ->
case re:run(B, "^[a-z0-9_\\-]*$") of case re:run(B, "^[a-z0-9_\\-]*$") of
nomatch -> {error, "invalid command"}; nomatch -> {error, "invalid command"};
_ -> binary_to_atom(B, latin1) _ -> binary_to_atom(B, latin1)
end. end.
validator(Map, Opts) -> validator(Map, Opts) ->
econf:and_then( econf:and_then(
fun(L) when is_list(L) -> fun(L) when is_list(L) ->
lists:map( lists:map(
fun({K, V}) -> {(econf:atom())(K), V}; fun({K, V}) -> {(econf:atom())(K), V};
(A) -> {acl, (econf:atom())(A)} (A) -> {acl, (econf:atom())(A)}
end, lists:flatten(L)); end,
lists:flatten(L));
(A) -> (A) ->
[{acl, (econf:atom())(A)}] [{acl, (econf:atom())(A)}]
end, end,
econf:and_then( econf:and_then(
econf:options(maps:merge(acl:validators(), Map), Opts), econf:options(maps:merge(acl:validators(), Map), Opts),
fun(Rules) -> fun(Rules) ->
lists:flatmap( lists:flatmap(
fun({Type, Rs}) when is_list(Rs) -> fun({Type, Rs}) when is_list(Rs) ->
case maps:is_key(Type, acl:validators()) of case maps:is_key(Type, acl:validators()) of
true -> [{acl, {Type, R}} || R <- Rs]; true -> [ {acl, {Type, R}} || R <- Rs ];
false -> [{Type, Rs}] false -> [{Type, Rs}]
end; end;
(Other) -> (Other) ->
[Other] [Other]
end, Rules) end,
end)). Rules)
end)).
validator(from) -> validator(from) ->
fun(L) when is_list(L) -> fun(L) when is_list(L) ->
lists:map( lists:map(
fun({K, V}) -> {(econf:enum([tag]))(K), (econf:binary())(V)}; fun({K, V}) -> {(econf:enum([tag]))(K), (econf:binary())(V)};
(A) -> (econf:enum([ejabberd_ctl, (A) ->
ejabberd_web_admin, (econf:enum([ejabberd_ctl,
ejabberd_xmlrpc, ejabberd_web_admin,
mod_adhoc_api, ejabberd_xmlrpc,
mod_cron, mod_adhoc_api,
mod_http_api]))(A) mod_cron,
end, lists:flatten(L)); mod_http_api]))(A)
end,
lists:flatten(L));
(A) -> (A) ->
[(econf:enum([ejabberd_ctl, [(econf:enum([ejabberd_ctl,
ejabberd_web_admin, ejabberd_web_admin,
ejabberd_xmlrpc, ejabberd_xmlrpc,
mod_adhoc_api, mod_adhoc_api,
@ -367,26 +408,31 @@ validator(who) ->
validator(#{access => econf:acl(), oauth => validator(oauth)}, []); validator(#{access => econf:acl(), oauth => validator(oauth)}, []);
validator(oauth) -> validator(oauth) ->
econf:and_then( econf:and_then(
validator(#{access => econf:acl(), validator(#{
scope => econf:non_empty( access => econf:acl(),
econf:list_or_single(econf:binary()))}, scope => econf:non_empty(
[{required, [scope]}]), econf:list_or_single(econf:binary()))
},
[{required, [scope]}]),
fun(Os) -> fun(Os) ->
{[Scopes], Rest} = proplists:split(Os, [scope]), {[Scopes], Rest} = proplists:split(Os, [scope]),
{lists:flatten([S || {_, S} <- Scopes]), Rest} {lists:flatten([ S || {_, S} <- Scopes ]), Rest}
end). end).
validator() -> validator() ->
econf:map( econf:map(
econf:binary(), econf:binary(),
econf:and_then( econf:and_then(
econf:options( econf:options(
#{from => validator(from), #{
what => validator(what), from => validator(from),
who => validator(who)}), what => validator(what),
fun(Os) -> who => validator(who)
{proplists:get_value(from, Os, []), }),
proplists:get_value(who, Os, none), fun(Os) ->
proplists:get_value(what, Os, {none, none})} {proplists:get_value(from, Os, []),
end), proplists:get_value(who, Os, none),
proplists:get_value(what, Os, {none, none})}
end),
[unique]). [unique]).

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -38,65 +38,71 @@
%%% Application API %%% Application API
%%% %%%
start(normal, _Args) -> start(normal, _Args) ->
try try
{T1, _} = statistics(wall_clock), {T1, _} = statistics(wall_clock),
ejabberd_logger:start(), ejabberd_logger:start(),
write_pid_file(), write_pid_file(),
start_included_apps(), start_included_apps(),
misc:warn_unset_home(), misc:warn_unset_home(),
start_elixir_application(), start_elixir_application(),
setup_if_elixir_conf_used(), setup_if_elixir_conf_used(),
case ejabberd_config:load() of case ejabberd_config:load() of
ok -> ok ->
ejabberd_mnesia:start(), ejabberd_mnesia:start(),
file_queue_init(), file_queue_init(),
maybe_add_nameservers(), maybe_add_nameservers(),
case ejabberd_sup:start_link() of case ejabberd_sup:start_link() of
{ok, SupPid} -> {ok, SupPid} ->
ejabberd_system_monitor:start(), ejabberd_system_monitor:start(),
register_elixir_config_hooks(), register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity), ejabberd_cluster:wait_for_sync(infinity),
ejabberd_hooks:run(ejabberd_started, []), ejabberd_hooks:run(ejabberd_started, []),
ejabberd:check_apps(), ejabberd:check_apps(),
ejabberd_systemd:ready(), ejabberd_systemd:ready(),
maybe_start_exsync(), maybe_start_exsync(),
{T2, _} = statistics(wall_clock), {T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~ts is started in the node ~p in ~.2fs", ?INFO_MSG("ejabberd ~ts is started in the node ~p in ~.2fs",
[ejabberd_option:version(), [ejabberd_option:version(),
node(), (T2-T1)/1000]), node(),
maybe_print_elixir_version(), (T2 - T1) / 1000]),
?INFO_MSG("~ts", maybe_print_elixir_version(),
[erlang:system_info(system_version)]), ?INFO_MSG("~ts",
{ok, SupPid}; [erlang:system_info(system_version)]),
Err -> {ok, SupPid};
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]), Err ->
ejabberd:halt() ?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
end; ejabberd:halt()
Err -> end;
?CRITICAL_MSG("Failed to start ejabberd application: ~ts", Err ->
[ejabberd_config:format_error(Err)]), ?CRITICAL_MSG("Failed to start ejabberd application: ~ts",
ejabberd:halt() [ejabberd_config:format_error(Err)]),
end ejabberd:halt()
catch throw:{?MODULE, Error} -> end
?DEBUG("Failed to start ejabberd application: ~p", [Error]), catch
ejabberd:halt() throw:{?MODULE, Error} ->
?DEBUG("Failed to start ejabberd application: ~p", [Error]),
ejabberd:halt()
end; end;
start(_, _) -> start(_, _) ->
{error, badarg}. {error, badarg}.
start_included_apps() -> start_included_apps() ->
{ok, Apps} = application:get_key(ejabberd, included_applications), {ok, Apps} = application:get_key(ejabberd, included_applications),
lists:foreach( lists:foreach(
fun(mnesia) -> fun(mnesia) ->
ok; ok;
(lager) -> (lager) ->
ok; ok;
(os_mon)-> (os_mon) ->
ok; ok;
(App) -> (App) ->
application:ensure_all_started(App) application:ensure_all_started(App)
end, Apps). end,
Apps).
%% Prepare the application for termination. %% Prepare the application for termination.
%% This function is called when an application is about to be stopped, %% This function is called when an application is about to be stopped,
@ -113,76 +119,88 @@ prep_stop(State) ->
gen_mod:stop(), gen_mod:stop(),
State. State.
%% All the processes were killed when this function is called %% All the processes were killed when this function is called
stop(_State) -> stop(_State) ->
?INFO_MSG("ejabberd ~ts is stopped in the node ~p", ?INFO_MSG("ejabberd ~ts is stopped in the node ~p",
[ejabberd_option:version(), node()]), [ejabberd_option:version(), node()]),
delete_pid_file(). delete_pid_file().
%%% %%%
%%% Internal functions %%% Internal functions
%%% %%%
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang %% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
maybe_add_nameservers() -> maybe_add_nameservers() ->
case os:type() of case os:type() of
{win32, _} -> add_windows_nameservers(); {win32, _} -> add_windows_nameservers();
_ -> ok _ -> ok
end. end.
add_windows_nameservers() -> add_windows_nameservers() ->
IPTs = win32_dns:get_nameservers(), IPTs = win32_dns:get_nameservers(),
?INFO_MSG("Adding machine's DNS IPs to Erlang system:~n~p", [IPTs]), ?INFO_MSG("Adding machine's DNS IPs to Erlang system:~n~p", [IPTs]),
lists:foreach(fun(IPT) -> inet_db:add_ns(IPT) end, IPTs). lists:foreach(fun(IPT) -> inet_db:add_ns(IPT) end, IPTs).
%%% %%%
%%% PID file %%% PID file
%%% %%%
write_pid_file() -> write_pid_file() ->
case ejabberd:get_pid_file() of case ejabberd:get_pid_file() of
false -> false ->
ok; ok;
PidFilename -> PidFilename ->
write_pid_file(os:getpid(), PidFilename) write_pid_file(os:getpid(), PidFilename)
end. end.
write_pid_file(Pid, PidFilename) -> write_pid_file(Pid, PidFilename) ->
case file:write_file(PidFilename, io_lib:format("~ts~n", [Pid])) of case file:write_file(PidFilename, io_lib:format("~ts~n", [Pid])) of
ok -> ok ->
ok; ok;
{error, Reason} = Err -> {error, Reason} = Err ->
?CRITICAL_MSG("Cannot write PID file ~ts: ~ts", ?CRITICAL_MSG("Cannot write PID file ~ts: ~ts",
[PidFilename, file:format_error(Reason)]), [PidFilename, file:format_error(Reason)]),
throw({?MODULE, Err}) throw({?MODULE, Err})
end. end.
delete_pid_file() -> delete_pid_file() ->
case ejabberd:get_pid_file() of case ejabberd:get_pid_file() of
false -> false ->
ok; ok;
PidFilename -> PidFilename ->
file:delete(PidFilename) file:delete(PidFilename)
end. end.
file_queue_init() -> file_queue_init() ->
QueueDir = case ejabberd_option:queue_dir() of QueueDir = case ejabberd_option:queue_dir() of
undefined -> undefined ->
MnesiaDir = mnesia:system_info(directory), MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "queue"); filename:join(MnesiaDir, "queue");
Path -> Path ->
Path Path
end, end,
case p1_queue:start(QueueDir) of case p1_queue:start(QueueDir) of
ok -> ok; ok -> ok;
Err -> throw({?MODULE, Err}) Err -> throw({?MODULE, Err})
end. end.
%%% %%%
%%% Elixir %%% Elixir
%%% %%%
-ifdef(ELIXIR_ENABLED). -ifdef(ELIXIR_ENABLED).
is_using_elixir_config() -> is_using_elixir_config() ->
Config = ejabberd_config:path(), Config = ejabberd_config:path(),
try 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config) of try 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config) of
@ -191,36 +209,55 @@ is_using_elixir_config() ->
_:_ -> false _:_ -> false
end. end.
setup_if_elixir_conf_used() -> setup_if_elixir_conf_used() ->
case is_using_elixir_config() of case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link(); true -> 'Elixir.Ejabberd.Config.Store':start_link();
false -> ok false -> ok
end. end.
register_elixir_config_hooks() -> register_elixir_config_hooks() ->
case is_using_elixir_config() of case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config':start_hooks(); true -> 'Elixir.Ejabberd.Config':start_hooks();
false -> ok false -> ok
end. end.
start_elixir_application() -> start_elixir_application() ->
case application:ensure_started(elixir) of case application:ensure_started(elixir) of
ok -> ok; ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", []) {error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
end. end.
maybe_start_exsync() -> maybe_start_exsync() ->
case os:getenv("RELIVE") of case os:getenv("RELIVE") of
"true" -> rpc:call(node(), 'Elixir.ExSync.Application', start, []); "true" -> rpc:call(node(), 'Elixir.ExSync.Application', start, []);
_ -> ok _ -> ok
end. end.
maybe_print_elixir_version() -> maybe_print_elixir_version() ->
?INFO_MSG("Elixir ~ts", [maps:get(build, 'Elixir.System':build_info())]). ?INFO_MSG("Elixir ~ts", [maps:get(build, 'Elixir.System':build_info())]).
-else. -else.
setup_if_elixir_conf_used() -> ok. setup_if_elixir_conf_used() -> ok.
register_elixir_config_hooks() -> ok. register_elixir_config_hooks() -> ok.
start_elixir_application() -> ok. start_elixir_application() -> ok.
maybe_start_exsync() -> ok. maybe_start_exsync() -> ok.
maybe_print_elixir_version() -> ok. maybe_print_elixir_version() -> ok.
-endif. -endif.

File diff suppressed because it is too large Load diff

View file

@ -31,159 +31,196 @@
-protocol({xep, 175, '1.2', '1.1.0', "complete", ""}). -protocol({xep, 175, '1.2', '1.1.0', "complete", ""}).
-export([start/1, -export([start/1,
stop/1, stop/1,
use_cache/1, use_cache/1,
allow_anonymous/1, allow_anonymous/1,
is_sasl_anonymous_enabled/1, is_sasl_anonymous_enabled/1,
is_login_anonymous_enabled/1, is_login_anonymous_enabled/1,
anonymous_user_exist/2, anonymous_user_exist/2,
allow_multiple_connections/1, allow_multiple_connections/1,
register_connection/3, register_connection/3,
unregister_connection/3 unregister_connection/3]).
]).
-export([login/2, check_password/4, user_exists/2, -export([login/2,
get_users/2, count_users/2, store_type/1, check_password/4,
plain_password_required/1]). user_exists/2,
get_users/2,
count_users/2,
store_type/1,
plain_password_required/1]).
-include("logger.hrl"). -include("logger.hrl").
-include_lib("xmpp/include/jid.hrl"). -include_lib("xmpp/include/jid.hrl").
start(Host) -> start(Host) ->
ejabberd_hooks:add(sm_register_connection_hook, Host, ejabberd_hooks:add(sm_register_connection_hook,
?MODULE, register_connection, 100), Host,
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE,
?MODULE, unregister_connection, 100), register_connection,
100),
ejabberd_hooks:add(sm_remove_connection_hook,
Host,
?MODULE,
unregister_connection,
100),
ok. ok.
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(sm_register_connection_hook, Host, ejabberd_hooks:delete(sm_register_connection_hook,
?MODULE, register_connection, 100), Host,
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE,
?MODULE, unregister_connection, 100). register_connection,
100),
ejabberd_hooks:delete(sm_remove_connection_hook,
Host,
?MODULE,
unregister_connection,
100).
use_cache(_) -> use_cache(_) ->
false. false.
%% Return true if anonymous is allowed for host or false otherwise %% Return true if anonymous is allowed for host or false otherwise
allow_anonymous(Host) -> allow_anonymous(Host) ->
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)). lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
%% Return true if anonymous mode is enabled and if anonymous protocol is SASL %% Return true if anonymous mode is enabled and if anonymous protocol is SASL
%% anonymous protocol can be: sasl_anon|login_anon|both %% anonymous protocol can be: sasl_anon|login_anon|both
is_sasl_anonymous_enabled(Host) -> is_sasl_anonymous_enabled(Host) ->
case allow_anonymous(Host) of case allow_anonymous(Host) of
false -> false; false -> false;
true -> true ->
case anonymous_protocol(Host) of case anonymous_protocol(Host) of
sasl_anon -> true; sasl_anon -> true;
both -> true; both -> true;
_Other -> false _Other -> false
end end
end. end.
%% Return true if anonymous login is enabled on the server %% Return true if anonymous login is enabled on the server
%% anonymous login can be use using standard authentication method (i.e. with %% anonymous login can be use using standard authentication method (i.e. with
%% clients that do not support anonymous login) %% clients that do not support anonymous login)
is_login_anonymous_enabled(Host) -> is_login_anonymous_enabled(Host) ->
case allow_anonymous(Host) of case allow_anonymous(Host) of
false -> false; false -> false;
true -> true ->
case anonymous_protocol(Host) of case anonymous_protocol(Host) of
login_anon -> true; login_anon -> true;
both -> true; both -> true;
_Other -> false _Other -> false
end end
end. end.
%% Return the anonymous protocol to use: sasl_anon|login_anon|both %% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon %% defaults to login_anon
anonymous_protocol(Host) -> anonymous_protocol(Host) ->
ejabberd_option:anonymous_protocol(Host). ejabberd_option:anonymous_protocol(Host).
%% Return true if multiple connections have been allowed in the config file %% Return true if multiple connections have been allowed in the config file
%% defaults to false %% defaults to false
allow_multiple_connections(Host) -> allow_multiple_connections(Host) ->
ejabberd_option:allow_multiple_connections(Host). ejabberd_option:allow_multiple_connections(Host).
anonymous_user_exist(User, Server) -> anonymous_user_exist(User, Server) ->
lists:any( lists:any(
fun({_LResource, Info}) -> fun({_LResource, Info}) ->
proplists:get_value(auth_module, Info) == ?MODULE proplists:get_value(auth_module, Info) == ?MODULE
end, ejabberd_sm:get_user_info(User, Server)). end,
ejabberd_sm:get_user_info(User, Server)).
%% Register connection %% Register connection
-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. -spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
register_connection(_SID, register_connection(_SID,
#jid{luser = LUser, lserver = LServer, lresource = LResource}, Info) -> #jid{luser = LUser, lserver = LServer, lresource = LResource},
Info) ->
case proplists:get_value(auth_module, Info) of case proplists:get_value(auth_module, Info) of
?MODULE -> ?MODULE ->
% Register user only if we are first resource % Register user only if we are first resource
case ejabberd_sm:get_user_resources(LUser, LServer) of case ejabberd_sm:get_user_resources(LUser, LServer) of
[LResource] -> [LResource] ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]); ejabberd_hooks:run(register_user, LServer, [LUser, LServer]);
_ -> _ ->
ok ok
end; end;
_ -> _ ->
ok ok
end. end.
%% Remove an anonymous user from the anonymous users table %% Remove an anonymous user from the anonymous users table
-spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any(). -spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
unregister_connection(_SID, unregister_connection(_SID,
#jid{luser = LUser, lserver = LServer}, Info) -> #jid{luser = LUser, lserver = LServer},
Info) ->
case proplists:get_value(auth_module, Info) of case proplists:get_value(auth_module, Info) of
?MODULE -> ?MODULE ->
% Remove user data only if there is no more resources around % Remove user data only if there is no more resources around
case ejabberd_sm:get_user_resources(LUser, LServer) of case ejabberd_sm:get_user_resources(LUser, LServer) of
[] -> [] ->
ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]); ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
_ -> _ ->
ok ok
end; end;
_ -> _ ->
ok ok
end. end.
%% --------------------------------- %% ---------------------------------
%% Specific anonymous auth functions %% Specific anonymous auth functions
%% --------------------------------- %% ---------------------------------
check_password(User, _AuthzId, Server, _Password) -> check_password(User, _AuthzId, Server, _Password) ->
{nocache, {nocache,
case ejabberd_auth:user_exists_in_other_modules(?MODULE, User, Server) of case ejabberd_auth:user_exists_in_other_modules(?MODULE, User, Server) of
%% If user exists in other module, reject anonnymous authentication %% If user exists in other module, reject anonnymous authentication
true -> false; true -> false;
%% If we are not sure whether the user exists in other module, reject anon auth %% If we are not sure whether the user exists in other module, reject anon auth
maybe_exists -> false; maybe_exists -> false;
false -> login(User, Server) false -> login(User, Server)
end}. end}.
login(User, Server) -> login(User, Server) ->
case is_login_anonymous_enabled(Server) of case is_login_anonymous_enabled(Server) of
false -> false; false -> false;
true -> true ->
case anonymous_user_exist(User, Server) of case anonymous_user_exist(User, Server) of
%% Reject the login if an anonymous user with the same login %% Reject the login if an anonymous user with the same login
%% is already logged and if multiple login has not been enable %% is already logged and if multiple login has not been enable
%% in the config file. %% in the config file.
true -> allow_multiple_connections(Server); true -> allow_multiple_connections(Server);
%% Accept login and add user to the anonymous table %% Accept login and add user to the anonymous table
false -> true false -> true
end end
end. end.
get_users(Server, _) -> get_users(Server, _) ->
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)]. [ {U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server) ].
count_users(Server, Opts) -> count_users(Server, Opts) ->
length(get_users(Server, Opts)). length(get_users(Server, Opts)).
user_exists(User, Server) -> user_exists(User, Server) ->
{nocache, anonymous_user_exist(User, Server)}. {nocache, anonymous_user_exist(User, Server)}.
plain_password_required(_) -> plain_password_required(_) ->
false. false.
store_type(_) -> store_type(_) ->
external. external.

View file

@ -29,77 +29,99 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, reload/1, set_password/3, check_password/4, -export([start/1,
try_register/3, user_exists/2, remove_user/2, stop/1,
store_type/1, plain_password_required/1]). reload/1,
set_password/3,
check_password/4,
try_register/3,
user_exists/2,
remove_user/2,
store_type/1,
plain_password_required/1]).
-include("logger.hrl"). -include("logger.hrl").
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host) -> start(Host) ->
extauth:start(Host). extauth:start(Host).
stop(Host) -> stop(Host) ->
extauth:stop(Host). extauth:stop(Host).
reload(Host) -> reload(Host) ->
extauth:reload(Host). extauth:reload(Host).
plain_password_required(_) -> true. plain_password_required(_) -> true.
store_type(_) -> external. store_type(_) -> external.
check_password(User, AuthzId, Server, Password) -> check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User -> if
{nocache, false}; AuthzId /= <<>> andalso AuthzId /= User ->
true -> {nocache, false};
check_password_extauth(User, AuthzId, Server, Password) true ->
check_password_extauth(User, AuthzId, Server, Password)
end. end.
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of case extauth:set_password(User, Server, Password) of
Res when is_boolean(Res) -> {cache, {ok, Password}}; Res when is_boolean(Res) -> {cache, {ok, Password}};
{error, Reason} -> failure(User, Server, set_password, Reason) {error, Reason} -> failure(User, Server, set_password, Reason)
end. end.
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case extauth:try_register(User, Server, Password) of case extauth:try_register(User, Server, Password) of
true -> {cache, {ok, Password}}; true -> {cache, {ok, Password}};
false -> {cache, {error, not_allowed}}; false -> {cache, {error, not_allowed}};
{error, Reason} -> failure(User, Server, try_register, Reason) {error, Reason} -> failure(User, Server, try_register, Reason)
end. end.
user_exists(User, Server) -> user_exists(User, Server) ->
case extauth:user_exists(User, Server) of case extauth:user_exists(User, Server) of
Res when is_boolean(Res) -> {cache, Res}; Res when is_boolean(Res) -> {cache, Res};
{error, Reason} -> failure(User, Server, user_exists, Reason) {error, Reason} -> failure(User, Server, user_exists, Reason)
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
case extauth:remove_user(User, Server) of case extauth:remove_user(User, Server) of
false -> {error, not_allowed}; false -> {error, not_allowed};
true -> ok; true -> ok;
{error, Reason} -> {error, Reason} ->
{_, Err} = failure(User, Server, remove_user, Reason), {_, Err} = failure(User, Server, remove_user, Reason),
Err Err
end. end.
check_password_extauth(User, _AuthzId, Server, Password) -> check_password_extauth(User, _AuthzId, Server, Password) ->
if Password /= <<"">> -> if
case extauth:check_password(User, Server, Password) of Password /= <<"">> ->
Res when is_boolean(Res) -> {cache, Res}; case extauth:check_password(User, Server, Password) of
{error, Reason} -> Res when is_boolean(Res) -> {cache, Res};
{Tag, _} = failure(User, Server, check_password, Reason), {error, Reason} ->
{Tag, false} {Tag, _} = failure(User, Server, check_password, Reason),
end; {Tag, false}
true -> end;
{nocache, false} true ->
{nocache, false}
end. end.
-spec failure(binary(), binary(), atom(), any()) -> {nocache, {error, db_failure}}. -spec failure(binary(), binary(), atom(), any()) -> {nocache, {error, db_failure}}.
failure(User, Server, Fun, Reason) -> failure(User, Server, Fun, Reason) ->
?ERROR_MSG("External authentication program failed when calling " ?ERROR_MSG("External authentication program failed when calling "
"'~ts' for ~ts@~ts: ~p", [Fun, User, Server, Reason]), "'~ts' for ~ts@~ts: ~p",
[Fun, User, Server, Reason]),
{nocache, {error, db_failure}}. {nocache, {error, db_failure}}.

View file

@ -29,16 +29,21 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, check_password/4, -export([start/1,
store_type/1, plain_password_required/1, stop/1,
user_exists/2, use_cache/1 check_password/4,
]). store_type/1,
plain_password_required/1,
user_exists/2,
use_cache/1]).
%% 'ejabberd_hooks' callback: %% 'ejabberd_hooks' callback:
-export([check_decoded_jwt/5]). -export([check_decoded_jwt/5]).
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl"). -include("logger.hrl").
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -50,32 +55,40 @@ start(Host) ->
%% callback function. %% callback function.
ejabberd_hooks:add(check_decoded_jwt, Host, ?MODULE, check_decoded_jwt, 100), ejabberd_hooks:add(check_decoded_jwt, Host, ?MODULE, check_decoded_jwt, 100),
case ejabberd_option:jwt_key(Host) of case ejabberd_option:jwt_key(Host) of
undefined -> undefined ->
?ERROR_MSG("Option jwt_key is not configured for ~ts: " ?ERROR_MSG("Option jwt_key is not configured for ~ts: "
"JWT authentication won't work", [Host]); "JWT authentication won't work",
_ -> [Host]);
ok _ ->
ok
end. end.
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(check_decoded_jwt, Host, ?MODULE, check_decoded_jwt, 100). ejabberd_hooks:delete(check_decoded_jwt, Host, ?MODULE, check_decoded_jwt, 100).
plain_password_required(_Host) -> true. plain_password_required(_Host) -> true.
store_type(_Host) -> external. store_type(_Host) -> external.
-spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean() | {stop, boolean()}}. -spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean() | {stop, boolean()}}.
check_password(User, AuthzId, Server, Token) -> check_password(User, AuthzId, Server, Token) ->
%% MREMOND: Should we move the AuthzId check at a higher level in %% MREMOND: Should we move the AuthzId check at a higher level in
%% the call stack? %% the call stack?
if AuthzId /= <<>> andalso AuthzId /= User -> if
AuthzId /= <<>> andalso AuthzId /= User ->
{nocache, false}; {nocache, false};
true -> true ->
if Token == <<"">> -> {nocache, false}; if
true -> Token == <<"">> -> {nocache, false};
true ->
Res = check_jwt_token(User, Server, Token), Res = check_jwt_token(User, Server, Token),
Rule = ejabberd_option:jwt_auth_only_rule(Server), Rule = ejabberd_option:jwt_auth_only_rule(Server),
case acl:match_rule(Server, Rule, case acl:match_rule(Server,
Rule,
jid:make(User, Server, <<"">>)) of jid:make(User, Server, <<"">>)) of
deny -> deny ->
{nocache, Res}; {nocache, Res};
@ -85,18 +98,21 @@ check_password(User, AuthzId, Server, Token) ->
end end
end. end.
user_exists(User, Host) -> user_exists(User, Host) ->
%% Checking that the user has an active session %% Checking that the user has an active session
%% If the session was negociated by the JWT auth method then we define that the user exists %% If the session was negociated by the JWT auth method then we define that the user exists
%% Any other cases will return that the user doesn't exist %% Any other cases will return that the user doesn't exist
{nocache, case ejabberd_sm:get_user_info(User, Host) of {nocache, case ejabberd_sm:get_user_info(User, Host) of
[{_, Info}] -> proplists:get_value(auth_module, Info) == ejabberd_auth_jwt; [{_, Info}] -> proplists:get_value(auth_module, Info) == ejabberd_auth_jwt;
_ -> false _ -> false
end}. end}.
use_cache(_) -> use_cache(_) ->
false. false.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% 'ejabberd_hooks' callback %%% 'ejabberd_hooks' callback
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -107,15 +123,17 @@ check_decoded_jwt(true, Fields, _Signature, Server, User) ->
try try
JID = jid:decode(SJid), JID = jid:decode(SJid),
JID#jid.luser == User andalso JID#jid.lserver == Server JID#jid.luser == User andalso JID#jid.lserver == Server
catch error:{bad_jid, _} -> catch
false error:{bad_jid, _} ->
false
end; end;
_ -> % error | {ok, _UnknownType} _ -> % error | {ok, _UnknownType}
false false
end; end;
check_decoded_jwt(Acc, _, _, _, _) -> check_decoded_jwt(Acc, _, _, _, _) ->
Acc. Acc.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -125,19 +143,18 @@ check_jwt_token(User, Server, Token) ->
{true, {jose_jwt, Fields}, Signature} -> {true, {jose_jwt, Fields}, Signature} ->
Now = erlang:system_time(second), Now = erlang:system_time(second),
?DEBUG("jwt verify at system timestamp ~p: ~p - ~p~n", [Now, Fields, Signature]), ?DEBUG("jwt verify at system timestamp ~p: ~p - ~p~n", [Now, Fields, Signature]),
case maps:find(<<"exp">>, Fields) of case maps:find(<<"exp">>, Fields) of
error -> error ->
%% No expiry in token => We consider token invalid: %% No expiry in token => We consider token invalid:
false; false;
{ok, Exp} -> {ok, Exp} ->
if if
Exp > Now -> Exp > Now ->
ejabberd_hooks:run_fold( ejabberd_hooks:run_fold(
check_decoded_jwt, check_decoded_jwt,
Server, Server,
true, true,
[Fields, Signature, Server, User] [Fields, Signature, Server, User]);
);
true -> true ->
%% return false, if token has expired %% return false, if token has expired
false false

View file

@ -31,27 +31,40 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_info/2, handle_call/3, -export([init/1,
handle_cast/2, terminate/2, code_change/3]). handle_info/2,
handle_call/3,
handle_cast/2,
terminate/2,
code_change/3]).
-export([start/1, stop/1, start_link/1, set_password/3, -export([start/1,
check_password/4, user_exists/2, stop/1,
get_users/2, count_users/2, start_link/1,
store_type/1, plain_password_required/1, set_password/3,
reload/1]). check_password/4,
user_exists/2,
get_users/2,
count_users/2,
store_type/1,
plain_password_required/1,
reload/1]).
-include("logger.hrl"). -include("logger.hrl").
-include("eldap.hrl"). -include("eldap.hrl").
%%
%% @efmt:off
%% @indent-begin
-record(state, -record(state,
{host = <<"">> :: binary(), {host = <<"">> :: binary(),
eldap_id = <<"">> :: binary(), eldap_id = <<"">> :: binary(),
bind_eldap_id = <<"">> :: binary(), bind_eldap_id = <<"">> :: binary(),
servers = [] :: [binary()], servers = [] :: [binary()],
backups = [] :: [binary()], backups = [] :: [binary()],
port = ?LDAP_PORT :: inet:port_number(), port = ?LDAP_PORT :: inet:port_number(),
tls_options = [] :: list(), tls_options = [] :: list(),
dn = <<"">> :: binary(), dn = <<"">> :: binary(),
password = <<"">> :: binary(), password = <<"">> :: binary(),
base = <<"">> :: binary(), base = <<"">> :: binary(),
@ -61,118 +74,152 @@
deref_aliases = never :: never | searching | finding | always, deref_aliases = never :: never | searching | finding | always,
dn_filter :: binary() | undefined, dn_filter :: binary() | undefined,
dn_filter_attrs = [] :: [binary()]}). dn_filter_attrs = [] :: [binary()]}).
%% @indent-end
%% @efmt:on
%%
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
code_change(_OldVsn, State, _Extra) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
-define(LDAP_SEARCH_TIMEOUT, 5). -define(LDAP_SEARCH_TIMEOUT, 5).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host) -> start(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = {Proc, {?MODULE, start_link, [Host]}, ChildSpec = {Proc,
transient, 1000, worker, [?MODULE]}, {?MODULE, start_link, [Host]},
transient,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_backend_sup, ChildSpec). supervisor:start_child(ejabberd_backend_sup, ChildSpec).
stop(Host) -> stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
case supervisor:terminate_child(ejabberd_backend_sup, Proc) of case supervisor:terminate_child(ejabberd_backend_sup, Proc) of
ok -> supervisor:delete_child(ejabberd_backend_sup, Proc); ok -> supervisor:delete_child(ejabberd_backend_sup, Proc);
Err -> Err Err -> Err
end. end.
start_link(Host) -> start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []). gen_server:start_link({local, Proc}, ?MODULE, Host, []).
terminate(_Reason, _State) -> ok. terminate(_Reason, _State) -> ok.
init(Host) -> init(Host) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
State = parse_options(Host), State = parse_options(Host),
eldap_pool:start_link(State#state.eldap_id, eldap_pool:start_link(State#state.eldap_id,
State#state.servers, State#state.backups, State#state.servers,
State#state.port, State#state.dn, State#state.backups,
State#state.password, State#state.tls_options), State#state.port,
State#state.dn,
State#state.password,
State#state.tls_options),
eldap_pool:start_link(State#state.bind_eldap_id, eldap_pool:start_link(State#state.bind_eldap_id,
State#state.servers, State#state.backups, State#state.servers,
State#state.port, State#state.dn, State#state.backups,
State#state.password, State#state.tls_options), State#state.port,
State#state.dn,
State#state.password,
State#state.tls_options),
{ok, State}. {ok, State}.
reload(Host) -> reload(Host) ->
stop(Host), stop(Host),
start(Host). start(Host).
plain_password_required(_) -> true. plain_password_required(_) -> true.
store_type(_) -> external. store_type(_) -> external.
check_password(User, AuthzId, Server, Password) -> check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User -> if
{nocache, false}; AuthzId /= <<>> andalso AuthzId /= User ->
Password == <<"">> -> {nocache, false};
{nocache, false}; Password == <<"">> ->
true -> {nocache, false};
case catch check_password_ldap(User, Server, Password) of true ->
{'EXIT', _} -> {nocache, false}; case catch check_password_ldap(User, Server, Password) of
Result -> {cache, Result} {'EXIT', _} -> {nocache, false};
end Result -> {cache, Result}
end
end. end.
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> {cache, {error, db_failure}}; false -> {cache, {error, db_failure}};
DN -> DN ->
case eldap_pool:modify_passwd(State#state.eldap_id, DN, case eldap_pool:modify_passwd(State#state.eldap_id,
Password) of DN,
ok -> {cache, {ok, Password}}; Password) of
_Err -> {nocache, {error, db_failure}} ok -> {cache, {ok, Password}};
end _Err -> {nocache, {error, db_failure}}
end
end. end.
get_users(Server, []) -> get_users(Server, []) ->
case catch get_users_ldap(Server) of case catch get_users_ldap(Server) of
{'EXIT', _} -> []; {'EXIT', _} -> [];
Result -> Result Result -> Result
end. end.
count_users(Server, Opts) -> count_users(Server, Opts) ->
length(get_users(Server, Opts)). length(get_users(Server, Opts)).
user_exists(User, Server) -> user_exists(User, Server) ->
case catch user_exists_ldap(User, Server) of case catch user_exists_ldap(User, Server) of
{'EXIT', _Error} -> {nocache, {error, db_failure}}; {'EXIT', _Error} -> {nocache, {error, db_failure}};
Result -> {cache, Result} Result -> {cache, Result}
end. end.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
check_password_ldap(User, Server, Password) -> check_password_ldap(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> false; false -> false;
DN -> DN ->
case eldap_pool:bind(State#state.bind_eldap_id, DN, case eldap_pool:bind(State#state.bind_eldap_id,
Password) DN,
of Password) of
ok -> true; ok -> true;
_ -> false _ -> false
end end
end. end.
get_users_ldap(Server) -> get_users_ldap(Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
UIDs = State#state.uids, UIDs = State#state.uids,
@ -180,55 +227,54 @@ get_users_ldap(Server) ->
Server = State#state.host, Server = State#state.host,
ResAttrs = result_attrs(State), ResAttrs = result_attrs(State),
case eldap_filter:parse(State#state.sfilter) of case eldap_filter:parse(State#state.sfilter) of
{ok, EldapFilter} -> {ok, EldapFilter} ->
case eldap_pool:search(Eldap_ID, case eldap_pool:search(Eldap_ID,
[{base, State#state.base}, [{base, State#state.base},
{filter, EldapFilter}, {filter, EldapFilter},
{timeout, ?LDAP_SEARCH_TIMEOUT}, {timeout, ?LDAP_SEARCH_TIMEOUT},
{deref_aliases, State#state.deref_aliases}, {deref_aliases, State#state.deref_aliases},
{attributes, ResAttrs}]) {attributes, ResAttrs}]) of
of #eldap_search_result{entries = Entries} ->
#eldap_search_result{entries = Entries} -> lists:flatmap(fun(#eldap_entry{
lists:flatmap(fun (#eldap_entry{attributes = Attrs, attributes = Attrs,
object_name = DN}) -> object_name = DN
case is_valid_dn(DN, Attrs, State) of }) ->
false -> []; case is_valid_dn(DN, Attrs, State) of
_ -> false -> [];
case _ ->
eldap_utils:find_ldap_attrs(UIDs, case eldap_utils:find_ldap_attrs(UIDs,
Attrs) Attrs) of
of <<"">> -> [];
<<"">> -> []; {User, UIDFormat} ->
{User, UIDFormat} -> case eldap_utils:get_user_part(User,
case UIDFormat) of
eldap_utils:get_user_part(User, {ok, U} ->
UIDFormat) case jid:nodeprep(U) of
of error -> [];
{ok, U} -> LU ->
case jid:nodeprep(U) of [{LU,
error -> []; jid:nameprep(Server)}]
LU -> end;
[{LU, _ -> []
jid:nameprep(Server)}] end
end; end
_ -> [] end
end end,
end Entries);
end _ -> []
end, end;
Entries); _ -> []
_ -> []
end;
_ -> []
end. end.
user_exists_ldap(User, Server) -> user_exists_ldap(User, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> false; false -> false;
_DN -> true _DN -> true
end. end.
handle_call(get_state, _From, State) -> handle_call(get_state, _From, State) ->
{reply, {ok, State}, State}; {reply, {ok, State}, State};
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
@ -237,68 +283,75 @@ handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
find_user_dn(User, State) -> find_user_dn(User, State) ->
ResAttrs = result_attrs(State), ResAttrs = result_attrs(State),
case eldap_filter:parse(State#state.ufilter, case eldap_filter:parse(State#state.ufilter,
[{<<"%u">>, User}]) [{<<"%u">>, User}]) of
of {ok, Filter} ->
{ok, Filter} -> case eldap_pool:search(State#state.eldap_id,
case eldap_pool:search(State#state.eldap_id, [{base, State#state.base},
[{base, State#state.base}, {filter, Filter}, {filter, Filter},
{deref_aliases, State#state.deref_aliases}, {deref_aliases, State#state.deref_aliases},
{attributes, ResAttrs}]) {attributes, ResAttrs}]) of
of #eldap_search_result{
#eldap_search_result{entries = entries =
[#eldap_entry{attributes = Attrs, [#eldap_entry{
object_name = DN} attributes = Attrs,
| _]} -> object_name = DN
is_valid_dn(DN, Attrs, State); } | _]
_ -> false } ->
end; is_valid_dn(DN, Attrs, State);
_ -> false _ -> false
end;
_ -> false
end. end.
%% Check that the DN is valid, based on the dn filter %% Check that the DN is valid, based on the dn filter
is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN; is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
is_valid_dn(DN, Attrs, State) -> is_valid_dn(DN, Attrs, State) ->
DNAttrs = State#state.dn_filter_attrs, DNAttrs = State#state.dn_filter_attrs,
UIDs = State#state.uids, UIDs = State#state.uids,
Values = [{<<"%s">>, Values = [ {<<"%s">>,
eldap_utils:get_ldap_attr(Attr, Attrs), 1} eldap_utils:get_ldap_attr(Attr, Attrs),
|| Attr <- DNAttrs], 1}
|| Attr <- DNAttrs ],
SubstValues = case eldap_utils:find_ldap_attrs(UIDs, SubstValues = case eldap_utils:find_ldap_attrs(UIDs,
Attrs) Attrs) of
of <<"">> -> Values;
<<"">> -> Values; {S, UAF} ->
{S, UAF} -> case eldap_utils:get_user_part(S, UAF) of
case eldap_utils:get_user_part(S, UAF) of {ok, U} -> [{<<"%u">>, U} | Values];
{ok, U} -> [{<<"%u">>, U} | Values]; _ -> Values
_ -> Values end
end end ++
end [{<<"%d">>, State#state.host}, {<<"%D">>, DN}],
++ [{<<"%d">>, State#state.host}, {<<"%D">>, DN}],
case eldap_filter:parse(State#state.dn_filter, case eldap_filter:parse(State#state.dn_filter,
SubstValues) SubstValues) of
of {ok, EldapFilter} ->
{ok, EldapFilter} -> case eldap_pool:search(State#state.eldap_id,
case eldap_pool:search(State#state.eldap_id, [{base, State#state.base},
[{base, State#state.base}, {filter, EldapFilter},
{filter, EldapFilter}, {deref_aliases, State#state.deref_aliases},
{deref_aliases, State#state.deref_aliases}, {attributes, [<<"dn">>]}]) of
{attributes, [<<"dn">>]}]) #eldap_search_result{entries = [_ | _]} -> DN;
of _ -> false
#eldap_search_result{entries = [_ | _]} -> DN; end;
_ -> false _ -> false
end;
_ -> false
end. end.
result_attrs(#state{uids = UIDs,
dn_filter_attrs = DNFilterAttrs}) -> result_attrs(#state{
lists:foldl(fun ({UID}, Acc) -> [UID | Acc]; uids = UIDs,
({UID, _}, Acc) -> [UID | Acc] dn_filter_attrs = DNFilterAttrs
end, }) ->
DNFilterAttrs, UIDs). lists:foldl(fun({UID}, Acc) -> [UID | Acc];
({UID, _}, Acc) -> [UID | Acc]
end,
DNFilterAttrs,
UIDs).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Auxiliary functions %%% Auxiliary functions
@ -310,25 +363,30 @@ parse_options(Host) ->
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)), gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
UIDsTemp = ejabberd_option:ldap_uids(Host), UIDsTemp = ejabberd_option:ldap_uids(Host),
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp), UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
SubFilter = eldap_utils:generate_subfilter(UIDs), SubFilter = eldap_utils:generate_subfilter(UIDs),
UserFilter = case ejabberd_option:ldap_filter(Host) of UserFilter = case ejabberd_option:ldap_filter(Host) of
<<"">> -> <<"">> ->
SubFilter; SubFilter;
F -> F ->
<<"(&", SubFilter/binary, F/binary, ")">> <<"(&", SubFilter/binary, F/binary, ")">>
end, end,
SearchFilter = eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}]), SearchFilter = eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} = ejabberd_option:ldap_dn_filter(Host), {DNFilter, DNFilterAttrs} = ejabberd_option:ldap_dn_filter(Host),
#state{host = Host, eldap_id = Eldap_ID, #state{
bind_eldap_id = Bind_Eldap_ID, host = Host,
servers = Cfg#eldap_config.servers, eldap_id = Eldap_ID,
backups = Cfg#eldap_config.backups, bind_eldap_id = Bind_Eldap_ID,
port = Cfg#eldap_config.port, servers = Cfg#eldap_config.servers,
tls_options = Cfg#eldap_config.tls_options, backups = Cfg#eldap_config.backups,
dn = Cfg#eldap_config.dn, port = Cfg#eldap_config.port,
password = Cfg#eldap_config.password, tls_options = Cfg#eldap_config.tls_options,
base = Cfg#eldap_config.base, dn = Cfg#eldap_config.dn,
deref_aliases = Cfg#eldap_config.deref_aliases, password = Cfg#eldap_config.password,
uids = UIDs, ufilter = UserFilter, base = Cfg#eldap_config.base,
sfilter = SearchFilter, deref_aliases = Cfg#eldap_config.deref_aliases,
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}. uids = UIDs,
ufilter = UserFilter,
sfilter = SearchFilter,
dn_filter = DNFilter,
dn_filter_attrs = DNFilterAttrs
}.

View file

@ -29,19 +29,34 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password_multiple/3, try_register_multiple/3, -export([start/1,
get_users/2, init_db/0, stop/1,
count_users/2, get_password/2, set_password_multiple/3,
remove_user/2, store_type/1, import/2, try_register_multiple/3,
plain_password_required/1, use_cache/1, drop_password_type/2, set_password_instance/3]). get_users/2,
init_db/0,
count_users/2,
get_password/2,
remove_user/2,
store_type/1,
import/2,
plain_password_required/1,
use_cache/1,
drop_password_type/2,
set_password_instance/3]).
-export([need_transform/1, transform/1]). -export([need_transform/1, transform/1]).
-include("logger.hrl"). -include("logger.hrl").
-include_lib("xmpp/include/scram.hrl"). -include_lib("xmpp/include/scram.hrl").
-include("ejabberd_auth.hrl"). -include("ejabberd_auth.hrl").
-record(reg_users_counter, {vhost = <<"">> :: binary(), -record(reg_users_counter, {
count = 0 :: integer() | '$1'}). vhost = <<"">> :: binary(),
count = 0 :: integer() | '$1'
}).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
@ -51,214 +66,251 @@ start(Host) ->
update_reg_users_counter_table(Host), update_reg_users_counter_table(Host),
ok. ok.
stop(_Host) -> stop(_Host) ->
ok. ok.
init_db() -> init_db() ->
ejabberd_mnesia:create(?MODULE, passwd, ejabberd_mnesia:create(?MODULE,
[{disc_only_copies, [node()]}, passwd,
{attributes, record_info(fields, passwd)}]), [{disc_only_copies, [node()]},
ejabberd_mnesia:create(?MODULE, reg_users_counter, {attributes, record_info(fields, passwd)}]),
[{ram_copies, [node()]}, ejabberd_mnesia:create(?MODULE,
{attributes, record_info(fields, reg_users_counter)}]). reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]).
update_reg_users_counter_table(Server) -> update_reg_users_counter_table(Server) ->
Set = get_users(Server, []), Set = get_users(Server, []),
Size = length(Set), Size = length(Set),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
F = fun () -> F = fun() ->
mnesia:write(#reg_users_counter{vhost = LServer, mnesia:write(#reg_users_counter{
count = Size}) vhost = LServer,
end, count = Size
})
end,
mnesia:sync_dirty(F). mnesia:sync_dirty(F).
use_cache(Host) -> use_cache(Host) ->
case mnesia:table_info(passwd, storage_type) of case mnesia:table_info(passwd, storage_type) of
disc_only_copies -> disc_only_copies ->
ejabberd_option:auth_use_cache(Host); ejabberd_option:auth_use_cache(Host);
_ -> _ ->
false false
end. end.
plain_password_required(Server) -> plain_password_required(Server) ->
store_type(Server) == scram. store_type(Server) == scram.
store_type(Server) -> store_type(Server) ->
ejabberd_auth:password_format(Server). ejabberd_auth:password_format(Server).
set_password_multiple(User, Server, Passwords) -> set_password_multiple(User, Server, Passwords) ->
F = fun() -> F = fun() ->
lists:foreach( lists:foreach(
fun(#scram{hash = Hash} = Password) -> fun(#scram{hash = Hash} = Password) ->
mnesia:write(#passwd{us = {User, Server, Hash}, password = Password}); mnesia:write(#passwd{us = {User, Server, Hash}, password = Password});
(Plain) -> (Plain) ->
mnesia:write(#passwd{us = {User, Server, plain}, password = Plain}) mnesia:write(#passwd{us = {User, Server, plain}, password = Plain})
end, Passwords) end,
end, Passwords)
end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, ok} -> {atomic, ok} ->
{cache, {ok, Passwords}}; {cache, {ok, Passwords}};
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{nocache, {error, db_failure}} {nocache, {error, db_failure}}
end. end.
set_password_instance(User, Server, Password) -> set_password_instance(User, Server, Password) ->
F = fun() -> F = fun() ->
case Password of case Password of
#scram{hash = Hash} = Password -> #scram{hash = Hash} = Password ->
mnesia:write(#passwd{us = {User, Server, Hash}, password = Password}); mnesia:write(#passwd{us = {User, Server, Hash}, password = Password});
Plain -> Plain ->
mnesia:write(#passwd{us = {User, Server, plain}, password = Plain}) mnesia:write(#passwd{us = {User, Server, plain}, password = Plain})
end end
end, end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, ok} -> {atomic, ok} ->
ok; ok;
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure} {error, db_failure}
end. end.
try_register_multiple(User, Server, Passwords) -> try_register_multiple(User, Server, Passwords) ->
F = fun() -> F = fun() ->
case mnesia:select(passwd, [{{'_', {'$1', '$2', '_'}, '$3'}, case mnesia:select(passwd,
[{'==', '$1', User}, [{{'_', {'$1', '$2', '_'}, '$3'},
{'==', '$2', Server}], [{'==', '$1', User},
['$3']}]) of {'==', '$2', Server}],
[] -> ['$3']}]) of
lists:foreach( [] ->
fun(#scram{hash = Hash} = Password) -> lists:foreach(
mnesia:write(#passwd{us = {User, Server, Hash}, password = Password}); fun(#scram{hash = Hash} = Password) ->
(Plain) -> mnesia:write(#passwd{us = {User, Server, Hash}, password = Password});
mnesia:write(#passwd{us = {User, Server, plain}, password = Plain}) (Plain) ->
end, Passwords), mnesia:write(#passwd{us = {User, Server, plain}, password = Plain})
mnesia:dirty_update_counter(reg_users_counter, Server, 1), end,
{ok, Passwords}; Passwords),
[_] -> mnesia:dirty_update_counter(reg_users_counter, Server, 1),
{error, exists} {ok, Passwords};
end [_] ->
end, {error, exists}
end
end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, Res} -> {atomic, Res} ->
{cache, Res}; {cache, Res};
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{nocache, {error, db_failure}} {nocache, {error, db_failure}}
end. end.
get_users(Server, []) -> get_users(Server, []) ->
Users = mnesia:dirty_select(passwd, Users = mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'}, [{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Server}], ['$1']}]), [{'==', {element, 2, '$1'}, Server}],
misc:lists_uniq([{U, S} || {U, S, _} <- Users]); ['$1']}]),
misc:lists_uniq([ {U, S} || {U, S, _} <- Users ]);
get_users(Server, [{from, Start}, {to, End}]) get_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) -> when is_integer(Start) and is_integer(End) ->
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]); get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
get_users(Server, [{limit, Limit}, {offset, Offset}]) get_users(Server, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) -> when is_integer(Limit) and is_integer(Offset) ->
case get_users(Server, []) of case get_users(Server, []) of
[] -> [] ->
[]; [];
Users -> Users ->
Set = lists:keysort(1, Users), Set = lists:keysort(1, Users),
L = length(Set), L = length(Set),
Start = if Offset < 1 -> 1; Start = if
Offset > L -> L; Offset < 1 -> 1;
true -> Offset Offset > L -> L;
end, true -> Offset
lists:sublist(Set, Start, Limit) end,
lists:sublist(Set, Start, Limit)
end; end;
get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) -> get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)], Set = [ {U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U) ],
lists:keysort(1, Set); lists:keysort(1, Set);
get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}]) get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and is_integer(End) -> when is_binary(Prefix) and is_integer(Start) and is_integer(End) ->
get_users(Server, [{prefix, Prefix}, {limit, End - Start + 1}, get_users(Server,
{offset, Start}]); [{prefix, Prefix},
{limit, End - Start + 1},
{offset, Start}]);
get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) -> when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) ->
case [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)] of case [ {U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U) ] of
[] -> [] ->
[]; [];
Users -> Users ->
Set = lists:keysort(1, Users), Set = lists:keysort(1, Users),
L = length(Set), L = length(Set),
Start = if Offset < 1 -> 1; Start = if
Offset > L -> L; Offset < 1 -> 1;
true -> Offset Offset > L -> L;
end, true -> Offset
lists:sublist(Set, Start, Limit) end,
lists:sublist(Set, Start, Limit)
end; end;
get_users(Server, _) -> get_users(Server, _) ->
get_users(Server, []). get_users(Server, []).
count_users(Server, []) -> count_users(Server, []) ->
case mnesia:dirty_select( case mnesia:dirty_select(
reg_users_counter, reg_users_counter,
[{#reg_users_counter{vhost = Server, count = '$1'}, [{#reg_users_counter{vhost = Server, count = '$1'},
[], ['$1']}]) of [],
[Count] -> Count; ['$1']}]) of
_ -> 0 [Count] -> Count;
_ -> 0
end; end;
count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) -> count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)], Set = [ {U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U) ],
length(Set); length(Set);
count_users(Server, _) -> count_users(Server, _) ->
count_users(Server, []). count_users(Server, []).
get_password(User, Server) -> get_password(User, Server) ->
case mnesia:dirty_select(passwd, [{{'_', {'$1', '$2', '_'}, '$3'}, case mnesia:dirty_select(passwd,
[{'==', '$1', User}, [{{'_', {'$1', '$2', '_'}, '$3'},
{'==', '$2', Server}], [{'==', '$1', User},
['$3']}]) of {'==', '$2', Server}],
[_|_] = List -> ['$3']}]) of
List2 = lists:map( [_ | _] = List ->
fun({scram, SK, SEK, Salt, IC}) -> List2 = lists:map(
#scram{storedkey = SK, serverkey = SEK, fun({scram, SK, SEK, Salt, IC}) ->
salt = Salt, hash = sha, iterationcount = IC}; #scram{
(Other) -> Other storedkey = SK,
end, List), serverkey = SEK,
{cache, {ok, List2}}; salt = Salt,
_ -> hash = sha,
{cache, error} iterationcount = IC
};
(Other) -> Other
end,
List),
{cache, {ok, List2}};
_ ->
{cache, error}
end. end.
drop_password_type(Server, Hash) -> drop_password_type(Server, Hash) ->
F = fun() -> F = fun() ->
Keys = mnesia:select(passwd, [{{'_', '$1', '_'}, Keys = mnesia:select(passwd,
[{'==', {element, 3, '$1'}, Hash}, [{{'_', '$1', '_'},
{'==', {element, 2, '$1'}, Server}], [{'==', {element, 3, '$1'}, Hash},
['$1']}]), {'==', {element, 2, '$1'}, Server}],
lists:foreach(fun(Key) -> mnesia:delete({passwd, Key}) end, Keys), ['$1']}]),
ok lists:foreach(fun(Key) -> mnesia:delete({passwd, Key}) end, Keys),
end, ok
end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, ok} -> {atomic, ok} ->
ok; ok;
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure} {error, db_failure}
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
F = fun () -> F = fun() ->
Keys = mnesia:select(passwd, [{{'_', '$1', '_'}, Keys = mnesia:select(passwd,
[{'==', {element, 1, '$1'}, User}, [{{'_', '$1', '_'},
{'==', {element, 2, '$1'}, Server}], [{'==', {element, 1, '$1'}, User},
['$1']}]), {'==', {element, 2, '$1'}, Server}],
lists:foreach(fun(Key) -> mnesia:delete({passwd, Key}) end, Keys), ['$1']}]),
mnesia:dirty_update_counter(reg_users_counter, Server, -1), lists:foreach(fun(Key) -> mnesia:delete({passwd, Key}) end, Keys),
ok mnesia:dirty_update_counter(reg_users_counter, Server, -1),
end, ok
end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, ok} -> {atomic, ok} ->
ok; ok;
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure} {error, db_failure}
end. end.
need_transform(#reg_users_counter{}) -> need_transform(#reg_users_counter{}) ->
false; false;
need_transform({passwd, {_U, _S, _T}, _Pass}) -> need_transform({passwd, {_U, _S, _T}, _Pass}) ->
@ -266,32 +318,44 @@ need_transform({passwd, {_U, _S, _T}, _Pass}) ->
need_transform({passwd, {_U, _S}, _Pass}) -> need_transform({passwd, {_U, _S}, _Pass}) ->
true. true.
transform({passwd, {U, S}, Pass}) transform({passwd, {U, S}, Pass})
when is_list(U) orelse is_list(S) orelse is_list(Pass) -> when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
NewUS = {iolist_to_binary(U), iolist_to_binary(S)}, NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
NewPass = case Pass of NewPass = case Pass of
#scram{storedkey = StoredKey, #scram{
serverkey = ServerKey, storedkey = StoredKey,
salt = Salt} -> serverkey = ServerKey,
Pass#scram{ salt = Salt
storedkey = iolist_to_binary(StoredKey), } ->
serverkey = iolist_to_binary(ServerKey), Pass#scram{
salt = iolist_to_binary(Salt)}; storedkey = iolist_to_binary(StoredKey),
_ -> serverkey = iolist_to_binary(ServerKey),
iolist_to_binary(Pass) salt = iolist_to_binary(Salt)
end, };
_ ->
iolist_to_binary(Pass)
end,
transform(#passwd{us = NewUS, password = NewPass}); transform(#passwd{us = NewUS, password = NewPass});
transform(#passwd{us = {U, S}, password = Password} = P) transform(#passwd{us = {U, S}, password = Password} = P)
when is_binary(Password) -> when is_binary(Password) ->
P#passwd{us = {U, S, plain}, password = Password}; P#passwd{us = {U, S, plain}, password = Password};
transform({passwd, {U, S}, {scram, SK, SEK, Salt, IC}}) -> transform({passwd, {U, S}, {scram, SK, SEK, Salt, IC}}) ->
#passwd{us = {U, S, sha}, #passwd{
password = #scram{storedkey = SK, serverkey = SEK, us = {U, S, sha},
salt = Salt, hash = sha, iterationcount = IC}}; password = #scram{
storedkey = SK,
serverkey = SEK,
salt = Salt,
hash = sha,
iterationcount = IC
}
};
transform(#passwd{us = {U, S}, password = #scram{hash = Hash}} = P) -> transform(#passwd{us = {U, S}, password = #scram{hash = Hash}} = P) ->
P#passwd{us = {U, S, Hash}}; P#passwd{us = {U, S, Hash}};
transform(Other) -> Other. transform(Other) -> Other.
import(LServer, [LUser, Password, _TimeStamp]) -> import(LServer, [LUser, Password, _TimeStamp]) ->
mnesia:dirty_write( mnesia:dirty_write(
#passwd{us = {LUser, LServer}, password = Password}). #passwd{us = {LUser, LServer}, password = Password}).

View file

@ -28,52 +28,65 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, check_password/4, -export([start/1,
user_exists/2, store_type/1, plain_password_required/1]). stop/1,
check_password/4,
user_exists/2,
store_type/1,
plain_password_required/1]).
start(_Host) -> start(_Host) ->
ejabberd:start_app(epam). ejabberd:start_app(epam).
stop(_Host) -> stop(_Host) ->
ok. ok.
check_password(User, AuthzId, Host, Password) -> check_password(User, AuthzId, Host, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User -> if
false; AuthzId /= <<>> andalso AuthzId /= User ->
true -> false;
Service = get_pam_service(Host), true ->
UserInfo = case get_pam_userinfotype(Host) of Service = get_pam_service(Host),
username -> User; UserInfo = case get_pam_userinfotype(Host) of
jid -> <<User/binary, "@", Host/binary>> username -> User;
end, jid -> <<User/binary, "@", Host/binary>>
case catch epam:authenticate(Service, UserInfo, Password) of end,
true -> {cache, true}; case catch epam:authenticate(Service, UserInfo, Password) of
false -> {cache, false}; true -> {cache, true};
_ -> {nocache, false} false -> {cache, false};
end _ -> {nocache, false}
end
end. end.
user_exists(User, Host) -> user_exists(User, Host) ->
Service = get_pam_service(Host), Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of UserInfo = case get_pam_userinfotype(Host) of
username -> User; username -> User;
jid -> <<User/binary, "@", Host/binary>> jid -> <<User/binary, "@", Host/binary>>
end, end,
case catch epam:acct_mgmt(Service, UserInfo) of case catch epam:acct_mgmt(Service, UserInfo) of
true -> {cache, true}; true -> {cache, true};
false -> {cache, false}; false -> {cache, false};
_Err -> {nocache, {error, db_failure}} _Err -> {nocache, {error, db_failure}}
end. end.
plain_password_required(_) -> true. plain_password_required(_) -> true.
store_type(_) -> external. store_type(_) -> external.
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
get_pam_service(Host) -> get_pam_service(Host) ->
ejabberd_option:pam_service(Host). ejabberd_option:pam_service(Host).
get_pam_userinfotype(Host) -> get_pam_userinfotype(Host) ->
ejabberd_option:pam_userinfotype(Host). ejabberd_option:pam_userinfotype(Host).

View file

@ -25,22 +25,33 @@
-module(ejabberd_auth_sql). -module(ejabberd_auth_sql).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password_multiple/3, try_register_multiple/3, -export([start/1,
get_users/2, count_users/2, get_password/2, stop/1,
remove_user/2, store_type/1, plain_password_required/1, set_password_multiple/3,
export/1, which_users_exists/2, drop_password_type/2, set_password_instance/3]). try_register_multiple/3,
get_users/2,
count_users/2,
get_password/2,
remove_user/2,
store_type/1,
plain_password_required/1,
export/1,
which_users_exists/2,
drop_password_type/2,
set_password_instance/3]).
-export([sql_schemas/0]). -export([sql_schemas/0]).
-include_lib("xmpp/include/scram.hrl"). -include_lib("xmpp/include/scram.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_sql_pt.hrl"). -include("ejabberd_sql_pt.hrl").
-include("ejabberd_auth.hrl"). -include("ejabberd_auth.hrl").
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -48,251 +59,341 @@ start(Host) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()), ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()),
ok. ok.
sql_schemas() -> sql_schemas() ->
[ [#sql_schema{
#sql_schema{ version = 2,
version = 2, tables =
tables = [#sql_table{
[#sql_table{ name = <<"users">>,
name = <<"users">>, columns =
columns = [#sql_column{name = <<"username">>, type = text},
[#sql_column{name = <<"username">>, type = text}, #sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"server_host">>, type = text}, #sql_column{name = <<"type">>, type = smallint},
#sql_column{name = <<"type">>, type = smallint}, #sql_column{name = <<"password">>, type = text},
#sql_column{name = <<"password">>, type = text}, #sql_column{
#sql_column{name = <<"serverkey">>, type = {text, 128}, name = <<"serverkey">>,
default = true}, type = {text, 128},
#sql_column{name = <<"salt">>, type = {text, 128}, default = true
default = true}, },
#sql_column{name = <<"iterationcount">>, type = integer, #sql_column{
default = true}, name = <<"salt">>,
#sql_column{name = <<"created_at">>, type = timestamp, type = {text, 128},
default = true}], default = true
indices = [#sql_index{ },
columns = [<<"server_host">>, <<"username">>, <<"type">>], #sql_column{
unique = true}]}], name = <<"iterationcount">>,
update = [ type = integer,
{add_column, <<"users">>, <<"type">>}, default = true
{update_primary_key,<<"users">>, },
[<<"server_host">>, <<"username">>, <<"type">>]} #sql_column{
]}, name = <<"created_at">>,
#sql_schema{ type = timestamp,
version = 1, default = true
tables = }],
[#sql_table{ indices = [#sql_index{
name = <<"users">>, columns = [<<"server_host">>, <<"username">>, <<"type">>],
columns = unique = true
[#sql_column{name = <<"username">>, type = text}, }]
#sql_column{name = <<"server_host">>, type = text}, }],
#sql_column{name = <<"password">>, type = text}, update = [{add_column, <<"users">>, <<"type">>},
#sql_column{name = <<"serverkey">>, type = {text, 128}, {update_primary_key, <<"users">>,
default = true}, [<<"server_host">>, <<"username">>, <<"type">>]}]
#sql_column{name = <<"salt">>, type = {text, 128}, },
default = true}, #sql_schema{
#sql_column{name = <<"iterationcount">>, type = integer, version = 1,
default = true}, tables =
#sql_column{name = <<"created_at">>, type = timestamp, [#sql_table{
default = true}], name = <<"users">>,
indices = [#sql_index{ columns =
columns = [<<"server_host">>, <<"username">>], [#sql_column{name = <<"username">>, type = text},
unique = true}]}]}]. #sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"password">>, type = text},
#sql_column{
name = <<"serverkey">>,
type = {text, 128},
default = true
},
#sql_column{
name = <<"salt">>,
type = {text, 128},
default = true
},
#sql_column{
name = <<"iterationcount">>,
type = integer,
default = true
},
#sql_column{
name = <<"created_at">>,
type = timestamp,
default = true
}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true
}]
}]
}].
stop(_Host) -> ok. stop(_Host) -> ok.
plain_password_required(Server) -> plain_password_required(Server) ->
store_type(Server) == scram. store_type(Server) == scram.
store_type(Server) -> store_type(Server) ->
ejabberd_auth:password_format(Server). ejabberd_auth:password_format(Server).
hash_to_num(plain) -> 1; hash_to_num(plain) -> 1;
hash_to_num(sha) -> 2; hash_to_num(sha) -> 2;
hash_to_num(sha256) -> 3; hash_to_num(sha256) -> 3;
hash_to_num(sha512) -> 4. hash_to_num(sha512) -> 4.
num_to_hash(2) -> sha; num_to_hash(2) -> sha;
num_to_hash(3) -> sha256; num_to_hash(3) -> sha256;
num_to_hash(4) -> sha512. num_to_hash(4) -> sha512.
set_password_instance(User, Server, #scram{hash = Hash, storedkey = SK, serverkey = SEK,
salt = Salt, iterationcount = IC}) -> set_password_instance(User,
Server,
#scram{
hash = Hash,
storedkey = SK,
serverkey = SEK,
salt = Salt,
iterationcount = IC
}) ->
F = fun() -> F = fun() ->
set_password_scram_t(User, Server, Hash, set_password_scram_t(User,
SK, SEK, Salt, IC) Server,
end, Hash,
SK,
SEK,
Salt,
IC)
end,
case ejabberd_sql:sql_transaction(Server, F) of case ejabberd_sql:sql_transaction(Server, F) of
{atomic, _} -> {atomic, _} ->
ok; ok;
{aborted, _} -> {aborted, _} ->
{error, db_failure} {error, db_failure}
end; end;
set_password_instance(User, Server, Plain) -> set_password_instance(User, Server, Plain) ->
F = fun() -> F = fun() ->
set_password_t(User, Server, Plain) set_password_t(User, Server, Plain)
end, end,
case ejabberd_sql:sql_transaction(Server, F) of case ejabberd_sql:sql_transaction(Server, F) of
{atomic, _} -> {atomic, _} ->
ok; ok;
{aborted, _} -> {aborted, _} ->
{error, db_failure} {error, db_failure}
end. end.
set_password_multiple(User, Server, Passwords) -> set_password_multiple(User, Server, Passwords) ->
F = F =
fun() -> fun() ->
ejabberd_sql:sql_query_t( ejabberd_sql:sql_query_t(
?SQL("delete from users where username=%(User)s and %(Server)H")), ?SQL("delete from users where username=%(User)s and %(Server)H")),
lists:foreach( lists:foreach(
fun(#scram{hash = Hash, storedkey = SK, serverkey = SEK, fun(#scram{
salt = Salt, iterationcount = IC}) -> hash = Hash,
set_password_scram_t( storedkey = SK,
User, Server, Hash, serverkey = SEK,
SK, SEK, Salt, IC); salt = Salt,
(Plain) -> iterationcount = IC
set_password_t(User, Server, Plain) }) ->
end, Passwords) set_password_scram_t(
end, User,
Server,
Hash,
SK,
SEK,
Salt,
IC);
(Plain) ->
set_password_t(User, Server, Plain)
end,
Passwords)
end,
case ejabberd_sql:sql_transaction(Server, F) of case ejabberd_sql:sql_transaction(Server, F) of
{atomic, _} -> {atomic, _} ->
{cache, {ok, Passwords}}; {cache, {ok, Passwords}};
{aborted, _} -> {aborted, _} ->
{nocache, {error, db_failure}} {nocache, {error, db_failure}}
end. end.
try_register_multiple(User, Server, Passwords) -> try_register_multiple(User, Server, Passwords) ->
F = F =
fun() -> fun() ->
case ejabberd_sql:sql_query_t( case ejabberd_sql:sql_query_t(
?SQL("select @(count(*))d from users where username=%(User)s and %(Server)H")) of ?SQL("select @(count(*))d from users where username=%(User)s and %(Server)H")) of
{selected, [{0}]} -> {selected, [{0}]} ->
lists:foreach( lists:foreach(
fun(#scram{hash = Hash, storedkey = SK, serverkey = SEK, fun(#scram{
salt = Salt, iterationcount = IC}) -> hash = Hash,
set_password_scram_t( storedkey = SK,
User, Server, Hash, serverkey = SEK,
SK, SEK, Salt, IC); salt = Salt,
(Plain) -> iterationcount = IC
set_password_t(User, Server, Plain) }) ->
end, Passwords), set_password_scram_t(
{cache, {ok, Passwords}}; User,
{selected, _} -> Server,
{nocache, {error, exists}}; Hash,
_ -> SK,
{nocache, {error, db_failure}} SEK,
end Salt,
end, IC);
(Plain) ->
set_password_t(User, Server, Plain)
end,
Passwords),
{cache, {ok, Passwords}};
{selected, _} ->
{nocache, {error, exists}};
_ ->
{nocache, {error, db_failure}}
end
end,
case ejabberd_sql:sql_transaction(Server, F) of case ejabberd_sql:sql_transaction(Server, F) of
{atomic, Res} -> {atomic, Res} ->
Res; Res;
{aborted, _} -> {aborted, _} ->
{nocache, {error, db_failure}} {nocache, {error, db_failure}}
end. end.
get_users(Server, Opts) -> get_users(Server, Opts) ->
case list_users(Server, Opts) of case list_users(Server, Opts) of
{selected, Res} -> {selected, Res} ->
[{U, Server} || {U} <- Res]; [ {U, Server} || {U} <- Res ];
_ -> [] _ -> []
end. end.
count_users(Server, Opts) -> count_users(Server, Opts) ->
case users_number(Server, Opts) of case users_number(Server, Opts) of
{selected, [{Res}]} -> {selected, [{Res}]} ->
Res; Res;
_Other -> 0 _Other -> 0
end. end.
get_password(User, Server) -> get_password(User, Server) ->
case get_password_scram(Server, User) of case get_password_scram(Server, User) of
{selected, []} -> {selected, []} ->
{cache, error}; {cache, error};
{selected, Passwords} -> {selected, Passwords} ->
Converted = lists:map( Converted = lists:map(
fun({0, Password, <<>>, <<>>, 0}) -> fun({0, Password, <<>>, <<>>, 0}) ->
update_password_type(User, Server, 1), update_password_type(User, Server, 1),
Password; Password;
({_, Password, <<>>, <<>>, 0}) -> ({_, Password, <<>>, <<>>, 0}) ->
Password; Password;
({0, StoredKey, ServerKey, Salt, IterationCount}) -> ({0, StoredKey, ServerKey, Salt, IterationCount}) ->
{Hash, SK} = case StoredKey of {Hash, SK} = case StoredKey of
<<"sha256:", Rest/binary>> -> <<"sha256:", Rest/binary>> ->
update_password_type(User, Server, 3, Rest), update_password_type(User, Server, 3, Rest),
{sha256, Rest}; {sha256, Rest};
<<"sha512:", Rest/binary>> -> <<"sha512:", Rest/binary>> ->
update_password_type(User, Server, 4, Rest), update_password_type(User, Server, 4, Rest),
{sha512, Rest}; {sha512, Rest};
Other -> Other ->
update_password_type(User, Server, 2), update_password_type(User, Server, 2),
{sha, Other} {sha, Other}
end, end,
#scram{storedkey = SK, #scram{
serverkey = ServerKey, storedkey = SK,
salt = Salt, serverkey = ServerKey,
hash = Hash, salt = Salt,
iterationcount = IterationCount}; hash = Hash,
({Type, StoredKey, ServerKey, Salt, IterationCount}) -> iterationcount = IterationCount
Hash = num_to_hash(Type), };
#scram{storedkey = StoredKey, ({Type, StoredKey, ServerKey, Salt, IterationCount}) ->
serverkey = ServerKey, Hash = num_to_hash(Type),
salt = Salt, #scram{
hash = Hash, storedkey = StoredKey,
iterationcount = IterationCount} serverkey = ServerKey,
end, Passwords), salt = Salt,
{cache, {ok, Converted}}; hash = Hash,
_ -> iterationcount = IterationCount
{nocache, error} }
end,
Passwords),
{cache, {ok, Converted}};
_ ->
{nocache, error}
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
case del_user(Server, User) of case del_user(Server, User) of
{updated, _} -> {updated, _} ->
ok; ok;
_ -> _ ->
{error, db_failure} {error, db_failure}
end. end.
drop_password_type(LServer, Hash) -> drop_password_type(LServer, Hash) ->
Type = hash_to_num(Hash), Type = hash_to_num(Hash),
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
?SQL("delete from users" ?SQL("delete from users"
" where type=%(Type)d and %(LServer)H")). " where type=%(Type)d and %(LServer)H")).
set_password_scram_t(LUser, LServer, Hash,
StoredKey, ServerKey, Salt, IterationCount) -> set_password_scram_t(LUser,
LServer,
Hash,
StoredKey,
ServerKey,
Salt,
IterationCount) ->
Type = hash_to_num(Hash), Type = hash_to_num(Hash),
?SQL_UPSERT_T( ?SQL_UPSERT_T(
"users", "users",
["!username=%(LUser)s", ["!username=%(LUser)s",
"!server_host=%(LServer)s", "!server_host=%(LServer)s",
"!type=%(Type)d", "!type=%(Type)d",
"password=%(StoredKey)s", "password=%(StoredKey)s",
"serverkey=%(ServerKey)s", "serverkey=%(ServerKey)s",
"salt=%(Salt)s", "salt=%(Salt)s",
"iterationcount=%(IterationCount)d"]). "iterationcount=%(IterationCount)d"]).
set_password_t(LUser, LServer, Password) -> set_password_t(LUser, LServer, Password) ->
?SQL_UPSERT_T( ?SQL_UPSERT_T(
"users", "users",
["!username=%(LUser)s", ["!username=%(LUser)s",
"!server_host=%(LServer)s", "!server_host=%(LServer)s",
"!type=1", "!type=1",
"password=%(Password)s", "password=%(Password)s",
"serverkey=''", "serverkey=''",
"salt=''", "salt=''",
"iterationcount=0"]). "iterationcount=0"]).
update_password_type(LUser, LServer, Type, Password) -> update_password_type(LUser, LServer, Type, Password) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
?SQL("update users set type=%(Type)d, password=%(Password)s" ?SQL("update users set type=%(Type)d, password=%(Password)s"
" where username=%(LUser)s and type=0 and %(LServer)H")). " where username=%(LUser)s and type=0 and %(LServer)H")).
update_password_type(LUser, LServer, Type) -> update_password_type(LUser, LServer, Type) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
?SQL("update users set type=%(Type)d" ?SQL("update users set type=%(Type)d"
" where username=%(LUser)s and type=0 and %(LServer)H")). " where username=%(LUser)s and type=0 and %(LServer)H")).
get_password_scram(LServer, LUser) -> get_password_scram(LServer, LUser) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
@ -301,28 +402,31 @@ get_password_scram(LServer, LUser) ->
" from users" " from users"
" where username=%(LUser)s and %(LServer)H")). " where username=%(LUser)s and %(LServer)H")).
del_user(LServer, LUser) -> del_user(LServer, LUser) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
?SQL("delete from users where username=%(LUser)s and %(LServer)H")). ?SQL("delete from users where username=%(LUser)s and %(LServer)H")).
list_users(LServer, []) -> list_users(LServer, []) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(distinct username)s from users where %(LServer)H")); ?SQL("select @(distinct username)s from users where %(LServer)H"));
list_users(LServer, [{from, Start}, {to, End}]) list_users(LServer, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) -> when is_integer(Start) and is_integer(End) ->
list_users(LServer, list_users(LServer,
[{limit, End - Start + 1}, {offset, Start - 1}]); [{limit, End - Start + 1}, {offset, Start - 1}]);
list_users(LServer, list_users(LServer,
[{prefix, Prefix}, {from, Start}, {to, End}]) [{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and when is_binary(Prefix) and is_integer(Start) and
is_integer(End) -> is_integer(End) ->
list_users(LServer, list_users(LServer,
[{prefix, Prefix}, {limit, End - Start + 1}, [{prefix, Prefix},
{offset, Start - 1}]); {limit, End - Start + 1},
{offset, Start - 1}]);
list_users(LServer, [{limit, Limit}, {offset, Offset}]) list_users(LServer, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) -> when is_integer(Limit) and is_integer(Offset) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(distinct username)s from users " ?SQL("select @(distinct username)s from users "
@ -330,9 +434,9 @@ list_users(LServer, [{limit, Limit}, {offset, Offset}])
"order by username " "order by username "
"limit %(Limit)d offset %(Offset)d")); "limit %(Limit)d offset %(Offset)d"));
list_users(LServer, list_users(LServer,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) -> is_integer(Offset) ->
SPrefix = ejabberd_sql:escape_like_arg(Prefix), SPrefix = ejabberd_sql:escape_like_arg(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>, SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
@ -342,12 +446,12 @@ list_users(LServer,
"order by username " "order by username "
"limit %(Limit)d offset %(Offset)d")). "limit %(Limit)d offset %(Offset)d")).
users_number(LServer) -> users_number(LServer) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
LServer, LServer,
fun(pgsql, _) -> fun(pgsql, _) ->
case case ejabberd_option:pgsql_users_number_estimate(LServer) of
ejabberd_option:pgsql_users_number_estimate(LServer) of
true -> true ->
ejabberd_sql:sql_query_t( ejabberd_sql:sql_query_t(
?SQL("select @(reltuples :: bigint)d from pg_class" ?SQL("select @(reltuples :: bigint)d from pg_class"
@ -355,14 +459,15 @@ users_number(LServer) ->
_ -> _ ->
ejabberd_sql:sql_query_t( ejabberd_sql:sql_query_t(
?SQL("select @(count(distinct username))d from users where %(LServer)H")) ?SQL("select @(count(distinct username))d from users where %(LServer)H"))
end; end;
(_Type, _) -> (_Type, _) ->
ejabberd_sql:sql_query_t( ejabberd_sql:sql_query_t(
?SQL("select @(count(distinct username))d from users where %(LServer)H")) ?SQL("select @(count(distinct username))d from users where %(LServer)H"))
end). end).
users_number(LServer, [{prefix, Prefix}]) users_number(LServer, [{prefix, Prefix}])
when is_binary(Prefix) -> when is_binary(Prefix) ->
SPrefix = ejabberd_sql:escape_like_arg(Prefix), SPrefix = ejabberd_sql:escape_like_arg(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>, SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
@ -372,16 +477,18 @@ users_number(LServer, [{prefix, Prefix}])
users_number(LServer, []) -> users_number(LServer, []) ->
users_number(LServer). users_number(LServer).
which_users_exists(LServer, LUsers) when length(LUsers) =< 100 -> which_users_exists(LServer, LUsers) when length(LUsers) =< 100 ->
try ejabberd_sql:sql_query( try ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(distinct username)s from users where username in %(LUsers)ls")) of ?SQL("select @(distinct username)s from users where username in %(LUsers)ls")) of
{selected, Matching} -> {selected, Matching} ->
[U || {U} <- Matching]; [ U || {U} <- Matching ];
{error, _} = E -> {error, _} = E ->
E E
catch _:B -> catch
{error, B} _:B ->
{error, B}
end; end;
which_users_exists(LServer, LUsers) -> which_users_exists(LServer, LUsers) ->
{First, Rest} = lists:split(100, LUsers), {First, Rest} = lists:split(100, LUsers),
@ -397,6 +504,7 @@ which_users_exists(LServer, LUsers) ->
end end
end. end.
export(_Server) -> export(_Server) ->
[{passwd, [{passwd,
fun(Host, #passwd{us = {LUser, LServer, plain}, password = Password}) fun(Host, #passwd{us = {LUser, LServer, plain}, password = Password})
@ -404,43 +512,44 @@ export(_Server) ->
is_binary(Password) -> is_binary(Password) ->
[?SQL("delete from users where username=%(LUser)s and type=1 and %(LServer)H;"), [?SQL("delete from users where username=%(LUser)s and type=1 and %(LServer)H;"),
?SQL_INSERT( ?SQL_INSERT(
"users", "users",
["username=%(LUser)s", ["username=%(LUser)s",
"server_host=%(LServer)s", "server_host=%(LServer)s",
"type=1", "type=1",
"password=%(Password)s"])]; "password=%(Password)s"])];
(Host, {passwd, {LUser, LServer, _}, (Host,
{scram, StoredKey, ServerKey, Salt, IterationCount}}) {passwd, {LUser, LServer, _},
{scram, StoredKey, ServerKey, Salt, IterationCount}})
when LServer == Host -> when LServer == Host ->
Hash = sha, Hash = sha,
Type = hash_to_num(Hash), Type = hash_to_num(Hash),
[?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"), [?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"),
?SQL_INSERT( ?SQL_INSERT(
"users", "users",
["username=%(LUser)s", ["username=%(LUser)s",
"server_host=%(LServer)s", "server_host=%(LServer)s",
"type=%(Type)d", "type=%(Type)d",
"password=%(StoredKey)s", "password=%(StoredKey)s",
"serverkey=%(ServerKey)s", "serverkey=%(ServerKey)s",
"salt=%(Salt)s", "salt=%(Salt)s",
"iterationcount=%(IterationCount)d"])]; "iterationcount=%(IterationCount)d"])];
(Host, #passwd{us = {LUser, LServer, _}, password = #scram{} = Scram}) (Host, #passwd{us = {LUser, LServer, _}, password = #scram{} = Scram})
when LServer == Host -> when LServer == Host ->
StoredKey = Scram#scram.storedkey, StoredKey = Scram#scram.storedkey,
ServerKey = Scram#scram.serverkey, ServerKey = Scram#scram.serverkey,
Salt = Scram#scram.salt, Salt = Scram#scram.salt,
IterationCount = Scram#scram.iterationcount, IterationCount = Scram#scram.iterationcount,
Type = hash_to_num(Scram#scram.hash), Type = hash_to_num(Scram#scram.hash),
[?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"), [?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"),
?SQL_INSERT( ?SQL_INSERT(
"users", "users",
["username=%(LUser)s", ["username=%(LUser)s",
"server_host=%(LServer)s", "server_host=%(LServer)s",
"type=%(Type)d", "type=%(Type)d",
"password=%(StoredKey)s", "password=%(StoredKey)s",
"serverkey=%(ServerKey)s", "serverkey=%(ServerKey)s",
"salt=%(Salt)s", "salt=%(Salt)s",
"iterationcount=%(IterationCount)d"])]; "iterationcount=%(IterationCount)d"])];
(_Host, _R) -> (_Host, _R) ->
[] []
end}]. end}].

View file

@ -29,12 +29,14 @@
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
%%%=================================================================== %%%===================================================================
%%% API functions %%% API functions
%%%=================================================================== %%%===================================================================
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%%%=================================================================== %%%===================================================================
%%% Supervisor callbacks %%% Supervisor callbacks
%%%=================================================================== %%%===================================================================

View file

@ -34,8 +34,12 @@
-export([start_link/0]). -export([start_link/0]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, -export([init/1,
code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([register_task/5, task_status/1, abort_task/1]). -export([register_task/5, task_status/1, abort_task/1]).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
@ -47,159 +51,178 @@
%%% API %%% API
%%%=================================================================== %%%===================================================================
%% @doc Spawns the server and registers the local name (unique) %% @doc Spawns the server and registers the local name (unique)
-spec(start_link() -> -spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}). {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
register_task(Type, Steps, Rate, JobState, JobFun) -> register_task(Type, Steps, Rate, JobState, JobFun) ->
gen_server:call(?MODULE, {register_task, Type, Steps, Rate, JobState, JobFun}). gen_server:call(?MODULE, {register_task, Type, Steps, Rate, JobState, JobFun}).
task_status(Type) -> task_status(Type) ->
gen_server:call(?MODULE, {task_status, Type}). gen_server:call(?MODULE, {task_status, Type}).
abort_task(Type) -> abort_task(Type) ->
gen_server:call(?MODULE, {abort_task, Type}). gen_server:call(?MODULE, {abort_task, Type}).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
%% @private %% @private
%% @doc Initializes the server %% @doc Initializes the server
-spec(init(Args :: term()) -> -spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | {ok, State :: #state{}} |
{stop, Reason :: term()} | ignore). {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} |
ignore).
init([]) -> init([]) ->
{ok, #state{}}. {ok, #state{}}.
%% @private %% @private
%% @doc Handling call messages %% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, -spec(handle_call(Request :: term(),
State :: #state{}) -> From :: {pid(), Tag :: term()},
{reply, Reply :: term(), NewState :: #state{}} | State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | {reply, Reply :: term(), NewState :: #state{}} |
{noreply, NewState :: #state{}} | {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call({register_task, Type, Steps, Rate, JobState, JobFun}, _From, #state{tasks = Tasks} = State) -> handle_call({register_task, Type, Steps, Rate, JobState, JobFun}, _From, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, #task{}) of case maps:get(Type, Tasks, #task{}) of
#task{state = S} when S == completed; S == not_started; S == aborted; S == failed -> #task{state = S} when S == completed; S == not_started; S == aborted; S == failed ->
Pid = spawn(fun() -> work_loop(Type, JobState, JobFun, Rate, erlang:monotonic_time(second), 0) end), Pid = spawn(fun() -> work_loop(Type, JobState, JobFun, Rate, erlang:monotonic_time(second), 0) end),
Tasks2 = maps:put(Type, #task{state = working, pid = Pid, steps = Steps, done_steps = 0}, Tasks), Tasks2 = maps:put(Type, #task{state = working, pid = Pid, steps = Steps, done_steps = 0}, Tasks),
{reply, ok, #state{tasks = Tasks2}}; {reply, ok, #state{tasks = Tasks2}};
#task{state = working} -> #task{state = working} ->
{reply, {error, in_progress}, State} {reply, {error, in_progress}, State}
end; end;
handle_call({task_status, Type}, _From, #state{tasks = Tasks} = State) -> handle_call({task_status, Type}, _From, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of case maps:get(Type, Tasks, none) of
none -> none ->
{reply, not_started, State}; {reply, not_started, State};
#task{state = not_started} -> #task{state = not_started} ->
{reply, not_started, State}; {reply, not_started, State};
#task{state = failed, done_steps = Steps, pid = Error} -> #task{state = failed, done_steps = Steps, pid = Error} ->
{reply, {failed, Steps, Error}, State}; {reply, {failed, Steps, Error}, State};
#task{state = aborted, done_steps = Steps} -> #task{state = aborted, done_steps = Steps} ->
{reply, {aborted, Steps}, State}; {reply, {aborted, Steps}, State};
#task{state = working, done_steps = Steps} -> #task{state = working, done_steps = Steps} ->
{reply, {working, Steps}, State}; {reply, {working, Steps}, State};
#task{state = completed, done_steps = Steps} -> #task{state = completed, done_steps = Steps} ->
{reply, {completed, Steps}, State} {reply, {completed, Steps}, State}
end; end;
handle_call({abort_task, Type}, _From, #state{tasks = Tasks} = State) -> handle_call({abort_task, Type}, _From, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid} = T -> #task{state = working, pid = Pid} = T ->
Pid ! abort, Pid ! abort,
Tasks2 = maps:put(Type, T#task{state = aborted, pid = none}, Tasks), Tasks2 = maps:put(Type, T#task{state = aborted, pid = none}, Tasks),
{reply, aborted, State#state{tasks = Tasks2}}; {reply, aborted, State#state{tasks = Tasks2}};
_ -> _ ->
{reply, not_started, State} {reply, not_started, State}
end; end;
handle_call(_Request, _From, State = #state{}) -> handle_call(_Request, _From, State = #state{}) ->
{reply, ok, State}. {reply, ok, State}.
%% @private %% @private
%% @doc Handling cast messages %% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) -> -spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} | {noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_cast({task_finished, Type, Pid}, #state{tasks = Tasks} = State) -> handle_cast({task_finished, Type, Pid}, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid2} = T when Pid == Pid2 -> #task{state = working, pid = Pid2} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{state = completed, pid = none}, Tasks), Tasks2 = maps:put(Type, T#task{state = completed, pid = none}, Tasks),
{noreply, State#state{tasks = Tasks2}}; {noreply, State#state{tasks = Tasks2}};
_ -> _ ->
{noreply, State} {noreply, State}
end; end;
handle_cast({task_progress, Type, Pid, Count}, #state{tasks = Tasks} = State) -> handle_cast({task_progress, Type, Pid, Count}, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid2, done_steps = Steps} = T when Pid == Pid2 -> #task{state = working, pid = Pid2, done_steps = Steps} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{done_steps = Steps + Count}, Tasks), Tasks2 = maps:put(Type, T#task{done_steps = Steps + Count}, Tasks),
{noreply, State#state{tasks = Tasks2}}; {noreply, State#state{tasks = Tasks2}};
_ -> _ ->
{noreply, State} {noreply, State}
end; end;
handle_cast({task_error, Type, Pid, Error}, #state{tasks = Tasks} = State) -> handle_cast({task_error, Type, Pid, Error}, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid2} = T when Pid == Pid2 -> #task{state = working, pid = Pid2} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{state = failed, pid = Error}, Tasks), Tasks2 = maps:put(Type, T#task{state = failed, pid = Error}, Tasks),
{noreply, State#state{tasks = Tasks2}}; {noreply, State#state{tasks = Tasks2}};
_ -> _ ->
{noreply, State} {noreply, State}
end; end;
handle_cast(_Request, State = #state{}) -> handle_cast(_Request, State = #state{}) ->
{noreply, State}. {noreply, State}.
%% @private %% @private
%% @doc Handling all non call/cast messages %% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> -spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} | {noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_info(_Info, State = #state{}) -> handle_info(_Info, State = #state{}) ->
{noreply, State}. {noreply, State}.
%% @private %% @private
%% @doc This function is called by a gen_server when it is about to %% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any %% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates %% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored. %% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), -spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()). State :: #state{}) -> term()).
terminate(_Reason, _State = #state{}) -> terminate(_Reason, _State = #state{}) ->
ok. ok.
%% @private %% @private
%% @doc Convert process state when code is changed %% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, -spec(code_change(OldVsn :: term() | {down, term()},
Extra :: term()) -> State :: #state{},
{ok, NewState :: #state{}} | {error, Reason :: term()}). Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) -> code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
work_loop(Task, JobState, JobFun, Rate, StartDate, CurrentProgress) -> work_loop(Task, JobState, JobFun, Rate, StartDate, CurrentProgress) ->
try JobFun(JobState) of try JobFun(JobState) of
{ok, _NewState, 0} -> {ok, _NewState, 0} ->
gen_server:cast(?MODULE, {task_finished, Task, self()}); gen_server:cast(?MODULE, {task_finished, Task, self()});
{ok, NewState, Count} -> {ok, NewState, Count} ->
gen_server:cast(?MODULE, {task_progress, Task, self(), Count}), gen_server:cast(?MODULE, {task_progress, Task, self(), Count}),
NewProgress = CurrentProgress + Count, NewProgress = CurrentProgress + Count,
TimeSpent = erlang:monotonic_time(second) - StartDate, TimeSpent = erlang:monotonic_time(second) - StartDate,
SleepTime = max(0, NewProgress/Rate*60 - TimeSpent), SleepTime = max(0, NewProgress / Rate * 60 - TimeSpent),
receive receive
abort -> ok abort -> ok
after round(SleepTime*1000) -> after
work_loop(Task, NewState, JobFun, Rate, StartDate, NewProgress) round(SleepTime * 1000) ->
end; work_loop(Task, NewState, JobFun, Rate, StartDate, NewProgress)
{error, Error} -> end;
gen_server:cast(?MODULE, {task_error, Task, self(), Error}) {error, Error} ->
catch _:_ -> gen_server:cast(?MODULE, {task_error, Task, self(), Error})
gen_server:cast(?MODULE, {task_error, Task, self(), internal_error}) catch
_:_ ->
gen_server:cast(?MODULE, {task_error, Task, self(), internal_error})
end. end.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -30,23 +30,27 @@
-export([get_c2s_limits/0]). -export([get_c2s_limits/0]).
%% Get first c2s configuration limitations to apply it to other c2s %% Get first c2s configuration limitations to apply it to other c2s
%% connectors. %% connectors.
get_c2s_limits() -> get_c2s_limits() ->
C2SFirstListen = ejabberd_option:listen(), C2SFirstListen = ejabberd_option:listen(),
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
false -> []; false -> [];
{value, {_Port, ejabberd_c2s, Opts}} -> {value, {_Port, ejabberd_c2s, Opts}} ->
select_opts_values(Opts) select_opts_values(Opts)
end. end.
%% Only get access, shaper and max_stanza_size values %% Only get access, shaper and max_stanza_size values
select_opts_values(Opts) -> select_opts_values(Opts) ->
maps:fold( maps:fold(
fun(Opt, Val, Acc) when Opt == access; fun(Opt, Val, Acc) when Opt == access;
Opt == shaper; Opt == shaper;
Opt == max_stanza_size -> Opt == max_stanza_size ->
[{Opt, Val}|Acc]; [{Opt, Val} | Acc];
(_, _, Acc) -> (_, _, Acc) ->
Acc Acc
end, [], Opts). end,
[],
Opts).

File diff suppressed because it is too large Load diff

View file

@ -24,14 +24,27 @@
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/0, call/4, call/5, multicall/3, multicall/4, multicall/5, -export([start_link/0,
eval_everywhere/3, eval_everywhere/4]). call/4, call/5,
multicall/3, multicall/4, multicall/5,
eval_everywhere/3, eval_everywhere/4]).
%% Backend dependent API %% Backend dependent API
-export([get_nodes/0, get_known_nodes/0, join/1, leave/1, subscribe/0, -export([get_nodes/0,
subscribe/1, node_id/0, get_node_by_id/1, send/2, wait_for_sync/1]). get_known_nodes/0,
join/1,
leave/1,
subscribe/0, subscribe/1,
node_id/0,
get_node_by_id/1,
send/2,
wait_for_sync/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
%% hooks %% hooks
-export([set_ticktime/0]). -export([set_ticktime/0]).
@ -39,6 +52,7 @@
-type dst() :: pid() | atom() | {atom(), node()}. -type dst() :: pid() | atom() | {atom(), node()}.
-callback init() -> ok | {error, any()}. -callback init() -> ok | {error, any()}.
-callback get_nodes() -> [node()]. -callback get_nodes() -> [node()].
-callback get_known_nodes() -> [node()]. -callback get_known_nodes() -> [node()].
@ -52,42 +66,51 @@
-record(state, {}). -record(state, {}).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec call(node(), module(), atom(), [any()]) -> any(). -spec call(node(), module(), atom(), [any()]) -> any().
call(Node, Module, Function, Args) -> call(Node, Module, Function, Args) ->
call(Node, Module, Function, Args, rpc_timeout()). call(Node, Module, Function, Args, rpc_timeout()).
-spec call(node(), module(), atom(), [any()], timeout()) -> any(). -spec call(node(), module(), atom(), [any()], timeout()) -> any().
call(Node, Module, Function, Args, Timeout) -> call(Node, Module, Function, Args, Timeout) ->
rpc:call(Node, Module, Function, Args, Timeout). rpc:call(Node, Module, Function, Args, Timeout).
-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}. -spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
multicall(Module, Function, Args) -> multicall(Module, Function, Args) ->
multicall(get_nodes(), Module, Function, Args). multicall(get_nodes(), Module, Function, Args).
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}. -spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
multicall(Nodes, Module, Function, Args) -> multicall(Nodes, Module, Function, Args) ->
multicall(Nodes, Module, Function, Args, rpc_timeout()). multicall(Nodes, Module, Function, Args, rpc_timeout()).
-spec multicall([node()], module(), atom(), list(), timeout()) -> {list(), [node()]}. -spec multicall([node()], module(), atom(), list(), timeout()) -> {list(), [node()]}.
multicall(Nodes, Module, Function, Args, Timeout) -> multicall(Nodes, Module, Function, Args, Timeout) ->
rpc:multicall(Nodes, Module, Function, Args, Timeout). rpc:multicall(Nodes, Module, Function, Args, Timeout).
-spec eval_everywhere(module(), atom(), [any()]) -> ok. -spec eval_everywhere(module(), atom(), [any()]) -> ok.
eval_everywhere(Module, Function, Args) -> eval_everywhere(Module, Function, Args) ->
eval_everywhere(get_nodes(), Module, Function, Args), eval_everywhere(get_nodes(), Module, Function, Args),
ok. ok.
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok. -spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
eval_everywhere(Nodes, Module, Function, Args) -> eval_everywhere(Nodes, Module, Function, Args) ->
rpc:eval_everywhere(Nodes, Module, Function, Args), rpc:eval_everywhere(Nodes, Module, Function, Args),
ok. ok.
%%%=================================================================== %%%===================================================================
%%% Backend dependent API %%% Backend dependent API
%%%=================================================================== %%%===================================================================
@ -96,31 +119,37 @@ get_nodes() ->
Mod = get_mod(), Mod = get_mod(),
Mod:get_nodes(). Mod:get_nodes().
-spec get_known_nodes() -> [node()]. -spec get_known_nodes() -> [node()].
get_known_nodes() -> get_known_nodes() ->
Mod = get_mod(), Mod = get_mod(),
Mod:get_known_nodes(). Mod:get_known_nodes().
-spec join(node()) -> ok | {error, any()}. -spec join(node()) -> ok | {error, any()}.
join(Node) -> join(Node) ->
Mod = get_mod(), Mod = get_mod(),
Mod:join(Node). Mod:join(Node).
-spec leave(node()) -> ok | {error, any()}. -spec leave(node()) -> ok | {error, any()}.
leave(Node) -> leave(Node) ->
Mod = get_mod(), Mod = get_mod(),
Mod:leave(Node). Mod:leave(Node).
-spec node_id() -> binary(). -spec node_id() -> binary().
node_id() -> node_id() ->
Mod = get_mod(), Mod = get_mod(),
Mod:node_id(). Mod:node_id().
-spec get_node_by_id(binary()) -> node(). -spec get_node_by_id(binary()) -> node().
get_node_by_id(ID) -> get_node_by_id(ID) ->
Mod = get_mod(), Mod = get_mod(),
Mod:get_node_by_id(ID). Mod:get_node_by_id(ID).
%% Note that false positive returns are possible, while false negatives are not. %% Note that false positive returns are possible, while false negatives are not.
%% In other words: positive return value (i.e. 'true') doesn't guarantee %% In other words: positive return value (i.e. 'true') doesn't guarantee
%% successful delivery, while negative return value ('false') means %% successful delivery, while negative return value ('false') means
@ -134,45 +163,51 @@ send(Name, Msg) when is_atom(Name) ->
send(whereis(Name), Msg); send(whereis(Name), Msg);
send(Pid, Msg) when is_pid(Pid) andalso node(Pid) == node() -> send(Pid, Msg) when is_pid(Pid) andalso node(Pid) == node() ->
case erlang:is_process_alive(Pid) of case erlang:is_process_alive(Pid) of
true -> true ->
erlang:send(Pid, Msg), erlang:send(Pid, Msg),
true; true;
false -> false ->
false false
end; end;
send(Dst, Msg) -> send(Dst, Msg) ->
Mod = get_mod(), Mod = get_mod(),
Mod:send(Dst, Msg). Mod:send(Dst, Msg).
-spec wait_for_sync(timeout()) -> ok | {error, any()}. -spec wait_for_sync(timeout()) -> ok | {error, any()}.
wait_for_sync(Timeout) -> wait_for_sync(Timeout) ->
Mod = get_mod(), Mod = get_mod(),
Mod:wait_for_sync(Timeout). Mod:wait_for_sync(Timeout).
-spec subscribe() -> ok. -spec subscribe() -> ok.
subscribe() -> subscribe() ->
subscribe(self()). subscribe(self()).
-spec subscribe(dst()) -> ok. -spec subscribe(dst()) -> ok.
subscribe(Proc) -> subscribe(Proc) ->
Mod = get_mod(), Mod = get_mod(),
Mod:subscribe(Proc). Mod:subscribe(Proc).
%%%=================================================================== %%%===================================================================
%%% Hooks %%% Hooks
%%%=================================================================== %%%===================================================================
set_ticktime() -> set_ticktime() ->
Ticktime = ejabberd_option:net_ticktime() div 1000, Ticktime = ejabberd_option:net_ticktime() div 1000,
case net_kernel:set_net_ticktime(Ticktime) of case net_kernel:set_net_ticktime(Ticktime) of
{ongoing_change_to, Time} when Time /= Ticktime -> {ongoing_change_to, Time} when Time /= Ticktime ->
?ERROR_MSG("Failed to set new net_ticktime because " ?ERROR_MSG("Failed to set new net_ticktime because "
"the net kernel is busy changing it to the " "the net kernel is busy changing it to the "
"previously configured value. Please wait for " "previously configured value. Please wait for "
"~B seconds and retry", [Time]); "~B seconds and retry",
_ -> [Time]);
ok _ ->
ok
end. end.
%%%=================================================================== %%%===================================================================
%%% gen_server API %%% gen_server API
%%%=================================================================== %%%===================================================================
@ -181,25 +216,29 @@ init([]) ->
Nodes = ejabberd_option:cluster_nodes(), Nodes = ejabberd_option:cluster_nodes(),
lists:foreach(fun(Node) -> lists:foreach(fun(Node) ->
net_kernel:connect_node(Node) net_kernel:connect_node(Node)
end, Nodes), end,
Nodes),
Mod = get_mod(), Mod = get_mod(),
case Mod:init() of case Mod:init() of
ok -> ok ->
ejabberd_hooks:add(config_reloaded, ?MODULE, set_ticktime, 50), ejabberd_hooks:add(config_reloaded, ?MODULE, set_ticktime, 50),
Mod:subscribe(?MODULE), Mod:subscribe(?MODULE),
{ok, #state{}}; {ok, #state{}};
{error, Reason} -> {error, Reason} ->
{stop, Reason} {stop, Reason}
end. end.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({node_up, Node}, State) -> handle_info({node_up, Node}, State) ->
?INFO_MSG("Node ~ts has joined", [Node]), ?INFO_MSG("Node ~ts has joined", [Node]),
{noreply, State}; {noreply, State};
@ -210,12 +249,15 @@ handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, set_ticktime, 50). ejabberd_hooks:delete(config_reloaded, ?MODULE, set_ticktime, 50).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -223,5 +265,6 @@ get_mod() ->
Backend = ejabberd_option:cluster_backend(), Backend = ejabberd_option:cluster_backend(),
list_to_existing_atom("ejabberd_cluster_" ++ atom_to_list(Backend)). list_to_existing_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
rpc_timeout() -> rpc_timeout() ->
ejabberd_option:rpc_timeout(). ejabberd_option:rpc_timeout().

View file

@ -27,26 +27,37 @@
-behaviour(ejabberd_cluster). -behaviour(ejabberd_cluster).
%% API %% API
-export([init/0, get_nodes/0, join/1, leave/1, -export([init/0,
get_known_nodes/0, node_id/0, get_node_by_id/1, get_nodes/0,
send/2, wait_for_sync/1, subscribe/1]). join/1,
leave/1,
get_known_nodes/0,
node_id/0,
get_node_by_id/1,
send/2,
wait_for_sync/1,
subscribe/1]).
-include("logger.hrl"). -include("logger.hrl").
-spec init() -> ok. -spec init() -> ok.
init() -> init() ->
ok. ok.
-spec get_nodes() -> [node()]. -spec get_nodes() -> [node()].
get_nodes() -> get_nodes() ->
mnesia:system_info(running_db_nodes). mnesia:system_info(running_db_nodes).
-spec get_known_nodes() -> [node()]. -spec get_known_nodes() -> [node()].
get_known_nodes() -> get_known_nodes() ->
lists:usort(mnesia:system_info(db_nodes) lists:usort(mnesia:system_info(db_nodes) ++
++ mnesia:system_info(extra_db_nodes)). mnesia:system_info(extra_db_nodes)).
-spec join(node()) -> ok | {error, any()}. -spec join(node()) -> ok | {error, any()}.
@ -72,12 +83,13 @@ join(Node) ->
{error, {no_ping, Node}} {error, {no_ping, Node}}
end. end.
-spec leave(node()) -> ok | {error, any()}. -spec leave(node()) -> ok | {error, any()}.
leave(Node) -> leave(Node) ->
case {node(), net_adm:ping(Node)} of case {node(), net_adm:ping(Node)} of
{Node, _} -> {Node, _} ->
Cluster = get_nodes()--[Node], Cluster = get_nodes() -- [Node],
leave(Cluster, Node); leave(Cluster, Node);
{_, pong} -> {_, pong} ->
rpc:call(Node, ?MODULE, leave, [Node], 10000); rpc:call(Node, ?MODULE, leave, [Node], 10000);
@ -87,68 +99,81 @@ leave(Node) ->
{aborted, Reason} -> {error, Reason} {aborted, Reason} -> {error, Reason}
end end
end. end.
leave([], Node) -> leave([], Node) ->
{error, {no_cluster, Node}}; {error, {no_cluster, Node}};
leave([Master|_], Node) -> leave([Master | _], Node) ->
application:stop(ejabberd), application:stop(ejabberd),
application:stop(mnesia), application:stop(mnesia),
spawn(fun() -> spawn(fun() ->
rpc:call(Master, mnesia, del_table_copy, [schema, Node]), rpc:call(Master, mnesia, del_table_copy, [schema, Node]),
mnesia:delete_schema([node()]), mnesia:delete_schema([node()]),
erlang:halt(0) erlang:halt(0)
end), end),
ok. ok.
-spec node_id() -> binary(). -spec node_id() -> binary().
node_id() -> node_id() ->
integer_to_binary(erlang:phash2(node())). integer_to_binary(erlang:phash2(node())).
-spec get_node_by_id(binary()) -> node(). -spec get_node_by_id(binary()) -> node().
get_node_by_id(Hash) -> get_node_by_id(Hash) ->
try binary_to_integer(Hash) of try binary_to_integer(Hash) of
I -> match_node_id(I) I -> match_node_id(I)
catch _:_ -> catch
node() _:_ ->
node()
end. end.
-spec send({atom(), node()}, term()) -> boolean(). -spec send({atom(), node()}, term()) -> boolean().
send(Dst, Msg) -> send(Dst, Msg) ->
case erlang:send(Dst, Msg, [nosuspend, noconnect]) of case erlang:send(Dst, Msg, [nosuspend, noconnect]) of
ok -> true; ok -> true;
_ -> false _ -> false
end. end.
-spec wait_for_sync(timeout()) -> ok. -spec wait_for_sync(timeout()) -> ok.
wait_for_sync(Timeout) -> wait_for_sync(Timeout) ->
?INFO_MSG("Waiting for Mnesia synchronization to complete", []), ?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout), mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
ok. ok.
-spec subscribe(_) -> ok. -spec subscribe(_) -> ok.
subscribe(_) -> subscribe(_) ->
ok. ok.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
replicate_database(Node) -> replicate_database(Node) ->
mnesia:change_table_copy_type(schema, node(), disc_copies), mnesia:change_table_copy_type(schema, node(), disc_copies),
lists:foreach( lists:foreach(
fun(Table) -> fun(Table) ->
Type = rpc:call(Node, mnesia, table_info, [Table, storage_type]), Type = rpc:call(Node, mnesia, table_info, [Table, storage_type]),
mnesia:add_table_copy(Table, node(), Type) mnesia:add_table_copy(Table, node(), Type)
end, mnesia:system_info(tables)--[schema]). end,
mnesia:system_info(tables) -- [schema]).
-spec match_node_id(integer()) -> node(). -spec match_node_id(integer()) -> node().
match_node_id(I) -> match_node_id(I) ->
match_node_id(I, get_nodes()). match_node_id(I, get_nodes()).
-spec match_node_id(integer(), [node()]) -> node(). -spec match_node_id(integer(), [node()]) -> node().
match_node_id(I, [Node|Nodes]) -> match_node_id(I, [Node | Nodes]) ->
case erlang:phash2(Node) of case erlang:phash2(Node) of
I -> Node; I -> Node;
_ -> match_node_id(I, Nodes) _ -> match_node_id(I, Nodes)
end; end;
match_node_id(_I, []) -> match_node_id(_I, []) ->
node(). node().

View file

@ -31,120 +31,139 @@
-define(DEFAULT_VERSION, 1000000). -define(DEFAULT_VERSION, 1000000).
-export([start_link/0, -export([start_link/0,
list_commands/0, list_commands/0, list_commands/1, list_commands/2,
list_commands/1, get_command_format/1, get_command_format/2, get_command_format/3,
list_commands/2, get_command_definition/1, get_command_definition/2,
get_command_format/1, get_tags_commands/0, get_tags_commands/1,
get_command_format/2, register_commands/1, register_commands/2, register_commands/3,
get_command_format/3, unregister_commands/1, unregister_commands/3,
get_command_definition/1, get_commands_spec/0,
get_command_definition/2, get_commands_definition/0, get_commands_definition/1,
get_tags_commands/0, execute_command2/3, execute_command2/4]).
get_tags_commands/1,
register_commands/1,
register_commands/2,
register_commands/3,
unregister_commands/1,
unregister_commands/3,
get_commands_spec/0,
get_commands_definition/0,
get_commands_definition/1,
execute_command2/3,
execute_command2/4]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
-type auth() :: {binary(), binary(), binary() | {oauth, binary()}, boolean()} | map(). -type auth() :: {binary(), binary(), binary() | {oauth, binary()}, boolean()} | map().
-record(state, {}). -record(state, {}).
get_commands_spec() -> get_commands_spec() ->
[ [#ejabberd_commands{
#ejabberd_commands{name = gen_html_doc_for_commands, tags = [documentation], name = gen_html_doc_for_commands,
desc = "Generates html documentation for ejabberd_commands", tags = [documentation],
module = ejabberd_commands_doc, function = generate_html_output, desc = "Generates html documentation for ejabberd_commands",
args = [{file, binary}, {regexp, binary}, {examples, binary}], module = ejabberd_commands_doc,
result = {res, rescode}, function = generate_html_output,
args_desc = ["Path to file where generated " args = [{file, binary}, {regexp, binary}, {examples, binary}],
"documentation should be stored", result = {res, rescode},
"Regexp matching names of commands or modules " args_desc = ["Path to file where generated "
"that will be included inside generated document", "documentation should be stored",
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) " "Regexp matching names of commands or modules "
"that will have example invocation include in markdown document"], "that will be included inside generated document",
result_desc = "0 if command failed, 1 when succeeded", "Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"], "that will have example invocation include in markdown document"],
result_example = ok}, result_desc = "0 if command failed, 1 when succeeded",
#ejabberd_commands{name = gen_markdown_doc_for_commands, tags = [documentation], args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
desc = "Generates markdown documentation for ejabberd_commands", result_example = ok
module = ejabberd_commands_doc, function = generate_md_output, },
args = [{file, binary}, {regexp, binary}, {examples, binary}], #ejabberd_commands{
result = {res, rescode}, name = gen_markdown_doc_for_commands,
args_desc = ["Path to file where generated " tags = [documentation],
"documentation should be stored", desc = "Generates markdown documentation for ejabberd_commands",
"Regexp matching names of commands or modules " module = ejabberd_commands_doc,
"that will be included inside generated document, " function = generate_md_output,
"or `runtime` to get commands registered at runtime", args = [{file, binary}, {regexp, binary}, {examples, binary}],
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) " result = {res, rescode},
"that will have example invocation include in markdown document"], args_desc = ["Path to file where generated "
result_desc = "0 if command failed, 1 when succeeded", "documentation should be stored",
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"], "Regexp matching names of commands or modules "
result_example = ok}, "that will be included inside generated document, "
#ejabberd_commands{name = gen_markdown_doc_for_tags, tags = [documentation], "or `runtime` to get commands registered at runtime",
desc = "Generates markdown documentation for ejabberd_commands", "Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
note = "added in 21.12", "that will have example invocation include in markdown document"],
module = ejabberd_commands_doc, function = generate_tags_md, result_desc = "0 if command failed, 1 when succeeded",
args = [{file, binary}], args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result = {res, rescode}, result_example = ok
args_desc = ["Path to file where generated " },
"documentation should be stored"], #ejabberd_commands{
result_desc = "0 if command failed, 1 when succeeded", name = gen_markdown_doc_for_tags,
args_example = ["/home/me/docs/tags.md"], tags = [documentation],
result_example = ok}]. desc = "Generates markdown documentation for ejabberd_commands",
note = "added in 21.12",
module = ejabberd_commands_doc,
function = generate_tags_md,
args = [{file, binary}],
result = {res, rescode},
args_desc = ["Path to file where generated "
"documentation should be stored"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/tags.md"],
result_example = ok
}].
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) -> init([]) ->
try mnesia:transform_table(ejabberd_commands, ignore, try
record_info(fields, ejabberd_commands)) mnesia:transform_table(ejabberd_commands,
catch exit:{aborted, {no_exists, _}} -> ok ignore,
record_info(fields, ejabberd_commands))
catch
exit:{aborted, {no_exists, _}} -> ok
end, end,
ejabberd_mnesia:create(?MODULE, ejabberd_commands, ejabberd_mnesia:create(?MODULE,
[{ram_copies, [node()]}, ejabberd_commands,
{local_content, true}, [{ram_copies, [node()]},
{attributes, record_info(fields, ejabberd_commands)}, {local_content, true},
{type, bag}]), {attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
register_commands(get_commands_spec()), register_commands(get_commands_spec()),
{ok, #state{}}. {ok, #state{}}.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
-spec register_commands([ejabberd_commands()]) -> ok. -spec register_commands([ejabberd_commands()]) -> ok.
register_commands(Commands) -> register_commands(Commands) ->
register_commands(unknown, Commands). register_commands(unknown, Commands).
-spec register_commands(atom(), [ejabberd_commands()]) -> ok. -spec register_commands(atom(), [ejabberd_commands()]) -> ok.
register_commands(Definer, Commands) -> register_commands(Definer, Commands) ->
@ -164,6 +183,7 @@ register_commands(Definer, Commands) ->
ejabberd_access_permissions:invalidate(), ejabberd_access_permissions:invalidate(),
ok. ok.
-spec register_commands(binary(), atom(), [ejabberd_commands()]) -> ok. -spec register_commands(binary(), atom(), [ejabberd_commands()]) -> ok.
register_commands(Host, Definer, Commands) -> register_commands(Host, Definer, Commands) ->
@ -174,11 +194,12 @@ register_commands(Host, Definer, Commands) ->
ok ok
end. end.
register_command_prepare(Command, Definer) -> register_command_prepare(Command, Definer) ->
Tags1 = Command#ejabberd_commands.tags, Tags1 = Command#ejabberd_commands.tags,
Tags2 = case Command#ejabberd_commands.version of Tags2 = case Command#ejabberd_commands.version of
0 -> Tags1; 0 -> Tags1;
Version -> Tags1 ++ [list_to_atom("v"++integer_to_list(Version))] Version -> Tags1 ++ [list_to_atom("v" ++ integer_to_list(Version))]
end, end,
Command#ejabberd_commands{definer = Definer, tags = Tags2}. Command#ejabberd_commands{definer = Definer, tags = Tags2}.
@ -188,11 +209,12 @@ register_command_prepare(Command, Definer) ->
unregister_commands(Commands) -> unregister_commands(Commands) ->
lists:foreach( lists:foreach(
fun(Command) -> fun(Command) ->
mnesia:dirty_delete(ejabberd_commands, Command#ejabberd_commands.name) mnesia:dirty_delete(ejabberd_commands, Command#ejabberd_commands.name)
end, end,
Commands), Commands),
ejabberd_access_permissions:invalidate(). ejabberd_access_permissions:invalidate().
-spec unregister_commands(binary(), atom(), [ejabberd_commands()]) -> ok. -spec unregister_commands(binary(), atom(), [ejabberd_commands()]) -> ok.
unregister_commands(Host, Definer, Commands) -> unregister_commands(Host, Definer, Commands) ->
@ -203,47 +225,57 @@ unregister_commands(Host, Definer, Commands) ->
ok ok
end. end.
-spec list_commands() -> [{atom(), [aterm()], string()}]. -spec list_commands() -> [{atom(), [aterm()], string()}].
list_commands() -> list_commands() ->
list_commands(?DEFAULT_VERSION). list_commands(?DEFAULT_VERSION).
-spec list_commands(integer()) -> [{atom(), [aterm()], string()}]. -spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
list_commands(Version) -> list_commands(Version) ->
Commands = get_commands_definition(Version), Commands = get_commands_definition(Version),
[{Name, Args, Desc} || #ejabberd_commands{name = Name, [ {Name, Args, Desc} || #ejabberd_commands{
args = Args, name = Name,
tags = Tags, args = Args,
desc = Desc} <- Commands, tags = Tags,
not lists:member(internal, Tags)]. desc = Desc
} <- Commands,
not lists:member(internal, Tags) ].
-spec list_commands(integer(), map()) -> [{atom(), [aterm()], string()}]. -spec list_commands(integer(), map()) -> [{atom(), [aterm()], string()}].
list_commands(Version, CallerInfo) -> list_commands(Version, CallerInfo) ->
lists:filter( lists:filter(
fun({Name, _Args, _Desc}) -> fun({Name, _Args, _Desc}) ->
allow == ejabberd_access_permissions:can_access(Name, CallerInfo) allow == ejabberd_access_permissions:can_access(Name, CallerInfo)
end, end,
list_commands(Version) list_commands(Version)).
).
-spec get_command_format(atom()) -> {[aterm()], [{atom(),atom()}], rterm()}.
-spec get_command_format(atom()) -> {[aterm()], [{atom(), atom()}], rterm()}.
get_command_format(Name) -> get_command_format(Name) ->
get_command_format(Name, noauth, ?DEFAULT_VERSION). get_command_format(Name, noauth, ?DEFAULT_VERSION).
get_command_format(Name, Version) when is_integer(Version) -> get_command_format(Name, Version) when is_integer(Version) ->
get_command_format(Name, noauth, Version); get_command_format(Name, noauth, Version);
get_command_format(Name, Auth) -> get_command_format(Name, Auth) ->
get_command_format(Name, Auth, ?DEFAULT_VERSION). get_command_format(Name, Auth, ?DEFAULT_VERSION).
-spec get_command_format(atom(), noauth | admin | auth(), integer()) -> {[aterm()], [{atom(),atom()}], rterm()}.
-spec get_command_format(atom(), noauth | admin | auth(), integer()) -> {[aterm()], [{atom(), atom()}], rterm()}.
get_command_format(Name, Auth, Version) -> get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth, #{}), Admin = is_admin(Name, Auth, #{}),
#ejabberd_commands{args = Args, #ejabberd_commands{
result = Result, args = Args,
args_rename = Rename, result = Result,
policy = Policy} = args_rename = Rename,
policy = Policy
} =
get_command_definition(Name, Version), get_command_definition(Name, Version),
case Policy of case Policy of
user when Admin; user when Admin;
@ -253,11 +285,13 @@ get_command_format(Name, Auth, Version) ->
{Args, Rename, Result} {Args, Rename, Result}
end. end.
-spec get_command_definition(atom()) -> ejabberd_commands(). -spec get_command_definition(atom()) -> ejabberd_commands().
get_command_definition(Name) -> get_command_definition(Name) ->
get_command_definition(Name, ?DEFAULT_VERSION). get_command_definition(Name, ?DEFAULT_VERSION).
-spec get_command_definition(atom(), integer()) -> ejabberd_commands(). -spec get_command_definition(atom(), integer()) -> ejabberd_commands().
get_command_definition(Name, Version) -> get_command_definition(Name, Version) ->
@ -270,13 +304,15 @@ get_command_definition(Name, Version) ->
when N == Name, V =< Version -> when N == Name, V =< Version ->
{V, C} {V, C}
end)))) of end)))) of
[{_, Command} | _ ] -> Command; [{_, Command} | _] -> Command;
_E -> throw({error, unknown_command}) _E -> throw({error, unknown_command})
end. end.
get_commands_definition() -> get_commands_definition() ->
get_commands_definition(?DEFAULT_VERSION). get_commands_definition(?DEFAULT_VERSION).
-spec get_commands_definition(integer()) -> [ejabberd_commands()]. -spec get_commands_definition(integer()) -> [ejabberd_commands()].
get_commands_definition(Version) -> get_commands_definition(Version) ->
@ -291,28 +327,30 @@ get_commands_definition(Version) ->
end)))), end)))),
F = fun({_Name, _V, Command}, []) -> F = fun({_Name, _V, Command}, []) ->
[Command]; [Command];
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) -> ({Name, _V, _Command}, [#ejabberd_commands{name = Name} | _T] = Acc) ->
Acc; Acc;
({_Name, _V, Command}, Acc) -> [Command | Acc] ({_Name, _V, Command}, Acc) -> [Command | Acc]
end, end,
lists:foldl(F, [], L). lists:foldl(F, [], L).
execute_command2(Name, Arguments, CallerInfo) -> execute_command2(Name, Arguments, CallerInfo) ->
execute_command2(Name, Arguments, CallerInfo, ?DEFAULT_VERSION). execute_command2(Name, Arguments, CallerInfo, ?DEFAULT_VERSION).
execute_command2(Name, Arguments, CallerInfo, Version) -> execute_command2(Name, Arguments, CallerInfo, Version) ->
Command = get_command_definition(Name, Version), Command = get_command_definition(Name, Version),
FrontedCalledInternal = FrontedCalledInternal =
maps:get(caller_module, CallerInfo, none) /= ejabberd_web_admin maps:get(caller_module, CallerInfo, none) /= ejabberd_web_admin andalso
andalso lists:member(internal, Command#ejabberd_commands.tags), lists:member(internal, Command#ejabberd_commands.tags),
case {ejabberd_access_permissions:can_access(Name, CallerInfo), case {ejabberd_access_permissions:can_access(Name, CallerInfo),
FrontedCalledInternal} of FrontedCalledInternal} of
{allow, false} -> {allow, false} ->
do_execute_command(Command, Arguments); do_execute_command(Command, Arguments);
{_, true} -> {_, true} ->
throw({error, frontend_cannot_call_an_internal_command}); throw({error, frontend_cannot_call_an_internal_command});
{deny, false} -> {deny, false} ->
throw({error, access_rules_unauthorized}) throw({error, access_rules_unauthorized})
end. end.
@ -323,38 +361,40 @@ do_execute_command(Command, Arguments) ->
ejabberd_hooks:run(api_call, [Module, Function, Arguments]), ejabberd_hooks:run(api_call, [Module, Function, Arguments]),
apply(Module, Function, Arguments). apply(Module, Function, Arguments).
-spec get_tags_commands() -> [{string(), [string()]}]. -spec get_tags_commands() -> [{string(), [string()]}].
get_tags_commands() -> get_tags_commands() ->
get_tags_commands(?DEFAULT_VERSION). get_tags_commands(?DEFAULT_VERSION).
-spec get_tags_commands(integer()) -> [{string(), [string()]}]. -spec get_tags_commands(integer()) -> [{string(), [string()]}].
get_tags_commands(Version) -> get_tags_commands(Version) ->
CommandTags = [{Name, Tags} || CommandTags = [ {Name, Tags}
#ejabberd_commands{name = Name, tags = Tags} || #ejabberd_commands{name = Name, tags = Tags} <- get_commands_definition(Version),
<- get_commands_definition(Version), not lists:member(internal, Tags) ],
not lists:member(internal, Tags)],
Dict = lists:foldl( Dict = lists:foldl(
fun({CommandNameAtom, CTags}, D) -> fun({CommandNameAtom, CTags}, D) ->
CommandName = atom_to_list(CommandNameAtom), CommandName = atom_to_list(CommandNameAtom),
case CTags of case CTags of
[] -> [] ->
orddict:append("untagged", CommandName, D); orddict:append("untagged", CommandName, D);
_ -> _ ->
lists:foldl( lists:foldl(
fun(TagAtom, DD) -> fun(TagAtom, DD) ->
Tag = atom_to_list(TagAtom), Tag = atom_to_list(TagAtom),
orddict:append(Tag, CommandName, DD) orddict:append(Tag, CommandName, DD)
end, end,
D, D,
CTags) CTags)
end end
end, end,
orddict:new(), orddict:new(),
CommandTags), CommandTags),
orddict:to_list(Dict). orddict:to_list(Dict).
%% ----------------------------- %% -----------------------------
%% Access verification %% Access verification
%% ----------------------------- %% -----------------------------

View file

@ -33,7 +33,7 @@
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-define(RAW(V), if HTMLOutput -> fxml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end). -define(RAW(V), if HTMLOutput -> fxml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end).
-define(TAG_BIN(N), (atom_to_binary(N, latin1))/binary). -define(TAG_BIN(N), (atom_to_binary(N, latin1)) / binary).
-define(TAG_STR(N), atom_to_list(N)). -define(TAG_STR(N), atom_to_list(N)).
-define(TAG(N), if HTMLOutput -> [<<"<", ?TAG_BIN(N), "/>">>]; true -> md_tag(N, <<"">>) end). -define(TAG(N), if HTMLOutput -> [<<"<", ?TAG_BIN(N), "/>">>]; true -> md_tag(N, <<"">>) end).
-define(TAG(N, V), if HTMLOutput -> [<<"<", ?TAG_BIN(N), ">">>, V, <<"</", ?TAG_BIN(N), ">">>]; true -> md_tag(N, V) end). -define(TAG(N, V), if HTMLOutput -> [<<"<", ?TAG_BIN(N), ">">>, V, <<"</", ?TAG_BIN(N), ">">>]; true -> md_tag(N, V) end).
@ -42,13 +42,13 @@
-define(TAG_R(N, C, V), ?TAG(N, C, ?RAW(V))). -define(TAG_R(N, C, V), ?TAG(N, C, ?RAW(V))).
-define(SPAN(N, V), ?TAG_R(span, ??N, V)). -define(SPAN(N, V), ?TAG_R(span, ??N, V)).
-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])). -define(STR(A), ?SPAN(str, [<<"\"">>, A, <<"\"">>])).
-define(NUM(A), ?SPAN(num,integer_to_binary(A))). -define(NUM(A), ?SPAN(num, integer_to_binary(A))).
-define(FIELD(A), ?SPAN(field,A)). -define(FIELD(A), ?SPAN(field, A)).
-define(ID(A), ?SPAN(id,A)). -define(ID(A), ?SPAN(id, A)).
-define(OP(A), ?SPAN(op,A)). -define(OP(A), ?SPAN(op, A)).
-define(ARG(A), ?FIELD(atom_to_list(A))). -define(ARG(A), ?FIELD(atom_to_list(A))).
-define(KW(A), ?SPAN(kw,A)). -define(KW(A), ?SPAN(kw, A)).
-define(BR, <<"\n">>). -define(BR, <<"\n">>).
-define(ARG_S(A), ?STR(atom_to_list(A))). -define(ARG_S(A), ?STR(atom_to_list(A))).
@ -63,12 +63,16 @@
-define(STR_A(A), ?STR(atom_to_list(A))). -define(STR_A(A), ?STR(atom_to_list(A))).
-define(ID_A(A), ?ID(atom_to_list(A))). -define(ID_A(A), ?ID(atom_to_list(A))).
list_join_with([], _M) -> list_join_with([], _M) ->
[]; [];
list_join_with([El|Tail], M) -> list_join_with([El | Tail], M) ->
lists:reverse(lists:foldl(fun(E, Acc) -> lists:reverse(lists:foldl(fun(E, Acc) ->
[E, M | Acc] [E, M | Acc]
end, [El], Tail)). end,
[El],
Tail)).
md_tag(dt, V) -> md_tag(dt, V) ->
[<<"- ">>, V]; [<<"- ">>, V];
@ -91,6 +95,7 @@ md_tag('div', V) ->
md_tag(_, V) -> md_tag(_, V) ->
V. V.
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) -> perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)]; [?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
perl_gen({Name, string}, Str, _Indent, HTMLOutput) -> perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
@ -100,27 +105,55 @@ perl_gen({Name, binary}, Str, _Indent, HTMLOutput) ->
perl_gen({Name, atom}, Atom, _Indent, HTMLOutput) -> perl_gen({Name, atom}, Atom, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?STR_A(Atom)]; [?ARG(Name), ?OP_L(" => "), ?STR_A(Atom)];
perl_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) -> perl_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
Res = lists:map(fun({A,B})->perl_gen(A, B, Indent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))), Res = lists:map(fun({A, B}) -> perl_gen(A, B, Indent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))),
[?ARG(Name), ?OP_L(" => {"), list_join_with(Res, [?OP_L(", ")]), ?OP_L("}")]; [?ARG(Name), ?OP_L(" => {"), list_join_with(Res, [?OP_L(", ")]), ?OP_L("}")];
perl_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) -> perl_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
Res = lists:map(fun(E) -> [?OP_L("{"), perl_gen(ElDesc, E, Indent, HTMLOutput), ?OP_L("}")] end, List), Res = lists:map(fun(E) -> [?OP_L("{"), perl_gen(ElDesc, E, Indent, HTMLOutput), ?OP_L("}")] end, List),
[?ARG(Name), ?OP_L(" => ["), list_join_with(Res, [?OP_L(", ")]), ?OP_L("]")]. [?ARG(Name), ?OP_L(" => ["), list_join_with(Res, [?OP_L(", ")]), ?OP_L("]")].
perl_call(Name, ArgsDesc, Values, HTMLOutput) -> perl_call(Name, ArgsDesc, Values, HTMLOutput) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ perl\n">>} end, {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ perl\n">>} end,
[Preamble, [Preamble,
Indent, ?ID_L("XMLRPC::Lite"), ?OP_L("->"), ?ID_L("proxy"), ?OP_L("("), ?ID_L("$url"), ?OP_L(")->"), Indent,
?ID_L("call"), ?OP_L("("), ?STR_A(Name), ?OP_L(", {"), ?BR, Indent, <<" ">>, ?ID_L("XMLRPC::Lite"),
list_join_with(lists:map(fun({A,B})->perl_gen(A, B, <<Indent/binary, " ">>, HTMLOutput) end, lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, Indent, <<" ">>]), ?OP_L("->"),
?BR, Indent, ?OP_L("})->"), ?ID_L("results"), ?OP_L("()")]. ?ID_L("proxy"),
?OP_L("("),
?ID_L("$url"),
?OP_L(")->"),
?ID_L("call"),
?OP_L("("),
?STR_A(Name),
?OP_L(", {"),
?BR,
Indent,
<<" ">>,
list_join_with(lists:map(fun({A, B}) -> perl_gen(A, B, <<Indent/binary, " ">>, HTMLOutput) end, lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, Indent, <<" ">>]),
?BR,
Indent,
?OP_L("})->"),
?ID_L("results"),
?OP_L("()")].
java_gen_map(Vals, Indent, HTMLOutput) -> java_gen_map(Vals, Indent, HTMLOutput) ->
{Split, NL} = case Indent of {Split, NL} = case Indent of
none -> {<<" ">>, <<" ">>}; none -> {<<" ">>, <<" ">>};
_ -> {[?BR, <<" ", Indent/binary>>], [?BR, Indent]} _ -> {[?BR, <<" ", Indent/binary>>], [?BR, Indent]}
end, end,
[?KW_L("new "), ?ID_L("HashMap"), ?OP_L("<"), ?ID_L("String"), ?OP_L(", "), ?ID_L("Object"), [?KW_L("new "),
?OP_L(">() {{"), Split, list_join_with(Vals, Split), NL, ?OP_L("}}")]. ?ID_L("HashMap"),
?OP_L("<"),
?ID_L("String"),
?OP_L(", "),
?ID_L("Object"),
?OP_L(">() {{"),
Split,
list_join_with(Vals, Split),
NL,
?OP_L("}}")].
java_gen({Name, integer}, Int, _Indent, HTMLOutput) -> java_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Integer"), ?OP_L("("), ?NUM(Int), ?OP_L("));")]; [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Integer"), ?OP_L("("), ?NUM(Int), ?OP_L("));")];
@ -136,26 +169,74 @@ java_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), java_gen_map(Res, Indent, HTMLOutput), ?OP_L(")")]; [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), java_gen_map(Res, Indent, HTMLOutput), ?OP_L(")")];
java_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) -> java_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
{NI, NI2, I} = case List of {NI, NI2, I} = case List of
[_] -> {" ", " ", Indent}; [_] -> {" ", " ", Indent};
_ -> {[?BR, <<" ", Indent/binary>>], _ ->
[?BR, <<" ", Indent/binary>>], {[?BR, <<" ", Indent/binary>>],
<<" ", Indent/binary>>} [?BR, <<" ", Indent/binary>>],
end, <<" ", Indent/binary>>}
end,
Res = lists:map(fun(E) -> java_gen_map([java_gen(ElDesc, E, I, HTMLOutput)], none, HTMLOutput) end, List), Res = lists:map(fun(E) -> java_gen_map([java_gen(ElDesc, E, I, HTMLOutput)], none, HTMLOutput) end, List),
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Object"), ?OP_L("[] {"), NI, [?ID_L("put"),
list_join_with(Res, [?OP_L(","), NI]), NI2, ?OP_L("});")]. ?OP_L("("),
?STR_A(Name),
?OP_L(", "),
?KW_L("new "),
?ID_L("Object"),
?OP_L("[] {"),
NI,
list_join_with(Res, [?OP_L(","), NI]),
NI2,
?OP_L("});")].
java_call(Name, ArgsDesc, Values, HTMLOutput) -> java_call(Name, ArgsDesc, Values, HTMLOutput) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ java\n">>} end, {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ java\n">>} end,
[Preamble, [Preamble,
Indent, ?ID_L("XmlRpcClientConfigImpl config"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClientConfigImpl"), ?OP_L("();"), ?BR, Indent,
Indent, ?ID_L("config"), ?OP_L("."), ?ID_L("setServerURL"), ?OP_L("("), ?ID_L("url"), ?OP_L(");"), ?BR, Indent, ?BR, ?ID_L("XmlRpcClientConfigImpl config"),
Indent, ?ID_L("XmlRpcClient client"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClient"), ?OP_L("();"), ?BR, ?OP_L(" = "),
Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("setConfig"), ?OP_L("("), ?ID_L("config"), ?OP_L(");"), ?BR, Indent, ?BR, ?KW_L("new "),
Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("execute"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?ID_L("XmlRpcClientConfigImpl"),
java_gen_map(lists:map(fun({A,B})->java_gen(A, B, Indent, HTMLOutput) end, lists:zip(ArgsDesc, Values)), Indent, HTMLOutput), ?OP_L("();"),
?BR,
Indent,
?ID_L("config"),
?OP_L("."),
?ID_L("setServerURL"),
?OP_L("("),
?ID_L("url"),
?OP_L(");"),
?BR,
Indent,
?BR,
Indent,
?ID_L("XmlRpcClient client"),
?OP_L(" = "),
?KW_L("new "),
?ID_L("XmlRpcClient"),
?OP_L("();"),
?BR,
Indent,
?ID_L("client"),
?OP_L("."),
?ID_L("setConfig"),
?OP_L("("),
?ID_L("config"),
?OP_L(");"),
?BR,
Indent,
?BR,
Indent,
?ID_L("client"),
?OP_L("."),
?ID_L("execute"),
?OP_L("("),
?STR_A(Name),
?OP_L(", "),
java_gen_map(lists:map(fun({A, B}) -> java_gen(A, B, Indent, HTMLOutput) end, lists:zip(ArgsDesc, Values)), Indent, HTMLOutput),
?OP_L(");")]. ?OP_L(");")].
-define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V). -define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V).
-define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")). -define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")).
-define(XML(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?BR, Indent, ?XML_E(N)). -define(XML(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?BR, Indent, ?XML_E(N)).
@ -163,51 +244,75 @@ java_call(Name, ArgsDesc, Values, HTMLOutput) ->
-define(XML_L(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?XML_E(N)). -define(XML_L(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?XML_E(N)).
-define(XML_L(N, Indent, D, V), ?XML_L(N, [Indent, lists:duplicate(D, <<" ">>)], V)). -define(XML_L(N, Indent, D, V), ?XML_L(N, [Indent, lists:duplicate(D, <<" ">>)], V)).
xml_gen({Name, integer}, Int, Indent, HTMLOutput) -> xml_gen({Name, integer}, Int, Indent, HTMLOutput) ->
[?XML(member, Indent, [?XML(member,
[?XML_L(name, Indent, 1, ?ID_A(Name)), Indent,
?XML(value, Indent, 1, [?XML_L(name, Indent, 1, ?ID_A(Name)),
[?XML_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])]; ?XML(value,
Indent,
1,
[?XML_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])];
xml_gen({Name, string}, Str, Indent, HTMLOutput) -> xml_gen({Name, string}, Str, Indent, HTMLOutput) ->
[?XML(member, Indent, [?XML(member,
[?XML_L(name, Indent, 1, ?ID_A(Name)), Indent,
?XML(value, Indent, 1, [?XML_L(name, Indent, 1, ?ID_A(Name)),
[?XML_L(string, Indent, 2, ?ID(Str))])])]; ?XML(value,
Indent,
1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
xml_gen({Name, binary}, Str, Indent, HTMLOutput) -> xml_gen({Name, binary}, Str, Indent, HTMLOutput) ->
[?XML(member, Indent, [?XML(member,
[?XML_L(name, Indent, 1, ?ID_A(Name)), Indent,
?XML(value, Indent, 1, [?XML_L(name, Indent, 1, ?ID_A(Name)),
[?XML_L(string, Indent, 2, ?ID(Str))])])]; ?XML(value,
Indent,
1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
xml_gen({Name, atom}, Atom, Indent, HTMLOutput) -> xml_gen({Name, atom}, Atom, Indent, HTMLOutput) ->
[?XML(member, Indent, [?XML(member,
[?XML_L(name, Indent, 1, ?ID_A(Name)), Indent,
?XML(value, Indent, 1, [?XML_L(name, Indent, 1, ?ID_A(Name)),
[?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])]; ?XML(value,
Indent,
1,
[?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])];
xml_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) -> xml_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
NewIndent = <<" ", Indent/binary>>, NewIndent = <<" ", Indent/binary>>,
Res = lists:map(fun({A, B}) -> xml_gen(A, B, NewIndent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))), Res = lists:map(fun({A, B}) -> xml_gen(A, B, NewIndent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))),
[?XML(member, Indent, [?XML(member,
[?XML_L(name, Indent, 1, ?ID_A(Name)), Indent,
?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])]; [?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])];
xml_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) -> xml_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
Ind1 = <<" ", Indent/binary>>, Ind1 = <<" ", Indent/binary>>,
Ind2 = <<" ", Ind1/binary>>, Ind2 = <<" ", Ind1/binary>>,
Res = lists:map(fun(E) -> [?XML(value, Ind1, [?XML(struct, Ind1, 1, xml_gen(ElDesc, E, Ind2, HTMLOutput))])] end, List), Res = lists:map(fun(E) -> [?XML(value, Ind1, [?XML(struct, Ind1, 1, xml_gen(ElDesc, E, Ind2, HTMLOutput))])] end, List),
[?XML(member, Indent, [?XML(member,
[?XML_L(name, Indent, 1, ?ID_A(Name)), Indent,
?XML(value, Indent, 1, [?XML(array, Indent, 2, [?XML(data, Indent, 3, Res)])])])]. [?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1, [?XML(array, Indent, 2, [?XML(data, Indent, 3, Res)])])])].
xml_call(Name, ArgsDesc, Values, HTMLOutput) -> xml_call(Name, ArgsDesc, Values, HTMLOutput) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ xml">>} end, {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ xml">>} end,
Res = lists:map(fun({A, B}) -> xml_gen(A, B, <<Indent/binary, " ">>, HTMLOutput) end, lists:zip(ArgsDesc, Values)), Res = lists:map(fun({A, B}) -> xml_gen(A, B, <<Indent/binary, " ">>, HTMLOutput) end, lists:zip(ArgsDesc, Values)),
[Preamble, [Preamble,
?XML(methodCall, Indent, ?XML(methodCall,
Indent,
[?XML_L(methodName, Indent, 1, ?ID_A(Name)), [?XML_L(methodName, Indent, 1, ?ID_A(Name)),
?XML(params, Indent, 1, ?XML(params,
[?XML(param, Indent, 2, Indent,
[?XML(value, Indent, 3, 1,
[?XML(param,
Indent,
2,
[?XML(value,
Indent,
3,
[?XML(struct, Indent, 4, Res)])])])])]. [?XML(struct, Indent, 4, Res)])])])])].
% [?ARG_S(Name), ?OP_L(": "), ?STR(Str)]; % [?ARG_S(Name), ?OP_L(": "), ?STR(Str)];
json_gen({_Name, integer}, Int, _Indent, HTMLOutput) -> json_gen({_Name, integer}, Int, _Indent, HTMLOutput) ->
[?NUM(Int)]; [?NUM(Int)];
@ -220,15 +325,22 @@ json_gen({_Name, atom}, Atom, _Indent, HTMLOutput) ->
json_gen({_Name, rescode}, Val, _Indent, HTMLOutput) -> json_gen({_Name, rescode}, Val, _Indent, HTMLOutput) ->
[?ID_A(Val == ok orelse Val == true)]; [?ID_A(Val == ok orelse Val == true)];
json_gen({_Name, restuple}, {Val, Str}, _Indent, HTMLOutput) -> json_gen({_Name, restuple}, {Val, Str}, _Indent, HTMLOutput) ->
[?OP_L("{"), ?STR_L("res"), ?OP_L(": "), ?ID_A(Val == ok orelse Val == true), ?OP_L(", "), [?OP_L("{"),
?STR_L("text"), ?OP_L(": "), ?STR(Str), ?OP_L("}")]; ?STR_L("res"),
?OP_L(": "),
?ID_A(Val == ok orelse Val == true),
?OP_L(", "),
?STR_L("text"),
?OP_L(": "),
?STR(Str),
?OP_L("}")];
json_gen({_Name, {list, {_, {tuple, [{_, atom}, ValFmt]}}}}, List, Indent, HTMLOutput) -> json_gen({_Name, {list, {_, {tuple, [{_, atom}, ValFmt]}}}}, List, Indent, HTMLOutput) ->
Indent2 = <<" ", Indent/binary>>, Indent2 = <<" ", Indent/binary>>,
Res = lists:map(fun({N, V})->[?STR_A(N), ?OP_L(": "), json_gen(ValFmt, V, Indent2, HTMLOutput)] end, List), Res = lists:map(fun({N, V}) -> [?STR_A(N), ?OP_L(": "), json_gen(ValFmt, V, Indent2, HTMLOutput)] end, List),
[?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")]; [?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")];
json_gen({_Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) -> json_gen({_Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
Indent2 = <<" ", Indent/binary>>, Indent2 = <<" ", Indent/binary>>,
Res = lists:map(fun({{N, _} = A, B})->[?STR_A(N), ?OP_L(": "), json_gen(A, B, Indent2, HTMLOutput)] end, Res = lists:map(fun({{N, _} = A, B}) -> [?STR_A(N), ?OP_L(": "), json_gen(A, B, Indent2, HTMLOutput)] end,
lists:zip(Fields, tuple_to_list(Tuple))), lists:zip(Fields, tuple_to_list(Tuple))),
[?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")]; [?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")];
json_gen({_Name, {list, ElDesc}}, List, Indent, HTMLOutput) -> json_gen({_Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
@ -236,6 +348,7 @@ json_gen({_Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
Res = lists:map(fun(E) -> json_gen(ElDesc, E, Indent2, HTMLOutput) end, List), Res = lists:map(fun(E) -> json_gen(ElDesc, E, Indent2, HTMLOutput) end, List),
[?OP_L("["), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("]")]. [?OP_L("["), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("]")].
json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) -> json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<"">>, <<"~~~ json\n">>} end, {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<"">>, <<"~~~ json\n">>} end,
{Code, ResultStr} = case {ResultDesc, Result} of {Code, ResultStr} = case {ResultDesc, Result} of
@ -255,23 +368,40 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
500 -> <<" 500 Internal Server Error">> 500 -> <<" 500 Internal Server Error">>
end, end,
[Preamble, [Preamble,
Indent, ?ID_L("POST /api/"), ?ID_A(Name), ?BR, Indent,
Indent, ?OP_L("{"), ?BR, Indent, <<" ">>, ?ID_L("POST /api/"),
list_join_with(lists:map(fun({{N,_}=A,B})->[?STR_A(N), ?OP_L(": "), json_gen(A, B, <<Indent/binary, " ">>, HTMLOutput)] end, ?ID_A(Name),
lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, Indent, <<" ">>]), ?BR,
?BR, Indent, ?OP_L("}"), ?BR, Indent, ?BR, Indent, Indent,
?ID_L("HTTP/1.1"), ?ID(CodeStr), ?BR, Indent, ?OP_L("{"),
ResultStr ?BR,
]. Indent,
<<" ">>,
list_join_with(lists:map(fun({{N, _} = A, B}) -> [?STR_A(N), ?OP_L(": "), json_gen(A, B, <<Indent/binary, " ">>, HTMLOutput)] end,
lists:zip(ArgsDesc, Values)),
[?OP_L(","), ?BR, Indent, <<" ">>]),
?BR,
Indent,
?OP_L("}"),
?BR,
Indent,
?BR,
Indent,
?ID_L("HTTP/1.1"),
?ID(CodeStr),
?BR,
Indent,
ResultStr].
generate_example_input({_Name, integer}, {LastStr, LastNum}) -> generate_example_input({_Name, integer}, {LastStr, LastNum}) ->
{LastNum+1, {LastStr, LastNum+1}}; {LastNum + 1, {LastStr, LastNum + 1}};
generate_example_input({_Name, string}, {LastStr, LastNum}) -> generate_example_input({_Name, string}, {LastStr, LastNum}) ->
{string:chars(LastStr+1, 5), {LastStr+1, LastNum}}; {string:chars(LastStr + 1, 5), {LastStr + 1, LastNum}};
generate_example_input({_Name, binary}, {LastStr, LastNum}) -> generate_example_input({_Name, binary}, {LastStr, LastNum}) ->
{iolist_to_binary(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}}; {iolist_to_binary(string:chars(LastStr + 1, 5)), {LastStr + 1, LastNum}};
generate_example_input({_Name, atom}, {LastStr, LastNum}) -> generate_example_input({_Name, atom}, {LastStr, LastNum}) ->
{list_to_atom(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}}; {list_to_atom(string:chars(LastStr + 1, 5)), {LastStr + 1, LastNum}};
generate_example_input({_Name, rescode}, {LastStr, LastNum}) -> generate_example_input({_Name, rescode}, {LastStr, LastNum}) ->
{ok, {LastStr, LastNum}}; {ok, {LastStr, LastNum}};
generate_example_input({_Name, restuple}, {LastStr, LastNum}) -> generate_example_input({_Name, restuple}, {LastStr, LastNum}) ->
@ -280,68 +410,84 @@ generate_example_input({_Name, {tuple, Fields}}, Data) ->
{R, D} = lists:foldl(fun(Field, {Res2, Data2}) -> {R, D} = lists:foldl(fun(Field, {Res2, Data2}) ->
{Res3, Data3} = generate_example_input(Field, Data2), {Res3, Data3} = generate_example_input(Field, Data2),
{[Res3 | Res2], Data3} {[Res3 | Res2], Data3}
end, {[], Data}, Fields), end,
{[], Data},
Fields),
{list_to_tuple(lists:reverse(R)), D}; {list_to_tuple(lists:reverse(R)), D};
generate_example_input({_Name, {list, Desc}}, Data) -> generate_example_input({_Name, {list, Desc}}, Data) ->
{R1, D1} = generate_example_input(Desc, Data), {R1, D1} = generate_example_input(Desc, Data),
{R2, D2} = generate_example_input(Desc, D1), {R2, D2} = generate_example_input(Desc, D1),
{[R1, R2], D2}. {[R1, R2], D2}.
gen_calls(#ejabberd_commands{args_example=none, args=ArgsDesc} = C, HTMLOutput, Langs) ->
gen_calls(#ejabberd_commands{args_example = none, args = ArgsDesc} = C, HTMLOutput, Langs) ->
{R, _} = lists:foldl(fun(Arg, {Res, Data}) -> {R, _} = lists:foldl(fun(Arg, {Res, Data}) ->
{Res3, Data3} = generate_example_input(Arg, Data), {Res3, Data3} = generate_example_input(Arg, Data),
{[Res3 | Res], Data3} {[Res3 | Res], Data3}
end, {[], {$a-1, 0}}, ArgsDesc), end,
gen_calls(C#ejabberd_commands{args_example=lists:reverse(R)}, HTMLOutput, Langs); {[], {$a - 1, 0}},
gen_calls(#ejabberd_commands{result_example=none, result=ResultDesc} = C, HTMLOutput, Langs) -> ArgsDesc),
{R, _} = generate_example_input(ResultDesc, {$a-1, 0}), gen_calls(C#ejabberd_commands{args_example = lists:reverse(R)}, HTMLOutput, Langs);
gen_calls(C#ejabberd_commands{result_example=R}, HTMLOutput, Langs); gen_calls(#ejabberd_commands{result_example = none, result = ResultDesc} = C, HTMLOutput, Langs) ->
gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc, {R, _} = generate_example_input(ResultDesc, {$a - 1, 0}),
result_example=Result, result=ResultDesc, gen_calls(C#ejabberd_commands{result_example = R}, HTMLOutput, Langs);
name=Name}, HTMLOutput, Langs) -> gen_calls(#ejabberd_commands{
args_example = Values,
args = ArgsDesc,
result_example = Result,
result = ResultDesc,
name = Name
},
HTMLOutput,
Langs) ->
Perl = perl_call(Name, ArgsDesc, Values, HTMLOutput), Perl = perl_call(Name, ArgsDesc, Values, HTMLOutput),
Java = java_call(Name, ArgsDesc, Values, HTMLOutput), Java = java_call(Name, ArgsDesc, Values, HTMLOutput),
XML = xml_call(Name, ArgsDesc, Values, HTMLOutput), XML = xml_call(Name, ArgsDesc, Values, HTMLOutput),
JSON = json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput), JSON = json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput),
if HTMLOutput -> if
[?TAG(ul, "code-samples-names", HTMLOutput ->
[?TAG(ul,
"code-samples-names",
[case lists:member(<<"java">>, Langs) of true -> ?TAG(li, <<"Java">>); _ -> [] end, [case lists:member(<<"java">>, Langs) of true -> ?TAG(li, <<"Java">>); _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> ?TAG(li, <<"Perl">>); _ -> [] end, case lists:member(<<"perl">>, Langs) of true -> ?TAG(li, <<"Perl">>); _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, <<"XML">>); _ -> [] end, case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, <<"XML">>); _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> ?TAG(li, <<"JSON">>); _ -> [] end]), case lists:member(<<"json">>, Langs) of true -> ?TAG(li, <<"JSON">>); _ -> [] end]),
?TAG(ul, "code-samples", ?TAG(ul,
"code-samples",
[case lists:member(<<"java">>, Langs) of true -> ?TAG(li, ?TAG(pre, Java)); _ -> [] end, [case lists:member(<<"java">>, Langs) of true -> ?TAG(li, ?TAG(pre, Java)); _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> ?TAG(li, ?TAG(pre, Perl)); _ -> [] end, case lists:member(<<"perl">>, Langs) of true -> ?TAG(li, ?TAG(pre, Perl)); _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end, case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])]; case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
true -> true ->
case Langs of case Langs of
Val when length(Val) == 0 orelse length(Val) == 1 -> Val when length(Val) == 0 orelse length(Val) == 1 ->
[case lists:member(<<"java">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end, [case lists:member(<<"java">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end, case lists:member(<<"perl">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end, case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end, case lists:member(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"\n\n">>]; <<"\n\n">>];
_ -> _ ->
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end, [<<"\n">>,
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end, case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end, case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
<<"{: .code-samples-labels}\n">>, case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end, <<"{: .code-samples-labels}\n">>,
case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end, case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end, case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end, case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
<<"{: .code-samples-tabs}\n\n">>] case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
end <<"{: .code-samples-tabs}\n\n">>]
end
end. end.
format_type({list, {_, {tuple, Els}}}) -> format_type({list, {_, {tuple, Els}}}) ->
io_lib:format("[~ts]", [format_type({tuple, Els})]); io_lib:format("[~ts]", [format_type({tuple, Els})]);
format_type({list, El}) -> format_type({list, El}) ->
io_lib:format("[~ts]", [format_type(El)]); io_lib:format("[~ts]", [format_type(El)]);
format_type({tuple, Els}) -> format_type({tuple, Els}) ->
Args = [format_type(El) || El <- Els], Args = [ format_type(El) || El <- Els ],
io_lib:format("{~ts}", [string:join(Args, ", ")]); io_lib:format("{~ts}", [string:join(Args, ", ")]);
format_type({Name, Type}) -> format_type({Name, Type}) ->
io_lib:format("~ts::~ts", [Name, format_type(Type)]); io_lib:format("~ts::~ts", [Name, format_type(Type)]);
@ -352,68 +498,94 @@ format_type(atom) ->
format_type(Type) -> format_type(Type) ->
io_lib:format("~p", [Type]). io_lib:format("~p", [Type]).
gen_param(Name, Type, undefined, HTMLOutput) -> gen_param(Name, Type, undefined, HTMLOutput) ->
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])]; [?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
gen_param(Name, Type, Desc, HTMLOutput) -> gen_param(Name, Type, Desc, HTMLOutput) ->
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]), [?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
?TAG(dd, ?RAW(Desc))]. ?TAG(dd, ?RAW(Desc))].
make_tags(HTMLOutput) -> make_tags(HTMLOutput) ->
TagsList = ejabberd_commands:get_tags_commands(1000000), TagsList = ejabberd_commands:get_tags_commands(1000000),
lists:map(fun(T) -> gen_tags(T, HTMLOutput) end, TagsList). lists:map(fun(T) -> gen_tags(T, HTMLOutput) end, TagsList).
-dialyzer({no_match, gen_tags/2}).
gen_tags({TagName, Commands}, HTMLOutput) ->
[?TAG(h1, TagName) | [?TAG(p, ?RAW("* _`"++C++"`_")) || C <- Commands]].
gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc, -dialyzer({no_match, gen_tags/2}).
args=Args, args_desc=ArgsDesc, note=Note, definer=Definer,
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
gen_tags({TagName, Commands}, HTMLOutput) ->
[?TAG(h1, TagName) | [ ?TAG(p, ?RAW("* _`" ++ C ++ "`_")) || C <- Commands ]].
gen_doc(#ejabberd_commands{
name = Name,
tags = Tags,
desc = Desc,
longdesc = LongDesc,
args = Args,
args_desc = ArgsDesc,
note = Note,
definer = Definer,
result = Result,
result_desc = ResultDesc
} = Cmd,
HTMLOutput,
Langs) ->
try try
ArgsText = case ArgsDesc of ArgsText = case ArgsDesc of
none -> none ->
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput) [?TAG(ul,
|| {AName, Type} <- Args])]; "args-list",
[ gen_param(AName, Type, undefined, HTMLOutput)
|| {AName, Type} <- Args ])];
_ -> _ ->
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput) [?TAG(dl,
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])] "args-list",
[ gen_param(AName, Type, ADesc, HTMLOutput)
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc) ])]
end, end,
ResultText = case Result of ResultText = case Result of
{res,rescode} -> {res, rescode} ->
[?TAG(dl, [gen_param(res, integer, [?TAG(dl,
"Status code (`0` on success, `1` otherwise)", [gen_param(res,
HTMLOutput)])]; integer,
{res,restuple} -> "Status code (`0` on success, `1` otherwise)",
[?TAG(dl, [gen_param(res, string, HTMLOutput)])];
"Raw result string", {res, restuple} ->
HTMLOutput)])]; [?TAG(dl,
{RName, Type} -> [gen_param(res,
case ResultDesc of string,
none -> "Raw result string",
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])]; HTMLOutput)])];
_ -> {RName, Type} ->
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])] case ResultDesc of
end none ->
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
_ ->
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end
end, end,
TagsText = ?RAW(string:join(["_`"++atom_to_list(Tag)++"`_" || Tag <- Tags], ", ")), TagsText = ?RAW(string:join([ "_`" ++ atom_to_list(Tag) ++ "`_" || Tag <- Tags ], ", ")),
IsDefinerMod = case Definer of IsDefinerMod = case Definer of
unknown -> false; unknown -> false;
_ -> lists:member(gen_mod, lists:flatten(proplists:get_all_values(behaviour, Definer:module_info(attributes)))) _ -> lists:member(gen_mod, lists:flatten(proplists:get_all_values(behaviour, Definer:module_info(attributes))))
end, end,
ModuleText = case IsDefinerMod of ModuleText = case IsDefinerMod of
true -> true ->
[?TAG(h2, <<"Module:">>), ?TAG(p, ?RAW("_`"++atom_to_list(Definer)++"`_"))]; [?TAG(h2, <<"Module:">>), ?TAG(p, ?RAW("_`" ++ atom_to_list(Definer) ++ "`_"))];
false -> false ->
[] []
end, end,
NoteEl = case Note of NoteEl = case Note of
"" -> []; "" -> [];
_ -> ?TAG('div', "note-down", ?RAW(Note)) _ -> ?TAG('div', "note-down", ?RAW(Note))
end, end,
{NotePre, NotePost} = {NotePre, NotePost} =
if HTMLOutput -> {[], NoteEl}; if
true -> {NoteEl, []} HTMLOutput -> {[], NoteEl};
end, true -> {NoteEl, []}
end,
[?TAG(h1, make_command_name(Name, Note)), [?TAG(h1, make_command_name(Name, Note)),
NotePre, NotePre,
@ -423,18 +595,21 @@ gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
_ -> ?TAG(p, ?RAW(LongDesc)) _ -> ?TAG(p, ?RAW(LongDesc))
end, end,
NotePost, NotePost,
?TAG(h2, <<"Arguments:">>), ArgsText, ?TAG(h2, <<"Arguments:">>),
?TAG(h2, <<"Result:">>), ResultText, ArgsText,
?TAG(h2, <<"Tags:">>), ?TAG(p, TagsText)] ?TAG(h2, <<"Result:">>),
++ ModuleText ++ [ ResultText,
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)] ?TAG(h2, <<"Tags:">>),
?TAG(p, TagsText)] ++
ModuleText ++ [?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
catch catch
_:Ex -> _:Ex ->
throw(iolist_to_binary(io_lib:format( throw(iolist_to_binary(io_lib:format(
<<"Error when generating documentation for command '~p': ~p">>, <<"Error when generating documentation for command '~p': ~p">>,
[Name, Ex]))) [Name, Ex])))
end. end.
get_version_mark("") -> get_version_mark("") ->
""; "";
get_version_mark(Note) -> get_version_mark(Note) ->
@ -445,33 +620,39 @@ get_version_mark(Note) ->
_ -> " 🟤" _ -> " 🟤"
end. end.
make_command_name(Name, Note) -> make_command_name(Name, Note) ->
atom_to_list(Name) ++ get_version_mark(Note). atom_to_list(Name) ++ get_version_mark(Note).
find_commands_definitions() -> find_commands_definitions() ->
lists:flatmap( lists:flatmap(
fun(Mod) -> fun(Mod) ->
code:ensure_loaded(Mod), code:ensure_loaded(Mod),
Cs = case erlang:function_exported(Mod, get_commands_spec, 0) of Cs = case erlang:function_exported(Mod, get_commands_spec, 0) of
true -> true ->
apply(Mod, get_commands_spec, []); apply(Mod, get_commands_spec, []);
_ -> _ ->
[] []
end, end,
[C#ejabberd_commands{definer = Mod} || C <- Cs] [ C#ejabberd_commands{definer = Mod} || C <- Cs ]
end, ejabberd_config:beams(all)). end,
ejabberd_config:beams(all)).
generate_html_output(File, RegExp, Languages) -> generate_html_output(File, RegExp, Languages) ->
Cmds = find_commands_definitions(), Cmds = find_commands_definitions(),
{ok, RE} = re:compile(RegExp), {ok, RE} = re:compile(RegExp),
Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) -> Cmds2 = lists:filter(fun(#ejabberd_commands{name = Name, module = Module}) ->
re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse
re:run(atom_to_list(Module), RE, [{capture, none}]) == match re:run(atom_to_list(Module), RE, [{capture, none}]) == match
end, Cmds), end,
Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) -> Cmds),
Cmds3 = lists:sort(fun(#ejabberd_commands{name = N1}, #ejabberd_commands{name = N2}) ->
N1 =< N2 N1 =< N2
end, Cmds2), end,
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3], Cmds2),
Cmds4 = [ maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3 ],
Langs = binary:split(Languages, <<",">>, [global]), Langs = binary:split(Languages, <<",">>, [global]),
Out = lists:map(fun(C) -> gen_doc(C, true, Langs) end, Cmds4), Out = lists:map(fun(C) -> gen_doc(C, true, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write]), {ok, Fh} = file:open(File, [write]),
@ -479,52 +660,64 @@ generate_html_output(File, RegExp, Languages) ->
file:close(Fh), file:close(Fh),
ok. ok.
maybe_add_policy_arguments(#ejabberd_commands{args=Args1, policy=user}=Cmd) ->
maybe_add_policy_arguments(#ejabberd_commands{args = Args1, policy = user} = Cmd) ->
Args2 = [{user, binary}, {host, binary} | Args1], Args2 = [{user, binary}, {host, binary} | Args1],
Cmd#ejabberd_commands{args = Args2}; Cmd#ejabberd_commands{args = Args2};
maybe_add_policy_arguments(Cmd) -> maybe_add_policy_arguments(Cmd) ->
Cmd. Cmd.
generate_md_output(File, <<"runtime">>, Languages) -> generate_md_output(File, <<"runtime">>, Languages) ->
Cmds = lists:map(fun({N, _, _}) -> Cmds = lists:map(fun({N, _, _}) ->
ejabberd_commands:get_command_definition(N) ejabberd_commands:get_command_definition(N)
end, ejabberd_commands:list_commands()), end,
ejabberd_commands:list_commands()),
generate_md_output(File, <<".">>, Languages, Cmds); generate_md_output(File, <<".">>, Languages, Cmds);
generate_md_output(File, RegExp, Languages) -> generate_md_output(File, RegExp, Languages) ->
Cmds = find_commands_definitions(), Cmds = find_commands_definitions(),
generate_md_output(File, RegExp, Languages, Cmds). generate_md_output(File, RegExp, Languages, Cmds).
generate_md_output(File, RegExp, Languages, Cmds) -> generate_md_output(File, RegExp, Languages, Cmds) ->
{ok, RE} = re:compile(RegExp), {ok, RE} = re:compile(RegExp),
Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) -> Cmds2 = lists:filter(fun(#ejabberd_commands{name = Name, module = Module}) ->
re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse
re:run(atom_to_list(Module), RE, [{capture, none}]) == match re:run(atom_to_list(Module), RE, [{capture, none}]) == match
end, Cmds), end,
Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) -> Cmds),
Cmds3 = lists:sort(fun(#ejabberd_commands{name = N1}, #ejabberd_commands{name = N2}) ->
N1 =< N2 N1 =< N2
end, Cmds2), end,
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3], Cmds2),
Cmds4 = [ maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3 ],
Langs = binary:split(Languages, <<",">>, [global]), Langs = binary:split(Languages, <<",">>, [global]),
Version = binary_to_list(ejabberd_config:version()), Version = binary_to_list(ejabberd_config:version()),
Header = ["# API Reference\n\n" Header = ["# API Reference\n\n"
"This section describes API commands of ejabberd ", Version, ". " "This section describes API commands of ejabberd ",
"The commands that changed in this version are marked with 🟤.\n\n"], Version,
". "
"The commands that changed in this version are marked with 🟤.\n\n"],
Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4), Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write, {encoding, utf8}]), {ok, Fh} = file:open(File, [write, {encoding, utf8}]),
io:format(Fh, "~ts~ts", [Header, Out]), io:format(Fh, "~ts~ts", [Header, Out]),
file:close(Fh), file:close(Fh),
ok. ok.
generate_tags_md(File) -> generate_tags_md(File) ->
Version = binary_to_list(ejabberd_config:version()), Version = binary_to_list(ejabberd_config:version()),
Header = ["# API Tags\n\n" Header = ["# API Tags\n\n"
"This section enumerates the API tags of ejabberd ", Version, ". \n\n"], "This section enumerates the API tags of ejabberd ",
Version,
". \n\n"],
Tags = make_tags(false), Tags = make_tags(false),
{ok, Fh} = file:open(File, [write, {encoding, utf8}]), {ok, Fh} = file:open(File, [write, {encoding, utf8}]),
io:format(Fh, "~ts~ts", [Header, Tags]), io:format(Fh, "~ts~ts", [Header, Tags]),
file:close(Fh), file:close(Fh),
ok. ok.
html_pre() -> html_pre() ->
"<!DOCTYPE> "<!DOCTYPE>
<html> <html>
@ -650,8 +843,9 @@ html_pre() ->
} }
</script>". </script>".
html_post() -> html_post() ->
"<script> "<script>
var ul = document.getElementsByTagName('ul'); var ul = document.getElementsByTagName('ul');
for (var i = 0; i < ul.length; i++) { for (var i = 0; i < ul.length; i++) {
if (ul[i].className == 'code-samples-names') if (ul[i].className == 'code-samples-names')

File diff suppressed because it is too large Load diff

View file

@ -23,23 +23,25 @@
-include("logger.hrl"). -include("logger.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
map_reduce(Y) -> map_reduce(Y) ->
F = F =
fun(Y1) -> fun(Y1) ->
Y2 = (validator())(Y1), Y2 = (validator())(Y1),
Y3 = transform(Y2), Y3 = transform(Y2),
case application:get_env(ejabberd, custom_config_transformer) of case application:get_env(ejabberd, custom_config_transformer) of
{ok, TransMod} when is_atom(TransMod) -> {ok, TransMod} when is_atom(TransMod) ->
TransMod:transform(Y3); TransMod:transform(Y3);
_ -> _ ->
Y3 Y3
end end
end, end,
econf:validate(F, Y). econf:validate(F, Y).
%%%=================================================================== %%%===================================================================
%%% Transformer %%% Transformer
%%%=================================================================== %%%===================================================================
@ -48,50 +50,60 @@ transform(Y) ->
{Y2, Acc2} = update(Y1, Acc1), {Y2, Acc2} = update(Y1, Acc1),
filter(global, Y2, Acc2). filter(global, Y2, Acc2).
transform(Host, Y, Acc) -> transform(Host, Y, Acc) ->
filtermapfoldr( filtermapfoldr(
fun({Opt, HostOpts}, Acc1) when (Opt == host_config orelse fun({Opt, HostOpts}, Acc1) when (Opt == host_config orelse
Opt == append_host_config) Opt == append_host_config) andalso
andalso Host == global -> Host == global ->
case filtermapfoldr( case filtermapfoldr(
fun({Host1, Opts}, Acc2) -> fun({Host1, Opts}, Acc2) ->
case transform(Host1, Opts, Acc2) of case transform(Host1, Opts, Acc2) of
{[], Acc3} -> {[], Acc3} ->
{false, Acc3}; {false, Acc3};
{Opts1, Acc3} -> {Opts1, Acc3} ->
{{true, {Host1, Opts1}}, Acc3} {{true, {Host1, Opts1}}, Acc3}
end end
end, Acc1, HostOpts) of end,
{[], Acc4} -> Acc1,
{false, Acc4}; HostOpts) of
{HostOpts1, Acc4} -> {[], Acc4} ->
{{true, {Opt, HostOpts1}}, Acc4} {false, Acc4};
end; {HostOpts1, Acc4} ->
({Opt, Val}, Acc1) -> {{true, {Opt, HostOpts1}}, Acc4}
transform(Host, Opt, Val, Acc1) end;
end, Acc, Y). ({Opt, Val}, Acc1) ->
transform(Host, Opt, Val, Acc1)
end,
Acc,
Y).
transform(Host, modules, ModOpts, Acc) -> transform(Host, modules, ModOpts, Acc) ->
{ModOpts1, Acc2} = {ModOpts1, Acc2} =
lists:mapfoldr( lists:mapfoldr(
fun({Mod, Opts}, Acc1) -> fun({Mod, Opts}, Acc1) ->
Opts1 = transform_module_options(Opts), Opts1 = transform_module_options(Opts),
transform_module(Host, Mod, Opts1, Acc1) transform_module(Host, Mod, Opts1, Acc1)
end, Acc, ModOpts), end,
Acc,
ModOpts),
{{true, {modules, ModOpts1}}, Acc2}; {{true, {modules, ModOpts1}}, Acc2};
transform(global, listen, Listeners, Acc) -> transform(global, listen, Listeners, Acc) ->
{Listeners1, Acc2} = {Listeners1, Acc2} =
lists:mapfoldr( lists:mapfoldr(
fun(Opts, Acc1) -> fun(Opts, Acc1) ->
transform_listener(Opts, Acc1) transform_listener(Opts, Acc1)
end, Acc, Listeners), end,
Acc,
Listeners),
{{true, {listen, Listeners1}}, Acc2}; {{true, {listen, Listeners1}}, Acc2};
transform(_Host, Opt, CertFile, Acc) when (Opt == domain_certfile) orelse transform(_Host, Opt, CertFile, Acc) when (Opt == domain_certfile) orelse
(Opt == c2s_certfile) orelse (Opt == c2s_certfile) orelse
(Opt == s2s_certfile) -> (Opt == s2s_certfile) ->
?WARNING_MSG("Option '~ts' is deprecated and was automatically " ?WARNING_MSG("Option '~ts' is deprecated and was automatically "
"appended to 'certfiles' option. ~ts", "appended to 'certfiles' option. ~ts",
[Opt, adjust_hint()]), [Opt, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []), CertFiles = maps:get(certfiles, Acc, []),
Acc1 = maps:put(certfiles, CertFiles ++ [CertFile], Acc), Acc1 = maps:put(certfiles, CertFiles ++ [CertFile], Acc),
{false, Acc1}; {false, Acc1};
@ -101,57 +113,63 @@ transform(_Host, certfiles, CertFiles1, Acc) ->
{true, Acc1}; {true, Acc1};
transform(_Host, acme, ACME, Acc) -> transform(_Host, acme, ACME, Acc) ->
ACME1 = lists:map( ACME1 = lists:map(
fun({ca_url, URL} = Opt) -> fun({ca_url, URL} = Opt) ->
case misc:uri_parse(URL) of case misc:uri_parse(URL) of
{ok, _, _, "acme-v01.api.letsencrypt.org", _, _, _} -> {ok, _, _, "acme-v01.api.letsencrypt.org", _, _, _} ->
NewURL = ejabberd_acme:default_directory_url(), NewURL = ejabberd_acme:default_directory_url(),
?WARNING_MSG("ACME directory URL ~ts defined in " ?WARNING_MSG("ACME directory URL ~ts defined in "
"option acme->ca_url is deprecated " "option acme->ca_url is deprecated "
"and was automatically replaced " "and was automatically replaced "
"with ~ts. ~ts", "with ~ts. ~ts",
[URL, NewURL, adjust_hint()]), [URL, NewURL, adjust_hint()]),
{ca_url, NewURL}; {ca_url, NewURL};
_ -> _ ->
Opt Opt
end; end;
(Opt) -> (Opt) ->
Opt Opt
end, ACME), end,
ACME),
{{true, {acme, ACME1}}, Acc}; {{true, {acme, ACME1}}, Acc};
transform(Host, s2s_use_starttls, required_trusted, Acc) -> transform(Host, s2s_use_starttls, required_trusted, Acc) ->
?WARNING_MSG("The value 'required_trusted' of option " ?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprecated and was " "'s2s_use_starttls' is deprecated and was "
"automatically replaced with value 'required'. " "automatically replaced with value 'required'. "
"The module 'mod_s2s_dialback' has also " "The module 'mod_s2s_dialback' has also "
"been automatically removed from the configuration. ~ts", "been automatically removed from the configuration. ~ts",
[adjust_hint()]), [adjust_hint()]),
Hosts = maps:get(remove_s2s_dialback, Acc, []), Hosts = maps:get(remove_s2s_dialback, Acc, []),
Acc1 = maps:put(remove_s2s_dialback, [Host|Hosts], Acc), Acc1 = maps:put(remove_s2s_dialback, [Host | Hosts], Acc),
{{true, {s2s_use_starttls, required}}, Acc1}; {{true, {s2s_use_starttls, required}}, Acc1};
transform(Host, define_macro, Macro, Acc) when is_binary(Host) -> transform(Host, define_macro, Macro, Acc) when is_binary(Host) ->
?WARNING_MSG("The option 'define_macro' is not supported inside 'host_config'. " ?WARNING_MSG("The option 'define_macro' is not supported inside 'host_config'. "
"Consequently those macro definitions for host '~ts' are unused: ~ts", "Consequently those macro definitions for host '~ts' are unused: ~ts",
[Host, io_lib:format("~p", [Macro])]), [Host, io_lib:format("~p", [Macro])]),
{true, Acc}; {true, Acc};
transform(_Host, _Opt, _Val, Acc) -> transform(_Host, _Opt, _Val, Acc) ->
{true, Acc}. {true, Acc}.
update(Y, Acc) -> update(Y, Acc) ->
set_certfiles(Y, Acc). set_certfiles(Y, Acc).
filter(Host, Y, Acc) -> filter(Host, Y, Acc) ->
lists:filtermap( lists:filtermap(
fun({Opt, HostOpts}) when (Opt == host_config orelse fun({Opt, HostOpts}) when (Opt == host_config orelse
Opt == append_host_config) Opt == append_host_config) andalso
andalso Host == global -> Host == global ->
HostOpts1 = lists:map( HostOpts1 = lists:map(
fun({Host1, Opts1}) -> fun({Host1, Opts1}) ->
{Host1, filter(Host1, Opts1, Acc)} {Host1, filter(Host1, Opts1, Acc)}
end, HostOpts), end,
{true, {Opt, HostOpts1}}; HostOpts),
({Opt, Val}) -> {true, {Opt, HostOpts1}};
filter(Host, Opt, Val, Acc) ({Opt, Val}) ->
end, Y). filter(Host, Opt, Val, Acc)
end,
Y).
filter(_Host, log_rotate_date, _, _) -> filter(_Host, log_rotate_date, _, _) ->
warn_removed_option(log_rotate_date), warn_removed_option(log_rotate_date),
@ -201,10 +219,11 @@ filter(_Host, default_db, odbc, _) ->
{true, {default_db, sql}}; {true, {default_db, sql}};
filter(_Host, auth_method, Ms, _) -> filter(_Host, auth_method, Ms, _) ->
Ms1 = lists:map( Ms1 = lists:map(
fun(internal) -> mnesia; fun(internal) -> mnesia;
(odbc) -> sql; (odbc) -> sql;
(M) -> M (M) -> M
end, Ms), end,
Ms),
{true, {auth_method, Ms1}}; {true, {auth_method, Ms1}};
filter(_Host, default_ram_db, internal, _) -> filter(_Host, default_ram_db, internal, _) ->
{true, {default_ram_db, mnesia}}; {true, {default_ram_db, mnesia}};
@ -212,16 +231,17 @@ filter(_Host, default_ram_db, odbc, _) ->
{true, {default_ram_db, sql}}; {true, {default_ram_db, sql}};
filter(_Host, extauth_cache, _, _) -> filter(_Host, extauth_cache, _, _) ->
?WARNING_MSG("Option 'extauth_cache' is deprecated " ?WARNING_MSG("Option 'extauth_cache' is deprecated "
"and has no effect, use authentication " "and has no effect, use authentication "
"or global cache configuration options: " "or global cache configuration options: "
"auth_use_cache, auth_cache_life_time, " "auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []), "use_cache, cache_life_time, and so on",
[]),
false; false;
filter(_Host, extauth_instances, Val, _) -> filter(_Host, extauth_instances, Val, _) ->
warn_replaced_option(extauth_instances, extauth_pool_size), warn_replaced_option(extauth_instances, extauth_pool_size),
{true, {extauth_pool_size, Val}}; {true, {extauth_pool_size, Val}};
filter(_Host, Opt, Val, _) when Opt == outgoing_s2s_timeout; filter(_Host, Opt, Val, _) when Opt == outgoing_s2s_timeout;
Opt == s2s_dns_timeout -> Opt == s2s_dns_timeout ->
warn_huge_timeout(Opt, Val), warn_huge_timeout(Opt, Val),
true; true;
filter(_Host, captcha_host, _, _) -> filter(_Host, captcha_host, _, _) ->
@ -235,18 +255,20 @@ filter(_Host, auth_password_types_hidden_in_scram1, Val, _) ->
filter(Host, modules, ModOpts, State) -> filter(Host, modules, ModOpts, State) ->
NoDialbackHosts = maps:get(remove_s2s_dialback, State, []), NoDialbackHosts = maps:get(remove_s2s_dialback, State, []),
ModOpts1 = lists:filter( ModOpts1 = lists:filter(
fun({mod_s2s_dialback, _}) -> fun({mod_s2s_dialback, _}) ->
not lists:member(Host, NoDialbackHosts); not lists:member(Host, NoDialbackHosts);
({mod_echo, _}) -> ({mod_echo, _}) ->
warn_removed_module(mod_echo), warn_removed_module(mod_echo),
false; false;
(_) -> (_) ->
true true
end, ModOpts), end,
ModOpts),
{true, {modules, ModOpts1}}; {true, {modules, ModOpts1}};
filter(_, _, _, _) -> filter(_, _, _, _) ->
true. true.
%%%=================================================================== %%%===================================================================
%%% Listener transformers %%% Listener transformers
%%%=================================================================== %%%===================================================================
@ -256,127 +278,143 @@ transform_listener(Opts, Acc) ->
Opts3 = remove_inet_options(Opts2), Opts3 = remove_inet_options(Opts2),
collect_listener_certfiles(Opts3, Acc). collect_listener_certfiles(Opts3, Acc).
transform_request_handlers(Opts) -> transform_request_handlers(Opts) ->
case lists:keyfind(module, 1, Opts) of case lists:keyfind(module, 1, Opts) of
{_, ejabberd_http} -> {_, ejabberd_http} ->
replace_request_handlers(Opts); replace_request_handlers(Opts);
{_, ejabberd_xmlrpc} -> {_, ejabberd_xmlrpc} ->
remove_xmlrpc_access_commands(Opts); remove_xmlrpc_access_commands(Opts);
_ -> _ ->
Opts Opts
end. end.
transform_turn_ip(Opts) -> transform_turn_ip(Opts) ->
case lists:keyfind(module, 1, Opts) of case lists:keyfind(module, 1, Opts) of
{_, ejabberd_stun} -> {_, ejabberd_stun} ->
replace_turn_ip(Opts); replace_turn_ip(Opts);
_ -> _ ->
Opts Opts
end. end.
replace_request_handlers(Opts) -> replace_request_handlers(Opts) ->
Handlers = proplists:get_value(request_handlers, Opts, []), Handlers = proplists:get_value(request_handlers, Opts, []),
Handlers1 = Handlers1 =
lists:foldl( lists:foldl(
fun({captcha, IsEnabled}, Acc) -> fun({captcha, IsEnabled}, Acc) ->
Handler = {<<"/captcha">>, ejabberd_captcha}, Handler = {<<"/captcha">>, ejabberd_captcha},
warn_replaced_handler(captcha, Handler, IsEnabled), warn_replaced_handler(captcha, Handler, IsEnabled),
[Handler|Acc]; [Handler | Acc];
({register, IsEnabled}, Acc) -> ({register, IsEnabled}, Acc) ->
Handler = {<<"/register">>, mod_register_web}, Handler = {<<"/register">>, mod_register_web},
warn_replaced_handler(register, Handler, IsEnabled), warn_replaced_handler(register, Handler, IsEnabled),
[Handler|Acc]; [Handler | Acc];
({web_admin, IsEnabled}, Acc) -> ({web_admin, IsEnabled}, Acc) ->
Handler = {<<"/admin">>, ejabberd_web_admin}, Handler = {<<"/admin">>, ejabberd_web_admin},
warn_replaced_handler(web_admin, Handler, IsEnabled), warn_replaced_handler(web_admin, Handler, IsEnabled),
[Handler|Acc]; [Handler | Acc];
({http_bind, IsEnabled}, Acc) -> ({http_bind, IsEnabled}, Acc) ->
Handler = {<<"/bosh">>, mod_bosh}, Handler = {<<"/bosh">>, mod_bosh},
warn_replaced_handler(http_bind, Handler, IsEnabled), warn_replaced_handler(http_bind, Handler, IsEnabled),
[Handler|Acc]; [Handler | Acc];
({xmlrpc, IsEnabled}, Acc) -> ({xmlrpc, IsEnabled}, Acc) ->
Handler = {<<"/">>, ejabberd_xmlrpc}, Handler = {<<"/">>, ejabberd_xmlrpc},
warn_replaced_handler(xmlrpc, Handler, IsEnabled), warn_replaced_handler(xmlrpc, Handler, IsEnabled),
Acc ++ [Handler]; Acc ++ [Handler];
(_, Acc) -> (_, Acc) ->
Acc Acc
end, Handlers, Opts), end,
Handlers,
Opts),
Handlers2 = lists:map( Handlers2 = lists:map(
fun({Path, mod_http_bind}) -> fun({Path, mod_http_bind}) ->
warn_replaced_module(mod_http_bind, mod_bosh), warn_replaced_module(mod_http_bind, mod_bosh),
{Path, mod_bosh}; {Path, mod_bosh};
(PathMod) -> (PathMod) ->
PathMod PathMod
end, Handlers1), end,
Handlers1),
Opts1 = lists:filtermap( Opts1 = lists:filtermap(
fun({captcha, _}) -> false; fun({captcha, _}) -> false;
({register, _}) -> false; ({register, _}) -> false;
({web_admin, _}) -> false; ({web_admin, _}) -> false;
({http_bind, _}) -> false; ({http_bind, _}) -> false;
({xmlrpc, _}) -> false; ({xmlrpc, _}) -> false;
({http_poll, _}) -> ({http_poll, _}) ->
?WARNING_MSG("Listening option 'http_poll' is " ?WARNING_MSG("Listening option 'http_poll' is "
"ignored: HTTP Polling support was " "ignored: HTTP Polling support was "
"removed in ejabberd 15.04. ~ts", "removed in ejabberd 15.04. ~ts",
[adjust_hint()]), [adjust_hint()]),
false; false;
({request_handlers, _}) -> ({request_handlers, _}) ->
false; false;
(_) -> true (_) -> true
end, Opts), end,
Opts),
case Handlers2 of case Handlers2 of
[] -> Opts1; [] -> Opts1;
_ -> [{request_handlers, Handlers2}|Opts1] _ -> [{request_handlers, Handlers2} | Opts1]
end. end.
remove_xmlrpc_access_commands(Opts) -> remove_xmlrpc_access_commands(Opts) ->
lists:filter( lists:filter(
fun({access_commands, _}) -> fun({access_commands, _}) ->
warn_removed_option(access_commands, api_permissions), warn_removed_option(access_commands, api_permissions),
false; false;
(_) -> (_) ->
true true
end, Opts). end,
Opts).
replace_turn_ip(Opts) -> replace_turn_ip(Opts) ->
lists:filtermap( lists:filtermap(
fun({turn_ip, Val}) -> fun({turn_ip, Val}) ->
warn_replaced_option(turn_ip, turn_ipv4_address), warn_replaced_option(turn_ip, turn_ipv4_address),
{true, {turn_ipv4_address, Val}}; {true, {turn_ipv4_address, Val}};
(_) -> (_) ->
true true
end, Opts). end,
Opts).
remove_inet_options(Opts) -> remove_inet_options(Opts) ->
lists:filter( lists:filter(
fun({Opt, _}) when Opt == inet; Opt == inet6 -> fun({Opt, _}) when Opt == inet; Opt == inet6 ->
warn_removed_option(Opt, ip), warn_removed_option(Opt, ip),
false; false;
(_) -> (_) ->
true true
end, Opts). end,
Opts).
collect_listener_certfiles(Opts, Acc) -> collect_listener_certfiles(Opts, Acc) ->
Mod = proplists:get_value(module, Opts), Mod = proplists:get_value(module, Opts),
if Mod == ejabberd_http; if
Mod == ejabberd_c2s; Mod == ejabberd_http;
Mod == ejabberd_s2s_in -> Mod == ejabberd_c2s;
case lists:keyfind(certfile, 1, Opts) of Mod == ejabberd_s2s_in ->
{_, CertFile} -> case lists:keyfind(certfile, 1, Opts) of
?WARNING_MSG("Listening option 'certfile' of module ~ts " {_, CertFile} ->
"is deprecated and was automatically " ?WARNING_MSG("Listening option 'certfile' of module ~ts "
"appended to global 'certfiles' option. ~ts", "is deprecated and was automatically "
[Mod, adjust_hint()]), "appended to global 'certfiles' option. ~ts",
CertFiles = maps:get(certfiles, Acc, []), [Mod, adjust_hint()]),
{proplists:delete(certfile, Opts), CertFiles = maps:get(certfiles, Acc, []),
maps:put(certfiles, [CertFile|CertFiles], Acc)}; {proplists:delete(certfile, Opts),
false -> maps:put(certfiles, [CertFile | CertFiles], Acc)};
{Opts, Acc} false ->
end; {Opts, Acc}
true -> end;
{Opts, Acc} true ->
{Opts, Acc}
end. end.
%%%=================================================================== %%%===================================================================
%%% Module transformers %%% Module transformers
%%% NOTE: transform_module_options/1 is called before transform_module/4 %%% NOTE: transform_module_options/1 is called before transform_module/4
@ -384,32 +422,34 @@ collect_listener_certfiles(Opts, Acc) ->
transform_module_options(Opts) -> transform_module_options(Opts) ->
lists:filtermap( lists:filtermap(
fun({Opt, internal}) when Opt == db_type; fun({Opt, internal}) when Opt == db_type;
Opt == ram_db_type -> Opt == ram_db_type ->
{true, {Opt, mnesia}}; {true, {Opt, mnesia}};
({Opt, odbc}) when Opt == db_type; ({Opt, odbc}) when Opt == db_type;
Opt == ram_db_type -> Opt == ram_db_type ->
{true, {Opt, sql}}; {true, {Opt, sql}};
({deref_aliases, Val}) -> ({deref_aliases, Val}) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases), warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}}; {true, {ldap_deref_aliases, Val}};
({ldap_group_cache_size, _}) -> ({ldap_group_cache_size, _}) ->
warn_removed_option(ldap_group_cache_size, cache_size), warn_removed_option(ldap_group_cache_size, cache_size),
false; false;
({ldap_user_cache_size, _}) -> ({ldap_user_cache_size, _}) ->
warn_removed_option(ldap_user_cache_size, cache_size), warn_removed_option(ldap_user_cache_size, cache_size),
false; false;
({ldap_group_cache_validity, _}) -> ({ldap_group_cache_validity, _}) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time), warn_removed_option(ldap_group_cache_validity, cache_life_time),
false; false;
({ldap_user_cache_validity, _}) -> ({ldap_user_cache_validity, _}) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time), warn_removed_option(ldap_user_cache_validity, cache_life_time),
false; false;
({iqdisc, _}) -> ({iqdisc, _}) ->
warn_removed_option(iqdisc), warn_removed_option(iqdisc),
false; false;
(_) -> (_) ->
true true
end, Opts). end,
Opts).
transform_module(Host, mod_http_bind, Opts, Acc) -> transform_module(Host, mod_http_bind, Opts, Acc) ->
warn_replaced_module(mod_http_bind, mod_bosh), warn_replaced_module(mod_http_bind, mod_bosh),
@ -419,177 +459,209 @@ transform_module(Host, mod_vcard_xupdate_odbc, Opts, Acc) ->
transform_module(Host, mod_vcard_xupdate, Opts, Acc); transform_module(Host, mod_vcard_xupdate, Opts, Acc);
transform_module(Host, mod_vcard_ldap, Opts, Acc) -> transform_module(Host, mod_vcard_ldap, Opts, Acc) ->
warn_replaced_module(mod_vcard_ldap, mod_vcard, ldap), warn_replaced_module(mod_vcard_ldap, mod_vcard, ldap),
transform_module(Host, mod_vcard, [{db_type, ldap}|Opts], Acc); transform_module(Host, mod_vcard, [{db_type, ldap} | Opts], Acc);
transform_module(Host, M, Opts, Acc) when (M == mod_announce_odbc orelse transform_module(Host, M, Opts, Acc) when (M == mod_announce_odbc orelse
M == mod_blocking_odbc orelse M == mod_blocking_odbc orelse
M == mod_caps_odbc orelse M == mod_caps_odbc orelse
M == mod_last_odbc orelse M == mod_last_odbc orelse
M == mod_muc_odbc orelse M == mod_muc_odbc orelse
M == mod_offline_odbc orelse M == mod_offline_odbc orelse
M == mod_privacy_odbc orelse M == mod_privacy_odbc orelse
M == mod_private_odbc orelse M == mod_private_odbc orelse
M == mod_pubsub_odbc orelse M == mod_pubsub_odbc orelse
M == mod_roster_odbc orelse M == mod_roster_odbc orelse
M == mod_shared_roster_odbc orelse M == mod_shared_roster_odbc orelse
M == mod_vcard_odbc) -> M == mod_vcard_odbc) ->
M1 = strip_odbc_suffix(M), M1 = strip_odbc_suffix(M),
warn_replaced_module(M, M1, sql), warn_replaced_module(M, M1, sql),
transform_module(Host, M1, [{db_type, sql}|Opts], Acc); transform_module(Host, M1, [{db_type, sql} | Opts], Acc);
transform_module(_Host, mod_blocking, Opts, Acc) -> transform_module(_Host, mod_blocking, Opts, Acc) ->
Opts1 = lists:filter( Opts1 = lists:filter(
fun({db_type, _}) -> fun({db_type, _}) ->
warn_removed_module_option(db_type, mod_blocking), warn_removed_module_option(db_type, mod_blocking),
false; false;
(_) -> (_) ->
true true
end, Opts), end,
Opts),
{{mod_blocking, Opts1}, Acc}; {{mod_blocking, Opts1}, Acc};
transform_module(_Host, mod_carboncopy, Opts, Acc) -> transform_module(_Host, mod_carboncopy, Opts, Acc) ->
Opts1 = lists:filter( Opts1 = lists:filter(
fun({Opt, _}) when Opt == ram_db_type; fun({Opt, _}) when Opt == ram_db_type;
Opt == use_cache; Opt == use_cache;
Opt == cache_size; Opt == cache_size;
Opt == cache_missed; Opt == cache_missed;
Opt == cache_life_time -> Opt == cache_life_time ->
warn_removed_module_option(Opt, mod_carboncopy), warn_removed_module_option(Opt, mod_carboncopy),
false; false;
(_) -> (_) ->
true true
end, Opts), end,
Opts),
{{mod_carboncopy, Opts1}, Acc}; {{mod_carboncopy, Opts1}, Acc};
transform_module(_Host, mod_http_api, Opts, Acc) -> transform_module(_Host, mod_http_api, Opts, Acc) ->
Opts1 = lists:filter( Opts1 = lists:filter(
fun({admin_ip_access, _}) -> fun({admin_ip_access, _}) ->
warn_removed_option(admin_ip_access, api_permissions), warn_removed_option(admin_ip_access, api_permissions),
false; false;
(_) -> (_) ->
true true
end, Opts), end,
Opts),
{{mod_http_api, Opts1}, Acc}; {{mod_http_api, Opts1}, Acc};
transform_module(_Host, mod_http_upload, Opts, Acc) -> transform_module(_Host, mod_http_upload, Opts, Acc) ->
Opts1 = lists:filter( Opts1 = lists:filter(
fun({service_url, _}) -> fun({service_url, _}) ->
warn_deprecated_option(service_url, external_secret), warn_deprecated_option(service_url, external_secret),
true; true;
(_) -> (_) ->
true true
end, Opts), end,
Opts),
{{mod_http_upload, Opts1}, Acc}; {{mod_http_upload, Opts1}, Acc};
transform_module(_Host, mod_pubsub, Opts, Acc) -> transform_module(_Host, mod_pubsub, Opts, Acc) ->
Opts1 = lists:map( Opts1 = lists:map(
fun({plugins, Plugins}) -> fun({plugins, Plugins}) ->
{plugins, {plugins,
lists:filter( lists:filter(
fun(Plugin) -> fun(Plugin) ->
case lists:member( case lists:member(
Plugin, Plugin,
[<<"buddy">>, <<"club">>, <<"dag">>, [<<"buddy">>,
<<"dispatch">>, <<"hometree">>, <<"mb">>, <<"club">>,
<<"mix">>, <<"online">>, <<"private">>, <<"dag">>,
<<"public">>]) of <<"dispatch">>,
true -> <<"hometree">>,
?WARNING_MSG( <<"mb">>,
"Plugin '~ts' of mod_pubsub is not " <<"mix">>,
"supported anymore and has been " <<"online">>,
"automatically removed from 'plugins' " <<"private">>,
"option. ~ts", <<"public">>]) of
[Plugin, adjust_hint()]), true ->
false; ?WARNING_MSG(
false -> "Plugin '~ts' of mod_pubsub is not "
true "supported anymore and has been "
end "automatically removed from 'plugins' "
end, Plugins)}; "option. ~ts",
(Opt) -> [Plugin, adjust_hint()]),
Opt false;
end, Opts), false ->
true
end
end,
Plugins)};
(Opt) ->
Opt
end,
Opts),
{{mod_pubsub, Opts1}, Acc}; {{mod_pubsub, Opts1}, Acc};
transform_module(_Host, Mod, Opts, Acc) -> transform_module(_Host, Mod, Opts, Acc) ->
{{Mod, Opts}, Acc}. {{Mod, Opts}, Acc}.
strip_odbc_suffix(M) -> strip_odbc_suffix(M) ->
[_|T] = lists:reverse(string:tokens(atom_to_list(M), "_")), [_ | T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
list_to_atom(string:join(lists:reverse(T), "_")). list_to_atom(string:join(lists:reverse(T), "_")).
%%%=================================================================== %%%===================================================================
%%% Aux %%% Aux
%%%=================================================================== %%%===================================================================
filtermapfoldr(Fun, Init, List) -> filtermapfoldr(Fun, Init, List) ->
lists:foldr( lists:foldr(
fun(X, {Ret, Acc}) -> fun(X, {Ret, Acc}) ->
case Fun(X, Acc) of case Fun(X, Acc) of
{true, Acc1} -> {[X|Ret], Acc1}; {true, Acc1} -> {[X | Ret], Acc1};
{{true, X1}, Acc1} -> {[X1|Ret], Acc1}; {{true, X1}, Acc1} -> {[X1 | Ret], Acc1};
{false, Acc1} -> {Ret, Acc1} {false, Acc1} -> {Ret, Acc1}
end end
end, {[], Init}, List). end,
{[], Init},
List).
set_certfiles(Y, #{certfiles := CertFiles} = Acc) -> set_certfiles(Y, #{certfiles := CertFiles} = Acc) ->
{lists:keystore(certfiles, 1, Y, {certfiles, CertFiles}), Acc}; {lists:keystore(certfiles, 1, Y, {certfiles, CertFiles}), Acc};
set_certfiles(Y, Acc) -> set_certfiles(Y, Acc) ->
{Y, Acc}. {Y, Acc}.
%%%=================================================================== %%%===================================================================
%%% Warnings %%% Warnings
%%%=================================================================== %%%===================================================================
warn_replaced_module(From, To) -> warn_replaced_module(From, To) ->
?WARNING_MSG("Module ~ts is deprecated and was automatically " ?WARNING_MSG("Module ~ts is deprecated and was automatically "
"replaced by ~ts. ~ts", "replaced by ~ts. ~ts",
[From, To, adjust_hint()]). [From, To, adjust_hint()]).
warn_replaced_module(From, To, Type) -> warn_replaced_module(From, To, Type) ->
?WARNING_MSG("Module ~ts is deprecated and was automatically " ?WARNING_MSG("Module ~ts is deprecated and was automatically "
"replaced by ~ts with db_type: ~ts. ~ts", "replaced by ~ts with db_type: ~ts. ~ts",
[From, To, Type, adjust_hint()]). [From, To, Type, adjust_hint()]).
warn_removed_module(Mod) -> warn_removed_module(Mod) ->
?WARNING_MSG("Module ~ts is deprecated and was automatically " ?WARNING_MSG("Module ~ts is deprecated and was automatically "
"removed from the configuration. ~ts", [Mod, adjust_hint()]). "removed from the configuration. ~ts",
[Mod, adjust_hint()]).
warn_replaced_handler(Opt, {Path, Module}, false) -> warn_replaced_handler(Opt, {Path, Module}, false) ->
?WARNING_MSG("Listening option '~ts' is deprecated, " ?WARNING_MSG("Listening option '~ts' is deprecated, "
"please use instead the " "please use instead the "
"HTTP request handler: \"~ts\" -> ~ts. ~ts", "HTTP request handler: \"~ts\" -> ~ts. ~ts",
[Opt, Path, Module, adjust_hint()]); [Opt, Path, Module, adjust_hint()]);
warn_replaced_handler(Opt, {Path, Module}, true) -> warn_replaced_handler(Opt, {Path, Module}, true) ->
?WARNING_MSG("Listening option '~ts' is deprecated " ?WARNING_MSG("Listening option '~ts' is deprecated "
"and was automatically replaced by " "and was automatically replaced by "
"HTTP request handler: \"~ts\" -> ~ts. ~ts", "HTTP request handler: \"~ts\" -> ~ts. ~ts",
[Opt, Path, Module, adjust_hint()]). [Opt, Path, Module, adjust_hint()]).
warn_deprecated_option(OldOpt, NewOpt) -> warn_deprecated_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~ts' is deprecated. Use option '~ts' instead.", ?WARNING_MSG("Option '~ts' is deprecated. Use option '~ts' instead.",
[OldOpt, NewOpt]). [OldOpt, NewOpt]).
warn_replaced_option(OldOpt, NewOpt) -> warn_replaced_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~ts' is deprecated and was automatically " ?WARNING_MSG("Option '~ts' is deprecated and was automatically "
"replaced by '~ts'. ~ts", "replaced by '~ts'. ~ts",
[OldOpt, NewOpt, adjust_hint()]). [OldOpt, NewOpt, adjust_hint()]).
warn_removed_option(Opt) -> warn_removed_option(Opt) ->
?WARNING_MSG("Option '~ts' is deprecated and has no effect anymore. " ?WARNING_MSG("Option '~ts' is deprecated and has no effect anymore. "
"Please remove it from the configuration.", [Opt]). "Please remove it from the configuration.",
[Opt]).
warn_removed_option(OldOpt, NewOpt) -> warn_removed_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~ts' is deprecated and has no effect anymore. " ?WARNING_MSG("Option '~ts' is deprecated and has no effect anymore. "
"Use option '~ts' instead.", [OldOpt, NewOpt]). "Use option '~ts' instead.",
[OldOpt, NewOpt]).
warn_removed_module_option(Opt, Mod) -> warn_removed_module_option(Opt, Mod) ->
?WARNING_MSG("Option '~ts' of module ~ts is deprecated " ?WARNING_MSG("Option '~ts' of module ~ts is deprecated "
"and has no effect anymore. ~ts", "and has no effect anymore. ~ts",
[Opt, Mod, adjust_hint()]). [Opt, Mod, adjust_hint()]).
warn_huge_timeout(Opt, T) when is_integer(T), T >= 1000 -> warn_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
?WARNING_MSG("Value '~B' of option '~ts' is too big, " ?WARNING_MSG("Value '~B' of option '~ts' is too big, "
"are you sure you have set seconds?", "are you sure you have set seconds?",
[T, Opt]); [T, Opt]);
warn_huge_timeout(_, _) -> warn_huge_timeout(_, _) ->
ok. ok.
adjust_hint() -> adjust_hint() ->
"Please adjust your configuration file accordingly. " "Please adjust your configuration file accordingly. "
"Hint: run `ejabberdctl dump-config` command to view current " "Hint: run `ejabberdctl dump-config` command to view current "
"configuration as it is seen by ejabberd.". "configuration as it is seen by ejabberd.".
%%%=================================================================== %%%===================================================================
%%% Very raw validator: just to make sure we get properly typed terms %%% Very raw validator: just to make sure we get properly typed terms
%%% Expand it if you need to transform more options, but don't %%% Expand it if you need to transform more options, but don't
@ -597,48 +669,60 @@ adjust_hint() ->
%%%=================================================================== %%%===================================================================
validator() -> validator() ->
Validators = Validators =
#{s2s_use_starttls => econf:atom(), #{
certfiles => econf:list(econf:any()), s2s_use_starttls => econf:atom(),
c2s_certfile => econf:binary(), certfiles => econf:list(econf:any()),
s2s_certfile => econf:binary(), c2s_certfile => econf:binary(),
domain_certfile => econf:binary(), s2s_certfile => econf:binary(),
default_db => econf:atom(), domain_certfile => econf:binary(),
default_ram_db => econf:atom(), default_db => econf:atom(),
auth_method => econf:list_or_single(econf:atom()), default_ram_db => econf:atom(),
acme => econf:options( auth_method => econf:list_or_single(econf:atom()),
#{ca_url => econf:binary(), acme => econf:options(
'_' => econf:any()}, #{
[unique]), ca_url => econf:binary(),
listen => '_' => econf:any()
econf:list( },
econf:options( [unique]),
#{captcha => econf:bool(), listen =>
register => econf:bool(), econf:list(
web_admin => econf:bool(), econf:options(
http_bind => econf:bool(), #{
http_poll => econf:bool(), captcha => econf:bool(),
xmlrpc => econf:bool(), register => econf:bool(),
module => econf:atom(), web_admin => econf:bool(),
certfile => econf:binary(), http_bind => econf:bool(),
request_handlers => http_poll => econf:bool(),
econf:map(econf:binary(), econf:atom()), xmlrpc => econf:bool(),
'_' => econf:any()}, module => econf:atom(),
[])), certfile => econf:binary(),
modules => request_handlers =>
econf:options( econf:map(econf:binary(), econf:atom()),
#{'_' => '_' => econf:any()
econf:options( },
#{db_type => econf:atom(), [])),
plugins => econf:list(econf:binary()), modules =>
'_' => econf:any()}, econf:options(
[])}, #{
[]), '_' =>
'_' => econf:any()}, econf:options(
#{
db_type => econf:atom(),
plugins => econf:list(econf:binary()),
'_' => econf:any()
},
[])
},
[]),
'_' => econf:any()
},
econf:options( econf:options(
Validators#{host_config => Validators#{
econf:map(econf:binary(), host_config =>
econf:options(Validators, [])), econf:map(econf:binary(),
append_host_config => econf:options(Validators, [])),
econf:map(econf:binary(), append_host_config =>
econf:options(Validators, []))}, econf:map(econf:binary(),
econf:options(Validators, []))
},
[]). []).

File diff suppressed because it is too large Load diff

View file

@ -29,12 +29,14 @@
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
%%%=================================================================== %%%===================================================================
%%% API functions %%% API functions
%%%=================================================================== %%%===================================================================
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%%%=================================================================== %%%===================================================================
%%% Supervisor callbacks %%% Supervisor callbacks
%%%=================================================================== %%%===================================================================

View file

@ -27,12 +27,14 @@
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-include("translate.hrl"). -include("translate.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
man() -> man() ->
man(<<"en">>). man(<<"en">>).
man(Lang) when is_list(Lang) -> man(Lang) when is_list(Lang) ->
man(list_to_binary(Lang)); man(list_to_binary(Lang));
man(Lang) -> man(Lang) ->
@ -40,7 +42,7 @@ man(Lang) ->
lists:foldl( lists:foldl(
fun(M, {Mods, SubMods} = Acc) -> fun(M, {Mods, SubMods} = Acc) ->
case lists:prefix("mod_", atom_to_list(M)) orelse case lists:prefix("mod_", atom_to_list(M)) orelse
lists:prefix("Elixir.Mod", atom_to_list(M)) of lists:prefix("Elixir.Mod", atom_to_list(M)) of
true -> true ->
try M:mod_doc() of try M:mod_doc() of
#{desc := Descr} = Map -> #{desc := Descr} = Map ->
@ -48,17 +50,18 @@ man(Lang) ->
Example = maps:get(example, Map, []), Example = maps:get(example, Map, []),
Note = maps:get(note, Map, []), Note = maps:get(note, Map, []),
Apitags = get_module_apitags(M), Apitags = get_module_apitags(M),
{[{M, Descr, DocOpts, #{example => Example, note => Note, apitags => Apitags}}|Mods], SubMods}; {[{M, Descr, DocOpts, #{example => Example, note => Note, apitags => Apitags}} | Mods], SubMods};
#{opts := DocOpts} -> #{opts := DocOpts} ->
{ParentMod, Backend} = strip_backend_suffix(M), {ParentMod, Backend} = strip_backend_suffix(M),
{Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)}; {Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)};
#{} -> #{} ->
warn("module ~s is not properly documented", [M]), warn("module ~s is not properly documented", [M]),
Acc Acc
catch _:undef -> catch
_:undef ->
case erlang:function_exported( case erlang:function_exported(
M, mod_options, 1) of M, mod_options, 1) of
true -> true ->
warn("module ~s is not documented", [M]); warn("module ~s is not documented", [M]);
false -> false ->
ok ok
@ -68,13 +71,18 @@ man(Lang) ->
false -> false ->
Acc Acc
end end
end, {[], dict:new()}, ejabberd_config:beams(all)), end,
{[], dict:new()},
ejabberd_config:beams(all)),
Doc = lists:flatmap( Doc = lists:flatmap(
fun(M) -> fun(M) ->
try M:doc() try
catch _:undef -> [] M:doc()
catch
_:undef -> []
end end
end, ejabberd_config:callback_modules(all)), end,
ejabberd_config:callback_modules(all)),
Version = binary_to_list(ejabberd_config:version()), Version = binary_to_list(ejabberd_config:version()),
Options = Options =
["TOP LEVEL OPTIONS", ["TOP LEVEL OPTIONS",
@ -85,7 +93,8 @@ man(Lang) ->
lists:flatmap( lists:flatmap(
fun(Opt) -> fun(Opt) ->
opt_to_man(Lang, Opt, 1) opt_to_man(Lang, Opt, 1)
end, lists:keysort(1, Doc)), end,
lists:keysort(1, Doc)),
ModDoc1 = lists:map( ModDoc1 = lists:map(
fun({M, Descr, DocOpts, Ex}) -> fun({M, Descr, DocOpts, Ex}) ->
case dict:find(M, SubModDoc) of case dict:find(M, SubModDoc) of
@ -94,7 +103,8 @@ man(Lang) ->
error -> error ->
{M, Descr, DocOpts, [], Ex} {M, Descr, DocOpts, [], Ex}
end end
end, ModDoc), end,
ModDoc),
ModOptions = ModOptions =
[io_lib:nl(), [io_lib:nl(),
"MODULES", "MODULES",
@ -112,12 +122,13 @@ man(Lang) ->
lists:duplicate(length(atom_to_list(M)), $~), lists:duplicate(length(atom_to_list(M)), $~),
"[[" ++ ModName ++ "]]", "[[" ++ ModName ++ "]]",
io_lib:nl()] ++ io_lib:nl()] ++
format_versions(Lang, Example) ++ [io_lib:nl()] ++ format_versions(Lang, Example) ++ [io_lib:nl()] ++
tr_multi(Lang, Descr) ++ [io_lib:nl()] ++ tr_multi(Lang, Descr) ++ [io_lib:nl()] ++
opts_to_man(Lang, [{M, '', DocOpts}|Backends]) ++ opts_to_man(Lang, [{M, '', DocOpts} | Backends]) ++
format_example(0, Lang, Example) ++ [io_lib:nl()] ++ format_example(0, Lang, Example) ++ [io_lib:nl()] ++
format_apitags(Lang, Example) format_apitags(Lang, Example)
end, lists:keysort(1, ModDoc1)), end,
lists:keysort(1, ModDoc1)),
ListenOptions = ListenOptions =
[io_lib:nl(), [io_lib:nl(),
"LISTENERS", "LISTENERS",
@ -127,13 +138,14 @@ man(Lang) ->
io_lib:nl(), io_lib:nl(),
"TODO"], "TODO"],
AsciiData = AsciiData =
[[unicode:characters_to_binary(Line), io_lib:nl()] [ [unicode:characters_to_binary(Line), io_lib:nl()]
|| Line <- man_header(Lang) ++ Options ++ [io_lib:nl()] || Line <- man_header(Lang) ++ Options ++ [io_lib:nl()] ++
++ ModOptions ++ ListenOptions ++ man_footer(Lang)], ModOptions ++ ListenOptions ++ man_footer(Lang) ],
warn_undocumented_modules(ModDoc1), warn_undocumented_modules(ModDoc1),
warn_undocumented_options(Doc), warn_undocumented_options(Doc),
write_man(AsciiData). write_man(AsciiData).
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -143,33 +155,36 @@ opts_to_man(Lang, [{_, _, []}]) ->
opts_to_man(Lang, Backends) -> opts_to_man(Lang, Backends) ->
lists:flatmap( lists:flatmap(
fun({_, Backend, DocOpts}) when DocOpts /= [] -> fun({_, Backend, DocOpts}) when DocOpts /= [] ->
Text = if Backend == '' -> Text = if
Backend == '' ->
tr(Lang, ?T("Available options")); tr(Lang, ?T("Available options"));
true -> true ->
lists:flatten( lists:flatten(
io_lib:format( io_lib:format(
tr(Lang, ?T("Available options for '~s' backend")), tr(Lang, ?T("Available options for '~s' backend")),
[Backend])) [Backend]))
end, end,
[Text ++ ":", lists:duplicate(length(Text)+1, $^)| [Text ++ ":", lists:duplicate(length(Text) + 1, $^) | lists:flatmap(
lists:flatmap( fun(Opt) -> opt_to_man(Lang, Opt, 1) end,
fun(Opt) -> opt_to_man(Lang, Opt, 1) end, lists:keysort(1, DocOpts))] ++ [io_lib:nl()];
lists:keysort(1, DocOpts))] ++ [io_lib:nl()];
(_) -> (_) ->
[] []
end, Backends). end,
Backends).
opt_to_man(Lang, {Option, Options}, Level) -> opt_to_man(Lang, {Option, Options}, Level) ->
[format_option(Lang, Option, Options)|format_versions(Lang, Options)++format_desc(Lang, Options)] ++ [format_option(Lang, Option, Options) | format_versions(Lang, Options) ++ format_desc(Lang, Options)] ++
format_example(Level, Lang, Options); format_example(Level, Lang, Options);
opt_to_man(Lang, {Option, Options, Children}, Level) -> opt_to_man(Lang, {Option, Options, Children}, Level) ->
[format_option(Lang, Option, Options)|format_desc(Lang, Options)] ++ [format_option(Lang, Option, Options) | format_desc(Lang, Options)] ++
lists:append( lists:append(
[[H ++ ":"|T] [ [H ++ ":" | T]
|| [H|T] <- lists:map( || [H | T] <- lists:map(
fun(Opt) -> opt_to_man(Lang, Opt, Level+1) end, fun(Opt) -> opt_to_man(Lang, Opt, Level + 1) end,
lists:keysort(1, Children))]) ++ lists:keysort(1, Children)) ]) ++
[io_lib:nl()|format_example(Level, Lang, Options)]. [io_lib:nl() | format_example(Level, Lang, Options)].
get_version_mark(#{note := Note}) -> get_version_mark(#{note := Note}) ->
[XX, YY | _] = string:tokens(binary_to_list(ejabberd_option:version()), "."), [XX, YY | _] = string:tokens(binary_to_list(ejabberd_option:version()), "."),
@ -181,84 +196,95 @@ get_version_mark(#{note := Note}) ->
get_version_mark(_) -> get_version_mark(_) ->
"". "".
format_option(Lang, Option, #{value := Val} = Options) -> format_option(Lang, Option, #{value := Val} = Options) ->
VersionMark = get_version_mark(Options), VersionMark = get_version_mark(Options),
"*" ++ atom_to_list(Option) ++ VersionMark ++ "*: 'pass:[" ++ "*" ++ atom_to_list(Option) ++ VersionMark ++ "*: 'pass:[" ++
tr(Lang, Val) ++ "]'::"; tr(Lang, Val) ++ "]'::";
format_option(_Lang, Option, #{}) -> format_option(_Lang, Option, #{}) ->
"*" ++ atom_to_list(Option) ++ "*::". "*" ++ atom_to_list(Option) ++ "*::".
format_versions(_Lang, #{note := Note}) when Note /= [] -> format_versions(_Lang, #{note := Note}) when Note /= [] ->
["_Note_ about this option: " ++ Note ++ ". "]; ["_Note_ about this option: " ++ Note ++ ". "];
format_versions(_, _) -> format_versions(_, _) ->
[]. [].
%% @format-begin %% @format-begin
get_module_apitags(M) -> get_module_apitags(M) ->
AllCommands = ejabberd_commands:get_commands_definition(), AllCommands = ejabberd_commands:get_commands_definition(),
Tags = [C#ejabberd_commands.tags || C <- AllCommands, C#ejabberd_commands.module == M], Tags = [ C#ejabberd_commands.tags || C <- AllCommands, C#ejabberd_commands.module == M ],
TagsClean = TagsClean =
lists:sort( lists:sort(
misc:lists_uniq( misc:lists_uniq(
lists:flatten(Tags))), lists:flatten(Tags))),
TagsStrings = [atom_to_list(C) || C <- TagsClean], TagsStrings = [ atom_to_list(C) || C <- TagsClean ],
TagFiltering = TagFiltering =
fun ("internal") -> fun("internal") ->
false; false;
([$v | Rest]) -> ([$v | Rest]) ->
{error, no_integer} == string:to_integer(Rest); {error, no_integer} == string:to_integer(Rest);
(_) -> (_) ->
true true
end, end,
TagsFiltered = lists:filter(TagFiltering, TagsStrings), TagsFiltered = lists:filter(TagFiltering, TagsStrings),
TagsUrls = TagsUrls =
[["_`../../developer/ejabberd-api/admin-tags.md#", C, "|", C, "`_"] || C <- TagsFiltered], [ ["_`../../developer/ejabberd-api/admin-tags.md#", C, "|", C, "`_"] || C <- TagsFiltered ],
lists:join(", ", TagsUrls). lists:join(", ", TagsUrls).
format_apitags(_Lang, #{apitags := TagsString}) when TagsString /= "" -> format_apitags(_Lang, #{apitags := TagsString}) when TagsString /= "" ->
["**API Tags:** ", TagsString]; ["**API Tags:** ", TagsString];
format_apitags(_, _) -> format_apitags(_, _) ->
[]. [].
%% @format-end %% @format-end
format_desc(Lang, #{desc := Desc}) -> format_desc(Lang, #{desc := Desc}) ->
tr_multi(Lang, Desc). tr_multi(Lang, Desc).
format_example(Level, Lang, #{example := [_|_] = Example}) ->
format_example(Level, Lang, #{example := [_ | _] = Example}) ->
case lists:all(fun is_list/1, Example) of case lists:all(fun is_list/1, Example) of
true -> true ->
if Level == 0 -> if
Level == 0 ->
["*Example*:", ["*Example*:",
"^^^^^^^^^^"]; "^^^^^^^^^^"];
true -> true ->
["+", "*Example*:", "+"] ["+", "*Example*:", "+"]
end ++ format_yaml(Example); end ++ format_yaml(Example);
false when Level == 0 -> false when Level == 0 ->
["Examples:", ["Examples:",
"^^^^^^^^^"] ++ "^^^^^^^^^"] ++
lists:flatmap( lists:flatmap(
fun({Text, Lines}) -> fun({Text, Lines}) ->
[tr(Lang, Text)] ++ format_yaml(Lines) [tr(Lang, Text)] ++ format_yaml(Lines)
end, Example); end,
Example);
false -> false ->
lists:flatmap( lists:flatmap(
fun(Block) -> fun(Block) ->
["+", "*Examples*:", "+"|Block] ["+", "*Examples*:", "+" | Block]
end, end,
lists:map( lists:map(
fun({Text, Lines}) -> fun({Text, Lines}) ->
[tr(Lang, Text), "+"] ++ format_yaml(Lines) [tr(Lang, Text), "+"] ++ format_yaml(Lines)
end, Example)) end,
Example))
end; end;
format_example(_, _, _) -> format_example(_, _, _) ->
[]. [].
format_yaml(Lines) -> format_yaml(Lines) ->
["==========================", ["==========================",
"[source,yaml]", "[source,yaml]",
"----"|Lines] ++ "----" | Lines] ++
["----", ["----",
"=========================="]. "=========================="].
man_header(Lang) -> man_header(Lang) ->
["ejabberd.yml(5)", ["ejabberd.yml(5)",
@ -276,18 +302,21 @@ man_header(Lang) ->
io_lib:nl(), io_lib:nl(),
"DESCRIPTION", "DESCRIPTION",
"-----------", "-----------",
tr(Lang, ?T("The configuration file is written in " tr(Lang,
"https://en.wikipedia.org/wiki/YAML[YAML] language.")), ?T("The configuration file is written in "
"https://en.wikipedia.org/wiki/YAML[YAML] language.")),
io_lib:nl(), io_lib:nl(),
tr(Lang, ?T("WARNING: YAML is indentation sensitive, so make sure you respect " tr(Lang,
"indentation, or otherwise you will get pretty cryptic " ?T("WARNING: YAML is indentation sensitive, so make sure you respect "
"configuration errors.")), "indentation, or otherwise you will get pretty cryptic "
"configuration errors.")),
io_lib:nl(), io_lib:nl(),
tr(Lang, ?T("Logically, configuration options are split into 3 main categories: " tr(Lang,
"'Modules', 'Listeners' and everything else called 'Top Level' options. " ?T("Logically, configuration options are split into 3 main categories: "
"Thus this document is split into 3 main chapters describing each " "'Modules', 'Listeners' and everything else called 'Top Level' options. "
"category separately. So, the contents of ejabberd.yml will typically " "Thus this document is split into 3 main chapters describing each "
"look like this:")), "category separately. So, the contents of ejabberd.yml will typically "
"look like this:")),
io_lib:nl(), io_lib:nl(),
"==========================", "==========================",
"[source,yaml]", "[source,yaml]",
@ -308,41 +337,47 @@ man_header(Lang) ->
"----", "----",
"==========================", "==========================",
io_lib:nl(), io_lib:nl(),
tr(Lang, ?T("Any configuration error (such as syntax error, unknown option " tr(Lang,
"or invalid option value) is fatal in the sense that ejabberd will " ?T("Any configuration error (such as syntax error, unknown option "
"refuse to load the whole configuration file and will not start or will " "or invalid option value) is fatal in the sense that ejabberd will "
"abort configuration reload.")), "refuse to load the whole configuration file and will not start or will "
"abort configuration reload.")),
io_lib:nl(), io_lib:nl(),
tr(Lang, ?T("All options can be changed in runtime by running 'ejabberdctl " tr(Lang,
"reload-config' command. Configuration reload is atomic: either all options " ?T("All options can be changed in runtime by running 'ejabberdctl "
"are accepted and applied simultaneously or the new configuration is " "reload-config' command. Configuration reload is atomic: either all options "
"refused without any impact on currently running configuration.")), "are accepted and applied simultaneously or the new configuration is "
"refused without any impact on currently running configuration.")),
io_lib:nl(), io_lib:nl(),
tr(Lang, ?T("Some options can be specified for particular virtual host(s) only " tr(Lang,
"using 'host_config' or 'append_host_config' options. Such options " ?T("Some options can be specified for particular virtual host(s) only "
"are called 'local'. Examples are 'modules', 'auth_method' and 'default_db'. " "using 'host_config' or 'append_host_config' options. Such options "
"The options that cannot be defined per virtual host are called 'global'. " "are called 'local'. Examples are 'modules', 'auth_method' and 'default_db'. "
"Examples are 'loglevel', 'certfiles' and 'listen'. It is a configuration " "The options that cannot be defined per virtual host are called 'global'. "
"mistake to put 'global' options under 'host_config' or 'append_host_config' " "Examples are 'loglevel', 'certfiles' and 'listen'. It is a configuration "
"section - ejabberd will refuse to load such configuration.")), "mistake to put 'global' options under 'host_config' or 'append_host_config' "
"section - ejabberd will refuse to load such configuration.")),
io_lib:nl(), io_lib:nl(),
str:format( str:format(
tr(Lang, ?T("It is not recommended to write ejabberd.yml from scratch. Instead it is " tr(Lang,
"better to start from \"default\" configuration file available at ~s. " ?T("It is not recommended to write ejabberd.yml from scratch. Instead it is "
"Once you get ejabberd running you can start changing configuration " "better to start from \"default\" configuration file available at ~s. "
"options to meet your requirements.")), "Once you get ejabberd running you can start changing configuration "
"options to meet your requirements.")),
[default_config_url()]), [default_config_url()]),
io_lib:nl(), io_lib:nl(),
str:format( str:format(
tr(Lang, ?T("Note that this document is intended to provide comprehensive description of " tr(Lang,
"all configuration options that can be consulted to understand the meaning " ?T("Note that this document is intended to provide comprehensive description of "
"of a particular option, its format and possible values. It will be quite " "all configuration options that can be consulted to understand the meaning "
"hard to understand how to configure ejabberd by reading this document only " "of a particular option, its format and possible values. It will be quite "
"- for this purpose the reader is recommended to read online Configuration " "hard to understand how to configure ejabberd by reading this document only "
"Guide available at ~s.")), "- for this purpose the reader is recommended to read online Configuration "
"Guide available at ~s.")),
[configuration_guide_url()]), [configuration_guide_url()]),
io_lib:nl()]. io_lib:nl()].
man_footer(Lang) -> man_footer(Lang) ->
{Year, _, _} = date(), {Year, _, _} = date(),
[io_lib:nl(), [io_lib:nl(),
@ -353,9 +388,10 @@ man_footer(Lang) ->
"VERSION", "VERSION",
"-------", "-------",
str:format( str:format(
tr(Lang, ?T("This document describes the configuration file of ejabberd ~ts. " tr(Lang,
"Configuration options of other ejabberd versions " ?T("This document describes the configuration file of ejabberd ~ts. "
"may differ significantly.")), "Configuration options of other ejabberd versions "
"may differ significantly.")),
[ejabberd_config:version()]), [ejabberd_config:version()]),
io_lib:nl(), io_lib:nl(),
"REPORTING BUGS", "REPORTING BUGS",
@ -377,7 +413,8 @@ man_footer(Lang) ->
"COPYING", "COPYING",
"-------", "-------",
"Copyright (c) 2002-" ++ integer_to_list(Year) ++ "Copyright (c) 2002-" ++ integer_to_list(Year) ++
" https://www.process-one.net[ProcessOne]."]. " https://www.process-one.net[ProcessOne]."].
tr(Lang, {Format, Args}) -> tr(Lang, {Format, Args}) ->
unicode:characters_to_list( unicode:characters_to_list(
@ -387,12 +424,14 @@ tr(Lang, {Format, Args}) ->
tr(Lang, Txt) -> tr(Lang, Txt) ->
unicode:characters_to_list(translate:translate(Lang, iolist_to_binary(Txt))). unicode:characters_to_list(translate:translate(Lang, iolist_to_binary(Txt))).
tr_multi(Lang, Txt) when is_binary(Txt) -> tr_multi(Lang, Txt) when is_binary(Txt) ->
tr_multi(Lang, [Txt]); tr_multi(Lang, [Txt]);
tr_multi(Lang, {Format, Args}) -> tr_multi(Lang, {Format, Args}) ->
tr_multi(Lang, [{Format, Args}]); tr_multi(Lang, [{Format, Args}]);
tr_multi(Lang, Lines) when is_list(Lines) -> tr_multi(Lang, Lines) when is_list(Lines) ->
[tr(Lang, Txt) || Txt <- Lines]. [ tr(Lang, Txt) || Txt <- Lines ].
write_man(AsciiData) -> write_man(AsciiData) ->
case file:get_cwd() of case file:get_cwd() of
@ -425,12 +464,14 @@ write_man(AsciiData) ->
[file:format_error(Reason)]))} [file:format_error(Reason)]))}
end. end.
have_a2x() -> have_a2x() ->
case os:find_executable("a2x") of case os:find_executable("a2x") of
false -> false; false -> false;
Path -> {true, Path} Path -> {true, Path}
end. end.
run_a2x(Cwd, AsciiDocFile) -> run_a2x(Cwd, AsciiDocFile) ->
case have_a2x() of case have_a2x() of
false -> false ->
@ -445,6 +486,7 @@ run_a2x(Cwd, AsciiDocFile) ->
end end
end. end.
warn_undocumented_modules(Docs) -> warn_undocumented_modules(Docs) ->
lists:foreach( lists:foreach(
fun({M, _, DocOpts, Backends, _}) -> fun({M, _, DocOpts, Backends, _}) ->
@ -452,8 +494,11 @@ warn_undocumented_modules(Docs) ->
lists:foreach( lists:foreach(
fun({SubM, _, SubOpts}) -> fun({SubM, _, SubOpts}) ->
warn_undocumented_module(SubM, SubOpts) warn_undocumented_module(SubM, SubOpts)
end, Backends) end,
end, Docs). Backends)
end,
Docs).
warn_undocumented_module(M, DocOpts) -> warn_undocumented_module(M, DocOpts) ->
try M:mod_options(ejabberd_config:get_myname()) of try M:mod_options(ejabberd_config:get_myname()) of
@ -471,11 +516,14 @@ warn_undocumented_module(M, DocOpts) ->
true -> true ->
ok ok
end end
end, Defaults) end,
catch _:undef -> Defaults)
catch
_:undef ->
ok ok
end. end.
warn_undocumented_options(Docs) -> warn_undocumented_options(Docs) ->
Opts = lists:flatmap( Opts = lists:flatmap(
fun(M) -> fun(M) ->
@ -484,11 +532,14 @@ warn_undocumented_options(Docs) ->
lists:map( lists:map(
fun({O, _}) -> O; fun({O, _}) -> O;
(O) when is_atom(O) -> O (O) when is_atom(O) -> O
end, Defaults) end,
catch _:undef -> Defaults)
catch
_:undef ->
[] []
end end
end, ejabberd_config:callback_modules(all)), end,
ejabberd_config:callback_modules(all)),
lists:foreach( lists:foreach(
fun(Opt) -> fun(Opt) ->
case lists:keymember(Opt, 1, Docs) of case lists:keymember(Opt, 1, Docs) of
@ -497,19 +548,24 @@ warn_undocumented_options(Docs) ->
true -> true ->
ok ok
end end
end, Opts). end,
Opts).
warn(Format, Args) -> warn(Format, Args) ->
io:format(standard_error, "Warning: " ++ Format ++ "~n", Args). io:format(standard_error, "Warning: " ++ Format ++ "~n", Args).
strip_backend_suffix(M) -> strip_backend_suffix(M) ->
[H|T] = lists:reverse(string:tokens(atom_to_list(M), "_")), [H | T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
{list_to_atom(string:join(lists:reverse(T), "_")), list_to_atom(H)}. {list_to_atom(string:join(lists:reverse(T), "_")), list_to_atom(H)}.
default_config_url() -> default_config_url() ->
"<https://github.com/processone/ejabberd/blob/" ++ "<https://github.com/processone/ejabberd/blob/" ++
binary_to_list(binary:part(ejabberd_config:version(), {0,5})) ++ binary_to_list(binary:part(ejabberd_config:version(), {0, 5})) ++
"/ejabberd.yml.example>". "/ejabberd.yml.example>".
configuration_guide_url() -> configuration_guide_url() ->
"<https://docs.ejabberd.im/admin/configuration>". "<https://docs.ejabberd.im/admin/configuration>".

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -27,12 +27,25 @@
-behaviour(xmpp_socket). -behaviour(xmpp_socket).
-behaviour(p1_fsm). -behaviour(p1_fsm).
-export([start/1, start_link/1, init/1, handle_event/3, -export([start/1,
handle_sync_event/4, code_change/4, handle_info/3, start_link/1,
terminate/3, send_xml/2, setopts/2, sockname/1, init/1,
peername/1, controlling_process/2, get_owner/1, handle_event/3,
reset_stream/1, close/1, change_shaper/2, handle_sync_event/4,
socket_handoff/3, get_transport/1]). code_change/4,
handle_info/3,
terminate/3,
send_xml/2,
setopts/2,
sockname/1,
peername/1,
controlling_process/2,
get_owner/1,
reset_stream/1,
close/1,
change_shaper/2,
socket_handoff/3,
get_transport/1]).
-include("logger.hrl"). -include("logger.hrl").
@ -40,6 +53,10 @@
-include("ejabberd_http.hrl"). -include("ejabberd_http.hrl").
%%
%% @efmt:off
%% @indent-begin
-record(state, -record(state,
{socket :: ws_socket(), {socket :: ws_socket(),
ping_interval :: non_neg_integer(), ping_interval :: non_neg_integer(),
@ -48,11 +65,15 @@
timeout :: non_neg_integer(), timeout :: non_neg_integer(),
timer = make_ref() :: reference(), timer = make_ref() :: reference(),
input = [] :: list(), input = [] :: list(),
active = false :: boolean(), active = false :: boolean(),
c2s_pid :: pid(), c2s_pid :: pid(),
ws :: {#ws{}, pid()}}). ws :: {#ws{}, pid()}}).
%-define(DBGFSM, true). %% @indent-end
%% @efmt:on
%%
%%%-define(DBGFSM, true).
-ifdef(DBGFSM). -ifdef(DBGFSM).
@ -67,56 +88,70 @@
-type ws_socket() :: {http_ws, pid(), {inet:ip_address(), inet:port_number()}}. -type ws_socket() :: {http_ws, pid(), {inet:ip_address(), inet:port_number()}}.
-export_type([ws_socket/0]). -export_type([ws_socket/0]).
start(WS) -> start(WS) ->
p1_fsm:start(?MODULE, [WS], ?FSMOPTS). p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
start_link(WS) -> start_link(WS) ->
p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS). p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send_xml({http_ws, FsmRef, _IP}, Packet) -> send_xml({http_ws, FsmRef, _IP}, Packet) ->
case catch p1_fsm:sync_send_all_state_event(FsmRef, case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet}, {send_xml, Packet},
15000) 15000) of
of {'EXIT', {timeout, _}} -> {error, timeout};
{'EXIT', {timeout, _}} -> {error, timeout}; {'EXIT', _} -> {error, einval};
{'EXIT', _} -> {error, einval}; Res -> Res
Res -> Res
end. end.
setopts({http_ws, FsmRef, _IP}, Opts) -> setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of case lists:member({active, once}, Opts) of
true -> true ->
p1_fsm:send_all_state_event(FsmRef, p1_fsm:send_all_state_event(FsmRef,
{activate, self()}); {activate, self()});
_ -> ok _ -> ok
end. end.
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
peername({http_ws, _FsmRef, IP}) -> {ok, IP}. peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
controlling_process(_Socket, _Pid) -> ok. controlling_process(_Socket, _Pid) -> ok.
close({http_ws, FsmRef, _IP}) -> close({http_ws, FsmRef, _IP}) ->
catch p1_fsm:sync_send_all_state_event(FsmRef, close). catch p1_fsm:sync_send_all_state_event(FsmRef, close).
reset_stream({http_ws, _FsmRef, _IP} = Socket) -> reset_stream({http_ws, _FsmRef, _IP} = Socket) ->
Socket. Socket.
change_shaper({http_ws, FsmRef, _IP}, Shaper) -> change_shaper({http_ws, FsmRef, _IP}, Shaper) ->
p1_fsm:send_all_state_event(FsmRef, {new_shaper, Shaper}). p1_fsm:send_all_state_event(FsmRef, {new_shaper, Shaper}).
get_transport(_Socket) -> get_transport(_Socket) ->
websocket. websocket.
get_owner({http_ws, FsmRef, _IP}) -> get_owner({http_ws, FsmRef, _IP}) ->
FsmRef. FsmRef.
socket_handoff(LocalPath, Request, Opts) -> socket_handoff(LocalPath, Request, Opts) ->
ejabberd_websocket:socket_handoff(LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0). ejabberd_websocket:socket_handoff(LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0).
%%% Internal %%% Internal
init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) -> init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
SOpts = lists:filtermap(fun({stream_management, _}) -> true; SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true; ({max_ack_queue, _}) -> true;
@ -127,84 +162,100 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
({resend_on_timeout, _}) -> true; ({resend_on_timeout, _}) -> true;
({access, _}) -> true; ({access, _}) -> true;
(_) -> false (_) -> false
end, HOpts), end,
HOpts),
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts, Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
PingInterval = ejabberd_option:websocket_ping_interval(), PingInterval = ejabberd_option:websocket_ping_interval(),
WSTimeout = ejabberd_option:websocket_timeout(), WSTimeout = ejabberd_option:websocket_timeout(),
Socket = {http_ws, self(), IP}, Socket = {http_ws, self(), IP},
?DEBUG("Client connected through websocket ~p", ?DEBUG("Client connected through websocket ~p",
[Socket]), [Socket]),
case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()}|Opts]) of case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()} | Opts]) of
{ok, C2SPid} -> {ok, C2SPid} ->
ejabberd_c2s:accept(C2SPid), ejabberd_c2s:accept(C2SPid),
Timer = erlang:start_timer(WSTimeout, self(), []), Timer = erlang:start_timer(WSTimeout, self(), []),
{ok, loop, {ok, loop,
#state{socket = Socket, timeout = WSTimeout, #state{
timer = Timer, ws = WS, c2s_pid = C2SPid, socket = Socket,
ping_interval = PingInterval}}; timeout = WSTimeout,
{error, Reason} -> timer = Timer,
{stop, Reason}; ws = WS,
ignore -> c2s_pid = C2SPid,
ignore ping_interval = PingInterval
}};
{error, Reason} ->
{stop, Reason};
ignore ->
ignore
end. end.
handle_event({activate, From}, StateName, State) -> handle_event({activate, From}, StateName, State) ->
State1 = case State#state.input of State1 = case State#state.input of
[] -> State#state{active = true}; [] -> State#state{active = true};
Input -> Input ->
lists:foreach( lists:foreach(
fun(I) when is_binary(I)-> fun(I) when is_binary(I) ->
From ! {tcp, State#state.socket, I}; From ! {tcp, State#state.socket, I};
(I2) -> (I2) ->
From ! {tcp, State#state.socket, [I2]} From ! {tcp, State#state.socket, [I2]}
end, Input), end,
State#state{active = false, input = []} Input),
end, State#state{active = false, input = []}
end,
{next_state, StateName, State1#state{c2s_pid = From}}; {next_state, StateName, State1#state{c2s_pid = From}};
handle_event({new_shaper, Shaper}, StateName, #state{ws = {_, WsPid}} = StateData) -> handle_event({new_shaper, Shaper}, StateName, #state{ws = {_, WsPid}} = StateData) ->
WsPid ! {new_shaper, Shaper}, WsPid ! {new_shaper, Shaper},
{next_state, StateName, StateData}. {next_state, StateName, StateData}.
handle_sync_event({send_xml, Packet}, _From, StateName,
handle_sync_event({send_xml, Packet},
_From,
StateName,
#state{ws = {_, WsPid}} = StateData) -> #state{ws = {_, WsPid}} = StateData) ->
SN2 = case Packet of SN2 = case Packet of
{xmlstreamstart, _, Attrs} -> {xmlstreamstart, _, Attrs} ->
Attrs2 = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} | Attrs2 = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} | lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))], route_el(WsPid, #xmlel{name = <<"open">>, attrs = Attrs2}),
route_el(WsPid, #xmlel{name = <<"open">>, attrs = Attrs2}), StateName;
StateName; {xmlstreamend, _} ->
{xmlstreamend, _} -> route_el(WsPid,
route_el(WsPid, #xmlel{name = <<"close">>, #xmlel{
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]}), name = <<"close">>,
stream_end_sent; attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]
{xmlstreamraw, <<"\r\n\r\n">>} -> }),
% cdata ping stream_end_sent;
StateName; {xmlstreamraw, <<"\r\n\r\n">>} ->
{xmlstreamelement, #xmlel{name = Name2} = El2} -> % cdata ping
El3 = case Name2 of StateName;
<<"stream:", _/binary>> -> {xmlstreamelement, #xmlel{name = Name2} = El2} ->
fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2); El3 = case Name2 of
_ -> <<"stream:", _/binary>> ->
case fxml:get_tag_attr_s(<<"xmlns">>, El2) of fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
<<"">> -> _ ->
fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2); case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
_ -> <<"">> ->
El2 fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
end _ ->
end, El2
route_el(WsPid, El3), end
StateName end,
end, route_el(WsPid, El3),
StateName
end,
{reply, ok, SN2, StateData}; {reply, ok, SN2, StateData};
handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}} = StateData) handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}} = StateData)
when StateName /= stream_end_sent -> when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>, Close = #xmlel{
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]}, name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]
},
route_text(WsPid, fxml:element_to_binary(Close)), route_text(WsPid, fxml:element_to_binary(Close)),
{stop, normal, StateData}; {stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) -> handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}. {stop, normal, StateData}.
handle_info(closed, _StateName, StateData) -> handle_info(closed, _StateName, StateData) ->
{stop, normal, StateData}; {stop, normal, StateData};
handle_info({received, Packet}, StateName, StateDataI) -> handle_info({received, Packet}, StateName, StateDataI) ->
@ -222,84 +273,116 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
PingPong == pong -> PingPong == pong ->
StateData2 = setup_timers(StateData), StateData2 = setup_timers(StateData),
{next_state, StateName, {next_state, StateName,
StateData2#state{pong_expected = false}}; StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _}, _StateName, handle_info({timeout, Timer, _},
#state{timer = Timer} = StateData) -> _StateName,
#state{timer = Timer} = StateData) ->
?DEBUG("Closing websocket connection from hitting inactivity timeout", []), ?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
{stop, normal, StateData}; {stop, normal, StateData};
handle_info({timeout, Timer, _}, StateName, handle_info({timeout, Timer, _},
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) -> StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
case StateData#state.pong_expected of case StateData#state.pong_expected of
false -> false ->
misc:cancel_timer(StateData#state.ping_timer), misc:cancel_timer(StateData#state.ping_timer),
PingTimer = erlang:start_timer(StateData#state.ping_interval, PingTimer = erlang:start_timer(StateData#state.ping_interval,
self(), []), self(),
[]),
WsPid ! {ping, <<>>}, WsPid ! {ping, <<>>},
{next_state, StateName, {next_state, StateName,
StateData#state{ping_timer = PingTimer, pong_expected = true}}; StateData#state{ping_timer = PingTimer, pong_expected = true}};
true -> true ->
?DEBUG("Closing websocket connection from missing pongs", []), ?DEBUG("Closing websocket connection from missing pongs", []),
{stop, normal, StateData} {stop, normal, StateData}
end; end;
handle_info(_, StateName, StateData) -> handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}. {next_state, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) -> code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}. {ok, StateName, StateData}.
terminate(_Reason, _StateName, StateData) -> terminate(_Reason, _StateName, StateData) ->
StateData#state.c2s_pid ! {tcp_closed, StateData#state.socket}. StateData#state.c2s_pid ! {tcp_closed, StateData#state.socket}.
setup_timers(StateData) -> setup_timers(StateData) ->
misc:cancel_timer(StateData#state.timer), misc:cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(StateData#state.timeout, Timer = erlang:start_timer(StateData#state.timeout,
self(), []), self(),
[]),
misc:cancel_timer(StateData#state.ping_timer), misc:cancel_timer(StateData#state.ping_timer),
PingTimer = case StateData#state.ping_interval of PingTimer = case StateData#state.ping_interval of
0 -> StateData#state.ping_timer; 0 -> StateData#state.ping_timer;
V -> erlang:start_timer(V, self(), []) V -> erlang:start_timer(V, self(), [])
end, end,
StateData#state{timer = Timer, ping_timer = PingTimer, StateData#state{
pong_expected = false}. timer = Timer,
ping_timer = PingTimer,
pong_expected = false
}.
get_human_html_xmlel() -> get_human_html_xmlel() ->
Heading = <<"ejabberd ", (misc:atom_to_binary(?MODULE))/binary>>, Heading = <<"ejabberd ", (misc:atom_to_binary(?MODULE))/binary>>,
#xmlel{name = <<"html">>, #xmlel{
attrs = name = <<"html">>,
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], attrs =
children = [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
[#xmlel{name = <<"head">>, attrs = [], children =
children = [#xmlel{
[#xmlel{name = <<"title">>, attrs = [], name = <<"head">>,
children = [{xmlcdata, Heading}]}]}, attrs = [],
#xmlel{name = <<"body">>, attrs = [], children =
children = [#xmlel{
[#xmlel{name = <<"h1">>, attrs = [], name = <<"title">>,
children = [{xmlcdata, Heading}]}, attrs = [],
#xmlel{name = <<"p">>, attrs = [], children = [{xmlcdata, Heading}]
children = }]
[{xmlcdata, <<"An implementation of ">>}, },
#xmlel{name = <<"a">>, #xmlel{
attrs = name = <<"body">>,
[{<<"href">>, attrs = [],
<<"http://tools.ietf.org/html/rfc6455">>}], children =
children = [#xmlel{
[{xmlcdata, name = <<"h1">>,
<<"WebSocket protocol">>}]}]}, attrs = [],
#xmlel{name = <<"p">>, attrs = [], children = [{xmlcdata, Heading}]
children = },
[{xmlcdata, #xmlel{
<<"This web page is only informative. To " name = <<"p">>,
"use WebSocket connection you need a Jabber/XMPP " attrs = [],
"client that supports it.">>}]}]}]}. children =
[{xmlcdata, <<"An implementation of ">>},
#xmlel{
name = <<"a">>,
attrs =
[{<<"href">>,
<<"http://tools.ietf.org/html/rfc6455">>}],
children =
[{xmlcdata,
<<"WebSocket protocol">>}]
}]
},
#xmlel{
name = <<"p">>,
attrs = [],
children =
[{xmlcdata,
<<"This web page is only informative. To "
"use WebSocket connection you need a Jabber/XMPP "
"client that supports it.">>}]
}]
}]
}.
parse(State, Data) -> parse(State, Data) ->
El = fxml_stream:parse_element(Data), El = fxml_stream:parse_element(Data),
case El of case El of
#xmlel{name = <<"open">>, attrs = Attrs} -> #xmlel{name = <<"open">>, attrs = Attrs} ->
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} | Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} | lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
{State, [{xmlstreamstart, <<"stream:stream">>, Attrs2}]}; {State, [{xmlstreamstart, <<"stream:stream">>, Attrs2}]};
#xmlel{name = <<"close">>} -> #xmlel{name = <<"close">>} ->
{State, [{xmlstreamend, <<"stream:stream">>}]}; {State, [{xmlstreamend, <<"stream:stream">>}]};
@ -309,6 +392,7 @@ parse(State, Data) ->
{State, [El]} {State, [El]}
end. end.
-spec route_text(pid(), binary()) -> ok. -spec route_text(pid(), binary()) -> ok.
route_text(Pid, Data) -> route_text(Pid, Data) ->
Pid ! {text_with_reply, Data, self()}, Pid ! {text_with_reply, Data, self()},
@ -317,6 +401,7 @@ route_text(Pid, Data) ->
ok ok
end. end.
-spec route_el(pid(), xmlel() | cdata()) -> ok. -spec route_el(pid(), xmlel() | cdata()) -> ok.
route_el(Pid, Data) -> route_el(Pid, Data) ->
route_text(Pid, fxml:element_to_binary(Data)). route_text(Pid, fxml:element_to_binary(Data)).

View file

@ -31,22 +31,29 @@
-export([start_link/0, route/4, dispatch/1]). -export([start_link/0, route/4, dispatch/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_stacktrace.hrl"). -include("ejabberd_stacktrace.hrl").
-record(state, {expire = infinity :: timeout()}). -record(state, {expire = infinity :: timeout()}).
-type state() :: #state{}. -type state() :: #state{}.
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(iq(), atom() | pid(), term(), non_neg_integer()) -> ok. -spec route(iq(), atom() | pid(), term(), non_neg_integer()) -> ok.
route(#iq{type = T} = IQ, Proc, Ctx, Timeout) when T == set; T == get -> route(#iq{type = T} = IQ, Proc, Ctx, Timeout) when T == set; T == get ->
Expire = current_time() + Timeout, Expire = current_time() + Timeout,
@ -56,17 +63,19 @@ route(#iq{type = T} = IQ, Proc, Ctx, Timeout) when T == set; T == get ->
gen_server:cast(?MODULE, {restart_timer, Expire}), gen_server:cast(?MODULE, {restart_timer, Expire}),
ejabberd_router:route(IQ#iq{id = ID}). ejabberd_router:route(IQ#iq{id = ID}).
-spec dispatch(iq()) -> boolean(). -spec dispatch(iq()) -> boolean().
dispatch(#iq{type = T, id = ID} = IQ) when T == error; T == result -> dispatch(#iq{type = T, id = ID} = IQ) when T == error; T == result ->
case decode_id(ID) of case decode_id(ID) of
{ok, Expire, Rnd, Node} -> {ok, Expire, Rnd, Node} ->
ejabberd_cluster:send({?MODULE, Node}, {route, IQ, {Expire, Rnd}}); ejabberd_cluster:send({?MODULE, Node}, {route, IQ, {Expire, Rnd}});
error -> error ->
false false
end; end;
dispatch(_) -> dispatch(_) ->
false. false.
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
@ -74,10 +83,12 @@ init([]) ->
_ = ets:new(?MODULE, [named_table, ordered_set, public]), _ = ets:new(?MODULE, [named_table, ordered_set, public]),
{ok, #state{}}. {ok, #state{}}.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
noreply(State). noreply(State).
handle_cast({restart_timer, Expire}, State) -> handle_cast({restart_timer, Expire}, State) ->
State1 = State#state{expire = min(Expire, State#state.expire)}, State1 = State#state{expire = min(Expire, State#state.expire)},
noreply(State1); noreply(State1);
@ -85,13 +96,14 @@ handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
noreply(State). noreply(State).
handle_info({route, IQ, Key}, State) -> handle_info({route, IQ, Key}, State) ->
case ets:lookup(?MODULE, Key) of case ets:lookup(?MODULE, Key) of
[{_, Proc, Ctx}] -> [{_, Proc, Ctx}] ->
callback(Proc, IQ, Ctx), callback(Proc, IQ, Ctx),
ets:delete(?MODULE, Key); ets:delete(?MODULE, Key);
[] -> [] ->
ok ok
end, end,
noreply(State); noreply(State);
handle_info(timeout, State) -> handle_info(timeout, State) ->
@ -101,12 +113,15 @@ handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
noreply(State). noreply(State).
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -114,35 +129,38 @@ code_change(_OldVsn, State, _Extra) ->
current_time() -> current_time() ->
erlang:system_time(millisecond). erlang:system_time(millisecond).
-spec clean({non_neg_integer(), binary()} | '$end_of_table')
-> non_neg_integer() | infinity. -spec clean({non_neg_integer(), binary()} | '$end_of_table') ->
non_neg_integer() | infinity.
clean({Expire, _} = Key) -> clean({Expire, _} = Key) ->
case current_time() of case current_time() of
Time when Time >= Expire -> Time when Time >= Expire ->
case ets:lookup(?MODULE, Key) of case ets:lookup(?MODULE, Key) of
[{_, Proc, Ctx}] -> [{_, Proc, Ctx}] ->
callback(Proc, timeout, Ctx), callback(Proc, timeout, Ctx),
ets:delete(?MODULE, Key); ets:delete(?MODULE, Key);
[] -> [] ->
ok ok
end, end,
clean(ets:next(?MODULE, Key)); clean(ets:next(?MODULE, Key));
_ -> _ ->
Expire Expire
end; end;
clean('$end_of_table') -> clean('$end_of_table') ->
infinity. infinity.
-spec noreply(state()) -> {noreply, state()} | {noreply, state(), non_neg_integer()}. -spec noreply(state()) -> {noreply, state()} | {noreply, state(), non_neg_integer()}.
noreply(#state{expire = Expire} = State) -> noreply(#state{expire = Expire} = State) ->
case Expire of case Expire of
infinity -> infinity ->
{noreply, State}; {noreply, State};
_ -> _ ->
Timeout = max(0, Expire - current_time()), Timeout = max(0, Expire - current_time()),
{noreply, State, Timeout} {noreply, State, Timeout}
end. end.
-spec encode_id(non_neg_integer(), binary()) -> binary(). -spec encode_id(non_neg_integer(), binary()) -> binary().
encode_id(Expire, Rnd) -> encode_id(Expire, Rnd) ->
ExpireBin = integer_to_binary(Expire), ExpireBin = integer_to_binary(Expire),
@ -150,39 +168,46 @@ encode_id(Expire, Rnd) ->
CheckSum = calc_checksum(<<ExpireBin/binary, Rnd/binary, Node/binary>>), CheckSum = calc_checksum(<<ExpireBin/binary, Rnd/binary, Node/binary>>),
<<"rr-", ExpireBin/binary, $-, Rnd/binary, $-, CheckSum/binary, $-, Node/binary>>. <<"rr-", ExpireBin/binary, $-, Rnd/binary, $-, CheckSum/binary, $-, Node/binary>>.
-spec decode_id(binary()) -> {ok, non_neg_integer(), binary(), atom()} | error. -spec decode_id(binary()) -> {ok, non_neg_integer(), binary(), atom()} | error.
decode_id(<<"rr-", ID/binary>>) -> decode_id(<<"rr-", ID/binary>>) ->
try try
[ExpireBin, Tail] = binary:split(ID, <<"-">>), [ExpireBin, Tail] = binary:split(ID, <<"-">>),
[Rnd, Rest] = binary:split(Tail, <<"-">>), [Rnd, Rest] = binary:split(Tail, <<"-">>),
[CheckSum, NodeBin] = binary:split(Rest, <<"-">>), [CheckSum, NodeBin] = binary:split(Rest, <<"-">>),
CheckSum = calc_checksum(<<ExpireBin/binary, Rnd/binary, NodeBin/binary>>), CheckSum = calc_checksum(<<ExpireBin/binary, Rnd/binary, NodeBin/binary>>),
Node = ejabberd_cluster:get_node_by_id(NodeBin), Node = ejabberd_cluster:get_node_by_id(NodeBin),
Expire = binary_to_integer(ExpireBin), Expire = binary_to_integer(ExpireBin),
{ok, Expire, Rnd, Node} {ok, Expire, Rnd, Node}
catch _:{badmatch, _} -> catch
error _:{badmatch, _} ->
error
end; end;
decode_id(_) -> decode_id(_) ->
error. error.
-spec calc_checksum(binary()) -> binary(). -spec calc_checksum(binary()) -> binary().
calc_checksum(Data) -> calc_checksum(Data) ->
Key = ejabberd_config:get_shared_key(), Key = ejabberd_config:get_shared_key(),
base64:encode(crypto:hash(sha, <<Data/binary, Key/binary>>)). base64:encode(crypto:hash(sha, <<Data/binary, Key/binary>>)).
-spec callback(atom() | pid(), #iq{} | timeout, term()) -> any(). -spec callback(atom() | pid(), #iq{} | timeout, term()) -> any().
callback(undefined, IQRes, Fun) -> callback(undefined, IQRes, Fun) ->
try Fun(IQRes) try
catch ?EX_RULE(Class, Reason, St) -> Fun(IQRes)
StackTrace = ?EX_STACK(St), catch
?ERROR_MSG("Failed to process iq response:~n~ts~n** ~ts", ?EX_RULE(Class, Reason, St) ->
[xmpp:pp(IQRes), StackTrace = ?EX_STACK(St),
misc:format_exception(2, Class, Reason, StackTrace)]) ?ERROR_MSG("Failed to process iq response:~n~ts~n** ~ts",
[xmpp:pp(IQRes),
misc:format_exception(2, Class, Reason, StackTrace)])
end; end;
callback(Proc, IQRes, Ctx) -> callback(Proc, IQRes, Ctx) ->
try try
Proc ! {iq_reply, IQRes, Ctx} Proc ! {iq_reply, IQRes, Ctx}
catch _:badarg -> catch
ok _:badarg ->
ok
end. end.

File diff suppressed because it is too large Load diff

View file

@ -33,26 +33,34 @@
-export([start/0, start_link/0]). -export([start/0, start_link/0]).
-export([route/1, -export([route/1,
get_features/1, get_features/1,
bounce_resource_packet/1, bounce_resource_packet/1,
host_up/1, host_down/1]). host_up/1,
host_down/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, -export([init/1,
handle_info/2, terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
%% deprecated functions: use ejabberd_router:route_iq/3,4 %% deprecated functions: use ejabberd_router:route_iq/3,4
-export([route_iq/2, route_iq/3]). -export([route_iq/2, route_iq/3]).
-deprecated([{route_iq, 2}, {route_iq, 3}]). -deprecated([{route_iq, 2}, {route_iq, 3}]).
-include("logger.hrl"). -include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_stacktrace.hrl"). -include("ejabberd_stacktrace.hrl").
-include("translate.hrl"). -include("translate.hrl").
-record(state, {}). -record(state, {}).
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -62,37 +70,49 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start() -> start() ->
ChildSpec = {?MODULE, {?MODULE, start_link, []}, ChildSpec = {?MODULE, {?MODULE, start_link, []},
transient, 1000, worker, [?MODULE]}, transient,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec). supervisor:start_child(ejabberd_sup, ChildSpec).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], gen_server:start_link({local, ?MODULE},
[]). ?MODULE,
[],
[]).
-spec route(stanza()) -> ok. -spec route(stanza()) -> ok.
route(Packet) -> route(Packet) ->
?DEBUG("Local route:~n~ts", [xmpp:pp(Packet)]), ?DEBUG("Local route:~n~ts", [xmpp:pp(Packet)]),
Type = xmpp:get_type(Packet), Type = xmpp:get_type(Packet),
To = xmpp:get_to(Packet), To = xmpp:get_to(Packet),
if To#jid.luser /= <<"">> -> if
ejabberd_sm:route(Packet); To#jid.luser /= <<"">> ->
is_record(Packet, iq), To#jid.lresource == <<"">> -> ejabberd_sm:route(Packet);
gen_iq_handler:handle(?MODULE, Packet); is_record(Packet, iq), To#jid.lresource == <<"">> ->
Type == result; Type == error -> gen_iq_handler:handle(?MODULE, Packet);
ok; Type == result; Type == error ->
true -> ok;
ejabberd_hooks:run(local_send_to_resource_hook, true ->
To#jid.lserver, [Packet]) ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver,
[Packet])
end. end.
-spec route_iq(iq(), function()) -> ok. -spec route_iq(iq(), function()) -> ok.
route_iq(IQ, Fun) -> route_iq(IQ, Fun) ->
route_iq(IQ, Fun, undefined). route_iq(IQ, Fun, undefined).
-spec route_iq(iq(), function(), undefined | non_neg_integer()) -> ok. -spec route_iq(iq(), function(), undefined | non_neg_integer()) -> ok.
route_iq(IQ, Fun, Timeout) -> route_iq(IQ, Fun, Timeout) ->
ejabberd_router:route_iq(IQ, Fun, undefined, Timeout). ejabberd_router:route_iq(IQ, Fun, undefined, Timeout).
-spec bounce_resource_packet(stanza()) -> ok | stop. -spec bounce_resource_packet(stanza()) -> ok | stop.
bounce_resource_packet(#presence{to = #jid{lresource = <<"">>}}) -> bounce_resource_packet(#presence{to = #jid{lresource = <<"">>}}) ->
ok; ok;
@ -105,14 +125,17 @@ bounce_resource_packet(Packet) ->
ejabberd_router:route_error(Packet, Err), ejabberd_router:route_error(Packet, Err),
stop. stop.
-spec get_features(binary()) -> [binary()]. -spec get_features(binary()) -> [binary()].
get_features(Host) -> get_features(Host) ->
gen_iq_handler:get_features(?MODULE, Host). gen_iq_handler:get_features(?MODULE, Host).
%%==================================================================== %%====================================================================
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
init([]) -> init([]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
lists:foreach(fun host_up/1, ejabberd_option:hosts()), lists:foreach(fun host_up/1, ejabberd_option:hosts()),
@ -122,14 +145,17 @@ init([]) ->
update_table(), update_table(),
{ok, #state{}}. {ok, #state{}}.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({route, Packet}, State) -> handle_info({route, Packet}, State) ->
route(Packet), route(Packet),
{noreply, State}; {noreply, State};
@ -137,15 +163,18 @@ handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
lists:foreach(fun host_down/1, ejabberd_option:hosts()), lists:foreach(fun host_down/1, ejabberd_option:hosts()),
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 10), ejabberd_hooks:delete(host_up, ?MODULE, host_up, 10),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 100), ejabberd_hooks:delete(host_down, ?MODULE, host_down, 100),
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -154,20 +183,28 @@ update_table() ->
catch mnesia:delete_table(iq_response), catch mnesia:delete_table(iq_response),
ok. ok.
host_up(Host) -> host_up(Host) ->
Owner = case whereis(?MODULE) of Owner = case whereis(?MODULE) of
undefined -> self(); undefined -> self();
Pid -> Pid Pid -> Pid
end, end,
ejabberd_router:register_route(Host, Host, {apply, ?MODULE, route}, Owner), ejabberd_router:register_route(Host, Host, {apply, ?MODULE, route}, Owner),
ejabberd_hooks:add(local_send_to_resource_hook, Host, ejabberd_hooks:add(local_send_to_resource_hook,
?MODULE, bounce_resource_packet, 100). Host,
?MODULE,
bounce_resource_packet,
100).
host_down(Host) -> host_down(Host) ->
Owner = case whereis(?MODULE) of Owner = case whereis(?MODULE) of
undefined -> self(); undefined -> self();
Pid -> Pid Pid -> Pid
end, end,
ejabberd_router:unregister_route(Host, Owner), ejabberd_router:unregister_route(Host, Owner),
ejabberd_hooks:delete(local_send_to_resource_hook, Host, ejabberd_hooks:delete(local_send_to_resource_hook,
?MODULE, bounce_resource_packet, 100). Host,
?MODULE,
bounce_resource_packet,
100).

View file

@ -34,42 +34,52 @@
%% Deprecated functions %% Deprecated functions
-export([restart/0, reopen_log/0, rotate_log/0]). -export([restart/0, reopen_log/0, rotate_log/0]).
-deprecated([{restart, 0}, -deprecated([{restart, 0},
{reopen_log, 0}, {reopen_log, 0},
{rotate_log, 0}]). {rotate_log, 0}]).
-type loglevel() :: none | emergency | alert | critical | -type loglevel() :: none |
error | warning | notice | info | debug. emergency |
alert |
critical |
error |
warning |
notice |
info |
debug.
-define(is_loglevel(L), -define(is_loglevel(L),
((L == none) or (L == emergency) or (L == alert) ((L == none) or (L == emergency) or (L == alert) or
or (L == critical) or (L == error) or (L == warning) (L == critical) or (L == error) or (L == warning) or
or (L == notice) or (L == info) or (L == debug))). (L == notice) or (L == info) or (L == debug))).
-export_type([loglevel/0]). -export_type([loglevel/0]).
-include("logger.hrl"). -include("logger.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
-spec get_log_path() -> string(). -spec get_log_path() -> string().
get_log_path() -> get_log_path() ->
case ejabberd_config:env_binary_to_list(ejabberd, log_path) of case ejabberd_config:env_binary_to_list(ejabberd, log_path) of
{ok, Path} -> {ok, Path} ->
Path; Path;
undefined -> undefined ->
case os:getenv("EJABBERD_LOG_PATH") of case os:getenv("EJABBERD_LOG_PATH") of
false -> false ->
"ejabberd.log"; "ejabberd.log";
Path -> Path ->
Path Path
end end
end. end.
-spec loglevels() -> [loglevel(), ...]. -spec loglevels() -> [loglevel(), ...].
loglevels() -> loglevels() ->
[none, emergency, alert, critical, error, warning, notice, info, debug]. [none, emergency, alert, critical, error, warning, notice, info, debug].
-spec convert_loglevel(0..5) -> loglevel(). -spec convert_loglevel(0..5) -> loglevel().
convert_loglevel(0) -> none; convert_loglevel(0) -> none;
convert_loglevel(1) -> critical; convert_loglevel(1) -> critical;
@ -78,16 +88,18 @@ convert_loglevel(3) -> warning;
convert_loglevel(4) -> info; convert_loglevel(4) -> info;
convert_loglevel(5) -> debug. convert_loglevel(5) -> debug.
quiet_mode() -> quiet_mode() ->
case application:get_env(ejabberd, quiet) of case application:get_env(ejabberd, quiet) of
{ok, true} -> true; {ok, true} -> true;
_ -> false _ -> false
end. end.
-spec get_integer_env(atom(), T) -> T. -spec get_integer_env(atom(), T) -> T.
get_integer_env(Name, Default) -> get_integer_env(Name, Default) ->
case application:get_env(ejabberd, Name) of case application:get_env(ejabberd, Name) of
{ok, I} when is_integer(I), I>=0 -> {ok, I} when is_integer(I), I >= 0 ->
I; I;
{ok, infinity} -> {ok, infinity} ->
infinity; infinity;
@ -100,7 +112,10 @@ get_integer_env(Name, Default) ->
Default Default
end. end.
-ifdef(LAGER). -ifdef(LAGER).
-spec get_string_env(atom(), T) -> T. -spec get_string_env(atom(), T) -> T.
get_string_env(Name, Default) -> get_string_env(Name, Default) ->
case application:get_env(ejabberd, Name) of case application:get_env(ejabberd, Name) of
@ -115,9 +130,11 @@ get_string_env(Name, Default) ->
Default Default
end. end.
start() -> start() ->
start(info). start(info).
start(Level) -> start(Level) ->
StartedApps = application:which_applications(5000), StartedApps = application:which_applications(5000),
case lists:keyfind(logger, 1, StartedApps) of case lists:keyfind(logger, 1, StartedApps) of
@ -131,6 +148,7 @@ start(Level) ->
do_start(Level) do_start(Level)
end. end.
do_start_for_logger(Level) -> do_start_for_logger(Level) ->
application:load(sasl), application:load(sasl),
application:set_env(sasl, sasl_error_logger, false), application:set_env(sasl, sasl_error_logger, false),
@ -142,6 +160,7 @@ do_start_for_logger(Level) ->
ejabberd:start_app(lager), ejabberd:start_app(lager),
ok. ok.
do_start(Level) -> do_start(Level) ->
application:load(sasl), application:load(sasl),
application:set_env(sasl, sasl_error_logger, false), application:set_env(sasl, sasl_error_logger, false),
@ -151,48 +170,60 @@ do_start(Level) ->
ErrorLog = filename:join([Dir, "error.log"]), ErrorLog = filename:join([Dir, "error.log"]),
CrashLog = filename:join([Dir, "crash.log"]), CrashLog = filename:join([Dir, "crash.log"]),
LogRotateDate = get_string_env(log_rotate_date, ""), LogRotateDate = get_string_env(log_rotate_date, ""),
LogRotateSize = case get_integer_env(log_rotate_size, 10*1024*1024) of LogRotateSize = case get_integer_env(log_rotate_size, 10 * 1024 * 1024) of
infinity -> 0; infinity -> 0;
V -> V V -> V
end, end,
LogRotateCount = get_integer_env(log_rotate_count, 1), LogRotateCount = get_integer_env(log_rotate_count, 1),
LogRateLimit = get_integer_env(log_rate_limit, 100), LogRateLimit = get_integer_env(log_rate_limit, 100),
ConsoleLevel0 = case quiet_mode() of ConsoleLevel0 = case quiet_mode() of
true -> critical; true -> critical;
_ -> Level _ -> Level
end, end,
ConsoleLevel = case get_lager_version() >= "3.6.0" of ConsoleLevel = case get_lager_version() >= "3.6.0" of
true -> [{level, ConsoleLevel0}]; true -> [{level, ConsoleLevel0}];
false -> ConsoleLevel0 false -> ConsoleLevel0
end, end,
application:set_env(lager, error_logger_hwm, LogRateLimit), application:set_env(lager, error_logger_hwm, LogRateLimit),
application:set_env( application:set_env(
lager, handlers, lager,
handlers,
[{lager_console_backend, ConsoleLevel}, [{lager_console_backend, ConsoleLevel},
{lager_file_backend, [{file, ConsoleLog}, {level, Level}, {date, LogRotateDate}, {lager_file_backend, [{file, ConsoleLog},
{count, LogRotateCount}, {size, LogRotateSize}]}, {level, Level},
{lager_file_backend, [{file, ErrorLog}, {level, error}, {date, LogRotateDate}, {date, LogRotateDate},
{count, LogRotateCount}, {size, LogRotateSize}]}]), {count, LogRotateCount},
{size, LogRotateSize}]},
{lager_file_backend, [{file, ErrorLog},
{level, error},
{date, LogRotateDate},
{count, LogRotateCount},
{size, LogRotateSize}]}]),
application:set_env(lager, crash_log, CrashLog), application:set_env(lager, crash_log, CrashLog),
application:set_env(lager, crash_log_date, LogRotateDate), application:set_env(lager, crash_log_date, LogRotateDate),
application:set_env(lager, crash_log_size, LogRotateSize), application:set_env(lager, crash_log_size, LogRotateSize),
application:set_env(lager, crash_log_count, LogRotateCount), application:set_env(lager, crash_log_count, LogRotateCount),
ejabberd:start_app(lager), ejabberd:start_app(lager),
lists:foreach(fun(Handler) -> lists:foreach(fun(Handler) ->
lager:set_loghwm(Handler, LogRateLimit) lager:set_loghwm(Handler, LogRateLimit)
end, gen_event:which_handlers(lager_event)). end,
gen_event:which_handlers(lager_event)).
restart() -> restart() ->
Level = ejabberd_option:loglevel(), Level = ejabberd_option:loglevel(),
application:stop(lager), application:stop(lager),
start(Level). start(Level).
config_reloaded() -> config_reloaded() ->
ok. ok.
reopen_log() -> reopen_log() ->
ok. ok.
rotate_log() -> rotate_log() ->
catch lager_crash_log ! rotate, catch lager_crash_log ! rotate,
lists:foreach( lists:foreach(
@ -200,7 +231,9 @@ rotate_log() ->
whereis(lager_event) ! {rotate, File}; whereis(lager_event) ! {rotate, File};
(_) -> (_) ->
ok ok
end, gen_event:which_handlers(lager_event)). end,
gen_event:which_handlers(lager_event)).
get() -> get() ->
Handlers = get_lager_handlers(), Handlers = get_lager_handlers(),
@ -211,9 +244,11 @@ get() ->
(_, Acc) -> (_, Acc) ->
Acc Acc
end, end,
none, Handlers). none,
Handlers).
set(N) when is_integer(N), N>=0, N=<5 ->
set(N) when is_integer(N), N >= 0, N =< 5 ->
set(convert_loglevel(N)); set(convert_loglevel(N));
set(Level) when ?is_loglevel(Level) -> set(Level) when ?is_loglevel(Level) ->
case get() of case get() of
@ -231,99 +266,119 @@ set(Level) when ?is_loglevel(Level) ->
lager:set_loglevel(H, Level); lager:set_loglevel(H, Level);
(_) -> (_) ->
ok ok
end, get_lager_handlers()) end,
get_lager_handlers())
end, end,
case Level of case Level of
debug -> xmpp:set_config([{debug, true}]); debug -> xmpp:set_config([{debug, true}]);
_ -> xmpp:set_config([{debug, false}]) _ -> xmpp:set_config([{debug, false}])
end. end.
get_lager_handlers() -> get_lager_handlers() ->
case catch gen_event:which_handlers(lager_event) of case catch gen_event:which_handlers(lager_event) of
{'EXIT',noproc} -> {'EXIT', noproc} ->
[]; [];
Result -> Result ->
Result Result
end. end.
-spec get_lager_version() -> string(). -spec get_lager_version() -> string().
get_lager_version() -> get_lager_version() ->
Apps = application:loaded_applications(), Apps = application:loaded_applications(),
case lists:keyfind(lager, 1, Apps) of case lists:keyfind(lager, 1, Apps) of
{_, _, Vsn} -> Vsn; {_, _, Vsn} -> Vsn;
false -> "0.0.0" false -> "0.0.0"
end. end.
set_modules_fully_logged(_) -> ok. set_modules_fully_logged(_) -> ok.
flush() -> flush() ->
application:stop(lager), application:stop(lager),
application:stop(sasl). application:stop(sasl).
-else. -else.
-include_lib("kernel/include/logger.hrl"). -include_lib("kernel/include/logger.hrl").
-spec start() -> ok | {error, term()}. -spec start() -> ok | {error, term()}.
start() -> start() ->
start(info). start(info).
start(Level) -> start(Level) ->
EjabberdLog = get_log_path(), EjabberdLog = get_log_path(),
Dir = filename:dirname(EjabberdLog), Dir = filename:dirname(EjabberdLog),
ErrorLog = filename:join([Dir, "error.log"]), ErrorLog = filename:join([Dir, "error.log"]),
LogRotateSize = get_integer_env(log_rotate_size, 10*1024*1024), LogRotateSize = get_integer_env(log_rotate_size, 10 * 1024 * 1024),
LogRotateCount = get_integer_env(log_rotate_count, 1), LogRotateCount = get_integer_env(log_rotate_count, 1),
LogBurstLimitWindowTime = get_integer_env(log_burst_limit_window_time, 1000), LogBurstLimitWindowTime = get_integer_env(log_burst_limit_window_time, 1000),
LogBurstLimitCount = get_integer_env(log_burst_limit_count, 500), LogBurstLimitCount = get_integer_env(log_burst_limit_count, 500),
Config = #{max_no_bytes => LogRotateSize, Config = #{
max_no_files => LogRotateCount, max_no_bytes => LogRotateSize,
filesync_repeat_interval => no_repeat, max_no_files => LogRotateCount,
file_check => 1000, filesync_repeat_interval => no_repeat,
sync_mode_qlen => 1000, file_check => 1000,
drop_mode_qlen => 1000, sync_mode_qlen => 1000,
flush_qlen => 5000, drop_mode_qlen => 1000,
burst_limit_window_time => LogBurstLimitWindowTime, flush_qlen => 5000,
burst_limit_max_count => LogBurstLimitCount}, burst_limit_window_time => LogBurstLimitWindowTime,
FmtConfig = #{legacy_header => false, burst_limit_max_count => LogBurstLimitCount
time_designator => $\s, },
max_size => 100*1024, FmtConfig = #{
single_line => false}, legacy_header => false,
time_designator => $\s,
max_size => 100 * 1024,
single_line => false
},
FileFmtConfig = FmtConfig#{template => file_template()}, FileFmtConfig = FmtConfig#{template => file_template()},
ConsoleFmtConfig = FmtConfig#{template => console_template()}, ConsoleFmtConfig = FmtConfig#{template => console_template()},
try try
ok = logger:set_primary_config(level, Level), ok = logger:set_primary_config(level, Level),
DefaultHandlerId = get_default_handlerid(), DefaultHandlerId = get_default_handlerid(),
ok = logger:update_formatter_config(DefaultHandlerId, ConsoleFmtConfig), ok = logger:update_formatter_config(DefaultHandlerId, ConsoleFmtConfig),
case quiet_mode() of case quiet_mode() of
true -> true ->
ok = logger:set_handler_config(DefaultHandlerId, level, critical); ok = logger:set_handler_config(DefaultHandlerId, level, critical);
_ -> _ ->
ok ok
end, end,
case logger:add_primary_filter(progress_report, case logger:add_primary_filter(progress_report,
{fun ?MODULE:progress_filter/2, stop}) of {fun ?MODULE:progress_filter/2, stop}) of
ok -> ok; ok -> ok;
{error, {already_exist, _}} -> ok {error, {already_exist, _}} -> ok
end, end,
case logger:add_handler(ejabberd_log, logger_std_h, case logger:add_handler(ejabberd_log,
#{level => all, logger_std_h,
config => Config#{file => EjabberdLog}, #{
formatter => {logger_formatter, FileFmtConfig}}) of level => all,
ok -> ok; config => Config#{file => EjabberdLog},
{error, {already_exist, _}} -> ok formatter => {logger_formatter, FileFmtConfig}
end, }) of
case logger:add_handler(error_log, logger_std_h, ok -> ok;
#{level => error, {error, {already_exist, _}} -> ok
config => Config#{file => ErrorLog}, end,
formatter => {logger_formatter, FileFmtConfig}}) of case logger:add_handler(error_log,
ok -> ok; logger_std_h,
{error, {already_exist, _}} -> ok #{
end level => error,
catch _:{Tag, Err} when Tag == badmatch; Tag == case_clause -> config => Config#{file => ErrorLog},
?LOG_CRITICAL("Failed to set logging: ~p", [Err]), formatter => {logger_formatter, FileFmtConfig}
Err }) of
ok -> ok;
{error, {already_exist, _}} -> ok
end
catch
_:{Tag, Err} when Tag == badmatch; Tag == case_clause ->
?LOG_CRITICAL("Failed to set logging: ~p", [Err]),
Err
end. end.
get_default_handlerid() -> get_default_handlerid() ->
Ids = logger:get_handler_ids(), Ids = logger:get_handler_ids(),
case lists:member(default, Ids) of case lists:member(default, Ids) of
@ -331,10 +386,12 @@ get_default_handlerid() ->
false -> hd(Ids) false -> hd(Ids)
end. end.
-spec restart() -> ok. -spec restart() -> ok.
restart() -> restart() ->
ok. ok.
-spec config_reloaded() -> ok. -spec config_reloaded() -> ok.
config_reloaded() -> config_reloaded() ->
LogRotateSize = ejabberd_option:log_rotate_size(), LogRotateSize = ejabberd_option:log_rotate_size(),
@ -342,106 +399,138 @@ config_reloaded() ->
LogBurstLimitWindowTime = ejabberd_option:log_burst_limit_window_time(), LogBurstLimitWindowTime = ejabberd_option:log_burst_limit_window_time(),
LogBurstLimitCount = ejabberd_option:log_burst_limit_count(), LogBurstLimitCount = ejabberd_option:log_burst_limit_count(),
lists:foreach( lists:foreach(
fun(Handler) -> fun(Handler) ->
case logger:get_handler_config(Handler) of case logger:get_handler_config(Handler) of
{ok, #{config := Config}} -> {ok, #{config := Config}} ->
Config2 = Config#{ Config2 = Config#{
max_no_bytes => LogRotateSize, max_no_bytes => LogRotateSize,
max_no_files => LogRotateCount, max_no_files => LogRotateCount,
burst_limit_window_time => LogBurstLimitWindowTime, burst_limit_window_time => LogBurstLimitWindowTime,
burst_limit_max_count => LogBurstLimitCount}, burst_limit_max_count => LogBurstLimitCount
logger:update_handler_config(Handler, config, Config2); },
_ -> logger:update_handler_config(Handler, config, Config2);
ok _ ->
end ok
end, [ejabberd_log, error_log]). end
end,
[ejabberd_log, error_log]).
progress_filter(#{level:=info,msg:={report,#{label:={_,progress}}}} = Event, _) ->
progress_filter(#{level := info, msg := {report, #{label := {_, progress}}}} = Event, _) ->
case get() of case get() of
debug -> debug ->
logger_filters:progress(Event#{level => debug}, log); logger_filters:progress(Event#{level => debug}, log);
_ -> _ ->
stop stop
end; end;
progress_filter(Event, _) -> progress_filter(Event, _) ->
Event. Event.
-ifdef(ELIXIR_ENABLED). -ifdef(ELIXIR_ENABLED).
console_template() -> console_template() ->
case (false /= code:is_loaded('Elixir.Logger')) case (false /= code:is_loaded('Elixir.Logger')) andalso
andalso 'Elixir.System':version() >= <<"1.15">> of
'Elixir.System':version() >= <<"1.15">> of
true -> true ->
{ok, DC} = logger:get_handler_config(default), {ok, DC} = logger:get_handler_config(default),
MessageFormat = case maps:get(formatter, DC) of MessageFormat = case maps:get(formatter, DC) of
%% https://hexdocs.pm/logger/1.17.2/Logger.Formatter.html#module-formatting %% https://hexdocs.pm/logger/1.17.2/Logger.Formatter.html#module-formatting
{'Elixir.Logger.Formatter', _} -> {'Elixir.Logger.Formatter', _} ->
message; message;
%% https://www.erlang.org/doc/apps/kernel/logger_formatter#t:template/0 %% https://www.erlang.org/doc/apps/kernel/logger_formatter#t:template/0
{logger_formatter, _} -> {logger_formatter, _} ->
msg msg
end, end,
[date, " ", time, " [", level, "] ", MessageFormat, "\n"]; [date, " ", time, " [", level, "] ", MessageFormat, "\n"];
false -> false ->
[time, " [", level, "] " | msg()] [time, " [", level, "] " | msg()]
end. end.
msg() -> msg() ->
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []}, [{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
msg, io_lib:nl()]. msg,
io_lib:nl()].
-else. -else.
console_template() -> console_template() ->
[time, " ", ?CLEAD, ?CDEFAULT, clevel, "[", level, "] ", ?CMID, ?CDEFAULT, ctext | msg()]. [time, " ", ?CLEAD, ?CDEFAULT, clevel, "[", level, "] ", ?CMID, ?CDEFAULT, ctext | msg()].
msg() -> msg() ->
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []}, [{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
msg, ?CCLEAN, io_lib:nl()]. msg,
?CCLEAN,
io_lib:nl()].
-endif. -endif.
file_template() -> file_template() ->
[time, " [", level, "] ", pid, [time,
{mfa, ["@", mfa, {line, [":", line], []}], []}, " " | msg()]. " [",
level,
"] ",
pid,
{mfa, ["@", mfa, {line, [":", line], []}], []},
" " | msg()].
-spec reopen_log() -> ok. -spec reopen_log() -> ok.
reopen_log() -> reopen_log() ->
ok. ok.
-spec rotate_log() -> ok. -spec rotate_log() -> ok.
rotate_log() -> rotate_log() ->
ok. ok.
-spec get() -> loglevel(). -spec get() -> loglevel().
get() -> get() ->
#{level := Level} = logger:get_primary_config(), #{level := Level} = logger:get_primary_config(),
Level. Level.
-spec set(0..5 | loglevel()) -> ok. -spec set(0..5 | loglevel()) -> ok.
set(N) when is_integer(N), N>=0, N=<5 -> set(N) when is_integer(N), N >= 0, N =< 5 ->
set(convert_loglevel(N)); set(convert_loglevel(N));
set(Level) when ?is_loglevel(Level) -> set(Level) when ?is_loglevel(Level) ->
case get() of case get() of
Level -> ok; Level -> ok;
PrevLevel -> PrevLevel ->
?LOG_NOTICE("Changing loglevel from '~s' to '~s'", ?LOG_NOTICE("Changing loglevel from '~s' to '~s'",
[PrevLevel, Level]), [PrevLevel, Level]),
logger:set_primary_config(level, Level), logger:set_primary_config(level, Level),
case Level of case Level of
debug -> xmpp:set_config([{debug, true}]); debug -> xmpp:set_config([{debug, true}]);
_ -> xmpp:set_config([{debug, false}]) _ -> xmpp:set_config([{debug, false}])
end end
end. end.
set_modules_fully_logged(Modules) -> set_modules_fully_logged(Modules) ->
logger:unset_module_level(), logger:unset_module_level(),
logger:set_module_level(Modules, all). logger:set_module_level(Modules, all).
-spec flush() -> ok. -spec flush() -> ok.
flush() -> flush() ->
lists:foreach( lists:foreach(
fun(#{id := HandlerId, module := logger_std_h}) -> fun(#{id := HandlerId, module := logger_std_h}) ->
logger_std_h:filesync(HandlerId); logger_std_h:filesync(HandlerId);
(#{id := HandlerId, module := logger_disk_log_h}) -> (#{id := HandlerId, module := logger_disk_log_h}) ->
logger_disk_log_h:filesync(HandlerId); logger_disk_log_h:filesync(HandlerId);
(_) -> (_) ->
ok ok
end, logger:get_handler_config()). end,
logger:get_handler_config()).
-endif. -endif.

View file

@ -33,432 +33,507 @@
-behaviour(gen_server). -behaviour(gen_server).
-export([start/0, create/3, update/2, transform/2, transform/3, -export([start/0,
dump_schema/0]). create/3,
update/2,
transform/2, transform/3,
dump_schema/0]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(NEED_RESET, [local_content, type]). -define(NEED_RESET, [local_content, type]).
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_stacktrace.hrl"). -include("ejabberd_stacktrace.hrl").
-record(state, {tables = #{} :: tables(), -record(state, {
schema = [] :: [{atom(), custom_schema()}]}). tables = #{} :: tables(),
schema = [] :: [{atom(), custom_schema()}]
}).
-type tables() :: #{atom() => {[{atom(), term()}], term()}}. -type tables() :: #{atom() => {[{atom(), term()}], term()}}.
-type custom_schema() :: [{ram_copies | disc_copies | disc_only_copies, [node()]} | -type custom_schema() :: [{ram_copies | disc_copies | disc_only_copies, [node()]} |
{local_content, boolean()} | {local_content, boolean()} |
{type, set | ordered_set | bag} | {type, set | ordered_set | bag} |
{attributes, [atom()]} | {attributes, [atom()]} |
{index, [atom()]}]. {index, [atom()]}].
start() -> start() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec create(module(), atom(), list()) -> any(). -spec create(module(), atom(), list()) -> any().
create(Module, Name, TabDef) -> create(Module, Name, TabDef) ->
gen_server:call(?MODULE, {create, Module, Name, TabDef}, gen_server:call(?MODULE,
%% Huge timeout is need to have enough {create, Module, Name, TabDef},
%% time to transform huge tables %% Huge timeout is need to have enough
timer:minutes(30)). %% time to transform huge tables
timer:minutes(30)).
init([]) -> init([]) ->
ejabberd_config:env_binary_to_list(mnesia, dir), ejabberd_config:env_binary_to_list(mnesia, dir),
MyNode = node(), MyNode = node(),
DbNodes = mnesia:system_info(db_nodes), DbNodes = mnesia:system_info(db_nodes),
case lists:member(MyNode, DbNodes) of case lists:member(MyNode, DbNodes) of
true -> true ->
case mnesia:system_info(extra_db_nodes) of case mnesia:system_info(extra_db_nodes) of
[] -> mnesia:create_schema([node()]); [] -> mnesia:create_schema([node()]);
_ -> ok _ -> ok
end, end,
ejabberd:start_app(mnesia, permanent), ejabberd:start_app(mnesia, permanent),
?DEBUG("Waiting for Mnesia tables synchronization...", []), ?DEBUG("Waiting for Mnesia tables synchronization...", []),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity), mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity),
Schema = read_schema_file(), Schema = read_schema_file(),
{ok, #state{schema = Schema}}; {ok, #state{schema = Schema}};
false -> false ->
?CRITICAL_MSG("Erlang node name mismatch: I'm running in node [~ts], " ?CRITICAL_MSG("Erlang node name mismatch: I'm running in node [~ts], "
"but the mnesia database is owned by ~p", [MyNode, DbNodes]), "but the mnesia database is owned by ~p",
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg " [MyNode, DbNodes]),
"or change node name in Mnesia by running: " ?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
"ejabberdctl mnesia_change ~ts", [hd(DbNodes)]), "or change node name in Mnesia by running: "
{stop, node_name_mismatch} "ejabberdctl mnesia_change ~ts",
[hd(DbNodes)]),
{stop, node_name_mismatch}
end. end.
handle_call({create, Module, Name, TabDef}, _From, State) -> handle_call({create, Module, Name, TabDef}, _From, State) ->
case maps:get(Name, State#state.tables, undefined) of case maps:get(Name, State#state.tables, undefined) of
{TabDef, Result} -> {TabDef, Result} ->
{reply, Result, State}; {reply, Result, State};
_ -> _ ->
Result = do_create(Module, Name, TabDef, State#state.schema), Result = do_create(Module, Name, TabDef, State#state.schema),
Tables = maps:put(Name, {TabDef, Result}, State#state.tables), Tables = maps:put(Name, {TabDef, Result}, State#state.tables),
{reply, Result, State#state{tables = Tables}} {reply, Result, State#state{tables = Tables}}
end; end;
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
do_create(Module, Name, TabDef, TabDefs) -> do_create(Module, Name, TabDef, TabDefs) ->
code:ensure_loaded(Module), code:ensure_loaded(Module),
Schema = schema(Name, TabDef, TabDefs), Schema = schema(Name, TabDef, TabDefs),
{attributes, Attrs} = lists:keyfind(attributes, 1, Schema), {attributes, Attrs} = lists:keyfind(attributes, 1, Schema),
case catch mnesia:table_info(Name, attributes) of case catch mnesia:table_info(Name, attributes) of
{'EXIT', _} -> {'EXIT', _} ->
create(Name, TabDef); create(Name, TabDef);
Attrs -> Attrs ->
case need_reset(Name, Schema) of case need_reset(Name, Schema) of
true -> true ->
reset(Name, Schema); reset(Name, Schema);
false -> false ->
case update(Name, Attrs, Schema) of case update(Name, Attrs, Schema) of
{atomic, ok} -> {atomic, ok} ->
transform(Module, Name, Attrs, Attrs); transform(Module, Name, Attrs, Attrs);
Err -> Err ->
Err Err
end end
end; end;
OldAttrs -> OldAttrs ->
transform(Module, Name, OldAttrs, Attrs) transform(Module, Name, OldAttrs, Attrs)
end. end.
reset(Name, TabDef) -> reset(Name, TabDef) ->
?INFO_MSG("Deleting Mnesia table '~ts'", [Name]), ?INFO_MSG("Deleting Mnesia table '~ts'", [Name]),
mnesia_op(delete_table, [Name]), mnesia_op(delete_table, [Name]),
create(Name, TabDef). create(Name, TabDef).
update(Name, TabDef) -> update(Name, TabDef) ->
{attributes, Attrs} = lists:keyfind(attributes, 1, TabDef), {attributes, Attrs} = lists:keyfind(attributes, 1, TabDef),
update(Name, Attrs, TabDef). update(Name, Attrs, TabDef).
update(Name, Attrs, TabDef) -> update(Name, Attrs, TabDef) ->
case change_table_copy_type(Name, TabDef) of case change_table_copy_type(Name, TabDef) of
{atomic, ok} -> {atomic, ok} ->
CurrIndexes = [lists:nth(N-1, Attrs) || CurrIndexes = [ lists:nth(N - 1, Attrs)
N <- mnesia:table_info(Name, index)], || N <- mnesia:table_info(Name, index) ],
NewIndexes = proplists:get_value(index, TabDef, []), NewIndexes = proplists:get_value(index, TabDef, []),
case delete_indexes(Name, CurrIndexes -- NewIndexes) of case delete_indexes(Name, CurrIndexes -- NewIndexes) of
{atomic, ok} -> {atomic, ok} ->
add_indexes(Name, NewIndexes -- CurrIndexes); add_indexes(Name, NewIndexes -- CurrIndexes);
Err -> Err ->
Err Err
end; end;
Err -> Err ->
Err Err
end. end.
change_table_copy_type(Name, TabDef) -> change_table_copy_type(Name, TabDef) ->
CurrType = mnesia:table_info(Name, storage_type), CurrType = mnesia:table_info(Name, storage_type),
NewType = case lists:filter(fun is_storage_type_option/1, TabDef) of NewType = case lists:filter(fun is_storage_type_option/1, TabDef) of
[{Type, _}|_] -> Type; [{Type, _} | _] -> Type;
[] -> CurrType [] -> CurrType
end, end,
if NewType /= CurrType -> if
?INFO_MSG("Changing Mnesia table '~ts' from ~ts to ~ts", NewType /= CurrType ->
[Name, CurrType, NewType]), ?INFO_MSG("Changing Mnesia table '~ts' from ~ts to ~ts",
if CurrType == unknown -> mnesia_op(add_table_copy, [Name, node(), NewType]); [Name, CurrType, NewType]),
true -> if
mnesia_op(change_table_copy_type, [Name, node(), NewType]) CurrType == unknown -> mnesia_op(add_table_copy, [Name, node(), NewType]);
end; true ->
true -> mnesia_op(change_table_copy_type, [Name, node(), NewType])
{atomic, ok} end;
true ->
{atomic, ok}
end. end.
delete_indexes(Name, [Index|Indexes]) ->
delete_indexes(Name, [Index | Indexes]) ->
?INFO_MSG("Deleting index '~ts' from Mnesia table '~ts'", [Index, Name]), ?INFO_MSG("Deleting index '~ts' from Mnesia table '~ts'", [Index, Name]),
case mnesia_op(del_table_index, [Name, Index]) of case mnesia_op(del_table_index, [Name, Index]) of
{atomic, ok} -> {atomic, ok} ->
delete_indexes(Name, Indexes); delete_indexes(Name, Indexes);
Err -> Err ->
Err Err
end; end;
delete_indexes(_Name, []) -> delete_indexes(_Name, []) ->
{atomic, ok}. {atomic, ok}.
add_indexes(Name, [Index|Indexes]) ->
add_indexes(Name, [Index | Indexes]) ->
?INFO_MSG("Adding index '~ts' to Mnesia table '~ts'", [Index, Name]), ?INFO_MSG("Adding index '~ts' to Mnesia table '~ts'", [Index, Name]),
case mnesia_op(add_table_index, [Name, Index]) of case mnesia_op(add_table_index, [Name, Index]) of
{atomic, ok} -> {atomic, ok} ->
add_indexes(Name, Indexes); add_indexes(Name, Indexes);
Err -> Err ->
Err Err
end; end;
add_indexes(_Name, []) -> add_indexes(_Name, []) ->
{atomic, ok}. {atomic, ok}.
% %
% utilities % utilities
% %
schema(Name, Default, Schema) -> schema(Name, Default, Schema) ->
case lists:keyfind(Name, 1, Schema) of case lists:keyfind(Name, 1, Schema) of
{_, Custom} -> {_, Custom} ->
TabDefs = merge(Custom, Default), TabDefs = merge(Custom, Default),
?DEBUG("Using custom schema for table '~ts': ~p", ?DEBUG("Using custom schema for table '~ts': ~p",
[Name, TabDefs]), [Name, TabDefs]),
TabDefs; TabDefs;
false -> false ->
Default Default
end. end.
-spec read_schema_file() -> [{atom(), custom_schema()}]. -spec read_schema_file() -> [{atom(), custom_schema()}].
read_schema_file() -> read_schema_file() ->
File = schema_path(), File = schema_path(),
case fast_yaml:decode_from_file(File, [plain_as_atom]) of case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, Y} -> {ok, Y} ->
case econf:validate(validator(), lists:flatten(Y)) of case econf:validate(validator(), lists:flatten(Y)) of
{ok, []} -> {ok, []} ->
?WARNING_MSG("Mnesia schema file ~ts is empty", [File]), ?WARNING_MSG("Mnesia schema file ~ts is empty", [File]),
[]; [];
{ok, Config} -> {ok, Config} ->
lists:map( lists:map(
fun({Tab, Opts}) -> fun({Tab, Opts}) ->
{Tab, lists:map( {Tab,
fun({storage_type, T}) -> {T, [node()]}; lists:map(
(Other) -> Other fun({storage_type, T}) -> {T, [node()]};
end, Opts)} (Other) -> Other
end, Config); end,
{error, Reason, Ctx} -> Opts)}
?ERROR_MSG("Failed to read Mnesia schema from ~ts: ~ts", end,
[File, econf:format_error(Reason, Ctx)]), Config);
[] {error, Reason, Ctx} ->
end; ?ERROR_MSG("Failed to read Mnesia schema from ~ts: ~ts",
{error, enoent} -> [File, econf:format_error(Reason, Ctx)]),
?DEBUG("No custom Mnesia schema file found at ~ts", [File]), []
end;
{error, enoent} ->
?DEBUG("No custom Mnesia schema file found at ~ts", [File]),
[]; [];
{error, Reason} -> {error, Reason} ->
?ERROR_MSG("Failed to read Mnesia schema file ~ts: ~ts", ?ERROR_MSG("Failed to read Mnesia schema file ~ts: ~ts",
[File, fast_yaml:format_error(Reason)]) [File, fast_yaml:format_error(Reason)])
end. end.
-spec validator() -> econf:validator(). -spec validator() -> econf:validator().
validator() -> validator() ->
econf:map( econf:map(
econf:atom(), econf:atom(),
econf:options( econf:options(
#{storage_type => econf:enum([ram_copies, disc_copies, disc_only_copies]), #{
local_content => econf:bool(), storage_type => econf:enum([ram_copies, disc_copies, disc_only_copies]),
type => econf:enum([set, ordered_set, bag]), local_content => econf:bool(),
attributes => econf:list(econf:atom()), type => econf:enum([set, ordered_set, bag]),
index => econf:list(econf:atom())}, attributes => econf:list(econf:atom()),
[{return, orddict}, unique]), index => econf:list(econf:atom())
},
[{return, orddict}, unique]),
[unique]). [unique]).
create(Name, TabDef) -> create(Name, TabDef) ->
Type = lists:foldl( Type = lists:foldl(
fun({ram_copies, _}, _) -> " ram "; fun({ram_copies, _}, _) -> " ram ";
({disc_copies, _}, _) -> " disc "; ({disc_copies, _}, _) -> " disc ";
({disc_only_copies, _}, _) -> " disc_only "; ({disc_only_copies, _}, _) -> " disc_only ";
(_, Acc) -> Acc (_, Acc) -> Acc
end, " ", TabDef), end,
" ",
TabDef),
?INFO_MSG("Creating Mnesia~tstable '~ts'", [Type, Name]), ?INFO_MSG("Creating Mnesia~tstable '~ts'", [Type, Name]),
case mnesia_op(create_table, [Name, TabDef]) of case mnesia_op(create_table, [Name, TabDef]) of
{atomic, ok} -> {atomic, ok} ->
add_table_copy(Name); add_table_copy(Name);
Err -> Err ->
Err Err
end. end.
%% The table MUST exist, otherwise the function would fail %% The table MUST exist, otherwise the function would fail
add_table_copy(Name) -> add_table_copy(Name) ->
Type = mnesia:table_info(Name, storage_type), Type = mnesia:table_info(Name, storage_type),
Nodes = mnesia:table_info(Name, Type), Nodes = mnesia:table_info(Name, Type),
case lists:member(node(), Nodes) of case lists:member(node(), Nodes) of
true -> true ->
{atomic, ok}; {atomic, ok};
false -> false ->
mnesia_op(add_table_copy, [Name, node(), Type]) mnesia_op(add_table_copy, [Name, node(), Type])
end. end.
merge(Custom, Default) -> merge(Custom, Default) ->
NewDefault = case lists:any(fun is_storage_type_option/1, Custom) of NewDefault = case lists:any(fun is_storage_type_option/1, Custom) of
true -> true ->
lists:filter( lists:filter(
fun(O) -> fun(O) ->
not is_storage_type_option(O) not is_storage_type_option(O)
end, Default); end,
false -> Default);
Default false ->
end, Default
end,
lists:ukeymerge(1, Custom, lists:ukeysort(1, NewDefault)). lists:ukeymerge(1, Custom, lists:ukeysort(1, NewDefault)).
need_reset(Table, TabDef) -> need_reset(Table, TabDef) ->
ValuesF = [mnesia:table_info(Table, Key) || Key <- ?NEED_RESET], ValuesF = [ mnesia:table_info(Table, Key) || Key <- ?NEED_RESET ],
ValuesT = [proplists:get_value(Key, TabDef) || Key <- ?NEED_RESET], ValuesT = [ proplists:get_value(Key, TabDef) || Key <- ?NEED_RESET ],
lists:foldl( lists:foldl(
fun({Val, Val}, Acc) -> Acc; fun({Val, Val}, Acc) -> Acc;
({_, undefined}, Acc) -> Acc; ({_, undefined}, Acc) -> Acc;
({_, _}, _) -> true ({_, _}, _) -> true
end, false, lists:zip(ValuesF, ValuesT)). end,
false,
lists:zip(ValuesF, ValuesT)).
transform(Module, Name) -> transform(Module, Name) ->
try mnesia:table_info(Name, attributes) of try mnesia:table_info(Name, attributes) of
Attrs -> Attrs ->
transform(Module, Name, Attrs, Attrs) transform(Module, Name, Attrs, Attrs)
catch _:{aborted, _} = Err -> catch
Err _:{aborted, _} = Err ->
Err
end. end.
transform(Module, Name, NewAttrs) -> transform(Module, Name, NewAttrs) ->
try mnesia:table_info(Name, attributes) of try mnesia:table_info(Name, attributes) of
OldAttrs -> OldAttrs ->
transform(Module, Name, OldAttrs, NewAttrs) transform(Module, Name, OldAttrs, NewAttrs)
catch _:{aborted, _} = Err -> catch
Err _:{aborted, _} = Err ->
Err
end. end.
transform(Module, Name, Attrs, Attrs) -> transform(Module, Name, Attrs, Attrs) ->
case need_transform(Module, Name) of case need_transform(Module, Name) of
true -> true ->
?INFO_MSG("Transforming table '~ts', this may take a while", [Name]), ?INFO_MSG("Transforming table '~ts', this may take a while", [Name]),
transform_table(Module, Name); transform_table(Module, Name);
false -> false ->
{atomic, ok} {atomic, ok}
end; end;
transform(Module, Name, OldAttrs, NewAttrs) -> transform(Module, Name, OldAttrs, NewAttrs) ->
Fun = case erlang:function_exported(Module, transform, 1) of Fun = case erlang:function_exported(Module, transform, 1) of
true -> transform_fun(Module, Name); true -> transform_fun(Module, Name);
false -> fun(Old) -> do_transform(OldAttrs, NewAttrs, Old) end false -> fun(Old) -> do_transform(OldAttrs, NewAttrs, Old) end
end, end,
mnesia_op(transform_table, [Name, Fun, NewAttrs]). mnesia_op(transform_table, [Name, Fun, NewAttrs]).
-spec need_transform(module(), atom()) -> boolean(). -spec need_transform(module(), atom()) -> boolean().
need_transform(Module, Name) -> need_transform(Module, Name) ->
case erlang:function_exported(Module, need_transform, 1) of case erlang:function_exported(Module, need_transform, 1) of
true -> true ->
do_need_transform(Module, Name, mnesia:dirty_first(Name)); do_need_transform(Module, Name, mnesia:dirty_first(Name));
false -> false ->
false false
end. end.
do_need_transform(_Module, _Name, '$end_of_table') -> do_need_transform(_Module, _Name, '$end_of_table') ->
false; false;
do_need_transform(Module, Name, Key) -> do_need_transform(Module, Name, Key) ->
Objs = mnesia:dirty_read(Name, Key), Objs = mnesia:dirty_read(Name, Key),
case lists:foldl( case lists:foldl(
fun(_, true) -> true; fun(_, true) -> true;
(Obj, _) -> Module:need_transform(Obj) (Obj, _) -> Module:need_transform(Obj)
end, undefined, Objs) of end,
true -> true; undefined,
false -> false; Objs) of
_ -> true -> true;
do_need_transform(Module, Name, mnesia:dirty_next(Name, Key)) false -> false;
_ ->
do_need_transform(Module, Name, mnesia:dirty_next(Name, Key))
end. end.
do_transform(OldAttrs, Attrs, Old) -> do_transform(OldAttrs, Attrs, Old) ->
[Name|OldValues] = tuple_to_list(Old), [Name | OldValues] = tuple_to_list(Old),
Before = lists:zip(OldAttrs, OldValues), Before = lists:zip(OldAttrs, OldValues),
After = lists:foldl( After = lists:foldl(
fun(Attr, Acc) -> fun(Attr, Acc) ->
case lists:keyfind(Attr, 1, Before) of case lists:keyfind(Attr, 1, Before) of
false -> [{Attr, undefined}|Acc]; false -> [{Attr, undefined} | Acc];
Value -> [Value|Acc] Value -> [Value | Acc]
end end
end, [], lists:reverse(Attrs)), end,
[],
lists:reverse(Attrs)),
{Attrs, NewRecord} = lists:unzip(After), {Attrs, NewRecord} = lists:unzip(After),
list_to_tuple([Name|NewRecord]). list_to_tuple([Name | NewRecord]).
transform_fun(Module, Name) -> transform_fun(Module, Name) ->
fun(Obj) -> fun(Obj) ->
try Module:transform(Obj) try
catch ?EX_RULE(Class, Reason, St) -> Module:transform(Obj)
StackTrace = ?EX_STACK(St), catch
?ERROR_MSG("Failed to transform Mnesia table ~ts:~n" ?EX_RULE(Class, Reason, St) ->
"** Record: ~p~n" StackTrace = ?EX_STACK(St),
"** ~ts", ?ERROR_MSG("Failed to transform Mnesia table ~ts:~n"
[Name, Obj, "** Record: ~p~n"
misc:format_exception(2, Class, Reason, StackTrace)]), "** ~ts",
erlang:raise(Class, Reason, StackTrace) [Name,
end Obj,
misc:format_exception(2, Class, Reason, StackTrace)]),
erlang:raise(Class, Reason, StackTrace)
end
end. end.
transform_table(Module, Name) -> transform_table(Module, Name) ->
Type = mnesia:table_info(Name, type), Type = mnesia:table_info(Name, type),
Attrs = mnesia:table_info(Name, attributes), Attrs = mnesia:table_info(Name, attributes),
TmpTab = list_to_atom(atom_to_list(Name) ++ "_backup"), TmpTab = list_to_atom(atom_to_list(Name) ++ "_backup"),
StorageType = if Type == ordered_set -> disc_copies; StorageType = if
true -> disc_only_copies Type == ordered_set -> disc_copies;
end, true -> disc_only_copies
end,
mnesia:create_table(TmpTab, mnesia:create_table(TmpTab,
[{StorageType, [node()]}, [{StorageType, [node()]},
{type, Type}, {type, Type},
{local_content, true}, {local_content, true},
{record_name, Name}, {record_name, Name},
{attributes, Attrs}]), {attributes, Attrs}]),
mnesia:clear_table(TmpTab), mnesia:clear_table(TmpTab),
Fun = transform_fun(Module, Name), Fun = transform_fun(Module, Name),
Res = mnesia_op( Res = mnesia_op(
transaction, transaction,
[fun() -> do_transform_table(Name, Fun, TmpTab, mnesia:first(Name)) end]), [fun() -> do_transform_table(Name, Fun, TmpTab, mnesia:first(Name)) end]),
mnesia:delete_table(TmpTab), mnesia:delete_table(TmpTab),
Res. Res.
do_transform_table(Name, _Fun, TmpTab, '$end_of_table') -> do_transform_table(Name, _Fun, TmpTab, '$end_of_table') ->
mnesia:foldl( mnesia:foldl(
fun(Obj, _) -> fun(Obj, _) ->
mnesia:write(Name, Obj, write) mnesia:write(Name, Obj, write)
end, ok, TmpTab); end,
ok,
TmpTab);
do_transform_table(Name, Fun, TmpTab, Key) -> do_transform_table(Name, Fun, TmpTab, Key) ->
Next = mnesia:next(Name, Key), Next = mnesia:next(Name, Key),
Objs = mnesia:read(Name, Key), Objs = mnesia:read(Name, Key),
lists:foreach( lists:foreach(
fun(Obj) -> fun(Obj) ->
mnesia:write(TmpTab, Fun(Obj), write), mnesia:write(TmpTab, Fun(Obj), write),
mnesia:delete_object(Obj) mnesia:delete_object(Obj)
end, Objs), end,
Objs),
do_transform_table(Name, Fun, TmpTab, Next). do_transform_table(Name, Fun, TmpTab, Next).
mnesia_op(Fun, Args) -> mnesia_op(Fun, Args) ->
case apply(mnesia, Fun, Args) of case apply(mnesia, Fun, Args) of
{atomic, ok} -> {atomic, ok} ->
{atomic, ok}; {atomic, ok};
Other -> Other ->
?ERROR_MSG("Failure on mnesia ~ts ~p: ~p", ?ERROR_MSG("Failure on mnesia ~ts ~p: ~p",
[Fun, Args, Other]), [Fun, Args, Other]),
Other Other
end. end.
schema_path() -> schema_path() ->
Dir = case os:getenv("EJABBERD_MNESIA_SCHEMA") of Dir = case os:getenv("EJABBERD_MNESIA_SCHEMA") of
false -> mnesia:system_info(directory); false -> mnesia:system_info(directory);
Path -> Path Path -> Path
end, end,
filename:join(Dir, "ejabberd.schema"). filename:join(Dir, "ejabberd.schema").
is_storage_type_option({O, _}) -> is_storage_type_option({O, _}) ->
O == ram_copies orelse O == disc_copies orelse O == disc_only_copies. O == ram_copies orelse O == disc_copies orelse O == disc_only_copies.
dump_schema() -> dump_schema() ->
File = schema_path(), File = schema_path(),
Schema = lists:flatmap( Schema = lists:flatmap(
fun(schema) -> fun(schema) ->
[]; [];
(Tab) -> (Tab) ->
[{Tab, [{storage_type, [{Tab,
mnesia:table_info(Tab, storage_type)}, [{storage_type,
{local_content, mnesia:table_info(Tab, storage_type)},
mnesia:table_info(Tab, local_content)}]}] {local_content,
end, mnesia:system_info(tables)), mnesia:table_info(Tab, local_content)}]}]
end,
mnesia:system_info(tables)),
case file:write_file(File, [fast_yaml:encode(Schema), io_lib:nl()]) of case file:write_file(File, [fast_yaml:encode(Schema), io_lib:nl()]) of
ok -> ok ->
io:format("Mnesia schema is written to ~ts~n", [File]); io:format("Mnesia schema is written to ~ts~n", [File]);
{error, Reason} -> {error, Reason} ->
io:format("Failed to write Mnesia schema to ~ts: ~ts", io:format("Failed to write Mnesia schema to ~ts: ~ts",
[File, file:format_error(Reason)]) [File, file:format_error(Reason)])
end. end.

View file

@ -29,8 +29,12 @@
-behaviour(gen_server). -behaviour(gen_server).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, -export([init/1,
handle_info/2, terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([start_link/0, -export([start_link/0,
get_client_identity/2, get_client_identity/2,
@ -40,16 +44,16 @@
associate_access_code/3, associate_access_code/3,
associate_access_token/3, associate_access_token/3,
associate_refresh_token/3, associate_refresh_token/3,
check_token/1, check_token/1, check_token/4, check_token/2,
check_token/4,
check_token/2,
scope_in_scope_list/2, scope_in_scope_list/2,
process/2, process/2,
config_reloaded/0, config_reloaded/0,
verify_resowner_scope/3]). verify_resowner_scope/3]).
-export([get_commands_spec/0, -export([get_commands_spec/0,
oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_issue_token/3,
oauth_list_tokens/0,
oauth_revoke_token/1,
oauth_add_client_password/3, oauth_add_client_password/3,
oauth_add_client_implicit/3, oauth_add_client_implicit/3,
oauth_remove_client/1]). oauth_remove_client/1]).
@ -57,6 +61,7 @@
-export([web_menu_main/2, web_page_main/2]). -export([web_menu_main/2, web_page_main/2]).
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_http.hrl"). -include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl"). -include("ejabberd_web_admin.hrl").
@ -64,6 +69,7 @@
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-include("translate.hrl"). -include("translate.hrl").
-callback init() -> any(). -callback init() -> any().
-callback store(#oauth_token{}) -> ok | {error, any()}. -callback store(#oauth_token{}) -> ok | {error, any()}.
-callback lookup(binary()) -> {ok, #oauth_token{}} | error. -callback lookup(binary()) -> {ok, #oauth_token{}} | error.
@ -71,8 +77,8 @@
-callback clean(non_neg_integer()) -> any(). -callback clean(non_neg_integer()) -> any().
-record(oauth_ctx, { -record(oauth_ctx, {
password :: binary() | admin_generated, password :: binary() | admin_generated,
client :: #oauth_client{} | undefined client :: #oauth_client{} | undefined
}). }).
%% There are two ways to obtain an oauth token: %% There are two ways to obtain an oauth token:
@ -80,145 +86,178 @@
%% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin %% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin
%% (as it has access to ejabberd command line). %% (as it has access to ejabberd command line).
get_commands_spec() ->
[
#ejabberd_commands{name = oauth_issue_token, tags = [oauth],
desc = "Issue an OAuth token for the given jid",
module = ?MODULE, function = oauth_issue_token,
args = [{jid, string},{ttl, integer}, {scopes, string}],
policy = restricted,
args_example = ["user@server.com", 3600, "connected_users_number;muc_online_rooms"],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"List of scopes to allow, separated by ';'"],
result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}}
},
#ejabberd_commands{name = oauth_issue_token, tags = [oauth],
desc = "Issue an OAuth token for the given jid",
module = ?MODULE, function = oauth_issue_token,
version = 1,
note = "updated in 24.02",
args = [{jid, string}, {ttl, integer}, {scopes, {list, {scope, binary}}}],
policy = restricted,
args_example = ["user@server.com", 3600, ["connected_users_number", "muc_online_rooms"]],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"List of scopes to allow"],
result = {result, {tuple, [{token, string}, {scopes, {list, {scope, string}}}, {expires_in, string}]}}
},
#ejabberd_commands{name = oauth_list_tokens, tags = [oauth],
desc = "List OAuth tokens, user, scope, and seconds to expire (only Mnesia)",
longdesc = "List _`oauth.md|OAuth`_ tokens, their user and scope, and how many seconds remain until expiry",
module = ?MODULE, function = oauth_list_tokens,
args = [],
policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
},
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
desc = "Revoke authorization for an OAuth token",
note = "changed in 22.05",
module = ?MODULE, function = oauth_revoke_token,
args = [{token, binary}],
policy = restricted,
result = {res, restuple},
result_desc = "Result code"
},
#ejabberd_commands{name = oauth_add_client_password, tags = [oauth],
desc = "Add OAuth client_id with password grant type",
module = ?MODULE, function = oauth_add_client_password,
args = [{client_id, binary},
{client_name, binary},
{secret, binary}],
policy = restricted,
result = {res, restuple}
},
#ejabberd_commands{name = oauth_add_client_implicit, tags = [oauth],
desc = "Add OAuth client_id with implicit grant type",
module = ?MODULE, function = oauth_add_client_implicit,
args = [{client_id, binary},
{client_name, binary},
{redirect_uri, binary}],
policy = restricted,
result = {res, restuple}
},
#ejabberd_commands{name = oauth_remove_client, tags = [oauth],
desc = "Remove OAuth client_id",
module = ?MODULE, function = oauth_remove_client,
args = [{client_id, binary}],
policy = restricted,
result = {res, restuple}
}
].
oauth_issue_token(Jid, TTLSeconds, [Head|_] = ScopesString) when is_integer(Head) -> get_commands_spec() ->
Scopes = [list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";")], [#ejabberd_commands{
name = oauth_issue_token,
tags = [oauth],
desc = "Issue an OAuth token for the given jid",
module = ?MODULE,
function = oauth_issue_token,
args = [{jid, string}, {ttl, integer}, {scopes, string}],
policy = restricted,
args_example = ["user@server.com", 3600, "connected_users_number;muc_online_rooms"],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"List of scopes to allow, separated by ';'"],
result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}}
},
#ejabberd_commands{
name = oauth_issue_token,
tags = [oauth],
desc = "Issue an OAuth token for the given jid",
module = ?MODULE,
function = oauth_issue_token,
version = 1,
note = "updated in 24.02",
args = [{jid, string}, {ttl, integer}, {scopes, {list, {scope, binary}}}],
policy = restricted,
args_example = ["user@server.com", 3600, ["connected_users_number", "muc_online_rooms"]],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"List of scopes to allow"],
result = {result, {tuple, [{token, string}, {scopes, {list, {scope, string}}}, {expires_in, string}]}}
},
#ejabberd_commands{
name = oauth_list_tokens,
tags = [oauth],
desc = "List OAuth tokens, user, scope, and seconds to expire (only Mnesia)",
longdesc = "List _`oauth.md|OAuth`_ tokens, their user and scope, and how many seconds remain until expiry",
module = ?MODULE,
function = oauth_list_tokens,
args = [],
policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
},
#ejabberd_commands{
name = oauth_revoke_token,
tags = [oauth],
desc = "Revoke authorization for an OAuth token",
note = "changed in 22.05",
module = ?MODULE,
function = oauth_revoke_token,
args = [{token, binary}],
policy = restricted,
result = {res, restuple},
result_desc = "Result code"
},
#ejabberd_commands{
name = oauth_add_client_password,
tags = [oauth],
desc = "Add OAuth client_id with password grant type",
module = ?MODULE,
function = oauth_add_client_password,
args = [{client_id, binary},
{client_name, binary},
{secret, binary}],
policy = restricted,
result = {res, restuple}
},
#ejabberd_commands{
name = oauth_add_client_implicit,
tags = [oauth],
desc = "Add OAuth client_id with implicit grant type",
module = ?MODULE,
function = oauth_add_client_implicit,
args = [{client_id, binary},
{client_name, binary},
{redirect_uri, binary}],
policy = restricted,
result = {res, restuple}
},
#ejabberd_commands{
name = oauth_remove_client,
tags = [oauth],
desc = "Remove OAuth client_id",
module = ?MODULE,
function = oauth_remove_client,
args = [{client_id, binary}],
policy = restricted,
result = {res, restuple}
}].
oauth_issue_token(Jid, TTLSeconds, [Head | _] = ScopesString) when is_integer(Head) ->
Scopes = [ list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";") ],
oauth_issue_token(Jid, TTLSeconds, Scopes); oauth_issue_token(Jid, TTLSeconds, Scopes);
oauth_issue_token(Jid, TTLSeconds, Scopes) -> oauth_issue_token(Jid, TTLSeconds, Scopes) ->
try jid:decode(list_to_binary(Jid)) of try jid:decode(list_to_binary(Jid)) of
#jid{luser =Username, lserver = Server} -> #jid{luser = Username, lserver = Server} ->
Ctx1 = #oauth_ctx{password = admin_generated}, Ctx1 = #oauth_ctx{password = admin_generated},
case oauth2:authorize_password({Username, Server}, Scopes, Ctx1) of case oauth2:authorize_password({Username, Server}, Scopes, Ctx1) of
{ok, {_Ctx,Authorization}} -> {ok, {_Ctx, Authorization}} ->
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, [{expiry_time, TTLSeconds}]), {ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, [{expiry_time, TTLSeconds}]),
{ok, AccessToken} = oauth2_response:access_token(Response), {ok, AccessToken} = oauth2_response:access_token(Response),
{ok, VerifiedScope} = oauth2_response:scope(Response), {ok, VerifiedScope} = oauth2_response:scope(Response),
{AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"}; {AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"};
{error, Error} -> {error, Error} ->
{error, Error} {error, Error}
end end
catch _:{bad_jid, _} -> catch
_:{bad_jid, _} ->
{error, "Invalid JID: " ++ Jid} {error, "Invalid JID: " ++ Jid}
end. end.
oauth_list_tokens() -> oauth_list_tokens() ->
Tokens = mnesia:dirty_match_object(#oauth_token{_ = '_'}), Tokens = mnesia:dirty_match_object(#oauth_token{_ = '_'}),
{MegaSecs, Secs, _MiniSecs} = os:timestamp(), {MegaSecs, Secs, _MiniSecs} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs, TS = 1000000 * MegaSecs + Secs,
[{Token, jid:encode(jid:make(U,S)), Scope, integer_to_list(Expires - TS) ++ " seconds"} || [ {Token, jid:encode(jid:make(U, S)), Scope, integer_to_list(Expires - TS) ++ " seconds"}
#oauth_token{token=Token, scope=Scope, us= {U,S},expire=Expires} <- Tokens]. || #oauth_token{token = Token, scope = Scope, us = {U, S}, expire = Expires} <- Tokens ].
oauth_revoke_token(Token) -> oauth_revoke_token(Token) ->
DBMod = get_db_backend(), DBMod = get_db_backend(),
case DBMod:revoke(Token) of case DBMod:revoke(Token) of
ok -> ok ->
ets_cache:delete(oauth_cache, Token, ets_cache:delete(oauth_cache,
Token,
ejabberd_cluster:get_nodes()), ejabberd_cluster:get_nodes()),
{ok, ""}; {ok, ""};
Other -> Other ->
Other Other
end. end.
oauth_add_client_password(ClientID, ClientName, Secret) -> oauth_add_client_password(ClientID, ClientName, Secret) ->
DBMod = get_db_backend(), DBMod = get_db_backend(),
DBMod:store_client(#oauth_client{client_id = ClientID, DBMod:store_client(#oauth_client{
client_name = ClientName, client_id = ClientID,
grant_type = password, client_name = ClientName,
options = [{secret, Secret}]}), grant_type = password,
options = [{secret, Secret}]
}),
{ok, []}. {ok, []}.
oauth_add_client_implicit(ClientID, ClientName, RedirectURI) -> oauth_add_client_implicit(ClientID, ClientName, RedirectURI) ->
DBMod = get_db_backend(), DBMod = get_db_backend(),
DBMod:store_client(#oauth_client{client_id = ClientID, DBMod:store_client(#oauth_client{
client_name = ClientName, client_id = ClientID,
grant_type = implicit, client_name = ClientName,
options = [{redirect_uri, RedirectURI}]}), grant_type = implicit,
options = [{redirect_uri, RedirectURI}]
}),
{ok, []}. {ok, []}.
oauth_remove_client(Client) -> oauth_remove_client(Client) ->
DBMod = get_db_backend(), DBMod = get_db_backend(),
DBMod:remove_client(Client), DBMod:remove_client(Client),
{ok, []}. {ok, []}.
config_reloaded() -> config_reloaded() ->
DBMod = get_db_backend(), DBMod = get_db_backend(),
case init_cache(DBMod) of case init_cache(DBMod) of
true -> true ->
ets_cache:setopts(oauth_cache, cache_opts()); ets_cache:setopts(oauth_cache, cache_opts());
false -> false ->
ok ok
end. end.
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@ -238,38 +277,46 @@ init([]) ->
erlang:send_after(expire(), self(), clean), erlang:send_after(expire(), self(), clean),
{ok, ok}. {ok, ok}.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(clean, State) -> handle_info(clean, State) ->
{MegaSecs, Secs, MiniSecs} = os:timestamp(), {MegaSecs, Secs, MiniSecs} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs, TS = 1000000 * MegaSecs + Secs,
DBMod = get_db_backend(), DBMod = get_db_backend(),
DBMod:clean(TS), DBMod:clean(TS),
erlang:send_after(trunc(expire() * (1 + MiniSecs / 1000000)), erlang:send_after(trunc(expire() * (1 + MiniSecs / 1000000)),
self(), clean), self(),
clean),
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50), ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50), ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50). ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50).
code_change(_OldVsn, State, _Extra) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}.
get_client_identity(<<"">>, Ctx) -> get_client_identity(<<"">>, Ctx) ->
{ok, {Ctx, {client, unknown_client}}}; {ok, {Ctx, {client, unknown_client}}};
get_client_identity(ClientID, Ctx) when is_binary(ClientID) -> get_client_identity(ClientID, Ctx) when is_binary(ClientID) ->
{ok, {Ctx, {client, ClientID}}}. {ok, {Ctx, {client, ClientID}}}.
verify_redirection_uri(_ClientID, RedirectURI, Ctx) -> verify_redirection_uri(_ClientID, RedirectURI, Ctx) ->
case Ctx of case Ctx of
#oauth_ctx{client = #oauth_client{grant_type = implicit} = Client} -> #oauth_ctx{client = #oauth_client{grant_type = implicit} = Client} ->
@ -285,6 +332,7 @@ verify_redirection_uri(_ClientID, RedirectURI, Ctx) ->
{ok, Ctx} {ok, Ctx}
end. end.
authenticate_user({User, Server}, Ctx) -> authenticate_user({User, Server}, Ctx) ->
case jid:make(User, Server) of case jid:make(User, Server) of
#jid{} = JID -> #jid{} = JID ->
@ -296,7 +344,7 @@ authenticate_user({User, Server}, Ctx) ->
#oauth_ctx{password = admin_generated} -> #oauth_ctx{password = admin_generated} ->
{ok, {Ctx, {user, User, Server}}}; {ok, {Ctx, {user, User, Server}}};
#oauth_ctx{password = Password} #oauth_ctx{password = Password}
when is_binary(Password) -> when is_binary(Password) ->
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true -> true ->
{ok, {Ctx, {user, User, Server}}}; {ok, {Ctx, {user, User, Server}}};
@ -311,6 +359,7 @@ authenticate_user({User, Server}, Ctx) ->
{error, badpass} {error, badpass}
end. end.
authenticate_client(ClientID, Ctx) -> authenticate_client(ClientID, Ctx) ->
case ejabberd_option:oauth_client_id_check() of case ejabberd_option:oauth_client_id_check() of
allow -> allow ->
@ -326,10 +375,11 @@ authenticate_client(ClientID, Ctx) ->
end end
end. end.
-spec verify_resowner_scope({user, binary(), binary()}, [binary()], any()) -> -spec verify_resowner_scope({user, binary(), binary()}, [binary()], any()) ->
{ok, any(), [binary()]} | {error, any()}. {ok, any(), [binary()]} | {error, any()}.
verify_resowner_scope({user, _User, _Server}, Scope, Ctx) -> verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
Cmds = [atom_to_binary(Name, utf8) || {Name, _, _} <- ejabberd_commands:list_commands()], Cmds = [ atom_to_binary(Name, utf8) || {Name, _, _} <- ejabberd_commands:list_commands() ],
AllowedScopes = [<<"ejabberd:user">>, <<"ejabberd:admin">>, <<"sasl_auth">>] ++ Cmds, AllowedScopes = [<<"ejabberd:user">>, <<"ejabberd:admin">>, <<"sasl_auth">>] ++ Cmds,
case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope), case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope),
oauth2_priv_set:new(AllowedScopes)) of oauth2_priv_set:new(AllowedScopes)) of
@ -341,6 +391,7 @@ verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
verify_resowner_scope(_, _, _) -> verify_resowner_scope(_, _, _) ->
{error, badscope}. {error, badscope}.
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are %% This is callback for oauth tokens generated through the command line. Only open and admin commands are
%% made available. %% made available.
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) -> %verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
@ -354,8 +405,6 @@ verify_resowner_scope(_, _, _) ->
% end. % end.
-spec seconds_since_epoch(integer()) -> non_neg_integer(). -spec seconds_since_epoch(integer()) -> non_neg_integer().
seconds_since_epoch(Diff) -> seconds_since_epoch(Diff) ->
{Mega, Secs, _} = os:timestamp(), {Mega, Secs, _} = os:timestamp(),
@ -366,45 +415,52 @@ associate_access_code(_AccessCode, _Context, AppContext) ->
%put(?ACCESS_CODE_TABLE, AccessCode, Context), %put(?ACCESS_CODE_TABLE, AccessCode, Context),
{ok, AppContext}. {ok, AppContext}.
associate_access_token(AccessToken, Context, AppContext) -> associate_access_token(AccessToken, Context, AppContext) ->
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>), {user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
Expire = case proplists:get_value(expiry_time, AppContext, undefined) of Expire = case proplists:get_value(expiry_time, AppContext, undefined) of
undefined -> undefined ->
proplists:get_value(<<"expiry_time">>, Context, 0); proplists:get_value(<<"expiry_time">>, Context, 0);
ExpiresIn -> ExpiresIn ->
%% There is no clean way in oauth2 lib to actually override the TTL of the generated token. %% There is no clean way in oauth2 lib to actually override the TTL of the generated token.
%% It always pass the global configured value. Here we use the app context to pass the per-case %% It always pass the global configured value. Here we use the app context to pass the per-case
%% ttl if we want to override it. %% ttl if we want to override it.
seconds_since_epoch(ExpiresIn) seconds_since_epoch(ExpiresIn)
end, end,
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>), {user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
Scope = proplists:get_value(<<"scope">>, Context, []), Scope = proplists:get_value(<<"scope">>, Context, []),
R = #oauth_token{ R = #oauth_token{
token = AccessToken, token = AccessToken,
us = {jid:nodeprep(User), jid:nodeprep(Server)}, us = {jid:nodeprep(User), jid:nodeprep(Server)},
scope = Scope, scope = Scope,
expire = Expire expire = Expire
}, },
store(R), store(R),
{ok, AppContext}. {ok, AppContext}.
associate_refresh_token(_RefreshToken, _Context, AppContext) -> associate_refresh_token(_RefreshToken, _Context, AppContext) ->
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context), %put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
{ok, AppContext}. {ok, AppContext}.
scope_in_scope_list(Scope, ScopeList) -> scope_in_scope_list(Scope, ScopeList) ->
TokenScopeSet = oauth2_priv_set:new(Scope), TokenScopeSet = oauth2_priv_set:new(Scope),
lists:any(fun(Scope2) -> lists:any(fun(Scope2) ->
oauth2_priv_set:is_member(Scope2, TokenScopeSet) end, oauth2_priv_set:is_member(Scope2, TokenScopeSet)
end,
ScopeList). ScopeList).
-spec check_token(binary()) -> {ok, {binary(), binary()}, [binary()]} | -spec check_token(binary()) -> {ok, {binary(), binary()}, [binary()]} |
{false, expired | not_found}. {false, expired | not_found}.
check_token(Token) -> check_token(Token) ->
case lookup(Token) of case lookup(Token) of
{ok, #oauth_token{us = US, {ok, #oauth_token{
scope = TokenScope, us = US,
expire = Expire}} -> scope = TokenScope,
expire = Expire
}} ->
{MegaSecs, Secs, _} = os:timestamp(), {MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs, TS = 1000000 * MegaSecs + Secs,
if if
@ -417,20 +473,24 @@ check_token(Token) ->
{false, not_found} {false, not_found}
end. end.
check_token(User, Server, ScopeList, Token) -> check_token(User, Server, ScopeList, Token) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
case lookup(Token) of case lookup(Token) of
{ok, #oauth_token{us = {LUser, LServer}, {ok, #oauth_token{
scope = TokenScope, us = {LUser, LServer},
expire = Expire}} -> scope = TokenScope,
expire = Expire
}} ->
{MegaSecs, Secs, _} = os:timestamp(), {MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs, TS = 1000000 * MegaSecs + Secs,
if if
Expire > TS -> Expire > TS ->
TokenScopeSet = oauth2_priv_set:new(TokenScope), TokenScopeSet = oauth2_priv_set:new(TokenScope),
lists:any(fun(Scope) -> lists:any(fun(Scope) ->
oauth2_priv_set:is_member(Scope, TokenScopeSet) end, oauth2_priv_set:is_member(Scope, TokenScopeSet)
end,
ScopeList); ScopeList);
true -> true ->
{false, expired} {false, expired}
@ -439,22 +499,26 @@ check_token(User, Server, ScopeList, Token) ->
{false, not_found} {false, not_found}
end. end.
check_token(ScopeList, Token) -> check_token(ScopeList, Token) ->
case lookup(Token) of case lookup(Token) of
{ok, #oauth_token{us = US, {ok, #oauth_token{
scope = TokenScope, us = US,
expire = Expire}} -> scope = TokenScope,
expire = Expire
}} ->
{MegaSecs, Secs, _} = os:timestamp(), {MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs, TS = 1000000 * MegaSecs + Secs,
if if
Expire > TS -> Expire > TS ->
TokenScopeSet = oauth2_priv_set:new(TokenScope), TokenScopeSet = oauth2_priv_set:new(TokenScope),
case lists:any(fun(Scope) -> case lists:any(fun(Scope) ->
oauth2_priv_set:is_member(Scope, TokenScopeSet) end, oauth2_priv_set:is_member(Scope, TokenScopeSet)
end,
ScopeList) of ScopeList) of
true -> {ok, user, US}; true -> {ok, user, US};
false -> {false, no_matching_scope} false -> {false, no_matching_scope}
end; end;
true -> true ->
{false, expired} {false, expired}
end; end;
@ -466,64 +530,79 @@ check_token(ScopeList, Token) ->
store(R) -> store(R) ->
DBMod = get_db_backend(), DBMod = get_db_backend(),
case DBMod:store(R) of case DBMod:store(R) of
ok -> ok ->
ets_cache:delete(oauth_cache, R#oauth_token.token, ets_cache:delete(oauth_cache,
ejabberd_cluster:get_nodes()); R#oauth_token.token,
{error, _} = Err -> ejabberd_cluster:get_nodes());
Err {error, _} = Err ->
Err
end. end.
lookup(Token) -> lookup(Token) ->
ets_cache:lookup(oauth_cache, Token, ets_cache:lookup(oauth_cache,
fun() -> Token,
DBMod = get_db_backend(), fun() ->
DBMod:lookup(Token) DBMod = get_db_backend(),
end). DBMod:lookup(Token)
end).
-spec init_cache(module()) -> boolean(). -spec init_cache(module()) -> boolean().
init_cache(DBMod) -> init_cache(DBMod) ->
UseCache = use_cache(DBMod), UseCache = use_cache(DBMod),
case UseCache of case UseCache of
true -> true ->
ets_cache:new(oauth_cache, cache_opts()); ets_cache:new(oauth_cache, cache_opts());
false -> false ->
ets_cache:delete(oauth_cache) ets_cache:delete(oauth_cache)
end, end,
UseCache. UseCache.
use_cache(DBMod) -> use_cache(DBMod) ->
case erlang:function_exported(DBMod, use_cache, 0) of case erlang:function_exported(DBMod, use_cache, 0) of
true -> DBMod:use_cache(); true -> DBMod:use_cache();
false -> ejabberd_option:oauth_use_cache() false -> ejabberd_option:oauth_use_cache()
end. end.
cache_opts() -> cache_opts() ->
MaxSize = ejabberd_option:oauth_cache_size(), MaxSize = ejabberd_option:oauth_cache_size(),
CacheMissed = ejabberd_option:oauth_cache_missed(), CacheMissed = ejabberd_option:oauth_cache_missed(),
LifeTime = ejabberd_option:oauth_cache_life_time(), LifeTime = ejabberd_option:oauth_cache_life_time(),
[{max_size, MaxSize}, {life_time, LifeTime}, {cache_missed, CacheMissed}]. [{max_size, MaxSize}, {life_time, LifeTime}, {cache_missed, CacheMissed}].
expire() -> expire() ->
ejabberd_option:oauth_expire(). ejabberd_option:oauth_expire().
-define(DIV(Class, Els), -define(DIV(Class, Els),
?XAE(<<"div">>, [{<<"class">>, Class}], Els)). ?XAE(<<"div">>, [{<<"class">>, Class}], Els)).
-define(INPUTID(Type, Name, Value), -define(INPUTID(Type, Name, Value),
?XA(<<"input">>, ?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name}, [{<<"type">>, Type},
{<<"value">>, Value}, {<<"id">>, Name}])). {<<"name">>, Name},
{<<"value">>, Value},
{<<"id">>, Name}])).
-define(LABEL(ID, Els), -define(LABEL(ID, Els),
?XAE(<<"label">>, [{<<"for">>, ID}], Els)). ?XAE(<<"label">>, [{<<"for">>, ID}], Els)).
process(_Handlers, process(_Handlers,
#request{method = 'GET', q = Q, lang = Lang, #request{
path = [_, <<"authorization_token">>]}) -> method = 'GET',
q = Q,
lang = Lang,
path = [_, <<"authorization_token">>]
}) ->
ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>), ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>), ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
JidEls = case proplists:get_value(<<"jid">>, Q, <<"">>) of JidEls = case proplists:get_value(<<"jid">>, Q, <<"">>) of
<<"">> -> [?INPUTID(<<"email">>, <<"username">>, <<"">>)]; <<"">> -> [?INPUTID(<<"email">>, <<"username">>, <<"">>)];
Jid -> [?C(Jid), ?INPUT(<<"hidden">>, <<"username">>, Jid)] Jid -> [?C(Jid), ?INPUT(<<"hidden">>, <<"username">>, Jid)]
end, end,
RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>), RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
Scope = proplists:get_value(<<"scope">>, Q, <<"">>), Scope = proplists:get_value(<<"scope">>, Q, <<"">>),
State = proplists:get_value(<<"state">>, Q, <<"">>), State = proplists:get_value(<<"state">>, Q, <<"">>),
@ -531,36 +610,32 @@ process(_Handlers,
?XAE(<<"form">>, ?XAE(<<"form">>,
[{<<"action">>, <<"authorization_token">>}, [{<<"action">>, <<"authorization_token">>},
{<<"method">>, <<"post">>}], {<<"method">>, <<"post">>}],
[?LABEL(<<"username">>, [?CT(?T("User (jid)")), ?C(<<": ">>)]) [?LABEL(<<"username">>, [?CT(?T("User (jid)")), ?C(<<": ">>)])] ++ JidEls ++ [?BR,
] ++ JidEls ++ [ ?LABEL(<<"password">>, [?CT(?T("Password")), ?C(<<": ">>)]),
?BR, ?INPUTID(<<"password">>, <<"password">>, <<"">>),
?LABEL(<<"password">>, [?CT(?T("Password")), ?C(<<": ">>)]), ?INPUT(<<"hidden">>, <<"response_type">>, ResponseType),
?INPUTID(<<"password">>, <<"password">>, <<"">>), ?INPUT(<<"hidden">>, <<"client_id">>, ClientId),
?INPUT(<<"hidden">>, <<"response_type">>, ResponseType), ?INPUT(<<"hidden">>, <<"redirect_uri">>, RedirectURI),
?INPUT(<<"hidden">>, <<"client_id">>, ClientId), ?INPUT(<<"hidden">>, <<"scope">>, Scope),
?INPUT(<<"hidden">>, <<"redirect_uri">>, RedirectURI), ?INPUT(<<"hidden">>, <<"state">>, State),
?INPUT(<<"hidden">>, <<"scope">>, Scope), ?BR,
?INPUT(<<"hidden">>, <<"state">>, State), ?LABEL(<<"ttl">>, [?CT(?T("Token TTL")), ?C(<<": ">>)]),
?BR, ?XAE(<<"select">>,
?LABEL(<<"ttl">>, [?CT(?T("Token TTL")), ?C(<<": ">>)]), [{<<"name">>, <<"ttl">>}],
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}], [?XAC(<<"option">>, [{<<"value">>, <<"3600">>}], <<"1 Hour">>),
[ ?XAC(<<"option">>, [{<<"value">>, <<"86400">>}], <<"1 Day">>),
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>), ?XAC(<<"option">>, [{<<"value">>, <<"2592000">>}], <<"1 Month">>),
?XAC(<<"option">>, [{<<"value">>, <<"86400">>}],<<"1 Day">>), ?XAC(<<"option">>, [{<<"selected">>, <<"selected">>}, {<<"value">>, <<"31536000">>}], <<"1 Year">>),
?XAC(<<"option">>, [{<<"value">>, <<"2592000">>}],<<"1 Month">>), ?XAC(<<"option">>, [{<<"value">>, <<"315360000">>}], <<"10 Years">>)]),
?XAC(<<"option">>, [{<<"selected">>, <<"selected">>},{<<"value">>, <<"31536000">>}],<<"1 Year">>), ?BR,
?XAC(<<"option">>, [{<<"value">>, <<"315360000">>}],<<"10 Years">>)]), ?INPUTT(<<"submit">>, <<"">>, ?T("Accept"))]),
?BR,
?INPUTT(<<"submit">>, <<"">>, ?T("Accept"))
]),
Top = Top =
?DIV(<<"section">>, ?DIV(<<"section">>,
[?DIV(<<"block">>, [?DIV(<<"block">>,
[?A(<<"https://www.ejabberd.im">>, [?A(<<"https://www.ejabberd.im">>,
[?XA(<<"img">>, [?XA(<<"img">>,
[{<<"height">>, <<"32">>}, [{<<"height">>, <<"32">>},
{<<"src">>, logo()}])] {<<"src">>, logo()}])])])]),
)])]),
Middle = Middle =
?DIV(<<"white section">>, ?DIV(<<"white section">>,
[?DIV(<<"block">>, [?DIV(<<"block">>,
@ -570,8 +645,7 @@ process(_Handlers,
?XC(<<"em">>, ClientId), ?XC(<<"em">>, ClientId),
?C(<<" wants to access scope ">>), ?C(<<" wants to access scope ">>),
?XC(<<"em">>, Scope)]), ?XC(<<"em">>, Scope)]),
Form Form])]),
])]),
Bottom = Bottom =
?DIV(<<"section">>, ?DIV(<<"section">>,
[?DIV(<<"block">>, [?DIV(<<"block">>,
@ -583,13 +657,16 @@ process(_Handlers,
?XAC(<<"a">>, ?XAC(<<"a">>,
[{<<"href">>, <<"https://www.process-one.net">>}, [{<<"href">>, <<"https://www.process-one.net">>},
{<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}], {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
<<"ProcessOne">>) <<"ProcessOne">>)])]),
])]),
Body = ?DIV(<<"container">>, [Top, Middle, Bottom]), Body = ?DIV(<<"container">>, [Top, Middle, Bottom]),
ejabberd_web:make_xhtml(web_head(), [Body]); ejabberd_web:make_xhtml(web_head(), [Body]);
process(_Handlers, process(_Handlers,
#request{method = 'POST', q = Q, lang = _Lang, #request{
path = [_, <<"authorization_token">>]}) -> method = 'POST',
q = Q,
lang = _Lang,
path = [_, <<"authorization_token">>]
}) ->
_ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>), _ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>), ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>), RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
@ -612,7 +689,7 @@ process(_Handlers,
#oauth_ctx{password = Password}) of #oauth_ctx{password = Password}) of
{ok, {_AppContext, Authorization}} -> {ok, {_AppContext, Authorization}} ->
{ok, {_AppContext2, Response}} = {ok, {_AppContext2, Response}} =
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined]), oauth2:issue_token(Authorization, [ {expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
{ok, AccessToken} = oauth2_response:access_token(Response), {ok, AccessToken} = oauth2_response:access_token(Response),
{ok, Type} = oauth2_response:token_type(Response), {ok, Type} = oauth2_response:token_type(Response),
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have %%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
@ -633,38 +710,50 @@ process(_Handlers,
% VerifiedScope, % VerifiedScope,
% State, % State,
% Context); % Context);
{302, [{<<"Location">>, {302,
<<RedirectURI/binary, [{<<"Location">>,
"?access_token=", AccessToken/binary, <<RedirectURI/binary,
"&token_type=", Type/binary, "?access_token=",
"&expires_in=", (integer_to_binary(Expires))/binary, AccessToken/binary,
"&scope=", (str:join(VerifiedScope, <<" ">>))/binary, "&token_type=",
"&state=", State/binary>> Type/binary,
}], "&expires_in=",
(integer_to_binary(Expires))/binary,
"&scope=",
(str:join(VerifiedScope, <<" ">>))/binary,
"&state=",
State/binary>>}],
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}; ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])};
{error, Error} when is_atom(Error) -> {error, Error} when is_atom(Error) ->
%oauth2_wrq:redirected_error_response( %oauth2_wrq:redirected_error_response(
% ReqData, RedirectURI, Error, State, Context) % ReqData, RedirectURI, Error, State, Context)
{302, [{<<"Location">>, {302,
<<RedirectURI/binary, [{<<"Location">>,
"?error=", (atom_to_binary(Error, utf8))/binary, <<RedirectURI/binary,
"&state=", State/binary>> "?error=",
}], (atom_to_binary(Error, utf8))/binary,
"&state=",
State/binary>>}],
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])} ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
end end
catch _:{bad_jid, _} -> catch
State = proplists:get_value(<<"state">>, Q, <<"">>), _:{bad_jid, _} ->
{400, [{<<"Location">>, State = proplists:get_value(<<"state">>, Q, <<"">>),
<<RedirectURI/binary, {400,
"?error=invalid_request", [{<<"Location">>,
"&state=", State/binary>> <<RedirectURI/binary,
}], "?error=invalid_request",
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"400 Invalid request">>)])} "&state=", State/binary>>}],
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"400 Invalid request">>)])}
end; end;
process(_Handlers, process(_Handlers,
#request{method = 'POST', q = Q, lang = _Lang, #request{
auth = HTTPAuth, method = 'POST',
path = [_, <<"token">>]}) -> q = Q,
lang = _Lang,
auth = HTTPAuth,
path = [_, <<"token">>]
}) ->
Access = Access =
case ejabberd_option:oauth_client_id_check() of case ejabberd_option:oauth_client_id_check() of
allow -> allow ->
@ -694,8 +783,7 @@ process(_Handlers,
case get_client_secret(Client) of case get_client_secret(Client) of
Secret -> Secret ->
case proplists:get_value(<<"grant_type">>, Q, <<"">>) of case proplists:get_value(<<"grant_type">>, Q, <<"">>) of
<<"password">> when <<"password">> when Client#oauth_client.grant_type == password ->
Client#oauth_client.grant_type == password ->
password; password;
_ -> _ ->
unsupported_grant_type unsupported_grant_type
@ -725,7 +813,7 @@ process(_Handlers,
#oauth_ctx{password = Password}) of #oauth_ctx{password = Password}) of
{ok, {_AppContext, Authorization}} -> {ok, {_AppContext, Authorization}} ->
{ok, {_AppContext2, Response}} = {ok, {_AppContext2, Response}} =
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined]), oauth2:issue_token(Authorization, [ {expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
{ok, AccessToken} = oauth2_response:access_token(Response), {ok, AccessToken} = oauth2_response:access_token(Response),
{ok, Type} = oauth2_response:token_type(Response), {ok, Type} = oauth2_response:token_type(Response),
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have %%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
@ -738,18 +826,23 @@ process(_Handlers,
ExpiresIn ExpiresIn
end, end,
{ok, VerifiedScope} = oauth2_response:scope(Response), {ok, VerifiedScope} = oauth2_response:scope(Response),
json_response(200, #{<<"access_token">> => AccessToken, json_response(200,
<<"token_type">> => Type, #{
<<"scope">> => str:join(VerifiedScope, <<" ">>), <<"access_token">> => AccessToken,
<<"expires_in">> => Expires}); <<"token_type">> => Type,
<<"scope">> => str:join(VerifiedScope, <<" ">>),
<<"expires_in">> => Expires
});
{error, Error} when is_atom(Error) -> {error, Error} when is_atom(Error) ->
json_error(400, <<"invalid_grant">>, Error) json_error(400, <<"invalid_grant">>, Error)
end end
catch _:{bad_jid, _} -> catch
json_error(400, <<"invalid_request">>, invalid_jid) _:{bad_jid, _} ->
json_error(400, <<"invalid_request">>, invalid_jid)
end; end;
unsupported_grant_type -> unsupported_grant_type ->
json_error(400, <<"unsupported_grant_type">>, json_error(400,
<<"unsupported_grant_type">>,
unsupported_grant_type); unsupported_grant_type);
deny -> deny ->
ejabberd_web:error(not_allowed) ejabberd_web:error(not_allowed)
@ -758,73 +851,89 @@ process(_Handlers,
process(_Handlers, _Request) -> process(_Handlers, _Request) ->
ejabberd_web:error(not_found). ejabberd_web:error(not_found).
-spec get_db_backend() -> module(). -spec get_db_backend() -> module().
get_db_backend() -> get_db_backend() ->
DBType = ejabberd_option:oauth_db_type(), DBType = ejabberd_option:oauth_db_type(),
list_to_existing_atom("ejabberd_oauth_" ++ atom_to_list(DBType)). list_to_existing_atom("ejabberd_oauth_" ++ atom_to_list(DBType)).
get_client_secret(#oauth_client{grant_type = password, options = Options}) -> get_client_secret(#oauth_client{grant_type = password, options = Options}) ->
proplists:get_value(secret, Options, false). proplists:get_value(secret, Options, false).
get_redirect_uri(#oauth_client{grant_type = implicit, options = Options}) -> get_redirect_uri(#oauth_client{grant_type = implicit, options = Options}) ->
proplists:get_value(redirect_uri, Options, false). proplists:get_value(redirect_uri, Options, false).
%% Headers as per RFC 6749 %% Headers as per RFC 6749
json_response(Code, Body) -> json_response(Code, Body) ->
{Code, [{<<"Content-Type">>, <<"application/json;charset=UTF-8">>}, {Code,
{<<"Cache-Control">>, <<"no-store">>}, [{<<"Content-Type">>, <<"application/json;charset=UTF-8">>},
{<<"Pragma">>, <<"no-cache">>}], {<<"Cache-Control">>, <<"no-store">>},
{<<"Pragma">>, <<"no-cache">>}],
misc:json_encode(Body)}. misc:json_encode(Body)}.
%% OAauth error are defined in: %% OAauth error are defined in:
%% https://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-5.2 %% https://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-5.2
json_error(Code, Error, Reason) -> json_error(Code, Error, Reason) ->
Desc = json_error_desc(Reason), Desc = json_error_desc(Reason),
Body = #{<<"error">> => Error, Body = #{
<<"error_description">> => Desc}, <<"error">> => Error,
<<"error_description">> => Desc
},
json_response(Code, Body). json_response(Code, Body).
json_error_desc(access_denied) -> <<"Access denied">>;
json_error_desc(badpass) -> <<"Bad password">>; json_error_desc(access_denied) -> <<"Access denied">>;
json_error_desc(badpass) -> <<"Bad password">>;
json_error_desc(unsupported_grant_type) -> <<"Unsupported grant type">>; json_error_desc(unsupported_grant_type) -> <<"Unsupported grant type">>;
json_error_desc(invalid_scope) -> <<"Invalid scope">>; json_error_desc(invalid_scope) -> <<"Invalid scope">>;
json_error_desc(invalid_jid) -> <<"Invalid JID">>. json_error_desc(invalid_jid) -> <<"Invalid JID">>.
web_head() -> web_head() ->
[?XA(<<"meta">>, [{<<"http-equiv">>, <<"X-UA-Compatible">>}, [?XA(<<"meta">>,
{<<"content">>, <<"IE=edge">>}]), [{<<"http-equiv">>, <<"X-UA-Compatible">>},
?XA(<<"meta">>, [{<<"name">>, <<"viewport">>}, {<<"content">>, <<"IE=edge">>}]),
{<<"content">>, ?XA(<<"meta">>,
<<"width=device-width, initial-scale=1">>}]), [{<<"name">>, <<"viewport">>},
{<<"content">>,
<<"width=device-width, initial-scale=1">>}]),
?XC(<<"title">>, <<"Authorization request">>), ?XC(<<"title">>, <<"Authorization request">>),
?XC(<<"style">>, css()) ?XC(<<"style">>, css())].
].
css() -> css() ->
case misc:read_css("oauth.css") of case misc:read_css("oauth.css") of
{ok, Data} -> Data; {ok, Data} -> Data;
{error, _} -> <<>> {error, _} -> <<>>
end. end.
logo() -> logo() ->
case misc:read_img("oauth-logo.png") of case misc:read_img("oauth-logo.png") of
{ok, Img} -> {ok, Img} ->
B64Img = base64:encode(Img), B64Img = base64:encode(Img),
<<"data:image/png;base64,", B64Img/binary>>; <<"data:image/png;base64,", B64Img/binary>>;
{error, _} -> {error, _} ->
<<>> <<>>
end. end.
%%% %%%
%%% WebAdmin %%% WebAdmin
%%% %%%
%% @format-begin %% @format-begin
web_menu_main(Acc, _Lang) -> web_menu_main(Acc, _Lang) ->
Acc ++ [{<<"oauth">>, <<"OAuth">>}]. Acc ++ [{<<"oauth">>, <<"OAuth">>}].
web_page_main(_, #request{path = [<<"oauth">>]} = R) -> web_page_main(_, #request{path = [<<"oauth">>]} = R) ->
Head = ?H1GLraw(<<"OAuth">>, <<"developer/ejabberd-api/oauth/">>, <<"OAuth">>), Head = ?H1GLraw(<<"OAuth">>, <<"developer/ejabberd-api/oauth/">>, <<"OAuth">>),
Set = [?X(<<"hr">>), Set = [?X(<<"hr">>),

View file

@ -28,38 +28,45 @@
-behaviour(ejabberd_oauth). -behaviour(ejabberd_oauth).
-export([init/0, -export([init/0,
store/1, store/1,
lookup/1, lookup/1,
clean/1, clean/1,
lookup_client/1, lookup_client/1,
store_client/1, store_client/1,
remove_client/1, remove_client/1,
use_cache/0, revoke/1]). use_cache/0,
revoke/1]).
-include("ejabberd_oauth.hrl"). -include("ejabberd_oauth.hrl").
init() -> init() ->
ejabberd_mnesia:create(?MODULE, oauth_token, ejabberd_mnesia:create(?MODULE,
[{disc_only_copies, [node()]}, oauth_token,
{attributes, [{disc_only_copies, [node()]},
record_info(fields, oauth_token)}]), {attributes,
ejabberd_mnesia:create(?MODULE, oauth_client, record_info(fields, oauth_token)}]),
[{disc_copies, [node()]}, ejabberd_mnesia:create(?MODULE,
{attributes, oauth_client,
record_info(fields, oauth_client)}]), [{disc_copies, [node()]},
{attributes,
record_info(fields, oauth_client)}]),
ok. ok.
use_cache() -> use_cache() ->
case mnesia:table_info(oauth_token, storage_type) of case mnesia:table_info(oauth_token, storage_type) of
disc_only_copies -> disc_only_copies ->
ejabberd_option:oauth_use_cache(); ejabberd_option:oauth_use_cache();
_ -> _ ->
false false
end. end.
store(R) -> store(R) ->
mnesia:dirty_write(R). mnesia:dirty_write(R).
lookup(Token) -> lookup(Token) ->
case catch mnesia:dirty_read(oauth_token, Token) of case catch mnesia:dirty_read(oauth_token, Token) of
[R] -> [R] ->
@ -73,17 +80,19 @@ lookup(Token) ->
revoke(Token) -> revoke(Token) ->
mnesia:dirty_delete(oauth_token, Token). mnesia:dirty_delete(oauth_token, Token).
clean(TS) -> clean(TS) ->
F = fun() -> F = fun() ->
Ts = mnesia:select( Ts = mnesia:select(
oauth_token, oauth_token,
[{#oauth_token{expire = '$1', _ = '_'}, [{#oauth_token{expire = '$1', _ = '_'},
[{'<', '$1', TS}], [{'<', '$1', TS}],
['$_']}]), ['$_']}]),
lists:foreach(fun mnesia:delete_object/1, Ts) lists:foreach(fun mnesia:delete_object/1, Ts)
end, end,
mnesia:async_dirty(F). mnesia:async_dirty(F).
lookup_client(ClientID) -> lookup_client(ClientID) ->
case catch mnesia:dirty_read(oauth_client, ClientID) of case catch mnesia:dirty_read(oauth_client, ClientID) of
[R] -> [R] ->
@ -92,8 +101,10 @@ lookup_client(ClientID) ->
error error
end. end.
remove_client(ClientID) -> remove_client(ClientID) ->
mnesia:dirty_delete(oauth_client, ClientID). mnesia:dirty_delete(oauth_client, ClientID).
store_client(R) -> store_client(R) ->
mnesia:dirty_write(R). mnesia:dirty_write(R).

View file

@ -32,16 +32,20 @@
lookup/1, lookup/1,
clean/1, clean/1,
lookup_client/1, lookup_client/1,
store_client/1, revoke/1]). store_client/1,
revoke/1]).
-include("ejabberd_oauth.hrl"). -include("ejabberd_oauth.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include_lib("xmpp/include/jid.hrl"). -include_lib("xmpp/include/jid.hrl").
init() -> init() ->
rest:start(ejabberd_config:get_myname()), rest:start(ejabberd_config:get_myname()),
ok. ok.
store(R) -> store(R) ->
Path = path(<<"store">>), Path = path(<<"store">>),
%% Retry 2 times, with a backoff of 500millisec %% Retry 2 times, with a backoff of 500millisec
@ -49,12 +53,17 @@ store(R) ->
SJID = jid:encode({User, Server, <<"">>}), SJID = jid:encode({User, Server, <<"">>}),
case rest:with_retry( case rest:with_retry(
post, post,
[ejabberd_config:get_myname(), Path, [], [ejabberd_config:get_myname(),
#{<<"token">> => R#oauth_token.token, Path,
[],
#{
<<"token">> => R#oauth_token.token,
<<"user">> => SJID, <<"user">> => SJID,
<<"scope">> => R#oauth_token.scope, <<"scope">> => R#oauth_token.scope,
<<"expire">> => R#oauth_token.expire <<"expire">> => R#oauth_token.expire
}], 2, 500) of }],
2,
500) of
{ok, Code, _} when Code == 200 orelse Code == 201 -> {ok, Code, _} when Code == 200 orelse Code == 201 ->
ok; ok;
Err -> Err ->
@ -62,11 +71,16 @@ store(R) ->
{error, db_failure} {error, db_failure}
end. end.
lookup(Token) -> lookup(Token) ->
Path = path(<<"lookup">>), Path = path(<<"lookup">>),
case rest:with_retry(post, [ejabberd_config:get_myname(), Path, [], case rest:with_retry(post,
#{<<"token">> => Token}], [ejabberd_config:get_myname(),
2, 500) of Path,
[],
#{<<"token">> => Token}],
2,
500) of
{ok, 200, Data} -> {ok, 200, Data} ->
SJID = case maps:find(<<"user">>, Data) of SJID = case maps:find(<<"user">>, Data) of
{ok, U} -> U; {ok, U} -> U;
@ -82,10 +96,12 @@ lookup(Token) ->
{ok, E} -> E; {ok, E} -> E;
error -> 0 error -> 0
end, end,
{ok, #oauth_token{token = Token, {ok, #oauth_token{
us = US, token = Token,
scope = Scope, us = US,
expire = Expire}}; scope = Scope,
expire = Expire
}};
{ok, 404, _Resp} -> {ok, 404, _Resp} ->
error; error;
Other -> Other ->
@ -93,24 +109,30 @@ lookup(Token) ->
case ejabberd_option:oauth_cache_rest_failure_life_time() of case ejabberd_option:oauth_cache_rest_failure_life_time() of
infinity -> error; infinity -> error;
Time -> {cache_with_timeout, error, Time} Time -> {cache_with_timeout, error, Time}
end end
end. end.
-spec revoke(binary()) -> ok | {error, binary()}. -spec revoke(binary()) -> ok | {error, binary()}.
revoke(_Token) -> revoke(_Token) ->
{error, <<"not available">>}. {error, <<"not available">>}.
clean(_TS) -> clean(_TS) ->
ok. ok.
path(Path) -> path(Path) ->
Base = ejabberd_option:ext_api_path_oauth(), Base = ejabberd_option:ext_api_path_oauth(),
<<Base/binary, "/", Path/binary>>. <<Base/binary, "/", Path/binary>>.
store_client(#oauth_client{client_id = ClientID,
client_name = ClientName, store_client(#oauth_client{
grant_type = GrantType, client_id = ClientID,
options = Options} = R) -> client_name = ClientName,
grant_type = GrantType,
options = Options
} = R) ->
Path = path(<<"store_client">>), Path = path(<<"store_client">>),
SGrantType = SGrantType =
case GrantType of case GrantType of
@ -121,12 +143,17 @@ store_client(#oauth_client{client_id = ClientID,
%% Retry 2 times, with a backoff of 500millisec %% Retry 2 times, with a backoff of 500millisec
case rest:with_retry( case rest:with_retry(
post, post,
[ejabberd_config:get_myname(), Path, [], [ejabberd_config:get_myname(),
#{<<"client_id">> => ClientID, Path,
[],
#{
<<"client_id">> => ClientID,
<<"client_name">> => ClientName, <<"client_name">> => ClientName,
<<"grant_type">> => SGrantType, <<"grant_type">> => SGrantType,
<<"options">> => SOptions <<"options">> => SOptions
}], 2, 500) of }],
2,
500) of
{ok, Code, _} when Code == 200 orelse Code == 201 -> {ok, Code, _} when Code == 200 orelse Code == 201 ->
ok; ok;
Err -> Err ->
@ -134,11 +161,16 @@ store_client(#oauth_client{client_id = ClientID,
{error, db_failure} {error, db_failure}
end. end.
lookup_client(ClientID) -> lookup_client(ClientID) ->
Path = path(<<"lookup_client">>), Path = path(<<"lookup_client">>),
case rest:with_retry(post, [ejabberd_config:get_myname(), Path, [], case rest:with_retry(post,
#{<<"client_id">> => ClientID}], [ejabberd_config:get_myname(),
2, 500) of Path,
[],
#{<<"client_id">> => ClientID}],
2,
500) of
{ok, 200, Data} -> {ok, 200, Data} ->
ClientName = case maps:find(<<"client_name">>, Data) of ClientName = case maps:find(<<"client_name">>, Data) of
{ok, CN} -> CN; {ok, CN} -> CN;
@ -159,10 +191,12 @@ lookup_client(ClientID) ->
end, end,
case misc:base64_to_term(SOptions) of case misc:base64_to_term(SOptions) of
{term, Options} -> {term, Options} ->
{ok, #oauth_client{client_id = ClientID, {ok, #oauth_client{
client_name = ClientName, client_id = ClientID,
grant_type = GrantType, client_name = ClientName,
options = Options}}; grant_type = GrantType,
options = Options
}};
_ -> _ ->
error error
end; end;
@ -170,5 +204,5 @@ lookup_client(ClientID) ->
error; error;
Other -> Other ->
?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]), ?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]),
error error
end. end.

View file

@ -28,48 +28,59 @@
-behaviour(ejabberd_oauth). -behaviour(ejabberd_oauth).
-export([init/0, -export([init/0,
store/1, store/1,
lookup/1, lookup/1,
clean/1, clean/1,
lookup_client/1, lookup_client/1,
store_client/1, store_client/1,
remove_client/1, revoke/1]). remove_client/1,
revoke/1]).
-export([sql_schemas/0]). -export([sql_schemas/0]).
-include("ejabberd_oauth.hrl"). -include("ejabberd_oauth.hrl").
-include("ejabberd_sql_pt.hrl"). -include("ejabberd_sql_pt.hrl").
-include_lib("xmpp/include/jid.hrl"). -include_lib("xmpp/include/jid.hrl").
-include("logger.hrl"). -include("logger.hrl").
init() -> init() ->
ejabberd_sql_schema:update_schema( ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, sql_schemas()), ejabberd_config:get_myname(), ?MODULE, sql_schemas()),
ok. ok.
sql_schemas() -> sql_schemas() ->
[#sql_schema{ [#sql_schema{
version = 1, version = 1,
tables = tables =
[#sql_table{ [#sql_table{
name = <<"oauth_token">>, name = <<"oauth_token">>,
columns = columns =
[#sql_column{name = <<"token">>, type = text}, [#sql_column{name = <<"token">>, type = text},
#sql_column{name = <<"jid">>, type = text}, #sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"scope">>, type = text}, #sql_column{name = <<"scope">>, type = text},
#sql_column{name = <<"expire">>, type = bigint}], #sql_column{name = <<"expire">>, type = bigint}],
indices = [#sql_index{ indices = [#sql_index{
columns = [<<"token">>], columns = [<<"token">>],
unique = true}]}, unique = true
#sql_table{ }]
name = <<"oauth_client">>, },
columns = #sql_table{
[#sql_column{name = <<"client_id">>, type = text}, name = <<"oauth_client">>,
#sql_column{name = <<"client_name">>, type = text}, columns =
#sql_column{name = <<"grant_type">>, type = text}, [#sql_column{name = <<"client_id">>, type = text},
#sql_column{name = <<"options">>, type = text}], #sql_column{name = <<"client_name">>, type = text},
indices = [#sql_index{ #sql_column{name = <<"grant_type">>, type = text},
columns = [<<"client_id">>], #sql_column{name = <<"options">>, type = text}],
unique = true}]}]}]. indices = [#sql_index{
columns = [<<"client_id">>],
unique = true
}]
}]
}].
store(R) -> store(R) ->
Token = R#oauth_token.token, Token = R#oauth_token.token,
@ -78,18 +89,19 @@ store(R) ->
Scope = str:join(R#oauth_token.scope, <<" ">>), Scope = str:join(R#oauth_token.scope, <<" ">>),
Expire = R#oauth_token.expire, Expire = R#oauth_token.expire,
case ?SQL_UPSERT( case ?SQL_UPSERT(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),
"oauth_token", "oauth_token",
["!token=%(Token)s", ["!token=%(Token)s",
"jid=%(SJID)s", "jid=%(SJID)s",
"scope=%(Scope)s", "scope=%(Scope)s",
"expire=%(Expire)d"]) of "expire=%(Expire)d"]) of
ok -> ok ->
ok; ok;
_ -> _ ->
{error, db_failure} {error, db_failure}
end. end.
lookup(Token) -> lookup(Token) ->
case ejabberd_sql:sql_query( case ejabberd_sql:sql_query(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),
@ -98,29 +110,34 @@ lookup(Token) ->
{selected, [{SJID, Scope, Expire}]} -> {selected, [{SJID, Scope, Expire}]} ->
JID = jid:decode(SJID), JID = jid:decode(SJID),
US = {JID#jid.luser, JID#jid.lserver}, US = {JID#jid.luser, JID#jid.lserver},
{ok, #oauth_token{token = Token, {ok, #oauth_token{
us = US, token = Token,
scope = str:tokens(Scope, <<" ">>), us = US,
expire = Expire}}; scope = str:tokens(Scope, <<" ">>),
expire = Expire
}};
_ -> _ ->
error error
end. end.
revoke(Token) -> revoke(Token) ->
case ejabberd_sql:sql_query( case ejabberd_sql:sql_query(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),
?SQL("delete from oauth_token where token=%(Token)s")) of ?SQL("delete from oauth_token where token=%(Token)s")) of
{error, _} -> {error, _} ->
{error, <<"db error">>}; {error, <<"db error">>};
_ -> _ ->
ok ok
end. end.
clean(TS) -> clean(TS) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),
?SQL("delete from oauth_token where expire < %(TS)d")). ?SQL("delete from oauth_token where expire < %(TS)d")).
lookup_client(ClientID) -> lookup_client(ClientID) ->
case ejabberd_sql:sql_query( case ejabberd_sql:sql_query(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),
@ -134,10 +151,12 @@ lookup_client(ClientID) ->
end, end,
case misc:base64_to_term(SOptions) of case misc:base64_to_term(SOptions) of
{term, Options} -> {term, Options} ->
{ok, #oauth_client{client_id = ClientID, {ok, #oauth_client{
client_name = ClientName, client_id = ClientID,
grant_type = GrantType, client_name = ClientName,
options = Options}}; grant_type = GrantType,
options = Options
}};
_ -> _ ->
error error
end; end;
@ -145,10 +164,13 @@ lookup_client(ClientID) ->
error error
end. end.
store_client(#oauth_client{client_id = ClientID,
client_name = ClientName, store_client(#oauth_client{
grant_type = GrantType, client_id = ClientID,
options = Options}) -> client_name = ClientName,
grant_type = GrantType,
options = Options
}) ->
SGrantType = SGrantType =
case GrantType of case GrantType of
password -> <<"password">>; password -> <<"password">>;
@ -156,18 +178,19 @@ store_client(#oauth_client{client_id = ClientID,
end, end,
SOptions = misc:term_to_base64(Options), SOptions = misc:term_to_base64(Options),
case ?SQL_UPSERT( case ?SQL_UPSERT(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),
"oauth_client", "oauth_client",
["!client_id=%(ClientID)s", ["!client_id=%(ClientID)s",
"client_name=%(ClientName)s", "client_name=%(ClientName)s",
"grant_type=%(SGrantType)s", "grant_type=%(SGrantType)s",
"options=%(SOptions)s"]) of "options=%(SOptions)s"]) of
ok -> ok ->
ok; ok;
_ -> _ ->
{error, db_failure} {error, db_failure}
end. end.
remove_client(Client) -> remove_client(Client) ->
ejabberd_sql:sql_query( ejabberd_sql:sql_query(
ejabberd_config:get_myname(), ejabberd_config:get_myname(),

View file

@ -25,22 +25,24 @@
-include("logger.hrl"). -include("logger.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
read_file(File) -> read_file(File) ->
case consult(File) of case consult(File) of
{ok, Terms1} -> {ok, Terms1} ->
?INFO_MSG("Converting from old configuration format", []), ?INFO_MSG("Converting from old configuration format", []),
Terms2 = strings_to_binary(Terms1), Terms2 = strings_to_binary(Terms1),
Terms3 = transform(Terms2), Terms3 = transform(Terms2),
Terms4 = transform_certfiles(Terms3), Terms4 = transform_certfiles(Terms3),
Terms5 = transform_host_config(Terms4), Terms5 = transform_host_config(Terms4),
{ok, collect_options(Terms5)}; {ok, collect_options(Terms5)};
{error, Reason} -> {error, Reason} ->
{error, {old_config, File, Reason}} {error, {old_config, File, Reason}}
end. end.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -52,10 +54,13 @@ collect_options(Opts) ->
({K, V}, {D, Os}) -> ({K, V}, {D, Os}) ->
{orddict:store(K, V, D), Os}; {orddict:store(K, V, D), Os};
(Opt, {D, Os}) -> (Opt, {D, Os}) ->
{D, [Opt|Os]} {D, [Opt | Os]}
end, {orddict:new(), []}, Opts), end,
{orddict:new(), []},
Opts),
InvalidOpts ++ orddict:to_list(D). InvalidOpts ++ orddict:to_list(D).
transform(Opts) -> transform(Opts) ->
Opts1 = transform_register(Opts), Opts1 = transform_register(Opts),
Opts2 = transform_s2s(Opts1), Opts2 = transform_s2s(Opts1),
@ -68,6 +73,7 @@ transform(Opts) ->
Opts10 = transform_globals(Opts9), Opts10 = transform_globals(Opts9),
collect_options(Opts10). collect_options(Opts10).
%%%=================================================================== %%%===================================================================
%%% mod_register %%% mod_register
%%%=================================================================== %%%===================================================================
@ -80,7 +86,8 @@ transform_register(Opts) ->
?WARNING_MSG("Old 'ip_access' format detected. " ?WARNING_MSG("Old 'ip_access' format detected. "
"The old format is still supported " "The old format is still supported "
"but it is better to fix your config: " "but it is better to fix your config: "
"use access rules instead.", []), "use access rules instead.",
[]),
ACLs = lists:flatmap( ACLs = lists:flatmap(
fun({Action, S}) -> fun({Action, S}) ->
ACLName = misc:binary_to_atom( ACLName = misc:binary_to_atom(
@ -88,43 +95,46 @@ transform_register(Opts) ->
["ip_", S])), ["ip_", S])),
[{Action, ACLName}, [{Action, ACLName},
{acl, ACLName, {ip, S}}] {acl, ACLName, {ip, S}}]
end, L), end,
L),
Access = {access, mod_register_networks, Access = {access, mod_register_networks,
[{Action, ACLName} || {Action, ACLName} <- ACLs]}, [ {Action, ACLName} || {Action, ACLName} <- ACLs ]},
[ACL || {acl, _, _} = ACL <- ACLs] ++ [ ACL || {acl, _, _} = ACL <- ACLs ] ++
[Access, [Access,
{modules, {modules,
[{mod_register, [{mod_register,
[{ip_access, mod_register_networks}|RegOpts1]} [{ip_access, mod_register_networks} | RegOpts1]} | ModOpts1]} | Opts1]
| ModOpts1]}|Opts1] catch
catch error:{badmatch, false} -> error:{badmatch, false} ->
Opts Opts
end. end.
%%%=================================================================== %%%===================================================================
%%% ejabberd_s2s %%% ejabberd_s2s
%%%=================================================================== %%%===================================================================
transform_s2s(Opts) -> transform_s2s(Opts) ->
lists:foldl(fun transform_s2s/2, [], Opts). lists:foldl(fun transform_s2s/2, [], Opts).
transform_s2s({{s2s_host, Host}, Action}, Opts) -> transform_s2s({{s2s_host, Host}, Action}, Opts) ->
?WARNING_MSG("Option 's2s_host' is deprecated.", []), ?WARNING_MSG("Option 's2s_host' is deprecated.", []),
ACLName = misc:binary_to_atom( ACLName = misc:binary_to_atom(
iolist_to_binary(["s2s_access_", Host])), iolist_to_binary(["s2s_access_", Host])),
[{acl, ACLName, {server, Host}}, [{acl, ACLName, {server, Host}},
{access, s2s, [{Action, ACLName}]}, {access, s2s, [{Action, ACLName}]},
{s2s_access, s2s} | {s2s_access, s2s} | Opts];
Opts];
transform_s2s({s2s_default_policy, Action}, Opts) -> transform_s2s({s2s_default_policy, Action}, Opts) ->
?WARNING_MSG("Option 's2s_default_policy' is deprecated. " ?WARNING_MSG("Option 's2s_default_policy' is deprecated. "
"The option is still supported but it is better to " "The option is still supported but it is better to "
"fix your config: " "fix your config: "
"use 's2s_access' with an access rule.", []), "use 's2s_access' with an access rule.",
[]),
[{access, s2s, [{Action, all}]}, [{access, s2s, [{Action, all}]},
{s2s_access, s2s} | {s2s_access, s2s} | Opts];
Opts];
transform_s2s(Opt, Opts) -> transform_s2s(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% ejabberd_s2s_out %%% ejabberd_s2s_out
@ -132,31 +142,36 @@ transform_s2s(Opt, Opts) ->
transform_s2s_out(Opts) -> transform_s2s_out(Opts) ->
lists:foldl(fun transform_s2s_out/2, [], Opts). lists:foldl(fun transform_s2s_out/2, [], Opts).
transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) -> transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) ->
?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. " ?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. "
"The option is still supported " "The option is still supported "
"but it is better to fix your config: " "but it is better to fix your config: "
"use 'outgoing_s2s_timeout' and " "use 'outgoing_s2s_timeout' and "
"'outgoing_s2s_families' instead.", []), "'outgoing_s2s_families' instead.",
[]),
[{outgoing_s2s_families, Families}, [{outgoing_s2s_families, Families},
{outgoing_s2s_timeout, Timeout} {outgoing_s2s_timeout, Timeout} | Opts];
| Opts];
transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) -> transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) ->
?WARNING_MSG("Option 's2s_dns_options' is deprecated. " ?WARNING_MSG("Option 's2s_dns_options' is deprecated. "
"The option is still supported " "The option is still supported "
"but it is better to fix your config: " "but it is better to fix your config: "
"use 's2s_dns_timeout' and " "use 's2s_dns_timeout' and "
"'s2s_dns_retries' instead", []), "'s2s_dns_retries' instead",
[]),
lists:foldr( lists:foldr(
fun({timeout, T}, AccOpts) -> fun({timeout, T}, AccOpts) ->
[{s2s_dns_timeout, T}|AccOpts]; [{s2s_dns_timeout, T} | AccOpts];
({retries, R}, AccOpts) -> ({retries, R}, AccOpts) ->
[{s2s_dns_retries, R}|AccOpts]; [{s2s_dns_retries, R} | AccOpts];
(_, AccOpts) -> (_, AccOpts) ->
AccOpts AccOpts
end, AllOpts, S2SDNSOpts); end,
AllOpts,
S2SDNSOpts);
transform_s2s_out(Opt, Opts) -> transform_s2s_out(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% ejabberd_listener %%% ejabberd_listener
@ -164,33 +179,41 @@ transform_s2s_out(Opt, Opts) ->
transform_listeners(Opts) -> transform_listeners(Opts) ->
lists:foldl(fun transform_listeners/2, [], Opts). lists:foldl(fun transform_listeners/2, [], Opts).
transform_listeners({listen, LOpts}, Opts) -> transform_listeners({listen, LOpts}, Opts) ->
[{listen, lists:map(fun transform_listener/1, LOpts)} | Opts]; [{listen, lists:map(fun transform_listener/1, LOpts)} | Opts];
transform_listeners(Opt, Opts) -> transform_listeners(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
transform_listener({{Port, IP, Transport}, Mod, Opts}) -> transform_listener({{Port, IP, Transport}, Mod, Opts}) ->
IPStr = if is_tuple(IP) -> IPStr = if
is_tuple(IP) ->
list_to_binary(inet_parse:ntoa(IP)); list_to_binary(inet_parse:ntoa(IP));
true -> true ->
IP IP
end, end,
Opts1 = lists:map( Opts1 = lists:map(
fun({ip, IPT}) when is_tuple(IPT) -> fun({ip, IPT}) when is_tuple(IPT) ->
{ip, list_to_binary(inet_parse:ntoa(IP))}; {ip, list_to_binary(inet_parse:ntoa(IP))};
(ssl) -> {tls, true}; (ssl) -> {tls, true};
(A) when is_atom(A) -> {A, true}; (A) when is_atom(A) -> {A, true};
(Opt) -> Opt (Opt) -> Opt
end, Opts), end,
Opts),
Opts2 = lists:foldl( Opts2 = lists:foldl(
fun(Opt, Acc) -> fun(Opt, Acc) ->
transform_listen_option(Mod, Opt, Acc) transform_listen_option(Mod, Opt, Acc)
end, [], Opts1), end,
TransportOpt = if Transport == tcp -> []; [],
true -> [{transport, Transport}] Opts1),
TransportOpt = if
Transport == tcp -> [];
true -> [{transport, Transport}]
end, end,
IPOpt = if IPStr == <<"0.0.0.0">> -> []; IPOpt = if
true -> [{ip, IPStr}] IPStr == <<"0.0.0.0">> -> [];
true -> [{ip, IPStr}]
end, end,
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2]; IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
transform_listener({{Port, Transport}, Mod, Opts}) transform_listener({{Port, Transport}, Mod, Opts})
@ -203,24 +226,26 @@ transform_listener({Port, Mod, Opts}) ->
transform_listener(Opt) -> transform_listener(Opt) ->
Opt. Opt.
transform_listen_option(ejabberd_http, captcha, Opts) -> transform_listen_option(ejabberd_http, captcha, Opts) ->
[{captcha, true}|Opts]; [{captcha, true} | Opts];
transform_listen_option(ejabberd_http, register, Opts) -> transform_listen_option(ejabberd_http, register, Opts) ->
[{register, true}|Opts]; [{register, true} | Opts];
transform_listen_option(ejabberd_http, web_admin, Opts) -> transform_listen_option(ejabberd_http, web_admin, Opts) ->
[{web_admin, true}|Opts]; [{web_admin, true} | Opts];
transform_listen_option(ejabberd_http, http_bind, Opts) -> transform_listen_option(ejabberd_http, http_bind, Opts) ->
[{http_bind, true}|Opts]; [{http_bind, true} | Opts];
transform_listen_option(ejabberd_http, http_poll, Opts) -> transform_listen_option(ejabberd_http, http_poll, Opts) ->
[{http_poll, true}|Opts]; [{http_poll, true} | Opts];
transform_listen_option(ejabberd_http, {request_handlers, Hs}, Opts) -> transform_listen_option(ejabberd_http, {request_handlers, Hs}, Opts) ->
Hs1 = lists:map( Hs1 = lists:map(
fun({PList, Mod}) when is_list(PList) -> fun({PList, Mod}) when is_list(PList) ->
Path = iolist_to_binary([[$/, P] || P <- PList]), Path = iolist_to_binary([ [$/, P] || P <- PList ]),
{Path, Mod}; {Path, Mod};
(Opt) -> (Opt) ->
Opt Opt
end, Hs), end,
Hs),
[{request_handlers, Hs1} | Opts]; [{request_handlers, Hs1} | Opts];
transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) -> transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) ->
case lists:keyfind(hosts, 1, Opts) of case lists:keyfind(hosts, 1, Opts) of
@ -229,11 +254,12 @@ transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) ->
lists:foldl( lists:foldl(
fun(H, Acc) -> fun(H, Acc) ->
dict:append_list(H, O, Acc) dict:append_list(H, O, Acc)
end, dict:from_list(PrevHostOpts), Hosts), end,
[{hosts, dict:to_list(NewHostOpts)}| dict:from_list(PrevHostOpts),
lists:keydelete(hosts, 1, Opts)]; Hosts),
[{hosts, dict:to_list(NewHostOpts)} | lists:keydelete(hosts, 1, Opts)];
_ -> _ ->
[{hosts, [{H, O} || H <- Hosts]}|Opts] [{hosts, [ {H, O} || H <- Hosts ]} | Opts]
end; end;
transform_listen_option(ejabberd_service, {host, Host, Os}, Opts) -> transform_listen_option(ejabberd_service, {host, Host, Os}, Opts) ->
transform_listen_option(ejabberd_service, {hosts, [Host], Os}, Opts); transform_listen_option(ejabberd_service, {hosts, [Host], Os}, Opts);
@ -243,41 +269,47 @@ transform_listen_option(ejabberd_xmlrpc, {access_commands, ACOpts}, Opts) ->
{AName, [{commands, ACmds}, {options, AOpts}]}; {AName, [{commands, ACmds}, {options, AOpts}]};
(Opt) -> (Opt) ->
Opt Opt
end, ACOpts), end,
[{access_commands, NewACOpts}|Opts]; ACOpts),
[{access_commands, NewACOpts} | Opts];
transform_listen_option(_, Opt, Opts) -> transform_listen_option(_, Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
-spec all_zero_ip([proplists:property()]) -> inet:ip_address(). -spec all_zero_ip([proplists:property()]) -> inet:ip_address().
all_zero_ip(Opts) -> all_zero_ip(Opts) ->
case proplists:get_bool(inet6, Opts) of case proplists:get_bool(inet6, Opts) of
true -> {0,0,0,0,0,0,0,0}; true -> {0, 0, 0, 0, 0, 0, 0, 0};
false -> {0,0,0,0} false -> {0, 0, 0, 0}
end. end.
%%%=================================================================== %%%===================================================================
%%% ejabberd_shaper %%% ejabberd_shaper
%%%=================================================================== %%%===================================================================
transform_shaper(Opts) -> transform_shaper(Opts) ->
lists:foldl(fun transform_shaper/2, [], Opts). lists:foldl(fun transform_shaper/2, [], Opts).
transform_shaper({shaper, Name, {maxrate, N}}, Opts) -> transform_shaper({shaper, Name, {maxrate, N}}, Opts) ->
[{shaper, [{Name, N}]} | Opts]; [{shaper, [{Name, N}]} | Opts];
transform_shaper({shaper, Name, none}, Opts) -> transform_shaper({shaper, Name, none}, Opts) ->
[{shaper, [{Name, none}]} | Opts]; [{shaper, [{Name, none}]} | Opts];
transform_shaper({shaper, List}, Opts) when is_list(List) -> transform_shaper({shaper, List}, Opts) when is_list(List) ->
R = lists:map( R = lists:map(
fun({Name, Args}) when is_list(Args) -> fun({Name, Args}) when is_list(Args) ->
MaxRate = proplists:get_value(rate, Args, 1000), MaxRate = proplists:get_value(rate, Args, 1000),
BurstSize = proplists:get_value(burst_size, Args, MaxRate), BurstSize = proplists:get_value(burst_size, Args, MaxRate),
{Name, MaxRate, BurstSize}; {Name, MaxRate, BurstSize};
({Name, Val}) -> ({Name, Val}) ->
{Name, Val, Val} {Name, Val, Val}
end, List), end,
List),
[{shaper, R} | Opts]; [{shaper, R} | Opts];
transform_shaper(Opt, Opts) -> transform_shaper(Opt, Opts) ->
[Opt | Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% acl %%% acl
%%%=================================================================== %%%===================================================================
@ -287,26 +319,34 @@ transform_acl(Opts) ->
fun({acl, Os}, Acc) -> fun({acl, Os}, Acc) ->
{Os, Acc}; {Os, Acc};
(O, Acc) -> (O, Acc) ->
{[], [O|Acc]} {[], [O | Acc]}
end, [], Opts1), end,
[],
Opts1),
{AccessOpts, Opts3} = lists:mapfoldl( {AccessOpts, Opts3} = lists:mapfoldl(
fun({access, Os}, Acc) -> fun({access, Os}, Acc) ->
{Os, Acc}; {Os, Acc};
(O, Acc) -> (O, Acc) ->
{[], [O|Acc]} {[], [O | Acc]}
end, [], Opts2), end,
[],
Opts2),
{NewAccessOpts, Opts4} = lists:mapfoldl( {NewAccessOpts, Opts4} = lists:mapfoldl(
fun({access_rules, Os}, Acc) -> fun({access_rules, Os}, Acc) ->
{Os, Acc}; {Os, Acc};
(O, Acc) -> (O, Acc) ->
{[], [O|Acc]} {[], [O | Acc]}
end, [], Opts3), end,
[],
Opts3),
{ShaperOpts, Opts5} = lists:mapfoldl( {ShaperOpts, Opts5} = lists:mapfoldl(
fun({shaper_rules, Os}, Acc) -> fun({shaper_rules, Os}, Acc) ->
{Os, Acc}; {Os, Acc};
(O, Acc) -> (O, Acc) ->
{[], [O|Acc]} {[], [O | Acc]}
end, [], Opts4), end,
[],
Opts4),
ACLOpts1 = collect_options(lists:flatten(ACLOpts)), ACLOpts1 = collect_options(lists:flatten(ACLOpts)),
AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of
[] -> []; [] -> [];
@ -315,26 +355,30 @@ transform_acl(Opts) ->
ACLOpts2 = case lists:map( ACLOpts2 = case lists:map(
fun({ACLName, Os}) -> fun({ACLName, Os}) ->
{ACLName, collect_options(Os)} {ACLName, collect_options(Os)}
end, ACLOpts1) of end,
ACLOpts1) of
[] -> []; [] -> [];
L2 -> [{acl, L2}] L2 -> [{acl, L2}]
end, end,
NewAccessOpts1 = case lists:map( NewAccessOpts1 = case lists:map(
fun({NAName, Os}) -> fun({NAName, Os}) ->
{NAName, transform_access_rules_config(Os)} {NAName, transform_access_rules_config(Os)}
end, lists:flatten(NewAccessOpts)) of end,
[] -> []; lists:flatten(NewAccessOpts)) of
L3 -> [{access_rules, L3}] [] -> [];
end, L3 -> [{access_rules, L3}]
end,
ShaperOpts1 = case lists:map( ShaperOpts1 = case lists:map(
fun({SName, Ss}) -> fun({SName, Ss}) ->
{SName, transform_access_rules_config(Ss)} {SName, transform_access_rules_config(Ss)}
end, lists:flatten(ShaperOpts)) of end,
[] -> []; lists:flatten(ShaperOpts)) of
L4 -> [{shaper_rules, L4}] [] -> [];
end, L4 -> [{shaper_rules, L4}]
end,
ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5. ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5.
transform_acl({acl, Name, Type}, Opts) -> transform_acl({acl, Name, Type}, Opts) ->
T = case Type of T = case Type of
all -> all; all -> all;
@ -357,59 +401,66 @@ transform_acl({acl, Name, Type}, Opts) ->
{resource_glob, R} -> {resource_glob, [b(R)]}; {resource_glob, R} -> {resource_glob, [b(R)]};
{resource_regexp, R} -> {resource_regexp, [b(R)]} {resource_regexp, R} -> {resource_regexp, [b(R)]}
end, end,
[{acl, [{Name, [T]}]}|Opts]; [{acl, [{Name, [T]}]} | Opts];
transform_acl({access, Name, Rules}, Opts) -> transform_acl({access, Name, Rules}, Opts) ->
NewRules = [{ACL, Action} || {Action, ACL} <- Rules], NewRules = [ {ACL, Action} || {Action, ACL} <- Rules ],
[{access, [{Name, NewRules}]}|Opts]; [{access, [{Name, NewRules}]} | Opts];
transform_acl(Opt, Opts) -> transform_acl(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
transform_access_rules_config(Config) when is_list(Config) -> transform_access_rules_config(Config) when is_list(Config) ->
lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)); lists:map(fun transform_access_rules_config2/1, lists:flatten(Config));
transform_access_rules_config(Config) -> transform_access_rules_config(Config) ->
transform_access_rules_config([Config]). transform_access_rules_config([Config]).
transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) -> transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) ->
{Type, [all]}; {Type, [all]};
transform_access_rules_config2({Type, ACL}) when is_atom(ACL) -> transform_access_rules_config2({Type, ACL}) when is_atom(ACL) ->
{Type, [{acl, ACL}]}; {Type, [{acl, ACL}]};
transform_access_rules_config2({Res, Rules}) when is_list(Rules) -> transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
T = lists:map(fun({Type, Args}) when is_list(Args) -> T = lists:map(fun({Type, Args}) when is_list(Args) ->
{Type, hd(lists:flatten(Args))}; {Type, hd(lists:flatten(Args))};
(V) -> (V) ->
V V
end, lists:flatten(Rules)), end,
lists:flatten(Rules)),
{Res, T}; {Res, T};
transform_access_rules_config2({Res, Rule}) -> transform_access_rules_config2({Res, Rule}) ->
{Res, [Rule]}. {Res, [Rule]}.
%%%=================================================================== %%%===================================================================
%%% SQL %%% SQL
%%%=================================================================== %%%===================================================================
-define(PGSQL_PORT, 5432). -define(PGSQL_PORT, 5432).
-define(MYSQL_PORT, 3306). -define(MYSQL_PORT, 3306).
transform_sql(Opts) -> transform_sql(Opts) ->
lists:foldl(fun transform_sql/2, [], Opts). lists:foldl(fun transform_sql/2, [], Opts).
transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
[{sql_type, Type}, [{sql_type, Type},
{sql_server, Server}, {sql_server, Server},
{sql_port, Port}, {sql_port, Port},
{sql_database, DB}, {sql_database, DB},
{sql_username, User}, {sql_username, User},
{sql_password, Pass}|Opts]; {sql_password, Pass} | Opts];
transform_sql({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> transform_sql({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
transform_sql({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); transform_sql({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
transform_sql({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> transform_sql({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
transform_sql({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); transform_sql({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts);
transform_sql({odbc_server, {sqlite, DB}}, Opts) -> transform_sql({odbc_server, {sqlite, DB}}, Opts) ->
[{sql_type, sqlite}, [{sql_type, sqlite},
{sql_database, DB}|Opts]; {sql_database, DB} | Opts];
transform_sql({odbc_pool_size, N}, Opts) -> transform_sql({odbc_pool_size, N}, Opts) ->
[{sql_pool_size, N}|Opts]; [{sql_pool_size, N} | Opts];
transform_sql(Opt, Opts) -> transform_sql(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% modules %%% modules
@ -417,15 +468,18 @@ transform_sql(Opt, Opts) ->
transform_modules(Opts) -> transform_modules(Opts) ->
lists:foldl(fun transform_modules/2, [], Opts). lists:foldl(fun transform_modules/2, [], Opts).
transform_modules({modules, ModOpts}, Opts) -> transform_modules({modules, ModOpts}, Opts) ->
[{modules, lists:map( [{modules, lists:map(
fun({Mod, Opts1}) -> fun({Mod, Opts1}) ->
{Mod, transform_module(Mod, Opts1)}; {Mod, transform_module(Mod, Opts1)};
(Other) -> (Other) ->
Other Other
end, ModOpts)}|Opts]; end,
ModOpts)} | Opts];
transform_modules(Opt, Opts) -> transform_modules(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
transform_module(mod_disco, Opts) -> transform_module(mod_disco, Opts) ->
lists:map( lists:map(
@ -437,18 +491,21 @@ transform_module(mod_disco, Opts) ->
{urls, URLs}]]; {urls, URLs}]];
(Opt) -> (Opt) ->
Opt Opt
end, Infos), end,
Infos),
{server_info, NewInfos}; {server_info, NewInfos};
(Opt) -> (Opt) ->
Opt Opt
end, Opts); end,
Opts);
transform_module(mod_muc_log, Opts) -> transform_module(mod_muc_log, Opts) ->
lists:map( lists:map(
fun({top_link, {S1, S2}}) -> fun({top_link, {S1, S2}}) ->
{top_link, [{S1, S2}]}; {top_link, [{S1, S2}]};
(Opt) -> (Opt) ->
Opt Opt
end, Opts); end,
Opts);
transform_module(mod_proxy65, Opts) -> transform_module(mod_proxy65, Opts) ->
lists:map( lists:map(
fun({ip, IP}) when is_tuple(IP) -> fun({ip, IP}) when is_tuple(IP) ->
@ -457,17 +514,20 @@ transform_module(mod_proxy65, Opts) ->
{hostname, misc:ip_to_list(IP)}; {hostname, misc:ip_to_list(IP)};
(Opt) -> (Opt) ->
Opt Opt
end, Opts); end,
Opts);
transform_module(mod_register, Opts) -> transform_module(mod_register, Opts) ->
lists:flatmap( lists:flatmap(
fun({welcome_message, {Subj, Body}}) -> fun({welcome_message, {Subj, Body}}) ->
[{welcome_message, [{subject, Subj}, {body, Body}]}]; [{welcome_message, [{subject, Subj}, {body, Body}]}];
(Opt) -> (Opt) ->
[Opt] [Opt]
end, Opts); end,
Opts);
transform_module(_Mod, Opts) -> transform_module(_Mod, Opts) ->
Opts. Opts.
%%%=================================================================== %%%===================================================================
%%% Host config %%% Host config
%%%=================================================================== %%%===================================================================
@ -477,42 +537,50 @@ transform_host_config(Opts) ->
fun({host_config, O}, Os) -> fun({host_config, O}, Os) ->
{[O], Os}; {[O], Os};
(O, Os) -> (O, Os) ->
{[], [O|Os]} {[], [O | Os]}
end, [], Opts1), end,
[],
Opts1),
{AHOpts, Opts3} = lists:mapfoldl( {AHOpts, Opts3} = lists:mapfoldl(
fun({append_host_config, O}, Os) -> fun({append_host_config, O}, Os) ->
{[O], Os}; {[O], Os};
(O, Os) -> (O, Os) ->
{[], [O|Os]} {[], [O | Os]}
end, [], Opts2), end,
[],
Opts2),
HOpts1 = case collect_options(lists:flatten(HOpts)) of HOpts1 = case collect_options(lists:flatten(HOpts)) of
[] -> [] ->
[]; [];
HOs -> HOs ->
[{host_config, [{host_config,
[{H, transform(O)} || {H, O} <- HOs]}] [ {H, transform(O)} || {H, O} <- HOs ]}]
end, end,
AHOpts1 = case collect_options(lists:flatten(AHOpts)) of AHOpts1 = case collect_options(lists:flatten(AHOpts)) of
[] -> [] ->
[]; [];
AHOs -> AHOs ->
[{append_host_config, [{append_host_config,
[{H, transform(O)} || {H, O} <- AHOs]}] [ {H, transform(O)} || {H, O} <- AHOs ]}]
end, end,
HOpts1 ++ AHOpts1 ++ Opts3. HOpts1 ++ AHOpts1 ++ Opts3.
transform_host_config({host_config, Host, HOpts}, Opts) -> transform_host_config({host_config, Host, HOpts}, Opts) ->
{AddOpts, HOpts1} = {AddOpts, HOpts1} =
lists:mapfoldl( lists:mapfoldl(
fun({{add, Opt}, Val}, Os) -> fun({{add, Opt}, Val}, Os) ->
{[{Opt, Val}], Os}; {[{Opt, Val}], Os};
(O, Os) -> (O, Os) ->
{[], [O|Os]} {[], [O | Os]}
end, [], HOpts), end,
[],
HOpts),
[{append_host_config, [{Host, lists:flatten(AddOpts)}]}, [{append_host_config, [{Host, lists:flatten(AddOpts)}]},
{host_config, [{Host, HOpts1}]}|Opts]; {host_config, [{Host, HOpts1}]} | Opts];
transform_host_config(Opt, Opts) -> transform_host_config(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% Top-level options %%% Top-level options
@ -520,20 +588,22 @@ transform_host_config(Opt, Opts) ->
transform_globals(Opts) -> transform_globals(Opts) ->
lists:foldl(fun transform_globals/2, [], Opts). lists:foldl(fun transform_globals/2, [], Opts).
transform_globals(Opt, Opts) when Opt == override_global; transform_globals(Opt, Opts) when Opt == override_global;
Opt == override_local; Opt == override_local;
Opt == override_acls -> Opt == override_acls ->
?WARNING_MSG("Option '~ts' has no effect anymore", [Opt]), ?WARNING_MSG("Option '~ts' has no effect anymore", [Opt]),
Opts; Opts;
transform_globals({node_start, _}, Opts) -> transform_globals({node_start, _}, Opts) ->
?WARNING_MSG("Option 'node_start' has no effect anymore", []), ?WARNING_MSG("Option 'node_start' has no effect anymore", []),
Opts; Opts;
transform_globals({iqdisc, {queues, N}}, Opts) -> transform_globals({iqdisc, {queues, N}}, Opts) ->
[{iqdisc, N}|Opts]; [{iqdisc, N} | Opts];
transform_globals({define_macro, Macro, Val}, Opts) -> transform_globals({define_macro, Macro, Val}, Opts) ->
[{define_macro, [{Macro, Val}]}|Opts]; [{define_macro, [{Macro, Val}]} | Opts];
transform_globals(Opt, Opts) -> transform_globals(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% Certfiles %%% Certfiles
@ -541,70 +611,79 @@ transform_globals(Opt, Opts) ->
transform_certfiles(Opts) -> transform_certfiles(Opts) ->
lists:foldl(fun transform_certfiles/2, [], Opts). lists:foldl(fun transform_certfiles/2, [], Opts).
transform_certfiles({domain_certfile, Domain, CertFile}, Opts) -> transform_certfiles({domain_certfile, Domain, CertFile}, Opts) ->
[{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts]; [{host_config, [{Domain, [{domain_certfile, CertFile}]}]} | Opts];
transform_certfiles(Opt, Opts) -> transform_certfiles(Opt, Opts) ->
[Opt|Opts]. [Opt | Opts].
%%%=================================================================== %%%===================================================================
%%% Consult file %%% Consult file
%%%=================================================================== %%%===================================================================
consult(File) -> consult(File) ->
case file:consult(File) of case file:consult(File) of
{ok, Terms} -> {ok, Terms} ->
include_config_files(Terms); include_config_files(Terms);
Err -> Err ->
Err Err
end. end.
include_config_files(Terms) -> include_config_files(Terms) ->
include_config_files(Terms, []). include_config_files(Terms, []).
include_config_files([], Res) -> include_config_files([], Res) ->
{ok, Res}; {ok, Res};
include_config_files([{include_config_file, Filename} | Terms], Res) -> include_config_files([{include_config_file, Filename} | Terms], Res) ->
include_config_files([{include_config_file, Filename, []} | Terms], Res); include_config_files([{include_config_file, Filename, []} | Terms], Res);
include_config_files([{include_config_file, Filename, Options} | Terms], Res) -> include_config_files([{include_config_file, Filename, Options} | Terms], Res) ->
case consult(Filename) of case consult(Filename) of
{ok, Included_terms} -> {ok, Included_terms} ->
Disallow = proplists:get_value(disallow, Options, []), Disallow = proplists:get_value(disallow, Options, []),
Included_terms2 = delete_disallowed(Disallow, Included_terms), Included_terms2 = delete_disallowed(Disallow, Included_terms),
Allow_only = proplists:get_value(allow_only, Options, all), Allow_only = proplists:get_value(allow_only, Options, all),
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2), Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
include_config_files(Terms, Res ++ Included_terms3); include_config_files(Terms, Res ++ Included_terms3);
Err -> Err ->
Err Err
end; end;
include_config_files([Term | Terms], Res) -> include_config_files([Term | Terms], Res) ->
include_config_files(Terms, Res ++ [Term]). include_config_files(Terms, Res ++ [Term]).
delete_disallowed(Disallowed, Terms) -> delete_disallowed(Disallowed, Terms) ->
lists:foldl( lists:foldl(
fun(Dis, Ldis) -> fun(Dis, Ldis) ->
delete_disallowed2(Dis, Ldis) delete_disallowed2(Dis, Ldis)
end, end,
Terms, Terms,
Disallowed). Disallowed).
delete_disallowed2(Disallowed, [H|T]) ->
delete_disallowed2(Disallowed, [H | T]) ->
case element(1, H) of case element(1, H) of
Disallowed -> Disallowed ->
delete_disallowed2(Disallowed, T); delete_disallowed2(Disallowed, T);
_ -> _ ->
[H|delete_disallowed2(Disallowed, T)] [H | delete_disallowed2(Disallowed, T)]
end; end;
delete_disallowed2(_, []) -> delete_disallowed2(_, []) ->
[]. [].
keep_only_allowed(all, Terms) -> keep_only_allowed(all, Terms) ->
Terms; Terms;
keep_only_allowed(Allowed, Terms) -> keep_only_allowed(Allowed, Terms) ->
{As, _NAs} = lists:partition( {As, _NAs} = lists:partition(
fun(Term) -> fun(Term) ->
lists:member(element(1, Term), Allowed) lists:member(element(1, Term), Allowed)
end, Terms), end,
Terms),
As. As.
%%%=================================================================== %%%===================================================================
%%% Aux functions %%% Aux functions
%%%=================================================================== %%%===================================================================
@ -617,27 +696,30 @@ strings_to_binary(L) when is_list(L) ->
false -> false ->
strings_to_binary1(L) strings_to_binary1(L)
end; end;
strings_to_binary({A, B, C, D}) when strings_to_binary({A, B, C, D})
is_integer(A), is_integer(B), is_integer(C), is_integer(D) -> when is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
{A, B, C, D}; {A, B, C, D};
strings_to_binary(T) when is_tuple(T) -> strings_to_binary(T) when is_tuple(T) ->
list_to_tuple(strings_to_binary1(tuple_to_list(T))); list_to_tuple(strings_to_binary1(tuple_to_list(T)));
strings_to_binary(X) -> strings_to_binary(X) ->
X. X.
strings_to_binary1([El|L]) ->
[strings_to_binary(El)|strings_to_binary1(L)]; strings_to_binary1([El | L]) ->
[strings_to_binary(El) | strings_to_binary1(L)];
strings_to_binary1([]) -> strings_to_binary1([]) ->
[]; [];
strings_to_binary1(T) -> strings_to_binary1(T) ->
T. T.
is_string([C|T]) when (C >= 0) and (C =< 255) ->
is_string([C | T]) when (C >= 0) and (C =< 255) ->
is_string(T); is_string(T);
is_string([]) -> is_string([]) ->
true; true;
is_string(_) -> is_string(_) ->
false. false.
b(S) -> b(S) ->
iolist_to_binary(S). iolist_to_binary(S).

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,7 @@
-include_lib("kernel/include/inet.hrl"). -include_lib("kernel/include/inet.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
@ -39,10 +40,12 @@ opt_type(acl) ->
acl:validator(acl); acl:validator(acl);
opt_type(acme) -> opt_type(acme) ->
econf:options( econf:options(
#{ca_url => econf:url(), #{
contact => econf:list_or_single(econf:binary("^[a-zA-Z]+:[^:]+$")), ca_url => econf:url(),
auto => econf:bool(), contact => econf:list_or_single(econf:binary("^[a-zA-Z]+:[^:]+$")),
cert_type => econf:enum([ec, rsa])}, auto => econf:bool(),
cert_type => econf:enum([ec, rsa])
},
[unique, {return, map}]); [unique, {return, map}]);
opt_type(allow_contrib_modules) -> opt_type(allow_contrib_modules) ->
econf:bool(); econf:bool();
@ -75,7 +78,8 @@ opt_type(auth_opts) ->
{basic_auth, V}; {basic_auth, V};
({path_prefix, V}) when is_binary(V) -> ({path_prefix, V}) when is_binary(V) ->
{path_prefix, V} {path_prefix, V}
end, L) end,
L)
end; end;
opt_type(auth_stored_password_types) -> opt_type(auth_stored_password_types) ->
econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512])); econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512]));
@ -144,16 +148,18 @@ opt_type(disable_sasl_scram_downgrade_protection) ->
opt_type(disable_sasl_mechanisms) -> opt_type(disable_sasl_mechanisms) ->
econf:list_or_single( econf:list_or_single(
econf:and_then( econf:and_then(
econf:binary(), econf:binary(),
fun str:to_upper/1)); fun str:to_upper/1));
opt_type(domain_balancing) -> opt_type(domain_balancing) ->
econf:map( econf:map(
econf:domain(), econf:domain(),
econf:options( econf:options(
#{component_number => econf:int(2, 1000), #{
type => econf:enum([random, source, destination, component_number => econf:int(2, 1000),
bare_source, bare_destination])}, type => econf:enum([random, source, destination,
[{return, map}, unique]), bare_source, bare_destination])
},
[{return, map}, unique]),
[{return, map}]); [{return, map}]);
opt_type(ext_api_path_oauth) -> opt_type(ext_api_path_oauth) ->
econf:binary(); econf:binary();
@ -179,7 +185,7 @@ opt_type(host_config) ->
econf:map(econf:domain(), econf:list(econf:any())), econf:map(econf:domain(), econf:list(econf:any())),
fun econf:group_dups/1), fun econf:group_dups/1),
econf:map( econf:map(
econf:enum(ejabberd_config:get_option(hosts)), econf:enum(ejabberd_config:get_option(hosts)),
validator(), validator(),
[unique])); [unique]));
opt_type(hosts) -> opt_type(hosts) ->
@ -206,9 +212,9 @@ opt_type(ldap_deref_aliases) ->
opt_type(ldap_dn_filter) -> opt_type(ldap_dn_filter) ->
econf:and_then( econf:and_then(
econf:non_empty( econf:non_empty(
econf:map( econf:map(
econf:ldap_filter(), econf:ldap_filter(),
econf:list(econf:binary()))), econf:list(econf:binary()))),
fun hd/1); fun hd/1);
opt_type(ldap_encrypt) -> opt_type(ldap_encrypt) ->
econf:enum([tls, starttls, none]); econf:enum([tls, starttls, none]);
@ -233,9 +239,9 @@ opt_type(ldap_tls_verify) ->
opt_type(ldap_uids) -> opt_type(ldap_uids) ->
econf:either( econf:either(
econf:list( econf:list(
econf:and_then( econf:and_then(
econf:binary(), econf:binary(),
fun(U) -> {U, <<"%u">>} end)), fun(U) -> {U, <<"%u">>} end)),
econf:map(econf:binary(), econf:binary(), [unique])); econf:map(econf:binary(), econf:binary(), [unique]));
opt_type(listen) -> opt_type(listen) ->
ejabberd_listener:validator(); ejabberd_listener:validator();
@ -251,12 +257,12 @@ opt_type(log_modules_fully) ->
econf:list(econf:atom()); econf:list(econf:atom());
opt_type(loglevel) -> opt_type(loglevel) ->
fun(N) when is_integer(N) -> fun(N) when is_integer(N) ->
(econf:and_then( (econf:and_then(
econf:int(0, 5), econf:int(0, 5),
fun ejabberd_logger:convert_loglevel/1))(N); fun ejabberd_logger:convert_loglevel/1))(N);
(Level) -> (Level) ->
(econf:enum([none, emergency, alert, critical, (econf:enum([none, emergency, alert, critical,
error, warning, notice, info, debug]))(Level) error, warning, notice, info, debug]))(Level)
end; end;
opt_type(max_fsm_queue) -> opt_type(max_fsm_queue) ->
econf:pos_int(); econf:pos_int();
@ -299,12 +305,13 @@ opt_type(oom_watermark) ->
opt_type(outgoing_s2s_families) -> opt_type(outgoing_s2s_families) ->
econf:and_then( econf:and_then(
econf:non_empty( econf:non_empty(
econf:list(econf:enum([ipv4, ipv6]), [unique])), econf:list(econf:enum([ipv4, ipv6]), [unique])),
fun(L) -> fun(L) ->
lists:map( lists:map(
fun(ipv4) -> inet; fun(ipv4) -> inet;
(ipv6) -> inet6 (ipv6) -> inet6
end, L) end,
L)
end); end);
opt_type(outgoing_s2s_ipv4_address) -> opt_type(outgoing_s2s_ipv4_address) ->
econf:ipv4(); econf:ipv4();
@ -392,9 +399,9 @@ opt_type(s2s_zlib) ->
econf:and_then( econf:and_then(
econf:bool(), econf:bool(),
fun(false) -> false; fun(false) -> false;
(true) -> (true) ->
ejabberd:start_app(ezlib), ejabberd:start_app(ezlib),
true true
end); end);
opt_type(shaper) -> opt_type(shaper) ->
ejabberd_shaper:validator(shaper); ejabberd_shaper:validator(shaper);
@ -459,10 +466,10 @@ opt_type(version) ->
opt_type(websocket_origin) -> opt_type(websocket_origin) ->
econf:list( econf:list(
econf:and_then( econf:and_then(
econf:and_then( econf:and_then(
econf:binary_sep("\\s+"), econf:binary_sep("\\s+"),
econf:list(econf:url(), [unique])), econf:list(econf:url(), [unique])),
fun(L) -> str:join(L, <<" ">>) end), fun(L) -> str:join(L, <<" ">>) end),
[unique]); [unique]);
opt_type(websocket_ping_interval) -> opt_type(websocket_ping_interval) ->
econf:timeout(second); econf:timeout(second);
@ -474,22 +481,23 @@ opt_type(jwt_key) ->
fun(Path) -> fun(Path) ->
case file:read_file(Path) of case file:read_file(Path) of
{ok, Data} -> {ok, Data} ->
try jose_jwk:from_binary(Data) of try jose_jwk:from_binary(Data) of
{error, _} -> econf:fail({bad_jwt_key, Path}); {error, _} -> econf:fail({bad_jwt_key, Path});
JWK -> JWK ->
case jose_jwk:to_map(JWK) of case jose_jwk:to_map(JWK) of
{_, #{<<"keys">> := [Key]}} -> {_, #{<<"keys">> := [Key]}} ->
jose_jwk:from_map(Key); jose_jwk:from_map(Key);
{_, #{<<"keys">> := [_|_]}} -> {_, #{<<"keys">> := [_ | _]}} ->
econf:fail({bad_jwt_key_set, Path}); econf:fail({bad_jwt_key_set, Path});
{_, #{<<"keys">> := _}} -> {_, #{<<"keys">> := _}} ->
econf:fail({bad_jwt_key, Path}); econf:fail({bad_jwt_key, Path});
_ -> _ ->
JWK JWK
end end
catch _:_ -> catch
econf:fail({bad_jwt_key, Path}) _:_ ->
end; econf:fail({bad_jwt_key, Path})
end;
{error, Reason} -> {error, Reason} ->
econf:fail({read_file, Reason, Path}) econf:fail({read_file, Reason, Path})
end end
@ -499,36 +507,37 @@ opt_type(jwt_jid_field) ->
opt_type(jwt_auth_only_rule) -> opt_type(jwt_auth_only_rule) ->
econf:atom(). econf:atom().
%% We only define the types of options that cannot be derived %% We only define the types of options that cannot be derived
%% automatically by tools/opt_type.sh script %% automatically by tools/opt_type.sh script
-spec options() -> [{s2s_protocol_options, undefined | binary()} | -spec options() -> [{s2s_protocol_options, undefined | binary()} |
{c2s_protocol_options, undefined | binary()} | {c2s_protocol_options, undefined | binary()} |
{s2s_ciphers, undefined | binary()} | {s2s_ciphers, undefined | binary()} |
{c2s_ciphers, undefined | binary()} | {c2s_ciphers, undefined | binary()} |
{websocket_origin, [binary()]} | {websocket_origin, [binary()]} |
{disable_sasl_mechanisms, [binary()]} | {disable_sasl_mechanisms, [binary()]} |
{s2s_zlib, boolean()} | {s2s_zlib, boolean()} |
{loglevel, ejabberd_logger:loglevel()} | {loglevel, ejabberd_logger:loglevel()} |
{auth_opts, [{any(), any()}]} | {auth_opts, [{any(), any()}]} |
{listen, [ejabberd_listener:listener()]} | {listen, [ejabberd_listener:listener()]} |
{modules, [{module(), gen_mod:opts(), integer()}]} | {modules, [{module(), gen_mod:opts(), integer()}]} |
{ldap_uids, [{binary(), binary()}]} | {ldap_uids, [{binary(), binary()}]} |
{ldap_dn_filter, {binary(), [binary()]}} | {ldap_dn_filter, {binary(), [binary()]}} |
{outgoing_s2s_families, [inet | inet6, ...]} | {outgoing_s2s_families, [inet | inet6, ...]} |
{acl, [{atom(), [acl:acl_rule()]}]} | {acl, [{atom(), [acl:acl_rule()]}]} |
{access_rules, [{atom(), acl:access()}]} | {access_rules, [{atom(), acl:access()}]} |
{shaper, #{atom() => ejabberd_shaper:shaper_rate()}} | {shaper, #{atom() => ejabberd_shaper:shaper_rate()}} |
{shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} | {shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} |
{api_permissions, [ejabberd_access_permissions:permission()]} | {api_permissions, [ejabberd_access_permissions:permission()]} |
{jwt_key, jose_jwk:key() | undefined} | {jwt_key, jose_jwk:key() | undefined} |
{append_host_config, [{binary(), any()}]} | {append_host_config, [{binary(), any()}]} |
{host_config, [{binary(), any()}]} | {host_config, [{binary(), any()}]} |
{define_keyword, any()} | {define_keyword, any()} |
{define_macro, any()} | {define_macro, any()} |
{include_config_file, any()} | {include_config_file, any()} |
{atom(), any()}]. {atom(), any()}].
options() -> options() ->
[%% Top-priority options [ %% Top-priority options
hosts, hosts,
{loglevel, info}, {loglevel, info},
{cache_life_time, timer:seconds(3600)}, {cache_life_time, timer:seconds(3600)},
@ -549,10 +558,10 @@ options() ->
{anonymous_protocol, sasl_anon}, {anonymous_protocol, sasl_anon},
{api_permissions, {api_permissions,
[{<<"admin access">>, [{<<"admin access">>,
{[], {[],
[{acl, admin}, [{acl, admin},
{oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}], {oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}],
{all, [start, stop]}}}]}, {all, [start, stop]}}}]},
{append_host_config, []}, {append_host_config, []},
{auth_cache_life_time, {auth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end}, fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
@ -610,10 +619,10 @@ options() ->
{ldap_password, <<"">>}, {ldap_password, <<"">>},
{ldap_port, {ldap_port,
fun(Host) -> fun(Host) ->
case ejabberd_config:get_option({ldap_encrypt, Host}) of case ejabberd_config:get_option({ldap_encrypt, Host}) of
tls -> 636; tls -> 636;
_ -> 389 _ -> 389
end end
end}, end},
{ldap_rootdn, <<"">>}, {ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]}, {ldap_servers, [<<"localhost">>]},
@ -624,7 +633,7 @@ options() ->
{ldap_uids, [{<<"uid">>, <<"%u">>}]}, {ldap_uids, [{<<"uid">>, <<"%u">>}]},
{listen, []}, {listen, []},
{log_rotate_count, 1}, {log_rotate_count, 1},
{log_rotate_size, 10*1024*1024}, {log_rotate_size, 10 * 1024 * 1024},
{log_burst_limit_window_time, timer:seconds(1)}, {log_burst_limit_window_time, timer:seconds(1)},
{log_burst_limit_count, 500}, {log_burst_limit_count, 500},
{log_modules_fully, []}, {log_modules_fully, []},
@ -717,22 +726,22 @@ options() ->
{sql_database, undefined}, {sql_database, undefined},
{sql_keepalive_interval, undefined}, {sql_keepalive_interval, undefined},
{sql_password, <<"">>}, {sql_password, <<"">>},
{sql_odbc_driver, <<"libtdsodbc.so">>}, % default is FreeTDS driver {sql_odbc_driver, <<"libtdsodbc.so">>}, % default is FreeTDS driver
{sql_pool_size, {sql_pool_size,
fun(Host) -> fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of case ejabberd_config:get_option({sql_type, Host}) of
sqlite -> 1; sqlite -> 1;
_ -> 10 _ -> 10
end end
end}, end},
{sql_port, {sql_port,
fun(Host) -> fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of case ejabberd_config:get_option({sql_type, Host}) of
mssql -> 1433; mssql -> 1433;
mysql -> 3306; mysql -> 3306;
pgsql -> 5432; pgsql -> 5432;
_ -> undefined _ -> undefined
end end
end}, end},
{sql_query_timeout, timer:seconds(60)}, {sql_query_timeout, timer:seconds(60)},
{sql_queue_type, {sql_queue_type,
@ -755,6 +764,7 @@ options() ->
{jwt_jid_field, <<"jid">>}, {jwt_jid_field, <<"jid">>},
{jwt_auth_only_rule, none}]. {jwt_auth_only_rule, none}].
-spec globals() -> [atom()]. -spec globals() -> [atom()].
globals() -> globals() ->
[acme, [acme,
@ -829,9 +839,11 @@ globals() ->
websocket_ping_interval, websocket_ping_interval,
websocket_timeout]. websocket_timeout].
doc() -> doc() ->
ejabberd_options_doc:doc(). ejabberd_options_doc:doc().
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -845,21 +857,23 @@ validator() ->
Validators, Validators,
[{disallowed, Required ++ Disallowed}, unique])). [{disallowed, Required ++ Disallowed}, unique])).
-spec fqdn(global | binary()) -> [binary()]. -spec fqdn(global | binary()) -> [binary()].
fqdn(global) -> fqdn(global) ->
{ok, Hostname} = inet:gethostname(), {ok, Hostname} = inet:gethostname(),
case inet:gethostbyname(Hostname) of case inet:gethostbyname(Hostname) of
{ok, #hostent{h_name = FQDN}} -> {ok, #hostent{h_name = FQDN}} ->
case jid:nameprep(iolist_to_binary(FQDN)) of case jid:nameprep(iolist_to_binary(FQDN)) of
error -> []; error -> [];
Domain -> [Domain] Domain -> [Domain]
end; end;
{error, _} -> {error, _} ->
[] []
end; end;
fqdn(_) -> fqdn(_) ->
ejabberd_config:get_option(fqdn). ejabberd_config:get_option(fqdn).
-spec concat_binary(char()) -> fun(([binary()]) -> binary()). -spec concat_binary(char()) -> fun(([binary()]) -> binary()).
concat_binary(C) -> concat_binary(C) ->
fun(Opts) -> str:join(Opts, <<C>>) end. fun(Opts) -> str:join(Opts, <<C>>) end.

File diff suppressed because it is too large Load diff

View file

@ -36,11 +36,14 @@
-export([import_file/1, export_server/1, export_host/2]). -export([import_file/1, export_server/1, export_host/2]).
-define(CHUNK_SIZE, 1024*20). %20k -define(CHUNK_SIZE, 1024 * 20). %20k
-include_lib("xmpp/include/scram.hrl"). -include_lib("xmpp/include/scram.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-include("mod_roster.hrl"). -include("mod_roster.hrl").
@ -52,9 +55,13 @@
%%-define(INFO_MSG(M,Args),ok). %%-define(INFO_MSG(M,Args),ok).
%%%================================== %%%==================================
%%%% Import file %%%% Import file
-define(NS_PIE, <<"urn:xmpp:pie:0">>). -define(NS_PIE, <<"urn:xmpp:pie:0">>).
-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>). -define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>).
-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>). -define(NS_XI, <<"http://www.w3.org/2001/XInclude">>).
%%
%% @efmt:off
%% @indent-begin
-record(state, {xml_stream_state :: fxml_stream:xml_stream_state() | undefined, -record(state, {xml_stream_state :: fxml_stream:xml_stream_state() | undefined,
user = <<"">> :: binary(), user = <<"">> :: binary(),
@ -62,8 +69,13 @@
fd = self() :: file:io_device(), fd = self() :: file:io_device(),
dir = <<"">> :: binary()}). dir = <<"">> :: binary()}).
%% @indent-end
%% @efmt:on
%%
-type state() :: #state{}. -type state() :: #state{}.
%%File could be large.. we read it in chunks %%File could be large.. we read it in chunks
%%%=================================================================== %%%===================================================================
%%% API %%% API
@ -71,31 +83,37 @@
import_file(FileName) -> import_file(FileName) ->
import_file(FileName, #state{}). import_file(FileName, #state{}).
-spec import_file(binary(), state()) -> ok | {error, atom()}. -spec import_file(binary(), state()) -> ok | {error, atom()}.
import_file(FileName, State) -> import_file(FileName, State) ->
case file:open(FileName, [read, binary]) of case file:open(FileName, [read, binary]) of
{ok, Fd} -> {ok, Fd} ->
Dir = filename:dirname(FileName), Dir = filename:dirname(FileName),
XMLStreamState = fxml_stream:new(self(), infinity), XMLStreamState = fxml_stream:new(self(), infinity),
Res = process(State#state{xml_stream_state = XMLStreamState, Res = process(State#state{
fd = Fd, xml_stream_state = XMLStreamState,
dir = Dir}), fd = Fd,
dir = Dir
}),
file:close(Fd), file:close(Fd),
Res; Res;
{error, Reason} -> {error, Reason} ->
ErrTxt = file:format_error(Reason), ErrTxt = file:format_error(Reason),
?ERROR_MSG("Failed to open file '~ts': ~ts", [FileName, ErrTxt]), ?ERROR_MSG("Failed to open file '~ts': ~ts", [FileName, ErrTxt]),
{error, Reason} {error, Reason}
end. end.
-spec export_server(binary()) -> any(). -spec export_server(binary()) -> any().
export_server(Dir) -> export_server(Dir) ->
export_hosts(ejabberd_option:hosts(), Dir). export_hosts(ejabberd_option:hosts(), Dir).
-spec export_host(binary(), binary()) -> any(). -spec export_host(binary(), binary()) -> any().
export_host(Dir, Host) -> export_host(Dir, Host) ->
export_hosts([Host], Dir). export_hosts([Host], Dir).
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -106,12 +124,13 @@ export_hosts(Hosts, Dir) ->
{ok, Fd} -> {ok, Fd} ->
print(Fd, make_piefxis_xml_head()), print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_server_head()), print(Fd, make_piefxis_server_head()),
FilesAndHosts = [{make_host_filename(FnT, Host), Host} FilesAndHosts = [ {make_host_filename(FnT, Host), Host}
|| Host <- Hosts], || Host <- Hosts ],
lists:foreach( lists:foreach(
fun({FnH, _}) -> fun({FnH, _}) ->
print(Fd, make_xinclude(FnH)) print(Fd, make_xinclude(FnH))
end, FilesAndHosts), end,
FilesAndHosts),
print(Fd, make_piefxis_server_tail()), print(Fd, make_piefxis_server_tail()),
print(Fd, make_piefxis_xml_tail()), print(Fd, make_piefxis_xml_tail()),
file:close(Fd), file:close(Fd),
@ -120,13 +139,16 @@ export_hosts(Hosts, Dir) ->
export_host(Dir, FnH, Host); export_host(Dir, FnH, Host);
(_, Err) -> (_, Err) ->
Err Err
end, ok, FilesAndHosts); end,
ok,
FilesAndHosts);
{error, Reason} -> {error, Reason} ->
ErrTxt = file:format_error(Reason), ErrTxt = file:format_error(Reason),
?ERROR_MSG("Failed to open file '~ts': ~ts", [DFn, ErrTxt]), ?ERROR_MSG("Failed to open file '~ts': ~ts", [DFn, ErrTxt]),
{error, Reason} {error, Reason}
end. end.
export_host(Dir, FnH, Host) -> export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH), DFn = make_host_basefilename(Dir, FnH),
case file:open(DFn, [raw, write]) of case file:open(DFn, [raw, write]) of
@ -151,7 +173,8 @@ export_host(Dir, FnH, Host) ->
{error, Reason} {error, Reason}
end. end.
export_users([{User, _S}|Users], Server, Fd) ->
export_users([{User, _S} | Users], Server, Fd) ->
case export_user(User, Server, Fd) of case export_user(User, Server, Fd) of
ok -> ok ->
export_users(Users, Server, Fd); export_users(Users, Server, Fd);
@ -161,14 +184,15 @@ export_users([{User, _S}|Users], Server, Fd) ->
export_users([], _Server, _Fd) -> export_users([], _Server, _Fd) ->
ok. ok.
export_user(User, Server, Fd) -> export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server), Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
{PassPlain, PassScram} = case ejabberd_auth:password_format(LServer) of {PassPlain, PassScram} = case ejabberd_auth:password_format(LServer) of
scram -> {[], [format_scram_password(Password)]}; scram -> {[], [format_scram_password(Password)]};
_ when Password == <<"">> -> {[], []}; _ when Password == <<"">> -> {[], []};
_ -> {[{<<"password">>, Password}], []} _ -> {[{<<"password">>, Password}], []}
end, end,
Els = Els =
PassScram ++ PassScram ++
get_offline(User, Server) ++ get_offline(User, Server) ++
@ -176,13 +200,22 @@ export_user(User, Server, Fd) ->
get_privacy(User, Server) ++ get_privacy(User, Server) ++
get_roster(User, Server) ++ get_roster(User, Server) ++
get_private(User, Server), get_private(User, Server),
print(Fd, fxml:element_to_binary( print(Fd,
#xmlel{name = <<"user">>, fxml:element_to_binary(
attrs = [{<<"name">>, User} | PassPlain], #xmlel{
children = Els})). name = <<"user">>,
attrs = [{<<"name">>, User} | PassPlain],
children = Els
})).
format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = ServerKey,
salt = Salt, iterationcount = IterationCount}) -> format_scram_password(#scram{
hash = Hash,
storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
iterationcount = IterationCount
}) ->
StoredKeyB64 = base64:encode(StoredKey), StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey), ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt), SaltB64 = base64:encode(Salt),
@ -193,20 +226,29 @@ format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = Ser
sha512 -> <<"SCRAM-SHA-512">> sha512 -> <<"SCRAM-SHA-512">>
end, end,
Children = Children =
[ [#xmlel{
#xmlel{name = <<"iter-count">>, name = <<"iter-count">>,
children = [{xmlcdata, IterationCountBin}]}, children = [{xmlcdata, IterationCountBin}]
#xmlel{name = <<"salt">>, },
children = [{xmlcdata, SaltB64}]}, #xmlel{
#xmlel{name = <<"server-key">>, name = <<"salt">>,
children = [{xmlcdata, ServerKeyB64}]}, children = [{xmlcdata, SaltB64}]
#xmlel{name = <<"stored-key">>, },
children = [{xmlcdata, StoredKeyB64}]} #xmlel{
], name = <<"server-key">>,
#xmlel{name = <<"scram-credentials">>, children = [{xmlcdata, ServerKeyB64}]
attrs = [{<<"xmlns">>, <<?NS_PIE/binary, "#scram">>}, },
{<<"mechanism">>, MechanismB}], #xmlel{
children = Children}. name = <<"stored-key">>,
children = [{xmlcdata, StoredKeyB64}]
}],
#xmlel{
name = <<"scram-credentials">>,
attrs = [{<<"xmlns">>, <<?NS_PIE/binary, "#scram">>},
{<<"mechanism">>, MechanismB}],
children = Children
}.
parse_scram_password(#xmlel{attrs = Attrs} = El) -> parse_scram_password(#xmlel{attrs = Attrs} = El) ->
Hash = case fxml:get_attr_s(<<"mechanism">>, Attrs) of Hash = case fxml:get_attr_s(<<"mechanism">>, Attrs) of
@ -219,40 +261,42 @@ parse_scram_password(#xmlel{attrs = Attrs} = El) ->
IterationCountBin = fxml:get_path_s(El, [{elem, <<"iter-count">>}, cdata]), IterationCountBin = fxml:get_path_s(El, [{elem, <<"iter-count">>}, cdata]),
SaltB64 = fxml:get_path_s(El, [{elem, <<"salt">>}, cdata]), SaltB64 = fxml:get_path_s(El, [{elem, <<"salt">>}, cdata]),
#scram{ #scram{
storedkey = base64:decode(StoredKeyB64), storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64), serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64), salt = base64:decode(SaltB64),
hash = Hash, hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin)) iterationcount = (binary_to_integer(IterationCountBin))
}; };
parse_scram_password(PassData) -> parse_scram_password(PassData) ->
Split = binary:split(PassData, <<",">>, [global]), Split = binary:split(PassData, <<",">>, [global]),
[Hash, StoredKeyB64, ServerKeyB64, SaltB64, IterationCountBin] = [Hash, StoredKeyB64, ServerKeyB64, SaltB64, IterationCountBin] =
case Split of case Split of
[K1, K2, K3, K4] -> [sha, K1, K2, K3, K4]; [K1, K2, K3, K4] -> [sha, K1, K2, K3, K4];
[<<"sha256">>, K1, K2, K3, K4] -> [sha256, K1, K2, K3, K4]; [<<"sha256">>, K1, K2, K3, K4] -> [sha256, K1, K2, K3, K4];
[<<"sha512">>, K1, K2, K3, K4] -> [sha512, K1, K2, K3, K4] [<<"sha512">>, K1, K2, K3, K4] -> [sha512, K1, K2, K3, K4]
end, end,
#scram{ #scram{
storedkey = base64:decode(StoredKeyB64), storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64), serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64), salt = base64:decode(SaltB64),
hash = Hash, hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin)) iterationcount = (binary_to_integer(IterationCountBin))
}. }.
-spec get_vcard(binary(), binary()) -> [xmlel()]. -spec get_vcard(binary(), binary()) -> [xmlel()].
get_vcard(User, Server) -> get_vcard(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
try mod_vcard:get_vcard(LUser, LServer) of try mod_vcard:get_vcard(LUser, LServer) of
error -> []; error -> [];
Els -> Els Els -> Els
catch catch
error:{module_not_loaded, _, _} -> [] error:{module_not_loaded, _, _} -> []
end. end.
-spec get_offline(binary(), binary()) -> [xmlel()]. -spec get_offline(binary(), binary()) -> [xmlel()].
get_offline(User, Server) -> get_offline(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
@ -267,76 +311,92 @@ get_offline(User, Server) ->
error:{module_not_loaded, _, _} -> [] error:{module_not_loaded, _, _} -> []
end. end.
-spec get_privacy(binary(), binary()) -> [xmlel()]. -spec get_privacy(binary(), binary()) -> [xmlel()].
get_privacy(User, Server) -> get_privacy(User, Server) ->
try mod_privacy:get_user_lists(User, Server) of try mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default, {ok, #privacy{
lists = [_|_] = Lists}} -> default = Default,
lists = [_ | _] = Lists
}} ->
XLists = lists:map( XLists = lists:map(
fun({Name, Items}) -> fun({Name, Items}) ->
XItems = lists:map( XItems = lists:map(
fun mod_privacy:encode_list_item/1, fun mod_privacy:encode_list_item/1,
Items), Items),
#privacy_list{name = Name, items = XItems} #privacy_list{name = Name, items = XItems}
end, Lists), end,
[xmpp:encode(#privacy_query{default = Default, lists = XLists})]; Lists),
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
_ -> _ ->
[] []
catch catch
error:{module_not_loaded, _, _} -> [] error:{module_not_loaded, _, _} -> []
end. end.
-spec get_roster(binary(), binary()) -> [xmlel()]. -spec get_roster(binary(), binary()) -> [xmlel()].
get_roster(User, Server) -> get_roster(User, Server) ->
JID = jid:make(User, Server), JID = jid:make(User, Server),
try mod_roster:get_roster(User, Server) of try mod_roster:get_roster(User, Server) of
[_|_] = Items -> [_ | _] = Items ->
Subs = Subs =
lists:flatmap( lists:flatmap(
fun(#roster{ask = Ask, fun(#roster{
askmessage = Msg} = R) ask = Ask,
askmessage = Msg
} = R)
when Ask == in; Ask == both -> when Ask == in; Ask == both ->
Status = if is_binary(Msg) -> (Msg); Status = if
true -> <<"">> is_binary(Msg) -> (Msg);
true -> <<"">>
end, end,
[xmpp:encode( [xmpp:encode(
#presence{from = jid:make(R#roster.jid), #presence{
to = JID, from = jid:make(R#roster.jid),
type = subscribe, to = JID,
status = xmpp:mk_text(Status)})]; type = subscribe,
status = xmpp:mk_text(Status)
})];
(_) -> (_) ->
[] []
end, Items), end,
Items),
Rs = lists:flatmap( Rs = lists:flatmap(
fun(#roster{ask = in, subscription = none}) -> fun(#roster{ask = in, subscription = none}) ->
[]; [];
(R) -> (R) ->
[mod_roster:encode_item(R)] [mod_roster:encode_item(R)]
end, Items), end,
[xmpp:encode(#roster_query{items = Rs}) | Subs]; Items),
[xmpp:encode(#roster_query{items = Rs}) | Subs];
_ -> _ ->
[] []
catch catch
error:{module_not_loaded, _, _} -> [] error:{module_not_loaded, _, _} -> []
end. end.
-spec get_private(binary(), binary()) -> [xmlel()]. -spec get_private(binary(), binary()) -> [xmlel()].
get_private(User, Server) -> get_private(User, Server) ->
try mod_private:get_data(User, Server) of try mod_private:get_data(User, Server) of
[_|_] = Els -> [_ | _] = Els ->
[xmpp:encode(#private{sub_els = Els})]; [xmpp:encode(#private{sub_els = Els})];
_ -> _ ->
[] []
catch catch
error:{module_not_loaded, _, _} -> [] error:{module_not_loaded, _, _} -> []
end. end.
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) -> process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
case file:read(Fd, ?CHUNK_SIZE) of case file:read(Fd, ?CHUNK_SIZE) of
{ok, Data} -> {ok, Data} ->
NewXMLStreamState = fxml_stream:parse(XMLStreamState, Data), NewXMLStreamState = fxml_stream:parse(XMLStreamState, Data),
case process_els(State#state{xml_stream_state = case process_els(State#state{
NewXMLStreamState}) of xml_stream_state =
NewXMLStreamState
}) of
{ok, NewState} -> {ok, NewState} ->
process(NewState); process(NewState);
Err -> Err ->
@ -348,17 +408,21 @@ process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
ok ok
end. end.
process_els(State) -> process_els(State) ->
Els = gather_els(State, []), Els = gather_els(State, []),
process_els(State, lists:reverse(Els)). process_els(State, lists:reverse(Els)).
gather_els(State, List) -> gather_els(State, List) ->
receive receive
{'$gen_event', El} -> {'$gen_event', El} ->
gather_els(State, [El | List]) gather_els(State, [El | List])
after 0 -> after
List 0 ->
end. List
end.
process_els(State, [El | Tail]) -> process_els(State, [El | Tail]) ->
case process_el(El, State) of case process_el(El, State) of
@ -370,6 +434,7 @@ process_els(State, [El | Tail]) ->
process_els(State, []) -> process_els(State, []) ->
{ok, State}. {ok, State}.
process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) -> process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_PIEFXIS -> ?NS_PIEFXIS ->
@ -383,8 +448,10 @@ process_el({xmlstreamend, _}, State) ->
{ok, State}; {ok, State};
process_el({xmlstreamcdata, _}, State) -> process_el({xmlstreamcdata, _}, State) ->
{ok, State}; {ok, State};
process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>, process_el({xmlstreamelement, #xmlel{
attrs = Attrs}}, name = <<"xi:include">>,
attrs = Attrs
}},
#state{dir = Dir, user = <<"">>} = State) -> #state{dir = Dir, user = <<"">>} = State) ->
FileName = fxml:get_attr_s(<<"href">>, Attrs), FileName = fxml:get_attr_s(<<"href">>, Attrs),
case import_file(filename:join([Dir, FileName]), State) of case import_file(filename:join([Dir, FileName]), State) of
@ -394,11 +461,17 @@ process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
Err Err
end; end;
process_el({xmlstreamstart, <<"host">>, Attrs}, State) -> process_el({xmlstreamstart, <<"host">>, Attrs}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"host">>, process_el({xmlstreamelement, #xmlel{
attrs = Attrs}}, State); name = <<"host">>,
process_el({xmlstreamelement, #xmlel{name = <<"host">>, attrs = Attrs
attrs = Attrs, }},
children = Els}}, State) -> State);
process_el({xmlstreamelement, #xmlel{
name = <<"host">>,
attrs = Attrs,
children = Els
}},
State) ->
JIDS = fxml:get_attr_s(<<"jid">>, Attrs), JIDS = fxml:get_attr_s(<<"jid">>, Attrs),
try jid:decode(JIDS) of try jid:decode(JIDS) of
#jid{lserver = S} -> #jid{lserver = S} ->
@ -408,7 +481,8 @@ process_el({xmlstreamelement, #xmlel{name = <<"host">>,
false -> false ->
stop("Unknown host: ~ts", [S]) stop("Unknown host: ~ts", [S])
end end
catch _:{bad_jid, _} -> catch
_:{bad_jid, _} ->
stop("Invalid 'jid': ~ts", [JIDS]) stop("Invalid 'jid': ~ts", [JIDS])
end; end;
process_el({xmlstreamstart, <<"user">>, Attrs}, State = #state{server = S}) process_el({xmlstreamstart, <<"user">>, Attrs}, State = #state{server = S})
@ -428,18 +502,20 @@ process_el({xmlstreamstart, El, Attrs}, _State) ->
process_el({xmlstreamerror, Err}, _State) -> process_el({xmlstreamerror, Err}, _State) ->
stop("Failed to process element = ~p", [Err]). stop("Failed to process element = ~p", [Err]).
process_users([#xmlel{} = El|Els], State) ->
process_users([#xmlel{} = El | Els], State) ->
case process_user(El, State) of case process_user(El, State) of
{ok, NewState} -> {ok, NewState} ->
process_users(Els, NewState); process_users(Els, NewState);
Err -> Err ->
Err Err
end; end;
process_users([_|Els], State) -> process_users([_ | Els], State) ->
process_users(Els, State); process_users(Els, State);
process_users([], State) -> process_users([], State) ->
{ok, State}. {ok, State}.
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El, process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El,
#state{server = LServer} = State) -> #state{server = LServer} = State) ->
Name = fxml:get_attr_s(<<"name">>, Attrs), Name = fxml:get_attr_s(<<"name">>, Attrs),
@ -458,11 +534,12 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El,
end end
end. end.
process_password(#xmlel{name = <<"user">>, attrs = Attrs} = El, LServer) -> process_password(#xmlel{name = <<"user">>, attrs = Attrs} = El, LServer) ->
{PassPlain, PassOldScram} = case fxml:get_attr_s(<<"password">>, Attrs) of {PassPlain, PassOldScram} = case fxml:get_attr_s(<<"password">>, Attrs) of
<<"scram:", PassData/binary>> -> {<<"">>, PassData}; <<"scram:", PassData/binary>> -> {<<"">>, PassData};
P -> {P, false} P -> {P, false}
end, end,
ScramCred = fxml:get_subtag(El, <<"scram-credentials">>), ScramCred = fxml:get_subtag(El, <<"scram-credentials">>),
PasswordFormat = ejabberd_auth:password_format(LServer), PasswordFormat = ejabberd_auth:password_format(LServer),
case {PassPlain, PassOldScram, ScramCred, PasswordFormat} of case {PassPlain, PassOldScram, ScramCred, PasswordFormat} of
@ -474,58 +551,63 @@ process_password(#xmlel{name = <<"user">>, attrs = Attrs} = El, LServer) ->
{<<"">>, PassOldScram, false, scram} -> parse_scram_password(PassOldScram) {<<"">>, PassOldScram, false, scram} -> parse_scram_password(PassOldScram)
end. end.
process_user_els([#xmlel{} = El|Els], State) ->
process_user_els([#xmlel{} = El | Els], State) ->
case process_user_el(El, State) of case process_user_el(El, State) of
{ok, NewState} -> {ok, NewState} ->
process_user_els(Els, NewState); process_user_els(Els, NewState);
Err -> Err ->
Err Err
end; end;
process_user_els([_|Els], State) -> process_user_els([_ | Els], State) ->
process_user_els(Els, State); process_user_els(Els, State);
process_user_els([], State) -> process_user_els([], State) ->
{ok, State}. {ok, State}.
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El, process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) -> State) ->
try try
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} -> {<<"query">>, ?NS_ROSTER} ->
process_roster(xmpp:decode(El), State); process_roster(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVACY} -> {<<"query">>, ?NS_PRIVACY} ->
%% Make sure <list/> elements go before <active/> and <default/> %% Make sure <list/> elements go before <active/> and <default/>
process_privacy(xmpp:decode(El), State); process_privacy(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVATE} -> {<<"query">>, ?NS_PRIVATE} ->
process_private(xmpp:decode(El), State); process_private(xmpp:decode(El), State);
{<<"vCard">>, ?NS_VCARD} -> {<<"vCard">>, ?NS_VCARD} ->
process_vcard(xmpp:decode(El), State); process_vcard(xmpp:decode(El), State);
{<<"offline-messages">>, NS} -> {<<"offline-messages">>, NS} ->
Msgs = [xmpp:decode(E, NS, [ignore_els]) || E <- Els], Msgs = [ xmpp:decode(E, NS, [ignore_els]) || E <- Els ],
process_offline_msgs(Msgs, State); process_offline_msgs(Msgs, State);
{<<"presence">>, ?NS_CLIENT} -> {<<"presence">>, ?NS_CLIENT} ->
process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State); process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State);
_ -> _ ->
{ok, State} {ok, State}
end end
catch _:{xmpp_codec, Why} -> catch
ErrTxt = xmpp:format_error(Why), _:{xmpp_codec, Why} ->
stop("failed to decode XML '~ts': ~ts", ErrTxt = xmpp:format_error(Why),
[fxml:element_to_binary(El), ErrTxt]) stop("failed to decode XML '~ts': ~ts",
[fxml:element_to_binary(El), ErrTxt])
end. end.
-spec process_offline_msgs([stanza()], state()) -> {ok, state()} | {error, _}. -spec process_offline_msgs([stanza()], state()) -> {ok, state()} | {error, _}.
process_offline_msgs([#message{} = Msg|Msgs], State) -> process_offline_msgs([#message{} = Msg | Msgs], State) ->
case process_offline_msg(Msg, State) of case process_offline_msg(Msg, State) of
{ok, NewState} -> {ok, NewState} ->
process_offline_msgs(Msgs, NewState); process_offline_msgs(Msgs, NewState);
Err -> Err ->
Err Err
end; end;
process_offline_msgs([_|Msgs], State) -> process_offline_msgs([_ | Msgs], State) ->
process_offline_msgs(Msgs, State); process_offline_msgs(Msgs, State);
process_offline_msgs([], State) -> process_offline_msgs([], State) ->
{ok, State}. {ok, State}.
-spec process_roster(roster_query(), state()) -> {ok, state()} | {error, _}. -spec process_roster(roster_query(), state()) -> {ok, state()} | {error, _}.
process_roster(RosterQuery, State = #state{user = U, server = S}) -> process_roster(RosterQuery, State = #state{user = U, server = S}) ->
case mod_roster:set_items(U, S, RosterQuery) of case mod_roster:set_items(U, S, RosterQuery) of
@ -535,51 +617,69 @@ process_roster(RosterQuery, State = #state{user = U, server = S}) ->
stop("Failed to write roster: ~p", [Err]) stop("Failed to write roster: ~p", [Err])
end. end.
-spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}. -spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}.
process_privacy(#privacy_query{lists = Lists, process_privacy(#privacy_query{
default = Default, lists = Lists,
active = Active}, default = Default,
State = #state{user = U, server = S}) -> active = Active
},
State = #state{user = U, server = S}) ->
JID = jid:make(U, S), JID = jid:make(U, S),
if Lists /= undefined -> if
process_privacy2(JID, #privacy_query{lists = Lists}); Lists /= undefined ->
true -> process_privacy2(JID, #privacy_query{lists = Lists});
ok true ->
ok
end, end,
if Active /= undefined -> if
process_privacy2(JID, #privacy_query{active = Active}); Active /= undefined ->
true -> process_privacy2(JID, #privacy_query{active = Active});
ok true ->
ok
end, end,
if Default /= undefined -> if
process_privacy2(JID, #privacy_query{default = Default}); Default /= undefined ->
true -> process_privacy2(JID, #privacy_query{default = Default});
ok true ->
ok
end, end,
{ok, State}. {ok, State}.
process_privacy2(JID, PQ) -> process_privacy2(JID, PQ) ->
case mod_privacy:process_iq(#iq{type = set, id = p1_rand:get_string(), case mod_privacy:process_iq(#iq{
from = JID, to = JID, type = set,
sub_els = [PQ]}) of id = p1_rand:get_string(),
#iq{type = error} = ResIQ -> from = JID,
#stanza_error{reason = Reason} = xmpp:get_error(ResIQ), to = JID,
if Reason /= 'item-not-found' -> sub_els = [PQ]
%% Failed to set default list because there is no }) of
%% list with such name. We shouldn't stop here. #iq{type = error} = ResIQ ->
stop("Failed to write default privacy: ~p", [Reason]); #stanza_error{reason = Reason} = xmpp:get_error(ResIQ),
if
Reason /= 'item-not-found' ->
%% Failed to set default list because there is no
%% list with such name. We shouldn't stop here.
stop("Failed to write default privacy: ~p", [Reason]);
true -> true ->
ok ok
end; end;
_ -> _ ->
ok ok
end. end.
-spec process_private(private(), state()) -> {ok, state()} | {error, _}. -spec process_private(private(), state()) -> {ok, state()} | {error, _}.
process_private(Private, State = #state{user = U, server = S}) -> process_private(Private, State = #state{user = U, server = S}) ->
JID = jid:make(U, S), JID = jid:make(U, S),
IQ = #iq{type = set, id = p1_rand:get_string(), IQ = #iq{
from = JID, to = JID, sub_els = [Private]}, type = set,
id = p1_rand:get_string(),
from = JID,
to = JID,
sub_els = [Private]
},
case mod_private:process_sm_iq(IQ) of case mod_private:process_sm_iq(IQ) of
#iq{type = result} -> #iq{type = result} ->
{ok, State}; {ok, State};
@ -587,11 +687,17 @@ process_private(Private, State = #state{user = U, server = S}) ->
stop("Failed to write private: ~p", [Err]) stop("Failed to write private: ~p", [Err])
end. end.
-spec process_vcard(xmpp_element(), state()) -> {ok, state()} | {error, _}. -spec process_vcard(xmpp_element(), state()) -> {ok, state()} | {error, _}.
process_vcard(El, State = #state{user = U, server = S}) -> process_vcard(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S), JID = jid:make(U, S),
IQ = #iq{type = set, id = p1_rand:get_string(), IQ = #iq{
from = JID, to = JID, sub_els = [El]}, type = set,
id = p1_rand:get_string(),
from = JID,
to = JID,
sub_els = [El]
},
case mod_vcard:process_sm_iq(IQ) of case mod_vcard:process_sm_iq(IQ) of
#iq{type = result} -> #iq{type = result} ->
{ok, State}; {ok, State};
@ -599,6 +705,7 @@ process_vcard(El, State = #state{user = U, server = S}) ->
stop("Failed to write vcard: ~p", [Err]) stop("Failed to write vcard: ~p", [Err])
end. end.
-spec process_offline_msg(message(), state()) -> {ok, state()} | {error, _}. -spec process_offline_msg(message(), state()) -> {ok, state()} | {error, _}.
process_offline_msg(#message{from = undefined}, _State) -> process_offline_msg(#message{from = undefined}, _State) ->
stop("No 'from' attribute found", []); stop("No 'from' attribute found", []);
@ -608,6 +715,7 @@ process_offline_msg(Msg, State = #state{user = U, server = S}) ->
offline_message_hook, To#jid.lserver, {pass, xmpp:set_to(Msg, To)}, []), offline_message_hook, To#jid.lserver, {pass, xmpp:set_to(Msg, To)}, []),
{ok, State}. {ok, State}.
-spec process_presence(presence(), state()) -> {ok, state()} | {error, _}. -spec process_presence(presence(), state()) -> {ok, state()} | {error, _}.
process_presence(#presence{from = undefined}, _State) -> process_presence(#presence{from = undefined}, _State) ->
stop("No 'from' attribute found", []); stop("No 'from' attribute found", []);
@ -617,19 +725,23 @@ process_presence(Pres, #state{user = U, server = S} = State) ->
ejabberd_router:route(NewPres), ejabberd_router:route(NewPres),
{ok, State}. {ok, State}.
stop(Fmt, Args) -> stop(Fmt, Args) ->
?ERROR_MSG(Fmt, Args), ?ERROR_MSG(Fmt, Args),
{error, import_failed}. {error, import_failed}.
make_filename_template() -> make_filename_template() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
str:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w", str:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
[Year, Month, Day, Hour, Minute, Second]). [Year, Month, Day, Hour, Minute, Second]).
make_main_basefilename(Dir, FnT) -> make_main_basefilename(Dir, FnT) ->
Filename2 = <<FnT/binary, ".xml">>, Filename2 = <<FnT/binary, ".xml">>,
filename:join([Dir, Filename2]). filename:join([Dir, Filename2]).
%% @doc Make the filename for the host. %% @doc Make the filename for the host.
%% Example: ``(<<"20080804-231550">>, <<"xmpp.domain.tld">>) -> %% Example: ``(<<"20080804-231550">>, <<"xmpp.domain.tld">>) ->
%% <<"20080804-231550_xmpp_domain_tld.xml">>'' %% <<"20080804-231550_xmpp_domain_tld.xml">>''
@ -637,34 +749,43 @@ make_host_filename(FnT, Host) ->
Host2 = str:join(str:tokens(Host, <<".">>), <<"_">>), Host2 = str:join(str:tokens(Host, <<".">>), <<"_">>),
<<FnT/binary, "_", Host2/binary, ".xml">>. <<FnT/binary, "_", Host2/binary, ".xml">>.
%%%================================== %%%==================================
%%%% PIEFXIS formatting %%%% PIEFXIS formatting
make_host_basefilename(Dir, FnT) -> make_host_basefilename(Dir, FnT) ->
filename:join([Dir, FnT]). filename:join([Dir, FnT]).
make_piefxis_xml_head() -> make_piefxis_xml_head() ->
"<?xml version='1.0' encoding='UTF-8'?>". "<?xml version='1.0' encoding='UTF-8'?>".
make_piefxis_xml_tail() -> make_piefxis_xml_tail() ->
"". "".
make_piefxis_server_head() -> make_piefxis_server_head() ->
io_lib:format("<server-data xmlns='~ts' xmlns:xi='~ts'>", io_lib:format("<server-data xmlns='~ts' xmlns:xi='~ts'>",
[?NS_PIE, ?NS_XI]). [?NS_PIE, ?NS_XI]).
make_piefxis_server_tail() -> make_piefxis_server_tail() ->
"</server-data>". "</server-data>".
make_piefxis_host_head(Host) -> make_piefxis_host_head(Host) ->
io_lib:format("<host xmlns='~ts' xmlns:xi='~ts' jid='~ts'>", io_lib:format("<host xmlns='~ts' xmlns:xi='~ts' jid='~ts'>",
[?NS_PIE, ?NS_XI, Host]). [?NS_PIE, ?NS_XI, Host]).
make_piefxis_host_tail() -> make_piefxis_host_tail() ->
"</host>". "</host>".
make_xinclude(Fn) -> make_xinclude(Fn) ->
Base = filename:basename(Fn), Base = filename:basename(Fn),
io_lib:format("<xi:include href='~ts'/>", [Base]). io_lib:format("<xi:include href='~ts'/>", [Base]).
print(Fd, String) -> print(Fd, String) ->
file:write(Fd, String). file:write(Fd, String).

View file

@ -33,8 +33,12 @@
%% Hooks %% Hooks
-export([ejabberd_started/0, config_reloaded/0, cert_expired/2]). -export([ejabberd_started/0, config_reloaded/0, cert_expired/2]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-include("logger.hrl"). -include("logger.hrl").
-define(CALL_TIMEOUT, timer:minutes(1)). -define(CALL_TIMEOUT, timer:minutes(1)).
@ -44,6 +48,7 @@
-type state() :: #state{}. -type state() :: #state{}.
-type filename() :: binary(). -type filename() :: binary().
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
@ -51,96 +56,118 @@
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec add_certfile(file:filename_all()) -> {ok, filename()} | {error, pkix:error_reason()}. -spec add_certfile(file:filename_all()) -> {ok, filename()} | {error, pkix:error_reason()}.
add_certfile(Path0) -> add_certfile(Path0) ->
Path = prep_path(Path0), Path = prep_path(Path0),
try gen_server:call(?MODULE, {add_certfile, Path}, ?CALL_TIMEOUT) try
catch exit:{noproc, _} -> gen_server:call(?MODULE, {add_certfile, Path}, ?CALL_TIMEOUT)
case add_file(Path) of catch
ok -> {ok, Path}; exit:{noproc, _} ->
Err -> Err case add_file(Path) of
end ok -> {ok, Path};
Err -> Err
end
end. end.
-spec del_certfile(file:filename_all()) -> ok. -spec del_certfile(file:filename_all()) -> ok.
del_certfile(Path0) -> del_certfile(Path0) ->
Path = prep_path(Path0), Path = prep_path(Path0),
try gen_server:call(?MODULE, {del_certfile, Path}, ?CALL_TIMEOUT) try
catch exit:{noproc, _} -> gen_server:call(?MODULE, {del_certfile, Path}, ?CALL_TIMEOUT)
pkix:del_file(Path) catch
exit:{noproc, _} ->
pkix:del_file(Path)
end. end.
-spec try_certfile(file:filename_all()) -> filename(). -spec try_certfile(file:filename_all()) -> filename().
try_certfile(Path0) -> try_certfile(Path0) ->
Path = prep_path(Path0), Path = prep_path(Path0),
case pkix:is_pem_file(Path) of case pkix:is_pem_file(Path) of
true -> Path; true -> Path;
{false, Reason} -> {false, Reason} ->
?ERROR_MSG("Failed to read PEM file ~ts: ~ts", ?ERROR_MSG("Failed to read PEM file ~ts: ~ts",
[Path, pkix:format_error(Reason)]), [Path, pkix:format_error(Reason)]),
erlang:error(badarg) erlang:error(badarg)
end. end.
-spec get_certfile(binary()) -> {ok, filename()} | error. -spec get_certfile(binary()) -> {ok, filename()} | error.
get_certfile(Domain) -> get_certfile(Domain) ->
case get_certfile_no_default(Domain) of case get_certfile_no_default(Domain) of
{ok, Path} -> {ok, Path} ->
{ok, Path}; {ok, Path};
error -> error ->
get_certfile() get_certfile()
end. end.
-spec get_certfile_no_default(binary()) -> {ok, filename()} | error. -spec get_certfile_no_default(binary()) -> {ok, filename()} | error.
get_certfile_no_default(Domain) -> get_certfile_no_default(Domain) ->
try list_to_binary(idna:utf8_to_ascii(Domain)) of try list_to_binary(idna:utf8_to_ascii(Domain)) of
ASCIIDomain -> ASCIIDomain ->
case pkix:get_certfile(ASCIIDomain) of case pkix:get_certfile(ASCIIDomain) of
error -> error; error -> error;
Ret -> {ok, select_certfile(Ret)} Ret -> {ok, select_certfile(Ret)}
end end
catch _:_ -> catch
error _:_ ->
error
end. end.
-spec get_certfile() -> {ok, filename()} | error. -spec get_certfile() -> {ok, filename()} | error.
get_certfile() -> get_certfile() ->
case pkix:get_certfile() of case pkix:get_certfile() of
error -> error; error -> error;
Ret -> {ok, select_certfile(Ret)} Ret -> {ok, select_certfile(Ret)}
end. end.
-spec certs_dir() -> file:filename_all(). -spec certs_dir() -> file:filename_all().
certs_dir() -> certs_dir() ->
MnesiaDir = mnesia:system_info(directory), MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "certs"). filename:join(MnesiaDir, "certs").
-spec commit() -> ok. -spec commit() -> ok.
commit() -> commit() ->
gen_server:call(?MODULE, commit, ?CALL_TIMEOUT). gen_server:call(?MODULE, commit, ?CALL_TIMEOUT).
-spec ejabberd_started() -> ok. -spec ejabberd_started() -> ok.
ejabberd_started() -> ejabberd_started() ->
gen_server:call(?MODULE, ejabberd_started, ?CALL_TIMEOUT). gen_server:call(?MODULE, ejabberd_started, ?CALL_TIMEOUT).
-spec config_reloaded() -> ok. -spec config_reloaded() -> ok.
config_reloaded() -> config_reloaded() ->
gen_server:call(?MODULE, config_reloaded, ?CALL_TIMEOUT). gen_server:call(?MODULE, config_reloaded, ?CALL_TIMEOUT).
-spec notify_expired(pkix:notify_event()) -> ok. -spec notify_expired(pkix:notify_event()) -> ok.
notify_expired(Event) -> notify_expired(Event) ->
gen_server:cast(?MODULE, Event). gen_server:cast(?MODULE, Event).
-spec cert_expired(_, pkix:cert_info()) -> ok. -spec cert_expired(_, pkix:cert_info()) -> ok.
cert_expired(_Cert, #{domains := Domains, cert_expired(_Cert,
expiry := Expiry, #{
files := [{Path, Line}|_]}) -> domains := Domains,
expiry := Expiry,
files := [{Path, Line} | _]
}) ->
?WARNING_MSG("Certificate in ~ts (at line: ~B)~ts ~ts", ?WARNING_MSG("Certificate in ~ts (at line: ~B)~ts ~ts",
[Path, Line, [Path,
case Domains of Line,
[] -> ""; case Domains of
_ -> " for " ++ misc:format_hosts_list(Domains) [] -> "";
end, _ -> " for " ++ misc:format_hosts_list(Domains)
format_expiration_date(Expiry)]). end,
format_expiration_date(Expiry)]).
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
@ -152,47 +179,48 @@ init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 100), ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 100),
ejabberd_hooks:add(ejabberd_started, ?MODULE, ejabberd_started, 30), ejabberd_hooks:add(ejabberd_started, ?MODULE, ejabberd_started, 30),
case add_files() of case add_files() of
{_Files, []} -> {_Files, []} ->
{ok, #state{}}; {ok, #state{}};
{Files, [_|_]} -> {Files, [_ | _]} ->
case ejabberd:is_loaded() of case ejabberd:is_loaded() of
true -> true ->
{ok, #state{}}; {ok, #state{}};
false -> false ->
del_files(Files), del_files(Files),
stop_ejabberd() stop_ejabberd()
end end
end. end.
-spec handle_call(term(), {pid(), term()}, state()) -> -spec handle_call(term(), {pid(), term()}, state()) ->
{reply, ok, state()} | {noreply, state()}. {reply, ok, state()} | {noreply, state()}.
handle_call({add_certfile, Path}, _From, State) -> handle_call({add_certfile, Path}, _From, State) ->
case add_file(Path) of case add_file(Path) of
ok -> ok ->
{reply, {ok, Path}, State}; {reply, {ok, Path}, State};
{error, _} = Err -> {error, _} = Err ->
{reply, Err, State} {reply, Err, State}
end; end;
handle_call({del_certfile, Path}, _From, State) -> handle_call({del_certfile, Path}, _From, State) ->
pkix:del_file(Path), pkix:del_file(Path),
{reply, ok, State}; {reply, ok, State};
handle_call(ejabberd_started, _From, State) -> handle_call(ejabberd_started, _From, State) ->
case do_commit() of case do_commit() of
{ok, []} -> {ok, []} ->
check_domain_certfiles(), check_domain_certfiles(),
{reply, ok, State}; {reply, ok, State};
_ -> _ ->
stop_ejabberd() stop_ejabberd()
end; end;
handle_call(config_reloaded, _From, State) -> handle_call(config_reloaded, _From, State) ->
Files = get_certfiles_from_config_options(), Files = get_certfiles_from_config_options(),
_ = add_files(Files), _ = add_files(Files),
case do_commit() of case do_commit() of
{ok, _} -> {ok, _} ->
check_domain_certfiles(), check_domain_certfiles(),
{reply, ok, State}; {reply, ok, State};
error -> error ->
{reply, ok, State} {reply, ok, State}
end; end;
handle_call(commit, From, State) -> handle_call(commit, From, State) ->
handle_call(config_reloaded, From, State); handle_call(config_reloaded, From, State);
@ -200,6 +228,7 @@ handle_call(Request, _From, State) ->
?WARNING_MSG("Unexpected call: ~p", [Request]), ?WARNING_MSG("Unexpected call: ~p", [Request]),
{noreply, State}. {noreply, State}.
-spec handle_cast(term(), state()) -> {noreply, state()}. -spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast({cert_expired, Cert, CertInfo}, State) -> handle_cast({cert_expired, Cert, CertInfo}, State) ->
ejabberd_hooks:run(cert_expired, [Cert, CertInfo]), ejabberd_hooks:run(cert_expired, [Cert, CertInfo]),
@ -208,23 +237,27 @@ handle_cast(Request, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Request]), ?WARNING_MSG("Unexpected cast: ~p", [Request]),
{noreply, State}. {noreply, State}.
-spec handle_info(term(), state()) -> {noreply, state()}. -spec handle_info(term(), state()) -> {noreply, state()}.
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
-spec terminate(normal | shutdown | {shutdown, term()} | term(), -spec terminate(normal | shutdown | {shutdown, term()} | term(),
state()) -> any(). state()) -> any().
terminate(_Reason, State) -> terminate(_Reason, State) ->
ejabberd_hooks:delete(cert_expired, ?MODULE, cert_expired, 50), ejabberd_hooks:delete(cert_expired, ?MODULE, cert_expired, 50),
ejabberd_hooks:delete(ejabberd_started, ?MODULE, ejabberd_started, 30), ejabberd_hooks:delete(ejabberd_started, ?MODULE, ejabberd_started, 30),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 100), ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 100),
del_files(State#state.files). del_files(State#state.files).
-spec code_change(term() | {down, term()}, state(), term()) -> {ok, state()}. -spec code_change(term() | {down, term()}, state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -233,201 +266,230 @@ add_files() ->
Files = get_certfiles_from_config_options(), Files = get_certfiles_from_config_options(),
add_files(sets:to_list(Files), sets:new(), []). add_files(sets:to_list(Files), sets:new(), []).
-spec add_files(sets:set(filename())) -> -spec add_files(sets:set(filename())) ->
{sets:set(filename()), [{filename(), pkix:error_reason()}]}. {sets:set(filename()), [{filename(), pkix:error_reason()}]}.
add_files(Files) -> add_files(Files) ->
add_files(sets:to_list(Files), sets:new(), []). add_files(sets:to_list(Files), sets:new(), []).
-spec add_files([filename()], sets:set(filename()),
[{filename(), pkix:error_reason()}]) -> -spec add_files([filename()],
{sets:set(filename()), [{filename(), pkix:error_reason()}]}. sets:set(filename()),
add_files([File|Files], Set, Errs) -> [{filename(), pkix:error_reason()}]) ->
{sets:set(filename()), [{filename(), pkix:error_reason()}]}.
add_files([File | Files], Set, Errs) ->
case add_file(File) of case add_file(File) of
ok -> ok ->
Set1 = sets:add_element(File, Set), Set1 = sets:add_element(File, Set),
add_files(Files, Set1, Errs); add_files(Files, Set1, Errs);
{error, Reason} -> {error, Reason} ->
Errs1 = [{File, Reason}|Errs], Errs1 = [{File, Reason} | Errs],
add_files(Files, Set, Errs1) add_files(Files, Set, Errs1)
end; end;
add_files([], Set, Errs) -> add_files([], Set, Errs) ->
{Set, Errs}. {Set, Errs}.
-spec add_file(filename()) -> ok | {error, pkix:error_reason()}. -spec add_file(filename()) -> ok | {error, pkix:error_reason()}.
add_file(File) -> add_file(File) ->
case pkix:add_file(File) of case pkix:add_file(File) of
ok -> ok; ok -> ok;
{error, Reason} = Err -> {error, Reason} = Err ->
?ERROR_MSG("Failed to read PEM file ~ts: ~ts", ?ERROR_MSG("Failed to read PEM file ~ts: ~ts",
[File, pkix:format_error(Reason)]), [File, pkix:format_error(Reason)]),
Err Err
end. end.
-spec del_files(sets:set(filename())) -> ok. -spec del_files(sets:set(filename())) -> ok.
del_files(Files) -> del_files(Files) ->
lists:foreach(fun pkix:del_file/1, sets:to_list(Files)). lists:foreach(fun pkix:del_file/1, sets:to_list(Files)).
-spec do_commit() -> {ok, [{filename(), pkix:error_reason()}]} | error. -spec do_commit() -> {ok, [{filename(), pkix:error_reason()}]} | error.
do_commit() -> do_commit() ->
CAFile = ejabberd_option:ca_file(), CAFile = ejabberd_option:ca_file(),
?DEBUG("Using CA root certificates from: ~ts", [CAFile]), ?DEBUG("Using CA root certificates from: ~ts", [CAFile]),
Opts = [{cafile, CAFile}, Opts = [{cafile, CAFile},
{notify_before, [7*24*60*60, % 1 week {notify_before, [7 * 24 * 60 * 60, % 1 week
24*60*60, % 1 day 24 * 60 * 60, % 1 day
60*60, % 1 hour 60 * 60, % 1 hour
0]}, 0]},
{notify_fun, fun ?MODULE:notify_expired/1}], {notify_fun, fun ?MODULE:notify_expired/1}],
case pkix:commit(certs_dir(), Opts) of case pkix:commit(certs_dir(), Opts) of
{ok, Errors, Warnings, CAError} -> {ok, Errors, Warnings, CAError} ->
log_errors(Errors), log_errors(Errors),
log_cafile_error(CAError), log_cafile_error(CAError),
log_warnings(Warnings), log_warnings(Warnings),
fast_tls_add_certfiles(), fast_tls_add_certfiles(),
{ok, Errors}; {ok, Errors};
{error, File, Reason} -> {error, File, Reason} ->
?CRITICAL_MSG("Failed to write to ~ts: ~ts", ?CRITICAL_MSG("Failed to write to ~ts: ~ts",
[File, file:format_error(Reason)]), [File, file:format_error(Reason)]),
error error
end. end.
-spec check_domain_certfiles() -> ok. -spec check_domain_certfiles() -> ok.
check_domain_certfiles() -> check_domain_certfiles() ->
Hosts = ejabberd_option:hosts(), Hosts = ejabberd_option:hosts(),
Routes = ejabberd_router:get_all_routes(), Routes = ejabberd_router:get_all_routes(),
check_domain_certfiles(Hosts ++ Routes). check_domain_certfiles(Hosts ++ Routes).
-spec check_domain_certfiles([binary()]) -> ok. -spec check_domain_certfiles([binary()]) -> ok.
check_domain_certfiles(Hosts) -> check_domain_certfiles(Hosts) ->
case ejabberd_listener:tls_listeners() of case ejabberd_listener:tls_listeners() of
[] -> ok; [] -> ok;
_ -> _ ->
lists:foreach( lists:foreach(
fun(Host) -> fun(Host) ->
case get_certfile_no_default(Host) of case get_certfile_no_default(Host) of
error -> error ->
?WARNING_MSG( ?WARNING_MSG(
"No certificate found matching ~ts", "No certificate found matching ~ts",
[Host]); [Host]);
_ -> _ ->
ok ok
end end
end, Hosts) end,
Hosts)
end. end.
-spec get_certfiles_from_config_options() -> sets:set(filename()). -spec get_certfiles_from_config_options() -> sets:set(filename()).
get_certfiles_from_config_options() -> get_certfiles_from_config_options() ->
case ejabberd_option:certfiles() of case ejabberd_option:certfiles() of
undefined -> undefined ->
sets:new(); sets:new();
Paths -> Paths ->
lists:foldl( lists:foldl(
fun(Path, Acc) -> fun(Path, Acc) ->
Files = wildcard(Path), Files = wildcard(Path),
lists:foldl(fun sets:add_element/2, Acc, Files) lists:foldl(fun sets:add_element/2, Acc, Files)
end, sets:new(), Paths) end,
sets:new(),
Paths)
end. end.
-spec prep_path(file:filename_all()) -> filename(). -spec prep_path(file:filename_all()) -> filename().
prep_path(Path0) -> prep_path(Path0) ->
case filename:pathtype(Path0) of case filename:pathtype(Path0) of
relative -> relative ->
case file:get_cwd() of case file:get_cwd() of
{ok, CWD} -> {ok, CWD} ->
unicode:characters_to_binary(filename:join(CWD, Path0)); unicode:characters_to_binary(filename:join(CWD, Path0));
{error, Reason} -> {error, Reason} ->
?WARNING_MSG("Failed to get current directory name: ~ts", ?WARNING_MSG("Failed to get current directory name: ~ts",
[file:format_error(Reason)]), [file:format_error(Reason)]),
unicode:characters_to_binary(Path0) unicode:characters_to_binary(Path0)
end; end;
_ -> _ ->
unicode:characters_to_binary(Path0) unicode:characters_to_binary(Path0)
end. end.
-spec stop_ejabberd() -> no_return(). -spec stop_ejabberd() -> no_return().
stop_ejabberd() -> stop_ejabberd() ->
?CRITICAL_MSG("ejabberd initialization was aborted due to " ?CRITICAL_MSG("ejabberd initialization was aborted due to "
"invalid certificates configuration", []), "invalid certificates configuration",
[]),
ejabberd:halt(). ejabberd:halt().
-spec wildcard(file:filename_all()) -> [filename()]. -spec wildcard(file:filename_all()) -> [filename()].
wildcard(Path) when is_binary(Path) -> wildcard(Path) when is_binary(Path) ->
wildcard(binary_to_list(Path)); wildcard(binary_to_list(Path));
wildcard(Path) -> wildcard(Path) ->
case filelib:wildcard(Path) of case filelib:wildcard(Path) of
[] -> [] ->
?WARNING_MSG("Path ~ts is empty, please make sure ejabberd has " ?WARNING_MSG("Path ~ts is empty, please make sure ejabberd has "
"sufficient rights to read it", [Path]), "sufficient rights to read it",
[]; [Path]),
Files -> [];
[prep_path(File) || File <- Files] Files ->
[ prep_path(File) || File <- Files ]
end. end.
-spec select_certfile({filename() | undefined, -spec select_certfile({filename() | undefined,
filename() | undefined, filename() | undefined,
filename() | undefined}) -> filename(). filename() | undefined}) -> filename().
select_certfile({EC, _, _}) when EC /= undefined -> EC; select_certfile({EC, _, _}) when EC /= undefined -> EC;
select_certfile({_, RSA, _}) when RSA /= undefined -> RSA; select_certfile({_, RSA, _}) when RSA /= undefined -> RSA;
select_certfile({_, _, DSA}) when DSA /= undefined -> DSA. select_certfile({_, _, DSA}) when DSA /= undefined -> DSA.
-spec fast_tls_add_certfiles() -> ok. -spec fast_tls_add_certfiles() -> ok.
fast_tls_add_certfiles() -> fast_tls_add_certfiles() ->
lists:foreach( lists:foreach(
fun({Domain, Files}) -> fun({Domain, Files}) ->
fast_tls:add_certfile(Domain, select_certfile(Files)) fast_tls:add_certfile(Domain, select_certfile(Files))
end, pkix:get_certfiles()), end,
pkix:get_certfiles()),
fast_tls:clear_cache(). fast_tls:clear_cache().
reason_to_fmt({invalid_cert, _, _}) -> reason_to_fmt({invalid_cert, _, _}) ->
"Invalid certificate in ~ts: ~ts"; "Invalid certificate in ~ts: ~ts";
reason_to_fmt(_) -> reason_to_fmt(_) ->
"Failed to read PEM file ~ts: ~ts". "Failed to read PEM file ~ts: ~ts".
-spec log_warnings([{filename(), pkix:error_reason()}]) -> ok. -spec log_warnings([{filename(), pkix:error_reason()}]) -> ok.
log_warnings(Warnings) -> log_warnings(Warnings) ->
lists:foreach( lists:foreach(
fun({File, Reason}) -> fun({File, Reason}) ->
?WARNING_MSG(reason_to_fmt(Reason), ?WARNING_MSG(reason_to_fmt(Reason),
[File, pkix:format_error(Reason)]) [File, pkix:format_error(Reason)])
end, Warnings). end,
Warnings).
-spec log_errors([{filename(), pkix:error_reason()}]) -> ok. -spec log_errors([{filename(), pkix:error_reason()}]) -> ok.
log_errors(Errors) -> log_errors(Errors) ->
lists:foreach( lists:foreach(
fun({File, Reason}) -> fun({File, Reason}) ->
?ERROR_MSG(reason_to_fmt(Reason), ?ERROR_MSG(reason_to_fmt(Reason),
[File, pkix:format_error(Reason)]) [File, pkix:format_error(Reason)])
end, Errors). end,
Errors).
-spec log_cafile_error({filename(), pkix:error_reason()} | undefined) -> ok. -spec log_cafile_error({filename(), pkix:error_reason()} | undefined) -> ok.
log_cafile_error({File, Reason}) -> log_cafile_error({File, Reason}) ->
?CRITICAL_MSG("Failed to read CA certitificates from ~ts: ~ts. " ?CRITICAL_MSG("Failed to read CA certitificates from ~ts: ~ts. "
"Try to change/set option 'ca_file'", "Try to change/set option 'ca_file'",
[File, pkix:format_error(Reason)]); [File, pkix:format_error(Reason)]);
log_cafile_error(_) -> log_cafile_error(_) ->
ok. ok.
-spec time_before_expiration(calendar:datetime()) -> {non_neg_integer(), string()}. -spec time_before_expiration(calendar:datetime()) -> {non_neg_integer(), string()}.
time_before_expiration(Expiry) -> time_before_expiration(Expiry) ->
T1 = calendar:datetime_to_gregorian_seconds(Expiry), T1 = calendar:datetime_to_gregorian_seconds(Expiry),
T2 = calendar:datetime_to_gregorian_seconds( T2 = calendar:datetime_to_gregorian_seconds(
calendar:now_to_datetime(erlang:timestamp())), calendar:now_to_datetime(erlang:timestamp())),
Secs = max(0, T1 - T2), Secs = max(0, T1 - T2),
if Secs == {0, ""}; if
Secs >= 220752000 -> {round(Secs/220752000), "year"}; Secs == {0, ""};
Secs >= 2592000 -> {round(Secs/2592000), "month"}; Secs >= 220752000 -> {round(Secs / 220752000), "year"};
Secs >= 604800 -> {round(Secs/604800), "week"}; Secs >= 2592000 -> {round(Secs / 2592000), "month"};
Secs >= 86400 -> {round(Secs/86400), "day"}; Secs >= 604800 -> {round(Secs / 604800), "week"};
Secs >= 3600 -> {round(Secs/3600), "hour"}; Secs >= 86400 -> {round(Secs / 86400), "day"};
Secs >= 60 -> {round(Secs/60), "minute"}; Secs >= 3600 -> {round(Secs / 3600), "hour"};
true -> {Secs, "second"} Secs >= 60 -> {round(Secs / 60), "minute"};
true -> {Secs, "second"}
end. end.
-spec format_expiration_date(calendar:datetime()) -> string(). -spec format_expiration_date(calendar:datetime()) -> string().
format_expiration_date(DateTime) -> format_expiration_date(DateTime) ->
case time_before_expiration(DateTime) of case time_before_expiration(DateTime) of
{0, _} -> "is expired"; {0, _} -> "is expired";
{1, Unit} -> "will expire in a " ++ Unit; {1, Unit} -> "will expire in a " ++ Unit;
{Int, Unit} -> {Int, Unit} ->
"will expire in " ++ integer_to_list(Int) "will expire in " ++ integer_to_list(Int) ++
++ " " ++ Unit ++ "s" " " ++ Unit ++ "s"
end. end.

File diff suppressed because it is too large Load diff

View file

@ -33,58 +33,68 @@
-include("logger.hrl"). -include("logger.hrl").
%%%=================================================================== %%%===================================================================
%%% API functions %%% API functions
%%%=================================================================== %%%===================================================================
start() -> start() ->
case is_started() of case is_started() of
true -> ok; true -> ok;
false -> false ->
ejabberd:start_app(eredis), ejabberd:start_app(eredis),
Spec = {?MODULE, {?MODULE, start_link, []}, Spec = {?MODULE, {?MODULE, start_link, []},
permanent, infinity, supervisor, [?MODULE]}, permanent,
case supervisor:start_child(ejabberd_db_sup, Spec) of infinity,
{ok, _} -> ok; supervisor,
{error, {already_started, Pid}} -> [?MODULE]},
case supervisor:start_child(ejabberd_db_sup, Spec) of
{ok, _} -> ok;
{error, {already_started, Pid}} ->
%% Wait for the supervisor to fully start %% Wait for the supervisor to fully start
_ = supervisor:count_children(Pid), _ = supervisor:count_children(Pid),
ok; ok;
{error, Why} = Err -> {error, Why} = Err ->
?ERROR_MSG("Failed to start ~ts: ~p", [?MODULE, Why]), ?ERROR_MSG("Failed to start ~ts: ~p", [?MODULE, Why]),
Err Err
end end
end. end.
stop() -> stop() ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 20), ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 20),
_ = supervisor:terminate_child(ejabberd_db_sup, ?MODULE), _ = supervisor:terminate_child(ejabberd_db_sup, ?MODULE),
_ = supervisor:delete_child(ejabberd_db_sup, ?MODULE), _ = supervisor:delete_child(ejabberd_db_sup, ?MODULE),
ok. ok.
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
config_reloaded() -> config_reloaded() ->
case is_started() of case is_started() of
true -> true ->
lists:foreach( lists:foreach(
fun(Spec) -> fun(Spec) ->
supervisor:start_child(?MODULE, Spec) supervisor:start_child(?MODULE, Spec)
end, get_specs()), end,
PoolSize = get_pool_size(), get_specs()),
lists:foreach( PoolSize = get_pool_size(),
fun({Id, _, _, _}) when Id > PoolSize -> lists:foreach(
case supervisor:terminate_child(?MODULE, Id) of fun({Id, _, _, _}) when Id > PoolSize ->
ok -> supervisor:delete_child(?MODULE, Id); case supervisor:terminate_child(?MODULE, Id) of
_ -> ok ok -> supervisor:delete_child(?MODULE, Id);
end; _ -> ok
(_) -> end;
ok (_) ->
end, supervisor:which_children(?MODULE)); ok
false -> end,
ok supervisor:which_children(?MODULE));
false ->
ok
end. end.
%%%=================================================================== %%%===================================================================
%%% Supervisor callbacks %%% Supervisor callbacks
%%%=================================================================== %%%===================================================================
@ -92,18 +102,26 @@ init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20), ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20),
{ok, {{one_for_one, 500, 1}, get_specs()}}. {ok, {{one_for_one, 500, 1}, get_specs()}}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
get_specs() -> get_specs() ->
lists:map( lists:map(
fun(I) -> fun(I) ->
{I, {ejabberd_redis, start_link, [I]}, {I,
transient, 2000, worker, [?MODULE]} {ejabberd_redis, start_link, [I]},
end, lists:seq(1, get_pool_size())). transient,
2000,
worker,
[?MODULE]}
end,
lists:seq(1, get_pool_size())).
get_pool_size() -> get_pool_size() ->
ejabberd_option:redis_pool_size() + 1. ejabberd_option:redis_pool_size() + 1.
is_started() -> is_started() ->
whereis(?MODULE) /= undefined. whereis(?MODULE) /= undefined.

View file

@ -27,37 +27,44 @@
-export([run/2, split/2, replace/3, greplace/3, sh_to_awk/1]). -export([run/2, split/2, replace/3, greplace/3, sh_to_awk/1]).
-spec run(binary(), binary()) -> match | nomatch | {error, any()}. -spec run(binary(), binary()) -> match | nomatch | {error, any()}.
run(String, Regexp) -> run(String, Regexp) ->
re:run(String, Regexp, [{capture, none}, unicode]). re:run(String, Regexp, [{capture, none}, unicode]).
-spec split(binary(), binary()) -> [binary()]. -spec split(binary(), binary()) -> [binary()].
split(String, Regexp) -> split(String, Regexp) ->
re:split(String, Regexp, [{return, binary}]). re:split(String, Regexp, [{return, binary}]).
-spec replace(binary(), binary(), binary()) -> binary(). -spec replace(binary(), binary(), binary()) -> binary().
replace(String, Regexp, New) -> replace(String, Regexp, New) ->
re:replace(String, Regexp, New, [{return, binary}]). re:replace(String, Regexp, New, [{return, binary}]).
-spec greplace(binary(), binary(), binary()) -> binary(). -spec greplace(binary(), binary(), binary()) -> binary().
greplace(String, Regexp, New) -> greplace(String, Regexp, New) ->
re:replace(String, Regexp, New, [global, {return, binary}]). re:replace(String, Regexp, New, [global, {return, binary}]).
%% This code was copied and adapted from xmerl_regexp.erl %% This code was copied and adapted from xmerl_regexp.erl
-spec sh_to_awk(binary()) -> binary(). -spec sh_to_awk(binary()) -> binary().
sh_to_awk(Sh) -> sh_to_awk(Sh) ->
iolist_to_binary([<<"^(">>, sh_to_awk_1(Sh)]). %Fix the beginning iolist_to_binary([<<"^(">>, sh_to_awk_1(Sh)]). %Fix the beginning
sh_to_awk_1(<<"*", Sh/binary>>) -> %This matches any string
sh_to_awk_1(<<"*", Sh/binary>>) -> %This matches any string
[<<".*">>, sh_to_awk_1(Sh)]; [<<".*">>, sh_to_awk_1(Sh)];
sh_to_awk_1(<<"?", Sh/binary>>) -> %This matches any character sh_to_awk_1(<<"?", Sh/binary>>) -> %This matches any character
[$., sh_to_awk_1(Sh)]; [$., sh_to_awk_1(Sh)];
sh_to_awk_1(<<"[^]", Sh/binary>>) -> %This takes careful handling sh_to_awk_1(<<"[^]", Sh/binary>>) -> %This takes careful handling
[<<"\\^">>, sh_to_awk_1(Sh)]; [<<"\\^">>, sh_to_awk_1(Sh)];
%% Must move '^' to end. %% Must move '^' to end.
sh_to_awk_1(<<"[^", Sh/binary>>) -> sh_to_awk_1(<<"[^", Sh/binary>>) ->
@ -66,30 +73,33 @@ sh_to_awk_1(<<"[!", Sh/binary>>) ->
[<<"[^">>, sh_to_awk_2(Sh, false)]; [<<"[^">>, sh_to_awk_2(Sh, false)];
sh_to_awk_1(<<"[", Sh/binary>>) -> sh_to_awk_1(<<"[", Sh/binary>>) ->
[$[, sh_to_awk_2(Sh, false)]; [$[, sh_to_awk_2(Sh, false)];
sh_to_awk_1(<<C:8, Sh/binary>>) -> %% Unspecialise everything else which is not an escape character. sh_to_awk_1(<<C:8, Sh/binary>>) -> %% Unspecialise everything else which is not an escape character.
case sh_special_char(C) of case sh_special_char(C) of
true -> [$\\,C|sh_to_awk_1(Sh)]; true -> [$\\, C | sh_to_awk_1(Sh)];
false -> [C|sh_to_awk_1(Sh)] false -> [C | sh_to_awk_1(Sh)]
end; end;
sh_to_awk_1(<<>>) -> sh_to_awk_1(<<>>) ->
<<")$">>. %Fix the end <<")$">>. %Fix the end
sh_to_awk_2(<<"]", Sh/binary>>, UpArrow) -> sh_to_awk_2(<<"]", Sh/binary>>, UpArrow) ->
[$]|sh_to_awk_3(Sh, UpArrow)]; [$] | sh_to_awk_3(Sh, UpArrow)];
sh_to_awk_2(Sh, UpArrow) -> sh_to_awk_2(Sh, UpArrow) ->
sh_to_awk_3(Sh, UpArrow). sh_to_awk_3(Sh, UpArrow).
sh_to_awk_3(<<"]", Sh/binary>>, true) -> sh_to_awk_3(<<"]", Sh/binary>>, true) ->
[<<"^]">>, sh_to_awk_1(Sh)]; [<<"^]">>, sh_to_awk_1(Sh)];
sh_to_awk_3(<<"]", Sh/binary>>, false) -> sh_to_awk_3(<<"]", Sh/binary>>, false) ->
[$]|sh_to_awk_1(Sh)]; [$] | sh_to_awk_1(Sh)];
sh_to_awk_3(<<C:8, Sh/binary>>, UpArrow) -> sh_to_awk_3(<<C:8, Sh/binary>>, UpArrow) ->
[C|sh_to_awk_3(Sh, UpArrow)]; [C | sh_to_awk_3(Sh, UpArrow)];
sh_to_awk_3(<<>>, true) -> sh_to_awk_3(<<>>, true) ->
[$^|sh_to_awk_1(<<>>)]; [$^ | sh_to_awk_1(<<>>)];
sh_to_awk_3(<<>>, false) -> sh_to_awk_3(<<>>, false) ->
sh_to_awk_1(<<>>). sh_to_awk_1(<<>>).
%% Test if a character is a special character. %% Test if a character is a special character.
-spec sh_special_char(char()) -> boolean(). -spec sh_special_char(char()) -> boolean().
sh_special_char($|) -> true; sh_special_char($|) -> true;

View file

@ -34,283 +34,323 @@
%% API %% API
-export([route/1, -export([route/1,
route_error/2, route_error/2,
route_iq/2, route_iq/2, route_iq/3, route_iq/4,
route_iq/3, register_route/2, register_route/3, register_route/4,
route_iq/4, register_routes/1,
register_route/2, host_of_route/1,
register_route/3, process_iq/1,
register_route/4, unregister_route/1, unregister_route/2,
register_routes/1, unregister_routes/1,
host_of_route/1, get_all_routes/0,
process_iq/1, is_my_route/1,
unregister_route/1, is_my_host/1,
unregister_route/2, clean_cache/1,
unregister_routes/1, config_reloaded/0,
get_all_routes/0, get_backend/0]).
is_my_route/1,
is_my_host/1,
clean_cache/1,
config_reloaded/0,
get_backend/0]).
-export([start_link/0]). -export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, -export([init/1,
handle_info/2, terminate/2, code_change/3]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
%% Deprecated functions %% Deprecated functions
-export([route/3, route_error/4]). -export([route/3, route_error/4]).
-deprecated([{route, 3}, {route_error, 4}]). -deprecated([{route, 3}, {route_error, 4}]).
%% This value is used in SIP and Megaco for a transaction lifetime. %% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000). -define(IQ_TIMEOUT, 32000).
-define(CALL_TIMEOUT, timer:minutes(10)). -define(CALL_TIMEOUT, timer:minutes(10)).
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_router.hrl"). -include("ejabberd_router.hrl").
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_stacktrace.hrl"). -include("ejabberd_stacktrace.hrl").
-callback init() -> any(). -callback init() -> any().
-callback register_route(binary(), binary(), local_hint(), -callback register_route(binary(),
undefined | pos_integer(), pid()) -> ok | {error, term()}. binary(),
local_hint(),
undefined | pos_integer(),
pid()) -> ok | {error, term()}.
-callback unregister_route(binary(), undefined | pos_integer(), pid()) -> ok | {error, term()}. -callback unregister_route(binary(), undefined | pos_integer(), pid()) -> ok | {error, term()}.
-callback find_routes(binary()) -> {ok, [#route{}]} | {error, any()}. -callback find_routes(binary()) -> {ok, [#route{}]} | {error, any()}.
-callback get_all_routes() -> {ok, [binary()]} | {error, any()}. -callback get_all_routes() -> {ok, [binary()]} | {error, any()}.
-record(state, {route_monitors = #{} :: #{{binary(), pid()} => reference()}}). -record(state, {route_monitors = #{} :: #{{binary(), pid()} => reference()}}).
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
start_link() -> start_link() ->
?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []). ?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(stanza()) -> ok. -spec route(stanza()) -> ok.
route(Packet) -> route(Packet) ->
try do_route(Packet) try
catch ?EX_RULE(Class, Reason, St) -> do_route(Packet)
StackTrace = ?EX_STACK(St), catch
?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts", ?EX_RULE(Class, Reason, St) ->
[xmpp:pp(Packet), StackTrace = ?EX_STACK(St),
misc:format_exception(2, Class, Reason, StackTrace)]) ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
[xmpp:pp(Packet),
misc:format_exception(2, Class, Reason, StackTrace)])
end. end.
-spec route(jid(), jid(), xmlel() | stanza()) -> ok. -spec route(jid(), jid(), xmlel() | stanza()) -> ok.
route(#jid{} = From, #jid{} = To, #xmlel{} = El) -> route(#jid{} = From, #jid{} = To, #xmlel{} = El) ->
try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
Pkt -> route(From, To, Pkt) Pkt -> route(From, To, Pkt)
catch _:{xmpp_codec, Why} -> catch
?ERROR_MSG("Failed to decode xml element ~p when " _:{xmpp_codec, Why} ->
"routing from ~ts to ~ts: ~ts", ?ERROR_MSG("Failed to decode xml element ~p when "
[El, jid:encode(From), jid:encode(To), "routing from ~ts to ~ts: ~ts",
xmpp:format_error(Why)]) [El,
jid:encode(From),
jid:encode(To),
xmpp:format_error(Why)])
end; end;
route(#jid{} = From, #jid{} = To, Packet) -> route(#jid{} = From, #jid{} = To, Packet) ->
route(xmpp:set_from_to(Packet, From, To)). route(xmpp:set_from_to(Packet, From, To)).
-spec route_error(stanza(), stanza_error()) -> ok. -spec route_error(stanza(), stanza_error()) -> ok.
route_error(Packet, Err) -> route_error(Packet, Err) ->
Type = xmpp:get_type(Packet), Type = xmpp:get_type(Packet),
if Type == error; Type == result -> if
ok; Type == error; Type == result ->
true -> ok;
route(xmpp:make_error(Packet, Err)) true ->
route(xmpp:make_error(Packet, Err))
end. end.
%% Route the error packet only if the originating packet is not an error itself. %% Route the error packet only if the originating packet is not an error itself.
%% RFC3920 9.3.1 %% RFC3920 9.3.1
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok; -spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
(jid(), jid(), stanza(), stanza_error()) -> ok. (jid(), jid(), stanza(), stanza_error()) -> ok.
route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) -> route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket, #xmlel{attrs = Attrs} = OrigPacket,
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket); false -> route(From, To, ErrPacket);
true -> ok true -> ok
end; end;
route_error(From, To, Packet, #stanza_error{} = Err) -> route_error(From, To, Packet, #stanza_error{} = Err) ->
Type = xmpp:get_type(Packet), Type = xmpp:get_type(Packet),
if Type == error; Type == result -> if
ok; Type == error; Type == result ->
true -> ok;
route(From, To, xmpp:make_error(Packet, Err)) true ->
route(From, To, xmpp:make_error(Packet, Err))
end. end.
-spec route_iq(iq(), fun((iq() | timeout) -> any())) -> ok. -spec route_iq(iq(), fun((iq() | timeout) -> any())) -> ok.
route_iq(IQ, Fun) -> route_iq(IQ, Fun) ->
route_iq(IQ, Fun, undefined, ?IQ_TIMEOUT). route_iq(IQ, Fun, undefined, ?IQ_TIMEOUT).
-spec route_iq(iq(), term(), pid() | atom()) -> ok. -spec route_iq(iq(), term(), pid() | atom()) -> ok.
route_iq(IQ, State, Proc) -> route_iq(IQ, State, Proc) ->
route_iq(IQ, State, Proc, ?IQ_TIMEOUT). route_iq(IQ, State, Proc, ?IQ_TIMEOUT).
-spec route_iq(iq(), term(), pid() | atom(), undefined | non_neg_integer()) -> ok. -spec route_iq(iq(), term(), pid() | atom(), undefined | non_neg_integer()) -> ok.
route_iq(IQ, State, Proc, undefined) -> route_iq(IQ, State, Proc, undefined) ->
route_iq(IQ, State, Proc, ?IQ_TIMEOUT); route_iq(IQ, State, Proc, ?IQ_TIMEOUT);
route_iq(IQ, State, Proc, Timeout) -> route_iq(IQ, State, Proc, Timeout) ->
ejabberd_iq:route(IQ, Proc, State, Timeout). ejabberd_iq:route(IQ, Proc, State, Timeout).
-spec register_route(binary(), binary()) -> ok. -spec register_route(binary(), binary()) -> ok.
register_route(Domain, ServerHost) -> register_route(Domain, ServerHost) ->
register_route(Domain, ServerHost, undefined). register_route(Domain, ServerHost, undefined).
-spec register_route(binary(), binary(), local_hint() | undefined) -> ok. -spec register_route(binary(), binary(), local_hint() | undefined) -> ok.
register_route(Domain, ServerHost, LocalHint) -> register_route(Domain, ServerHost, LocalHint) ->
register_route(Domain, ServerHost, LocalHint, self()). register_route(Domain, ServerHost, LocalHint, self()).
-spec register_route(binary(), binary(), local_hint() | undefined, pid()) -> ok. -spec register_route(binary(), binary(), local_hint() | undefined, pid()) -> ok.
register_route(Domain, ServerHost, LocalHint, Pid) -> register_route(Domain, ServerHost, LocalHint, Pid) ->
case {jid:nameprep(Domain), jid:nameprep(ServerHost)} of case {jid:nameprep(Domain), jid:nameprep(ServerHost)} of
{error, _} -> {error, _} ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
{_, error} -> {_, error} ->
erlang:error({invalid_domain, ServerHost}); erlang:error({invalid_domain, ServerHost});
{LDomain, LServerHost} -> {LDomain, LServerHost} ->
Mod = get_backend(), Mod = get_backend(),
case Mod:register_route(LDomain, LServerHost, LocalHint, case Mod:register_route(LDomain,
get_component_number(LDomain), Pid) of LServerHost,
ok -> LocalHint,
?DEBUG("Route registered: ~ts", [LDomain]), get_component_number(LDomain),
monitor_route(LDomain, Pid), Pid) of
ejabberd_hooks:run(route_registered, [LDomain]), ok ->
delete_cache(Mod, LDomain); ?DEBUG("Route registered: ~ts", [LDomain]),
{error, Err} -> monitor_route(LDomain, Pid),
?ERROR_MSG("Failed to register route ~ts: ~p", ejabberd_hooks:run(route_registered, [LDomain]),
[LDomain, Err]) delete_cache(Mod, LDomain);
end {error, Err} ->
?ERROR_MSG("Failed to register route ~ts: ~p",
[LDomain, Err])
end
end. end.
-spec register_routes([{binary(), binary()}]) -> ok. -spec register_routes([{binary(), binary()}]) -> ok.
register_routes(Domains) -> register_routes(Domains) ->
lists:foreach(fun ({Domain, ServerHost}) -> register_route(Domain, ServerHost) lists:foreach(fun({Domain, ServerHost}) -> register_route(Domain, ServerHost)
end, end,
Domains). Domains).
-spec unregister_route(binary()) -> ok. -spec unregister_route(binary()) -> ok.
unregister_route(Domain) -> unregister_route(Domain) ->
unregister_route(Domain, self()). unregister_route(Domain, self()).
-spec unregister_route(binary(), pid()) -> ok. -spec unregister_route(binary(), pid()) -> ok.
unregister_route(Domain, Pid) -> unregister_route(Domain, Pid) ->
case jid:nameprep(Domain) of case jid:nameprep(Domain) of
error -> error ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
Mod = get_backend(), Mod = get_backend(),
case Mod:unregister_route( case Mod:unregister_route(
LDomain, get_component_number(LDomain), Pid) of LDomain, get_component_number(LDomain), Pid) of
ok -> ok ->
?DEBUG("Route unregistered: ~ts", [LDomain]), ?DEBUG("Route unregistered: ~ts", [LDomain]),
demonitor_route(LDomain, Pid), demonitor_route(LDomain, Pid),
ejabberd_hooks:run(route_unregistered, [LDomain]), ejabberd_hooks:run(route_unregistered, [LDomain]),
delete_cache(Mod, LDomain); delete_cache(Mod, LDomain);
{error, Err} -> {error, Err} ->
?ERROR_MSG("Failed to unregister route ~ts: ~p", ?ERROR_MSG("Failed to unregister route ~ts: ~p",
[LDomain, Err]) [LDomain, Err])
end end
end. end.
-spec unregister_routes([binary()]) -> ok. -spec unregister_routes([binary()]) -> ok.
unregister_routes(Domains) -> unregister_routes(Domains) ->
lists:foreach(fun (Domain) -> unregister_route(Domain) lists:foreach(fun(Domain) -> unregister_route(Domain)
end, end,
Domains). Domains).
-spec find_routes(binary()) -> [#route{}]. -spec find_routes(binary()) -> [#route{}].
find_routes(Domain) -> find_routes(Domain) ->
Mod = get_backend(), Mod = get_backend(),
case use_cache(Mod) of case use_cache(Mod) of
true -> true ->
case ets_cache:lookup( case ets_cache:lookup(
?ROUTES_CACHE, {route, Domain}, ?ROUTES_CACHE,
fun() -> {route, Domain},
case Mod:find_routes(Domain) of fun() ->
{ok, Rs} when Rs /= [] -> case Mod:find_routes(Domain) of
{ok, Rs}; {ok, Rs} when Rs /= [] ->
_ -> {ok, Rs};
error _ ->
end error
end) of end
{ok, Rs} -> Rs; end) of
error -> [] {ok, Rs} -> Rs;
end; error -> []
false -> end;
case Mod:find_routes(Domain) of false ->
{ok, Rs} -> Rs; case Mod:find_routes(Domain) of
_ -> [] {ok, Rs} -> Rs;
end _ -> []
end
end. end.
-spec get_all_routes() -> [binary()]. -spec get_all_routes() -> [binary()].
get_all_routes() -> get_all_routes() ->
Mod = get_backend(), Mod = get_backend(),
case use_cache(Mod) of case use_cache(Mod) of
true -> true ->
case ets_cache:lookup( case ets_cache:lookup(
?ROUTES_CACHE, routes, ?ROUTES_CACHE,
fun() -> routes,
case Mod:get_all_routes() of fun() ->
{ok, Rs} when Rs /= [] -> case Mod:get_all_routes() of
{ok, Rs}; {ok, Rs} when Rs /= [] ->
_ -> {ok, Rs};
error _ ->
end error
end) of end
{ok, Rs} -> Rs; end) of
error -> [] {ok, Rs} -> Rs;
end; error -> []
false -> end;
case Mod:get_all_routes() of false ->
{ok, Rs} -> Rs; case Mod:get_all_routes() of
_ -> [] {ok, Rs} -> Rs;
end _ -> []
end
end. end.
-spec host_of_route(binary()) -> binary(). -spec host_of_route(binary()) -> binary().
host_of_route(Domain) -> host_of_route(Domain) ->
case jid:nameprep(Domain) of case jid:nameprep(Domain) of
error -> error ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
case find_routes(LDomain) of case find_routes(LDomain) of
[#route{server_host = ServerHost}|_] -> [#route{server_host = ServerHost} | _] ->
ServerHost; ServerHost;
_ -> _ ->
erlang:error({unregistered_route, Domain}) erlang:error({unregistered_route, Domain})
end end
end. end.
-spec is_my_route(binary()) -> boolean(). -spec is_my_route(binary()) -> boolean().
is_my_route(Domain) -> is_my_route(Domain) ->
case jid:nameprep(Domain) of case jid:nameprep(Domain) of
error -> error ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
find_routes(LDomain) /= [] find_routes(LDomain) /= []
end. end.
-spec is_my_host(binary()) -> boolean(). -spec is_my_host(binary()) -> boolean().
is_my_host(Domain) -> is_my_host(Domain) ->
case jid:nameprep(Domain) of case jid:nameprep(Domain) of
error -> error ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
case find_routes(LDomain) of case find_routes(LDomain) of
[#route{server_host = LDomain}|_] -> true; [#route{server_host = LDomain} | _] -> true;
_ -> false _ -> false
end end
end. end.
-spec process_iq(iq()) -> any(). -spec process_iq(iq()) -> any().
process_iq(IQ) -> process_iq(IQ) ->
gen_iq_handler:handle(IQ). gen_iq_handler:handle(IQ).
-spec config_reloaded() -> ok. -spec config_reloaded() -> ok.
config_reloaded() -> config_reloaded() ->
Mod = get_backend(), Mod = get_backend(),
init_cache(Mod). init_cache(Mod).
%%==================================================================== %%====================================================================
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
@ -322,60 +362,69 @@ init([]) ->
clean_cache(), clean_cache(),
{ok, #state{}}. {ok, #state{}}.
handle_call({monitor, Domain, Pid}, _From, State) -> handle_call({monitor, Domain, Pid}, _From, State) ->
MRefs = State#state.route_monitors, MRefs = State#state.route_monitors,
MRefs1 = case maps:is_key({Domain, Pid}, MRefs) of MRefs1 = case maps:is_key({Domain, Pid}, MRefs) of
true -> MRefs; true -> MRefs;
false -> false ->
MRef = erlang:monitor(process, Pid), MRef = erlang:monitor(process, Pid),
MRefs#{{Domain, Pid} => MRef} MRefs#{{Domain, Pid} => MRef}
end, end,
{reply, ok, State#state{route_monitors = MRefs1}}; {reply, ok, State#state{route_monitors = MRefs1}};
handle_call({demonitor, Domain, Pid}, _From, State) -> handle_call({demonitor, Domain, Pid}, _From, State) ->
MRefs = State#state.route_monitors, MRefs = State#state.route_monitors,
MRefs1 = case maps:find({Domain, Pid}, MRefs) of MRefs1 = case maps:find({Domain, Pid}, MRefs) of
{ok, MRef} -> {ok, MRef} ->
erlang:demonitor(MRef, [flush]), erlang:demonitor(MRef, [flush]),
maps:remove({Domain, Pid}, MRefs); maps:remove({Domain, Pid}, MRefs);
error -> error ->
MRefs MRefs
end, end,
{reply, ok, State#state{route_monitors = MRefs1}}; {reply, ok, State#state{route_monitors = MRefs1}};
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({route, Packet}, State) -> handle_info({route, Packet}, State) ->
route(Packet), route(Packet),
{noreply, State}; {noreply, State};
handle_info({'DOWN', MRef, _, Pid, Info}, State) -> handle_info({'DOWN', MRef, _, Pid, Info}, State) ->
MRefs = maps:filter( MRefs = maps:filter(
fun({Domain, P}, M) when P == Pid, M == MRef -> fun({Domain, P}, M) when P == Pid, M == MRef ->
?DEBUG("Process ~p with route registered to ~ts " ?DEBUG("Process ~p with route registered to ~ts "
"has terminated unexpectedly with reason: ~p", "has terminated unexpectedly with reason: ~p",
[P, Domain, Info]), [P, Domain, Info]),
try unregister_route(Domain, Pid) try
catch _:_ -> ok unregister_route(Domain, Pid)
end, catch
false; _:_ -> ok
(_, _) -> end,
true false;
end, State#state.route_monitors), (_, _) ->
true
end,
State#state.route_monitors),
{noreply, State#state{route_monitors = MRefs}}; {noreply, State#state{route_monitors = MRefs}};
handle_info(Info, State) -> handle_info(Info, State) ->
?ERROR_MSG("Unexpected info: ~p", [Info]), ?ERROR_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50). ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -384,149 +433,167 @@ do_route(OrigPacket1) ->
?DEBUG("Route:~n~ts", [xmpp:pp(OrigPacket1)]), ?DEBUG("Route:~n~ts", [xmpp:pp(OrigPacket1)]),
OrigPacket = process_privilege_iq(OrigPacket1), OrigPacket = process_privilege_iq(OrigPacket1),
case ejabberd_hooks:run_fold(filter_packet, OrigPacket, []) of case ejabberd_hooks:run_fold(filter_packet, OrigPacket, []) of
drop -> drop ->
ok; ok;
Packet -> Packet ->
case ejabberd_iq:dispatch(Packet) of case ejabberd_iq:dispatch(Packet) of
true -> true ->
ok; ok;
false -> false ->
To = xmpp:get_to(Packet), To = xmpp:get_to(Packet),
LDstDomain = To#jid.lserver, LDstDomain = To#jid.lserver,
case find_routes(LDstDomain) of case find_routes(LDstDomain) of
[] -> [] ->
ejabberd_s2s:route(Packet); ejabberd_s2s:route(Packet);
[Route] -> [Route] ->
do_route(Packet, Route); do_route(Packet, Route);
Routes -> Routes ->
From = xmpp:get_from(Packet), From = xmpp:get_from(Packet),
balancing_route(From, To, Packet, Routes) balancing_route(From, To, Packet, Routes)
end, end,
ok ok
end end
end. end.
%% @format-begin %% @format-begin
process_privilege_iq(Packet) -> process_privilege_iq(Packet) ->
Type = xmpp:get_type(Packet), Type = xmpp:get_type(Packet),
case xmpp:get_meta(Packet, privilege_iq, none) of case xmpp:get_meta(Packet, privilege_iq, none) of
{OriginalId, OriginalHost, ReplacedJid} when (Type == result) or (Type == error) -> {OriginalId, OriginalHost, ReplacedJid} when (Type == result) or (Type == error) ->
Privilege = #privilege{forwarded = #forwarded{sub_els = [Packet]}}, Privilege = #privilege{forwarded = #forwarded{sub_els = [Packet]}},
#iq{type = xmpp:get_type(Packet), #iq{
id = OriginalId, type = xmpp:get_type(Packet),
to = jid:make(OriginalHost), id = OriginalId,
from = ReplacedJid, to = jid:make(OriginalHost),
sub_els = [Privilege]}; from = ReplacedJid,
sub_els = [Privilege]
};
_ -> _ ->
Packet Packet
end. end.
%% @format-end %% @format-end
-spec do_route(stanza(), #route{}) -> any(). -spec do_route(stanza(), #route{}) -> any().
do_route(Pkt, #route{local_hint = LocalHint, do_route(Pkt,
pid = Pid}) when is_pid(Pid) -> #route{
local_hint = LocalHint,
pid = Pid
}) when is_pid(Pid) ->
case LocalHint of case LocalHint of
{apply, Module, Function} when node(Pid) == node() -> {apply, Module, Function} when node(Pid) == node() ->
Module:Function(Pkt); Module:Function(Pkt);
_ -> _ ->
ejabberd_cluster:send(Pid, {route, Pkt}) ejabberd_cluster:send(Pid, {route, Pkt})
end; end;
do_route(_Pkt, _Route) -> do_route(_Pkt, _Route) ->
ok. ok.
-spec balancing_route(jid(), jid(), stanza(), [#route{}]) -> any(). -spec balancing_route(jid(), jid(), stanza(), [#route{}]) -> any().
balancing_route(From, To, Packet, Rs) -> balancing_route(From, To, Packet, Rs) ->
case get_domain_balancing(From, To, To#jid.lserver) of case get_domain_balancing(From, To, To#jid.lserver) of
undefined -> undefined ->
Value = erlang:system_time(), Value = erlang:system_time(),
case [R || R <- Rs, node(R#route.pid) == node()] of case [ R || R <- Rs, node(R#route.pid) == node() ] of
[] -> [] ->
R = lists:nth(erlang:phash2(Value, length(Rs))+1, Rs), R = lists:nth(erlang:phash2(Value, length(Rs)) + 1, Rs),
do_route(Packet, R); do_route(Packet, R);
LRs -> LRs ->
R = lists:nth(erlang:phash2(Value, length(LRs))+1, LRs), R = lists:nth(erlang:phash2(Value, length(LRs)) + 1, LRs),
do_route(Packet, R) do_route(Packet, R)
end; end;
Value -> Value ->
SRs = lists:ukeysort(#route.local_hint, Rs), SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash2(Value, length(SRs))+1, SRs), R = lists:nth(erlang:phash2(Value, length(SRs)) + 1, SRs),
do_route(Packet, R) do_route(Packet, R)
end. end.
-spec get_component_number(binary()) -> pos_integer() | undefined. -spec get_component_number(binary()) -> pos_integer() | undefined.
get_component_number(LDomain) -> get_component_number(LDomain) ->
M = ejabberd_option:domain_balancing(), M = ejabberd_option:domain_balancing(),
case maps:get(LDomain, M, undefined) of case maps:get(LDomain, M, undefined) of
undefined -> undefined; undefined -> undefined;
Opts -> maps:get(component_number, Opts) Opts -> maps:get(component_number, Opts)
end. end.
-spec get_domain_balancing(jid(), jid(), binary()) -> integer() | ljid() | undefined. -spec get_domain_balancing(jid(), jid(), binary()) -> integer() | ljid() | undefined.
get_domain_balancing(From, To, LDomain) -> get_domain_balancing(From, To, LDomain) ->
M = ejabberd_option:domain_balancing(), M = ejabberd_option:domain_balancing(),
case maps:get(LDomain, M, undefined) of case maps:get(LDomain, M, undefined) of
undefined -> undefined; undefined -> undefined;
Opts -> Opts ->
case maps:get(type, Opts, random) of case maps:get(type, Opts, random) of
random -> erlang:system_time(); random -> erlang:system_time();
source -> jid:tolower(From); source -> jid:tolower(From);
destination -> jid:tolower(To); destination -> jid:tolower(To);
bare_source -> jid:remove_resource(jid:tolower(From)); bare_source -> jid:remove_resource(jid:tolower(From));
bare_destination -> jid:remove_resource(jid:tolower(To)) bare_destination -> jid:remove_resource(jid:tolower(To))
end end
end. end.
-spec monitor_route(binary(), pid()) -> ok. -spec monitor_route(binary(), pid()) -> ok.
monitor_route(Domain, Pid) -> monitor_route(Domain, Pid) ->
?GEN_SERVER:call(?MODULE, {monitor, Domain, Pid}, ?CALL_TIMEOUT). ?GEN_SERVER:call(?MODULE, {monitor, Domain, Pid}, ?CALL_TIMEOUT).
-spec demonitor_route(binary(), pid()) -> ok. -spec demonitor_route(binary(), pid()) -> ok.
demonitor_route(Domain, Pid) -> demonitor_route(Domain, Pid) ->
case whereis(?MODULE) == self() of case whereis(?MODULE) == self() of
true -> true ->
ok; ok;
false -> false ->
?GEN_SERVER:call(?MODULE, {demonitor, Domain, Pid}, ?CALL_TIMEOUT) ?GEN_SERVER:call(?MODULE, {demonitor, Domain, Pid}, ?CALL_TIMEOUT)
end. end.
-spec get_backend() -> module(). -spec get_backend() -> module().
get_backend() -> get_backend() ->
DBType = ejabberd_option:router_db_type(), DBType = ejabberd_option:router_db_type(),
list_to_existing_atom("ejabberd_router_" ++ atom_to_list(DBType)). list_to_existing_atom("ejabberd_router_" ++ atom_to_list(DBType)).
-spec cache_nodes(module()) -> [node()]. -spec cache_nodes(module()) -> [node()].
cache_nodes(Mod) -> cache_nodes(Mod) ->
case erlang:function_exported(Mod, cache_nodes, 0) of case erlang:function_exported(Mod, cache_nodes, 0) of
true -> Mod:cache_nodes(); true -> Mod:cache_nodes();
false -> ejabberd_cluster:get_nodes() false -> ejabberd_cluster:get_nodes()
end. end.
-spec use_cache(module()) -> boolean(). -spec use_cache(module()) -> boolean().
use_cache(Mod) -> use_cache(Mod) ->
case erlang:function_exported(Mod, use_cache, 0) of case erlang:function_exported(Mod, use_cache, 0) of
true -> Mod:use_cache(); true -> Mod:use_cache();
false -> ejabberd_option:router_use_cache() false -> ejabberd_option:router_use_cache()
end. end.
-spec delete_cache(module(), binary()) -> ok. -spec delete_cache(module(), binary()) -> ok.
delete_cache(Mod, Domain) -> delete_cache(Mod, Domain) ->
case use_cache(Mod) of case use_cache(Mod) of
true -> true ->
ets_cache:delete(?ROUTES_CACHE, {route, Domain}, cache_nodes(Mod)), ets_cache:delete(?ROUTES_CACHE, {route, Domain}, cache_nodes(Mod)),
ets_cache:delete(?ROUTES_CACHE, routes, cache_nodes(Mod)); ets_cache:delete(?ROUTES_CACHE, routes, cache_nodes(Mod));
false -> false ->
ok ok
end. end.
-spec init_cache(module()) -> ok. -spec init_cache(module()) -> ok.
init_cache(Mod) -> init_cache(Mod) ->
case use_cache(Mod) of case use_cache(Mod) of
true -> true ->
ets_cache:new(?ROUTES_CACHE, cache_opts()); ets_cache:new(?ROUTES_CACHE, cache_opts());
false -> false ->
ets_cache:delete(?ROUTES_CACHE) ets_cache:delete(?ROUTES_CACHE)
end. end.
-spec cache_opts() -> [proplists:property()]. -spec cache_opts() -> [proplists:property()].
cache_opts() -> cache_opts() ->
MaxSize = ejabberd_option:router_cache_size(), MaxSize = ejabberd_option:router_cache_size(),
@ -534,21 +601,24 @@ cache_opts() ->
LifeTime = ejabberd_option:router_cache_life_time(), LifeTime = ejabberd_option:router_cache_life_time(),
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec clean_cache(node()) -> non_neg_integer(). -spec clean_cache(node()) -> non_neg_integer().
clean_cache(Node) -> clean_cache(Node) ->
ets_cache:filter( ets_cache:filter(
?ROUTES_CACHE, ?ROUTES_CACHE,
fun(_, error) -> fun(_, error) ->
false; false;
(routes, _) -> (routes, _) ->
false; false;
({route, _}, {ok, Rs}) -> ({route, _}, {ok, Rs}) ->
not lists:any( not lists:any(
fun(#route{pid = Pid}) -> fun(#route{pid = Pid}) ->
node(Pid) == Node node(Pid) == Node
end, Rs) end,
Rs)
end). end).
-spec clean_cache() -> ok. -spec clean_cache() -> ok.
clean_cache() -> clean_cache() ->
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]). ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).

View file

@ -24,213 +24,260 @@
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([init/0, register_route/5, unregister_route/3, find_routes/1, -export([init/0,
get_all_routes/0, use_cache/0]). register_route/5,
unregister_route/3,
find_routes/1,
get_all_routes/0,
use_cache/0]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, handle_info/2, -export([init/1,
terminate/2, code_change/3, start_link/0]). handle_cast/2,
handle_call/3,
handle_info/2,
terminate/2,
code_change/3,
start_link/0]).
-include("ejabberd_router.hrl"). -include("ejabberd_router.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
-record(state, {}). -record(state, {}).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
-spec init() -> ok | {error, any()}. -spec init() -> ok | {error, any()}.
init() -> init() ->
Spec = {?MODULE, {?MODULE, start_link, []}, Spec = {?MODULE, {?MODULE, start_link, []},
transient, 5000, worker, [?MODULE]}, transient,
5000,
worker,
[?MODULE]},
case supervisor:start_child(ejabberd_backend_sup, Spec) of case supervisor:start_child(ejabberd_backend_sup, Spec) of
{ok, _Pid} -> ok; {ok, _Pid} -> ok;
Err -> Err Err -> Err
end. end.
-spec start_link() -> {ok, pid()} | {error, any()}. -spec start_link() -> {ok, pid()} | {error, any()}.
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
use_cache() -> use_cache() ->
false. false.
register_route(Domain, ServerHost, LocalHint, undefined, Pid) -> register_route(Domain, ServerHost, LocalHint, undefined, Pid) ->
F = fun () -> F = fun() ->
mnesia:write(#route{domain = Domain, mnesia:write(#route{
pid = Pid, domain = Domain,
server_host = ServerHost, pid = Pid,
local_hint = LocalHint}) server_host = ServerHost,
end, local_hint = LocalHint
})
end,
transaction(F); transaction(F);
register_route(Domain, ServerHost, _LocalHint, N, Pid) -> register_route(Domain, ServerHost, _LocalHint, N, Pid) ->
F = fun () -> F = fun() ->
case mnesia:wread({route, Domain}) of case mnesia:wread({route, Domain}) of
[] -> [] ->
mnesia:write(#route{domain = Domain, mnesia:write(#route{
server_host = ServerHost, domain = Domain,
pid = Pid, server_host = ServerHost,
local_hint = 1}), pid = Pid,
lists:foreach( local_hint = 1
fun (I) -> }),
mnesia:write( lists:foreach(
#route{domain = Domain, fun(I) ->
pid = undefined, mnesia:write(
server_host = ServerHost, #route{
local_hint = I}) domain = Domain,
end, pid = undefined,
lists:seq(2, N)); server_host = ServerHost,
Rs -> local_hint = I
lists:any( })
fun (#route{pid = undefined, end,
local_hint = I} = R) -> lists:seq(2, N));
mnesia:write( Rs ->
#route{domain = Domain, lists:any(
pid = Pid, fun(#route{
server_host = ServerHost, pid = undefined,
local_hint = I}), local_hint = I
mnesia:delete_object(R), } = R) ->
true; mnesia:write(
(_) -> false #route{
end, domain = Domain,
Rs) pid = Pid,
end server_host = ServerHost,
end, local_hint = I
}),
mnesia:delete_object(R),
true;
(_) -> false
end,
Rs)
end
end,
transaction(F). transaction(F).
unregister_route(Domain, undefined, Pid) -> unregister_route(Domain, undefined, Pid) ->
F = fun () -> F = fun() ->
case mnesia:select( case mnesia:select(
route, route,
ets:fun2ms( ets:fun2ms(
fun(#route{domain = D, pid = P} = R) fun(#route{domain = D, pid = P} = R)
when D == Domain, P == Pid -> R when D == Domain, P == Pid -> R
end)) of end)) of
[R] -> mnesia:delete_object(R); [R] -> mnesia:delete_object(R);
_ -> ok _ -> ok
end end
end, end,
transaction(F); transaction(F);
unregister_route(Domain, _, Pid) -> unregister_route(Domain, _, Pid) ->
F = fun () -> F = fun() ->
case mnesia:select( case mnesia:select(
route, route,
ets:fun2ms( ets:fun2ms(
fun(#route{domain = D, pid = P} = R) fun(#route{domain = D, pid = P} = R)
when D == Domain, P == Pid -> R when D == Domain, P == Pid -> R
end)) of end)) of
[R] -> [R] ->
I = R#route.local_hint, I = R#route.local_hint,
ServerHost = R#route.server_host, ServerHost = R#route.server_host,
mnesia:write(#route{domain = Domain, mnesia:write(#route{
server_host = ServerHost, domain = Domain,
pid = undefined, server_host = ServerHost,
local_hint = I}), pid = undefined,
mnesia:delete_object(R); local_hint = I
_ -> ok }),
end mnesia:delete_object(R);
end, _ -> ok
end
end,
transaction(F). transaction(F).
find_routes(Domain) -> find_routes(Domain) ->
{ok, mnesia:dirty_read(route, Domain)}. {ok, mnesia:dirty_read(route, Domain)}.
get_all_routes() -> get_all_routes() ->
{ok, mnesia:dirty_select( {ok, mnesia:dirty_select(
route, route,
ets:fun2ms( ets:fun2ms(
fun(#route{domain = Domain, server_host = ServerHost}) fun(#route{domain = Domain, server_host = ServerHost})
when Domain /= ServerHost -> Domain when Domain /= ServerHost -> Domain
end))}. end))}.
%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
init([]) -> init([]) ->
update_tables(), update_tables(),
ejabberd_mnesia:create(?MODULE, route, ejabberd_mnesia:create(?MODULE,
[{ram_copies, [node()]}, route,
{type, bag}, [{ram_copies, [node()]},
{attributes, record_info(fields, route)}]), {type, bag},
{attributes, record_info(fields, route)}]),
mnesia:subscribe({table, route, simple}), mnesia:subscribe({table, route, simple}),
lists:foreach( lists:foreach(
fun (Pid) -> erlang:monitor(process, Pid) end, fun(Pid) -> erlang:monitor(process, Pid) end,
mnesia:dirty_select( mnesia:dirty_select(
route, route,
ets:fun2ms( ets:fun2ms(
fun(#route{pid = Pid}) -> Pid end))), fun(#route{pid = Pid}) -> Pid end))),
{ok, #state{}}. {ok, #state{}}.
handle_call(Request, From, State) -> handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({mnesia_table_event, handle_info({mnesia_table_event,
{write, #route{pid = Pid}, _ActivityId}}, State) -> {write, #route{pid = Pid}, _ActivityId}},
State) ->
erlang:monitor(process, Pid), erlang:monitor(process, Pid),
{noreply, State}; {noreply, State};
handle_info({mnesia_table_event, _}, State) -> handle_info({mnesia_table_event, _}, State) ->
{noreply, State}; {noreply, State};
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) -> handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
F = fun () -> F = fun() ->
Es = mnesia:select( Es = mnesia:select(
route, route,
ets:fun2ms( ets:fun2ms(
fun(#route{pid = P} = E) fun(#route{pid = P} = E)
when P == Pid -> E when P == Pid -> E
end)), end)),
lists:foreach( lists:foreach(
fun(E) -> fun(E) ->
if is_integer(E#route.local_hint) -> if
LDomain = E#route.domain, is_integer(E#route.local_hint) ->
I = E#route.local_hint, LDomain = E#route.domain,
ServerHost = E#route.server_host, I = E#route.local_hint,
mnesia:write(#route{domain = LDomain, ServerHost = E#route.server_host,
server_host = ServerHost, mnesia:write(#route{
pid = undefined, domain = LDomain,
local_hint = I}), server_host = ServerHost,
mnesia:delete_object(E); pid = undefined,
true -> local_hint = I
mnesia:delete_object(E) }),
end mnesia:delete_object(E);
end, Es) true ->
end, mnesia:delete_object(E)
end
end,
Es)
end,
transaction(F), transaction(F),
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?ERROR_MSG("Unexpected info: ~p", [Info]), ?ERROR_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
transaction(F) -> transaction(F) ->
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, _} -> {atomic, _} ->
ok; ok;
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure} {error, db_failure}
end. end.
-spec update_tables() -> ok. -spec update_tables() -> ok.
update_tables() -> update_tables() ->
try try
mnesia:transform_table(route, ignore, record_info(fields, route)) mnesia:transform_table(route, ignore, record_info(fields, route))
catch exit:{aborted, {no_exists, _}} -> catch
ok exit:{aborted, {no_exists, _}} ->
ok
end, end,
case lists:member(local_route, mnesia:system_info(tables)) of case lists:member(local_route, mnesia:system_info(tables)) of
true -> mnesia:delete_table(local_route); true -> mnesia:delete_table(local_route);
false -> ok false -> ok
end. end.

View file

@ -31,23 +31,31 @@
%% API %% API
-export([route_multicast/5, -export([route_multicast/5,
register_route/1, register_route/1,
unregister_route/1 unregister_route/1]).
]).
-export([start_link/0]). -export([start_link/0]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1,
terminate/2, code_change/3, update_to_in_wrapped/2]). handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3,
update_to_in_wrapped/2]).
-include("logger.hrl"). -include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl"). -include_lib("xmpp/include/xmpp.hrl").
-record(route_multicast, {domain = <<"">> :: binary() | '_', -record(route_multicast, {
pid = self() :: pid()}). domain = <<"">> :: binary() | '_',
pid = self() :: pid()
}).
-record(state, {}). -record(state, {}).
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -58,49 +66,54 @@
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route_multicast(jid(), binary(), [jid()], stanza(), boolean()) -> ok. -spec route_multicast(jid(), binary(), [jid()], stanza(), boolean()) -> ok.
route_multicast(From0, Domain0, Destinations0, Packet0, Wrapped0) -> route_multicast(From0, Domain0, Destinations0, Packet0, Wrapped0) ->
{From, Domain, Destinations, Packet, Wrapped} = {From, Domain, Destinations, Packet, Wrapped} =
ejabberd_hooks:run_fold(multicast_route, Domain0, {From0, Domain0, Destinations0, Packet0, Wrapped0}, []), ejabberd_hooks:run_fold(multicast_route, Domain0, {From0, Domain0, Destinations0, Packet0, Wrapped0}, []),
case catch do_route(Domain, Destinations, xmpp:set_from(Packet, From), Wrapped) of case catch do_route(Domain, Destinations, xmpp:set_from(Packet, From), Wrapped) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, Domain, Destinations, Packet}]); [Reason, {From, Domain, Destinations, Packet}]);
_ -> _ ->
ok ok
end. end.
-spec register_route(binary()) -> any(). -spec register_route(binary()) -> any().
register_route(Domain) -> register_route(Domain) ->
case jid:nameprep(Domain) of case jid:nameprep(Domain) of
error -> error ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
Pid = self(), Pid = self(),
F = fun() -> F = fun() ->
mnesia:write(#route_multicast{domain = LDomain, mnesia:write(#route_multicast{
pid = Pid}) domain = LDomain,
end, pid = Pid
mnesia:transaction(F) })
end,
mnesia:transaction(F)
end. end.
-spec unregister_route(binary()) -> any(). -spec unregister_route(binary()) -> any().
unregister_route(Domain) -> unregister_route(Domain) ->
case jid:nameprep(Domain) of case jid:nameprep(Domain) of
error -> error ->
erlang:error({invalid_domain, Domain}); erlang:error({invalid_domain, Domain});
LDomain -> LDomain ->
Pid = self(), Pid = self(),
F = fun() -> F = fun() ->
case mnesia:select(route_multicast, case mnesia:select(route_multicast,
[{#route_multicast{pid = Pid, domain = LDomain, _ = '_'}, [{#route_multicast{pid = Pid, domain = LDomain, _ = '_'},
[], [],
['$_']}]) of ['$_']}]) of
[R] -> mnesia:delete_object(R); [R] -> mnesia:delete_object(R);
_ -> ok _ -> ok
end end
end, end,
mnesia:transaction(F) mnesia:transaction(F)
end. end.
@ -108,6 +121,7 @@ unregister_route(Domain) ->
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} | %% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} | %% {ok, State, Timeout} |
@ -116,19 +130,21 @@ unregister_route(Domain) ->
%% Description: Initiates the server %% Description: Initiates the server
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
ejabberd_mnesia:create(?MODULE, route_multicast, ejabberd_mnesia:create(?MODULE,
[{ram_copies, [node()]}, route_multicast,
{type, bag}, [{ram_copies, [node()]},
{attributes, {type, bag},
record_info(fields, route_multicast)}]), {attributes,
record_info(fields, route_multicast)}]),
mnesia:subscribe({table, route_multicast, simple}), mnesia:subscribe({table, route_multicast, simple}),
lists:foreach( lists:foreach(
fun(Pid) -> fun(Pid) ->
erlang:monitor(process, Pid) erlang:monitor(process, Pid)
end, end,
mnesia:dirty_select(route_multicast, [{{route_multicast, '_', '$1'}, [], ['$1']}])), mnesia:dirty_select(route_multicast, [{{route_multicast, '_', '$1'}, [], ['$1']}])),
{ok, #state{}}. {ok, #state{}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} | %% {reply, Reply, State, Timeout} |
@ -142,6 +158,7 @@ handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}. {noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} | %% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} | %% {noreply, State, Timeout} |
@ -152,6 +169,7 @@ handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]), ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} | %% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} | %% {noreply, State, Timeout} |
@ -160,35 +178,37 @@ handle_cast(Msg, State) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info({route_multicast, Domain, Destinations, Packet}, State) -> handle_info({route_multicast, Domain, Destinations, Packet}, State) ->
case catch do_route(Domain, Destinations, Packet, false) of case catch do_route(Domain, Destinations, Packet, false) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p", ?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {Domain, Destinations, Packet}]); [Reason, {Domain, Destinations, Packet}]);
_ -> _ ->
ok ok
end, end,
{noreply, State}; {noreply, State};
handle_info({mnesia_table_event, {write, #route_multicast{pid = Pid}, _ActivityId}}, handle_info({mnesia_table_event, {write, #route_multicast{pid = Pid}, _ActivityId}},
State) -> State) ->
erlang:monitor(process, Pid), erlang:monitor(process, Pid),
{noreply, State}; {noreply, State};
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) -> handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
F = fun() -> F = fun() ->
Es = mnesia:select( Es = mnesia:select(
route_multicast, route_multicast,
[{#route_multicast{pid = Pid, _ = '_'}, [{#route_multicast{pid = Pid, _ = '_'},
[], [],
['$_']}]), ['$_']}]),
lists:foreach( lists:foreach(
fun(E) -> fun(E) ->
mnesia:delete_object(E) mnesia:delete_object(E)
end, Es) end,
end, Es)
end,
mnesia:transaction(F), mnesia:transaction(F),
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]), ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void() %% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to %% Description: This function is called by a gen_server when it is about to
@ -199,6 +219,7 @@ handle_info(Info, State) ->
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed %% Description: Convert process state when code is changed
@ -206,25 +227,29 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
-spec update_to_in_wrapped(stanza(), jid()) -> stanza(). -spec update_to_in_wrapped(stanza(), jid()) -> stanza().
update_to_in_wrapped(Packet, To) -> update_to_in_wrapped(Packet, To) ->
case Packet of case Packet of
#message{sub_els = [#ps_event{ #message{
items = #ps_items{ sub_els = [#ps_event{
items = [#ps_item{ items = #ps_items{
sub_els = [Internal] items = [#ps_item{
} = PSItem] sub_els = [Internal]
} = PSItems } = PSItem]
} = PSEvent]} -> } = PSItems
Internal2 = xmpp:set_to(Internal, To), } = PSEvent]
PSItem2 = PSItem#ps_item{sub_els = [Internal2]}, } ->
PSItems2 = PSItems#ps_items{items = [PSItem2]}, Internal2 = xmpp:set_to(Internal, To),
PSEvent2 = PSEvent#ps_event{items = PSItems2}, PSItem2 = PSItem#ps_item{sub_els = [Internal2]},
xmpp:set_to(Packet#message{sub_els = [PSEvent2]}, To); PSItems2 = PSItems#ps_items{items = [PSItem2]},
_ -> PSEvent2 = PSEvent#ps_event{items = PSItems2},
xmpp:set_to(Packet, To) xmpp:set_to(Packet#message{sub_els = [PSEvent2]}, To);
_ ->
xmpp:set_to(Packet, To)
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -233,40 +258,46 @@ update_to_in_wrapped(Packet, To) ->
-spec do_route(binary(), [jid()], stanza(), boolean()) -> any(). -spec do_route(binary(), [jid()], stanza(), boolean()) -> any().
do_route(Domain, Destinations, Packet, true) -> do_route(Domain, Destinations, Packet, true) ->
?DEBUG("Route multicast:~n~ts~nDomain: ~ts~nDestinations: ~ts~n", ?DEBUG("Route multicast:~n~ts~nDomain: ~ts~nDestinations: ~ts~n",
[xmpp:pp(Packet), Domain, [xmpp:pp(Packet),
str:join([jid:encode(To) || To <- Destinations], <<", ">>)]), Domain,
str:join([ jid:encode(To) || To <- Destinations ], <<", ">>)]),
lists:foreach( lists:foreach(
fun(To) -> fun(To) ->
Packet2 = update_to_in_wrapped(Packet, To), Packet2 = update_to_in_wrapped(Packet, To),
ejabberd_router:route(Packet2) ejabberd_router:route(Packet2)
end, Destinations); end,
Destinations);
do_route(Domain, Destinations, Packet, false) -> do_route(Domain, Destinations, Packet, false) ->
?DEBUG("Route multicast:~n~ts~nDomain: ~ts~nDestinations: ~ts~n", ?DEBUG("Route multicast:~n~ts~nDomain: ~ts~nDestinations: ~ts~n",
[xmpp:pp(Packet), Domain, [xmpp:pp(Packet),
str:join([jid:encode(To) || To <- Destinations], <<", ">>)]), Domain,
str:join([ jid:encode(To) || To <- Destinations ], <<", ">>)]),
%% Try to find an appropriate multicast service %% Try to find an appropriate multicast service
case mnesia:dirty_read(route_multicast, Domain) of case mnesia:dirty_read(route_multicast, Domain) of
%% If no multicast service is available in this server, send manually %% If no multicast service is available in this server, send manually
[] -> do_route_normal(Destinations, Packet); [] -> do_route_normal(Destinations, Packet);
%% If some is available, send the packet using multicast service %% If some is available, send the packet using multicast service
Rs when is_list(Rs) -> Rs when is_list(Rs) ->
Pid = pick_multicast_pid(Rs), Pid = pick_multicast_pid(Rs),
Pid ! {route_trusted, Destinations, Packet} Pid ! {route_trusted, Destinations, Packet}
end. end.
-spec pick_multicast_pid([#route_multicast{}]) -> pid(). -spec pick_multicast_pid([#route_multicast{}]) -> pid().
pick_multicast_pid(Rs) -> pick_multicast_pid(Rs) ->
List = case [R || R <- Rs, node(R#route_multicast.pid) == node()] of List = case [ R || R <- Rs, node(R#route_multicast.pid) == node() ] of
[] -> Rs; [] -> Rs;
RLocals -> RLocals RLocals -> RLocals
end, end,
(hd(List))#route_multicast.pid. (hd(List))#route_multicast.pid.
-spec do_route_normal([jid()], stanza()) -> any(). -spec do_route_normal([jid()], stanza()) -> any().
do_route_normal(Destinations, Packet) -> do_route_normal(Destinations, Packet) ->
lists:foreach( lists:foreach(
fun(To) -> fun(To) ->
ejabberd_router:route(xmpp:set_to(Packet, To)) ejabberd_router:route(xmpp:set_to(Packet, To))
end, Destinations). end,
Destinations).

Some files were not shown because too many files have changed in this diff Show more