1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-03 17:59:31 +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@
EPMD = @EPMD@
IEX = @IEX@
EMACS = @EMACS@
INSTALLUSER=@INSTALLUSER@
INSTALLGROUP=@INSTALLGROUP@
@ -266,11 +267,15 @@ _build/edoc/logo.png: edoc_compile
#' format / indent
#
.SILENT: format indent
FORMAT_LOG=/tmp/ejabberd-format.log
format:
tools/rebar3-format.sh $(REBAR3)
tools/rebar3-format.sh $(FORMAT_LOG) $(REBAR3)
indent:
tools/emacs-indent.sh
tools/emacs-indent.sh $(FORMAT_LOG) $(EMACS)
#.
#' copy-files
@ -714,7 +719,7 @@ help:
@echo " translations Extract translation files"
@echo " TAGS Generate tags file for text editors"
@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 ""
@echo " dialyzer Run Dialyzer static analyzer"

View file

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

View file

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

View file

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

View file

@ -3,24 +3,24 @@
-export([init/1, do/1, format_error/1]).
-define(PROVIDER, 'configure-deps').
-define(DEPS, [install_deps]).
-define(DEPS, [install_deps]).
%% ===================================================================
%% Public API
%% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Provider = providers:create([
{namespace, default},
{name, ?PROVIDER}, % The 'user friendly' name of the task
{module, ?MODULE}, % The module implementation of the task
{bare, true}, % The task can be run by the user, always true
{deps, ?DEPS}, % The list of dependencies
{example, "rebar3 configure-deps"}, % How to use the plugin
{opts, []}, % list of options understood by the plugin
{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."}
]),
Provider = providers:create(
[{namespace, default},
{name, ?PROVIDER}, % The 'user friendly' name of the task
{module, ?MODULE}, % The module implementation of the task
{bare, true}, % The task can be run by the user, always true
{deps, ?DEPS}, % The list of dependencies
{example, "rebar3 configure-deps"}, % How to use the plugin
{opts, []}, % list of options understood by the plugin
{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."}]),
{ok, rebar_state:add_provider(State, Provider)}.
@ -30,25 +30,30 @@ do(State) ->
lists:foreach(fun do_app/1, Apps),
{ok, State}.
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.
parse_pre_hooks({pre_hooks, PreHooks}, Acc) ->
lists:foldl(fun exec_configure/2, Acc, PreHooks);
parse_pre_hooks(_, Acc) -> Acc.
parse_additions({add, App, Additions}, {MyApp, Dir}) when App == MyApp ->
lists:foldl(fun parse_pre_hooks/2, Dir, Additions),
{MyApp, Dir};
{MyApp, Dir};
parse_additions(_, Acc) -> Acc.
do_app(App) ->
Dir = rebar_app_info:dir(App),
Opts = rebar_app_info:opts(App),
Overrides = rebar_opts:get(Opts, overrides),
Opts = rebar_app_info:opts(App),
Overrides = rebar_opts:get(Opts, 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) ->
io_lib:format("~p", [Reason]).

View file

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

View file

@ -3,79 +3,101 @@
%% SEQUENCE and SET, and macro definitions for each value
%% definition,in module ELDAPv3
-record('LDAPMessage', {
messageID, protocolOp, controls = asn1_NOVALUE
}).
-record('AttributeValueAssertion', {
attributeDesc, assertionValue
}).
-record('LDAPMessage',{
messageID, protocolOp, controls = asn1_NOVALUE}).
-record('Attribute', {
type, vals
}).
-record('AttributeValueAssertion',{
attributeDesc, assertionValue}).
-record('LDAPResult', {
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE
}).
-record('Attribute',{
type, vals}).
-record('Control', {
controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE
}).
-record('LDAPResult',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE}).
-record('BindRequest', {
version, name, authentication
}).
-record('Control',{
controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE}).
-record('SaslCredentials', {
mechanism, credentials = asn1_NOVALUE
}).
-record('BindRequest',{
version, name, authentication}).
-record('BindResponse', {
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE
}).
-record('SaslCredentials',{
mechanism, credentials = asn1_NOVALUE}).
-record('SearchRequest', {
baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes
}).
-record('BindResponse',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE}).
-record('SubstringFilter', {
type, substrings
}).
-record('SearchRequest',{
baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes}).
-record('MatchingRuleAssertion', {
matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT
}).
-record('SubstringFilter',{
type, substrings}).
-record('SearchResultEntry', {
objectName, attributes
}).
-record('MatchingRuleAssertion',{
matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT}).
-record('PartialAttributeList_SEQOF', {
type, vals
}).
-record('SearchResultEntry',{
objectName, attributes}).
-record('ModifyRequest', {
object, modification
}).
-record('PartialAttributeList_SEQOF',{
type, vals}).
-record('ModifyRequest_modification_SEQOF', {
operation, modification
}).
-record('ModifyRequest',{
object, modification}).
-record('AttributeTypeAndValues', {
type, vals
}).
-record('ModifyRequest_modification_SEQOF',{
operation, modification}).
-record('AddRequest', {
entry, attributes
}).
-record('AttributeTypeAndValues',{
type, vals}).
-record('AttributeList_SEQOF', {
type, vals
}).
-record('AddRequest',{
entry, attributes}).
-record('ModifyDNRequest', {
entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE
}).
-record('AttributeList_SEQOF',{
type, vals}).
-record('CompareRequest', {
entry, ava
}).
-record('ModifyDNRequest',{
entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE}).
-record('ExtendedRequest', {
requestName, requestValue = asn1_NOVALUE
}).
-record('CompareRequest',{
entry, ava}).
-record('ExtendedResponse', {
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE
}).
-record('ExtendedRequest',{
requestName, requestValue = asn1_NOVALUE}).
-record('PasswdModifyRequestValue', {
userIdentity = asn1_NOVALUE, oldPasswd = asn1_NOVALUE, newPasswd = asn1_NOVALUE
}).
-record('ExtendedResponse',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE}).
-record('PasswdModifyResponseValue', {
genPasswd = asn1_NOVALUE
}).
-record('PasswdModifyRequestValue',{
userIdentity = asn1_NOVALUE, oldPasswd = asn1_NOVALUE, newPasswd = asn1_NOVALUE}).
-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]).
-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,
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
-define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}).
{<<"Content-Type">>, <<"text/plain">>}).
-define(CT_JSON,
{<<"Content-Type">>, <<"application/json">>}).
-define(AC_ALLOW_ORIGIN,
{<<"Access-Control-Allow-Origin">>, <<"*">>}).
{<<"Access-Control-Allow-Origin">>, <<"*">>}).
-define(AC_ALLOW_METHODS,
{<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}).
{<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}).
-define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
-define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}).
{<<"Access-Control-Max-Age">>, <<"86400">>}).
-define(OPTIONS_HEADER,
[?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
[?CT_PLAIN,
?AC_ALLOW_ORIGIN,
?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS,
?AC_MAX_AGE]).
-define(HEADER(CType),
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
-define(BOSH_CACHE, bosh_cache).

View file

@ -18,5 +18,7 @@
%%%
%%%----------------------------------------------------------------------
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | {binary(), binary(), atom()} | '$1',
password = <<"">> :: binary() | scram() | '_'}).
-record(passwd, {
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.
%%%
%%%----------------------------------------------------------------------
%% @efmt:off
%% @indent-begin
-type aterm() :: {atom(), atype()}.
-type atype() :: integer | string | binary | any | atom |
@ -104,4 +106,3 @@
args_example :: none | [any()] | '_',
result_example :: any()
}.

View file

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

View file

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

View file

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

View file

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

View file

@ -30,46 +30,62 @@
-define(SQL_INSERT(Table, Fields), ?SQL_INSERT_MARK(Table, Fields)).
-ifdef(COMPILER_REPORTS_ONLY_LINES).
-record(sql_query, {hash :: binary(),
format_query :: fun(),
format_res :: fun(),
args :: fun(),
flags :: non_neg_integer(),
loc :: {module(), pos_integer()}}).
-record(sql_query, {
hash :: binary(),
format_query :: fun(),
format_res :: fun(),
args :: fun(),
flags :: non_neg_integer(),
loc :: {module(), pos_integer()}
}).
-else.
-record(sql_query, {hash :: binary(),
format_query :: fun(),
format_res :: fun(),
args :: fun(),
flags :: non_neg_integer(),
loc :: {module(), {pos_integer(), pos_integer()}}}).
-record(sql_query, {
hash :: binary(),
format_query :: fun(),
format_res :: fun(),
args :: fun(),
flags :: non_neg_integer(),
loc :: {module(), {pos_integer(), pos_integer()}}
}).
-endif.
-record(sql_escape, {string :: fun((binary()) -> binary()),
integer :: fun((integer()) -> binary()),
boolean :: fun((boolean()) -> binary()),
in_array_string :: fun((binary()) -> binary()),
like_escape :: fun(() -> binary())}).
-record(sql_escape, {
string :: fun((binary()) -> binary()),
integer :: fun((integer()) -> binary()),
boolean :: fun((boolean()) -> 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,
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_schema_info,
{db_type :: pgsql | mysql | sqlite,
db_version :: any(),
new_schema = true :: boolean()}).
-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),
#xmlel{name = Name, attrs = [], children = []}).
#xmlel{name = Name, attrs = [], children = []}).
-define(XA(Name, Attrs),
#xmlel{name = Name, attrs = Attrs, children = []}).
#xmlel{name = Name, attrs = Attrs, children = []}).
-define(XE(Name, Els),
#xmlel{name = Name, attrs = [], children = Els}).
#xmlel{name = Name, attrs = [], children = 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(XC(Name, Text), ?XE(Name, [?C(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(XCT(Name, Text), ?XC(Name, (translate:translate(Lang, 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(A(URL, Els),
?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
@ -58,69 +58,79 @@
-define(BR, ?X(<<"br">>)).
-define(INPUT(Type, Name, Value),
?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name},
{<<"value">>, Value}])).
?XA(<<"input">>,
[{<<"type">>, Type},
{<<"name">>, Name},
{<<"value">>, Value}])).
-define(INPUTPH(Type, Name, Value, PlaceHolder),
?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name},
{<<"value">>, Value}, {<<"placeholder">>, PlaceHolder}])).
?XA(<<"input">>,
[{<<"type">>, Type},
{<<"name">>, Name},
{<<"value">>, Value},
{<<"placeholder">>, PlaceHolder}])).
-define(INPUTT(Type, Name, Value),
?INPUT(Type, Name, (translate:translate(Lang, Value)))).
?INPUT(Type, Name, (translate:translate(Lang, Value)))).
-define(INPUTD(Type, Name, Value),
?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name},
{<<"class">>, <<"btn-danger">>}, {<<"value">>, Value}])).
?XA(<<"input">>,
[{<<"type">>, Type},
{<<"name">>, Name},
{<<"class">>, <<"btn-danger">>},
{<<"value">>, 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),
?XA(<<"input">>,
[{<<"type">>, Type}, {<<"name">>, Name},
{<<"value">>, Value}, {<<"size">>, Size}])).
?XA(<<"input">>,
[{<<"type">>, Type},
{<<"name">>, Name},
{<<"value">>, Value},
{<<"size">>, 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),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
-define(TEXTAREA(Name, Rows, Cols, Value),
?XAC(<<"textarea">>,
[{<<"name">>, Name}, {<<"rows">>, Rows},
{<<"cols">>, Cols}],
Value)).
?XAC(<<"textarea">>,
[{<<"name">>, Name},
{<<"rows">>, Rows},
{<<"cols">>, Cols}],
Value)).
%% Build an xmlelement for result
-define(XRES(Text),
?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
-define(DIVRES(Elements),
?XAE(<<"div">>, [{<<"class">>, <<"result">>}], Elements)).
?XAE(<<"div">>, [{<<"class">>, <<"result">>}], Elements)).
%% Guide Link
-define(XREST(Text), ?XRES((translate:translate(Lang, Text)))).
-define(GL(Ref, Title),
?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}],
[?XAE(<<"a">>,
[{<<"href">>, <<"https://docs.ejabberd.im/", Ref/binary>>},
{<<"target">>, <<"_blank">>}],
[?C(<<"docs: ", Title/binary>>)])])).
?XAE(<<"div">>,
[{<<"class">>, <<"guidelink">>}],
[?XAE(<<"a">>,
[{<<"href">>, <<"https://docs.ejabberd.im/", Ref/binary>>},
{<<"target">>, <<"_blank">>}],
[?C(<<"docs: ", Title/binary>>)])])).
%% h1 with a Guide Link
-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),
?H1GLraw(Name, <<"admin/configuration/", RefConf/binary>>, Title)).
?H1GLraw(Name, <<"admin/configuration/", RefConf/binary>>, Title)).
-define(ANCHORL(Ref),
?XAE(<<"div">>, [{<<"class">>, <<"anchorlink">>}],
[?XAE(<<"a">>,
[{<<"href">>, <<"#", Ref/binary>>}],
[?C(unicode:characters_to_binary(""))])])).
?XAE(<<"div">>,
[{<<"class">>, <<"anchorlink">>}],
[?XAE(<<"a">>,
[{<<"href">>, <<"#", Ref/binary>>}],
[?C(unicode:characters_to_binary(""))])])).

View file

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

View file

@ -19,31 +19,34 @@
%%%----------------------------------------------------------------------
-define(CT_XML,
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
-define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}).
{<<"Content-Type">>, <<"text/plain">>}).
-define(AC_ALLOW_ORIGIN,
{<<"Access-Control-Allow-Origin">>, <<"*">>}).
{<<"Access-Control-Allow-Origin">>, <<"*">>}).
-define(AC_ALLOW_METHODS,
{<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}).
{<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}).
-define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
-define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}).
{<<"Access-Control-Max-Age">>, <<"86400">>}).
-define(NO_CACHE,
{<<"Cache-Control">>, <<"max-age=0, no-cache, no-store">>}).
-define(OPTIONS_HEADER,
[?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
[?CT_PLAIN,
?AC_ALLOW_ORIGIN,
?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS,
?AC_MAX_AGE]).
-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}]).
-define(DEBUG(Format, Args),
begin lager:debug(Format, Args), ok end).
begin lager:debug(Format, Args), ok end).
-define(INFO_MSG(Format, Args),
begin lager:info(Format, Args), ok end).
begin lager:info(Format, Args), ok end).
-define(WARNING_MSG(Format, Args),
begin lager:warning(Format, Args), ok end).
begin lager:warning(Format, Args), ok end).
-define(ERROR_MSG(Format, Args),
begin lager:error(Format, Args), ok end).
begin lager:error(Format, Args), ok end).
-define(CRITICAL_MSG(Format, Args),
begin lager:critical(Format, Args), ok end).
begin lager:critical(Format, Args), ok end).
-else.
-include_lib("kernel/include/logger.hrl").
-define(CLEAD, "\e[1"). % bold
-define(CMID, "\e[0"). % normal
-define(CCLEAN, "\e[0m"). % clean
-define(CLEAD, "\e[1"). % bold
-define(CMID, "\e[0"). % normal
-define(CCLEAN, "\e[0m"). % clean
-define(CDEFAULT, ";49;95m"). % light magenta
-define(CDEBUG, ";49;90m"). % dark gray
-define(CINFO, ";49;92m"). % green
-define(CWARNING, ";49;93m"). % light yellow
-define(CERROR, ";49;91m"). % light magenta
-define(CCRITICAL,";49;31m"). % light red
-define(CDEFAULT, ";49;95m"). % light magenta
-define(CDEBUG, ";49;90m"). % dark gray
-define(CINFO, ";49;92m"). % green
-define(CWARNING, ";49;93m"). % light yellow
-define(CERROR, ";49;91m"). % light magenta
-define(CCRITICAL, ";49;31m"). % light red
-define(DEBUG(Format, Args),
begin ?LOG_DEBUG(Format, Args,
#{clevel => ?CLEAD ++ ?CDEBUG,
ctext => ?CMID ++ ?CDEBUG}),
ok end).
begin
?LOG_DEBUG(Format,
Args,
#{
clevel => ?CLEAD ++ ?CDEBUG,
ctext => ?CMID ++ ?CDEBUG
}),
ok
end).
-define(INFO_MSG(Format, Args),
begin ?LOG_INFO(Format, Args,
#{clevel => ?CLEAD ++ ?CINFO,
ctext => ?CCLEAN}),
ok end).
begin
?LOG_INFO(Format,
Args,
#{
clevel => ?CLEAD ++ ?CINFO,
ctext => ?CCLEAN
}),
ok
end).
-define(WARNING_MSG(Format, Args),
begin ?LOG_WARNING(Format, Args,
#{clevel => ?CLEAD ++ ?CWARNING,
ctext => ?CMID ++ ?CWARNING}),
ok end).
begin
?LOG_WARNING(Format,
Args,
#{
clevel => ?CLEAD ++ ?CWARNING,
ctext => ?CMID ++ ?CWARNING
}),
ok
end).
-define(ERROR_MSG(Format, Args),
begin ?LOG_ERROR(Format, Args,
#{clevel => ?CLEAD ++ ?CERROR,
ctext => ?CMID ++ ?CERROR}),
ok end).
begin
?LOG_ERROR(Format,
Args,
#{
clevel => ?CLEAD ++ ?CERROR,
ctext => ?CMID ++ ?CERROR
}),
ok
end).
-define(CRITICAL_MSG(Format, Args),
begin ?LOG_CRITICAL(Format, Args,
#{clevel => ?CLEAD++ ?CCRITICAL,
ctext => ?CMID ++ ?CCRITICAL}),
ok end).
begin
?LOG_CRITICAL(Format,
Args,
#{
clevel => ?CLEAD ++ ?CCRITICAL,
ctext => ?CMID ++ ?CCRITICAL
}),
ok
end).
-endif.
%% Use only when trying to troubleshoot test problem with ExUnit

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -23,87 +23,94 @@
%%%
{deps, [{if_not_rebar3,
{if_version_below, "24",
{base64url, "~> 1.0", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}}
}},
{if_version_below,
"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"}}},
{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"}}}},
{if_var_true, redis,
{if_var_true,
redis,
{if_not_rebar3,
{eredis, "~> 1.2.0", {git, "https://github.com/wooga/eredis/", {tag, "v1.2.0"}}}
}},
{if_var_true, redis,
{eredis, "~> 1.2.0", {git, "https://github.com/wooga/eredis/", {tag, "v1.2.0"}}}}},
{if_var_true,
redis,
{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.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}}
}}},
{if_var_true, sip,
{eredis, "~> 1.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}}}}},
{if_var_true,
sip,
{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"}}}},
{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_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"}}},
{if_version_below, "27",
{jiffy, "~> 1.1.1", {git, "https://github.com/davisp/jiffy", {tag, "1.1.1"}}}
},
{if_version_above, "23",
{if_version_below,
"27",
{jiffy, "~> 1.1.1", {git, "https://github.com/davisp/jiffy", {tag, "1.1.1"}}}},
{if_version_above,
"23",
{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"}}}
},
{if_version_below, "22",
{lager, "~> 3.9.1", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}}
},
{if_var_true, lua,
{jose, "1.11.1", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}}},
{if_version_below,
"22",
{lager, "~> 3.9.1", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}}},
{if_var_true,
lua,
{if_version_below, "21",
{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.0.0", {git, "https://github.com/rvirding/luerl", {tag, "1.0"}}},
{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"}}},
{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_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_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"}}},
{if_var_true, sqlite,
{if_var_true,
sqlite,
{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"}}},
{if_var_true, stun,
{if_var_true,
stun,
{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"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", "95692795a8a8d950ba560e5b07e6b80660557259"}}
]}.
{yconf, ".*", {git, "https://github.com/processone/yconf", "95692795a8a8d950ba560e5b07e6b80660557259"}}]}.
{gitonly_deps, [ejabberd_po]}.
{if_var_true, latest_deps,
{floating_deps, [cache_tab,
eimp,
epam,
esip,
ezlib,
fast_tls,
fast_xml,
fast_yaml,
mqtree,
p1_acme,
p1_mysql,
p1_oauth2,
p1_pgsql,
p1_utils,
pkix,
sqlite3,
stringprep,
stun,
xmpp,
yconf]}}.
{floating_deps, [cache_tab,
eimp,
epam,
esip,
ezlib,
fast_tls,
fast_xml,
fast_yaml,
mqtree,
p1_acme,
p1_mysql,
p1_oauth2,
p1_pgsql,
p1_utils,
pkix,
sqlite3,
stringprep,
stun,
xmpp,
yconf]}}.
%%%
%%% Compile
@ -121,8 +128,8 @@
{"stringprep", []}]}.
{erl_first_files, ["src/ejabberd_sql_pt.erl", "src/ejabberd_config.erl",
"src/gen_mod.erl", "src/mod_muc_room.erl",
"src/mod_push.erl", "src/xmpp_socket.erl"]}.
"src/gen_mod.erl", "src/mod_muc_room.erl",
"src/mod_push.erl", "src/xmpp_socket.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{i, "include"},
@ -159,22 +166,17 @@
%% https://github.com/Supersonido/rebar_mix/issues/27#issuecomment-894873335
%% Let's use this fixed rebar_mix fork, see its PR:
%% https://github.com/Supersonido/rebar_mix/pull/31
{if_var_true, elixir, {rebar_mix, ".*",
{git, "https://github.com/bsanyi/rebar_mix.git",
{branch, "consolidation_fix"}}}
}]}}.
{if_var_true, elixir,
{rebar_mix, ".*",
{git, "https://github.com/bsanyi/rebar_mix.git",
{branch, "consolidation_fix"}}}}]}}.
{if_rebar3, {project_plugins, [configure_deps,
{if_var_true, tools, rebar3_format},
{if_var_true, tools, {rebar3_lint, "4.1.1"}}
]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts, override_deps_versions2, override_opts, configure_deps
]}}.
{if_var_true, tools, rebar3_efmt},
{if_var_true, tools, {rebar3_lint, "4.1.1"}}]}}.
{if_not_rebar3, {plugins, [deps_erl_opts, override_deps_versions2, override_opts, configure_deps]}}.
{if_rebar3, {if_var_true, elixir,
{provider_hooks, [
{post, [{compile, {mix, consolidate_protocols}}]}
]}}}.
{provider_hooks, [{post, [{compile, {mix, consolidate_protocols}}]}]}}}.
%% 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]}]}]}}.
@ -192,16 +194,13 @@
{if_rebar3,
{xref_checks,
[deprecated_function_calls, deprecated_functions, locals_not_used,
undefined_function_calls, undefined_functions]}
}.
undefined_function_calls, undefined_functions]}}.
{if_not_rebar3,
{xref_checks,
[deprecated_function_calls, deprecated_functions,
undefined_function_calls, undefined_functions]}
}.
undefined_function_calls, undefined_functions]}}.
{xref_exclusions, [
"(\"gen_transport\":_/_)",
{xref_exclusions, ["(\"gen_transport\":_/_)",
"(\"eprof\":_/_)",
{if_var_false, elixir, "(\"Elixir.*\":_/_)"},
{if_var_false, http, "(\"lhttpc\":_/_)"},
@ -219,37 +218,62 @@
{eunit_compile_opts, [{i, "tools"},
{i, "include"}]}.
{dialyzer, [{get_warnings, false}, % Show warnings of dependencies
{dialyzer, [{get_warnings, false}, % Show warnings of dependencies
{if_version_above, "25",
{plt_extra_apps,
[asn1, odbc, public_key, stdlib, syntax_tools,
idna, jose,
cache_tab, eimp, fast_tls, fast_xml, fast_yaml,
mqtree, p1_acme, p1_oauth2, p1_utils, pkix,
stringprep, xmpp, yconf,
{if_version_below, "27", jiffy},
{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}]},
{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}]}
} ]}.
{plt_extra_apps,
[asn1,
odbc,
public_key,
stdlib,
syntax_tools,
idna,
jose,
cache_tab,
eimp,
fast_tls,
fast_xml,
fast_yaml,
mqtree,
p1_acme,
p1_oauth2,
p1_utils,
pkix,
stringprep,
xmpp,
yconf,
{if_version_below, "27", jiffy},
{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}]},
{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}]}.
@ -264,7 +288,7 @@
%%%
{relx, [{release, {ejabberd, {cmd, "grep {vsn, vars.config | sed 's|{vsn, \"||;s|\"}.||' | tr -d '\012'"}},
[ejabberd]},
[ejabberd]},
{sys_config, "./rel/sys.config"},
{vm_args, "./rel/vm.args"},
{overlay_vars, "vars.config"},
@ -277,12 +301,10 @@
{copy, "{{base_dir}}/consolidated/*", "lib/ejabberd-{{release_version}}/ebin/"},
{copy, "rel/overlays/iex", "releases/{{release_version}}/"},
{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, "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},
{dev_mode, false},
@ -311,8 +333,7 @@
--config rel/relive.config \
--eval sync:go(). \
--script rel/relive.escript \
--name ejabberd@localhost"}]}
]}.
--name ejabberd@localhost"}]}]}.
%% Local Variables:
%% mode: erlang

View file

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

View file

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

View file

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

View file

@ -32,41 +32,43 @@
%% API
-export([start_link/0,
can_access/2,
invalidate/0,
validator/0,
show_current_definitions/0]).
can_access/2,
invalidate/0,
validator/0,
show_current_definitions/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-define(SERVER, ?MODULE).
-define(CACHE_TAB, access_permissions_cache).
-record(state,
{definitions = none :: none | [definition()]}).
-record(state, {definitions = none :: none | [definition()]}).
-type state() :: #state{}.
-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 who() :: rule() | {oauth, {[binary()], [rule()]}}.
-type from() :: atom().
-type permission() :: {binary(), {[from()], [who()], {what(), what()}}}.
-type definition() :: {binary(), {[from()], [who()], [atom()] | all}}.
-type caller_info() :: #{caller_module => module(),
caller_host => global | binary(),
tag => binary() | none,
extra_permissions => [definition()],
atom() => term()}.
-type caller_info() :: #{
caller_module => module(),
caller_host => global | binary(),
tag => binary() | none,
extra_permissions => [definition()],
atom() => term()
}.
-export_type([permission/0]).
%%%===================================================================
%%% API
%%%===================================================================
@ -78,41 +80,51 @@ can_access(Cmd, CallerInfo) ->
Tag = maps:get(tag, CallerInfo, none),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl(
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule "
"'~ts'~n (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end, none, Defs),
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule "
"'~ts'~n (CallerInfo=~p)",
[Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end,
none,
Defs),
case Res of
allow -> allow;
_ ->
?DEBUG("Command '~p' execution denied~n "
"(CallerInfo=~p)", [Cmd, CallerInfo]),
deny
allow -> allow;
_ ->
?DEBUG("Command '~p' execution denied~n "
"(CallerInfo=~p)",
[Cmd, CallerInfo]),
deny
end.
-spec invalidate() -> ok.
invalidate() ->
gen_server:cast(?MODULE, invalidate),
ets_cache:delete(?CACHE_TAB, definitions).
-spec show_current_definitions() -> [definition()].
show_current_definitions() ->
ets_cache:lookup(?CACHE_TAB, definitions,
fun() ->
{cache, gen_server:call(?MODULE, show_current_definitions)}
end).
ets_cache:lookup(?CACHE_TAB,
definitions,
fun() ->
{cache, gen_server:call(?MODULE, show_current_definitions)}
end).
start_link() ->
ets_cache:new(?CACHE_TAB, [{max_size, 2}]),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
@ -122,8 +134,10 @@ init([]) ->
ets_cache:new(access_permissions),
{ok, #state{}}.
-spec handle_call(show_current_definitions | term(),
term(), state()) -> {reply, term(), state()}.
term(),
state()) -> {reply, term(), state()}.
handle_call(show_current_definitions, _From, State) ->
{State2, Defs} = get_definitions(State),
{reply, Defs, State2};
@ -131,6 +145,7 @@ handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
-spec handle_cast(invalidate | term(), state()) -> {noreply, state()}.
handle_cast(invalidate, State) ->
{noreply, State#state{definitions = none}};
@ -138,16 +153,20 @@ handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, invalidate, 90).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -158,81 +177,93 @@ get_definitions(#state{definitions = none} = State) ->
ApiPerms = ejabberd_option:api_permissions(),
AllCommands = ejabberd_commands:get_commands_definition(),
NDefs0 = lists:map(
fun({Name, {From, Who, {Add, Del}}}) ->
Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
{Name, {From, Who, Cmds}}
end, ApiPerms),
fun({Name, {From, Who, {Add, Del}}}) ->
Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
{Name, {From, Who, Cmds}}
end,
ApiPerms),
NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of
false ->
[{<<"console commands">>,
{[ejabberd_ctl],
[{acl, all}],
filter_commands_with_permissions(AllCommands, all, none)}} | NDefs0];
_ ->
NDefs0
end,
false ->
[{<<"console commands">>,
{[ejabberd_ctl],
[{acl, all}],
filter_commands_with_permissions(AllCommands, all, none)}} | NDefs0];
_ ->
NDefs0
end,
{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) ->
case What == all orelse lists:member(Cmd, What) of
true ->
{Tags, Modules} = lists:partition(fun({tag, _}) -> true; (_) -> false end, From),
case (Modules == [] orelse lists:member(Module, Modules)) andalso
(Tags == [] orelse lists:member({tag, Tag}, Tags)) of
true ->
Scope = maps:get(oauth_scope, CallerInfo, none),
lists:any(
fun({access, Access}) when Scope == none ->
acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when Scope == none, is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) when Scope == none ->
acl:match_acl(Host, Acl, CallerInfo);
({oauth, {Scopes, List}}) when Scope /= none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
true ->
lists:any(
fun({access, Access}) ->
acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) ->
acl:match_acl(Host, Acl, CallerInfo)
end, List);
_ ->
false
end;
(_) ->
false
end, Who);
_ ->
false
end;
_ ->
false
true ->
{Tags, Modules} = lists:partition(fun({tag, _}) -> true; (_) -> false end, From),
case (Modules == [] orelse lists:member(Module, Modules)) andalso
(Tags == [] orelse lists:member({tag, Tag}, Tags)) of
true ->
Scope = maps:get(oauth_scope, CallerInfo, none),
lists:any(
fun({access, Access}) when Scope == none ->
acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when Scope == none, is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) when Scope == none ->
acl:match_acl(Host, Acl, CallerInfo);
({oauth, {Scopes, List}}) when Scope /= none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
true ->
lists:any(
fun({access, Access}) ->
acl:match_rule(Host, Access, CallerInfo) == allow;
({acl, Name} = Acl) when is_atom(Name) ->
acl:match_acl(Host, Acl, CallerInfo);
({acl, Acl}) ->
acl:match_acl(Host, Acl, CallerInfo)
end,
List);
_ ->
false
end;
(_) ->
false
end,
Who);
_ ->
false
end;
_ ->
false
end.
-spec filter_commands_with_permissions([#ejabberd_commands{}], what(), what()) -> [atom()].
filter_commands_with_permissions(AllCommands, Add, Del) ->
CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []),
CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []),
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) ->
Acc;
filter_commands_with_patterns([C | CRest], Patterns, Acc) ->
case command_matches_patterns(C, Patterns) of
true ->
filter_commands_with_patterns(CRest, Patterns, [C | Acc]);
_ ->
filter_commands_with_patterns(CRest, Patterns, Acc)
true ->
filter_commands_with_patterns(CRest, Patterns, [C | Acc]);
_ ->
filter_commands_with_patterns(CRest, Patterns, Acc)
end.
-spec command_matches_patterns(#ejabberd_commands{}, what()) -> boolean().
command_matches_patterns(_, all) ->
true;
@ -242,46 +273,50 @@ command_matches_patterns(_, []) ->
false;
command_matches_patterns(#ejabberd_commands{tags = Tags} = C, [{tag, Tag} | Tail]) ->
case lists:member(Tag, Tags) of
true ->
true;
_ ->
command_matches_patterns(C, Tail)
true ->
true;
_ ->
command_matches_patterns(C, Tail)
end;
command_matches_patterns(#ejabberd_commands{name = Name}, [Name | _Tail]) ->
true;
command_matches_patterns(C, [_ | Tail]) ->
command_matches_patterns(C, Tail).
%%%===================================================================
%%% Validators
%%%===================================================================
-spec parse_what([binary()]) -> {what(), what()}.
parse_what(Defs) ->
{A, D} =
lists:foldl(
fun(Def, {Add, Del}) ->
case parse_single_what(Def) of
{error, Err} ->
econf:fail({invalid_syntax, [Err, ": ", Def]});
all ->
{case Add of none -> none; _ -> all end, Del};
{neg, all} ->
{none, all};
{neg, Value} ->
{Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
Value ->
{case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
end
end, {[], []}, Defs),
lists:foldl(
fun(Def, {Add, Del}) ->
case parse_single_what(Def) of
{error, Err} ->
econf:fail({invalid_syntax, [Err, ": ", Def]});
all ->
{case Add of none -> none; _ -> all end, Del};
{neg, all} ->
{none, all};
{neg, Value} ->
{Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
Value ->
{case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
end
end,
{[], []},
Defs),
case {A, D} of
{[], _} ->
{none, all};
{A2, []} ->
{A2, none};
V ->
V
{[], _} ->
{none, all};
{A2, []} ->
{A2, none};
V ->
V
end.
-spec parse_single_what(binary()) -> atom() | {neg, atom()} | {tag, atom()} | {error, string()}.
parse_single_what(<<"*">>) ->
all;
@ -289,70 +324,76 @@ parse_single_what(<<"!*">>) ->
{neg, all};
parse_single_what(<<"!", Rest/binary>>) ->
case parse_single_what(Rest) of
{neg, _} ->
{error, "double negation"};
{error, _} = Err ->
Err;
V ->
{neg, V}
{neg, _} ->
{error, "double negation"};
{error, _} = Err ->
Err;
V ->
{neg, V}
end;
parse_single_what(<<"[tag:", Rest/binary>>) ->
case binary:split(Rest, <<"]">>) of
[TagName, <<"">>] ->
case parse_single_what(TagName) of
{error, _} = Err ->
Err;
V when is_atom(V) ->
{tag, V};
_ ->
{error, "invalid tag"}
end;
_ ->
{error, "invalid tag"}
[TagName, <<"">>] ->
case parse_single_what(TagName) of
{error, _} = Err ->
Err;
V when is_atom(V) ->
{tag, V};
_ ->
{error, "invalid tag"}
end;
_ ->
{error, "invalid tag"}
end;
parse_single_what(B) ->
case re:run(B, "^[a-z0-9_\\-]*$") of
nomatch -> {error, "invalid command"};
_ -> binary_to_atom(B, latin1)
nomatch -> {error, "invalid command"};
_ -> binary_to_atom(B, latin1)
end.
validator(Map, Opts) ->
econf:and_then(
fun(L) when is_list(L) ->
lists:map(
fun({K, V}) -> {(econf:atom())(K), V};
(A) -> {acl, (econf:atom())(A)}
end, lists:flatten(L));
end,
lists:flatten(L));
(A) ->
[{acl, (econf:atom())(A)}]
end,
econf:and_then(
econf:options(maps:merge(acl:validators(), Map), Opts),
fun(Rules) ->
lists:flatmap(
fun({Type, Rs}) when is_list(Rs) ->
case maps:is_key(Type, acl:validators()) of
true -> [{acl, {Type, R}} || R <- Rs];
false -> [{Type, Rs}]
end;
(Other) ->
[Other]
end, Rules)
end)).
econf:options(maps:merge(acl:validators(), Map), Opts),
fun(Rules) ->
lists:flatmap(
fun({Type, Rs}) when is_list(Rs) ->
case maps:is_key(Type, acl:validators()) of
true -> [ {acl, {Type, R}} || R <- Rs ];
false -> [{Type, Rs}]
end;
(Other) ->
[Other]
end,
Rules)
end)).
validator(from) ->
fun(L) when is_list(L) ->
lists:map(
fun({K, V}) -> {(econf:enum([tag]))(K), (econf:binary())(V)};
(A) -> (econf:enum([ejabberd_ctl,
ejabberd_web_admin,
ejabberd_xmlrpc,
mod_adhoc_api,
mod_cron,
mod_http_api]))(A)
end, lists:flatten(L));
lists:map(
fun({K, V}) -> {(econf:enum([tag]))(K), (econf:binary())(V)};
(A) ->
(econf:enum([ejabberd_ctl,
ejabberd_web_admin,
ejabberd_xmlrpc,
mod_adhoc_api,
mod_cron,
mod_http_api]))(A)
end,
lists:flatten(L));
(A) ->
[(econf:enum([ejabberd_ctl,
[(econf:enum([ejabberd_ctl,
ejabberd_web_admin,
ejabberd_xmlrpc,
mod_adhoc_api,
@ -367,26 +408,31 @@ validator(who) ->
validator(#{access => econf:acl(), oauth => validator(oauth)}, []);
validator(oauth) ->
econf:and_then(
validator(#{access => econf:acl(),
scope => econf:non_empty(
econf:list_or_single(econf:binary()))},
[{required, [scope]}]),
validator(#{
access => econf:acl(),
scope => econf:non_empty(
econf:list_or_single(econf:binary()))
},
[{required, [scope]}]),
fun(Os) ->
{[Scopes], Rest} = proplists:split(Os, [scope]),
{lists:flatten([S || {_, S} <- Scopes]), Rest}
{[Scopes], Rest} = proplists:split(Os, [scope]),
{lists:flatten([ S || {_, S} <- Scopes ]), Rest}
end).
validator() ->
econf:map(
econf:binary(),
econf:and_then(
econf:options(
#{from => validator(from),
what => validator(what),
who => validator(who)}),
fun(Os) ->
{proplists:get_value(from, Os, []),
proplists:get_value(who, Os, none),
proplists:get_value(what, Os, {none, none})}
end),
econf:options(
#{
from => validator(from),
what => validator(what),
who => validator(who)
}),
fun(Os) ->
{proplists:get_value(from, Os, []),
proplists:get_value(who, Os, none),
proplists:get_value(what, Os, {none, none})}
end),
[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
%%%
start(normal, _Args) ->
try
{T1, _} = statistics(wall_clock),
ejabberd_logger:start(),
write_pid_file(),
start_included_apps(),
misc:warn_unset_home(),
start_elixir_application(),
setup_if_elixir_conf_used(),
case ejabberd_config:load() of
ok ->
ejabberd_mnesia:start(),
file_queue_init(),
maybe_add_nameservers(),
case ejabberd_sup:start_link() of
{ok, SupPid} ->
ejabberd_system_monitor:start(),
register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity),
ejabberd_hooks:run(ejabberd_started, []),
ejabberd:check_apps(),
ejabberd_systemd:ready(),
maybe_start_exsync(),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~ts is started in the node ~p in ~.2fs",
[ejabberd_option:version(),
node(), (T2-T1)/1000]),
maybe_print_elixir_version(),
?INFO_MSG("~ts",
[erlang:system_info(system_version)]),
{ok, SupPid};
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
ejabberd:halt()
end;
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~ts",
[ejabberd_config:format_error(Err)]),
ejabberd:halt()
end
catch throw:{?MODULE, Error} ->
?DEBUG("Failed to start ejabberd application: ~p", [Error]),
ejabberd:halt()
{T1, _} = statistics(wall_clock),
ejabberd_logger:start(),
write_pid_file(),
start_included_apps(),
misc:warn_unset_home(),
start_elixir_application(),
setup_if_elixir_conf_used(),
case ejabberd_config:load() of
ok ->
ejabberd_mnesia:start(),
file_queue_init(),
maybe_add_nameservers(),
case ejabberd_sup:start_link() of
{ok, SupPid} ->
ejabberd_system_monitor:start(),
register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity),
ejabberd_hooks:run(ejabberd_started, []),
ejabberd:check_apps(),
ejabberd_systemd:ready(),
maybe_start_exsync(),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~ts is started in the node ~p in ~.2fs",
[ejabberd_option:version(),
node(),
(T2 - T1) / 1000]),
maybe_print_elixir_version(),
?INFO_MSG("~ts",
[erlang:system_info(system_version)]),
{ok, SupPid};
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
ejabberd:halt()
end;
Err ->
?CRITICAL_MSG("Failed to start ejabberd application: ~ts",
[ejabberd_config:format_error(Err)]),
ejabberd:halt()
end
catch
throw:{?MODULE, Error} ->
?DEBUG("Failed to start ejabberd application: ~p", [Error]),
ejabberd:halt()
end;
start(_, _) ->
{error, badarg}.
start_included_apps() ->
{ok, Apps} = application:get_key(ejabberd, included_applications),
lists:foreach(
fun(mnesia) ->
ok;
(lager) ->
ok;
(os_mon)->
ok;
(App) ->
application:ensure_all_started(App)
end, Apps).
fun(mnesia) ->
ok;
(lager) ->
ok;
(os_mon) ->
ok;
(App) ->
application:ensure_all_started(App)
end,
Apps).
%% Prepare the application for termination.
%% This function is called when an application is about to be stopped,
@ -113,76 +119,88 @@ prep_stop(State) ->
gen_mod:stop(),
State.
%% All the processes were killed when this function is called
stop(_State) ->
?INFO_MSG("ejabberd ~ts is stopped in the node ~p",
[ejabberd_option:version(), node()]),
[ejabberd_option:version(), node()]),
delete_pid_file().
%%%
%%% Internal functions
%%%
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
maybe_add_nameservers() ->
case os:type() of
{win32, _} -> add_windows_nameservers();
_ -> ok
{win32, _} -> add_windows_nameservers();
_ -> ok
end.
add_windows_nameservers() ->
IPTs = win32_dns:get_nameservers(),
?INFO_MSG("Adding machine's DNS IPs to Erlang system:~n~p", [IPTs]),
lists:foreach(fun(IPT) -> inet_db:add_ns(IPT) end, IPTs).
%%%
%%% PID file
%%%
write_pid_file() ->
case ejabberd:get_pid_file() of
false ->
ok;
PidFilename ->
write_pid_file(os:getpid(), PidFilename)
false ->
ok;
PidFilename ->
write_pid_file(os:getpid(), PidFilename)
end.
write_pid_file(Pid, PidFilename) ->
case file:write_file(PidFilename, io_lib:format("~ts~n", [Pid])) of
ok ->
ok;
{error, Reason} = Err ->
?CRITICAL_MSG("Cannot write PID file ~ts: ~ts",
[PidFilename, file:format_error(Reason)]),
throw({?MODULE, Err})
ok ->
ok;
{error, Reason} = Err ->
?CRITICAL_MSG("Cannot write PID file ~ts: ~ts",
[PidFilename, file:format_error(Reason)]),
throw({?MODULE, Err})
end.
delete_pid_file() ->
case ejabberd:get_pid_file() of
false ->
ok;
PidFilename ->
file:delete(PidFilename)
false ->
ok;
PidFilename ->
file:delete(PidFilename)
end.
file_queue_init() ->
QueueDir = case ejabberd_option:queue_dir() of
undefined ->
MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "queue");
Path ->
Path
end,
undefined ->
MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "queue");
Path ->
Path
end,
case p1_queue:start(QueueDir) of
ok -> ok;
Err -> throw({?MODULE, Err})
ok -> ok;
Err -> throw({?MODULE, Err})
end.
%%%
%%% Elixir
%%%
-ifdef(ELIXIR_ENABLED).
is_using_elixir_config() ->
Config = ejabberd_config:path(),
try 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config) of
@ -191,36 +209,55 @@ is_using_elixir_config() ->
_:_ -> false
end.
setup_if_elixir_conf_used() ->
case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link();
false -> ok
end.
case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link();
false -> ok
end.
register_elixir_config_hooks() ->
case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config':start_hooks();
false -> ok
end.
case is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config':start_hooks();
false -> ok
end.
start_elixir_application() ->
case application:ensure_started(elixir) of
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
end.
maybe_start_exsync() ->
case os:getenv("RELIVE") of
"true" -> rpc:call(node(), 'Elixir.ExSync.Application', start, []);
_ -> ok
end.
maybe_print_elixir_version() ->
?INFO_MSG("Elixir ~ts", [maps:get(build, 'Elixir.System':build_info())]).
-else.
setup_if_elixir_conf_used() -> ok.
register_elixir_config_hooks() -> ok.
start_elixir_application() -> ok.
maybe_start_exsync() -> ok.
maybe_print_elixir_version() -> ok.
-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", ""}).
-export([start/1,
stop/1,
stop/1,
use_cache/1,
allow_anonymous/1,
is_sasl_anonymous_enabled/1,
is_login_anonymous_enabled/1,
anonymous_user_exist/2,
allow_multiple_connections/1,
register_connection/3,
unregister_connection/3
]).
allow_anonymous/1,
is_sasl_anonymous_enabled/1,
is_login_anonymous_enabled/1,
anonymous_user_exist/2,
allow_multiple_connections/1,
register_connection/3,
unregister_connection/3]).
-export([login/2, check_password/4, user_exists/2,
get_users/2, count_users/2, store_type/1,
plain_password_required/1]).
-export([login/2,
check_password/4,
user_exists/2,
get_users/2,
count_users/2,
store_type/1,
plain_password_required/1]).
-include("logger.hrl").
-include_lib("xmpp/include/jid.hrl").
start(Host) ->
ejabberd_hooks:add(sm_register_connection_hook, Host,
?MODULE, register_connection, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
?MODULE, unregister_connection, 100),
ejabberd_hooks:add(sm_register_connection_hook,
Host,
?MODULE,
register_connection,
100),
ejabberd_hooks:add(sm_remove_connection_hook,
Host,
?MODULE,
unregister_connection,
100),
ok.
stop(Host) ->
ejabberd_hooks:delete(sm_register_connection_hook, Host,
?MODULE, register_connection, 100),
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
?MODULE, unregister_connection, 100).
ejabberd_hooks:delete(sm_register_connection_hook,
Host,
?MODULE,
register_connection,
100),
ejabberd_hooks:delete(sm_remove_connection_hook,
Host,
?MODULE,
unregister_connection,
100).
use_cache(_) ->
false.
%% Return true if anonymous is allowed for host or false otherwise
allow_anonymous(Host) ->
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
%% Return true if anonymous mode is enabled and if anonymous protocol is SASL
%% anonymous protocol can be: sasl_anon|login_anon|both
is_sasl_anonymous_enabled(Host) ->
case allow_anonymous(Host) of
false -> false;
true ->
case anonymous_protocol(Host) of
sasl_anon -> true;
both -> true;
_Other -> false
end
false -> false;
true ->
case anonymous_protocol(Host) of
sasl_anon -> true;
both -> true;
_Other -> false
end
end.
%% Return true if anonymous login is enabled on the server
%% anonymous login can be use using standard authentication method (i.e. with
%% clients that do not support anonymous login)
is_login_anonymous_enabled(Host) ->
case allow_anonymous(Host) of
false -> false;
true ->
case anonymous_protocol(Host) of
login_anon -> true;
both -> true;
_Other -> false
end
false -> false;
true ->
case anonymous_protocol(Host) of
login_anon -> true;
both -> true;
_Other -> false
end
end.
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon
anonymous_protocol(Host) ->
ejabberd_option:anonymous_protocol(Host).
%% Return true if multiple connections have been allowed in the config file
%% defaults to false
allow_multiple_connections(Host) ->
ejabberd_option:allow_multiple_connections(Host).
anonymous_user_exist(User, Server) ->
lists:any(
fun({_LResource, Info}) ->
proplists:get_value(auth_module, Info) == ?MODULE
end, ejabberd_sm:get_user_info(User, Server)).
proplists:get_value(auth_module, Info) == ?MODULE
end,
ejabberd_sm:get_user_info(User, Server)).
%% Register connection
-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
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
?MODULE ->
% Register user only if we are first resource
case ejabberd_sm:get_user_resources(LUser, LServer) of
[LResource] ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]);
_ ->
ok
end;
_ ->
ok
?MODULE ->
% Register user only if we are first resource
case ejabberd_sm:get_user_resources(LUser, LServer) of
[LResource] ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]);
_ ->
ok
end;
_ ->
ok
end.
%% Remove an anonymous user from the anonymous users table
-spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
unregister_connection(_SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
#jid{luser = LUser, lserver = LServer},
Info) ->
case proplists:get_value(auth_module, Info) of
?MODULE ->
% Remove user data only if there is no more resources around
case ejabberd_sm:get_user_resources(LUser, LServer) of
[] ->
ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
_ ->
ok
end;
_ ->
ok
?MODULE ->
% Remove user data only if there is no more resources around
case ejabberd_sm:get_user_resources(LUser, LServer) of
[] ->
ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
_ ->
ok
end;
_ ->
ok
end.
%% ---------------------------------
%% Specific anonymous auth functions
%% ---------------------------------
check_password(User, _AuthzId, Server, _Password) ->
{nocache,
case ejabberd_auth:user_exists_in_other_modules(?MODULE, User, Server) of
%% If user exists in other module, reject anonnymous authentication
true -> false;
%% If we are not sure whether the user exists in other module, reject anon auth
maybe_exists -> false;
false -> login(User, Server)
%% If user exists in other module, reject anonnymous authentication
true -> false;
%% If we are not sure whether the user exists in other module, reject anon auth
maybe_exists -> false;
false -> login(User, Server)
end}.
login(User, Server) ->
case is_login_anonymous_enabled(Server) of
false -> false;
true ->
case anonymous_user_exist(User, Server) of
%% Reject the login if an anonymous user with the same login
%% is already logged and if multiple login has not been enable
%% in the config file.
true -> allow_multiple_connections(Server);
%% Accept login and add user to the anonymous table
false -> true
end
false -> false;
true ->
case anonymous_user_exist(User, Server) of
%% Reject the login if an anonymous user with the same login
%% is already logged and if multiple login has not been enable
%% in the config file.
true -> allow_multiple_connections(Server);
%% Accept login and add user to the anonymous table
false -> true
end
end.
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) ->
length(get_users(Server, Opts)).
user_exists(User, Server) ->
{nocache, anonymous_user_exist(User, Server)}.
plain_password_required(_) ->
false.
store_type(_) ->
external.

View file

@ -29,77 +29,99 @@
-behaviour(ejabberd_auth).
-export([start/1, stop/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]).
-export([start/1,
stop/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").
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
extauth:start(Host).
stop(Host) ->
extauth:stop(Host).
reload(Host) ->
extauth:reload(Host).
plain_password_required(_) -> true.
store_type(_) -> external.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
{nocache, false};
true ->
check_password_extauth(User, AuthzId, Server, Password)
if
AuthzId /= <<>> andalso AuthzId /= User ->
{nocache, false};
true ->
check_password_extauth(User, AuthzId, Server, Password)
end.
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
Res when is_boolean(Res) -> {cache, {ok, Password}};
{error, Reason} -> failure(User, Server, set_password, Reason)
Res when is_boolean(Res) -> {cache, {ok, Password}};
{error, Reason} -> failure(User, Server, set_password, Reason)
end.
try_register(User, Server, Password) ->
case extauth:try_register(User, Server, Password) of
true -> {cache, {ok, Password}};
false -> {cache, {error, not_allowed}};
{error, Reason} -> failure(User, Server, try_register, Reason)
true -> {cache, {ok, Password}};
false -> {cache, {error, not_allowed}};
{error, Reason} -> failure(User, Server, try_register, Reason)
end.
user_exists(User, Server) ->
case extauth:user_exists(User, Server) of
Res when is_boolean(Res) -> {cache, Res};
{error, Reason} -> failure(User, Server, user_exists, Reason)
Res when is_boolean(Res) -> {cache, Res};
{error, Reason} -> failure(User, Server, user_exists, Reason)
end.
remove_user(User, Server) ->
case extauth:remove_user(User, Server) of
false -> {error, not_allowed};
true -> ok;
{error, Reason} ->
{_, Err} = failure(User, Server, remove_user, Reason),
Err
false -> {error, not_allowed};
true -> ok;
{error, Reason} ->
{_, Err} = failure(User, Server, remove_user, Reason),
Err
end.
check_password_extauth(User, _AuthzId, Server, Password) ->
if Password /= <<"">> ->
case extauth:check_password(User, Server, Password) of
Res when is_boolean(Res) -> {cache, Res};
{error, Reason} ->
{Tag, _} = failure(User, Server, check_password, Reason),
{Tag, false}
end;
true ->
{nocache, false}
if
Password /= <<"">> ->
case extauth:check_password(User, Server, Password) of
Res when is_boolean(Res) -> {cache, Res};
{error, Reason} ->
{Tag, _} = failure(User, Server, check_password, Reason),
{Tag, false}
end;
true ->
{nocache, false}
end.
-spec failure(binary(), binary(), atom(), any()) -> {nocache, {error, db_failure}}.
failure(User, Server, Fun, Reason) ->
?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}}.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,8 +34,12 @@
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([register_task/5, task_status/1, abort_task/1]).
-define(SERVER, ?MODULE).
@ -47,159 +51,178 @@
%%% API
%%%===================================================================
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
register_task(Type, Steps, Rate, JobState, JobFun) ->
gen_server:call(?MODULE, {register_task, Type, Steps, Rate, JobState, JobFun}).
task_status(Type) ->
gen_server:call(?MODULE, {task_status, Type}).
abort_task(Type) ->
gen_server:call(?MODULE, {abort_task, Type}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
{ok, State :: #state{}} |
{ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} |
ignore).
init([]) ->
{ok, #state{}}.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
-spec(handle_call(Request :: term(),
From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{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) ->
case maps:get(Type, Tasks, #task{}) of
#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),
Tasks2 = maps:put(Type, #task{state = working, pid = Pid, steps = Steps, done_steps = 0}, Tasks),
{reply, ok, #state{tasks = Tasks2}};
#task{state = working} ->
{reply, {error, in_progress}, State}
#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),
Tasks2 = maps:put(Type, #task{state = working, pid = Pid, steps = Steps, done_steps = 0}, Tasks),
{reply, ok, #state{tasks = Tasks2}};
#task{state = working} ->
{reply, {error, in_progress}, State}
end;
handle_call({task_status, Type}, _From, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of
none ->
{reply, not_started, State};
#task{state = not_started} ->
{reply, not_started, State};
#task{state = failed, done_steps = Steps, pid = Error} ->
{reply, {failed, Steps, Error}, State};
#task{state = aborted, done_steps = Steps} ->
{reply, {aborted, Steps}, State};
#task{state = working, done_steps = Steps} ->
{reply, {working, Steps}, State};
#task{state = completed, done_steps = Steps} ->
{reply, {completed, Steps}, State}
none ->
{reply, not_started, State};
#task{state = not_started} ->
{reply, not_started, State};
#task{state = failed, done_steps = Steps, pid = Error} ->
{reply, {failed, Steps, Error}, State};
#task{state = aborted, done_steps = Steps} ->
{reply, {aborted, Steps}, State};
#task{state = working, done_steps = Steps} ->
{reply, {working, Steps}, State};
#task{state = completed, done_steps = Steps} ->
{reply, {completed, Steps}, State}
end;
handle_call({abort_task, Type}, _From, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid} = T ->
Pid ! abort,
Tasks2 = maps:put(Type, T#task{state = aborted, pid = none}, Tasks),
{reply, aborted, State#state{tasks = Tasks2}};
_ ->
{reply, not_started, State}
#task{state = working, pid = Pid} = T ->
Pid ! abort,
Tasks2 = maps:put(Type, T#task{state = aborted, pid = none}, Tasks),
{reply, aborted, State#state{tasks = Tasks2}};
_ ->
{reply, not_started, State}
end;
handle_call(_Request, _From, State = #state{}) ->
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast({task_finished, Type, Pid}, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid2} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{state = completed, pid = none}, Tasks),
{noreply, State#state{tasks = Tasks2}};
_ ->
{noreply, State}
#task{state = working, pid = Pid2} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{state = completed, pid = none}, Tasks),
{noreply, State#state{tasks = Tasks2}};
_ ->
{noreply, State}
end;
handle_cast({task_progress, Type, Pid, Count}, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid2, done_steps = Steps} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{done_steps = Steps + Count}, Tasks),
{noreply, State#state{tasks = Tasks2}};
_ ->
{noreply, State}
#task{state = working, pid = Pid2, done_steps = Steps} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{done_steps = Steps + Count}, Tasks),
{noreply, State#state{tasks = Tasks2}};
_ ->
{noreply, State}
end;
handle_cast({task_error, Type, Pid, Error}, #state{tasks = Tasks} = State) ->
case maps:get(Type, Tasks, none) of
#task{state = working, pid = Pid2} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{state = failed, pid = Error}, Tasks),
{noreply, State#state{tasks = Tasks2}};
_ ->
{noreply, State}
#task{state = working, pid = Pid2} = T when Pid == Pid2 ->
Tasks2 = maps:put(Type, T#task{state = failed, pid = Error}, Tasks),
{noreply, State#state{tasks = Tasks2}};
_ ->
{noreply, State}
end;
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(_Info, State = #state{}) ->
{noreply, State}.
%% @private
%% @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
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
State :: #state{}) -> term()).
terminate(_Reason, _State = #state{}) ->
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
-spec(code_change(OldVsn :: term() | {down, term()},
State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
work_loop(Task, JobState, JobFun, Rate, StartDate, CurrentProgress) ->
try JobFun(JobState) of
{ok, _NewState, 0} ->
gen_server:cast(?MODULE, {task_finished, Task, self()});
{ok, NewState, Count} ->
gen_server:cast(?MODULE, {task_progress, Task, self(), Count}),
NewProgress = CurrentProgress + Count,
TimeSpent = erlang:monotonic_time(second) - StartDate,
SleepTime = max(0, NewProgress/Rate*60 - TimeSpent),
receive
abort -> ok
after round(SleepTime*1000) ->
work_loop(Task, NewState, JobFun, Rate, StartDate, NewProgress)
end;
{error, Error} ->
gen_server:cast(?MODULE, {task_error, Task, self(), Error})
catch _:_ ->
gen_server:cast(?MODULE, {task_error, Task, self(), internal_error})
{ok, _NewState, 0} ->
gen_server:cast(?MODULE, {task_finished, Task, self()});
{ok, NewState, Count} ->
gen_server:cast(?MODULE, {task_progress, Task, self(), Count}),
NewProgress = CurrentProgress + Count,
TimeSpent = erlang:monotonic_time(second) - StartDate,
SleepTime = max(0, NewProgress / Rate * 60 - TimeSpent),
receive
abort -> ok
after
round(SleepTime * 1000) ->
work_loop(Task, NewState, JobFun, Rate, StartDate, NewProgress)
end;
{error, Error} ->
gen_server:cast(?MODULE, {task_error, Task, self(), Error})
catch
_:_ ->
gen_server:cast(?MODULE, {task_error, Task, self(), internal_error})
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]).
%% Get first c2s configuration limitations to apply it to other c2s
%% connectors.
get_c2s_limits() ->
C2SFirstListen = ejabberd_option:listen(),
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
false -> [];
{value, {_Port, ejabberd_c2s, Opts}} ->
select_opts_values(Opts)
false -> [];
{value, {_Port, ejabberd_c2s, Opts}} ->
select_opts_values(Opts)
end.
%% Only get access, shaper and max_stanza_size values
select_opts_values(Opts) ->
maps:fold(
fun(Opt, Val, Acc) when Opt == access;
Opt == shaper;
Opt == max_stanza_size ->
[{Opt, Val}|Acc];
(_, _, Acc) ->
Acc
end, [], Opts).
Opt == shaper;
Opt == max_stanza_size ->
[{Opt, Val} | Acc];
(_, _, Acc) ->
Acc
end,
[],
Opts).

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -33,7 +33,7 @@
-include("ejabberd_commands.hrl").
-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(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).
@ -42,13 +42,13 @@
-define(TAG_R(N, C, V), ?TAG(N, C, ?RAW(V))).
-define(SPAN(N, V), ?TAG_R(span, ??N, V)).
-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])).
-define(NUM(A), ?SPAN(num,integer_to_binary(A))).
-define(FIELD(A), ?SPAN(field,A)).
-define(ID(A), ?SPAN(id,A)).
-define(OP(A), ?SPAN(op,A)).
-define(STR(A), ?SPAN(str, [<<"\"">>, A, <<"\"">>])).
-define(NUM(A), ?SPAN(num, integer_to_binary(A))).
-define(FIELD(A), ?SPAN(field, A)).
-define(ID(A), ?SPAN(id, A)).
-define(OP(A), ?SPAN(op, 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(ARG_S(A), ?STR(atom_to_list(A))).
@ -63,12 +63,16 @@
-define(STR_A(A), ?STR(atom_to_list(A))).
-define(ID_A(A), ?ID(atom_to_list(A))).
list_join_with([], _M) ->
[];
list_join_with([El|Tail], M) ->
list_join_with([El | Tail], M) ->
lists:reverse(lists:foldl(fun(E, Acc) ->
[E, M | Acc]
end, [El], Tail)).
end,
[El],
Tail)).
md_tag(dt, V) ->
[<<"- ">>, V];
@ -91,6 +95,7 @@ md_tag('div', V) ->
md_tag(_, V) ->
V.
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
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) ->
[?ARG(Name), ?OP_L(" => "), ?STR_A(Atom)];
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("}")];
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),
[?ARG(Name), ?OP_L(" => ["), list_join_with(Res, [?OP_L(", ")]), ?OP_L("]")].
perl_call(Name, ArgsDesc, Values, HTMLOutput) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ perl\n">>} end,
[Preamble,
Indent, ?ID_L("XMLRPC::Lite"), ?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("()")].
Indent,
?ID_L("XMLRPC::Lite"),
?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) ->
{Split, NL} = case Indent of
none -> {<<" ">>, <<" ">>};
_ -> {[?BR, <<" ", Indent/binary>>], [?BR, Indent]}
end,
[?KW_L("new "), ?ID_L("HashMap"), ?OP_L("<"), ?ID_L("String"), ?OP_L(", "), ?ID_L("Object"),
?OP_L(">() {{"), Split, list_join_with(Vals, Split), NL, ?OP_L("}}")].
[?KW_L("new "),
?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) ->
[?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(")")];
java_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
{NI, NI2, I} = case List of
[_] -> {" ", " ", Indent};
_ -> {[?BR, <<" ", Indent/binary>>],
[?BR, <<" ", Indent/binary>>],
<<" ", Indent/binary>>}
end,
[_] -> {" ", " ", Indent};
_ ->
{[?BR, <<" ", Indent/binary>>],
[?BR, <<" ", Indent/binary>>],
<<" ", Indent/binary>>}
end,
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,
list_join_with(Res, [?OP_L(","), NI]), NI2, ?OP_L("});")].
[?ID_L("put"),
?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) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ java\n">>} end,
[Preamble,
Indent, ?ID_L("XmlRpcClientConfigImpl config"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClientConfigImpl"), ?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),
Indent,
?ID_L("XmlRpcClientConfigImpl config"),
?OP_L(" = "),
?KW_L("new "),
?ID_L("XmlRpcClientConfigImpl"),
?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(");")].
-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(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, D, V), ?XML_L(N, [Indent, lists:duplicate(D, <<" ">>)], V)).
xml_gen({Name, integer}, Int, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1,
[?XML_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])];
[?XML(member,
Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value,
Indent,
1,
[?XML_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])];
xml_gen({Name, string}, Str, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
[?XML(member,
Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value,
Indent,
1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
xml_gen({Name, binary}, Str, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
[?XML(member,
Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value,
Indent,
1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
xml_gen({Name, atom}, Atom, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1,
[?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])];
[?XML(member,
Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value,
Indent,
1,
[?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])];
xml_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
NewIndent = <<" ", Indent/binary>>,
Res = lists:map(fun({A, B}) -> xml_gen(A, B, NewIndent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))),
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])];
[?XML(member,
Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])];
xml_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
Ind1 = <<" ", Indent/binary>>,
Ind2 = <<" ", Ind1/binary>>,
Res = lists:map(fun(E) -> [?XML(value, Ind1, [?XML(struct, Ind1, 1, xml_gen(ElDesc, E, Ind2, HTMLOutput))])] end, List),
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1, [?XML(array, Indent, 2, [?XML(data, Indent, 3, Res)])])])].
[?XML(member,
Indent,
[?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) ->
{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)),
[Preamble,
?XML(methodCall, Indent,
?XML(methodCall,
Indent,
[?XML_L(methodName, Indent, 1, ?ID_A(Name)),
?XML(params, Indent, 1,
[?XML(param, Indent, 2,
[?XML(value, Indent, 3,
?XML(params,
Indent,
1,
[?XML(param,
Indent,
2,
[?XML(value,
Indent,
3,
[?XML(struct, Indent, 4, Res)])])])])].
% [?ARG_S(Name), ?OP_L(": "), ?STR(Str)];
json_gen({_Name, integer}, Int, _Indent, HTMLOutput) ->
[?NUM(Int)];
@ -220,15 +325,22 @@ json_gen({_Name, atom}, Atom, _Indent, HTMLOutput) ->
json_gen({_Name, rescode}, Val, _Indent, HTMLOutput) ->
[?ID_A(Val == ok orelse Val == true)];
json_gen({_Name, restuple}, {Val, Str}, _Indent, HTMLOutput) ->
[?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("}")];
[?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) ->
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("}")];
json_gen({_Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
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))),
[?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")];
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),
[?OP_L("["), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("]")].
json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
{Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<"">>, <<"~~~ json\n">>} end,
{Code, ResultStr} = case {ResultDesc, Result} of
@ -255,23 +368,40 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
500 -> <<" 500 Internal Server Error">>
end,
[Preamble,
Indent, ?ID_L("POST /api/"), ?ID_A(Name), ?BR,
Indent, ?OP_L("{"), ?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
].
Indent,
?ID_L("POST /api/"),
?ID_A(Name),
?BR,
Indent,
?OP_L("{"),
?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}) ->
{LastNum+1, {LastStr, LastNum+1}};
{LastNum + 1, {LastStr, LastNum + 1}};
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}) ->
{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}) ->
{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}) ->
{ok, {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}) ->
{Res3, Data3} = generate_example_input(Field, Data2),
{[Res3 | Res2], Data3}
end, {[], Data}, Fields),
end,
{[], Data},
Fields),
{list_to_tuple(lists:reverse(R)), D};
generate_example_input({_Name, {list, Desc}}, Data) ->
{R1, D1} = generate_example_input(Desc, Data),
{R2, D2} = generate_example_input(Desc, D1),
{[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}) ->
{Res3, Data3} = generate_example_input(Arg, Data),
{[Res3 | Res], Data3}
end, {[], {$a-1, 0}}, ArgsDesc),
gen_calls(C#ejabberd_commands{args_example=lists:reverse(R)}, HTMLOutput, Langs);
gen_calls(#ejabberd_commands{result_example=none, result=ResultDesc} = C, HTMLOutput, Langs) ->
{R, _} = generate_example_input(ResultDesc, {$a-1, 0}),
gen_calls(C#ejabberd_commands{result_example=R}, HTMLOutput, Langs);
gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
result_example=Result, result=ResultDesc,
name=Name}, HTMLOutput, Langs) ->
end,
{[], {$a - 1, 0}},
ArgsDesc),
gen_calls(C#ejabberd_commands{args_example = lists:reverse(R)}, HTMLOutput, Langs);
gen_calls(#ejabberd_commands{result_example = none, result = ResultDesc} = C, HTMLOutput, Langs) ->
{R, _} = generate_example_input(ResultDesc, {$a - 1, 0}),
gen_calls(C#ejabberd_commands{result_example = R}, 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),
Java = java_call(Name, ArgsDesc, Values, HTMLOutput),
XML = xml_call(Name, ArgsDesc, Values, HTMLOutput),
JSON = json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput),
if HTMLOutput ->
[?TAG(ul, "code-samples-names",
if
HTMLOutput ->
[?TAG(ul,
"code-samples-names",
[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(<<"xmlrpc">>, Langs) of true -> ?TAG(li, <<"XML">>); _ -> [] 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(<<"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(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
true ->
case Langs of
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(<<"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(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"\n\n">>];
_ ->
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
<<"{: .code-samples-labels}\n">>,
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(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"{: .code-samples-tabs}\n\n">>]
end
true ->
case Langs of
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(<<"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(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"\n\n">>];
_ ->
[<<"\n">>,
case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
<<"{: .code-samples-labels}\n">>,
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(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"{: .code-samples-tabs}\n\n">>]
end
end.
format_type({list, {_, {tuple, Els}}}) ->
io_lib:format("[~ts]", [format_type({tuple, Els})]);
format_type({list, El}) ->
io_lib:format("[~ts]", [format_type(El)]);
format_type({tuple, Els}) ->
Args = [format_type(El) || El <- Els],
Args = [ format_type(El) || El <- Els ],
io_lib:format("{~ts}", [string:join(Args, ", ")]);
format_type({Name, Type}) ->
io_lib:format("~ts::~ts", [Name, format_type(Type)]);
@ -352,68 +498,94 @@ format_type(atom) ->
format_type(Type) ->
io_lib:format("~p", [Type]).
gen_param(Name, Type, undefined, HTMLOutput) ->
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
gen_param(Name, Type, Desc, HTMLOutput) ->
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
?TAG(dd, ?RAW(Desc))].
make_tags(HTMLOutput) ->
TagsList = ejabberd_commands:get_tags_commands(1000000),
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,
args=Args, args_desc=ArgsDesc, note=Note, definer=Definer,
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
-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,
args = Args,
args_desc = ArgsDesc,
note = Note,
definer = Definer,
result = Result,
result_desc = ResultDesc
} = Cmd,
HTMLOutput,
Langs) ->
try
ArgsText = case ArgsDesc of
none ->
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|| {AName, Type} <- Args])];
[?TAG(ul,
"args-list",
[ gen_param(AName, Type, undefined, HTMLOutput)
|| {AName, Type} <- Args ])];
_ ->
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
[?TAG(dl,
"args-list",
[ gen_param(AName, Type, ADesc, HTMLOutput)
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc) ])]
end,
ResultText = case Result of
{res,rescode} ->
[?TAG(dl, [gen_param(res, integer,
"Status code (`0` on success, `1` otherwise)",
HTMLOutput)])];
{res,restuple} ->
[?TAG(dl, [gen_param(res, string,
"Raw result string",
HTMLOutput)])];
{RName, Type} ->
case ResultDesc of
none ->
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
_ ->
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end
{res, rescode} ->
[?TAG(dl,
[gen_param(res,
integer,
"Status code (`0` on success, `1` otherwise)",
HTMLOutput)])];
{res, restuple} ->
[?TAG(dl,
[gen_param(res,
string,
"Raw result string",
HTMLOutput)])];
{RName, Type} ->
case ResultDesc of
none ->
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
_ ->
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
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
unknown -> false;
_ -> lists:member(gen_mod, lists:flatten(proplists:get_all_values(behaviour, Definer:module_info(attributes))))
end,
unknown -> false;
_ -> lists:member(gen_mod, lists:flatten(proplists:get_all_values(behaviour, Definer:module_info(attributes))))
end,
ModuleText = case IsDefinerMod of
true ->
[?TAG(h2, <<"Module:">>), ?TAG(p, ?RAW("_`"++atom_to_list(Definer)++"`_"))];
false ->
[]
end,
true ->
[?TAG(h2, <<"Module:">>), ?TAG(p, ?RAW("_`" ++ atom_to_list(Definer) ++ "`_"))];
false ->
[]
end,
NoteEl = case Note of
"" -> [];
_ -> ?TAG('div', "note-down", ?RAW(Note))
end,
"" -> [];
_ -> ?TAG('div', "note-down", ?RAW(Note))
end,
{NotePre, NotePost} =
if HTMLOutput -> {[], NoteEl};
true -> {NoteEl, []}
end,
if
HTMLOutput -> {[], NoteEl};
true -> {NoteEl, []}
end,
[?TAG(h1, make_command_name(Name, Note)),
NotePre,
@ -423,18 +595,21 @@ gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
_ -> ?TAG(p, ?RAW(LongDesc))
end,
NotePost,
?TAG(h2, <<"Arguments:">>), ArgsText,
?TAG(h2, <<"Result:">>), ResultText,
?TAG(h2, <<"Tags:">>), ?TAG(p, TagsText)]
++ ModuleText ++ [
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
?TAG(h2, <<"Arguments:">>),
ArgsText,
?TAG(h2, <<"Result:">>),
ResultText,
?TAG(h2, <<"Tags:">>),
?TAG(p, TagsText)] ++
ModuleText ++ [?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
catch
_:Ex ->
throw(iolist_to_binary(io_lib:format(
<<"Error when generating documentation for command '~p': ~p">>,
[Name, Ex])))
_:Ex ->
throw(iolist_to_binary(io_lib:format(
<<"Error when generating documentation for command '~p': ~p">>,
[Name, Ex])))
end.
get_version_mark("") ->
"";
get_version_mark(Note) ->
@ -445,33 +620,39 @@ get_version_mark(Note) ->
_ -> " 🟤"
end.
make_command_name(Name, Note) ->
atom_to_list(Name) ++ get_version_mark(Note).
find_commands_definitions() ->
lists:flatmap(
fun(Mod) ->
code:ensure_loaded(Mod),
Cs = case erlang:function_exported(Mod, get_commands_spec, 0) of
true ->
apply(Mod, get_commands_spec, []);
_ ->
[]
end,
[C#ejabberd_commands{definer = Mod} || C <- Cs]
end, ejabberd_config:beams(all)).
fun(Mod) ->
code:ensure_loaded(Mod),
Cs = case erlang:function_exported(Mod, get_commands_spec, 0) of
true ->
apply(Mod, get_commands_spec, []);
_ ->
[]
end,
[ C#ejabberd_commands{definer = Mod} || C <- Cs ]
end,
ejabberd_config:beams(all)).
generate_html_output(File, RegExp, Languages) ->
Cmds = find_commands_definitions(),
{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(Module), RE, [{capture, none}]) == match
end, Cmds),
Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) ->
re:run(atom_to_list(Module), RE, [{capture, none}]) == match
end,
Cmds),
Cmds3 = lists:sort(fun(#ejabberd_commands{name = N1}, #ejabberd_commands{name = N2}) ->
N1 =< N2
end, Cmds2),
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3],
end,
Cmds2),
Cmds4 = [ maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3 ],
Langs = binary:split(Languages, <<",">>, [global]),
Out = lists:map(fun(C) -> gen_doc(C, true, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write]),
@ -479,52 +660,64 @@ generate_html_output(File, RegExp, Languages) ->
file:close(Fh),
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],
Cmd#ejabberd_commands{args = Args2};
maybe_add_policy_arguments(Cmd) ->
Cmd.
generate_md_output(File, <<"runtime">>, Languages) ->
Cmds = lists:map(fun({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, RegExp, Languages) ->
Cmds = find_commands_definitions(),
generate_md_output(File, RegExp, Languages, Cmds).
generate_md_output(File, RegExp, Languages, Cmds) ->
{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(Module), RE, [{capture, none}]) == match
end, Cmds),
Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) ->
re:run(atom_to_list(Module), RE, [{capture, none}]) == match
end,
Cmds),
Cmds3 = lists:sort(fun(#ejabberd_commands{name = N1}, #ejabberd_commands{name = N2}) ->
N1 =< N2
end, Cmds2),
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3],
end,
Cmds2),
Cmds4 = [ maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3 ],
Langs = binary:split(Languages, <<",">>, [global]),
Version = binary_to_list(ejabberd_config:version()),
Header = ["# API Reference\n\n"
"This section describes API commands of ejabberd ", Version, ". "
"The commands that changed in this version are marked with 🟤.\n\n"],
"This section describes API commands of ejabberd ",
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),
{ok, Fh} = file:open(File, [write, {encoding, utf8}]),
io:format(Fh, "~ts~ts", [Header, Out]),
file:close(Fh),
ok.
generate_tags_md(File) ->
Version = binary_to_list(ejabberd_config:version()),
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),
{ok, Fh} = file:open(File, [write, {encoding, utf8}]),
io:format(Fh, "~ts~ts", [Header, Tags]),
file:close(Fh),
ok.
html_pre() ->
"<!DOCTYPE>
<html>
@ -650,8 +843,9 @@ html_pre() ->
}
</script>".
html_post() ->
"<script>
"<script>
var ul = document.getElementsByTagName('ul');
for (var i = 0; i < ul.length; i++) {
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").
%%%===================================================================
%%% API
%%%===================================================================
map_reduce(Y) ->
F =
fun(Y1) ->
Y2 = (validator())(Y1),
Y3 = transform(Y2),
case application:get_env(ejabberd, custom_config_transformer) of
{ok, TransMod} when is_atom(TransMod) ->
TransMod:transform(Y3);
_ ->
Y3
end
end,
fun(Y1) ->
Y2 = (validator())(Y1),
Y3 = transform(Y2),
case application:get_env(ejabberd, custom_config_transformer) of
{ok, TransMod} when is_atom(TransMod) ->
TransMod:transform(Y3);
_ ->
Y3
end
end,
econf:validate(F, Y).
%%%===================================================================
%%% Transformer
%%%===================================================================
@ -48,50 +50,60 @@ transform(Y) ->
{Y2, Acc2} = update(Y1, Acc1),
filter(global, Y2, Acc2).
transform(Host, Y, Acc) ->
filtermapfoldr(
fun({Opt, HostOpts}, Acc1) when (Opt == host_config orelse
Opt == append_host_config)
andalso Host == global ->
case filtermapfoldr(
fun({Host1, Opts}, Acc2) ->
case transform(Host1, Opts, Acc2) of
{[], Acc3} ->
{false, Acc3};
{Opts1, Acc3} ->
{{true, {Host1, Opts1}}, Acc3}
end
end, Acc1, HostOpts) of
{[], Acc4} ->
{false, Acc4};
{HostOpts1, Acc4} ->
{{true, {Opt, HostOpts1}}, Acc4}
end;
({Opt, Val}, Acc1) ->
transform(Host, Opt, Val, Acc1)
end, Acc, Y).
Opt == append_host_config) andalso
Host == global ->
case filtermapfoldr(
fun({Host1, Opts}, Acc2) ->
case transform(Host1, Opts, Acc2) of
{[], Acc3} ->
{false, Acc3};
{Opts1, Acc3} ->
{{true, {Host1, Opts1}}, Acc3}
end
end,
Acc1,
HostOpts) of
{[], Acc4} ->
{false, Acc4};
{HostOpts1, Acc4} ->
{{true, {Opt, HostOpts1}}, Acc4}
end;
({Opt, Val}, Acc1) ->
transform(Host, Opt, Val, Acc1)
end,
Acc,
Y).
transform(Host, modules, ModOpts, Acc) ->
{ModOpts1, Acc2} =
lists:mapfoldr(
fun({Mod, Opts}, Acc1) ->
Opts1 = transform_module_options(Opts),
transform_module(Host, Mod, Opts1, Acc1)
end, Acc, ModOpts),
lists:mapfoldr(
fun({Mod, Opts}, Acc1) ->
Opts1 = transform_module_options(Opts),
transform_module(Host, Mod, Opts1, Acc1)
end,
Acc,
ModOpts),
{{true, {modules, ModOpts1}}, Acc2};
transform(global, listen, Listeners, Acc) ->
{Listeners1, Acc2} =
lists:mapfoldr(
fun(Opts, Acc1) ->
transform_listener(Opts, Acc1)
end, Acc, Listeners),
lists:mapfoldr(
fun(Opts, Acc1) ->
transform_listener(Opts, Acc1)
end,
Acc,
Listeners),
{{true, {listen, Listeners1}}, Acc2};
transform(_Host, Opt, CertFile, Acc) when (Opt == domain_certfile) orelse
(Opt == c2s_certfile) orelse
(Opt == s2s_certfile) ->
(Opt == c2s_certfile) orelse
(Opt == s2s_certfile) ->
?WARNING_MSG("Option '~ts' is deprecated and was automatically "
"appended to 'certfiles' option. ~ts",
[Opt, adjust_hint()]),
"appended to 'certfiles' option. ~ts",
[Opt, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
Acc1 = maps:put(certfiles, CertFiles ++ [CertFile], Acc),
{false, Acc1};
@ -101,57 +113,63 @@ transform(_Host, certfiles, CertFiles1, Acc) ->
{true, Acc1};
transform(_Host, acme, ACME, Acc) ->
ACME1 = lists:map(
fun({ca_url, URL} = Opt) ->
fun({ca_url, URL} = Opt) ->
case misc:uri_parse(URL) of
{ok, _, _, "acme-v01.api.letsencrypt.org", _, _, _} ->
NewURL = ejabberd_acme:default_directory_url(),
?WARNING_MSG("ACME directory URL ~ts defined in "
"option acme->ca_url is deprecated "
"and was automatically replaced "
"with ~ts. ~ts",
[URL, NewURL, adjust_hint()]),
{ca_url, NewURL};
_ ->
Opt
end;
(Opt) ->
Opt
end, ACME),
{ok, _, _, "acme-v01.api.letsencrypt.org", _, _, _} ->
NewURL = ejabberd_acme:default_directory_url(),
?WARNING_MSG("ACME directory URL ~ts defined in "
"option acme->ca_url is deprecated "
"and was automatically replaced "
"with ~ts. ~ts",
[URL, NewURL, adjust_hint()]),
{ca_url, NewURL};
_ ->
Opt
end;
(Opt) ->
Opt
end,
ACME),
{{true, {acme, ACME1}}, Acc};
transform(Host, s2s_use_starttls, required_trusted, Acc) ->
?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprecated and was "
"automatically replaced with value 'required'. "
"The module 'mod_s2s_dialback' has also "
"been automatically removed from the configuration. ~ts",
[adjust_hint()]),
"'s2s_use_starttls' is deprecated and was "
"automatically replaced with value 'required'. "
"The module 'mod_s2s_dialback' has also "
"been automatically removed from the configuration. ~ts",
[adjust_hint()]),
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};
transform(Host, define_macro, Macro, Acc) when is_binary(Host) ->
?WARNING_MSG("The option 'define_macro' is not supported inside 'host_config'. "
"Consequently those macro definitions for host '~ts' are unused: ~ts",
[Host, io_lib:format("~p", [Macro])]),
"Consequently those macro definitions for host '~ts' are unused: ~ts",
[Host, io_lib:format("~p", [Macro])]),
{true, Acc};
transform(_Host, _Opt, _Val, Acc) ->
{true, Acc}.
update(Y, Acc) ->
set_certfiles(Y, Acc).
filter(Host, Y, Acc) ->
lists:filtermap(
fun({Opt, HostOpts}) when (Opt == host_config orelse
Opt == append_host_config)
andalso Host == global ->
HostOpts1 = lists:map(
fun({Host1, Opts1}) ->
{Host1, filter(Host1, Opts1, Acc)}
end, HostOpts),
{true, {Opt, HostOpts1}};
({Opt, Val}) ->
filter(Host, Opt, Val, Acc)
end, Y).
Opt == append_host_config) andalso
Host == global ->
HostOpts1 = lists:map(
fun({Host1, Opts1}) ->
{Host1, filter(Host1, Opts1, Acc)}
end,
HostOpts),
{true, {Opt, HostOpts1}};
({Opt, Val}) ->
filter(Host, Opt, Val, Acc)
end,
Y).
filter(_Host, log_rotate_date, _, _) ->
warn_removed_option(log_rotate_date),
@ -201,10 +219,11 @@ filter(_Host, default_db, odbc, _) ->
{true, {default_db, sql}};
filter(_Host, auth_method, Ms, _) ->
Ms1 = lists:map(
fun(internal) -> mnesia;
(odbc) -> sql;
(M) -> M
end, Ms),
fun(internal) -> mnesia;
(odbc) -> sql;
(M) -> M
end,
Ms),
{true, {auth_method, Ms1}};
filter(_Host, default_ram_db, internal, _) ->
{true, {default_ram_db, mnesia}};
@ -212,16 +231,17 @@ filter(_Host, default_ram_db, odbc, _) ->
{true, {default_ram_db, sql}};
filter(_Host, extauth_cache, _, _) ->
?WARNING_MSG("Option 'extauth_cache' is deprecated "
"and has no effect, use authentication "
"or global cache configuration options: "
"auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []),
"and has no effect, use authentication "
"or global cache configuration options: "
"auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on",
[]),
false;
filter(_Host, extauth_instances, Val, _) ->
warn_replaced_option(extauth_instances, extauth_pool_size),
{true, {extauth_pool_size, Val}};
filter(_Host, Opt, Val, _) when Opt == outgoing_s2s_timeout;
Opt == s2s_dns_timeout ->
Opt == s2s_dns_timeout ->
warn_huge_timeout(Opt, Val),
true;
filter(_Host, captcha_host, _, _) ->
@ -235,18 +255,20 @@ filter(_Host, auth_password_types_hidden_in_scram1, Val, _) ->
filter(Host, modules, ModOpts, State) ->
NoDialbackHosts = maps:get(remove_s2s_dialback, State, []),
ModOpts1 = lists:filter(
fun({mod_s2s_dialback, _}) ->
not lists:member(Host, NoDialbackHosts);
({mod_echo, _}) ->
warn_removed_module(mod_echo),
false;
(_) ->
true
end, ModOpts),
fun({mod_s2s_dialback, _}) ->
not lists:member(Host, NoDialbackHosts);
({mod_echo, _}) ->
warn_removed_module(mod_echo),
false;
(_) ->
true
end,
ModOpts),
{true, {modules, ModOpts1}};
filter(_, _, _, _) ->
true.
%%%===================================================================
%%% Listener transformers
%%%===================================================================
@ -256,127 +278,143 @@ transform_listener(Opts, Acc) ->
Opts3 = remove_inet_options(Opts2),
collect_listener_certfiles(Opts3, Acc).
transform_request_handlers(Opts) ->
case lists:keyfind(module, 1, Opts) of
{_, ejabberd_http} ->
replace_request_handlers(Opts);
{_, ejabberd_xmlrpc} ->
remove_xmlrpc_access_commands(Opts);
_ ->
Opts
{_, ejabberd_http} ->
replace_request_handlers(Opts);
{_, ejabberd_xmlrpc} ->
remove_xmlrpc_access_commands(Opts);
_ ->
Opts
end.
transform_turn_ip(Opts) ->
case lists:keyfind(module, 1, Opts) of
{_, ejabberd_stun} ->
replace_turn_ip(Opts);
_ ->
Opts
{_, ejabberd_stun} ->
replace_turn_ip(Opts);
_ ->
Opts
end.
replace_request_handlers(Opts) ->
Handlers = proplists:get_value(request_handlers, Opts, []),
Handlers1 =
lists:foldl(
fun({captcha, IsEnabled}, Acc) ->
Handler = {<<"/captcha">>, ejabberd_captcha},
warn_replaced_handler(captcha, Handler, IsEnabled),
[Handler|Acc];
({register, IsEnabled}, Acc) ->
Handler = {<<"/register">>, mod_register_web},
warn_replaced_handler(register, Handler, IsEnabled),
[Handler|Acc];
({web_admin, IsEnabled}, Acc) ->
Handler = {<<"/admin">>, ejabberd_web_admin},
warn_replaced_handler(web_admin, Handler, IsEnabled),
[Handler|Acc];
({http_bind, IsEnabled}, Acc) ->
Handler = {<<"/bosh">>, mod_bosh},
warn_replaced_handler(http_bind, Handler, IsEnabled),
[Handler|Acc];
({xmlrpc, IsEnabled}, Acc) ->
Handler = {<<"/">>, ejabberd_xmlrpc},
warn_replaced_handler(xmlrpc, Handler, IsEnabled),
Acc ++ [Handler];
(_, Acc) ->
Acc
end, Handlers, Opts),
lists:foldl(
fun({captcha, IsEnabled}, Acc) ->
Handler = {<<"/captcha">>, ejabberd_captcha},
warn_replaced_handler(captcha, Handler, IsEnabled),
[Handler | Acc];
({register, IsEnabled}, Acc) ->
Handler = {<<"/register">>, mod_register_web},
warn_replaced_handler(register, Handler, IsEnabled),
[Handler | Acc];
({web_admin, IsEnabled}, Acc) ->
Handler = {<<"/admin">>, ejabberd_web_admin},
warn_replaced_handler(web_admin, Handler, IsEnabled),
[Handler | Acc];
({http_bind, IsEnabled}, Acc) ->
Handler = {<<"/bosh">>, mod_bosh},
warn_replaced_handler(http_bind, Handler, IsEnabled),
[Handler | Acc];
({xmlrpc, IsEnabled}, Acc) ->
Handler = {<<"/">>, ejabberd_xmlrpc},
warn_replaced_handler(xmlrpc, Handler, IsEnabled),
Acc ++ [Handler];
(_, Acc) ->
Acc
end,
Handlers,
Opts),
Handlers2 = lists:map(
fun({Path, mod_http_bind}) ->
warn_replaced_module(mod_http_bind, mod_bosh),
{Path, mod_bosh};
(PathMod) ->
PathMod
end, Handlers1),
fun({Path, mod_http_bind}) ->
warn_replaced_module(mod_http_bind, mod_bosh),
{Path, mod_bosh};
(PathMod) ->
PathMod
end,
Handlers1),
Opts1 = lists:filtermap(
fun({captcha, _}) -> false;
({register, _}) -> false;
({web_admin, _}) -> false;
({http_bind, _}) -> false;
({xmlrpc, _}) -> false;
({http_poll, _}) ->
?WARNING_MSG("Listening option 'http_poll' is "
"ignored: HTTP Polling support was "
"removed in ejabberd 15.04. ~ts",
[adjust_hint()]),
false;
({request_handlers, _}) ->
false;
(_) -> true
end, Opts),
fun({captcha, _}) -> false;
({register, _}) -> false;
({web_admin, _}) -> false;
({http_bind, _}) -> false;
({xmlrpc, _}) -> false;
({http_poll, _}) ->
?WARNING_MSG("Listening option 'http_poll' is "
"ignored: HTTP Polling support was "
"removed in ejabberd 15.04. ~ts",
[adjust_hint()]),
false;
({request_handlers, _}) ->
false;
(_) -> true
end,
Opts),
case Handlers2 of
[] -> Opts1;
_ -> [{request_handlers, Handlers2}|Opts1]
[] -> Opts1;
_ -> [{request_handlers, Handlers2} | Opts1]
end.
remove_xmlrpc_access_commands(Opts) ->
lists:filter(
fun({access_commands, _}) ->
warn_removed_option(access_commands, api_permissions),
false;
(_) ->
true
end, Opts).
warn_removed_option(access_commands, api_permissions),
false;
(_) ->
true
end,
Opts).
replace_turn_ip(Opts) ->
lists:filtermap(
fun({turn_ip, Val}) ->
warn_replaced_option(turn_ip, turn_ipv4_address),
{true, {turn_ipv4_address, Val}};
(_) ->
true
end, Opts).
warn_replaced_option(turn_ip, turn_ipv4_address),
{true, {turn_ipv4_address, Val}};
(_) ->
true
end,
Opts).
remove_inet_options(Opts) ->
lists:filter(
fun({Opt, _}) when Opt == inet; Opt == inet6 ->
warn_removed_option(Opt, ip),
false;
(_) ->
true
end, Opts).
warn_removed_option(Opt, ip),
false;
(_) ->
true
end,
Opts).
collect_listener_certfiles(Opts, Acc) ->
Mod = proplists:get_value(module, Opts),
if Mod == ejabberd_http;
Mod == ejabberd_c2s;
Mod == ejabberd_s2s_in ->
case lists:keyfind(certfile, 1, Opts) of
{_, CertFile} ->
?WARNING_MSG("Listening option 'certfile' of module ~ts "
"is deprecated and was automatically "
"appended to global 'certfiles' option. ~ts",
[Mod, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
{proplists:delete(certfile, Opts),
maps:put(certfiles, [CertFile|CertFiles], Acc)};
false ->
{Opts, Acc}
end;
true ->
{Opts, Acc}
if
Mod == ejabberd_http;
Mod == ejabberd_c2s;
Mod == ejabberd_s2s_in ->
case lists:keyfind(certfile, 1, Opts) of
{_, CertFile} ->
?WARNING_MSG("Listening option 'certfile' of module ~ts "
"is deprecated and was automatically "
"appended to global 'certfiles' option. ~ts",
[Mod, adjust_hint()]),
CertFiles = maps:get(certfiles, Acc, []),
{proplists:delete(certfile, Opts),
maps:put(certfiles, [CertFile | CertFiles], Acc)};
false ->
{Opts, Acc}
end;
true ->
{Opts, Acc}
end.
%%%===================================================================
%%% Module transformers
%%% NOTE: transform_module_options/1 is called before transform_module/4
@ -384,32 +422,34 @@ collect_listener_certfiles(Opts, Acc) ->
transform_module_options(Opts) ->
lists:filtermap(
fun({Opt, internal}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, mnesia}};
({Opt, odbc}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, sql}};
({deref_aliases, Val}) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}};
({ldap_group_cache_size, _}) ->
warn_removed_option(ldap_group_cache_size, cache_size),
false;
({ldap_user_cache_size, _}) ->
warn_removed_option(ldap_user_cache_size, cache_size),
false;
({ldap_group_cache_validity, _}) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time),
false;
({ldap_user_cache_validity, _}) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time),
false;
({iqdisc, _}) ->
warn_removed_option(iqdisc),
false;
(_) ->
true
end, Opts).
Opt == ram_db_type ->
{true, {Opt, mnesia}};
({Opt, odbc}) when Opt == db_type;
Opt == ram_db_type ->
{true, {Opt, sql}};
({deref_aliases, Val}) ->
warn_replaced_option(deref_aliases, ldap_deref_aliases),
{true, {ldap_deref_aliases, Val}};
({ldap_group_cache_size, _}) ->
warn_removed_option(ldap_group_cache_size, cache_size),
false;
({ldap_user_cache_size, _}) ->
warn_removed_option(ldap_user_cache_size, cache_size),
false;
({ldap_group_cache_validity, _}) ->
warn_removed_option(ldap_group_cache_validity, cache_life_time),
false;
({ldap_user_cache_validity, _}) ->
warn_removed_option(ldap_user_cache_validity, cache_life_time),
false;
({iqdisc, _}) ->
warn_removed_option(iqdisc),
false;
(_) ->
true
end,
Opts).
transform_module(Host, mod_http_bind, Opts, Acc) ->
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_ldap, Opts, Acc) ->
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
M == mod_blocking_odbc orelse
M == mod_caps_odbc orelse
M == mod_last_odbc orelse
M == mod_muc_odbc orelse
M == mod_offline_odbc orelse
M == mod_privacy_odbc orelse
M == mod_private_odbc orelse
M == mod_pubsub_odbc orelse
M == mod_roster_odbc orelse
M == mod_shared_roster_odbc orelse
M == mod_vcard_odbc) ->
M == mod_blocking_odbc orelse
M == mod_caps_odbc orelse
M == mod_last_odbc orelse
M == mod_muc_odbc orelse
M == mod_offline_odbc orelse
M == mod_privacy_odbc orelse
M == mod_private_odbc orelse
M == mod_pubsub_odbc orelse
M == mod_roster_odbc orelse
M == mod_shared_roster_odbc orelse
M == mod_vcard_odbc) ->
M1 = strip_odbc_suffix(M),
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) ->
Opts1 = lists:filter(
fun({db_type, _}) ->
warn_removed_module_option(db_type, mod_blocking),
false;
(_) ->
true
end, Opts),
fun({db_type, _}) ->
warn_removed_module_option(db_type, mod_blocking),
false;
(_) ->
true
end,
Opts),
{{mod_blocking, Opts1}, Acc};
transform_module(_Host, mod_carboncopy, Opts, Acc) ->
Opts1 = lists:filter(
fun({Opt, _}) when Opt == ram_db_type;
Opt == use_cache;
Opt == cache_size;
Opt == cache_missed;
Opt == cache_life_time ->
warn_removed_module_option(Opt, mod_carboncopy),
false;
(_) ->
true
end, Opts),
fun({Opt, _}) when Opt == ram_db_type;
Opt == use_cache;
Opt == cache_size;
Opt == cache_missed;
Opt == cache_life_time ->
warn_removed_module_option(Opt, mod_carboncopy),
false;
(_) ->
true
end,
Opts),
{{mod_carboncopy, Opts1}, Acc};
transform_module(_Host, mod_http_api, Opts, Acc) ->
Opts1 = lists:filter(
fun({admin_ip_access, _}) ->
warn_removed_option(admin_ip_access, api_permissions),
false;
(_) ->
true
end, Opts),
fun({admin_ip_access, _}) ->
warn_removed_option(admin_ip_access, api_permissions),
false;
(_) ->
true
end,
Opts),
{{mod_http_api, Opts1}, Acc};
transform_module(_Host, mod_http_upload, Opts, Acc) ->
Opts1 = lists:filter(
fun({service_url, _}) ->
warn_deprecated_option(service_url, external_secret),
true;
(_) ->
true
end, Opts),
fun({service_url, _}) ->
warn_deprecated_option(service_url, external_secret),
true;
(_) ->
true
end,
Opts),
{{mod_http_upload, Opts1}, Acc};
transform_module(_Host, mod_pubsub, Opts, Acc) ->
Opts1 = lists:map(
fun({plugins, Plugins}) ->
{plugins,
lists:filter(
fun(Plugin) ->
case lists:member(
Plugin,
[<<"buddy">>, <<"club">>, <<"dag">>,
<<"dispatch">>, <<"hometree">>, <<"mb">>,
<<"mix">>, <<"online">>, <<"private">>,
<<"public">>]) of
true ->
?WARNING_MSG(
"Plugin '~ts' of mod_pubsub is not "
"supported anymore and has been "
"automatically removed from 'plugins' "
"option. ~ts",
[Plugin, adjust_hint()]),
false;
false ->
true
end
end, Plugins)};
(Opt) ->
Opt
end, Opts),
fun({plugins, Plugins}) ->
{plugins,
lists:filter(
fun(Plugin) ->
case lists:member(
Plugin,
[<<"buddy">>,
<<"club">>,
<<"dag">>,
<<"dispatch">>,
<<"hometree">>,
<<"mb">>,
<<"mix">>,
<<"online">>,
<<"private">>,
<<"public">>]) of
true ->
?WARNING_MSG(
"Plugin '~ts' of mod_pubsub is not "
"supported anymore and has been "
"automatically removed from 'plugins' "
"option. ~ts",
[Plugin, adjust_hint()]),
false;
false ->
true
end
end,
Plugins)};
(Opt) ->
Opt
end,
Opts),
{{mod_pubsub, Opts1}, Acc};
transform_module(_Host, Mod, Opts, Acc) ->
{{Mod, Opts}, Acc}.
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), "_")).
%%%===================================================================
%%% Aux
%%%===================================================================
filtermapfoldr(Fun, Init, List) ->
lists:foldr(
fun(X, {Ret, Acc}) ->
case Fun(X, Acc) of
{true, Acc1} -> {[X|Ret], Acc1};
{{true, X1}, Acc1} -> {[X1|Ret], Acc1};
{false, Acc1} -> {Ret, Acc1}
end
end, {[], Init}, List).
case Fun(X, Acc) of
{true, Acc1} -> {[X | Ret], Acc1};
{{true, X1}, Acc1} -> {[X1 | Ret], Acc1};
{false, Acc1} -> {Ret, Acc1}
end
end,
{[], Init},
List).
set_certfiles(Y, #{certfiles := CertFiles} = Acc) ->
{lists:keystore(certfiles, 1, Y, {certfiles, CertFiles}), Acc};
set_certfiles(Y, Acc) ->
{Y, Acc}.
%%%===================================================================
%%% Warnings
%%%===================================================================
warn_replaced_module(From, To) ->
?WARNING_MSG("Module ~ts is deprecated and was automatically "
"replaced by ~ts. ~ts",
[From, To, adjust_hint()]).
"replaced by ~ts. ~ts",
[From, To, adjust_hint()]).
warn_replaced_module(From, To, Type) ->
?WARNING_MSG("Module ~ts is deprecated and was automatically "
"replaced by ~ts with db_type: ~ts. ~ts",
[From, To, Type, adjust_hint()]).
"replaced by ~ts with db_type: ~ts. ~ts",
[From, To, Type, adjust_hint()]).
warn_removed_module(Mod) ->
?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) ->
?WARNING_MSG("Listening option '~ts' is deprecated, "
"please use instead the "
"HTTP request handler: \"~ts\" -> ~ts. ~ts",
[Opt, Path, Module, adjust_hint()]);
"please use instead the "
"HTTP request handler: \"~ts\" -> ~ts. ~ts",
[Opt, Path, Module, adjust_hint()]);
warn_replaced_handler(Opt, {Path, Module}, true) ->
?WARNING_MSG("Listening option '~ts' is deprecated "
"and was automatically replaced by "
"HTTP request handler: \"~ts\" -> ~ts. ~ts",
[Opt, Path, Module, adjust_hint()]).
"and was automatically replaced by "
"HTTP request handler: \"~ts\" -> ~ts. ~ts",
[Opt, Path, Module, adjust_hint()]).
warn_deprecated_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~ts' is deprecated. Use option '~ts' instead.",
[OldOpt, NewOpt]).
[OldOpt, NewOpt]).
warn_replaced_option(OldOpt, NewOpt) ->
?WARNING_MSG("Option '~ts' is deprecated and was automatically "
"replaced by '~ts'. ~ts",
[OldOpt, NewOpt, adjust_hint()]).
"replaced by '~ts'. ~ts",
[OldOpt, NewOpt, adjust_hint()]).
warn_removed_option(Opt) ->
?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) ->
?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) ->
?WARNING_MSG("Option '~ts' of module ~ts is deprecated "
"and has no effect anymore. ~ts",
[Opt, Mod, adjust_hint()]).
"and has no effect anymore. ~ts",
[Opt, Mod, adjust_hint()]).
warn_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
?WARNING_MSG("Value '~B' of option '~ts' is too big, "
"are you sure you have set seconds?",
[T, Opt]);
"are you sure you have set seconds?",
[T, Opt]);
warn_huge_timeout(_, _) ->
ok.
adjust_hint() ->
"Please adjust your configuration file accordingly. "
"Hint: run `ejabberdctl dump-config` command to view current "
"configuration as it is seen by ejabberd.".
%%%===================================================================
%%% Very raw validator: just to make sure we get properly typed terms
%%% Expand it if you need to transform more options, but don't
@ -597,48 +669,60 @@ adjust_hint() ->
%%%===================================================================
validator() ->
Validators =
#{s2s_use_starttls => econf:atom(),
certfiles => econf:list(econf:any()),
c2s_certfile => econf:binary(),
s2s_certfile => econf:binary(),
domain_certfile => econf:binary(),
default_db => econf:atom(),
default_ram_db => econf:atom(),
auth_method => econf:list_or_single(econf:atom()),
acme => econf:options(
#{ca_url => econf:binary(),
'_' => econf:any()},
[unique]),
listen =>
econf:list(
econf:options(
#{captcha => econf:bool(),
register => econf:bool(),
web_admin => econf:bool(),
http_bind => econf:bool(),
http_poll => econf:bool(),
xmlrpc => econf:bool(),
module => econf:atom(),
certfile => econf:binary(),
request_handlers =>
econf:map(econf:binary(), econf:atom()),
'_' => econf:any()},
[])),
modules =>
econf:options(
#{'_' =>
econf:options(
#{db_type => econf:atom(),
plugins => econf:list(econf:binary()),
'_' => econf:any()},
[])},
[]),
'_' => econf:any()},
#{
s2s_use_starttls => econf:atom(),
certfiles => econf:list(econf:any()),
c2s_certfile => econf:binary(),
s2s_certfile => econf:binary(),
domain_certfile => econf:binary(),
default_db => econf:atom(),
default_ram_db => econf:atom(),
auth_method => econf:list_or_single(econf:atom()),
acme => econf:options(
#{
ca_url => econf:binary(),
'_' => econf:any()
},
[unique]),
listen =>
econf:list(
econf:options(
#{
captcha => econf:bool(),
register => econf:bool(),
web_admin => econf:bool(),
http_bind => econf:bool(),
http_poll => econf:bool(),
xmlrpc => econf:bool(),
module => econf:atom(),
certfile => econf:binary(),
request_handlers =>
econf:map(econf:binary(), econf:atom()),
'_' => econf:any()
},
[])),
modules =>
econf:options(
#{
'_' =>
econf:options(
#{
db_type => econf:atom(),
plugins => econf:list(econf:binary()),
'_' => econf:any()
},
[])
},
[]),
'_' => econf:any()
},
econf:options(
Validators#{host_config =>
econf:map(econf:binary(),
econf:options(Validators, [])),
append_host_config =>
econf:map(econf:binary(),
econf:options(Validators, []))},
Validators#{
host_config =>
econf:map(econf:binary(),
econf:options(Validators, [])),
append_host_config =>
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
-export([init/1]).
%%%===================================================================
%%% API functions
%%%===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================

View file

@ -27,12 +27,14 @@
-include("ejabberd_commands.hrl").
-include("translate.hrl").
%%%===================================================================
%%% API
%%%===================================================================
man() ->
man(<<"en">>).
man(Lang) when is_list(Lang) ->
man(list_to_binary(Lang));
man(Lang) ->
@ -40,7 +42,7 @@ man(Lang) ->
lists:foldl(
fun(M, {Mods, SubMods} = Acc) ->
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 ->
try M:mod_doc() of
#{desc := Descr} = Map ->
@ -48,17 +50,18 @@ man(Lang) ->
Example = maps:get(example, Map, []),
Note = maps:get(note, Map, []),
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} ->
{ParentMod, Backend} = strip_backend_suffix(M),
{Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)};
#{} ->
warn("module ~s is not properly documented", [M]),
Acc
catch _:undef ->
catch
_:undef ->
case erlang:function_exported(
M, mod_options, 1) of
true ->
true ->
warn("module ~s is not documented", [M]);
false ->
ok
@ -68,13 +71,18 @@ man(Lang) ->
false ->
Acc
end
end, {[], dict:new()}, ejabberd_config:beams(all)),
end,
{[], dict:new()},
ejabberd_config:beams(all)),
Doc = lists:flatmap(
fun(M) ->
try M:doc()
catch _:undef -> []
try
M:doc()
catch
_:undef -> []
end
end, ejabberd_config:callback_modules(all)),
end,
ejabberd_config:callback_modules(all)),
Version = binary_to_list(ejabberd_config:version()),
Options =
["TOP LEVEL OPTIONS",
@ -85,7 +93,8 @@ man(Lang) ->
lists:flatmap(
fun(Opt) ->
opt_to_man(Lang, Opt, 1)
end, lists:keysort(1, Doc)),
end,
lists:keysort(1, Doc)),
ModDoc1 = lists:map(
fun({M, Descr, DocOpts, Ex}) ->
case dict:find(M, SubModDoc) of
@ -94,7 +103,8 @@ man(Lang) ->
error ->
{M, Descr, DocOpts, [], Ex}
end
end, ModDoc),
end,
ModDoc),
ModOptions =
[io_lib:nl(),
"MODULES",
@ -112,12 +122,13 @@ man(Lang) ->
lists:duplicate(length(atom_to_list(M)), $~),
"[[" ++ ModName ++ "]]",
io_lib:nl()] ++
format_versions(Lang, Example) ++ [io_lib:nl()] ++
tr_multi(Lang, Descr) ++ [io_lib:nl()] ++
opts_to_man(Lang, [{M, '', DocOpts}|Backends]) ++
format_example(0, Lang, Example) ++ [io_lib:nl()] ++
format_apitags(Lang, Example)
end, lists:keysort(1, ModDoc1)),
format_versions(Lang, Example) ++ [io_lib:nl()] ++
tr_multi(Lang, Descr) ++ [io_lib:nl()] ++
opts_to_man(Lang, [{M, '', DocOpts} | Backends]) ++
format_example(0, Lang, Example) ++ [io_lib:nl()] ++
format_apitags(Lang, Example)
end,
lists:keysort(1, ModDoc1)),
ListenOptions =
[io_lib:nl(),
"LISTENERS",
@ -127,13 +138,14 @@ man(Lang) ->
io_lib:nl(),
"TODO"],
AsciiData =
[[unicode:characters_to_binary(Line), io_lib:nl()]
|| Line <- man_header(Lang) ++ Options ++ [io_lib:nl()]
++ ModOptions ++ ListenOptions ++ man_footer(Lang)],
[ [unicode:characters_to_binary(Line), io_lib:nl()]
|| Line <- man_header(Lang) ++ Options ++ [io_lib:nl()] ++
ModOptions ++ ListenOptions ++ man_footer(Lang) ],
warn_undocumented_modules(ModDoc1),
warn_undocumented_options(Doc),
write_man(AsciiData).
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -143,33 +155,36 @@ opts_to_man(Lang, [{_, _, []}]) ->
opts_to_man(Lang, Backends) ->
lists:flatmap(
fun({_, Backend, DocOpts}) when DocOpts /= [] ->
Text = if Backend == '' ->
Text = if
Backend == '' ->
tr(Lang, ?T("Available options"));
true ->
true ->
lists:flatten(
io_lib:format(
tr(Lang, ?T("Available options for '~s' backend")),
[Backend]))
end,
[Text ++ ":", lists:duplicate(length(Text)+1, $^)|
lists:flatmap(
fun(Opt) -> opt_to_man(Lang, Opt, 1) end,
lists:keysort(1, DocOpts))] ++ [io_lib:nl()];
[Text ++ ":", lists:duplicate(length(Text) + 1, $^) | lists:flatmap(
fun(Opt) -> opt_to_man(Lang, Opt, 1) end,
lists:keysort(1, DocOpts))] ++ [io_lib:nl()];
(_) ->
[]
end, Backends).
end,
Backends).
opt_to_man(Lang, {Option, Options}, Level) ->
[format_option(Lang, Option, Options)|format_versions(Lang, Options)++format_desc(Lang, Options)] ++
format_example(Level, Lang, Options);
[format_option(Lang, Option, Options) | format_versions(Lang, Options) ++ format_desc(Lang, Options)] ++
format_example(Level, Lang, Options);
opt_to_man(Lang, {Option, Options, Children}, Level) ->
[format_option(Lang, Option, Options)|format_desc(Lang, Options)] ++
lists:append(
[[H ++ ":"|T]
|| [H|T] <- lists:map(
fun(Opt) -> opt_to_man(Lang, Opt, Level+1) end,
lists:keysort(1, Children))]) ++
[io_lib:nl()|format_example(Level, Lang, Options)].
[format_option(Lang, Option, Options) | format_desc(Lang, Options)] ++
lists:append(
[ [H ++ ":" | T]
|| [H | T] <- lists:map(
fun(Opt) -> opt_to_man(Lang, Opt, Level + 1) end,
lists:keysort(1, Children)) ]) ++
[io_lib:nl() | format_example(Level, Lang, Options)].
get_version_mark(#{note := Note}) ->
[XX, YY | _] = string:tokens(binary_to_list(ejabberd_option:version()), "."),
@ -181,84 +196,95 @@ get_version_mark(#{note := Note}) ->
get_version_mark(_) ->
"".
format_option(Lang, Option, #{value := Val} = Options) ->
VersionMark = get_version_mark(Options),
"*" ++ atom_to_list(Option) ++ VersionMark ++ "*: 'pass:[" ++
tr(Lang, Val) ++ "]'::";
tr(Lang, Val) ++ "]'::";
format_option(_Lang, Option, #{}) ->
"*" ++ atom_to_list(Option) ++ "*::".
format_versions(_Lang, #{note := Note}) when Note /= [] ->
["_Note_ about this option: " ++ Note ++ ". "];
format_versions(_, _) ->
[].
%% @format-begin
get_module_apitags(M) ->
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 =
lists:sort(
misc:lists_uniq(
lists:flatten(Tags))),
TagsStrings = [atom_to_list(C) || C <- TagsClean],
misc:lists_uniq(
lists:flatten(Tags))),
TagsStrings = [ atom_to_list(C) || C <- TagsClean ],
TagFiltering =
fun ("internal") ->
fun("internal") ->
false;
([$v | Rest]) ->
([$v | Rest]) ->
{error, no_integer} == string:to_integer(Rest);
(_) ->
(_) ->
true
end,
TagsFiltered = lists:filter(TagFiltering, TagsStrings),
TagsUrls =
[["_`../../developer/ejabberd-api/admin-tags.md#", C, "|", C, "`_"] || C <- TagsFiltered],
[ ["_`../../developer/ejabberd-api/admin-tags.md#", C, "|", C, "`_"] || C <- TagsFiltered ],
lists:join(", ", TagsUrls).
format_apitags(_Lang, #{apitags := TagsString}) when TagsString /= "" ->
["**API Tags:** ", TagsString];
format_apitags(_, _) ->
[].
%% @format-end
format_desc(Lang, #{desc := 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
true ->
if Level == 0 ->
if
Level == 0 ->
["*Example*:",
"^^^^^^^^^^"];
true ->
true ->
["+", "*Example*:", "+"]
end ++ format_yaml(Example);
false when Level == 0 ->
["Examples:",
"^^^^^^^^^"] ++
lists:flatmap(
fun({Text, Lines}) ->
[tr(Lang, Text)] ++ format_yaml(Lines)
end, Example);
lists:flatmap(
fun({Text, Lines}) ->
[tr(Lang, Text)] ++ format_yaml(Lines)
end,
Example);
false ->
lists:flatmap(
fun(Block) ->
["+", "*Examples*:", "+"|Block]
["+", "*Examples*:", "+" | Block]
end,
lists:map(
fun({Text, Lines}) ->
[tr(Lang, Text), "+"] ++ format_yaml(Lines)
end, Example))
end,
Example))
end;
format_example(_, _, _) ->
[].
format_yaml(Lines) ->
["==========================",
"[source,yaml]",
"----"|Lines] ++
["----",
"=========================="].
"----" | Lines] ++
["----",
"=========================="].
man_header(Lang) ->
["ejabberd.yml(5)",
@ -276,18 +302,21 @@ man_header(Lang) ->
io_lib:nl(),
"DESCRIPTION",
"-----------",
tr(Lang, ?T("The configuration file is written in "
"https://en.wikipedia.org/wiki/YAML[YAML] language.")),
tr(Lang,
?T("The configuration file is written in "
"https://en.wikipedia.org/wiki/YAML[YAML] language.")),
io_lib:nl(),
tr(Lang, ?T("WARNING: YAML is indentation sensitive, so make sure you respect "
"indentation, or otherwise you will get pretty cryptic "
"configuration errors.")),
tr(Lang,
?T("WARNING: YAML is indentation sensitive, so make sure you respect "
"indentation, or otherwise you will get pretty cryptic "
"configuration errors.")),
io_lib:nl(),
tr(Lang, ?T("Logically, configuration options are split into 3 main categories: "
"'Modules', 'Listeners' and everything else called 'Top Level' options. "
"Thus this document is split into 3 main chapters describing each "
"category separately. So, the contents of ejabberd.yml will typically "
"look like this:")),
tr(Lang,
?T("Logically, configuration options are split into 3 main categories: "
"'Modules', 'Listeners' and everything else called 'Top Level' options. "
"Thus this document is split into 3 main chapters describing each "
"category separately. So, the contents of ejabberd.yml will typically "
"look like this:")),
io_lib:nl(),
"==========================",
"[source,yaml]",
@ -308,41 +337,47 @@ man_header(Lang) ->
"----",
"==========================",
io_lib:nl(),
tr(Lang, ?T("Any configuration error (such as syntax error, unknown option "
"or invalid option value) is fatal in the sense that ejabberd will "
"refuse to load the whole configuration file and will not start or will "
"abort configuration reload.")),
tr(Lang,
?T("Any configuration error (such as syntax error, unknown option "
"or invalid option value) is fatal in the sense that ejabberd will "
"refuse to load the whole configuration file and will not start or will "
"abort configuration reload.")),
io_lib:nl(),
tr(Lang, ?T("All options can be changed in runtime by running 'ejabberdctl "
"reload-config' command. Configuration reload is atomic: either all options "
"are accepted and applied simultaneously or the new configuration is "
"refused without any impact on currently running configuration.")),
tr(Lang,
?T("All options can be changed in runtime by running 'ejabberdctl "
"reload-config' command. Configuration reload is atomic: either all options "
"are accepted and applied simultaneously or the new configuration is "
"refused without any impact on currently running configuration.")),
io_lib:nl(),
tr(Lang, ?T("Some options can be specified for particular virtual host(s) only "
"using 'host_config' or 'append_host_config' options. Such options "
"are called 'local'. Examples are 'modules', 'auth_method' and 'default_db'. "
"The options that cannot be defined per virtual host are called 'global'. "
"Examples are 'loglevel', 'certfiles' and 'listen'. It is a configuration "
"mistake to put 'global' options under 'host_config' or 'append_host_config' "
"section - ejabberd will refuse to load such configuration.")),
tr(Lang,
?T("Some options can be specified for particular virtual host(s) only "
"using 'host_config' or 'append_host_config' options. Such options "
"are called 'local'. Examples are 'modules', 'auth_method' and 'default_db'. "
"The options that cannot be defined per virtual host are called 'global'. "
"Examples are 'loglevel', 'certfiles' and 'listen'. It is a configuration "
"mistake to put 'global' options under 'host_config' or 'append_host_config' "
"section - ejabberd will refuse to load such configuration.")),
io_lib:nl(),
str:format(
tr(Lang, ?T("It is not recommended to write ejabberd.yml from scratch. Instead it is "
"better to start from \"default\" configuration file available at ~s. "
"Once you get ejabberd running you can start changing configuration "
"options to meet your requirements.")),
tr(Lang,
?T("It is not recommended to write ejabberd.yml from scratch. Instead it is "
"better to start from \"default\" configuration file available at ~s. "
"Once you get ejabberd running you can start changing configuration "
"options to meet your requirements.")),
[default_config_url()]),
io_lib:nl(),
str:format(
tr(Lang, ?T("Note that this document is intended to provide comprehensive description of "
"all configuration options that can be consulted to understand the meaning "
"of a particular option, its format and possible values. It will be quite "
"hard to understand how to configure ejabberd by reading this document only "
"- for this purpose the reader is recommended to read online Configuration "
"Guide available at ~s.")),
tr(Lang,
?T("Note that this document is intended to provide comprehensive description of "
"all configuration options that can be consulted to understand the meaning "
"of a particular option, its format and possible values. It will be quite "
"hard to understand how to configure ejabberd by reading this document only "
"- for this purpose the reader is recommended to read online Configuration "
"Guide available at ~s.")),
[configuration_guide_url()]),
io_lib:nl()].
man_footer(Lang) ->
{Year, _, _} = date(),
[io_lib:nl(),
@ -353,9 +388,10 @@ man_footer(Lang) ->
"VERSION",
"-------",
str:format(
tr(Lang, ?T("This document describes the configuration file of ejabberd ~ts. "
"Configuration options of other ejabberd versions "
"may differ significantly.")),
tr(Lang,
?T("This document describes the configuration file of ejabberd ~ts. "
"Configuration options of other ejabberd versions "
"may differ significantly.")),
[ejabberd_config:version()]),
io_lib:nl(),
"REPORTING BUGS",
@ -377,7 +413,8 @@ man_footer(Lang) ->
"COPYING",
"-------",
"Copyright (c) 2002-" ++ integer_to_list(Year) ++
" https://www.process-one.net[ProcessOne]."].
" https://www.process-one.net[ProcessOne]."].
tr(Lang, {Format, Args}) ->
unicode:characters_to_list(
@ -387,12 +424,14 @@ tr(Lang, {Format, Args}) ->
tr(Lang, Txt) ->
unicode:characters_to_list(translate:translate(Lang, iolist_to_binary(Txt))).
tr_multi(Lang, Txt) when is_binary(Txt) ->
tr_multi(Lang, [Txt]);
tr_multi(Lang, {Format, Args}) ->
tr_multi(Lang, [{Format, Args}]);
tr_multi(Lang, Lines) when is_list(Lines) ->
[tr(Lang, Txt) || Txt <- Lines].
[ tr(Lang, Txt) || Txt <- Lines ].
write_man(AsciiData) ->
case file:get_cwd() of
@ -425,12 +464,14 @@ write_man(AsciiData) ->
[file:format_error(Reason)]))}
end.
have_a2x() ->
case os:find_executable("a2x") of
false -> false;
Path -> {true, Path}
end.
run_a2x(Cwd, AsciiDocFile) ->
case have_a2x() of
false ->
@ -445,6 +486,7 @@ run_a2x(Cwd, AsciiDocFile) ->
end
end.
warn_undocumented_modules(Docs) ->
lists:foreach(
fun({M, _, DocOpts, Backends, _}) ->
@ -452,8 +494,11 @@ warn_undocumented_modules(Docs) ->
lists:foreach(
fun({SubM, _, SubOpts}) ->
warn_undocumented_module(SubM, SubOpts)
end, Backends)
end, Docs).
end,
Backends)
end,
Docs).
warn_undocumented_module(M, DocOpts) ->
try M:mod_options(ejabberd_config:get_myname()) of
@ -471,11 +516,14 @@ warn_undocumented_module(M, DocOpts) ->
true ->
ok
end
end, Defaults)
catch _:undef ->
end,
Defaults)
catch
_:undef ->
ok
end.
warn_undocumented_options(Docs) ->
Opts = lists:flatmap(
fun(M) ->
@ -484,11 +532,14 @@ warn_undocumented_options(Docs) ->
lists:map(
fun({O, _}) -> O;
(O) when is_atom(O) -> O
end, Defaults)
catch _:undef ->
end,
Defaults)
catch
_:undef ->
[]
end
end, ejabberd_config:callback_modules(all)),
end,
ejabberd_config:callback_modules(all)),
lists:foreach(
fun(Opt) ->
case lists:keymember(Opt, 1, Docs) of
@ -497,19 +548,24 @@ warn_undocumented_options(Docs) ->
true ->
ok
end
end, Opts).
end,
Opts).
warn(Format, Args) ->
io:format(standard_error, "Warning: " ++ Format ++ "~n", Args).
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)}.
default_config_url() ->
"<https://github.com/processone/ejabberd/blob/" ++
binary_to_list(binary:part(ejabberd_config:version(), {0,5})) ++
"/ejabberd.yml.example>".
binary_to_list(binary:part(ejabberd_config:version(), {0, 5})) ++
"/ejabberd.yml.example>".
configuration_guide_url() ->
"<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(p1_fsm).
-export([start/1, start_link/1, init/1, handle_event/3,
handle_sync_event/4, 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]).
-export([start/1,
start_link/1,
init/1,
handle_event/3,
handle_sync_event/4,
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").
@ -40,6 +53,10 @@
-include("ejabberd_http.hrl").
%%
%% @efmt:off
%% @indent-begin
-record(state,
{socket :: ws_socket(),
ping_interval :: non_neg_integer(),
@ -48,11 +65,15 @@
timeout :: non_neg_integer(),
timer = make_ref() :: reference(),
input = [] :: list(),
active = false :: boolean(),
c2s_pid :: pid(),
active = false :: boolean(),
c2s_pid :: pid(),
ws :: {#ws{}, pid()}}).
%-define(DBGFSM, true).
%% @indent-end
%% @efmt:on
%%
%%%-define(DBGFSM, true).
-ifdef(DBGFSM).
@ -67,56 +88,70 @@
-type ws_socket() :: {http_ws, pid(), {inet:ip_address(), inet:port_number()}}.
-export_type([ws_socket/0]).
start(WS) ->
p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
start_link(WS) ->
p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send_xml({http_ws, FsmRef, _IP}, Packet) ->
case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet},
15000)
of
{'EXIT', {timeout, _}} -> {error, timeout};
{'EXIT', _} -> {error, einval};
Res -> Res
{send_xml, Packet},
15000) of
{'EXIT', {timeout, _}} -> {error, timeout};
{'EXIT', _} -> {error, einval};
Res -> Res
end.
setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
true ->
p1_fsm:send_all_state_event(FsmRef,
{activate, self()});
_ -> ok
true ->
p1_fsm:send_all_state_event(FsmRef,
{activate, self()});
_ -> ok
end.
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
controlling_process(_Socket, _Pid) -> ok.
close({http_ws, FsmRef, _IP}) ->
catch p1_fsm:sync_send_all_state_event(FsmRef, close).
reset_stream({http_ws, _FsmRef, _IP} = Socket) ->
Socket.
change_shaper({http_ws, FsmRef, _IP}, Shaper) ->
p1_fsm:send_all_state_event(FsmRef, {new_shaper, Shaper}).
get_transport(_Socket) ->
websocket.
get_owner({http_ws, FsmRef, _IP}) ->
FsmRef.
socket_handoff(LocalPath, Request, Opts) ->
ejabberd_websocket:socket_handoff(LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0).
%%% Internal
init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true;
@ -127,84 +162,100 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
({resend_on_timeout, _}) -> true;
({access, _}) -> true;
(_) -> false
end, HOpts),
end,
HOpts),
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
PingInterval = ejabberd_option:websocket_ping_interval(),
WSTimeout = ejabberd_option:websocket_timeout(),
Socket = {http_ws, self(), IP},
?DEBUG("Client connected through websocket ~p",
[Socket]),
case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()}|Opts]) of
{ok, C2SPid} ->
ejabberd_c2s:accept(C2SPid),
Timer = erlang:start_timer(WSTimeout, self(), []),
{ok, loop,
#state{socket = Socket, timeout = WSTimeout,
timer = Timer, ws = WS, c2s_pid = C2SPid,
ping_interval = PingInterval}};
{error, Reason} ->
{stop, Reason};
ignore ->
ignore
[Socket]),
case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()} | Opts]) of
{ok, C2SPid} ->
ejabberd_c2s:accept(C2SPid),
Timer = erlang:start_timer(WSTimeout, self(), []),
{ok, loop,
#state{
socket = Socket,
timeout = WSTimeout,
timer = Timer,
ws = WS,
c2s_pid = C2SPid,
ping_interval = PingInterval
}};
{error, Reason} ->
{stop, Reason};
ignore ->
ignore
end.
handle_event({activate, From}, StateName, State) ->
State1 = case State#state.input of
[] -> State#state{active = true};
Input ->
lists:foreach(
fun(I) when is_binary(I)->
From ! {tcp, State#state.socket, I};
(I2) ->
From ! {tcp, State#state.socket, [I2]}
end, Input),
State#state{active = false, input = []}
end,
[] -> State#state{active = true};
Input ->
lists:foreach(
fun(I) when is_binary(I) ->
From ! {tcp, State#state.socket, I};
(I2) ->
From ! {tcp, State#state.socket, [I2]}
end,
Input),
State#state{active = false, input = []}
end,
{next_state, StateName, State1#state{c2s_pid = From}};
handle_event({new_shaper, Shaper}, StateName, #state{ws = {_, WsPid}} = StateData) ->
WsPid ! {new_shaper, Shaper},
{next_state, StateName, StateData}.
handle_sync_event({send_xml, Packet}, _From, StateName,
handle_sync_event({send_xml, Packet},
_From,
StateName,
#state{ws = {_, WsPid}} = StateData) ->
SN2 = case Packet of
{xmlstreamstart, _, Attrs} ->
Attrs2 = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} |
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
route_el(WsPid, #xmlel{name = <<"open">>, attrs = Attrs2}),
StateName;
{xmlstreamend, _} ->
route_el(WsPid, #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]}),
stream_end_sent;
{xmlstreamraw, <<"\r\n\r\n">>} ->
% cdata ping
StateName;
{xmlstreamelement, #xmlel{name = Name2} = El2} ->
El3 = case Name2 of
<<"stream:", _/binary>> ->
fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
_ ->
case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
<<"">> ->
fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
_ ->
El2
end
end,
route_el(WsPid, El3),
StateName
end,
{xmlstreamstart, _, Attrs} ->
Attrs2 = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} | lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
route_el(WsPid, #xmlel{name = <<"open">>, attrs = Attrs2}),
StateName;
{xmlstreamend, _} ->
route_el(WsPid,
#xmlel{
name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]
}),
stream_end_sent;
{xmlstreamraw, <<"\r\n\r\n">>} ->
% cdata ping
StateName;
{xmlstreamelement, #xmlel{name = Name2} = El2} ->
El3 = case Name2 of
<<"stream:", _/binary>> ->
fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
_ ->
case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
<<"">> ->
fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
_ ->
El2
end
end,
route_el(WsPid, El3),
StateName
end,
{reply, ok, SN2, StateData};
handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}} = StateData)
when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
when StateName /= stream_end_sent ->
Close = #xmlel{
name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]
},
route_text(WsPid, fxml:element_to_binary(Close)),
{stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
handle_info(closed, _StateName, StateData) ->
{stop, normal, StateData};
handle_info({received, Packet}, StateName, StateDataI) ->
@ -222,84 +273,116 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
PingPong == pong ->
StateData2 = setup_timers(StateData),
{next_state, StateName,
StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _},
_StateName,
#state{timer = Timer} = StateData) ->
?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
{stop, normal, StateData};
handle_info({timeout, Timer, _}, StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
handle_info({timeout, Timer, _},
StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
case StateData#state.pong_expected of
false ->
misc:cancel_timer(StateData#state.ping_timer),
PingTimer = erlang:start_timer(StateData#state.ping_interval,
self(), []),
self(),
[]),
WsPid ! {ping, <<>>},
{next_state, StateName,
StateData#state{ping_timer = PingTimer, pong_expected = true}};
StateData#state{ping_timer = PingTimer, pong_expected = true}};
true ->
?DEBUG("Closing websocket connection from missing pongs", []),
?DEBUG("Closing websocket connection from missing pongs", []),
{stop, normal, StateData}
end;
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
terminate(_Reason, _StateName, StateData) ->
StateData#state.c2s_pid ! {tcp_closed, StateData#state.socket}.
setup_timers(StateData) ->
misc:cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(StateData#state.timeout,
self(), []),
self(),
[]),
misc:cancel_timer(StateData#state.ping_timer),
PingTimer = case StateData#state.ping_interval of
0 -> StateData#state.ping_timer;
V -> erlang:start_timer(V, self(), [])
end,
StateData#state{timer = Timer, ping_timer = PingTimer,
pong_expected = false}.
StateData#state{
timer = Timer,
ping_timer = PingTimer,
pong_expected = false
}.
get_human_html_xmlel() ->
Heading = <<"ejabberd ", (misc:atom_to_binary(?MODULE))/binary>>,
#xmlel{name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
children =
[#xmlel{name = <<"head">>, attrs = [],
children =
[#xmlel{name = <<"title">>, attrs = [],
children = [{xmlcdata, Heading}]}]},
#xmlel{name = <<"body">>, attrs = [],
children =
[#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, Heading}]},
#xmlel{name = <<"p">>, attrs = [],
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.">>}]}]}]}.
#xmlel{
name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
children =
[#xmlel{
name = <<"head">>,
attrs = [],
children =
[#xmlel{
name = <<"title">>,
attrs = [],
children = [{xmlcdata, Heading}]
}]
},
#xmlel{
name = <<"body">>,
attrs = [],
children =
[#xmlel{
name = <<"h1">>,
attrs = [],
children = [{xmlcdata, Heading}]
},
#xmlel{
name = <<"p">>,
attrs = [],
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) ->
El = fxml_stream:parse_element(Data),
case El of
#xmlel{name = <<"open">>, attrs = Attrs} ->
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} |
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} | lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
{State, [{xmlstreamstart, <<"stream:stream">>, Attrs2}]};
#xmlel{name = <<"close">>} ->
{State, [{xmlstreamend, <<"stream:stream">>}]};
@ -309,6 +392,7 @@ parse(State, Data) ->
{State, [El]}
end.
-spec route_text(pid(), binary()) -> ok.
route_text(Pid, Data) ->
Pid ! {text_with_reply, Data, self()},
@ -317,6 +401,7 @@ route_text(Pid, Data) ->
ok
end.
-spec route_el(pid(), xmlel() | cdata()) -> ok.
route_el(Pid, Data) ->
route_text(Pid, fxml:element_to_binary(Data)).

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,22 +25,24 @@
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
read_file(File) ->
case consult(File) of
{ok, Terms1} ->
?INFO_MSG("Converting from old configuration format", []),
Terms2 = strings_to_binary(Terms1),
Terms3 = transform(Terms2),
Terms4 = transform_certfiles(Terms3),
Terms5 = transform_host_config(Terms4),
{ok, collect_options(Terms5)};
{error, Reason} ->
{error, {old_config, File, Reason}}
{ok, Terms1} ->
?INFO_MSG("Converting from old configuration format", []),
Terms2 = strings_to_binary(Terms1),
Terms3 = transform(Terms2),
Terms4 = transform_certfiles(Terms3),
Terms5 = transform_host_config(Terms4),
{ok, collect_options(Terms5)};
{error, Reason} ->
{error, {old_config, File, Reason}}
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -52,10 +54,13 @@ collect_options(Opts) ->
({K, V}, {D, Os}) ->
{orddict:store(K, V, D), Os};
(Opt, {D, Os}) ->
{D, [Opt|Os]}
end, {orddict:new(), []}, Opts),
{D, [Opt | Os]}
end,
{orddict:new(), []},
Opts),
InvalidOpts ++ orddict:to_list(D).
transform(Opts) ->
Opts1 = transform_register(Opts),
Opts2 = transform_s2s(Opts1),
@ -68,6 +73,7 @@ transform(Opts) ->
Opts10 = transform_globals(Opts9),
collect_options(Opts10).
%%%===================================================================
%%% mod_register
%%%===================================================================
@ -80,7 +86,8 @@ transform_register(Opts) ->
?WARNING_MSG("Old 'ip_access' format detected. "
"The old format is still supported "
"but it is better to fix your config: "
"use access rules instead.", []),
"use access rules instead.",
[]),
ACLs = lists:flatmap(
fun({Action, S}) ->
ACLName = misc:binary_to_atom(
@ -88,43 +95,46 @@ transform_register(Opts) ->
["ip_", S])),
[{Action, ACLName},
{acl, ACLName, {ip, S}}]
end, L),
end,
L),
Access = {access, mod_register_networks,
[{Action, ACLName} || {Action, ACLName} <- ACLs]},
[ACL || {acl, _, _} = ACL <- ACLs] ++
[Access,
{modules,
[{mod_register,
[{ip_access, mod_register_networks}|RegOpts1]}
| ModOpts1]}|Opts1]
catch error:{badmatch, false} ->
[ {Action, ACLName} || {Action, ACLName} <- ACLs ]},
[ ACL || {acl, _, _} = ACL <- ACLs ] ++
[Access,
{modules,
[{mod_register,
[{ip_access, mod_register_networks} | RegOpts1]} | ModOpts1]} | Opts1]
catch
error:{badmatch, false} ->
Opts
end.
%%%===================================================================
%%% ejabberd_s2s
%%%===================================================================
transform_s2s(Opts) ->
lists:foldl(fun transform_s2s/2, [], Opts).
transform_s2s({{s2s_host, Host}, Action}, Opts) ->
?WARNING_MSG("Option 's2s_host' is deprecated.", []),
ACLName = misc:binary_to_atom(
iolist_to_binary(["s2s_access_", Host])),
[{acl, ACLName, {server, Host}},
{access, s2s, [{Action, ACLName}]},
{s2s_access, s2s} |
Opts];
{s2s_access, s2s} | Opts];
transform_s2s({s2s_default_policy, Action}, Opts) ->
?WARNING_MSG("Option 's2s_default_policy' is deprecated. "
"The option is still supported but it is better to "
"fix your config: "
"use 's2s_access' with an access rule.", []),
"use 's2s_access' with an access rule.",
[]),
[{access, s2s, [{Action, all}]},
{s2s_access, s2s} |
Opts];
{s2s_access, s2s} | Opts];
transform_s2s(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
%%%===================================================================
%%% ejabberd_s2s_out
@ -132,31 +142,36 @@ transform_s2s(Opt, Opts) ->
transform_s2s_out(Opts) ->
lists:foldl(fun transform_s2s_out/2, [], Opts).
transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) ->
?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 'outgoing_s2s_timeout' and "
"'outgoing_s2s_families' instead.", []),
"'outgoing_s2s_families' instead.",
[]),
[{outgoing_s2s_families, Families},
{outgoing_s2s_timeout, Timeout}
| Opts];
{outgoing_s2s_timeout, Timeout} | Opts];
transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) ->
?WARNING_MSG("Option 's2s_dns_options' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 's2s_dns_timeout' and "
"'s2s_dns_retries' instead", []),
"'s2s_dns_retries' instead",
[]),
lists:foldr(
fun({timeout, T}, AccOpts) ->
[{s2s_dns_timeout, T}|AccOpts];
[{s2s_dns_timeout, T} | AccOpts];
({retries, R}, AccOpts) ->
[{s2s_dns_retries, R}|AccOpts];
[{s2s_dns_retries, R} | AccOpts];
(_, AccOpts) ->
AccOpts
end, AllOpts, S2SDNSOpts);
end,
AllOpts,
S2SDNSOpts);
transform_s2s_out(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
%%%===================================================================
%%% ejabberd_listener
@ -164,33 +179,41 @@ transform_s2s_out(Opt, Opts) ->
transform_listeners(Opts) ->
lists:foldl(fun transform_listeners/2, [], Opts).
transform_listeners({listen, LOpts}, Opts) ->
[{listen, lists:map(fun transform_listener/1, LOpts)} | Opts];
transform_listeners(Opt, Opts) ->
[Opt|Opts].
[Opt | 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));
true ->
true ->
IP
end,
Opts1 = lists:map(
fun({ip, IPT}) when is_tuple(IPT) ->
{ip, list_to_binary(inet_parse:ntoa(IP))};
(ssl) -> {tls, true};
(A) when is_atom(A) -> {A, true};
(A) when is_atom(A) -> {A, true};
(Opt) -> Opt
end, Opts),
end,
Opts),
Opts2 = lists:foldl(
fun(Opt, Acc) ->
transform_listen_option(Mod, Opt, Acc)
end, [], Opts1),
TransportOpt = if Transport == tcp -> [];
true -> [{transport, Transport}]
transform_listen_option(Mod, Opt, Acc)
end,
[],
Opts1),
TransportOpt = if
Transport == tcp -> [];
true -> [{transport, Transport}]
end,
IPOpt = if IPStr == <<"0.0.0.0">> -> [];
true -> [{ip, IPStr}]
IPOpt = if
IPStr == <<"0.0.0.0">> -> [];
true -> [{ip, IPStr}]
end,
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
transform_listener({{Port, Transport}, Mod, Opts})
@ -203,24 +226,26 @@ transform_listener({Port, Mod, Opts}) ->
transform_listener(Opt) ->
Opt.
transform_listen_option(ejabberd_http, captcha, Opts) ->
[{captcha, true}|Opts];
[{captcha, true} | Opts];
transform_listen_option(ejabberd_http, register, Opts) ->
[{register, true}|Opts];
[{register, true} | 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) ->
[{http_bind, true}|Opts];
[{http_bind, true} | 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) ->
Hs1 = lists:map(
fun({PList, Mod}) when is_list(PList) ->
Path = iolist_to_binary([[$/, P] || P <- PList]),
Path = iolist_to_binary([ [$/, P] || P <- PList ]),
{Path, Mod};
(Opt) ->
Opt
end, Hs),
end,
Hs),
[{request_handlers, Hs1} | Opts];
transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) ->
case lists:keyfind(hosts, 1, Opts) of
@ -229,11 +254,12 @@ transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) ->
lists:foldl(
fun(H, Acc) ->
dict:append_list(H, O, Acc)
end, dict:from_list(PrevHostOpts), Hosts),
[{hosts, dict:to_list(NewHostOpts)}|
lists:keydelete(hosts, 1, Opts)];
end,
dict:from_list(PrevHostOpts),
Hosts),
[{hosts, dict:to_list(NewHostOpts)} | lists:keydelete(hosts, 1, Opts)];
_ ->
[{hosts, [{H, O} || H <- Hosts]}|Opts]
[{hosts, [ {H, O} || H <- Hosts ]} | Opts]
end;
transform_listen_option(ejabberd_service, {host, 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}]};
(Opt) ->
Opt
end, ACOpts),
[{access_commands, NewACOpts}|Opts];
end,
ACOpts),
[{access_commands, NewACOpts} | Opts];
transform_listen_option(_, Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
-spec all_zero_ip([proplists:property()]) -> inet:ip_address().
all_zero_ip(Opts) ->
case proplists:get_bool(inet6, Opts) of
true -> {0,0,0,0,0,0,0,0};
false -> {0,0,0,0}
true -> {0, 0, 0, 0, 0, 0, 0, 0};
false -> {0, 0, 0, 0}
end.
%%%===================================================================
%%% ejabberd_shaper
%%%===================================================================
transform_shaper(Opts) ->
lists:foldl(fun transform_shaper/2, [], Opts).
transform_shaper({shaper, Name, {maxrate, N}}, Opts) ->
[{shaper, [{Name, N}]} | Opts];
transform_shaper({shaper, Name, none}, Opts) ->
[{shaper, [{Name, none}]} | Opts];
transform_shaper({shaper, List}, Opts) when is_list(List) ->
R = lists:map(
fun({Name, Args}) when is_list(Args) ->
MaxRate = proplists:get_value(rate, Args, 1000),
BurstSize = proplists:get_value(burst_size, Args, MaxRate),
{Name, MaxRate, BurstSize};
({Name, Val}) ->
{Name, Val, Val}
end, List),
fun({Name, Args}) when is_list(Args) ->
MaxRate = proplists:get_value(rate, Args, 1000),
BurstSize = proplists:get_value(burst_size, Args, MaxRate),
{Name, MaxRate, BurstSize};
({Name, Val}) ->
{Name, Val, Val}
end,
List),
[{shaper, R} | Opts];
transform_shaper(Opt, Opts) ->
[Opt | Opts].
%%%===================================================================
%%% acl
%%%===================================================================
@ -287,26 +319,34 @@ transform_acl(Opts) ->
fun({acl, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts1),
{[], [O | Acc]}
end,
[],
Opts1),
{AccessOpts, Opts3} = lists:mapfoldl(
fun({access, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts2),
{[], [O | Acc]}
end,
[],
Opts2),
{NewAccessOpts, Opts4} = lists:mapfoldl(
fun({access_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts3),
fun({access_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O | Acc]}
end,
[],
Opts3),
{ShaperOpts, Opts5} = lists:mapfoldl(
fun({shaper_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts4),
{[], [O | Acc]}
end,
[],
Opts4),
ACLOpts1 = collect_options(lists:flatten(ACLOpts)),
AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of
[] -> [];
@ -315,26 +355,30 @@ transform_acl(Opts) ->
ACLOpts2 = case lists:map(
fun({ACLName, Os}) ->
{ACLName, collect_options(Os)}
end, ACLOpts1) of
end,
ACLOpts1) of
[] -> [];
L2 -> [{acl, L2}]
end,
NewAccessOpts1 = case lists:map(
fun({NAName, Os}) ->
{NAName, transform_access_rules_config(Os)}
end, lists:flatten(NewAccessOpts)) of
[] -> [];
L3 -> [{access_rules, L3}]
end,
fun({NAName, Os}) ->
{NAName, transform_access_rules_config(Os)}
end,
lists:flatten(NewAccessOpts)) of
[] -> [];
L3 -> [{access_rules, L3}]
end,
ShaperOpts1 = case lists:map(
fun({SName, Ss}) ->
{SName, transform_access_rules_config(Ss)}
end, lists:flatten(ShaperOpts)) of
[] -> [];
L4 -> [{shaper_rules, L4}]
end,
fun({SName, Ss}) ->
{SName, transform_access_rules_config(Ss)}
end,
lists:flatten(ShaperOpts)) of
[] -> [];
L4 -> [{shaper_rules, L4}]
end,
ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5.
transform_acl({acl, Name, Type}, Opts) ->
T = case Type of
all -> all;
@ -357,59 +401,66 @@ transform_acl({acl, Name, Type}, Opts) ->
{resource_glob, R} -> {resource_glob, [b(R)]};
{resource_regexp, R} -> {resource_regexp, [b(R)]}
end,
[{acl, [{Name, [T]}]}|Opts];
[{acl, [{Name, [T]}]} | Opts];
transform_acl({access, Name, Rules}, Opts) ->
NewRules = [{ACL, Action} || {Action, ACL} <- Rules],
[{access, [{Name, NewRules}]}|Opts];
NewRules = [ {ACL, Action} || {Action, ACL} <- Rules ],
[{access, [{Name, NewRules}]} | Opts];
transform_acl(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
transform_access_rules_config(Config) when is_list(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_config2(Type) when is_integer(Type); is_atom(Type) ->
{Type, [all]};
transform_access_rules_config2({Type, ACL}) when is_atom(ACL) ->
{Type, [{acl, ACL}]};
transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
T = lists:map(fun({Type, Args}) when is_list(Args) ->
{Type, hd(lists:flatten(Args))};
(V) ->
V
end, lists:flatten(Rules)),
{Type, hd(lists:flatten(Args))};
(V) ->
V
end,
lists:flatten(Rules)),
{Res, T};
transform_access_rules_config2({Res, Rule}) ->
{Res, [Rule]}.
%%%===================================================================
%%% SQL
%%%===================================================================
-define(PGSQL_PORT, 5432).
-define(MYSQL_PORT, 3306).
transform_sql(Opts) ->
lists:foldl(fun transform_sql/2, [], Opts).
transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
[{sql_type, Type},
{sql_server, Server},
{sql_port, Port},
{sql_database, DB},
{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, ?MYSQL_PORT, 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, {sqlite, DB}}, Opts) ->
[{sql_type, sqlite},
{sql_database, DB}|Opts];
{sql_database, DB} | Opts];
transform_sql({odbc_pool_size, N}, Opts) ->
[{sql_pool_size, N}|Opts];
[{sql_pool_size, N} | Opts];
transform_sql(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
%%%===================================================================
%%% modules
@ -417,15 +468,18 @@ transform_sql(Opt, Opts) ->
transform_modules(Opts) ->
lists:foldl(fun transform_modules/2, [], Opts).
transform_modules({modules, ModOpts}, Opts) ->
[{modules, lists:map(
fun({Mod, Opts1}) ->
{Mod, transform_module(Mod, Opts1)};
(Other) ->
Other
end, ModOpts)}|Opts];
fun({Mod, Opts1}) ->
{Mod, transform_module(Mod, Opts1)};
(Other) ->
Other
end,
ModOpts)} | Opts];
transform_modules(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
transform_module(mod_disco, Opts) ->
lists:map(
@ -437,18 +491,21 @@ transform_module(mod_disco, Opts) ->
{urls, URLs}]];
(Opt) ->
Opt
end, Infos),
end,
Infos),
{server_info, NewInfos};
(Opt) ->
Opt
end, Opts);
end,
Opts);
transform_module(mod_muc_log, Opts) ->
lists:map(
fun({top_link, {S1, S2}}) ->
{top_link, [{S1, S2}]};
(Opt) ->
Opt
end, Opts);
end,
Opts);
transform_module(mod_proxy65, Opts) ->
lists:map(
fun({ip, IP}) when is_tuple(IP) ->
@ -457,17 +514,20 @@ transform_module(mod_proxy65, Opts) ->
{hostname, misc:ip_to_list(IP)};
(Opt) ->
Opt
end, Opts);
end,
Opts);
transform_module(mod_register, Opts) ->
lists:flatmap(
fun({welcome_message, {Subj, Body}}) ->
[{welcome_message, [{subject, Subj}, {body, Body}]}];
(Opt) ->
[Opt]
end, Opts);
end,
Opts);
transform_module(_Mod, Opts) ->
Opts.
%%%===================================================================
%%% Host config
%%%===================================================================
@ -477,42 +537,50 @@ transform_host_config(Opts) ->
fun({host_config, O}, Os) ->
{[O], Os};
(O, Os) ->
{[], [O|Os]}
end, [], Opts1),
{[], [O | Os]}
end,
[],
Opts1),
{AHOpts, Opts3} = lists:mapfoldl(
fun({append_host_config, O}, Os) ->
{[O], Os};
(O, Os) ->
{[], [O|Os]}
end, [], Opts2),
{[], [O | Os]}
end,
[],
Opts2),
HOpts1 = case collect_options(lists:flatten(HOpts)) of
[] ->
[];
HOs ->
[{host_config,
[{H, transform(O)} || {H, O} <- HOs]}]
[ {H, transform(O)} || {H, O} <- HOs ]}]
end,
AHOpts1 = case collect_options(lists:flatten(AHOpts)) of
[] ->
[];
AHOs ->
[{append_host_config,
[{H, transform(O)} || {H, O} <- AHOs]}]
[ {H, transform(O)} || {H, O} <- AHOs ]}]
end,
HOpts1 ++ AHOpts1 ++ Opts3.
transform_host_config({host_config, Host, HOpts}, Opts) ->
{AddOpts, HOpts1} =
lists:mapfoldl(
fun({{add, Opt}, Val}, Os) ->
{[{Opt, Val}], Os};
(O, Os) ->
{[], [O|Os]}
end, [], HOpts),
{[], [O | Os]}
end,
[],
HOpts),
[{append_host_config, [{Host, lists:flatten(AddOpts)}]},
{host_config, [{Host, HOpts1}]}|Opts];
{host_config, [{Host, HOpts1}]} | Opts];
transform_host_config(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
%%%===================================================================
%%% Top-level options
@ -520,20 +588,22 @@ transform_host_config(Opt, Opts) ->
transform_globals(Opts) ->
lists:foldl(fun transform_globals/2, [], Opts).
transform_globals(Opt, Opts) when Opt == override_global;
Opt == override_local;
Opt == override_acls ->
Opt == override_local;
Opt == override_acls ->
?WARNING_MSG("Option '~ts' has no effect anymore", [Opt]),
Opts;
transform_globals({node_start, _}, Opts) ->
?WARNING_MSG("Option 'node_start' has no effect anymore", []),
Opts;
transform_globals({iqdisc, {queues, N}}, Opts) ->
[{iqdisc, N}|Opts];
[{iqdisc, N} | Opts];
transform_globals({define_macro, Macro, Val}, Opts) ->
[{define_macro, [{Macro, Val}]}|Opts];
[{define_macro, [{Macro, Val}]} | Opts];
transform_globals(Opt, Opts) ->
[Opt|Opts].
[Opt | Opts].
%%%===================================================================
%%% Certfiles
@ -541,70 +611,79 @@ transform_globals(Opt, Opts) ->
transform_certfiles(Opts) ->
lists:foldl(fun transform_certfiles/2, [], 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) ->
[Opt|Opts].
[Opt | Opts].
%%%===================================================================
%%% Consult file
%%%===================================================================
consult(File) ->
case file:consult(File) of
{ok, Terms} ->
include_config_files(Terms);
Err ->
Err
{ok, Terms} ->
include_config_files(Terms);
Err ->
Err
end.
include_config_files(Terms) ->
include_config_files(Terms, []).
include_config_files([], 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, Options} | Terms], Res) ->
case consult(Filename) of
{ok, Included_terms} ->
Disallow = proplists:get_value(disallow, Options, []),
Included_terms2 = delete_disallowed(Disallow, Included_terms),
Allow_only = proplists:get_value(allow_only, Options, all),
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
include_config_files(Terms, Res ++ Included_terms3);
Err ->
Err
{ok, Included_terms} ->
Disallow = proplists:get_value(disallow, Options, []),
Included_terms2 = delete_disallowed(Disallow, Included_terms),
Allow_only = proplists:get_value(allow_only, Options, all),
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
include_config_files(Terms, Res ++ Included_terms3);
Err ->
Err
end;
include_config_files([Term | Terms], Res) ->
include_config_files(Terms, Res ++ [Term]).
delete_disallowed(Disallowed, Terms) ->
lists:foldl(
fun(Dis, Ldis) ->
delete_disallowed2(Dis, Ldis)
delete_disallowed2(Dis, Ldis)
end,
Terms,
Disallowed).
delete_disallowed2(Disallowed, [H|T]) ->
delete_disallowed2(Disallowed, [H | T]) ->
case element(1, H) of
Disallowed ->
delete_disallowed2(Disallowed, T);
_ ->
[H|delete_disallowed2(Disallowed, T)]
Disallowed ->
delete_disallowed2(Disallowed, T);
_ ->
[H | delete_disallowed2(Disallowed, T)]
end;
delete_disallowed2(_, []) ->
[].
keep_only_allowed(all, Terms) ->
Terms;
keep_only_allowed(Allowed, Terms) ->
{As, _NAs} = lists:partition(
fun(Term) ->
lists:member(element(1, Term), Allowed)
end, Terms),
fun(Term) ->
lists:member(element(1, Term), Allowed)
end,
Terms),
As.
%%%===================================================================
%%% Aux functions
%%%===================================================================
@ -617,27 +696,30 @@ strings_to_binary(L) when is_list(L) ->
false ->
strings_to_binary1(L)
end;
strings_to_binary({A, B, C, D}) when
is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
strings_to_binary({A, B, C, D})
when is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
{A, B, C, D};
strings_to_binary(T) when is_tuple(T) ->
list_to_tuple(strings_to_binary1(tuple_to_list(T)));
strings_to_binary(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(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([]) ->
true;
is_string(_) ->
false.
b(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").
%%%===================================================================
%%% API
%%%===================================================================
@ -39,10 +40,12 @@ opt_type(acl) ->
acl:validator(acl);
opt_type(acme) ->
econf:options(
#{ca_url => econf:url(),
contact => econf:list_or_single(econf:binary("^[a-zA-Z]+:[^:]+$")),
auto => econf:bool(),
cert_type => econf:enum([ec, rsa])},
#{
ca_url => econf:url(),
contact => econf:list_or_single(econf:binary("^[a-zA-Z]+:[^:]+$")),
auto => econf:bool(),
cert_type => econf:enum([ec, rsa])
},
[unique, {return, map}]);
opt_type(allow_contrib_modules) ->
econf:bool();
@ -75,7 +78,8 @@ opt_type(auth_opts) ->
{basic_auth, V};
({path_prefix, V}) when is_binary(V) ->
{path_prefix, V}
end, L)
end,
L)
end;
opt_type(auth_stored_password_types) ->
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) ->
econf:list_or_single(
econf:and_then(
econf:binary(),
fun str:to_upper/1));
econf:binary(),
fun str:to_upper/1));
opt_type(domain_balancing) ->
econf:map(
econf:domain(),
econf:options(
#{component_number => econf:int(2, 1000),
type => econf:enum([random, source, destination,
bare_source, bare_destination])},
[{return, map}, unique]),
#{
component_number => econf:int(2, 1000),
type => econf:enum([random, source, destination,
bare_source, bare_destination])
},
[{return, map}, unique]),
[{return, map}]);
opt_type(ext_api_path_oauth) ->
econf:binary();
@ -179,7 +185,7 @@ opt_type(host_config) ->
econf:map(econf:domain(), econf:list(econf:any())),
fun econf:group_dups/1),
econf:map(
econf:enum(ejabberd_config:get_option(hosts)),
econf:enum(ejabberd_config:get_option(hosts)),
validator(),
[unique]));
opt_type(hosts) ->
@ -206,9 +212,9 @@ opt_type(ldap_deref_aliases) ->
opt_type(ldap_dn_filter) ->
econf:and_then(
econf:non_empty(
econf:map(
econf:ldap_filter(),
econf:list(econf:binary()))),
econf:map(
econf:ldap_filter(),
econf:list(econf:binary()))),
fun hd/1);
opt_type(ldap_encrypt) ->
econf:enum([tls, starttls, none]);
@ -233,9 +239,9 @@ opt_type(ldap_tls_verify) ->
opt_type(ldap_uids) ->
econf:either(
econf:list(
econf:and_then(
econf:binary(),
fun(U) -> {U, <<"%u">>} end)),
econf:and_then(
econf:binary(),
fun(U) -> {U, <<"%u">>} end)),
econf:map(econf:binary(), econf:binary(), [unique]));
opt_type(listen) ->
ejabberd_listener:validator();
@ -251,12 +257,12 @@ opt_type(log_modules_fully) ->
econf:list(econf:atom());
opt_type(loglevel) ->
fun(N) when is_integer(N) ->
(econf:and_then(
econf:int(0, 5),
fun ejabberd_logger:convert_loglevel/1))(N);
(econf:and_then(
econf:int(0, 5),
fun ejabberd_logger:convert_loglevel/1))(N);
(Level) ->
(econf:enum([none, emergency, alert, critical,
error, warning, notice, info, debug]))(Level)
(econf:enum([none, emergency, alert, critical,
error, warning, notice, info, debug]))(Level)
end;
opt_type(max_fsm_queue) ->
econf:pos_int();
@ -299,12 +305,13 @@ opt_type(oom_watermark) ->
opt_type(outgoing_s2s_families) ->
econf:and_then(
econf:non_empty(
econf:list(econf:enum([ipv4, ipv6]), [unique])),
econf:list(econf:enum([ipv4, ipv6]), [unique])),
fun(L) ->
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end, L)
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end,
L)
end);
opt_type(outgoing_s2s_ipv4_address) ->
econf:ipv4();
@ -392,9 +399,9 @@ opt_type(s2s_zlib) ->
econf:and_then(
econf:bool(),
fun(false) -> false;
(true) ->
ejabberd:start_app(ezlib),
true
(true) ->
ejabberd:start_app(ezlib),
true
end);
opt_type(shaper) ->
ejabberd_shaper:validator(shaper);
@ -459,10 +466,10 @@ opt_type(version) ->
opt_type(websocket_origin) ->
econf:list(
econf:and_then(
econf:and_then(
econf:binary_sep("\\s+"),
econf:list(econf:url(), [unique])),
fun(L) -> str:join(L, <<" ">>) end),
econf:and_then(
econf:binary_sep("\\s+"),
econf:list(econf:url(), [unique])),
fun(L) -> str:join(L, <<" ">>) end),
[unique]);
opt_type(websocket_ping_interval) ->
econf:timeout(second);
@ -474,22 +481,23 @@ opt_type(jwt_key) ->
fun(Path) ->
case file:read_file(Path) of
{ok, Data} ->
try jose_jwk:from_binary(Data) of
{error, _} -> econf:fail({bad_jwt_key, Path});
JWK ->
try jose_jwk:from_binary(Data) of
{error, _} -> econf:fail({bad_jwt_key, Path});
JWK ->
case jose_jwk:to_map(JWK) of
{_, #{<<"keys">> := [Key]}} ->
jose_jwk:from_map(Key);
{_, #{<<"keys">> := [_|_]}} ->
{_, #{<<"keys">> := [_ | _]}} ->
econf:fail({bad_jwt_key_set, Path});
{_, #{<<"keys">> := _}} ->
econf:fail({bad_jwt_key, Path});
{_, #{<<"keys">> := _}} ->
econf:fail({bad_jwt_key, Path});
_ ->
JWK
end
catch _:_ ->
econf:fail({bad_jwt_key, Path})
end;
catch
_:_ ->
econf:fail({bad_jwt_key, Path})
end;
{error, Reason} ->
econf:fail({read_file, Reason, Path})
end
@ -499,36 +507,37 @@ opt_type(jwt_jid_field) ->
opt_type(jwt_auth_only_rule) ->
econf:atom().
%% We only define the types of options that cannot be derived
%% automatically by tools/opt_type.sh script
-spec options() -> [{s2s_protocol_options, undefined | binary()} |
{c2s_protocol_options, undefined | binary()} |
{c2s_protocol_options, undefined | binary()} |
{s2s_ciphers, undefined | binary()} |
{c2s_ciphers, undefined | binary()} |
{websocket_origin, [binary()]} |
{disable_sasl_mechanisms, [binary()]} |
{s2s_zlib, boolean()} |
{loglevel, ejabberd_logger:loglevel()} |
{auth_opts, [{any(), any()}]} |
{listen, [ejabberd_listener:listener()]} |
{modules, [{module(), gen_mod:opts(), integer()}]} |
{ldap_uids, [{binary(), binary()}]} |
{ldap_dn_filter, {binary(), [binary()]}} |
{outgoing_s2s_families, [inet | inet6, ...]} |
{acl, [{atom(), [acl:acl_rule()]}]} |
{access_rules, [{atom(), acl:access()}]} |
{shaper, #{atom() => ejabberd_shaper:shaper_rate()}} |
{shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} |
{api_permissions, [ejabberd_access_permissions:permission()]} |
{jwt_key, jose_jwk:key() | undefined} |
{append_host_config, [{binary(), any()}]} |
{host_config, [{binary(), any()}]} |
{define_keyword, any()} |
{define_macro, any()} |
{include_config_file, any()} |
{atom(), any()}].
{websocket_origin, [binary()]} |
{disable_sasl_mechanisms, [binary()]} |
{s2s_zlib, boolean()} |
{loglevel, ejabberd_logger:loglevel()} |
{auth_opts, [{any(), any()}]} |
{listen, [ejabberd_listener:listener()]} |
{modules, [{module(), gen_mod:opts(), integer()}]} |
{ldap_uids, [{binary(), binary()}]} |
{ldap_dn_filter, {binary(), [binary()]}} |
{outgoing_s2s_families, [inet | inet6, ...]} |
{acl, [{atom(), [acl:acl_rule()]}]} |
{access_rules, [{atom(), acl:access()}]} |
{shaper, #{atom() => ejabberd_shaper:shaper_rate()}} |
{shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} |
{api_permissions, [ejabberd_access_permissions:permission()]} |
{jwt_key, jose_jwk:key() | undefined} |
{append_host_config, [{binary(), any()}]} |
{host_config, [{binary(), any()}]} |
{define_keyword, any()} |
{define_macro, any()} |
{include_config_file, any()} |
{atom(), any()}].
options() ->
[%% Top-priority options
[ %% Top-priority options
hosts,
{loglevel, info},
{cache_life_time, timer:seconds(3600)},
@ -549,10 +558,10 @@ options() ->
{anonymous_protocol, sasl_anon},
{api_permissions,
[{<<"admin access">>,
{[],
[{acl, admin},
{oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}],
{all, [start, stop]}}}]},
{[],
[{acl, admin},
{oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}],
{all, [start, stop]}}}]},
{append_host_config, []},
{auth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
@ -610,10 +619,10 @@ options() ->
{ldap_password, <<"">>},
{ldap_port,
fun(Host) ->
case ejabberd_config:get_option({ldap_encrypt, Host}) of
tls -> 636;
_ -> 389
end
case ejabberd_config:get_option({ldap_encrypt, Host}) of
tls -> 636;
_ -> 389
end
end},
{ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]},
@ -624,7 +633,7 @@ options() ->
{ldap_uids, [{<<"uid">>, <<"%u">>}]},
{listen, []},
{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_count, 500},
{log_modules_fully, []},
@ -717,22 +726,22 @@ options() ->
{sql_database, undefined},
{sql_keepalive_interval, undefined},
{sql_password, <<"">>},
{sql_odbc_driver, <<"libtdsodbc.so">>}, % default is FreeTDS driver
{sql_odbc_driver, <<"libtdsodbc.so">>}, % default is FreeTDS driver
{sql_pool_size,
fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of
sqlite -> 1;
_ -> 10
end
case ejabberd_config:get_option({sql_type, Host}) of
sqlite -> 1;
_ -> 10
end
end},
{sql_port,
fun(Host) ->
case ejabberd_config:get_option({sql_type, Host}) of
mssql -> 1433;
mysql -> 3306;
pgsql -> 5432;
_ -> undefined
end
case ejabberd_config:get_option({sql_type, Host}) of
mssql -> 1433;
mysql -> 3306;
pgsql -> 5432;
_ -> undefined
end
end},
{sql_query_timeout, timer:seconds(60)},
{sql_queue_type,
@ -755,6 +764,7 @@ options() ->
{jwt_jid_field, <<"jid">>},
{jwt_auth_only_rule, none}].
-spec globals() -> [atom()].
globals() ->
[acme,
@ -829,9 +839,11 @@ globals() ->
websocket_ping_interval,
websocket_timeout].
doc() ->
ejabberd_options_doc:doc().
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -845,21 +857,23 @@ validator() ->
Validators,
[{disallowed, Required ++ Disallowed}, unique])).
-spec fqdn(global | binary()) -> [binary()].
fqdn(global) ->
{ok, Hostname} = inet:gethostname(),
case inet:gethostbyname(Hostname) of
{ok, #hostent{h_name = FQDN}} ->
case jid:nameprep(iolist_to_binary(FQDN)) of
error -> [];
Domain -> [Domain]
end;
{error, _} ->
[]
{ok, #hostent{h_name = FQDN}} ->
case jid:nameprep(iolist_to_binary(FQDN)) of
error -> [];
Domain -> [Domain]
end;
{error, _} ->
[]
end;
fqdn(_) ->
ejabberd_config:get_option(fqdn).
-spec concat_binary(char()) -> fun(([binary()]) -> binary()).
concat_binary(C) ->
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]).
-define(CHUNK_SIZE, 1024*20). %20k
-define(CHUNK_SIZE, 1024 * 20). %20k
-include_lib("xmpp/include/scram.hrl").
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("mod_privacy.hrl").
-include("mod_roster.hrl").
@ -52,9 +55,13 @@
%%-define(INFO_MSG(M,Args),ok).
%%%==================================
%%%% 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_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,
user = <<"">> :: binary(),
@ -62,8 +69,13 @@
fd = self() :: file:io_device(),
dir = <<"">> :: binary()}).
%% @indent-end
%% @efmt:on
%%
-type state() :: #state{}.
%%File could be large.. we read it in chunks
%%%===================================================================
%%% API
@ -71,31 +83,37 @@
import_file(FileName) ->
import_file(FileName, #state{}).
-spec import_file(binary(), state()) -> ok | {error, atom()}.
import_file(FileName, State) ->
case file:open(FileName, [read, binary]) of
{ok, Fd} ->
{ok, Fd} ->
Dir = filename:dirname(FileName),
XMLStreamState = fxml_stream:new(self(), infinity),
Res = process(State#state{xml_stream_state = XMLStreamState,
fd = Fd,
dir = Dir}),
Res = process(State#state{
xml_stream_state = XMLStreamState,
fd = Fd,
dir = Dir
}),
file:close(Fd),
Res;
{error, Reason} ->
{error, Reason} ->
ErrTxt = file:format_error(Reason),
?ERROR_MSG("Failed to open file '~ts': ~ts", [FileName, ErrTxt]),
{error, Reason}
end.
-spec export_server(binary()) -> any().
export_server(Dir) ->
export_hosts(ejabberd_option:hosts(), Dir).
-spec export_host(binary(), binary()) -> any().
export_host(Dir, Host) ->
export_hosts([Host], Dir).
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -106,12 +124,13 @@ export_hosts(Hosts, Dir) ->
{ok, Fd} ->
print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_server_head()),
FilesAndHosts = [{make_host_filename(FnT, Host), Host}
|| Host <- Hosts],
FilesAndHosts = [ {make_host_filename(FnT, Host), Host}
|| Host <- Hosts ],
lists:foreach(
fun({FnH, _}) ->
print(Fd, make_xinclude(FnH))
end, FilesAndHosts),
end,
FilesAndHosts),
print(Fd, make_piefxis_server_tail()),
print(Fd, make_piefxis_xml_tail()),
file:close(Fd),
@ -120,13 +139,16 @@ export_hosts(Hosts, Dir) ->
export_host(Dir, FnH, Host);
(_, Err) ->
Err
end, ok, FilesAndHosts);
end,
ok,
FilesAndHosts);
{error, Reason} ->
ErrTxt = file:format_error(Reason),
?ERROR_MSG("Failed to open file '~ts': ~ts", [DFn, ErrTxt]),
{error, Reason}
end.
export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH),
case file:open(DFn, [raw, write]) of
@ -151,7 +173,8 @@ export_host(Dir, FnH, Host) ->
{error, Reason}
end.
export_users([{User, _S}|Users], Server, Fd) ->
export_users([{User, _S} | Users], Server, Fd) ->
case export_user(User, Server, Fd) of
ok ->
export_users(Users, Server, Fd);
@ -161,14 +184,15 @@ export_users([{User, _S}|Users], Server, Fd) ->
export_users([], _Server, _Fd) ->
ok.
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
{PassPlain, PassScram} = case ejabberd_auth:password_format(LServer) of
scram -> {[], [format_scram_password(Password)]};
_ when Password == <<"">> -> {[], []};
_ -> {[{<<"password">>, Password}], []}
end,
scram -> {[], [format_scram_password(Password)]};
_ when Password == <<"">> -> {[], []};
_ -> {[{<<"password">>, Password}], []}
end,
Els =
PassScram ++
get_offline(User, Server) ++
@ -176,13 +200,22 @@ export_user(User, Server, Fd) ->
get_privacy(User, Server) ++
get_roster(User, Server) ++
get_private(User, Server),
print(Fd, fxml:element_to_binary(
#xmlel{name = <<"user">>,
attrs = [{<<"name">>, User} | PassPlain],
children = Els})).
print(Fd,
fxml:element_to_binary(
#xmlel{
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),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
@ -193,20 +226,29 @@ format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = Ser
sha512 -> <<"SCRAM-SHA-512">>
end,
Children =
[
#xmlel{name = <<"iter-count">>,
children = [{xmlcdata, IterationCountBin}]},
#xmlel{name = <<"salt">>,
children = [{xmlcdata, SaltB64}]},
#xmlel{name = <<"server-key">>,
children = [{xmlcdata, ServerKeyB64}]},
#xmlel{name = <<"stored-key">>,
children = [{xmlcdata, StoredKeyB64}]}
],
#xmlel{name = <<"scram-credentials">>,
attrs = [{<<"xmlns">>, <<?NS_PIE/binary, "#scram">>},
{<<"mechanism">>, MechanismB}],
children = Children}.
[#xmlel{
name = <<"iter-count">>,
children = [{xmlcdata, IterationCountBin}]
},
#xmlel{
name = <<"salt">>,
children = [{xmlcdata, SaltB64}]
},
#xmlel{
name = <<"server-key">>,
children = [{xmlcdata, ServerKeyB64}]
},
#xmlel{
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) ->
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]),
SaltB64 = fxml:get_path_s(El, [{elem, <<"salt">>}, cdata]),
#scram{
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
};
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
};
parse_scram_password(PassData) ->
Split = binary:split(PassData, <<",">>, [global]),
[Hash, StoredKeyB64, ServerKeyB64, SaltB64, IterationCountBin] =
case Split of
[K1, K2, K3, K4] -> [sha, K1, K2, K3, K4];
[<<"sha256">>, K1, K2, K3, K4] -> [sha256, K1, K2, K3, K4];
[<<"sha512">>, K1, K2, K3, K4] -> [sha512, K1, K2, K3, K4]
end,
#scram{
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
}.
Split = binary:split(PassData, <<",">>, [global]),
[Hash, StoredKeyB64, ServerKeyB64, SaltB64, IterationCountBin] =
case Split of
[K1, K2, K3, K4] -> [sha, K1, K2, K3, K4];
[<<"sha256">>, K1, K2, K3, K4] -> [sha256, K1, K2, K3, K4];
[<<"sha512">>, K1, K2, K3, K4] -> [sha512, K1, K2, K3, K4]
end,
#scram{
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
}.
-spec get_vcard(binary(), binary()) -> [xmlel()].
get_vcard(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
try mod_vcard:get_vcard(LUser, LServer) of
error -> [];
Els -> Els
error -> [];
Els -> Els
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_offline(binary(), binary()) -> [xmlel()].
get_offline(User, Server) ->
LUser = jid:nodeprep(User),
@ -267,76 +311,92 @@ get_offline(User, Server) ->
error:{module_not_loaded, _, _} -> []
end.
-spec get_privacy(binary(), binary()) -> [xmlel()].
get_privacy(User, Server) ->
try mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
lists = [_|_] = Lists}} ->
{ok, #privacy{
default = Default,
lists = [_ | _] = Lists
}} ->
XLists = lists:map(
fun({Name, Items}) ->
XItems = lists:map(
fun mod_privacy:encode_list_item/1,
Items),
#privacy_list{name = Name, items = XItems}
end, Lists),
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
fun mod_privacy:encode_list_item/1,
Items),
#privacy_list{name = Name, items = XItems}
end,
Lists),
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_roster(binary(), binary()) -> [xmlel()].
get_roster(User, Server) ->
JID = jid:make(User, Server),
try mod_roster:get_roster(User, Server) of
[_|_] = Items ->
[_ | _] = Items ->
Subs =
lists:flatmap(
fun(#roster{ask = Ask,
askmessage = Msg} = R)
fun(#roster{
ask = Ask,
askmessage = Msg
} = R)
when Ask == in; Ask == both ->
Status = if is_binary(Msg) -> (Msg);
true -> <<"">>
Status = if
is_binary(Msg) -> (Msg);
true -> <<"">>
end,
[xmpp:encode(
#presence{from = jid:make(R#roster.jid),
to = JID,
type = subscribe,
status = xmpp:mk_text(Status)})];
[xmpp:encode(
#presence{
from = jid:make(R#roster.jid),
to = JID,
type = subscribe,
status = xmpp:mk_text(Status)
})];
(_) ->
[]
end, Items),
end,
Items),
Rs = lists:flatmap(
fun(#roster{ask = in, subscription = none}) ->
[];
(R) ->
[mod_roster:encode_item(R)]
end, Items),
[xmpp:encode(#roster_query{items = Rs}) | Subs];
end,
Items),
[xmpp:encode(#roster_query{items = Rs}) | Subs];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_private(binary(), binary()) -> [xmlel()].
get_private(User, Server) ->
try mod_private:get_data(User, Server) of
[_|_] = Els ->
[xmpp:encode(#private{sub_els = Els})];
[_ | _] = Els ->
[xmpp:encode(#private{sub_els = Els})];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
case file:read(Fd, ?CHUNK_SIZE) of
{ok, Data} ->
NewXMLStreamState = fxml_stream:parse(XMLStreamState, Data),
case process_els(State#state{xml_stream_state =
NewXMLStreamState}) of
case process_els(State#state{
xml_stream_state =
NewXMLStreamState
}) of
{ok, NewState} ->
process(NewState);
Err ->
@ -348,17 +408,21 @@ process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
ok
end.
process_els(State) ->
Els = gather_els(State, []),
process_els(State, lists:reverse(Els)).
gather_els(State, List) ->
receive
{'$gen_event', El} ->
gather_els(State, [El | List])
after 0 ->
List
end.
after
0 ->
List
end.
process_els(State, [El | Tail]) ->
case process_el(El, State) of
@ -370,6 +434,7 @@ process_els(State, [El | Tail]) ->
process_els(State, []) ->
{ok, State}.
process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_PIEFXIS ->
@ -383,8 +448,10 @@ process_el({xmlstreamend, _}, State) ->
{ok, State};
process_el({xmlstreamcdata, _}, State) ->
{ok, State};
process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
attrs = Attrs}},
process_el({xmlstreamelement, #xmlel{
name = <<"xi:include">>,
attrs = Attrs
}},
#state{dir = Dir, user = <<"">>} = State) ->
FileName = fxml:get_attr_s(<<"href">>, Attrs),
case import_file(filename:join([Dir, FileName]), State) of
@ -394,11 +461,17 @@ process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
Err
end;
process_el({xmlstreamstart, <<"host">>, Attrs}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"host">>,
attrs = Attrs}}, State);
process_el({xmlstreamelement, #xmlel{name = <<"host">>,
attrs = Attrs,
children = Els}}, State) ->
process_el({xmlstreamelement, #xmlel{
name = <<"host">>,
attrs = Attrs
}},
State);
process_el({xmlstreamelement, #xmlel{
name = <<"host">>,
attrs = Attrs,
children = Els
}},
State) ->
JIDS = fxml:get_attr_s(<<"jid">>, Attrs),
try jid:decode(JIDS) of
#jid{lserver = S} ->
@ -408,7 +481,8 @@ process_el({xmlstreamelement, #xmlel{name = <<"host">>,
false ->
stop("Unknown host: ~ts", [S])
end
catch _:{bad_jid, _} ->
catch
_:{bad_jid, _} ->
stop("Invalid 'jid': ~ts", [JIDS])
end;
process_el({xmlstreamstart, <<"user">>, Attrs}, State = #state{server = S})
@ -428,18 +502,20 @@ process_el({xmlstreamstart, El, Attrs}, _State) ->
process_el({xmlstreamerror, Err}, _State) ->
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
{ok, NewState} ->
process_users(Els, NewState);
Err ->
Err
end;
process_users([_|Els], State) ->
process_users([_ | Els], State) ->
process_users(Els, State);
process_users([], State) ->
{ok, State}.
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El,
#state{server = LServer} = State) ->
Name = fxml:get_attr_s(<<"name">>, Attrs),
@ -458,11 +534,12 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El,
end
end.
process_password(#xmlel{name = <<"user">>, attrs = Attrs} = El, LServer) ->
{PassPlain, PassOldScram} = case fxml:get_attr_s(<<"password">>, Attrs) of
<<"scram:", PassData/binary>> -> {<<"">>, PassData};
P -> {P, false}
end,
<<"scram:", PassData/binary>> -> {<<"">>, PassData};
P -> {P, false}
end,
ScramCred = fxml:get_subtag(El, <<"scram-credentials">>),
PasswordFormat = ejabberd_auth:password_format(LServer),
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)
end.
process_user_els([#xmlel{} = El|Els], State) ->
process_user_els([#xmlel{} = El | Els], State) ->
case process_user_el(El, State) of
{ok, NewState} ->
process_user_els(Els, NewState);
Err ->
Err
end;
process_user_els([_|Els], State) ->
process_user_els([_ | Els], State) ->
process_user_els(Els, State);
process_user_els([], State) ->
{ok, State}.
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) ->
try
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVACY} ->
%% Make sure <list/> elements go before <active/> and <default/>
process_privacy(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVATE} ->
process_private(xmpp:decode(El), State);
{<<"vCard">>, ?NS_VCARD} ->
process_vcard(xmpp:decode(El), State);
{<<"offline-messages">>, NS} ->
Msgs = [xmpp:decode(E, NS, [ignore_els]) || E <- Els],
process_offline_msgs(Msgs, State);
{<<"presence">>, ?NS_CLIENT} ->
process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State);
_ ->
{ok, State}
end
catch _:{xmpp_codec, Why} ->
ErrTxt = xmpp:format_error(Why),
stop("failed to decode XML '~ts': ~ts",
[fxml:element_to_binary(El), ErrTxt])
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVACY} ->
%% Make sure <list/> elements go before <active/> and <default/>
process_privacy(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVATE} ->
process_private(xmpp:decode(El), State);
{<<"vCard">>, ?NS_VCARD} ->
process_vcard(xmpp:decode(El), State);
{<<"offline-messages">>, NS} ->
Msgs = [ xmpp:decode(E, NS, [ignore_els]) || E <- Els ],
process_offline_msgs(Msgs, State);
{<<"presence">>, ?NS_CLIENT} ->
process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State);
_ ->
{ok, State}
end
catch
_:{xmpp_codec, Why} ->
ErrTxt = xmpp:format_error(Why),
stop("failed to decode XML '~ts': ~ts",
[fxml:element_to_binary(El), ErrTxt])
end.
-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
{ok, NewState} ->
process_offline_msgs(Msgs, NewState);
Err ->
Err
end;
process_offline_msgs([_|Msgs], State) ->
process_offline_msgs([_ | Msgs], State) ->
process_offline_msgs(Msgs, State);
process_offline_msgs([], State) ->
{ok, State}.
-spec process_roster(roster_query(), state()) -> {ok, state()} | {error, _}.
process_roster(RosterQuery, State = #state{user = U, server = S}) ->
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])
end.
-spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}.
process_privacy(#privacy_query{lists = Lists,
default = Default,
active = Active},
State = #state{user = U, server = S}) ->
process_privacy(#privacy_query{
lists = Lists,
default = Default,
active = Active
},
State = #state{user = U, server = S}) ->
JID = jid:make(U, S),
if Lists /= undefined ->
process_privacy2(JID, #privacy_query{lists = Lists});
true ->
ok
if
Lists /= undefined ->
process_privacy2(JID, #privacy_query{lists = Lists});
true ->
ok
end,
if Active /= undefined ->
process_privacy2(JID, #privacy_query{active = Active});
true ->
ok
if
Active /= undefined ->
process_privacy2(JID, #privacy_query{active = Active});
true ->
ok
end,
if Default /= undefined ->
process_privacy2(JID, #privacy_query{default = Default});
true ->
ok
if
Default /= undefined ->
process_privacy2(JID, #privacy_query{default = Default});
true ->
ok
end,
{ok, State}.
process_privacy2(JID, PQ) ->
case mod_privacy:process_iq(#iq{type = set, id = p1_rand:get_string(),
from = JID, to = JID,
sub_els = [PQ]}) of
#iq{type = error} = ResIQ ->
#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]);
case mod_privacy:process_iq(#iq{
type = set,
id = p1_rand:get_string(),
from = JID,
to = JID,
sub_els = [PQ]
}) of
#iq{type = error} = ResIQ ->
#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 ->
ok
end;
_ ->
ok
end.
ok
end;
_ ->
ok
end.
-spec process_private(private(), state()) -> {ok, state()} | {error, _}.
process_private(Private, State = #state{user = U, server = S}) ->
JID = jid:make(U, S),
IQ = #iq{type = set, id = p1_rand:get_string(),
from = JID, to = JID, sub_els = [Private]},
IQ = #iq{
type = set,
id = p1_rand:get_string(),
from = JID,
to = JID,
sub_els = [Private]
},
case mod_private:process_sm_iq(IQ) of
#iq{type = result} ->
{ok, State};
@ -587,11 +687,17 @@ process_private(Private, State = #state{user = U, server = S}) ->
stop("Failed to write private: ~p", [Err])
end.
-spec process_vcard(xmpp_element(), state()) -> {ok, state()} | {error, _}.
process_vcard(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S),
IQ = #iq{type = set, id = p1_rand:get_string(),
from = JID, to = JID, sub_els = [El]},
IQ = #iq{
type = set,
id = p1_rand:get_string(),
from = JID,
to = JID,
sub_els = [El]
},
case mod_vcard:process_sm_iq(IQ) of
#iq{type = result} ->
{ok, State};
@ -599,6 +705,7 @@ process_vcard(El, State = #state{user = U, server = S}) ->
stop("Failed to write vcard: ~p", [Err])
end.
-spec process_offline_msg(message(), state()) -> {ok, state()} | {error, _}.
process_offline_msg(#message{from = undefined}, _State) ->
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)}, []),
{ok, State}.
-spec process_presence(presence(), state()) -> {ok, state()} | {error, _}.
process_presence(#presence{from = undefined}, _State) ->
stop("No 'from' attribute found", []);
@ -617,19 +725,23 @@ process_presence(Pres, #state{user = U, server = S} = State) ->
ejabberd_router:route(NewPres),
{ok, State}.
stop(Fmt, Args) ->
?ERROR_MSG(Fmt, Args),
{error, import_failed}.
make_filename_template() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
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) ->
Filename2 = <<FnT/binary, ".xml">>,
filename:join([Dir, Filename2]).
%% @doc Make the filename for the host.
%% Example: ``(<<"20080804-231550">>, <<"xmpp.domain.tld">>) ->
%% <<"20080804-231550_xmpp_domain_tld.xml">>''
@ -637,34 +749,43 @@ make_host_filename(FnT, Host) ->
Host2 = str:join(str:tokens(Host, <<".">>), <<"_">>),
<<FnT/binary, "_", Host2/binary, ".xml">>.
%%%==================================
%%%% PIEFXIS formatting
make_host_basefilename(Dir, FnT) ->
filename:join([Dir, FnT]).
make_piefxis_xml_head() ->
"<?xml version='1.0' encoding='UTF-8'?>".
make_piefxis_xml_tail() ->
"".
make_piefxis_server_head() ->
io_lib:format("<server-data xmlns='~ts' xmlns:xi='~ts'>",
[?NS_PIE, ?NS_XI]).
make_piefxis_server_tail() ->
"</server-data>".
make_piefxis_host_head(Host) ->
io_lib:format("<host xmlns='~ts' xmlns:xi='~ts' jid='~ts'>",
[?NS_PIE, ?NS_XI, Host]).
make_piefxis_host_tail() ->
"</host>".
make_xinclude(Fn) ->
Base = filename:basename(Fn),
io_lib:format("<xi:include href='~ts'/>", [Base]).
print(Fd, String) ->
file:write(Fd, String).

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -27,37 +27,44 @@
-export([run/2, split/2, replace/3, greplace/3, sh_to_awk/1]).
-spec run(binary(), binary()) -> match | nomatch | {error, any()}.
run(String, Regexp) ->
re:run(String, Regexp, [{capture, none}, unicode]).
-spec split(binary(), binary()) -> [binary()].
split(String, Regexp) ->
re:split(String, Regexp, [{return, binary}]).
-spec replace(binary(), binary(), binary()) -> binary().
replace(String, Regexp, New) ->
re:replace(String, Regexp, New, [{return, binary}]).
-spec greplace(binary(), binary(), binary()) -> binary().
greplace(String, Regexp, New) ->
re:replace(String, Regexp, New, [global, {return, binary}]).
%% This code was copied and adapted from xmerl_regexp.erl
-spec sh_to_awk(binary()) -> binary().
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/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/binary>>) -> %This takes careful handling
sh_to_awk_1(<<"[^]", Sh/binary>>) -> %This takes careful handling
[<<"\\^">>, sh_to_awk_1(Sh)];
%% Must move '^' to end.
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_1(<<"[", Sh/binary>>) ->
[$[, 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
true -> [$\\,C|sh_to_awk_1(Sh)];
false -> [C|sh_to_awk_1(Sh)]
true -> [$\\, C | sh_to_awk_1(Sh)];
false -> [C | sh_to_awk_1(Sh)]
end;
sh_to_awk_1(<<>>) ->
<<")$">>. %Fix the end
<<")$">>. %Fix the end
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_3(Sh, UpArrow).
sh_to_awk_3(<<"]", Sh/binary>>, true) ->
[<<"^]">>, sh_to_awk_1(Sh)];
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) ->
[C|sh_to_awk_3(Sh, UpArrow)];
[C | sh_to_awk_3(Sh, UpArrow)];
sh_to_awk_3(<<>>, true) ->
[$^|sh_to_awk_1(<<>>)];
[$^ | sh_to_awk_1(<<>>)];
sh_to_awk_3(<<>>, false) ->
sh_to_awk_1(<<>>).
%% Test if a character is a special character.
-spec sh_special_char(char()) -> boolean().
sh_special_char($|) -> true;

View file

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

View file

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

View file

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

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