diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 64f8fba7c..95f9000b1 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -388,7 +388,11 @@ get_commands_spec() -> #ejabberd_commands{name = gc, tags = [server], desc = "Force full garbage collection", module = ?MODULE, function = gc, - args = [], result = {res, rescode}} + args = [], result = {res, rescode}}, + #ejabberd_commands{name = man, tags = [documentation], + desc = "Generate Unix manpage for current ejabberd version", + module = ejabberd_doc, function = man, + args = [], result = {res, restuple}} ]. diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 4463e4caa..ed200b300 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -36,6 +36,7 @@ -export([default_db/2, default_db/3, default_ram_db/2, default_ram_db/3]). -export([beams/1, validators/1, globals/0, may_hide_data/1]). -export([dump/0, dump/1, convert_to_yaml/1, convert_to_yaml/2]). +-export([callback_modules/1]). %% Deprecated functions -export([get_option/2, set_option/2]). @@ -63,6 +64,7 @@ -callback opt_type(atom()) -> econf:validator(). -callback options() -> [atom() | {atom(), term()}]. -callback globals() -> [atom()]. +-callback doc() -> any(). -optional_callbacks([globals/0]). diff --git a/src/ejabberd_doc.erl b/src/ejabberd_doc.erl new file mode 100644 index 000000000..b659149fa --- /dev/null +++ b/src/ejabberd_doc.erl @@ -0,0 +1,459 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_doc.erl +%%% Purpose : Options documentation generator +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(ejabberd_doc). + +%% API +-export([man/0, man/1, have_a2x/0]). + +-include("translate.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +man() -> + man(<<"en">>). + +man(Lang) when is_list(Lang) -> + man(list_to_binary(Lang)); +man(Lang) -> + {ModDoc, SubModDoc} = + 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 + true -> + try M:mod_doc() of + #{desc := Descr} = Map -> + DocOpts = maps:get(opts, Map, []), + Example = maps:get(example, Map, []), + {[{M, Descr, DocOpts, #{example => Example}}|Mods], SubMods}; + #{opts := DocOpts} -> + {ParentMod, Backend} = strip_backend_suffix(M), + {Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)} + catch _:undef -> + case erlang:function_exported( + M, mod_options, 1) of + true -> + warn("module ~s is not documented", [M]); + false -> + ok + end, + Acc + end; + false -> + Acc + end + end, {[], dict:new()}, ejabberd_config:beams(all)), + Doc = lists:flatmap( + fun(M) -> + try M:doc() + catch _:undef -> [] + end + end, ejabberd_config:callback_modules(all)), + Options = + ["TOP LEVEL OPTIONS", + "-----------------", + tr(Lang, ?T("This section describes top level options of ejabberd.")), + io_lib:nl()] ++ + lists:flatmap( + fun(Opt) -> + opt_to_man(Lang, Opt, 1) + end, lists:keysort(1, Doc)), + ModDoc1 = lists:map( + fun({M, Descr, DocOpts, Ex}) -> + case dict:find(M, SubModDoc) of + {ok, Backends} -> + {M, Descr, DocOpts, Backends, Ex}; + error -> + {M, Descr, DocOpts, [], Ex} + end + end, ModDoc), + ModOptions = + [io_lib:nl(), + "MODULES", + "-------", + "[[modules]]", + tr(Lang, ?T("This section describes options of all ejabberd modules.")), + io_lib:nl()] ++ + lists:flatmap( + fun({M, Descr, DocOpts, Backends, Example}) -> + ModName = atom_to_list(M), + [io_lib:nl(), + ModName, + lists:duplicate(length(atom_to_list(M)), $~), + "[[" ++ ModName ++ "]]", + io_lib:nl()] ++ + tr_multi(Lang, Descr) ++ [io_lib:nl()] ++ + opts_to_man(Lang, [{M, '', DocOpts}|Backends]) ++ + format_example(0, Lang, Example) + end, lists:keysort(1, ModDoc1)), + ListenOptions = + [io_lib:nl(), + "LISTENERS", + "-------", + "[[listeners]]", + tr(Lang, ?T("This section describes options of all ejabberd listeners.")), + 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)], + warn_undocumented_modules(ModDoc1), + warn_undocumented_options(Doc), + write_man(AsciiData). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +opts_to_man(Lang, [{_, _, []}]) -> + Text = tr(Lang, ?T("The module has no options.")), + [Text, io_lib:nl()]; +opts_to_man(Lang, Backends) -> + lists:flatmap( + fun({_, Backend, DocOpts}) when DocOpts /= [] -> + Text = if Backend == '' -> + tr(Lang, ?T("Available options")); + 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()]; + (_) -> + [] + end, Backends). + +opt_to_man(Lang, {Option, Options}, Level) -> + [format_option(Lang, Option, 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, #{value := Val}) -> + "*" ++ atom_to_list(Option) ++ "*: 'pass:[" ++ + tr(Lang, Val) ++ "]'::"; +format_option(_Lang, Option, #{}) -> + "*" ++ atom_to_list(Option) ++ "*::". + +format_desc(Lang, #{desc := Desc}) -> + tr_multi(Lang, Desc). + +format_example(Level, Lang, #{example := [_|_] = Example}) -> + case lists:all(fun is_list/1, Example) of + true -> + if Level == 0 -> + ["*Example*:", + "^^^^^^^^^^"]; + true -> + ["+", "*Example*:", "+"] + end ++ format_yaml(Example); + false when Level == 0 -> + ["Examples:", + "^^^^^^^^^"] ++ + lists:flatmap( + fun({Text, Lines}) -> + [tr(Lang, Text)] ++ format_yaml(Lines) + end, Example); + false -> + lists:flatmap( + fun(Block) -> + ["+", "''''", "+"|Block] + end, + lists:map( + fun({Text, Lines}) -> + [tr(Lang, Text), "+"] ++ format_yaml(Lines) + end, Example)) + end; +format_example(_, _, _) -> + []. + +format_yaml(Lines) -> + ["==========================", + "[source,yaml]", + "----"|Lines] ++ + ["----", + "=========================="]. + +man_header(Lang) -> + ["ejabberd.yml(5)", + "===============", + ":doctype: manpage", + ":version: " ++ binary_to_list(ejabberd_config:version()), + io_lib:nl(), + "NAME", + "----", + "ejabberd.yml - " ++ tr(Lang, ?T("main configuration file for ejabberd.")), + io_lib:nl(), + "SYNOPSIS", + "--------", + "ejabberd.yml", + io_lib:nl(), + "DESCRIPTION", + "-----------", + 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.")), + io_lib:nl(), + tr(Lang, ?T("Logically, configuration options are splitted into 3 main categories: " + "'Modules', 'Listeners' and everything else called 'Top Level' options. " + "Thus this document is splitted into 3 main chapters describing each " + "category separately. So, the contents of ejabberd.yml will typically " + "look like this:")), + io_lib:nl(), + "==========================", + "[source,yaml]", + "----", + "hosts:", + " - example.com", + " - domain.tld", + "loglevel: info", + "...", + "listen:", + " -", + " port: 5222", + " module: ejabberd_c2s", + " ...", + "modules:", + " mod_roster: {}", + " ...", + "----", + "==========================", + 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.")), + 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.")), + 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.")), + 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.")), + [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.")), + [configuration_guide_url()]), + io_lib:nl()]. + +man_footer(Lang) -> + {Year, _, _} = date(), + [io_lib:nl(), + "AUTHOR", + "------", + "https://www.process-one.net[ProcessOne].", + io_lib:nl(), + "VERSION", + "-------", + str:format( + 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", + "--------------", + tr(Lang, ?T("Report bugs to ")), + io_lib:nl(), + "SEE ALSO", + "---------", + tr(Lang, ?T("Default configuration file")) ++ ": " ++ default_config_url(), + io_lib:nl(), + tr(Lang, ?T("Main site")) ++ ": ", + io_lib:nl(), + tr(Lang, ?T("Documentation")) ++ ": ", + io_lib:nl(), + tr(Lang, ?T("Configuration Guide")) ++ ": " ++ configuration_guide_url(), + io_lib:nl(), + tr(Lang, ?T("Source code")) ++ ": ", + io_lib:nl(), + "COPYING", + "-------", + "Copyright (c) 2002-" ++ integer_to_list(Year) ++ + " https://www.process-one.net[ProcessOne]."]. + +tr(Lang, {Format, Args}) -> + unicode:characters_to_list( + str:format( + translate:translate(Lang, iolist_to_binary(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]. + +write_man(AsciiData) -> + case file:get_cwd() of + {ok, Cwd} -> + AsciiDocFile = filename:join(Cwd, "ejabberd.yml.5.txt"), + ManPage = filename:join(Cwd, "ejabberd.yml.5"), + case file:write_file(AsciiDocFile, AsciiData) of + ok -> + Ret = run_a2x(Cwd, AsciiDocFile), + %%file:delete(AsciiDocFile), + case Ret of + ok -> + {ok, lists:flatten( + io_lib:format( + "The manpage saved as ~ts", [ManPage]))}; + {error, Error} -> + {error, lists:flatten( + io_lib:format( + "Failed to generate manpage: ~ts", [Error]))} + end; + {error, Reason} -> + {error, lists:flatten( + io_lib:format( + "Failed to write to ~ts: ~s", + [AsciiDocFile, file:format_error(Reason)]))} + end; + {error, Reason} -> + {error, lists:flatten( + io_lib:format("Failed to get current directory: ~s", + [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 -> + {error, "a2x was not found: do you have 'asciidoc' installed?"}; + {true, Path} -> + Cmd = lists:flatten( + io_lib:format("~ts -f manpage ~ts -D ~ts", + [Path, AsciiDocFile, Cwd])), + case os:cmd(Cmd) of + "" -> ok; + Ret -> {error, Ret} + end + end. + +warn_undocumented_modules(Docs) -> + lists:foreach( + fun({M, _, DocOpts, Backends, _}) -> + warn_undocumented_module(M, DocOpts), + lists:foreach( + fun({SubM, _, SubOpts}) -> + warn_undocumented_module(SubM, SubOpts) + end, Backends) + end, Docs). + +warn_undocumented_module(M, DocOpts) -> + try M:mod_options(ejabberd_config:get_myname()) of + Defaults -> + lists:foreach( + fun(OptDefault) -> + Opt = case OptDefault of + O when is_atom(O) -> O; + {O, _} -> O + end, + case lists:keymember(Opt, 1, DocOpts) of + false -> + warn("~s: option ~s is not documented", + [M, Opt]); + true -> + ok + end + end, Defaults) + catch _:undef -> + ok + end. + +warn_undocumented_options(Docs) -> + Opts = lists:flatmap( + fun(M) -> + try M:options() of + Defaults -> + lists:map( + fun({O, _}) -> O; + (O) when is_atom(O) -> O + end, Defaults) + catch _:undef -> + [] + end + end, ejabberd_config:callback_modules(all)), + lists:foreach( + fun(Opt) -> + case lists:keymember(Opt, 1, Docs) of + false -> + warn("option ~s is not documented", [Opt]); + true -> + ok + end + 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), "_")), + {list_to_atom(string:join(lists:reverse(T), "_")), list_to_atom(H)}. + +default_config_url() -> + "". + +configuration_guide_url() -> + "". diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl index d1019dd62..47147e079 100644 --- a/src/ejabberd_options.erl +++ b/src/ejabberd_options.erl @@ -19,7 +19,7 @@ -module(ejabberd_options). -behaviour(ejabberd_config). --export([opt_type/1, options/0, globals/0]). +-export([opt_type/1, options/0, globals/0, doc/0]). -ifdef(NEW_SQL_SCHEMA). -define(USE_NEW_SQL_SCHEMA_DEFAULT, true). @@ -730,6 +730,9 @@ globals() -> websocket_ping_interval, websocket_timeout]. +doc() -> + ejabberd_options_doc:doc(). + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/src/ejabberd_options_doc.erl b/src/ejabberd_options_doc.erl new file mode 100644 index 000000000..e402de8dc --- /dev/null +++ b/src/ejabberd_options_doc.erl @@ -0,0 +1,1256 @@ +%%%---------------------------------------------------------------------- +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(ejabberd_options_doc). + +%% API +-export([doc/0]). + +-include("translate.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +doc() -> + [{hosts, + #{value => ?T("[Domain1, Domain2, ...]"), + desc => + ?T("The option defines a list containing one or more " + "domains that 'ejabberd' will serve. This is a " + "**mandatory** option.")}}, + {listen, + #{value => "[Options, ...]", + desc => + ?T("The option for listeners configuration. See " + "<> section of this document " + "for details.")}}, + {modules, + #{value => "{Module: Options}", + desc => + ?T("The option for modules configuration. See " + "<> section of this document " + "for details.")}}, + {loglevel, + #{value => + "none | emergency | alert | critical | " + "error | warning | notice | info | debug", + desc => + ?T("Verbosity of log files generated by ejabberd. " + "The default value is 'info'. " + "NOTE: previous versions of ejabberd had log levels " + "defined in numeric format ('0..5'). The numeric values " + "are still accepted for backward compatibility, but " + "are not recommended.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("The time of a cached item to keep in cache. " + "Once it's expired, the corresponding item is " + "erased from cache. The default value is 'one hour'.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Whether or not to cache missed lookups. When there is " + "an attempt to lookup for a value in a database and " + "this value is not found and the option is set to 'true', " + "this attempt will be cached and no attempts will be " + "performed until the cache expires (see 'cache_life_time'). " + "Usually you don't want to change it. Default is 'true'.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("A maximum number of items (not memory!) in cache. " + "The rule of thumb, for all tables except rosters, " + "you should set it to the number of maximum online " + "users you expect. For roster multiply this number " + "by 20 or so. If the cache size reaches this threshold, " + "it's fully cleared, i.e. all items are deleted, and " + "the corresponding warning is logged. You should avoid " + "frequent cache clearance, because this degrades " + "performance. The default value is '1000'.")}}, + {use_cache, + #{value => "true | false", + desc => ?T("Enable or disable cache. The default is 'true'.")}}, + {default_db, + #{value => "mnesia | sql", + desc => + ?T("Default persistent storage for ejabberd. " + "Modules and other components (e.g. authentication) " + "may have its own value. The default value is 'mnesia'.")}}, + {default_ram_db, + #{value => "mnesia | sql | redis", + desc => + ?T("Default volatile (in-memory) storage for ejabberd. " + "Modules and other components (e.g. session management) " + "may have its own value. The default value is 'mnesia'.")}}, + {queue_type, + #{value => "ram | file", + desc => + ?T("Default type of queues in ejabberd. " + "Modules may have its own value of the option. " + "The value of 'ram' means that queues will be kept in memory. " + "If value 'file' is set, you may also specify directory " + "in 'queue_dir' option where file queues will be placed. " + "The default value is 'ram'.")}}, + {version, + #{value => "string()", + desc => + ?T("The option can be used to set custom ejabberd version, " + "that will be used by different parts of ejabberd, for " + "example by 'mod_version' module. The default value is " + "obtained at compile time from the underlying version " + "control system.")}}, + {acl, + #{value => "{ACLName: {ACLType: ACLValue}}", + desc => + ?T("The option defines access control lists: named sets " + "of rules which are used to match against different targets " + "(such as a JID or an IP address). Every set of rules " + "has name 'ACLName': it can be any string except 'all' or 'none' " + "(those are predefined names for the rules that match all or nothing " + "respectively). The name 'ACLName' can be referenced from other " + "parts of the configuration file, for example in 'access_rules' " + "option. The rules of 'ACLName' are represented by mapping " + "'pass:[{ACLType: ACLValue}]'. These can be one of the following:")}, + [{user, + #{value => ?T("Username"), + desc => + ?T("If 'Username' is in the form of \"user@server\", " + "the rule matches a JID against this value. " + "Otherwise, if 'Username' is in the form of \"user\", " + "the rule matches any JID that has 'Username' in the node part " + "as long as the server part of this JID is any virtual " + "host served by ejabberd.")}}, + {server, + #{value => ?T("Server"), + desc => + ?T("The rule matches any JID from server 'Server'. " + "The value of 'Server' must be a valid " + "hostname or an IP address.")}}, + {resource, + #{value => ?T("Resource"), + desc => + ?T("The rule matches any JID with a resource 'Resource'.")}}, + {ip, + #{value => ?T("Network"), + desc => + ?T("The rule matches any IP address from the 'Network'.")}}, + {user_regexp, + #{value => ?T("Regexp"), + desc => + ?T("If 'Regexp' is in the form of \"regexp@server\", the rule " + "matches any JID with node part matching regular expression " + "\"regexp\" as long as the server part of this JID is equal " + "to \"server\". If 'Regexp' is in the form of \"regexp\", the rule " + "matches any JID with node part matching regular expression " + "\"regexp\" as long as the server part of this JID is any virtual " + "host served by ejabberd.")}}, + {server_regexp, + #{value => ?T("Regexp"), + desc => + ?T("The rule matches any JID from the server that " + "matches regular expression 'Regexp'.")}}, + {resource_regexp, + #{value => ?T("Regexp"), + desc => + ?T("The rule matches any JID with a resource that " + "matches regular expression 'Regexp'.")}}, + {node_regexp, + #{value => ?T("user_regexp@server_regexp"), + desc => + ?T("The rule matches any JID with node part matching regular " + "expression 'user_regexp' and server part matching regular " + "expression 'server_regexp'.")}}, + {user_glob, + #{value => ?T("Pattern"), + desc => + ?T("Same as 'user_regexp', but matching is performed on a " + "specified 'Pattern' according to the rules used by the " + "Unix shell.")}}, + {server_glob, + #{value => ?T("Pattern"), + desc => + ?T("Same as 'server_regexp', but matching is performed on a " + "specified 'Pattern' according to the rules used by the " + "Unix shell.")}}, + {resource_glob, + #{value => ?T("Pattern"), + desc => + ?T("Same as 'resource_regexp', but matching is performed on a " + "specified 'Pattern' according to the rules used by the " + "Unix shell.")}}, + {node_glob, + #{value => ?T("Pattern"), + desc => + ?T("Same as 'node_regexp', but matching is performed on a " + "specified 'Pattern' according to the rules used by the " + "Unix shell.")}}]}, + {access_rules, + #{value => "{AccessName: {allow|deny: ACLRules|ACLName}}", + desc => + ?T("The option specifies access rules. Each access rule is " + "assigned a name that can be referenced from other parts " + "of the configuration file (mostly from 'access' options of " + "ejabberd modules). Each rule definition may contain " + "arbitrary number of 'allow' or 'deny' sections, and each " + "section may contain any number of ACL rules (see 'acl' option). " + "There are no access rules defined by default."), + example => + ["access_rules:", + " configure:", + " allow: admin", + " something:", + " deny: someone", + " allow: all", + " s2s_banned:", + " deny: problematic_hosts", + " deny: banned_forever", + " deny:", + " ip: 222.111.222.111/32", + " deny:", + " ip: 111.222.111.222/32", + " allow: all", + " xmlrpc_access:", + " allow:", + " user: peter@example.com", + " allow:", + " user: ivone@example.com", + " allow:", + " user: bot@example.com", + " ip: 10.0.0.0/24"]}}, + {acme, + #{value => ?T("Options"), + desc => + ?T("ACME configuration. ACME is used to automatically " + "obtain SSL certificates for the domains served by ejabberd, " + "which means that certificate requests and renewals are " + "performed to some CA server (aka \"ACME server\") in a fully " + "automated mode. The 'Options' are:"), + example => + ["acme:", + " ca_url: https://acme-v02.api.letsencrypt.org/directory", + " contact:", + " - mailto:admin@domain.tld", + " - mailto:bot@domain.tld", + " auto: true", + " cert_type: rsa"]}, + [{ca_url, + #{value => ?T("URL"), + desc => + ?T("The ACME directory URL used as an entry point " + "for the ACME server. The default value is " + " - " + "the directory URL of Let's Encrypt authority.")}}, + {contact, + #{value => ?T("[Contact, ...]"), + desc => + ?T("A list of contact addresses (typically emails) " + "where an ACME server will send notifications " + "when problems occur. The value of 'Contact' must " + "be in the form of \"scheme:address\" (e.g. " + "\"mailto:user@domain.tld\"). The default " + "is an empty list which means an ACME server " + "will send no notices.")}}, + {auto, + #{value => "true | false", + desc => + ?T("Whether to automatically request certificates for " + "all configured domains (that yet have no a certificate) " + "on server start or configuration reload. The default is 'true'.")}}, + {cert_type, + #{value => "rsa | ec", + desc => + ?T("A type of a certificate key. Available values are " + "'ec' and 'rsa' for EC and RSA certificates respectively. " + "It's better to have RSA certificates for the purpose " + "of backward compatibility with legacy clients and servers, " + "thus the default is 'rsa'.")}}]}, + {allow_contrib_modules, + #{value => "true | false", + desc => + ?T("Whether to allow installation of third-party modules or not. " + "The default value is 'true'.")}}, + {allow_multiple_connections, + #{value => "true | false", + desc => + ?T("This option is only used when the anonymous mode is enabled. " + "Setting it to 'true' means that the same username can be " + "taken multiple times in anonymous login mode if different " + "resource are used to connect. This option is only useful " + "in very special occasions. The default value is 'false'.")}}, + {anonymous_protocol, + #{value => "login_anon | sasl_anon | both", + desc => + ?T("'login_anon' means that the anonymous login method will be used. " + "'sasl_anon' means that the SASL Anonymous method will be used. " + "'both' means that SASL Anonymous and login anonymous are both " + "enabled. The default value is 'sasl_anon'.")}}, + {append_host_config, + #{value => "{Host: Options}", + desc => + ?T("To define specific ejabberd modules in a virtual host, " + "you can define the global 'modules' option with the common modules, " + "and later add specific modules to certain virtual hosts. " + "To accomplish that, 'append_host_config' option can be used.")}}, + {auth_cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as 'cache_life_time', but applied to authentication cache " + "only. If not set, the value from 'cache_life_time' will be used.")}}, + {auth_cache_missed, + #{value => "true | false", + desc => + ?T("Same as 'cache_missed', but applied to authentication cache " + "only. If not set, the value from 'cache_missed' will be used.")}}, + {auth_cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as 'cache_size', but applied to authentication cache " + "only. If not set, the value from 'cache_size' will be used.")}}, + {auth_method, + #{value => "[mnesia | sql | anonymous | external | jwt | ldap | pam, ...]", + desc => + ?T("A list of authentication methods to use. " + "If several methods are defined, authentication is " + "considered successful as long as authentication of " + "at least one of the methods succeeds. " + "The default value is '[mnesia]'.")}}, + {auth_password_format, + #{value => "plain | scram", + desc => + ?T("The option defines in what format the users passwords " + "are stored. 'plain': The password is stored as plain text " + "in the database. This is risky because the passwords " + "can be read if your database gets compromised. " + "This is the default value. This format allows clients to " + "authenticate using: the old Jabber Non-SASL (XEP-0078), " + "SASL PLAIN, SASL DIGEST-MD5, and SASL SCRAM-SHA-1. " + "'scram': The password is not stored, only some information " + "that allows to verify the hash provided by the client. " + "It is impossible to obtain the original plain password " + "from the stored information; for this reason, when this " + "value is configured it cannot be changed to plain anymore. " + "This format allows clients to authenticate using: " + "SASL PLAIN and SASL SCRAM-SHA-1.")}}, + {auth_use_cache, + #{value => "true | false", + desc => + ?T("Same as 'use_cache', but applied to authentication cache " + "only. If not set, the value from 'use_cache' will be used.")}}, + {c2s_cafile, + #{value => ?T("Path"), + desc => + ?T("Full path to a file containing one or more CA certificates " + "in PEM format. All client certificates should be signed by " + "one of these root CA certificates and should contain the " + "corresponding JID(s) in subjectAltName field. " + "There is no default value.")}}, + {c2s_ciphers, + #{value => "[Cipher, ...]", + desc => + ?T("A list of OpenSSL ciphers to use for c2s connections. " + "The default value is shown in the example below:"), + example => + ["c2s_ciphers:", + " - HIGH", + " - \"!aNULL\"", + " - \"!eNULL\"", + " - \"!3DES\"", + " - \"@STRENGTH\""]}}, + {c2s_dhfile, + #{value => ?T("Path"), + desc => + ?T("Full path to a file containing custom DH parameters " + "to use for c2s connections. " + "Such a file could be created with the command \"openssl " + "dhparam -out dh.pem 2048\". If this option is not specified, " + "2048-bit MODP Group with 256-bit Prime Order Subgroup will be " + "used as defined in RFC5114 Section 2.3.")}}, + {c2s_protocol_options, + #{value => "[Option, ...]", + desc => + ?T("List of general SSL options to use for c2s connections. " + "These map to OpenSSL's 'set_options()'. The default value is " + "shown in the example below:"), + example => + ["c2s_protocol_options:", + " - no_sslv3", + " - cipher_server_preference", + " - no_compression"]}}, + {c2s_tls_compression, + #{value => "true | false", + desc => + ?T("Whether to enable or disable TLS compression for c2s connections. " + "The default value is 'false'.")}}, + {ca_file, + #{value => ?T("Path"), + desc => + ?T("Path to a file of CA root certificates. " + "The default is to use system defined file if possible.")}}, + {captcha_cmd, + #{value => ?T("Path"), + desc => + ?T("Full path to a script that generates CAPTCHA images. " + "There is no default value: when this option is not " + "set, CAPTCHA functionality is completely disabled.")}}, + {captcha_limit, + #{value => "pos_integer() | infinity", + desc => + ?T("Maximum number of CAPTCHA generated images per minute for " + "any given JID. The option is intended to protect the server " + "from CAPTCHA DoS. The default value is 'infinity'.")}}, + {captcha_host, + #{desc => ?T("Deprecated. Use 'captcha_url' instead.")}}, + {captcha_url, + #{value => ?T("URL"), + desc => + ?T("An URL where CAPTCHA requests should be sent. NOTE: you need " + "to configure 'request_handlers' for 'ejabberd_http' listener " + "as well. There is no default value.")}}, + {certfiles, + #{value => "[Path, ...]", + desc => + ?T("The option accepts a list of file paths (optionally with " + "wildcards) containing either PEM certificates or PEM private " + "keys. At startup or configuration reload, ejabberd reads all " + "certificates from these files, sorts them, removes duplicates, " + "finds matching private keys and then rebuilds full certificate " + "chains for the use in TLS connections. " + "Use this option when TLS is enabled in either of " + "ejabberd listeners: 'ejabberd_c2s', 'ejabberd_http' and so on. " + "NOTE: if you modify the certificate files or change the value " + "of the option, run 'ejabberdctl reload-config' in order to " + "rebuild and reload the certificate chains."), + example => + [{?T("If you use https://letsencrypt.org[Let's Encrypt] certificates " + "for your domain \"domain.tld\", the configuration will look " + "like this:"), + ["certfiles:", + " - /etc/letsencrypt/live/domain.tld/fullchain.pem", + " - /etc/letsencrypt/live/domain.tld/privkey.pem"]}]}}, + {cluster_backend, + #{value => ?T("Backend"), + desc => + ?T("A database backend to use for storing information about " + "cluster. The only available value so far is 'mnesia'.")}}, + {cluster_nodes, + #{value => "[Node, ...]", + desc => + ?T("A list of Erlang nodes to connect on ejabberd startup. " + "This option is mostly intended for ejabberd customization " + "and sofisticated setups. The default value is an empty list.")}}, + {define_macro, + #{value => "{MacroName: MacroValue}", + desc => + ?T("Defines a macro. The value can be any valid arbitrary " + "YAML value. For convenience, it's recommended to define " + "a 'MacroName' in capital letters. Duplicated macros are not allowed. " + "Macros are processed after additional configuration files have " + "been included, so it is possible to use macros that are defined " + "in configuration files included before the usage. " + "It is possible to use a 'MacroValue' in the definition of another macro."), + example => + ["define_macro:", + " DEBUG: debug", + " LOG_LEVEL: DEBUG", + " USERBOB:", + " user: bob@localhost", + "", + "loglevel: LOG_LEVEL", + "", + "acl:", + " admin: USERBOB"]}}, + {disable_sasl_mechanisms, + #{value => "[Mechanism, ...]", + desc => + ?T("Specify a list of SASL mechanisms (such as 'DIGEST-MD5' or " + "'SCRAM-SHA1') that should not be offered to the client. " + "For convenience, the value of 'Mechanism' is case-insensitive. " + "The default value is an empty list, i.e. no mechanisms " + "are disabled by default.")}}, + {domain_balancing, + #{value => "{Domain: Options}", + desc => + ?T("An algorithm to load balance the components that are plugged " + "on an ejabberd cluster. It means that you can plug one or several " + "instances of the same component on each ejabberd node and that " + "the traffic will be automatically distributed. The algorithm " + "to deliver messages to the component(s) can be specified by " + "this option. For any component connected as 'Domain', available " + "'Options' are:"), + example => + ["domain_balancing:", + " component.domain.tld:", + " type: destination", + " component_number: 5", + " transport.example.org:", + " type: bare_source"]}, + [{type, + #{value => "random | source | destination | bare_source | bare_destination", + desc => + ?T("How to deliver stanzas to connected components: " + "'random' - an instance is chosen at random; " + "'destination' - an instance is chosen by the full JID of " + "the packet's 'to' attribute; " + "'source' - by the full JID of the packet's 'from' attribute; " + "'bare_destination' - by the the bare JID (without resource) " + "of the packet's 'to' attribute; " + "'bare_source' - by the bare JID (without resource) of the " + "packet's 'from' attribute is used. The default value is 'random'.")}}, + {component_number, + #{value => "2..1000", + desc => + ?T("The number of components to balance.")}}]}, + {extauth_pool_size, + #{value => ?T("Size"), + desc => + ?T("The option defines the number of instances of the same " + "external program to start for better load balancing. " + "The default is the number of available CPU cores.")}}, + {extauth_program, + #{value => ?T("Path"), + desc => + ?T("Indicate in this option the full path to the external " + "authentication script. The script must be executable by ejabberd.")}}, + {fqdn, + #{value => ?T("Domain"), + desc => + ?T("A fully qualified domain name that will be used in " + "SASL DIGEST-MD5 authentication. The default is detected " + "automatically.")}}, + {hide_sensitive_log_data, + #{value => "true | false", + desc => + ?T("A privacy option to not log sensitive data " + "(mostly IP addresses). The default value " + "is 'false' for backward compatibility.")}}, + {host_config, + #{value => "{Host: Options}", + desc => + ?T("The option is used to redefine 'Options' for virtual host " + "'Host'. In the example below LDAP authentication method " + "will be used on virtual host 'domain.tld' and SQL method " + "will be used on virtual host 'example.org'."), + example => + ["hosts:", + " - domain.tld", + " - example.org", + "", + "auth_method:", + " - sql", + "", + "host_config:", + " domain.tld:", + " auth_method:", + " - ldap"]}}, + {include_config_file, + #{value => "[Filename, ...\\] | {Filename: Options}", + desc => + ?T("Read additional configuration from 'Filename'. If the " + "value is provided in 'pass:[{Filename: Options}]' format, the " + "'Options' must be one of the following:")}, + [{disallow, + #{value => "[OptionName, ...]", + desc => + ?T("Disallows the usage of those options in the included " + "file 'Filename'. The options that match this criteria " + "are not accepted. The default value is an empty list.")}}, + {allow_only, + #{value => "[OptionName, ...]", + desc => + ?T("Allows only the usage of those options in the included " + "file 'Filename'. The options that do not match this " + "criteria are not accepted. The default value is to include " + "all options.")}}]}, + {language, + #{value => ?T("Language"), + desc => + ?T("The option defines the default language of server strings " + "that can be seen by XMPP clients. If an XMPP client does not " + "possess 'xml:lang' attribute, the specified language is used.")}}, + {ldap_servers, + #{value => "[Host, ...]", + desc => + ?T("A list of IP addresses or DNS names of your LDAP servers. " + "The default value is '[localhost]'.")}}, + {ldap_backups, + #{value => "[Host, ...]", + desc => + ?T("A list of IP addresses or DNS names of LDAP backup servers. " + "When no servers listed in 'ldap_servers' option are reachable, " + "ejabberd will try to connect to these backup servers. " + "The default is an empty list, i.e. no backup servers specified. " + "WARNING: ejabberd doesn't try to reconnect back to the main " + "servers when they become operational again, so the only way " + "to restore these connections is to restart ejabberd. This " + "limitation might be fixed in future releases.")}}, + {ldap_encrypt, + #{value => "tls | none", + desc => + ?T("Whether to encrypt LDAP connection using TLS or not. " + "The default value is 'none'. NOTE: STARTTLS encryption " + "is not supported.")}}, + {ldap_tls_certfile, + #{value => ?T("Path"), + desc => + ?T("A path to a file containing PEM encoded certificate " + "along with PEM encoded private key. This certificate " + "will be provided by ejabberd when TLS enabled for " + "LDAP connections. There is no default value, which means " + "no client certificate will be sent.")}}, + {ldap_tls_verify, + #{value => "false | soft | hard", + desc => + ?T("This option specifies whether to verify LDAP server " + "certificate or not when TLS is enabled. When 'hard' is set, " + "ejabberd doesn't proceed if the certificate is invalid. " + "When 'soft' is set, ejabberd proceeds even if the check has failed. " + "The default is 'false', which means no checks are performed.")}}, + {ldap_tls_cacertfile, + #{value => ?T("Path"), + desc => + ?T("A path to a file containing PEM encoded CA certificates. " + "This option is required when TLS verification is enabled.")}}, + {ldap_tls_depth, + #{value => ?T("Number"), + desc => + ?T("Specifies the maximum verification depth when TLS verification " + "is enabled, i.e. how far in a chain of certificates the " + "verification process can proceed before the verification " + "is considered to be failed. Peer certificate = 0, " + "CA certificate = 1, higher level CA certificate = 2, etc. " + "The value '2' thus means that a chain can at most contain " + "peer cert, CA cert, next CA cert, and an additional CA cert. " + "The default value is '1'.")}}, + {ldap_port, + #{value => "1..65535", + desc => + ?T("Port to connect to your LDAP server. The default port is " + "'389' if encryption is disabled and '636' if encryption is " + "enabled.")}}, + {ldap_rootdn, + #{value => "RootDN", + desc => + ?T("Bind Distinguished Name. The default value is an empty " + "string, which means \"anonymous connection\".")}}, + {ldap_password, + #{value => ?T("Password"), + desc => + ?T("Bind password. The default value is an empty string.")}}, + {ldap_deref_aliases, + #{value => "never | always | finding | searching", + desc => + ?T("Whether to dereference aliases or not. " + "The default value is 'never'.")}}, + {ldap_base, + #{value => "Base", + desc => + ?T("LDAP base directory which stores users accounts. " + "There is no default value: you must set the option " + "in order for LDAP connections to work properly.")}}, + {ldap_uids, + #{value => "[Attr\\] | {Attr: AttrFormat}", + desc => + ?T("LDAP attributes which hold a list of attributes to use " + "as alternatives for getting the JID, where 'Attr' is " + "an LDAP attribute which holds the user's part of the JID and " + "'AttrFormat' must contain one and only one pattern variable " + "\"%u\" which will be replaced by the user's part of the JID. " + "For example, \"%u@example.org\". If the value is in the form " + "of '[Attr]' then 'AttrFormat' is assumed to be \"%u\".")}}, + {ldap_filter, + #{value => ?T("Filter"), + desc => + ?T("An LDAP filter as defined in " + "https://tools.ietf.org/html/rfc4515[RFC4515]. " + "There is no default value. Example: " + "\"(&(objectClass=shadowAccount)(memberOf=Jabber Users))\". " + "NOTE: don't forget to close brackets and don't use superfluous " + "whitespaces. Also you must not use \"uid\" attribute in the " + "filter because this attribute will be appended to the filter " + "automatically.")}}, + {ldap_dn_filter, + #{value => "{Filter: FilterAttrs}", + desc => + ?T("This filter is applied on the results returned by the main " + "filter. The filter performs an additional LDAP lookup to make " + "the complete result. This is useful when you are unable to " + "define all filter rules in 'ldap_filter'. You can define " + "\"%u\", \"%d\", \"%s\" and \"%D\" pattern variables in 'Filter': " + "\"%u\" is replaced by a user's part of the JID, \"%d\" is " + "replaced by the corresponding domain (virtual host), all \"%s\" " + "variables are consecutively replaced by values from the attributes " + "in 'FilterAttrs' and \"%D\" is replaced by Distinguished Name from " + "the result set. There is no default value, which means the " + "result is not filtered. WARNING: Since this filter makes " + "additional LDAP lookups, use it only as the last resort: " + "try to define all filter rules in 'ldap_filter' option if possible."), + example => + ["ldap_dn_filter:", + " \"(&(name=%s)(owner=%D)(user=%u@%d))\": [sn]"]}}, + {log_rotate_count, + #{value => ?T("Number"), + desc => + ?T("The number of rotated log files to keep. " + "The default value is '1'.")}}, + {log_rotate_size, + #{value => ?T("Size"), + desc => + ?T("The size (in bytes) of a log file to trigger rotation. " + "The default value is '10485760' (10 Mb).")}}, + {max_fsm_queue, + #{value => ?T("Size"), + desc => + ?T("This option specifies the maximum number of elements " + "in the queue of the FSM (Finite State Machine). Roughly " + "speaking, each message in such queues represents one " + "XML stanza queued to be sent into its relevant outgoing " + "stream. If queue size reaches the limit (because, for " + "example, the receiver of stanzas is too slow), the FSM " + "and the corresponding connection (if any) will be terminated " + "and error message will be logged. The reasonable value for " + "this option depends on your hardware configuration. " + "The allowed values are positive integers. " + "The default value is '10000'.")}}, + {negotiation_timeout, + #{value => "timeout()", + desc => + ?T("Time to wait for an XMPP stream negotiation to complete. " + "When timeout occurs, the corresponding XMPP stream is closed. " + "The default value is '30' seconds.")}}, + {net_ticktime, + #{value => "timeout()", + desc => + ?T("This option can be used to tune tick time parameter of " + "'net_kernel'. It tells Erlang VM how often nodes should check " + "if intra-node communication was not interrupted. This option " + "must have identical value on all nodes, or it will lead to subtle " + "bugs. Usually leaving default value of this is option is best, " + "tweak it only if you know what you are doing. " + "The default value is '1' minute.")}}, + {new_sql_schema, + #{value => "true | false", + desc => + {?T("Whether to use 'new' SQL schema. All schemas are located " + "at . " + "There are two schemas available. The default lecacy schema " + "allows to store one XMPP domain into one ejabberd database. " + "The 'new' schema allows to handle several XMPP domains in a " + "single ejabberd database. Using this 'new' schema is best when " + "serving several XMPP domains and/or changing domains from " + "time to time. This avoid need to manage several databases and " + "handle complex configuration changes. The default depends on " + "configuration flag '--enable-new-sql-schema' which is set " + "at compile time."), + [binary:part(ejabberd_config:version(), {0,5})]}}}, + {oauth_access, + #{value => ?T("AccessName"), + desc => ?T("By default creating OAuth tokens is not allowed. " + "To define which users can create OAuth tokens, " + "you can refer to an ejabberd access rule in the " + "'oauth_access' option. Use 'all' to allow everyone " + "to create tokens.")}}, + {oauth_cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as 'cache_life_time', but applied to OAuth cache " + "only. If not set, the value from 'cache_life_time' will be used.")}}, + {oauth_cache_missed, + #{value => "true | false", + desc => + ?T("Same as 'cache_missed', but applied to OAuth cache " + "only. If not set, the value from 'cache_missed' will be used.")}}, + {oauth_cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as 'cache_size', but applied to OAuth cache " + "only. If not set, the value from 'cache_size' will be used.")}}, + {oauth_use_cache, + #{value => "true | false", + desc => + ?T("Same as 'use_cache', but applied to OAuth cache " + "only. If not set, the value from 'use_cache' will be used.")}}, + {oauth_db_type, + #{value => "mnesia | sql", + desc => + ?T("Database backend to use for OAuth authentication. " + "The default value is picked from 'default_db' option, or " + "if it's not set, 'mnesia' will be used.")}}, + {oauth_expire, + #{value => "timeout()", + desc => + ?T("Time during which the OAuth token is valid, in seconds. " + "After that amount of time, the token expires and the delegated " + "credential cannot be used and is removed from the database. " + "The default is '4294967' seconds.")}}, + {oom_killer, + #{value => "true | false", + desc => + ?T("Enable or disable OOM (out-of-memory) killer. " + "When system memory raises above the limit defined in " + "'oom_watermark' option, ejabberd triggers OOM killer " + "to terminate most memory consuming Erlang processes. " + "Note that in order to maintain functionality, ejabberd only " + "attempts to kill transient processes, such as those managing " + "client sessions, s2s or database connections. " + "The default value is 'true'.")}}, + {oom_queue, + #{value => ?T("Size"), + desc => + ?T("Trigger OOM killer when some of the running Erlang processes " + "have messages queue above this 'Size'. Note that " + "such processes won't be killed if 'oom_killer' option is set " + "to 'false' or if 'oom_watermark' is not reached yet.")}}, + {oom_watermark, + #{value => ?T("Percent"), + desc => + ?T("A percent of total system memory consumed at which " + "OOM killer should be activated with some of the processes " + "possibly be killed (see 'oom_killer' option). Later, when " + "memory drops below this 'Percent', OOM killer is deactivated. " + "The default value is '80' percents.")}}, + {outgoing_s2s_families, + #{value => "[ipv4 | ipv6, ...]", + desc => + ?T("Specify which address families to try, in what order. " + "The default is '[ipv4, ipv6]' which means it first tries " + "connecting with IPv4, if that fails it tries using IPv6.")}}, + {outgoing_s2s_port, + #{value => "1..65535", + desc => + ?T("A port number to use for outgoing s2s connections when the target " + "server doesn't have an SRV record. The default value is '5269'.")}}, + {outgoing_s2s_timeout, + #{value => "timeout()", + desc => + ?T("The timeout in seconds for outgoing S2S connection attempts. " + "The default value is '10' seconds.")}}, + {pam_service, + #{value => ?T("Name"), + desc => + ?T("This option defines the PAM service name. Refer to the PAM " + "documentation of your operation system for more information. " + "The default value is 'ejabberd'.")}}, + {pam_userinfotype, + #{value => "username | jid", + desc => + ?T("This option defines what type of information about the " + "user ejabberd provides to the PAM service: only the username, " + "or the user's JID. Default is 'username'.")}}, + {pgsql_users_number_estimate, + #{value => "true | false", + desc => + ?T("Whether to use PostgreSQL estimation when counting registered " + "users. The default value is 'false'.")}}, + {queue_dir, + #{value => ?T("Directory"), + desc => + ?T("If 'queue_type' option is set to 'file', use this 'Directory' " + "to store file queues. The default is to keep queues inside " + "Mnesia directory.")}}, + {redis_connect_timeout, + #{value => "timeout()", + desc => + ?T("A timeout to wait for the connection to be re-established " + "to the Redis server. The default is '1 second'.")}}, + {redis_db, + #{value => ?T("Number"), + desc => ?T("Redis database number. The default is '0'.")}}, + {redis_password, + #{value => ?T("Password"), + desc => + ?T("The password to the Redis server. " + "The default is an empty string, i.e. no password.")}}, + {redis_pool_size, + #{value => ?T("Number"), + desc => + ?T("The number of simultaneous connections to the Redis server. " + "The default value is '10'.")}}, + {redis_port, + #{value => "1..65535", + desc => + ?T("The port where the Redis server is accepting connections. " + "The default is '6379'.")}}, + {redis_queue_type, + #{value => "ram | file", + desc => + ?T("The type of request queue for the Redis server. " + "See description of 'queue_type' option for the explanation. " + "The default value is the value defined in 'queue_type' " + "or 'ram' if the latter is not set.")}}, + {redis_server, + #{value => ?T("Hostname"), + desc => + ?T("A hostname or an IP address of the Redis server. " + "The default is 'localhost'.")}}, + {registration_timeout, + #{value => "timeout()", + desc => + ?T("This is a global option for module 'mod_register'. " + "It limits the frequency of registrations from a given " + "IP or username. So, a user that tries to register a " + "new account from the same IP address or JID during " + "this time after their previous registration " + "will receive an error with the corresponding explanation. " + "To disable this limitation, set the value to 'infinity'. " + "The default value is '600 seconds'.")}}, + {resource_conflict, + #{value => "setresource | closeold | closenew", + desc => + ?T("NOTE: this option is deprecated and may be removed " + "anytime in the future versions. The possible values " + "match exactly the three possibilities described in " + "https://tools.ietf.org/html/rfc6120#section-7.7.2.2" + "[XMPP Core: section 7.7.2.2]. " + "The default value is 'closeold'. If the client " + "uses old Jabber Non-SASL authentication (XEP-0078), " + "then this option is not respected, and the action performed " + "is 'closeold'.")}}, + {router_cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as 'cache_life_time', but applied to routing table cache " + "only. If not set, the value from 'cache_life_time' will be used.")}}, + {router_cache_missed, + #{value => "true | false", + desc => + ?T("Same as 'cache_missed', but applied to routing table cache " + "only. If not set, the value from 'cache_missed' will be used.")}}, + {router_cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as 'cache_size', but applied to routing table cache " + "only. If not set, the value from 'cache_size' will be used.")}}, + {router_db_type, + #{value => "mnesia | sql | redis", + desc => + ?T("Database backend to use for routing information. " + "The default value is picked from 'default_ram_db' option, or " + "if it's not set, 'mnesia' will be used.")}}, + {router_use_cache, + #{value => "true | false", + desc => + ?T("Same as 'use_cache', but applied to routing table cache " + "only. If not set, the value from 'use_cache' will be used.")}}, + {rpc_timeout, + #{value => "timeout()", + desc => + ?T("A timeout for remote function calls between nodes " + "in an ejabberd cluster. You should probably never change " + "this value since those calls are used for internal needs " + "only. The default value is '5' seconds.")}}, + {s2s_access, + #{value => ?T("Access"), + desc => + ?T("The access rule to restrict server-to-server connections. " + "The default value is 'all' which means no restrictions " + "are applied.")}}, + {s2s_cafile, + #{value => ?T("Path"), + desc => + ?T("A path to a file with CA root certificates that will " + "be used to authenticate s2s connections. If not set " + "the value of 'ca_file' will be used.")}}, + {s2s_ciphers, + #{value => "[Cipher, ...]", + desc => + ?T("A list of OpenSSL ciphers to use for s2s connections. " + "The default value is shown in the example below:"), + example => + ["s2s_ciphers:", + " - HIGH", + " - \"!aNULL\"", + " - \"!eNULL\"", + " - \"!3DES\"", + " - \"@STRENGTH\""]}}, + {s2s_dhfile, + #{value => ?T("Path"), + desc => + ?T("Full path to a file containing custom DH parameters " + "to use for s2s connections. " + "Such a file could be created with the command \"openssl " + "dhparam -out dh.pem 2048\". If this option is not specified, " + "2048-bit MODP Group with 256-bit Prime Order Subgroup will be " + "used as defined in RFC5114 Section 2.3.")}}, + {s2s_protocol_options, + #{value => "[Option, ...]", + desc => + ?T("List of general SSL options to use for s2s connections. " + "These map to OpenSSL's 'set_options()'. The default value is " + "shown in the example below:"), + example => + ["s2s_protocol_options:", + " - no_sslv3", + " - cipher_server_preference", + " - no_compression"]}}, + {s2s_tls_compression, + #{value => "true | false", + desc => + ?T("Whether to enable or disable TLS compression for s2s connections. " + "The default value is 'false'.")}}, + {s2s_dns_retries, + #{value => ?T("Number"), + desc => + ?T("DNS resolving retries. The default value is '2'.")}}, + {s2s_dns_timeout, + #{value => "timeout()", + desc => + ?T("The timeout for DNS resolving. The default value is '10' seconds.")}}, + {s2s_max_retry_delay, + #{value => "timeout()", + desc => + ?T("The maximum allowed delay for s2s connection retry to connect after a " + "failed connection attempt. The default value is '300' seconds " + "(5 minutes).")}}, + {s2s_queue_type, + #{value => "ram | file", + desc => + ?T("The type of a queue for s2s packets. " + "See description of 'queue_type' option for the explanation. " + "The default value is the value defined in 'queue_type' " + "or 'ram' if the latter is not set.")}}, + {s2s_timeout, + #{value => "timeout()", + desc => + ?T("A time to wait before closing an idle s2s connection. " + "The default value is '10' minutes.")}}, + {s2s_use_starttls, + #{value => "true | false | optional | required", + desc => + ?T("Whether to use STARTTLS for s2s connections. " + "The value of 'false' means STARTTLS is prohibited. " + "The value of 'true' or 'optional' means STARTTLS is enabled " + "but plain connections are still allowed. And the value of " + "'required' means that only STARTTLS connections are allowed. " + "The default value is 'false' (for historical reasons).")}}, + {s2s_zlib, + #{value => "true | false", + desc => + ?T("Whether to use 'zlib' compression (as defined in " + "https://xmpp.org/extensions/xep-0138.html[XEP-0138]) or not. " + "The default value is 'false'. WARNING: this type " + "of compression is nowadays considered insecure.")}}, + {shaper, + #{value => "{ShaperName: Rate}", + desc => + ?T("The option defines a set of shapers. Every shaper is assigned " + "a name 'ShaperName' that can be used in other parts of the " + "configuration file, such as 'shaper_rules' option. The shaper " + "itself is defined by its 'Rate', where 'Rate' stands for the " + "maximum allowed incoming rate in **bytes** per second. " + "When a connection exceeds this limit, ejabberd stops reading " + "from the socket until the average rate is again below the " + "allowed maximum. In the example below shaper 'normal' limits " + "the traffic speed to 1,000 bytes/sec and shaper 'fast' limits " + "the traffic speed to 50,000 bytes/sec:"), + example => + ["shaper:", + " normal: 1000", + " fast: 50000"]}}, + {shaper_rules, + #{value => "{ShaperRuleName: {Number|ShaperName: ACLRule|ACLName}}", + desc => + ?T("An entry allowing to declaring shaper to use for matching user/hosts. " + "Semantics is similar to 'access_rules' option, the only difference is " + "that instead using 'allow' or 'deny', a name of a shaper (defined in " + "'shaper' option) or a positive number should be used."), + example => + ["shaper_rules:", + " connections_limit:", + " 10:", + " user: peter@example.com", + " 100: admin", + " 5: all", + " download_speed:", + " fast: admin", + " slow: anonymous_users", + " normal: all", + " log_days: 30"]}}, + {sm_cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as 'cache_life_time', but applied to client sessions table cache " + "only. If not set, the value from 'cache_life_time' will be used.")}}, + {sm_cache_missed, + #{value => "true | false", + desc => + ?T("Same as 'cache_missed', but applied to client sessions table cache " + "only. If not set, the value from 'cache_missed' will be used.")}}, + {sm_cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as 'cache_size', but applied to client sessions table cache " + "only. If not set, the value from 'cache_size' will be used.")}}, + {sm_db_type, + #{value => "mnesia | sql | redis", + desc => + ?T("Database backend to use for client sessions information. " + "The default value is picked from 'default_ram_db' option, or " + "if it's not set, 'mnesia' will be used.")}}, + {sm_use_cache, + #{value => "true | false", + desc => + ?T("Same as 'use_cache', but applied to client sessions table cache " + "only. If not set, the value from 'use_cache' will be used.")}}, + {sql_type, + #{value => "mysql | pgsql | sqlite | mssql | odbc", + desc => + ?T("The type of an SQL connection. The default is 'odbc'.")}}, + {sql_connect_timeout, + #{value => "timeout()", + desc => + ?T("A time to wait for connection to an SQL server to be " + "established. The default value is '5' seconds.")}}, + {sql_database, + #{value => ?T("Database"), + desc => + ?T("An SQL database name. For SQLite this must be a full " + "path to a database file. The default value is 'ejabberd'.")}}, + {sql_keepalive_interval, + #{value => "timeout()", + desc => + ?T("An interval to make a dummy SQL request to keep alive the " + "connections to the database. There is no default value, so no " + "keepalive requests are made.")}}, + {sql_password, + #{value => ?T("Password"), + desc => + ?T("The password for SQL authentication. The default is empty string.")}}, + {sql_pool_size, + #{value => ?T("Size"), + desc => + ?T("A number of connections to the SQL server. By default ejabberd opens " + "10 connections to the database for each virtual host. WARNING: " + "for SQLite this value is '1' by default and it's not recommended " + "to change it due to potential race conditions.")}}, + {sql_port, + #{value => "1..65535", + desc => + ?T("The port where the SQL server is accepting connections. " + "The default is '3306' for MySQL, '5432' for PostgreSQL and " + "'1433' for MSSQL. The option has no effect for SQLite.")}}, + {sql_query_timeout, + #{value => "timeout()", + desc => + ?T("A time to wait for an SQL query response. " + "The default value is '60' seconds.")}}, + {sql_queue_type, + #{value => "ram | file", + desc => + ?T("The type of a request queue for the SQL server. " + "See description of 'queue_type' option for the explanation. " + "The default value is the value defined in 'queue_type' " + "or 'ram' if the latter is not set.")}}, + {sql_server, + #{value => ?T("Host"), + desc => + ?T("A hostname or an IP address of the SQL server. " + "The default value is 'localhost'.")}}, + {sql_ssl, + #{value => "true | false", + desc => + ?T("Whether to use SSL encrypted connections to the " + "SQL server. The option is only available for " + "PostgreSQL. The default value is 'false'.")}}, + {sql_ssl_cafile, + #{value => ?T("Path"), + desc => + ?T("A path to a file with CA root certificates that will " + "be used to verify SQL connections. Implies 'sql_ssl' " + "and 'sql_ssl_verify' options are set to 'true'. " + "There is no default which means " + "certificate verification is disabled.")}}, + {sql_ssl_certfile, + #{value => ?T("Path"), + desc => + ?T("A path to a certificate file that will be used " + "for SSL connections to the SQL server. Implies 'sql_ssl' " + "option is set to 'true'. There is no default which means " + "ejabberd won't provide a client certificate to the SQL " + "server.")}}, + {sql_ssl_verify, + #{value => "true | false", + desc => + ?T("Whether to verify SSL connection to the SQL server against " + "CA root certificates defined in 'sql_ssl_cafile' option. " + "Implies 'sql_ssl' option is set to 'true'. " + "The default value is 'false'.")}}, + {sql_start_interval, + #{value => "timeout()", + desc => + ?T("A time to wait before retrying to restore failed SQL connection. " + "The default value is '30' seconds.")}}, + {sql_username, + #{value => ?T("Username"), + desc => + ?T("A user name for SQL authentication. " + "The default value is 'ejabberd'.")}}, + {trusted_proxies, + #{value => "all | [Network1, Network2, ...]", + desc => + ?T("Specify what proxies are trusted when an HTTP request " + "contains the header 'X-Forwarded-For'. You can specify " + "'all' to allow all proxies, or specify a list of IPs, " + "possibly with masks. The default value is an empty list. " + "This allows, if enabled, to be able to know the real IP " + "of the request, for admin purpose, or security configuration " + "(for example using 'mod_fail2ban'). IMPORTANT: The proxy MUST " + "be configured to set the 'X-Forwarded-For' header if you " + "enable this option as, otherwise, the client can set it " + "itself and as a result the IP value cannot be trusted for " + "security rules in ejabberd.")}}, + {validate_stream, + #{value => "true | false", + desc => + ?T("Whether to validate any incoming XML packet according " + "to the schemas of " + "https://github.com/processone/xmpp#supported-xmpp-elements" + "[supported XMPP extensions]. WARNING: the validation is only " + "intended for the use by client developers - don't enable " + "it in production environment. The default value is 'false'.")}}, + {websocket_origin, + #{value => "ignore | URL", + desc => + ?T("This option enables validation for 'Origin' header to " + "protect against connections from other domains than given " + "in the configuration file. In this way, the lower layer load " + "balancer can be chosen for a specific ejabberd implementation " + "while still providing a secure Websocket connection. " + "The default value is 'ignore'. An example value of the 'URL' is " + "\"https://test.example.org:8081\".")}}, + {websocket_ping_interval, + #{value => "timeout()", + desc => + ?T("Defines time between pings sent by the server to a client " + "(Websocket level protocol pings are used for this) to keep " + "a connection active. If the client doesn't respond to two " + "consecutive pings, the connection will be assumed as closed. " + "The value of '0' can be used to disable the feature. This option " + "makes the server sending pings only for connections using the RFC " + "compliant protocol. For older style connections the server " + "expects that whitespace pings would be used for this purpose. " + "The default value is '60' seconds.")}}, + {websocket_timeout, + #{value => "timeout()", + desc => + ?T("Amount of time without any communication after which the " + "connection would be closed. The default value is '300' seconds.")}}]. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 58ec9950f..aa5f81dca 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -53,12 +53,18 @@ -type opts() :: #{atom() => term()}. -type db_type() :: atom(). +-type opt_desc() :: #{desc => binary() | [binary()], + value => string() | binary()}. +-type opt_doc() :: {atom(), opt_desc()} | {atom(), opt_desc(), [opt_doc()]}. -callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}. -callback stop(binary()) -> any(). -callback reload(binary(), opts(), opts()) -> ok | {ok, pid()} | {error, term()}. -callback mod_opt_type(atom()) -> econf:validator(). -callback mod_options(binary()) -> [{atom(), term()} | atom()]. +-callback mod_doc() -> #{desc => binary() | [binary()], + opts => [opt_doc()], + example => [string()] | [{binary(), [string()]}]}. -callback depends(binary(), opts()) -> [{module(), hard | soft}]. -optional_callbacks([mod_opt_type/1, reload/3]). diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index edba1cebe..54cfb1050 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -36,7 +36,7 @@ get_local_identity/5, get_local_features/5, get_sm_commands/5, get_sm_identity/5, get_sm_features/5, ping_item/4, ping_command/4, mod_opt_type/1, depends/2, - mod_options/1]). + mod_options/1, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -278,3 +278,15 @@ mod_opt_type(report_commands_node) -> mod_options(_Host) -> [{report_commands_node, false}]. + +mod_doc() -> + #{desc => + ?T("This module implements https://xmpp.org/extensions/xep-0050.html" + "[XEP-0050: Ad-Hoc Commands]. It's an auxiliary module and is " + "only needed by some of the other modules."), + opts => + [{report_commands_node, + #{value => "true | false", + desc => + ?T("Provide the Commands item in the Service Disvocery. " + "Default value: 'false'.")}}]}. diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 3387a831e..4f9a50e6f 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -29,9 +29,10 @@ -behaviour(gen_mod). -include("logger.hrl"). +-include("translate.hrl"). -export([start/2, stop/1, reload/3, mod_options/1, - get_commands_spec/0, depends/2]). + get_commands_spec/0, depends/2, mod_doc/0]). % Commands API -export([ @@ -1589,3 +1590,59 @@ num_prio(_) -> -1. mod_options(_) -> []. + +mod_doc() -> + #{desc => + [?T("This module provides additional administrative commands."), "", + ?T("Details for some commands:"), "", + ?T("- 'ban-acount':"), + ?T("This command kicks all the connected sessions of the + account from the server. It also changes their password to + a randomly generated one, so they can't login anymore + unless a server administrator changes their password + again. It is possible to define the reason of the ban. The + new password also includes the reason and the date and time + of the ban. For example, if this command is called: + 'ejabberdctl vhost example.org ban-account boby \"Spammed + rooms\"', then the sessions of the local account which JID + is boby@example.org will be kicked, and its password will + be set to something like this: + 'BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_rooms'"), + ?T("- 'pushroster' (and 'pushroster-all'):"), + ?T("The roster file must be placed, if using Windows, on + the directory where you installed ejabberd: C:/Program + Files/ejabberd or similar. If you use other Operating + System, place the file on the same directory where the + .beam files are installed. See below an example roster + file."), + ?T("- 'srg-create':"), + ?T("If you want to put a group Name with blankspaces, use + the characters \"\' and \\'\" to define when the Name + starts and ends. For example: 'ejabberdctl srg-create g1 + example.org \"\'Group number 1\\'\" this_is_g1 g1'")], + opts => + [{module_resource, + #{value => ?T("Resource"), + desc => + ?T("Indicate the resource that the XMPP stanzas must + use in the FROM or TO JIDs. This is only useful in + the 'get_vcard*' and 'set_vcard*' commands. The + default value is 'mod_admin_extra'.")}}], + example => + [{?T("With this configuration, vCards can only be modified + with mod_admin_extra commands:"), + ["acl:", + " adminextraresource:", + " - resource: \"modadminextraf8x,31ad\"", + "access_rules:", + " vcard_set:", + " - allow: adminextraresource", + "modules:", + " mod_admin_extra:", + " module_resource: \"modadminextraf8x,31ad\"", + " mod_vcard:", + " access_set: vcard_set"]}, + {?T("Content of roster file for 'pushroster' command:"), + ["[{<<\"bob\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Bob\">>},", + "{<<\"mart\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Mart\">>},", + "{<<\"Rich\">>, <<\"example.org\">>, <<\"bosses\">>, <<\"Rich\">>}]."]}]}. diff --git a/src/mod_admin_update_sql.erl b/src/mod_admin_update_sql.erl index 74e78cc44..18eb76ac6 100644 --- a/src/mod_admin_update_sql.erl +++ b/src/mod_admin_update_sql.erl @@ -29,7 +29,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, mod_options/1, - get_commands_spec/0, depends/2]). + get_commands_spec/0, depends/2, mod_doc/0]). % Commands API -export([update_sql/0]). @@ -39,6 +39,7 @@ -include("ejabberd_commands.hrl"). -include("xmpp.hrl"). -include("ejabberd_sql_pt.hrl"). +-include("translate.hrl"). %%% %%% gen_mod @@ -358,3 +359,9 @@ sql_query(Host, Query) -> end. mod_options(_) -> []. + +mod_doc() -> + #{desc => + ?T("This module can be used to update existing SQL database " + "from 'old' to 'new' schema. When the module is loaded " + "use 'update_sql' ejabberdctl command.")}. diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 013594af8..1e55755c4 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -35,7 +35,7 @@ -export([start/2, stop/1, reload/3, export/1, import_info/0, import_start/2, import/5, announce/1, send_motd/1, disco_identity/5, disco_features/5, disco_items/5, depends/2, - send_announcement_to_all/3, announce_commands/4, + send_announcement_to_all/3, announce_commands/4, mod_doc/0, announce_items/4, mod_opt_type/1, mod_options/1, clean_cache/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -917,3 +917,64 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + [?T("This module enables configured users to broadcast " + "announcements and to set the message of the day (MOTD). " + "Configured users can perform these actions with an XMPP " + "client either using Ad-hoc Commands or sending messages " + "to specific JIDs."), "", + ?T("The Ad-hoc Commands are listed in the Server Discovery. " + "For this feature to work, 'mod_adhoc' must be enabled."), "", + ?T("The specific JIDs where messages can be sent are listed below. " + "The first JID in each entry will apply only to the specified " + "virtual host example.org, while the JID between brackets " + "will apply to all virtual hosts in ejabberd:"), "", + "example.org/announce/all (example.org/announce/all-hosts/all)::", + ?T("The message is sent to all registered users. If the user is " + "online and connected to several resources, only the resource " + "with the highest priority will receive the message. " + "If the registered user is not connected, the message will be " + "stored offline in assumption that offline storage (see 'mod_offline') " + "is enabled."), + "example.org/announce/online (example.org/announce/all-hosts/online)::", + ?T("The message is sent to all connected users. If the user is " + "online and connected to several resources, all resources will " + "receive the message."), + "example.org/announce/motd (example.org/announce/all-hosts/motd)::", + ?T("The message is set as the message of the day (MOTD) and is sent " + "to users when they login. In addition the message is sent to all " + "connected users (similar to announce/online)."), + "example.org/announce/motd/update (example.org/announce/all-hosts/motd/update)::", + ?T("The message is set as message of the day (MOTD) and is sent to users " + "when they login. The message is not sent to any currently connected user."), + "example.org/announce/motd/delete (example.org/announce/all-hosts/motd/delete)::", + ?T("Any message sent to this JID removes the existing message of the day (MOTD).")], + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("This option specifies who is allowed to send announcements " + "and to set the message of the day. The default value is 'none' " + "(i.e. nobody is able to send such messages).")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_avatar.erl b/src/mod_avatar.erl index fd38cc2d2..a5d1ca8d4 100644 --- a/src/mod_avatar.erl +++ b/src/mod_avatar.erl @@ -26,6 +26,7 @@ %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). +-export([mod_doc/0]). %% Hooks -export([pubsub_publish_item/6, vcard_iq_convert/1, vcard_iq_publish/1, get_sm_features/5]). @@ -33,6 +34,7 @@ -include("xmpp.hrl"). -include("logger.hrl"). -include("pubsub.hrl"). +-include("translate.hrl"). -type avatar_id_meta() :: #{avatar_meta => {binary(), avatar_meta()}}. -opaque convert_rule() :: {default | eimp:img_type(), eimp:img_type()}. @@ -458,3 +460,36 @@ mod_opt_type(rate_limit) -> mod_options(_) -> [{rate_limit, 10}, {convert, []}]. + +mod_doc() -> + #{desc => + [?T("The purpose of the module is to cope with legacy and modern " + "XMPP clients posting avatars. The process is described in " + "https://xmpp.org/extensions/xep-0398.html" + "[XEP-0398: User Avatar to vCard-Based Avatars Conversion]."), "", + ?T("Also, the module supports conversion between avatar " + "image formats on the fly."), "", + ?T("The module depends on 'mod_vcard', 'mod_vcard_xupdate' and " + "'mod_pubsub'.")], + opts => + [{convert, + #{value => "{From: To}", + desc => + ?T("Defines image convertion rules: the format in 'From' " + "will be converted to format in 'To'. The value of 'From' " + "can also be 'default', which is match-all rule. NOTE: " + "the list of supported formats is detected at compile time " + "depending on the image libraries installed in the system."), + example => + [{?T("In this example avatars in WebP format are " + "converted to JPEG, all other formats are " + "converted to PNG:"), + ["convert:", + " webp: jpg", + " default: png"]}]}}, + {rate_limit, + #{value => ?T("Number"), + desc => + ?T("Limit any given JID by the number of avatars it is able " + "to convert per minute. This is to protect the server from " + "image convertion DoS. The default value is '10'.")}}]}. diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl index c1e93b94a..c1d45cf7c 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -29,7 +29,7 @@ -behaviour(gen_mod). %% API --export([start/2, stop/1, reload/3, +-export([start/2, stop/1, reload/3, mod_doc/0, depends/2, mod_opt_type/1, mod_options/1]). -export([filter_packet/1, filter_offline_msg/1, filter_subscription/2]). @@ -260,3 +260,54 @@ mod_options(_) -> {captcha, false}, {allow_local_users, true}, {allow_transports, true}]. + +mod_doc() -> + #{desc => + ?T("This module allows to block/log messages coming from an " + "unknown entity. If a writing entity is not in your roster, " + "you can let this module drop and/or log the message. " + "By default you'll just not receive message from that entity. " + "Enable this module if you want to drop SPAM messages."), + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("The option is supposed to be used when 'allow_local_users' " + "and 'allow_transports' are not enough. It's an ACL where " + "'deny' means the message will be rejected (or a CAPTCHA " + "would be generated for a presence, if configured), and " + "'allow' means the sender is whitelisted and the stanza " + "will pass through. The default value is 'none', which " + "means nothing is whitelisted.")}}, + {drop, + #{value => "true | false", + desc => + ?T("This option specifies if strangers messages should " + "be dropped or not. The default value is 'true'.")}}, + {log, + #{value => "true | false", + desc => + ?T("This option specifies if strangers' messages should " + "be logged (as info message) in ejabberd.log. " + "The default value is 'false'.")}}, + {allow_local_users, + #{value => "true | false", + desc => + ?T("This option specifies if strangers from the same " + "local host should be accepted or not. " + "The default value is 'true'.")}}, + {allow_transports, + #{value => "true | false", + desc => + ?T("If set to 'true' and some server's JID is in user's " + "roster, then messages from any user of this server " + "are accepted even if no subscription present. " + "The default value is 'true'.")}}, + {captcha, + #{value => "true | false", + desc => + ?T("Whether to generate CAPTCHA or not in response to " + "messages from strangers. See also section " + "https://docs.ejabberd.im/admin/configuration/#captcha" + "[CAPTCHA] of the Configuration Guide. " + "The default value is 'false'.")}}]}. diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index 3ddb10a6d..9b9f94dec 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -30,7 +30,7 @@ -protocol({xep, 191, '1.2'}). -export([start/2, stop/1, reload/3, process_iq/1, depends/2, - disco_features/5, mod_options/1]). + disco_features/5, mod_options/1, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -265,3 +265,11 @@ err_db_failure(#iq{lang = Lang} = IQ) -> mod_options(_Host) -> []. + +mod_doc() -> + #{desc => + [?T("The module implements " + "https://xmpp.org/extensions/xep-0191.html" + "[XEP-0191: Blocking Command]."), "", + ?T("This module depends on 'mod_privacy' where " + "all the configuration is performed.")]}. diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl index 9803e941c..c65d5314e 100644 --- a/src/mod_bosh.erl +++ b/src/mod_bosh.erl @@ -36,13 +36,14 @@ -export([start/2, stop/1, reload/3, process/2, open_session/2, close_session/1, find_session/1, clean_cache/1]). --export([depends/2, mod_opt_type/1, mod_options/1]). +-export([depends/2, mod_opt_type/1, mod_options/1, mod_doc/0]). -include("logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include("xmpp.hrl"). -include("ejabberd_http.hrl"). -include("bosh.hrl"). +-include("translate.hrl"). -callback init() -> any(). -callback open_session(binary(), pid()) -> ok | {error, any()}. @@ -197,6 +198,51 @@ mod_options(Host) -> {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. +mod_doc() -> + #{desc => + ?T("This module implements XMPP over BOSH as defined in " + "https://xmpp.org/extensions/xep-0124.html[XEP-0124] and " + "https://xmpp.org/extensions/xep-0206.html[XEP-0206]. BOSH " + "stands for Bidirectional-streams Over Synchronous HTTP. " + "It makes it possible to simulate long lived connections " + "required by XMPP over the HTTP protocol. In practice, " + "this module makes it possible to use XMPP in a browser without " + "Websocket support and more generally to have a way to use " + "XMPP while having to get through an HTTP proxy."), + opts => + [{json, + #{value => "true | false", + desc => ?T("This option has no effect.")}}, + {max_inactivity, + #{value => "timeout()", + desc => + ?T("The option defines the maximum inactivity period. " + "The default value is '30' seconds.")}}, + {queue_type, + #{value => "ram | file", + desc => + ?T("Same as top-level 'queue_type' option, but applied to this module only.")}}, + {ram_db_type, + #{value => "mnesia | sql | redis", + desc => + ?T("Same as 'default_ram_db' but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. + %%%---------------------------------------------------------------------- %%% Cache stuff %%%---------------------------------------------------------------------- diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 7a41d9ff2..8088c3614 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -49,12 +49,13 @@ handle_cast/2, terminate/2, code_change/3]). -export([user_send_packet/1, user_receive_packet/1, - c2s_presence_in/2, mod_opt_type/1, mod_options/1]). + c2s_presence_in/2, mod_opt_type/1, mod_options/1, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). -include("mod_caps.hrl"). +-include("translate.hrl"). -define(BAD_HASH_LIFETIME, 600). @@ -563,3 +564,32 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + [?T("This module implements " + "https://xmpp.org/extensions/xep-0115.html" + "[XEP-0115: Entity Capabilities]."), + ?T("The main purpose of the module is to provide " + "PEP functionality (see 'mod_pubsub').")], + opts => + [{db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index bda77f816..be30791dc 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -37,7 +37,7 @@ -export([user_send_packet/1, user_receive_packet/1, iq_handler/1, disco_features/5, is_carbon_copy/1, depends/2, - mod_options/1]). + mod_options/1, mod_doc/0]). -export([c2s_copy_session/2, c2s_session_opened/1, c2s_session_resumed/1]). %% For debugging purposes -export([list/2]). @@ -301,3 +301,10 @@ depends(_Host, _Opts) -> mod_options(_) -> []. + +mod_doc() -> + #{desc => + ?T("The module implements https://xmpp.org/extensions/xep-0280.html" + "[XEP-0280: Message Carbons]. " + "The module broadcasts messages on all connected " + "user resources (devices).")}. diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index 35eb6b733..525e2e5e0 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -32,6 +32,7 @@ %% gen_mod callbacks. -export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1]). +-export([mod_doc/0]). %% ejabberd_hooks callbacks. -export([filter_presence/1, filter_chat_states/1, @@ -42,6 +43,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -define(CSI_QUEUE_MAX, 100). @@ -151,6 +153,38 @@ mod_options(_) -> {queue_chat_states, true}, {queue_pep, true}]. +mod_doc() -> + #{desc => + [?T("This module allows for queueing certain types of stanzas " + "when a client indicates that the user is not actively using " + "the client right now (see https://xmpp.org/extensions/xep-0352.html" + "[XEP-0352: Client State Indication]). This can save bandwidth and " + "resources."), "", + ?T("A stanza is dropped from the queue if it's effectively obsoleted " + "by a new one (e.g., a new presence stanza would replace an old " + "one from the same client). The queue is flushed if a stanza arrives " + "that won't be queued, or if the queue size reaches a certain limit " + "(currently 100 stanzas), or if the client becomes active again.")], + opts => + [{queue_presence, + #{value => "true | false", + desc => + ?T("While a client is inactive, queue presence stanzas " + "that indicate (un)availability. The default value is 'true'.")}}, + {queue_chat_states, + #{value => "true | false", + desc => + ?T("Queue \"standalone\" chat state notifications (as defined in " + "https://xmpp.org/extensions/xep-0085.html" + "[XEP-0085: Chat State Notifications]) while a client " + "indicates inactivity. The default value is 'true'.")}}, + {queue_pep, + #{value => "true | false", + desc => + ?T("Queue PEP notifications while a client is inactive. " + "When the queue is flushed, only the most recent notification " + "of a given PEP node is delivered. The default value is 'true'.")}}]}. + -spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. depends(_Host, _Opts) -> []. diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 1a327064e..e3dfbcb55 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -36,7 +36,7 @@ adhoc_local_items/4, adhoc_local_commands/4, get_sm_identity/5, get_sm_features/5, get_sm_items/5, adhoc_sm_items/4, adhoc_sm_commands/4, mod_options/1, - depends/2]). + depends/2, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -1558,3 +1558,10 @@ tr(Lang, Text) -> translate:translate(Lang, Text). mod_options(_) -> []. + +mod_doc() -> + #{desc => + ?T("The module provides server configuration functionality via " + "https://xmpp.org/extensions/xep-0050.html" + "[XEP-0050: Ad-Hoc Commands]. This module requires " + "'mod_adhoc' to be loaded.")}. diff --git a/src/mod_delegation.erl b/src/mod_delegation.erl index 508cb84b6..0a3a34932 100644 --- a/src/mod_delegation.erl +++ b/src/mod_delegation.erl @@ -32,6 +32,7 @@ %% API -export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1]). +-export([mod_doc/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -82,6 +83,51 @@ mod_opt_type(namespaces) -> mod_options(_Host) -> [{namespaces, []}]. +mod_doc() -> + #{desc => + ?T("This module is an implementation of " + "https://xmpp.org/extensions/xep-0355.html" + "[XEP-0355: Namespace Delegation]. " + "Only admin mode has been implemented by now. " + "Namespace delegation allows external services to " + "handle IQ using specific namespace. This may be applied " + "for external PEP service."), + opts => + [{namespaces, + #{value => "{Namespace: Options}", + desc => + ?T("If you want to delegate namespaces to a component, " + "specify them in this option, and associate them " + "to an access rule. The 'Options' are:")}, + [{filtering, + #{value => ?T("Attributes"), + desc => + ?T("The list of attributes. Currently not used.")}}, + {access, + #{value => ?T("AccessName"), + desc => + ?T("The option defines which components are allowed " + "for namespace delegation. The default value is 'none'.")}}]}], + example => + ["access_rules:", + " external_pubsub:", + " allow: external_component", + " external_mam:", + " allow: external_component", + "", + "acl:", + " external_component:", + " server: sat-pubsub.example.org", + "", + "modules:", + " ...", + " mod_delegation:", + " namespaces:", + " urn:xmpp:mam:1:", + " access: external_mam", + " http://jabber.org/protocol/pubsub:", + " access: external_pubsub"]}. + depends(_, _) -> []. diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 4ec77e847..a7c5ad73f 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -37,7 +37,8 @@ get_local_features/5, get_local_services/5, process_sm_iq_items/1, process_sm_iq_info/1, get_sm_identity/5, get_sm_features/5, get_sm_items/5, - get_info/5, mod_opt_type/1, mod_options/1, depends/2]). + get_info/5, mod_opt_type/1, mod_options/1, depends/2, + mod_doc/0]). -include("logger.hrl"). -include("translate.hrl"). @@ -436,3 +437,70 @@ mod_options(_Host) -> [{extra_domains, []}, {server_info, []}, {name, ?T("ejabberd")}]. + +mod_doc() -> + #{desc => + ?T("This module adds support for " + "https://xmpp.org/extensions/xep-0030.html" + "[XEP-0030: Service Discovery]. With this module enabled, " + "services on your server can be discovered by XMPP clients."), + opts => + [{extra_domains, + #{value => "[Domain, ...]", + desc => + ?T("With this option, you can specify a list of extra " + "domains that are added to the Service Discovery item list. " + "The default value is an empty list.")}}, + {name, + #{value => ?T("Name"), + desc => + ?T("A name of the server in the Service Discovery. " + "This will only be displayed by special XMPP clients. " + "The default value is 'ejabberd'.")}}, + {server_info, + #{value => "[Info, ...]", + example => + ["server_info:", + " -", + " modules: all", + " name: abuse-addresses", + " urls: [mailto:abuse@shakespeare.lit]", + " -", + " modules: [mod_muc]", + " name: \"Web chatroom logs\"", + " urls: [http://www.example.org/muc-logs]", + " -", + " modules: [mod_disco]", + " name: feedback-addresses", + " urls:", + " - http://shakespeare.lit/feedback.php", + " - mailto:feedback@shakespeare.lit", + " - xmpp:feedback@shakespeare.lit", + " -", + " modules:", + " - mod_disco", + " - mod_vcard", + " name: admin-addresses", + " urls:", + " - mailto:xmpp@shakespeare.lit", + " - xmpp:admins@shakespeare.lit"], + desc => + ?T("Specify additional information about the server, " + "as described in https://xmpp.org/extensions/xep-0157.html" + "[XEP-0157: Contact Addresses for XMPP Services]. Every 'Info' " + "element in the list is constructed from the following options:")}, + [{modules, + #{value => "all | [Module, ...]", + desc => + ?T("The value can be the keyword 'all', in which case the " + "information is reported in all the services, " + "or a list of ejabberd modules, in which case the " + "information is only specified for the services provided " + "by those modules.")}}, + {name, + #{value => ?T("Name"), + desc => ?T("Any arbitrary name of the contact.")}}, + {urls, + #{value => "[URI, ...]", + desc => ?T("A list of contact URIs, such as " + "HTTP URLs, XMPP URIs and so on.")}}]}]}. diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl index 0d2473c15..6492501df 100644 --- a/src/mod_fail2ban.erl +++ b/src/mod_fail2ban.erl @@ -34,7 +34,7 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, - mod_opt_type/1, mod_options/1, depends/2]). + mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). %% ejabberd command. -export([get_commands_spec/0, unban/1]). @@ -254,3 +254,33 @@ mod_options(_Host) -> [{access, none}, {c2s_auth_ban_lifetime, timer:hours(1)}, {c2s_max_auth_failures, 20}]. + +mod_doc() -> + #{desc => + [?T("The module bans IPs that show the malicious signs. " + "Currently only C2S authentication failures are detected."), "", + ?T("Unlike the standalone program, 'mod_fail2ban' clears the " + "record of authentication failures after some time since the " + "first failure or on a successful authentication. " + "It also does not simply block network traffic, but " + "provides the client with a descriptive error message.")], + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("Specify an access rule for whitelisting IP " + "addresses or networks. If the rule returns 'allow' " + "for a given IP address, that address will never be " + "banned. The 'AccessName' should be of type 'ip'. " + "The default value is 'none'.")}}, + {c2s_auth_ban_lifetime, + #{value => "timeout()", + desc => + ?T("The lifetime of the IP ban caused by too many " + "C2S authentication failures. The default value is " + "'1' hour.")}}, + {c2s_max_auth_failures, + #{value => ?T("Number"), + desc => + ?T("The number of C2S authentication failures to " + "trigger the IP ban. The default value is '20'.")}}]}. diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index 34e118cc9..8890a3dd6 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -30,12 +30,13 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process/2, depends/2, - mod_options/1]). + mod_options/1, mod_doc/0]). -include("xmpp.hrl"). -include("logger.hrl"). -include("ejabberd_http.hrl"). -include("ejabberd_stacktrace.hrl"). +-include("translate.hrl"). -define(DEFAULT_API_VERSION, 0). @@ -520,3 +521,8 @@ hide_sensitive_args(NonListArgs) -> mod_options(_) -> []. + +mod_doc() -> + #{desc => + ?T("This module provides a ReST API to call " + "ejabberd commands using JSON data.")}. diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index f7d1b15ca..ed1444cc5 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -43,11 +43,12 @@ %% utility for other http modules -export([content_type/3]). --export([reopen_log/0, mod_opt_type/1, mod_options/1, depends/2]). +-export([reopen_log/0, mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("ejabberd_http.hrl"). -include_lib("kernel/include/file.hrl"). +-include("translate.hrl"). -record(state, {host, docroot, accesslog, accesslogfd, @@ -498,3 +499,87 @@ mod_options(_) -> {must_authenticate_with, []}, %% Required option docroot]. + +mod_doc() -> + #{desc => + ?T("This simple module serves files from the local disk over HTTP."), + opts => + [{accesslog, + #{value => ?T("Path"), + desc => + ?T("File to log accesses using an Apache-like format. " + "No log will be recorded if this option is not specified.")}}, + {docroot, + #{value => ?T("Path"), + desc => + ?T("Directory to serve the files from. " + "This is a mandatory option.")}}, + {content_types, + #{value => "{Extension: Type}", + desc => + ?T("Specify mappings of extension to content type. " + "There are several content types already defined. " + "With this option you can add new definitions " + "or modify existing ones."), + example => + [{?T("The default value is shown in the example below:"), + ["content_types:"| + [" " ++ binary_to_list(E) ++ ": " ++ binary_to_list(T) + || {E, T} <- ?DEFAULT_CONTENT_TYPES]]}]}}, + {default_content_type, + #{value => ?T("Type"), + desc => + ?T("Specify the content type to use for unknown extensions. " + "The default value is 'application/octet-stream'.")}}, + {custom_headers, + #{value => "{Name: Value}", + desc => + ?T("Indicate custom HTTP headers to be included in all responses. " + "There are no custom headers by default.")}}, + {directory_indices, + #{value => "[Index, ...]", + desc => + ?T("Indicate one or more directory index files, " + "similarly to Apache's 'DirectoryIndex' variable. " + "When an HTTP request hits a directory instead of a " + "regular file, those directory indices are looked in order, " + "and the first one found is returned. " + "The default value is an empty list.")}}, + {must_authenticate_with, + #{value => ?T("[{Username, Hostname}, ...]"), + desc => + ?T("List of accounts that are allowed to use this service. " + "Default value: '[]'.")}}], + example => + [{?T("This example configuration will serve the files from the " + "local directory '/var/www' in the address " + "'http://example.org:5280/pub/archive/'. In this example a new " + "content type 'ogg' is defined, 'png' is redefined, and 'jpg' " + "definition is deleted:"), + ["listen:", + " ...", + " -", + " port: 5280", + " module: ejabberd_http", + " request_handlers:", + " ...", + " /pub/archive: mod_http_fileserver", + " ...", + " ...", + "", + "modules:", + " ...", + " mod_http_fileserver:", + " docroot: /var/www", + " accesslog: /var/log/ejabberd/access.log", + " directory_indices:", + " - index.html", + " - main.htm", + " custom_headers:", + " X-Powered-By: Erlang/OTP", + " X-Fry: \"It's a widely-believed fact!\"", + " content_types:", + " .ogg: audio/ogg", + " .png: image/png", + " default_content_type: text/html", + " ..."]}]}. diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index 035aa3982..f2730e185 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -63,6 +63,7 @@ stop/1, reload/3, depends/2, + mod_doc/0, mod_opt_type/1, mod_options/1]). @@ -223,6 +224,182 @@ mod_options(Host) -> {rm_on_unregister, true}, {thumbnail, false}]. +mod_doc() -> + #{desc => + [?T("This module allows for requesting permissions to " + "upload a file via HTTP as described in " + "https://xmpp.org/extensions/xep-0363.html" + "[XEP-0363: HTTP File Upload]. If the request is accepted, " + "the client receives a URL for uploading the file and " + "another URL from which that file can later be downloaded."), "", + ?T("In order to use this module, it must be configured as " + "a 'request_handler' for 'ejabberd_http' listener.")], + opts => + [{host, + #{desc => ?T("Deprecated. Use 'hosts' instead.")}}, + {hosts, + #{value => ?T("[Host, ...]"), + desc => + ?T("This option defines the Jabber IDs of the service. " + "If the 'hosts' option is not specified, the only Jabber ID will " + "be the hostname of the virtual host with the prefix \"upload.\". " + "The keyword '@HOST@' is replaced with the real virtual host name.")}}, + {name, + #{value => ?T("Name"), + desc => + ?T("A name of the service in the Service Discovery. " + "This will only be displayed by special XMPP clients. " + "The default value is \"HTTP File Upload\".")}}, + {access, + #{value => ?T("AccessName"), + desc => + ?T("This option defines the access rule to limit who is " + "permitted to use the HTTP upload service. " + "The default value is 'local'. If no access rule of " + "that name exists, no user will be allowed to use the service.")}}, + {max_size, + #{value => ?T("Size"), + desc => + ?T("This option limits the acceptable file size. " + "Either a number of bytes (larger than zero) or " + "'infinity' must be specified. " + "The default value is '104857600'.")}}, + {secret_length, + #{value => ?T("Length"), + desc => + ?T("This option defines the length of the random " + "string included in the GET and PUT URLs generated " + "by 'mod_http_upload'. The minimum length is 8 characters, " + "but it is recommended to choose a larger value. " + "The default value is '40'.")}}, + {jid_in_url, + #{value => "node | sha1", + desc => + ?T("When this option is set to 'node', the node identifier " + "of the user's JID (i.e., the user name) is included in " + "the GET and PUT URLs generated by 'mod_http_upload'. " + "Otherwise, a SHA-1 hash of the user's bare JID is " + "included instead. The default value is 'sha1'.")}}, + {thumbnail, + #{value => "true | false", + desc => + ?T("This option specifies whether ejabberd should create " + "thumbnails of uploaded images. If a thumbnail is created, " + "a element that contains the download " + "and some metadata is returned with the PUT response. " + "The default value is 'false'.")}}, + {file_mode, + #{value => ?T("Permission"), + desc => + ?T("This option defines the permission bits of uploaded files. " + "The bits are specified as an octal number (see the chmod(1) " + "manual page) within double quotes. For example: \"0644\". " + "The default is undefined, which means no explicit permissions " + "will be set.")}}, + {dir_mode, + #{value => ?T("Permission"), + desc => + ?T("This option defines the permission bits of the 'docroot' " + "directory and any directories created during file uploads. " + "The bits are specified as an octal number (see the chmod(1) " + "manual page) within double quotes. For example: \"0755\". " + "The default is undefined, which means no explicit permissions " + "will be set.")}}, + {docroot, + #{value => ?T("Path"), + desc => + ?T("Uploaded files are stored below the directory specified " + "(as an absolute path) with this option. The keyword " + "@HOME@ is replaced with the home directory of the user " + "running ejabberd, and the keyword @HOST@ with the virtual " + "host name. The default value is \"@HOME@/upload\".")}}, + {put_url, + #{value => ?T("URL"), + desc => + ?T("This option specifies the initial part of the PUT URLs " + "used for file uploads. The keyword @HOST@ is replaced " + "with the virtual host name. NOTE: different virtual " + "hosts cannot use the same PUT URL. " + "The default value is \"http://@HOST@:5444\".")}}, + {get_url, + #{value => ?T("URL"), + desc => + ?T("This option specifies the initial part of the GET URLs " + "used for downloading the files. By default, it is set " + "to the same value as 'put_url'. The keyword @HOST@ is " + "replaced with the virtual host name. NOTE: if GET requests " + "are handled by 'mod_http_upload', the 'get_url' must match the " + "'put_url'. Setting it to a different value only makes " + "sense if an external web server or 'mod_http_fileserver' " + "is used to serve the uploaded files.")}}, + {service_url, + #{desc => ?T("Deprecated.")}}, + {custom_headers, + #{value => "{Name: Value}", + desc => + ?T("This option specifies additional header fields to be " + "included in all HTTP responses. By default no custom " + "headers are included.")}}, + {external_secret, + #{value => ?T("Text"), + desc => + ?T("This option makes it possible to offload all HTTP " + "Upload processing to a separate HTTP server. " + "Both ejabberd and the HTTP server should share this " + "secret and behave exactly as described at " + "https://modules.prosody.im/mod_http_upload_external.html" + "[Prosody's mod_http_upload_external] in the " + "'Implementation' section. There is no default value.")}}, + {rm_on_unregister, + #{value => "true | false", + desc => + ?T("This option specifies whether files uploaded by a user " + "should be removed when that user is unregistered. " + "The default value is 'true'.")}}, + {vcard, + #{value => ?T("vCard"), + desc => + ?T("A custom vCard of the service that will be displayed " + "by some XMPP clients in Service Discovery. The value of " + "'vCard' is a YAML map constructed from an XML representation " + "of vCard. Since the representation has no attributes, " + "the mapping is straightforward."), + example => + [{?T("For example, the following XML representation of vCard:"), + ["", + " Conferences", + " ", + " ", + " Elm Street", + " ", + ""]}, + {?T("will be translated to:"), + ["vcard:", + " fn: Conferences", + " adr:", + " -", + " work: true", + " street: Elm Street"]}]}}], + example => + ["listen:", + " ...", + " -", + " port: 5443", + " module: ejabberd_http", + " tls: true", + " request_handlers:", + " ...", + " /upload: mod_http_upload", + " ...", + " ...", + "", + "modules:", + " ...", + " mod_http_upload:", + " docroot: /ejabberd/upload", + " put_url: \"https://@HOST@:5443/upload\"", + " ..."]}. + -spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. depends(_Host, _Opts) -> []. diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl index b4c2203c3..330ad8cf4 100644 --- a/src/mod_http_upload_quota.erl +++ b/src/mod_http_upload_quota.erl @@ -37,6 +37,7 @@ -export([start/2, stop/1, depends/2, + mod_doc/0, mod_opt_type/1, mod_options/1]). @@ -53,6 +54,7 @@ -include("jid.hrl"). -include("logger.hrl"). +-include("translate.hrl"). -include_lib("kernel/include/file.hrl"). -record(state, @@ -92,6 +94,57 @@ mod_options(_) -> {access_hard_quota, hard_upload_quota}, {max_days, infinity}]. +mod_doc() -> + #{desc => + [?T("This module adds quota support for mod_http_upload."), "", + ?T("This module depends on 'mod_http_upload'.")], + opts => + [{max_days, + #{value => ?T("Days"), + desc => + ?T("If a number larger than zero is specified, " + "any files (and directories) older than this " + "number of days are removed from the subdirectories " + "of the 'docroot' directory, once per day. " + "The default value is 'infinity'.")}}, + {access_soft_quota, + #{value => ?T("AccessName"), + desc => + ?T("This option defines which access rule is used " + "to specify the \"soft quota\" for the matching JIDs. " + "That rule must yield a positive number of megabytes " + "for any JID that is supposed to have a quota limit. " + "See the description of the 'access_hard_quota' option " + "for details. The default value is 'soft_upload_quota'.")}}, + {access_hard_quota, + #{value => ?T("AccessName"), + desc => + ?T("This option defines which access rule is used to " + "specify the \"hard quota\" for the matching JIDs. " + "That rule must yield a positive number for any " + "JID that is supposed to have a quota limit. " + "This is the number of megabytes a corresponding " + "user may upload. When this threshold is exceeded, " + "ejabberd deletes the oldest files uploaded by that " + "user until their disk usage equals or falls below " + "the specified soft quota (see 'access_soft_quota'). " + "The default value is 'hard_upload_quota'.")}}], + example => + ["shaper_rules:", + " ...", + " soft_upload_quota:", + " 1000: all # MiB", + " hard_upload_quota:", + " 1100: all # MiB", + " ...", + "", + "modules:", + " ...", + " mod_http_upload: {}", + " mod_http_upload_quota:", + " max_days: 100", + " ..."]}. + -spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. depends(_Host, _Opts) -> [{mod_http_upload, hard}]. diff --git a/src/mod_jidprep.erl b/src/mod_jidprep.erl index 4aeb68ec6..c09c2157b 100644 --- a/src/mod_jidprep.erl +++ b/src/mod_jidprep.erl @@ -31,6 +31,7 @@ %% gen_mod callbacks. -export([start/2, stop/1, reload/3, mod_opt_type/1, mod_options/1, depends/2]). +-export([mod_doc/0]). %% ejabberd_hooks callbacks. -export([disco_local_features/5]). @@ -71,6 +72,22 @@ mod_opt_type(access) -> mod_options(_Host) -> [{access, local}]. +mod_doc() -> + #{desc => + ?T("This module allows XMPP clients to ask the " + "server to normalize a JID as per the rules specified " + "in https://tools.ietf.org/html/rfc6122" + "[RFC 6122: XMPP Address Format]. This might be useful " + "for clients in certain constrained environments, " + "or for testing purposes."), + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("This option defines which access rule will " + "be used to control who is allowed to use this " + "service. The default value is 'local'.")}}]}. + %%-------------------------------------------------------------------- %% Register/unregister hooks. %%-------------------------------------------------------------------- diff --git a/src/mod_last.erl b/src/mod_last.erl index 28b66be08..2766fcf70 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -34,7 +34,7 @@ -export([start/2, stop/1, reload/3, process_local_iq/1, export/1, process_sm_iq/1, on_presence_update/4, import_info/0, import/5, import_start/2, store_last_info/4, get_last_info/2, - remove_user/2, mod_opt_type/1, mod_options/1, + remove_user/2, mod_opt_type/1, mod_options/1, mod_doc/0, register_user/2, depends/2, privacy_check_packet/4]). -include("logger.hrl"). @@ -331,3 +331,33 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + ?T("This module adds support for " + "https://xmpp.org/extensions/xep-0012.html" + "[XEP-0012: Last Activity]. It can be used " + "to discover when a disconnected user last accessed " + "the server, to know when a connected user was last " + "active on the server, or to query the uptime of the ejabberd server."), + opts => + [{db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_legacy_auth.erl b/src/mod_legacy_auth.erl index a48ef8de5..1b25be54b 100644 --- a/src/mod_legacy_auth.erl +++ b/src/mod_legacy_auth.erl @@ -25,7 +25,7 @@ -protocol({xep, 78, '2.5'}). %% gen_mod API --export([start/2, stop/1, reload/3, depends/2, mod_options/1]). +-export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_doc/0]). %% hooks -export([c2s_unauthenticated_packet/2, c2s_stream_features/2]). @@ -58,6 +58,15 @@ depends(_Host, _Opts) -> mod_options(_) -> []. +mod_doc() -> + #{desc => + [?T("The module implements " + "https://xmpp.org/extensions/xep-0078.html" + "[XEP-0078: Non-SASL Authentication]."), "", + ?T("NOTE: This type of authentication was obsoleted in " + "2008 and you unlikely need this module unless " + "you have something like outdated Jabber bots.")]}. + -spec c2s_unauthenticated_packet(c2s_state(), iq()) -> c2s_state() | {stop, c2s_state()}. c2s_unauthenticated_packet(State, #iq{type = T, sub_els = [_]} = IQ) diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 7bc508db4..5b6bf0a05 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -32,7 +32,7 @@ -behaviour(gen_mod). %% API --export([start/2, stop/1, reload/3, depends/2]). +-export([start/2, stop/1, reload/3, depends/2, mod_doc/0]). -export([sm_receive_packet/1, user_receive_packet/1, user_send_packet/1, user_send_packet_strip_tag/1, process_iq_v0_2/1, process_iq_v0_3/1, @@ -1426,3 +1426,77 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + ?T("This module implements " + "https://xmpp.org/extensions/xep-0313.html" + "[XEP-0313: Message Archive Management]. " + "Compatible XMPP clients can use it to store their " + "chat history on the server."), + opts => + [{assume_mam_usage, + #{value => "true | false", + desc => + ?T("This option determines how ejabberd's " + "stream management code (see 'mod_stream_mgmt') " + "handles unacknowledged messages when the " + "connection is lost. Usually, such messages are " + "either bounced or resent. However, neither is " + "done for messages that were stored in the user's " + "MAM archive if this option is set to 'true'. In " + "this case, ejabberd assumes those messages will " + "be retrieved from the archive. " + "The default value is 'false'.")}}, + {default, + #{value => "always | never | roster", + desc => + ?T("The option defines default policy for chat history. " + "When 'always' is set every chat message is stored. " + "With 'roster' only chat history with contacts from " + "user's roster is stored. And 'never' fully disables " + "chat history. Note that a client can change its " + "policy via protocol commands. " + "The default value is 'never'.")}}, + {request_activates_archiving, + #{value => "true | false", + desc => + ?T("If the value is 'true', no messages are stored " + "for a user until their client issue a MAM request, " + "regardless of the value of the 'default' option. " + "Once the server received a request, that user's " + "messages are archived as usual. " + "The default value is 'false'.")}}, + {compress_xml, + #{value => "true | false", + desc => + ?T("When enabled, new messages added to archives are " + "compressed using a custom compression algorithm. " + "This feature works only with SQL backends. " + "The default value is 'false'.")}}, + {clear_archive_on_room_destroy, + #{value => "true | false", + desc => + ?T("Whether to destroy message archive of a room " + "(see 'mod_muc') when it gets destroyed. " + "The default value is 'true'.")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index 297f3eeb5..ab6e214b1 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -30,9 +30,10 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -export([start/2, stop/1, mod_opt_type/1, mod_options/1, depends/2, reload/3]). --export([push/2]). +-export([push/2, mod_doc/0]). -export([offline_message_hook/1, sm_register_connection_hook/3, sm_remove_connection_hook/3, user_send_packet/1, user_receive_packet/1, @@ -188,3 +189,32 @@ mod_opt_type(port) -> mod_options(_) -> [{ip, {127,0,0,1}}, {port, 11111}]. + +mod_doc() -> + #{desc => + [?T("This module sends events to external backend " + "(by now only https://github.com/processone/grapherl" + "[grapherl] is supported). Supported events are:"), "", + "- sm_register_connection", "", + "- sm_remove_connection", "", + "- user_send_packet", "", + "- user_receive_packet", "", + "- s2s_send_packet", "", + "- s2s_receive_packet", "", + "- register_user", "", + "- remove_user", "", + "- offline_message", "", + ?T("When enabled, every call to these hooks triggers " + "a counter event to be sent to the external backend.")], + opts => + [{ip, + #{value => ?T("IPv4Address"), + desc => + ?T("IPv4 address where the backend is located. " + "The default value is '127.0.0.1'.")}}, + {port, + #{value => ?T("Port"), + desc => + ?T("An internet port number at which the backend " + "is listening for incoming connections/packets. " + "The default value is '11111'.")}}]}. diff --git a/src/mod_mix.erl b/src/mod_mix.erl index 8a55f1cbe..bc69c1aa1 100644 --- a/src/mod_mix.erl +++ b/src/mod_mix.erl @@ -30,6 +30,7 @@ -export([route/1]). %% gen_mod callbacks -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). +-export([mod_doc/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, format_status/2]). @@ -96,6 +97,43 @@ mod_options(Host) -> {name, ?T("Channels")}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}]. +mod_doc() -> + #{desc => + [?T("This module is an experimental implementation of " + "https://xmpp.org/extensions/xep-0369.html" + "[XEP-0369: Mediated Information eXchange (MIX)]. " + "MIX support was added in ejabberd 16.03 as an " + "experimental feature, updated in 19.02, and is not " + "yet ready to use in production. It's asserted that " + "the MIX protocol is going to replace the MUC protocol " + "in the future (see 'mod_muc')."), "", + ?T("The module depends on 'mod_mam'.")], + opts => + [{access_create, + #{value => ?T("AccessName"), + desc => + ?T("An access rule to control MIX channels creations. " + "The default value is 'all'.")}}, + {host, + #{desc => ?T("Deprecated. Use 'hosts' instead.")}}, + {hosts, + #{value => ?T("[Host, ...]"), + desc => + ?T("This option defines the Jabber IDs of the service. " + "If the 'hosts' option is not specified, the only Jabber ID will " + "be the hostname of the virtual host with the prefix \"mix.\". " + "The keyword '@HOST@' is replaced with the real virtual host name.")}}, + {name, + #{value => ?T("Name"), + desc => + ?T("A name of the service in the Service Discovery. " + "This will only be displayed by special XMPP clients. " + "The default value is 'Channels'.")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}]}. + -spec route(stanza()) -> ok. route(#iq{} = IQ) -> ejabberd_router:process_iq(IQ); diff --git a/src/mod_mix_pam.erl b/src/mod_mix_pam.erl index 7b01965c7..1e5b32ba4 100644 --- a/src/mod_mix_pam.erl +++ b/src/mod_mix_pam.erl @@ -26,6 +26,7 @@ %% gen_mod callbacks -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). +-export([mod_doc/0]). %% Hooks and handlers -export([bounce_sm_packet/1, disco_sm_features/5, @@ -103,6 +104,40 @@ mod_options(Host) -> {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. +mod_doc() -> + #{desc => + [?T("This module implements " + "https://xmpp.org/extensions/xep-0405.html" + "[XEP-0405: Mediated Information eXchange (MIX): " + "Participant Server Requirements]. " + "The module is needed if MIX compatible clients " + "on your server are going to join MIX channels " + "(either on your server or on any remote servers)."), "", + ?T("NOTE: 'mod_mix' is not required for this module " + "to work, however, without 'mod_mix_pam' the MIX " + "functionality of your local XMPP clients will be impaired.")], + opts => + [{db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. + -spec bounce_sm_packet({term(), stanza()}) -> {term(), stanza()}. bounce_sm_packet({_, #message{to = #jid{lresource = <<>>} = To, from = From, diff --git a/src/mod_mqtt.erl b/src/mod_mqtt.erl index 60da1b796..c74200d5e 100644 --- a/src/mod_mqtt.erl +++ b/src/mod_mqtt.erl @@ -23,6 +23,7 @@ %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_opt_type/1]). +-export([mod_doc/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -39,6 +40,7 @@ -include("logger.hrl"). -include("mqtt.hrl"). +-include("translate.hrl"). -define(MQTT_TOPIC_CACHE, mqtt_topic_cache). -define(MQTT_PAYLOAD_CACHE, mqtt_payload_cache). @@ -259,6 +261,91 @@ listen_options() -> {tls, false}, {tls_verify, false}]. +%%%=================================================================== +%%% Doc +%%%=================================================================== +mod_doc() -> + #{desc => + ?T("This module adds support for the MQTT protocol " + "version '3.1.1' and '5.0'."), + opts => + [{access_subscribe, + #{value => "{TopicFilter: AccessName}", + desc => + ?T("Access rules to restrict access to topics " + "for subscribers. By default there are no restrictions.")}}, + {access_publish, + #{value => "{TopicFilter: AccessName}", + desc => + ?T("Access rules to restrict access to topics " + "for publishers. By default there are no restrictions.")}}, + {max_queue, + #{value => ?T("Size"), + desc => + ?T("Maximum queue size for outgoing packets. " + "The default value is '5000'.")}}, + {session_expiry, + #{value => "timeout()", + desc => + ?T("The option specifies how long to wait for " + "an MQTT session resumption. When '0' is set, " + "the session gets destroyed when the underlying " + "client connection is closed. The default value is " + "'5' minutes.")}}, + {max_topic_depth, + #{value => ?T("Depth"), + desc => + ?T("The maximum topic depth, i.e. the number of " + "slashes ('/') in the topic. The default " + "value is '8'.")}}, + {max_topic_aliases, + #{value => "0..65535", + desc => + ?T("The maximum number of aliases a client " + "is able to associate with the topics. " + "The default value is '100'.")}}, + {match_retained_limit, + #{value => "pos_integer() | infinity", + desc => + ?T("The option limits the number of retained messages " + "returned to a client when it subscribes to some " + "topic filter. The default value is '1000'.")}}, + {queue_type, + #{value => "ram | file", + desc => + ?T("Same as top-level 'queue_type' option, " + "but applied to this module only.")}}, + {ram_db_type, + #{value => "mnesia", + desc => + ?T("Same as top-level 'default_ram_db' option, " + "but applied to this module only.")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, " + "but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, " + "but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, " + "but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, " + "but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, " + "but applied to this module only.")}}]}. + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/src/mod_muc.erl b/src/mod_muc.erl index afbefa28a..303f306cd 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -36,6 +36,7 @@ stop/1, start_link/2, reload/3, + mod_doc/0, room_destroyed/4, store_room/4, store_room/5, @@ -1276,3 +1277,380 @@ mod_options(Host) -> {allow_private_messages_from_visitors,anyone}, {max_users,200}, {presence_broadcast,[moderator,participant,visitor]}]}]. + +mod_doc() -> + #{desc => + ?T("This module provides support for https://xmpp.org/extensions/xep-0045.html" + "[XEP-0045: Multi-User Chat]. Users can discover existing rooms, " + "join or create them. Occupants of a room can chat in public or have private chats."), + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("You can specify who is allowed to use the Multi-User Chat service. " + "By default everyone is allowed to use it.")}}, + {access_admin, + #{value => ?T("AccessName"), + desc => + ?T("This option specifies who is allowed to administrate " + "the Multi-User Chat service. The default value is 'none', " + "which means that only the room creator can administer " + "their room. The administrators can send a normal message " + "to the service JID, and it will be shown in all active " + "rooms as a service message. The administrators can send a " + "groupchat message to the JID of an active room, and the " + "message will be shown in the room as a service message.")}}, + {access_create, + #{value => ?T("AccessName"), + desc => + ?T("To configure who is allowed to create new rooms at the " + "Multi-User Chat service, this option can be used. " + "By default any account in the local ejabberd server is " + "allowed to create rooms.")}}, + {access_persistent, + #{value => ?T("AccessName"), + desc => + ?T("To configure who is allowed to modify the 'persistent' room option. " + "By default any account in the local ejabberd server is allowed to " + "modify that option.")}}, + {access_mam, + #{value => ?T("AccessName"), + desc => + ?T("To configure who is allowed to modify the 'mam' room option. " + "By default any account in the local ejabberd server is allowed to " + "modify that option.")}}, + {access_register, + #{value => ?T("AccessName"), + desc => + ?T("This option specifies who is allowed to register nickname " + "within the Multi-User Chat service. The default is 'all' for " + "backward compatibility, which means that any user is allowed " + "to register any free nick.")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Define the type of persistent storage where the module will " + "store room information. The default is the storage defined " + "by the global option 'default_db', or 'mnesia' if omitted.")}}, + {ram_db_type, + #{value => "mnesia", + desc => + ?T("Define the type of volatile (in-memory) storage where the module " + "will store room information. The only available value for this " + "module is 'mnesia'.")}}, + {hibernation_timeout, + #{value => "infinity | Seconds", + desc => + ?T("Timeout before hibernating the room process, expressed " + "in seconds. The default value is 'infinity'.")}}, + {history_size, + #{value => ?T("Size"), + desc => + ?T("A small history of the current discussion is sent to users " + "when they enter the room. With this option you can define the " + "number of history messages to keep and send to users joining the room. " + "The value is a non-negative integer. Setting the value to 0 disables " + "the history feature and, as a result, nothing is kept in memory. " + "The default value is 20. This value affects all rooms on the service. " + "NOTE: modern XMPP clients rely on Message Archives (XEP-0313), so feel " + "free to disable the history feature if you're only using modern clients " + "and have 'mod_mam' module loaded.")}}, + {host, #{desc => ?T("Deprecated. Use 'hosts' instead.")}}, + {hosts, + #{value => ?T("[Host, ...]"), + desc => + ?T("This option defines the Jabber IDs of the service. " + "If the 'hosts' option is not specified, the only Jabber ID will " + "be the hostname of the virtual host with the prefix \"conference.\". " + "The keyword '@HOST@' is replaced with the real virtual host name.")}}, + {name, + #{value => "string()", + desc => + ?T("The value of the service name. This name is only visible in some " + "clients that support https://xmpp.org/extensions/xep-0030.html" + "[XEP-0030: Service Discovery]. The default is 'Chatrooms'.")}}, + {max_room_desc, + #{value => ?T("Number"), + desc => + ?T("This option defines the maximum number of characters that " + "Room Description can have when configuring the room. " + "The default value is 'infinity'.")}}, + {max_room_id, + #{value => ?T("Number"), + desc => + ?T("This option defines the maximum number of characters that " + "Room ID can have when creating a new room. " + "The default value is 'infinity'.")}}, + {max_room_name, + #{value => ?T("Number"), + desc => + ?T("This option defines the maximum number of characters " + "that Room Name can have when configuring the room. " + "The default value is 'infinity'.")}}, + {max_rooms_discoitems, + #{value => ?T("Number"), + desc => + ?T("When there are more rooms than this 'Number', " + "only the non-empty ones are returned in a Service Discovery query. " + "The default value is '100'.")}}, + {max_user_conferences, + #{value => ?T("Number"), + desc => + ?T("This option defines the maximum number of rooms that any " + "given user can join. The default value is '100'. This option " + "is used to prevent possible abuses. Note that this is a soft " + "limit: some users can sometimes join more conferences in " + "cluster configurations.")}}, + {max_users, + #{value => ?T("Number"), + desc => + ?T("This option defines at the service level, the maximum " + "number of users allowed per room. It can be lowered in " + "each room configuration but cannot be increased in " + "individual room configuration. The default value is '200'.")}}, + {max_users_admin_threshold, + #{value => ?T("Number"), + desc => + ?T("This option defines the number of service admins or room " + "owners allowed to enter the room when the maximum number " + "of allowed occupants was reached. The default limit is '5'.")}}, + {max_users_presence, + #{value => ?T("Number"), + desc => + ?T("This option defines after how many users in the room, " + "it is considered overcrowded. When a MUC room is considered " + "overcrowed, presence broadcasts are limited to reduce load, " + "traffic and excessive presence \"storm\" received by participants.")}}, + {min_message_interval, + #{value => ?T("Number"), + desc => + ?T("This option defines the minimum interval between two " + "messages send by an occupant in seconds. This option " + "is global and valid for all rooms. A decimal value can be used. " + "When this option is not defined, message rate is not limited. " + "This feature can be used to protect a MUC service from occupant " + "abuses and limit number of messages that will be broadcasted by " + "the service. A good value for this minimum message interval is 0.4 second. " + "If an occupant tries to send messages faster, an error is send back " + "explaining that the message has been discarded and describing the " + "reason why the message is not acceptable.")}}, + {min_presence_interval, + #{value => ?T("Number"), + desc => + ?T("This option defines the minimum of time between presence " + "changes coming from a given occupant in seconds. " + "This option is global and valid for all rooms. A decimal " + "value can be used. When this option is not defined, no " + "restriction is applied. This option can be used to protect " + "a MUC service for occupants abuses. If an occupant tries " + "to change its presence more often than the specified interval, " + "the presence is cached by ejabberd and only the last presence " + "is broadcasted to all occupants in the room after expiration " + "of the interval delay. Intermediate presence packets are " + "silently discarded. A good value for this option is 4 seconds.")}}, + {queue_type, + #{value => "ram | file", + desc => + ?T("Same as top-level 'queue_type' option, but applied to this module only.")}}, + {regexp_room_id, + #{value => "string()", + desc => + ?T("This option defines the regular expression that a Room ID " + "must satisfy to allow the room creation. The default value " + "is the empty string.")}}, + {preload_rooms, + #{value => "true | false", + desc => + ?T("Whether to load all persistent rooms in memory on startup. " + "If disabled, the room is only loaded on first participant join. " + "The default is 'true'. It makes sense to disable room preloading " + "when the number of rooms is high: this will improve server startup " + "time and memory consumption.")}}, + {room_shaper, + #{value => "none | ShaperName", + desc => + ?T("This option defines shaper for the MUC rooms. " + "The default value is 'none'.")}}, + {user_message_shaper, + #{value => "none | ShaperName", + desc => + ?T("This option defines shaper for the users messages. " + "The default value is 'none'.")}}, + {user_presence_shaper, + #{value => "none | ShaperName", + desc => + ?T("This option defines shaper for the users presences. " + "The default value is 'none'.")}}, + {vcard, + #{value => ?T("vCard"), + desc => + ?T("A custom vCard of the service that will be displayed " + "by some XMPP clients in Service Discovery. The value of " + "'vCard' is a YAML map constructed from an XML representation " + "of vCard. Since the representation has no attributes, " + "the mapping is straightforward."), + example => + [{?T("For example, the following XML representation of vCard:"), + ["", + " Conferences", + " ", + " ", + " Elm Street", + " ", + ""]}, + {?T("will be translated to:"), + ["vcard:", + " fn: Conferences", + " adr:", + " -", + " work: true", + " street: Elm Street"]}]}}, + {default_room_options, + #{value => ?T("Options"), + desc => + ?T("This option allows to define the desired " + "default room options. Note that the creator of a room " + "can modify the options of his room at any time using an " + "XMPP client with MUC capability. The 'Options' are:")}, + [{allow_change_subj, + #{value => "true | false", + desc => + ?T("Allow occupants to change the subject. " + "The default value is 'true'.")}}, + {allow_private_messages, + #{value => "true | false", + desc => + ?T("Occupants can send private messages to other occupants. " + "The default value is 'true'.")}}, + {allow_query_users, + #{value => "true | false", + desc => + ?T("Occupants can send IQ queries to other occupants. " + "The default value is 'true'.")}}, + {allow_user_invites, + #{value => "true | false", + desc => + ?T("Allow occupants to send invitations. " + "The default value is 'false'.")}}, + {allow_visitor_nickchange, + #{value => "true | false", + desc => ?T("Allow visitors to change nickname. " + "The default value is 'true'.")}}, + {allow_visitor_status, + #{value => "true | false", + desc => + ?T("Allow visitors to send status text in presence updates. " + "If disallowed, the status text is stripped before broadcasting " + "the presence update to all the room occupants. " + "The default value is 'true'.")}}, + {anonymous, + #{value => "true | false", + desc => + ?T("The room is anonymous: occupants don't see the real " + "JIDs of other occupants. Note that the room moderators " + "can always see the real JIDs of the occupants. " + "The default value is 'true'.")}}, + {captcha_protected, + #{value => "true | false", + desc => + ?T("When a user tries to join a room where they have no " + "affiliation (not owner, admin or member), the room " + "requires them to fill a CAPTCHA challenge (see section " + "https://docs.ejabberd.im/admin/configuration/#captcha[CAPTCHA] " + "in order to accept their join in the room. " + "The default value is 'false'.")}}, + {lang, + #{value => ?T("Language"), + desc => + ?T("Preferred language for the discussions in the room. " + "The language format should conform to RFC 5646. " + "There is no value by default.")}}, + {logging, + #{value => "true | false", + desc => + ?T("The public messages are logged using 'mod_muc_log'. " + "The default value is 'false'.")}}, + {members_by_default, + #{value => "true | false", + desc => + ?T("The occupants that enter the room are participants " + "by default, so they have \"voice\". " + "The default value is 'true'.")}}, + {members_only, + #{value => "true | false", + desc => + ?T("Only members of the room can enter. " + "The default value is 'false'.")}}, + {moderated, + #{value => "true | false", + desc => + ?T("Only occupants with \"voice\" can send public messages. " + "The default value is 'true'.")}}, + {password_protected, + #{value => "true | false", + desc => + ?T("The password is required to enter the room. " + "The default value is 'false'.")}}, + {password, + #{value => ?T("Password"), + desc => + ?T("Password of the room. Implies option 'password_protected' " + "set to 'true'. There is no default value.")}}, + {persistent, + #{value => "true | false", + desc => + ?T("The room persists even if the last participant leaves. " + "The default value is 'false'.")}}, + {public, + #{value => "true | false", + desc => + ?T("The room is public in the list of the MUC service, " + "so it can be discovered. MUC admins and room participants " + "will see private rooms in Service Discovery if their XMPP " + "client supports this feature. " + "The default value is 'true'.")}}, + {public_list, + #{value => "true | false", + desc => + ?T("The list of participants is public, without requiring " + "to enter the room. The default value is 'true'.")}}, + {mam, + #{value => "true | false", + desc => + ?T("Enable message archiving. Implies mod_mam is enabled. " + "The default value is 'false'.")}}, + {allow_subscription, + #{value => "true | false", + desc => + ?T("Allow users to subscribe to room events as described in " + "https://docs.ejabberd.im/developer/xmpp-clients-bots/extensions/muc-sub/" + "[Multi-User Chat Subscriptions]. " + "The default value is 'false'.")}}, + {title, + #{value => ?T("Room Title"), + desc => + ?T("A human-readable title of the room. " + "There is no default value")}}, + {allow_private_messages_from_visitors, + #{value => "anyone | moderators | nobody", + desc => + ?T("Visitors can send private messages to other occupants. " + "The default value is 'anyone' which means visitors " + "can send private messages to any occupant.")}}, + {max_users, + #{value => ?T("Number"), + desc => + ?T("Maximum number of occupants in the room. " + "The default value is '200'.")}}, + {presence_broadcast, + #{value => "[moderator | participant | visitor, ...]", + desc => + ?T("List of roles for which presence is broadcasted. " + "The list can contain one or several of: 'moderator', " + "'participant', 'visitor'. The default value is shown " + "in the example below:"), + example => + ["presence_broadcast:", + " - moderator", + " - participant", + " - visitor"]}}]}]}. diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 2163b5e3e..9a3ed7602 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -28,7 +28,7 @@ -behaviour(gen_mod). --export([start/2, stop/1, reload/3, depends/2, +-export([start/2, stop/1, reload/3, depends/2, mod_doc/0, muc_online_rooms/1, muc_online_rooms_by_regex/2, muc_register_nick/3, muc_unregister_nick/2, create_room_with_opts/4, create_room/3, destroy_room/2, @@ -1315,3 +1315,10 @@ find_hosts(ServerHost) -> end. mod_options(_) -> []. + +mod_doc() -> + #{desc => + [?T("This module provides commands to administer local MUC " + "services and their MUC rooms. It also provides simple " + "WebAdmin pages to view the existing rooms."), "", + ?T("This module depends on 'mod_muc'.")]}. diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index b37f7cc7d..69362d2be 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -39,7 +39,7 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, - mod_opt_type/1, mod_options/1, depends/2]). + mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -988,3 +988,124 @@ mod_options(_) -> {timezone, local}, {url, undefined}, {top_link, {<<"/">>, <<"Home">>}}]. + +mod_doc() -> + #{desc => + [?T("This module enables optional logging " + "of Multi-User Chat (MUC) public " + "conversations to HTML. Once you enable " + "this module, users can join a room using a " + "MUC capable XMPP client, and if they have " + "enough privileges, they can request the " + "configuration form in which they can set " + "the option to enable room logging."), "", + ?T("Features:"), "", + ?T("- Room details are added on top of each page: " + "room title, JID, author, subject and configuration."), "", + ?T("- The room JID in the generated HTML is a link " + "to join the room (using XMPP URI)."), "", + ?T("- Subject and room configuration changes are tracked " + "and displayed."), "", + ?T("- Joins, leaves, nick changes, kicks, bans and '/me' " + "are tracked and displayed, including the reason if available."), "", + ?T("- Generated HTML files are XHTML 1.0 Transitional and " + "CSS compliant."), "", + ?T("- Timestamps are self-referencing links."), "", + ?T("- Links on top for quicker navigation: " + "Previous day, Next day, Up."), "", + ?T("- CSS is used for style definition, and a custom " + "CSS file can be used."), "", + ?T("- URLs on messages and subjects are converted to hyperlinks."), "", + ?T("- Timezone used on timestamps is shown on the log files."), "", + ?T("- A custom link can be added on top of each page."), "", + ?T("The module depends on 'mod_muc'.")], + opts => + [{access_log, + #{value => ?T("AccessName"), + desc => + ?T("This option restricts which occupants are " + "allowed to enable or disable room logging. " + "The default value is 'muc_admin'. NOTE: " + "for this default setting you need to have an " + "access rule for 'muc_admin' in order to take effect.")}}, + {cssfile, + #{value => ?T("Path | URL"), + desc => + ?T("With this option you can set whether the HTML " + "files should have a custom CSS file or if they " + "need to use the embedded CSS. Allowed values " + "are either 'Path' to local file or an 'URL' to " + "a remote file. By default a predefined CSS will " + "be embedded into the HTML page.")}}, + {dirname, + #{value => "room_jid | room_name", + desc => + ?T("Allows to configure the name of the room directory. " + "If set to 'room_jid', the room directory name will " + "be the full room JID. Otherwise, the room directory " + "name will be only the room name, not including the " + "MUC service name. The default value is 'room_jid'.")}}, + {dirtype, + #{value => "subdirs | plain", + desc => + ?T("The type of the created directories can be specified " + "with this option. If set to 'subdirs', subdirectories " + "are created for each year and month. Otherwise, the " + "names of the log files contain the full date, and " + "there are no subdirectories. The default value is 'subdirs'.")}}, + {file_format, + #{value => "html | plaintext", + desc => + ?T("Define the format of the log files: 'html' stores " + "in HTML format, 'plaintext' stores in plain text. " + "The default value is 'html'.")}}, + {file_permissions, + #{value => "{mode: Mode, group: Group}", + desc => + ?T("Define the permissions that must be used when " + "creating the log files: the number of the mode, " + "and the numeric id of the group that will own the " + "files. The default value is shown in the example below:"), + example => + ["file_permissions:", + " mode: 644", + " group: 33"]}}, + {outdir, + #{value => ?T("Path"), + desc => + ?T("This option sets the full path to the directory " + "in which the HTML files should be stored. " + "Make sure the ejabberd daemon user has write " + "access on that directory. The default value is 'www/muc'.")}}, + {spam_prevention, + #{value => "true | false", + desc => + ?T("If set to 'true', a special attribute is added to links " + "that prevent their indexation by search engines. " + "The default value is 'true', which mean that 'nofollow' " + "attributes will be added to user submitted links.")}}, + {timezone, + #{value => "local | universal", + desc => + ?T("The time zone for the logs is configurable with " + "this option. If set to 'local', the local time, as " + "reported to Erlang emulator by the operating system, " + "will be used. Otherwise, UTC time will be used. " + "The default value is 'local'.")}}, + {url, + #{value => ?T("URL"), + desc => + ?T("A top level 'URL' where a client can access " + "logs of a particular conference. The conference name " + "is appended to the URL if 'dirname' option is set to " + "'room_name' or a conference JID is appended to the 'URL' " + "otherwise. There is no default value.")}}, + {top_link, + #{value => "{URL: Text}", + desc => + ?T("With this option you can customize the link on " + "the top right corner of each log file. " + "The default value is shown in the example below:"), + example => + ["top_link:", + " /: Home"]}}]}. diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl index e76e9a63d..f55d21959 100644 --- a/src/mod_multicast.erl +++ b/src/mod_multicast.erl @@ -41,7 +41,7 @@ -export([init/1, handle_info/2, handle_call/3, handle_cast/2, terminate/2, code_change/3]). --export([purge_loop/1, mod_opt_type/1, mod_options/1, depends/2]). +-export([purge_loop/1, mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("translate.hrl"). @@ -1160,3 +1160,82 @@ mod_options(Host) -> {limits, [{local, []}, {remote, []}]}, {vcard, undefined}, {name, ?T("Multicast")}]. + +mod_doc() -> + #{desc => + [?T("This module implements a service for " + "https://xmpp.org/extensions/xep-0054.html" + "[XEP-0033: Extended Stanza Addressing].")], + opts => + [{access, + #{value => "Access", + desc => + ?T("The access rule to restrict who can send packets to " + "the multicast service. Default value: 'all'.")}}, + {host, + #{desc => ?T("Deprecated. Use 'hosts' instead.")}}, + {hosts, + #{value => ?T("[Host, ...]"), + desc => + [?T("This option defines the Jabber IDs of the service. " + "If the 'hosts' option is not specified, the only " + "Jabber ID will be the hostname of the virtual host " + "with the prefix \"multicast.\". The keyword '@HOST@' " + "is replaced with the real virtual host name."), + ?T("The default value is 'multicast.@HOST@'.")]}}, + {limits, + #{value => "Sender: Stanza: Number", + desc => + [?T("Specify a list of custom limits which override the " + "default ones defined in XEP-0033. Limits are defined " + "per sender type and stanza type, where:"), + ?T("- The sender type can be: 'local' or 'remote'."), "", + ?T("- The stanza type can be: 'message' or 'presence'."), "", + ?T("- The number can be a positive integer or the key word 'infinite'.")], + example => + [{?T("Default values:"), + ["local:", + " message: 100", + " presence: 100", + "remote:", + " message: 20", + " presence: 20"]} + ]}}, + {name, + #{desc => ?T("Service name to provide in the Info query to the " + "Service Discovery. Default is '\"Multicast\"'.")}}, + {vcard, + #{desc => ?T("vCard element to return when queried. " + "Default value is 'undefined'.")}}], + example => + ["# Only admins can send packets to multicast service", + "access_rules:", + " multicast:", + " - allow: admin", + "", + "# If you want to allow all your users:", + "access_rules:", + " multicast:", + " - allow", + "", + "# This allows both admins and remote users to send packets,", + "# but does not allow local users", + "acl:", + " allservers:", + " server_glob: \"*\"", + "access_rules:", + " multicast:", + " - allow: admin", + " - deny: local", + " - allow: allservers", + "", + "modules:", + " mod_multicast:", + " host: multicast.example.org", + " access: multicast", + " limits:", + " local:", + " message: 40", + " presence: infinite", + " remote:", + " message: 150"]}. diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 6a3f3f2d7..80ce855b1 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -63,7 +63,7 @@ webadmin_user/4, webadmin_user_parse_query/5]). --export([mod_opt_type/1, mod_options/1, depends/2]). +-export([mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]). -deprecated({get_queue_length,2}). @@ -1235,3 +1235,119 @@ mod_options(Host) -> {use_cache, ejabberd_option:use_cache(Host)}, {cache_size, ejabberd_option:cache_size(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + [?T("This module implements " + "https://xmpp.org/extensions/xep-0160.html" + "[XEP-0160: Best Practices for Handling Offline Messages] " + "and https://xmpp.org/extensions/xep-0013.html" + "[XEP-0013: Flexible Offline Message Retrieval]. " + "This means that all messages sent to an offline user " + "will be stored on the server until that user comes online " + "again. Thus it is very similar to how email works. A user " + "is considered offline if no session presence priority > 0 " + "are currently open."), "", + ?T("NOTE: 'ejabberdctl' has a command to " + "delete expired messages (see chapter" + "https://docs.ejabberd.im/admin/guide/managing" + "[Managing an ejabberd server] in online documentation.")], + opts => + [{access_max_user_messages, + #{value => ?T("AccessName"), + desc => + ?T("This option defines which access rule will be " + "enforced to limit the maximum number of offline " + "messages that a user can have (quota). When a user " + "has too many offline messages, any new messages that " + "they receive are discarded, and a " + "error is returned to the sender. The default value is " + "'max_user_offline_messages'.")}}, + {store_empty_body, + #{value => "true | false | unless_chat_state", + desc => + ?T("Whether or not to store messages that lack a " + "element. The default value is 'unless_chat_state', " + "which tells ejabberd to store messages even if they " + "lack the element, unless they only contain a " + "chat state notification (as defined in " + "https://xmpp.org/extensions/xep-0085.html" + "[XEP-0085: Chat State Notifications].")}}, + {store_groupchat, + #{value => "true | false", + desc => + ?T("Whether or not to store groupchat messages. " + "The default value is 'false'.")}}, + {use_mam_for_storage, + #{value => "true | false", + desc => + ?T("This is an experimetal option. Enabling this option " + "will make 'mod_offline' not use the former spool " + "table for storing MucSub offline messages, but will " + "use the archive table instead. This use of the archive " + "table is cleaner and it makes it possible for clients " + "to slowly drop the former offline use case and rely on " + "message archive instead. It also further reduces the " + "storage required when you enabled MucSub. Enabling this " + "option has a known drawback for the moment: most of " + "flexible message retrieval queries don't work (those that " + "allow retrieval/deletion of messages by id), but this " + "specification is not widely used. The default value " + "is 'false' to keep former behaviour as default and " + "ensure this option is disabled.")}}, + {bounce_groupchat, + #{value => "true | false", + desc => + ?T("This option is use the disable an optimisation that " + "avoids bouncing error messages when groupchat messages " + "could not be stored as offline. It will reduce chat " + "room load, without any drawback in standard use cases. " + "You may change default value only if you have a custom " + "module which uses offline hook after 'mod_offline'. This " + "option can be useful for both standard MUC and MucSub, " + "but the bounce is much more likely to happen in the context " + "of MucSub, so it is even more important to have it on " + "large MucSub services. The default value is 'false', meaning " + "the optimisation is enabled.")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}], + example => + [{?T("This example allows power users to have as much as 5000 " + "offline messages, administrators up to 2000, and all the " + "other users up to 100:"), + ["acl:", + " admin:", + " user:", + " - admin1@localhost", + " - admin2@example.org", + " poweruser:", + " user:", + " - bob@example.org", + " - jane@example.org", + "", + "shaper_rules:", + " max_user_offline_messages:", + " - 5000: poweruser", + " - 2000: admin", + " - 100", + "", + "modules:", + " ...", + " mod_offline:", + " access_max_user_messages: max_user_offline_messages", + " ..." + ]}]}. diff --git a/src/mod_ping.erl b/src/mod_ping.erl index 8e6827247..f44fb8fd9 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -49,7 +49,7 @@ -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2, code_change/3]). --export([iq_ping/1, user_online/3, user_offline/3, +-export([iq_ping/1, user_online/3, user_offline/3, mod_doc/0, user_send/1, mod_opt_type/1, mod_options/1, depends/2]). -record(state, @@ -271,3 +271,56 @@ mod_options(_Host) -> {ping_ack_timeout, undefined}, {send_pings, false}, {timeout_action, none}]. + +mod_doc() -> + #{desc => + ?T("This module implements support for " + "https://xmpp.org/extensions/xep-0199.html" + "[XEP-0199: XMPP Ping] and periodic keepalives. " + "When this module is enabled ejabberd responds " + "correctly to ping requests, as defined by the protocol."), + opts => + [{ping_interval, + #{value => "timeout()", + desc => + ?T("How often to send pings to connected clients, " + "if option 'send_pings' is set to 'true'. If a client " + "connection does not send or receive any stanza " + "within this interval, a ping request is sent to " + "the client. The default value is '1' minute.")}}, + {ping_ack_timeout, + #{value => "timeout()", + desc => + ?T("How long to wait before deeming that a client " + "has not answered a given server ping request. " + "The default value is '32' seconds.")}}, + {send_pings, + #{value => "true | false", + desc => + ?T("If this option is set to 'true', the server " + "sends pings to connected clients that are not " + "active in a given interval defined in 'ping_interval' " + "option. This is useful to keep client connections " + "alive or checking availability. " + "The default value is 'false'.")}}, + {timeout_action, + #{value => "none | kill", + desc => + ?T("What to do when a client does not answer to a " + "server ping request in less than period defined " + "in 'ping_ack_timeout' option: " + "'kill' means destroying the underlying connection, " + "'none' means to do nothing. NOTE: when 'mod_stream_mgmt' " + "module is loaded and stream management is enabled by " + "a client, killing the client connection doesn't mean " + "killing the client session - the session will be kept " + "alive in order to give the client a chance to resume it. " + "The default value is 'none'.")}}], + example => + ["modules:", + " ...", + " mod_ping:", + " send_pings: true", + " ping_interval: 4 min", + " timeout_action: kill", + " ..."]}. diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl index f151e6310..13f1e23c9 100644 --- a/src/mod_pres_counter.erl +++ b/src/mod_pres_counter.erl @@ -28,10 +28,10 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, check_packet/4, - mod_opt_type/1, mod_options/1, depends/2]). + mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). - +-include("translate.hrl"). -include("xmpp.hrl"). -record(pres_counter, @@ -129,3 +129,33 @@ mod_opt_type(interval) -> mod_options(_) -> [{count, 5}, {interval, timer:seconds(60)}]. + +mod_doc() -> + #{desc => + ?T("This module detects flood/spam in presence " + "subscriptions traffic. If a user sends or receives " + "more of those stanzas in a given time interval, " + "the exceeding stanzas are silently dropped, and a " + "warning is logged."), + opts => + [{count, + #{value => ?T("Number"), + desc => + ?T("The number of subscription presence stanzas " + "(subscribe, unsubscribe, subscribed, unsubscribed) " + "allowed for any direction (input or output) per time " + "defined in 'interval' option. Please note that two " + "users subscribing to each other usually generate 4 " + "stanzas, so the recommended value is '4' or more. " + "The default value is '5'.")}}, + {interval, + #{value => "timeout()", + desc => + ?T("The time interval. The default value is '1' minute.")}}], + example => + ["modules:", + " ...", + " mod_pres_counter:", + " count: 5", + " interval: 30 secs", + " ..."]}. diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index 850fd6aa5..919f2f9e0 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -36,7 +36,7 @@ check_packet/4, remove_user/2, encode_list_item/1, get_user_lists/2, get_user_list/3, set_list/1, set_list/4, set_default_list/3, - user_send_packet/1, + user_send_packet/1, mod_doc/0, import_start/2, import_stop/2, import/5, import_info/0, mod_opt_type/1, mod_options/1, depends/2]). @@ -868,3 +868,35 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + [?T("This module implements " + "https://xmpp.org/extensions/xep-0016.html" + "[XEP-0016: Privacy Lists]."), "", + ?T("NOTE: Nowadays modern XMPP clients rely on " + "https://xmpp.org/extensions/xep-0191.html" + "[XEP-0191: Blocking Command] which is implemented by " + "'mod_blocking' module. However, you still need " + "'mod_privacy' loaded in order for 'mod_blocking' to work.")], + opts => + [{db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_private.erl b/src/mod_private.erl index 19f547642..54a216afc 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -33,7 +33,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process_sm_iq/1, import_info/0, - remove_user/2, get_data/2, get_data/3, export/1, + remove_user/2, get_data/2, get_data/3, export/1, mod_doc/0, import/5, import_start/2, mod_opt_type/1, set_data/2, mod_options/1, depends/2, get_sm_features/5, pubsub_publish_item/6]). @@ -111,6 +111,41 @@ mod_options(Host) -> {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. +mod_doc() -> + #{desc => + [?T("This module adds support for " + "https://xmpp.org/extensions/xep-0049.html" + "[XEP-0049: Private XML Storage]."), "", + ?T("Using this method, XMPP entities can store " + "private data on the server, retrieve it " + "whenever necessary and share it between multiple " + "connected clients of the same user. The data stored " + "might be anything, as long as it is a valid XML. " + "One typical usage is storing a bookmark of all user's conferences " + "(https://xmpp.org/extensions/xep-0048.html" + "[XEP-0048: Bookmarks]).")], + opts => + [{db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. + -spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]}, jid(), jid(), binary(), binary()) -> {error, stanza_error()} | empty | {result, [binary()]}. diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl index 9c05c60b0..097df393b 100644 --- a/src/mod_privilege.erl +++ b/src/mod_privilege.erl @@ -32,6 +32,7 @@ %% API -export([start/2, stop/1, reload/3, mod_opt_type/1, mod_options/1, depends/2]). +-export([mod_doc/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -82,6 +83,90 @@ mod_options(_) -> {presence, [{managed_entity, none}, {roster, none}]}, {message, [{outgoing,none}]}]. +mod_doc() -> + #{desc => + [?T("This module is an implementation of " + "https://xmpp.org/extensions/xep-0356.html" + "[XEP-0356: Privileged Entity]. This extension " + "allows components to have privileged access to " + "other entity data (send messages on behalf of the " + "server or on behalf of a user, get/set user roster, " + "access presence information, etc.). This may be used " + "to write powerful external components, for example " + "implementing an external " + "https://xmpp.org/extensions/xep-0163.html[PEP] or " + "https://xmpp.org/extensions/xep-0313.html[MAM] service."), "", + ?T("By default a component does not have any privileged access. " + "It is worth noting that the permissions grant access to " + "the component to a specific data type for all users of " + "the virtual host on which 'mod_privilege' is loaded."), "", + ?T("NOTE: This module is complementary to 'mod_delegation', " + "but can also be used separately.")], + opts => + [{roster, + #{value => ?T("Options"), + desc => + ?T("This option defines roster permissions. " + "By default no permissions are given. " + "The 'Options' are:")}, + [{both, + #{value => ?T("AccessName"), + desc => + ?T("Sets read/write access to a user's roster. " + "The default value is 'none'.")}}, + {get, + #{value => ?T("AccessName"), + desc => + ?T("Sets read access to a user's roster. " + "The default value is 'none'.")}}, + {set, + #{value => ?T("AccessName"), + desc => + ?T("Sets write access to a user's roster. " + "The default value is 'none'.")}}]}, + {message, + #{value => ?T("Options"), + desc => + ?T("This option defines permissions for messages. " + "By default no permissions are given. " + "The 'Options' are:")}, + [{outgoing, + #{value => ?T("AccessName"), + desc => + ?T("The option defines an access rule for sending " + "outgoing messages by the component. " + "The default value is 'none'.")}}]}, + {presence, + #{value => ?T("Options"), + desc => + ?T("This option defines permissions for presences. " + "By default no permissions are given. " + "The 'Options' are:")}, + [{managed_entity, + #{value => ?T("AccessName"), + desc => + ?T("An access rule that gives permissions to " + "the component to receive server presences. " + "The default value is 'none'.")}}, + {roster, + #{value => ?T("AccessName"), + desc => + ?T("An access rule that gives permissions to " + "the component to receive the presence of both " + "the users and the contacts in their roster. " + "The default value is 'none'.")}}]}], + example => + ["modules:", + " ...", + " mod_privilege:", + " roster:", + " get: all", + " presence:", + " managed_entity: all", + " message:", + " outgoing: all", + " ..."]}. + depends(_, _) -> []. diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl index 718f49cb4..d682e6c6a 100644 --- a/src/mod_proxy65.erl +++ b/src/mod_proxy65.erl @@ -39,7 +39,7 @@ %% supervisor callbacks. -export([init/1]). --export([start_link/1, mod_opt_type/1, mod_options/1, depends/2]). +-export([start_link/1, mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). -define(PROCNAME, ejabberd_mod_proxy65). @@ -139,3 +139,150 @@ mod_options(Host) -> {recbuf, 65536}, {sndbuf, 65536}, {shaper, none}]. + +mod_doc() -> + #{desc => + ?T("This module implements " + "https://xmpp.org/extensions/xep-0065.html" + "[XEP-0065: SOCKS5 Bytestreams]. It allows ejabberd " + "to act as a file transfer proxy between two XMPP clients."), + opts => + [{host, + #{desc => ?T("Deprecated. Use 'hosts' instead.")}}, + {hosts, + #{value => ?T("[Host, ...]"), + desc => + ?T("This option defines the Jabber IDs of the service. " + "If the 'hosts' option is not specified, the only Jabber ID will " + "be the hostname of the virtual host with the prefix \"proxy.\". " + "The keyword '@HOST@' is replaced with the real virtual host name.")}}, + {name, + #{value => ?T("Name"), + desc => + ?T("The value of the service name. This name is only visible in some " + "clients that support https://xmpp.org/extensions/xep-0030.html" + "[XEP-0030: Service Discovery]. The default is \"SOCKS5 Bytestreams\".")}}, + {access, + #{value => ?T("AccessName"), + desc => + ?T("Defines an access rule for file transfer initiators. " + "The default value is 'all'. You may want to restrict " + "access to the users of your server only, in order to " + "avoid abusing your proxy by the users of remote " + "servers.")}}, + {ram_db_type, + #{value => "mnesia | redis | sql", + desc => + ?T("Define the type of volatile (in-memory) storage where the module " + "will store room information.")}}, + {ip, + #{value => ?T("IPAddress"), + desc => + ?T("This option specifies which network interface to listen " + "for. The default value is an IP address of the service's " + "DNS name, or, if fails, '127.0.0.1'.")}}, + {hostname, + #{value => ?T("Host"), + desc => + ?T("Defines a hostname offered by the proxy when " + "establishing a session with clients. This is useful " + "when you run the proxy behind a NAT. The keyword " + "'@HOST@' is replaced with the virtual host name. " + "The default is to use the value of 'ip' option. " + "Examples: 'proxy.mydomain.org', '200.150.100.50'.")}}, + {port, + #{value => "1..65535", + desc => + ?T("A port number to listen for incoming connections. " + "The default value is '7777'.")}}, + {auth_type, + #{value => "anonymous | plain", + desc => + ?T("SOCKS5 authentication type. " + "The default value is 'anonymous'. " + "If set to 'plain', ejabberd will use " + "authentication backend as it would " + "for SASL PLAIN.")}}, + {max_connections, + #{value => "pos_integer() | infinity", + desc => + ?T("Maximum number of active connections per file transfer " + "initiator. The default value is 'infinity'.")}}, + {shaper, + #{value => ?T("Shaper"), + desc => + ?T("This option defines a shaper for the file transfer peers. " + "A shaper with the maximum bandwidth will be selected. " + "The default is 'none', i.e. no shaper.")}}, + {recbuf, + #{value => ?T("Size"), + desc => + ?T("A size of the buffer for incoming packets. " + "If you define a shaper, set the value of this " + "option to the size of the shaper in order " + "to avoid traffic spikes in file transfers. " + "The default value is '65536' bytes.")}}, + {sndbuf, + #{value => ?T("Size"), + desc => + ?T("A size of the buffer for outgoing packets. " + "If you define a shaper, set the value of this " + "option to the size of the shaper in order " + "to avoid traffic spikes in file transfers. " + "The default value is '65536' bytes.")}}, + {vcard, + #{value => ?T("vCard"), + desc => + ?T("A custom vCard of the service that will be displayed " + "by some XMPP clients in Service Discovery. The value of " + "'vCard' is a YAML map constructed from an XML representation " + "of vCard. Since the representation has no attributes, " + "the mapping is straightforward."), + example => + [{?T("For example, the following XML representation of vCard:"), + ["", + " Conferences", + " ", + " ", + " Elm Street", + " ", + ""]}, + {?T("will be translated to:"), + ["vcard:", + " fn: Conferences", + " adr:", + " -", + " work: true", + " street: Elm Street"]}]}}], + example => + ["acl:", + " admin:", + " user: admin@example.org", + " proxy_users:", + " server: example.org", + "", + "access_rules:", + " proxy65_access:", + " allow: proxy_users", + "", + "shaper_rules:", + " proxy65_shaper:", + " none: admin", + " proxyrate: proxy_users", + "", + "shaper:", + " proxyrate: 10240", + "", + "modules:", + " ...", + " mod_proxy65:", + " host: proxy1.example.org", + " name: \"File Transfer Proxy\"", + " ip: 200.150.100.1", + " port: 7778", + " max_connections: 5", + " access: proxy65_access", + " shaper: proxy65_shaper", + " recbuf: 10240", + " sndbuf: 10240", + " ..."]}. diff --git a/src/mod_push.erl b/src/mod_push.erl index c9804a7e5..f964108a4 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -31,7 +31,7 @@ %% gen_mod callbacks. -export([start/2, stop/1, reload/3, mod_opt_type/1, mod_options/1, depends/2]). - +-export([mod_doc/0]). %% ejabberd_hooks callbacks. -export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2, c2s_handle_cast/2, c2s_stanza/3, mam_message/7, offline_message/1, @@ -153,6 +153,60 @@ mod_options(Host) -> {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. +mod_doc() -> + #{desc => + ?T("This module implements the XMPP server's part of " + "the push notification solution specified in " + "https://xmpp.org/extensions/xep-0357.html" + "[XEP-0357: Push Notifications]. It does not generate, " + "for example, APNS or FCM notifications directly. " + "Instead, it's designed to work with so-called " + "\"app servers\" operated by third-party vendors of " + "mobile apps. Those app servers will usually trigger " + "notification delivery to the user's mobile device using " + "platform-dependant backend services such as FCM or APNS."), + opts => + [{include_sender, + #{value => "true | false", + desc => + ?T("If this option is set to 'true', the sender's JID " + "is included with push notifications generated for " + "incoming messages with a body. " + "The default value is 'false'.")}}, + {include_body, + #{value => "true | false | Text", + desc => + ?T("If this option is set to 'true', the message text " + "is included with push notifications generated for " + "incoming messages with a body. The option can instead " + "be set to a static 'Text', in which case the specified " + "text will be included in place of the actual message " + "body. This can be useful to signal the app server " + "whether the notification was triggered by a message " + "with body (as opposed to other types of traffic) " + "without leaking actual message contents. " + "The default value is \"New message\".")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. + %%-------------------------------------------------------------------- %% ejabberd command callback. %%-------------------------------------------------------------------- diff --git a/src/mod_push_keepalive.erl b/src/mod_push_keepalive.erl index abbc49a57..11117a371 100644 --- a/src/mod_push_keepalive.erl +++ b/src/mod_push_keepalive.erl @@ -30,13 +30,14 @@ %% gen_mod callbacks. -export([start/2, stop/1, reload/3, mod_opt_type/1, mod_options/1, depends/2]). - +-export([mod_doc/0]). %% ejabberd_hooks callbacks. -export([c2s_session_pending/1, c2s_session_resumed/1, c2s_copy_session/2, c2s_handle_cast/2, c2s_handle_info/2, c2s_stanza/3]). -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -define(PUSH_BEFORE_TIMEOUT_PERIOD, 120000). % 2 minutes. @@ -90,6 +91,45 @@ mod_options(_Host) -> {wake_on_start, false}, {wake_on_timeout, true}]. +mod_doc() -> + #{desc => + [?T("This module tries to keep the stream management " + "session (see 'mod_stream_mgmt') of a disconnected " + "mobile client alive if the client enabled push " + "notifications for that session. However, the normal " + "session resumption timeout is restored once a push " + "notification is issued, so the session will be closed " + "if the client doesn't respond to push notifications."), "", + ?T("The module depends on 'mod_push'.")], + opts => + [{resume_timeout, + #{value => "timeout()", + desc => + ?T("This option specifies the period of time until " + "the session of a disconnected push client times out. " + "This timeout is only in effect as long as no push " + "notification is issued. Once that happened, the " + "resumption timeout configured for the 'mod_stream_mgmt' " + "module is restored. " + "The default value is '72' minutes.")}}, + {wake_on_start, + #{value => "true | false", + desc => + ?T("If this option is set to 'true', notifications " + "are generated for **all** registered push clients " + "during server startup. This option should not be " + "enabled on servers with many push clients as it " + "can generate significant load on the involved push " + "services and the server itself. " + "The default value is 'false'.")}}, + {wake_on_timeout, + #{value => "true | false", + desc => + ?T("If this option is set to 'true', a notification " + "is generated shortly before the session would time " + "out as per the 'resume_timeout' option. " + "The default value is 'true'.")}}]}. + %%-------------------------------------------------------------------- %% Register/unregister hooks. %%-------------------------------------------------------------------- diff --git a/src/mod_register.erl b/src/mod_register.erl index 1f943e61a..2a28737b0 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -35,7 +35,7 @@ c2s_unauthenticated_packet/2, try_register/4, process_iq/1, send_registration_notifications/3, mod_opt_type/1, mod_options/1, depends/2, - format_error/1]). + format_error/1, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -620,3 +620,68 @@ mod_options(_Host) -> {registration_watchers, []}, {redirect_url, undefined}, {welcome_message, {<<>>, <<>>}}]. + +mod_doc() -> + #{desc => + [?T("This module adds support for https://xmpp.org/extensions/xep-0077.html" + "[XEP-0077: In-Band Registration]. " + "This protocol enables end users to use a XMPP client to:"), "", + ?T("* Register a new account on the server."), "", + ?T("* Change the password from an existing account on the server."), "", + ?T("* Delete an existing account on the server.")], + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("Specify rules to restrict what usernames can be registered and " + "unregistered. If a rule returns 'deny' on the requested username, " + "registration and unregistration of that user name is denied. " + "There are no restrictions by default.")}}, + {access_from, + #{value => ?T("AccessName"), + desc => + ?T("By default, 'ejabberd' doesn't allow to register new accounts " + "from s2s or existing c2s sessions. You can change it by defining " + "access rule in this option. Use with care: allowing registration " + "from s2s leads to uncontrolled massive accounts creation by rogue users.")}}, + {access_remove, + #{value => ?T("AccessName"), + desc => + ?T("Specify rules to restrict access for user unregistration. " + "By default any user is able to unregister their account.")}}, + {captcha_protected, + #{value => "true | false", + desc => + ?T("Protect registrations with CAPTCHA (see section " + "https://docs.ejabberd.im/admin/configuration/#captcha[CAPTCHA] " + "of the Configuration Guide). The default is 'false'.")}}, + {ip_access, + #{value => ?T("AccessName"), + desc => + ?T("Define rules to allow or deny account registration depending " + "on the IP address of the XMPP client. The 'AccessName' should " + "be of type 'ip'. The default value is 'all'.")}}, + {password_strength, + #{value => "Entropy", + desc => + ?T("This option sets the minimum " + "https://en.wikipedia.org/wiki/Entropy_(information_theory)" + "[Shannon entropy] for passwords. The value 'Entropy' is a " + "number of bits of entropy. The recommended minimum is 32 bits. " + "The default is 0, i.e. no checks are performed.")}}, + {registration_watchers, + #{value => "[JID, ...]", + desc => + ?T("This option defines a list of JIDs which will be notified each " + "time a new account is registered.")}}, + {redirect_url, + #{value => ?T("URL"), + desc => + ?T("This option enables registration redirection as described in " + "https://xmpp.org/extensions/xep-0077.html#redirect" + "[XEP-0077: In-Band Registration: Redirection].")}}, + {welcome_message, + #{value => "{subject: Subject, body: Body}", + desc => + ?T("Set a welcome message that is sent to each newly registered account. " + "The message will have subject 'Subject' and text 'Body'.")}}]}. diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl index 3ff172f13..967dc0769 100644 --- a/src/mod_register_web.erl +++ b/src/mod_register_web.erl @@ -56,6 +56,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process/2, mod_options/1, depends/2]). +-export([mod_doc/0]). -include("logger.hrl"). @@ -628,3 +629,12 @@ get_error_text({error, wrong_parameters}) -> mod_options(_) -> []. + +mod_doc() -> + #{desc => + [?T("This module provides a web page where users can:"), "", + ?T("- Register a new account on the server."), "", + ?T("- Change the password from an existing account on the server."), "", + ?T("- Delete an existing account on the server."), "", + ?T("The module depends on 'mod_register' where all the configuration " + "is performed.")]}. diff --git a/src/mod_roster.erl b/src/mod_roster.erl index e2d98ec13..62fb40ae0 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -48,7 +48,7 @@ out_subscription/1, set_items/3, remove_user/2, get_jid_info/4, encode_item/1, webadmin_page/3, webadmin_user/4, get_versioning_feature/2, - roster_version/2, + roster_version/2, mod_doc/0, mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3, process_rosteritems/5, depends/2, set_item_and_notify_clients/3]). @@ -1255,3 +1255,68 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + ?T("This module implements roster management as " + "defined in https://tools.ietf.org/html/rfc6121#section-2" + "[RFC6121 Section 2]. The module also adds support for " + "https://xmpp.org/extensions/xep-0237.html" + "[XEP-0237: Roster Versioning]."), + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("This option can be configured to specify " + "rules to restrict roster management. " + "If the rule returns 'deny' on the requested " + "user name, that user cannot modify their personal " + "roster, i.e. they cannot add/remove/modify contacts " + "or send presence subscriptions. " + "The default value is 'all', i.e. no restrictions.")}}, + {versioning, + #{value => "true | false", + desc => + ?T("Enables/disables Roster Versioning. " + "The default value is 'false'.")}}, + {store_current_id, + #{value => "true | false", + desc => + ?T("If this option is set to 'true', the current " + "roster version number is stored on the database. " + "If set to 'false', the roster version number is " + "calculated on the fly each time. Enabling this " + "option reduces the load for both ejabberd and the database. " + "This option does not affect the client in any way. " + "This option is only useful if option 'versioning' is " + "set to 'true'. The default value is 'false'. " + "IMPORTANT: if you use 'mod_shared_roster' or " + "'mod_shared_roster_ldap', you must set the value " + "of the option to 'false'.")}}, + {db_type, + #{value => "mnesia | sql", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}], + example => + ["modules:", + " ...", + " mod_roster:", + " versioning: true", + " store_current_id: false", + " ..."]}. diff --git a/src/mod_s2s_dialback.erl b/src/mod_s2s_dialback.erl index dd941a3d2..85ccdbce0 100644 --- a/src/mod_s2s_dialback.erl +++ b/src/mod_s2s_dialback.erl @@ -26,6 +26,7 @@ %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). +-export([mod_doc/0]). %% Hooks -export([s2s_out_auth_result/2, s2s_out_downgraded/2, s2s_in_packet/2, s2s_out_packet/2, s2s_in_recv/3, @@ -95,6 +96,40 @@ mod_opt_type(access) -> mod_options(_Host) -> [{access, all}]. +mod_doc() -> + #{desc => + [?T("The module adds support for " + "https://xmpp.org/extensions/xep-0220.html" + "[XEP-0220: Server Dialback] to provide server identity " + "verification based on DNS."), "", + ?T("WARNING: DNS-based verification is vulnerable to " + "https://en.wikipedia.org/wiki/DNS_spoofing" + "[DNS cache poisoning], so modern servers rely on " + "verification based on PKIX certificates. Thus this module " + "is only recommended for backward compatibility " + "with servers running outdated software or non-TLS servers, " + "or those with invalid certificates (as long as you accept " + "the risks, e.g. you assume that the remote server has " + "an invalid certificate due to poor administration and " + "not because it's compromised).")], + opts => + [{access, + #{value => ?T("AccessName"), + desc => + ?T("An access rule that can be used to restrict " + "dialback for some servers. The default value " + "is 'all'.")}}], + example => + ["modules:", + " ...", + " mod_s2s_dialback:", + " access:", + " allow:", + " server: legacy.domain.tld", + " server: invalid-cert.example.org", + " deny: all", + " ..."]}. + s2s_in_features(Acc, _) -> [#db_feature{errors = true}|Acc]. diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl index eca95cb27..ecd704dc4 100644 --- a/src/mod_service_log.erl +++ b/src/mod_service_log.erl @@ -30,10 +30,10 @@ -behaviour(gen_mod). -export([start/2, stop/1, log_user_send/1, mod_options/1, - log_user_receive/1, mod_opt_type/1, depends/2]). + log_user_receive/1, mod_opt_type/1, depends/2, mod_doc/0]). -include("logger.hrl"). - +-include("translate.hrl"). -include("xmpp.hrl"). start(Host, _Opts) -> @@ -82,3 +82,26 @@ mod_opt_type(loggers) -> mod_options(_) -> [{loggers, []}]. + +mod_doc() -> + #{desc => + ?T("This module forwards copies of all stanzas " + "to remote XMPP servers or components. " + "Every stanza is encapsulated into " + "element as described in " + "https://xmpp.org/extensions/xep-0297.html" + "[XEP-0297: Stanza Forwarding]."), + opts => + [{loggers, + #{value => "[Domain, ...]", + desc => + ?T("A list of servers or connected components " + "to which stanzas will be forwarded.")}}], + example => + ["modules:", + " ...", + " mod_service_log:", + " loggers:", + " - xmpp-server.tld", + " - component.domain.tld", + " ..."]}. diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 0ce97d1ca..73305bb2c 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -39,7 +39,7 @@ delete_group/2, get_group_opts/2, set_group_opts/3, get_group_users/2, get_group_explicit_users/2, is_user_in_group/3, add_user_to_group/3, opts_to_binary/1, - remove_user_from_group/3, mod_opt_type/1, mod_options/1, depends/2]). + remove_user_from_group/3, mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]). -include("logger.hrl"). @@ -1031,3 +1031,83 @@ mod_opt_type(db_type) -> mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}]. + +mod_doc() -> + #{desc => + [?T("This module enables you to create shared roster groups: " + "groups of accounts that can see members from (other) groups " + "in their rosters."), "", + ?T("The big advantages of this feature are that end users do not " + "need to manually add all users to their rosters, and that they " + "cannot permanently delete users from the shared roster groups. " + "A shared roster group can have members from any XMPP server, " + "but the presence will only be available from and to members of " + "the same virtual host where the group is created. It still " + "allows the users to have / add their own contacts, as it does " + "not replace the standard roster. Instead, the shared roster " + "contacts are merged to the relevant users at retrieval time. " + "The standard user rosters thus stay unmodified."), "", + ?T("Shared roster groups can be edited only via the Web Admin. " + "Each group has unique identification and those parameters:"), "", + ?T("- Name: The group's name will be displayed in the roster."), "", + ?T("- Description: of the group, which has no effect."), "", + ?T("- Members: A list of JIDs of group members, entered one per " + "line in the Web Admin. The special member directive '@all@' " + "represents all the registered users in the virtual host; " + "which is only recommended for a small server with just a few " + "hundred users. The special member directive '@online@' " + "represents the online users in the virtual host. With those " + "two directives, the actual list of members in those shared " + "rosters is generated dynamically at retrieval time."), "", + ?T("- Displayed groups: A list of groups that will be in the " + "rosters of this group's members. A group of other vhost can " + "be identified with 'groupid@vhost'."), "", + ?T("This module depends on 'mod_roster'. " + "If not enabled, roster queries will return 503 errors.")], + opts => + [{db_type, + #{value => "mnesia | sql", + desc => + ?T("Define the type of storage where the module will create " + "the tables and store user information. The default is " + "the storage defined by the global option 'default_db', " + "or 'mnesia' if omitted. If 'sql' value is defined, " + "make sure you have defined the database.")}}], + example => + [{?T("Take the case of a computer club that wants all its members " + "seeing each other in their rosters. To achieve this, they " + "need to create a shared roster group similar to this one:"), + ["Identification: club_members", + "Name: Club Members", + "Description: Members from the computer club", + "Members: member1@example.org, member2@example.org, member3@example.org", + "Displayed Groups: club_members"]}, + {?T("In another case we have a company which has three divisions: " + "Management, Marketing and Sales. All group members should see " + "all other members in their rosters. Additionally, all managers " + "should have all marketing and sales people in their roster. " + "Simultaneously, all marketeers and the whole sales team " + "should see all managers. This scenario can be achieved by " + "creating shared roster groups as shown in the following lists:"), + ["First list:", + "Identification: management", + "Name: Management", + "Description: Management", + "Members: manager1@example.org, manager2@example.org", + "Displayed Groups: management, marketing, sales", + "", + "Second list:", + "Identification: marketing", + "Name: Marketing", + "Description: Marketing", + "Members: marketeer1@example.org, marketeer2@example.org, marketeer3@example.org", + "Displayed Groups: management, marketing", + "", + "Third list:", + "Identification: sales", + "Name: Sales", + "Description: Sales", + "Members: salesman1@example.org, salesman2@example.org, salesman3@example.org", + "Displayed Groups: management, sales" + ]} + ]}. diff --git a/src/mod_sic.erl b/src/mod_sic.erl index f8105f2cd..037e514c2 100644 --- a/src/mod_sic.erl +++ b/src/mod_sic.erl @@ -32,7 +32,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process_local_iq/1, - process_sm_iq/1, mod_options/1, depends/2]). + process_sm_iq/1, mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -96,3 +96,14 @@ get_ip({User, Server, Resource}, mod_options(_Host) -> []. + +mod_doc() -> + #{desc => + [?T("This module adds support for " + "https://xmpp.org/extensions/xep-0279.html" + "[XEP-0279: Server IP Check]. This protocol enables " + "a client to discover its external IP address."), "", + ?T("WARNING: The protocol extension is deferred and seems " + "like there are no clients supporting it, so using this " + "module is not recommended and, furthermore, the module " + "might be removed in the future.")]}. diff --git a/src/mod_sip.erl b/src/mod_sip.erl index 0e8394e37..6e239b35b 100644 --- a/src/mod_sip.erl +++ b/src/mod_sip.erl @@ -29,7 +29,7 @@ -include("logger.hrl"). -ifndef(SIP). --export([start/2, stop/1, depends/2, mod_options/1]). +-export([start/2, stop/1, depends/2, mod_options/1, mod_doc/0]). start(_, _) -> ?CRITICAL_MSG("ejabberd is not compiled with SIP support", []), {error, sip_not_compiled}. @@ -49,9 +49,11 @@ mod_options(_) -> -export([data_in/2, data_out/2, message_in/2, message_out/2, request/2, request/3, response/2, - locate/1, mod_opt_type/1, mod_options/1, depends/2]). + locate/1, mod_opt_type/1, mod_options/1, depends/2, + mod_doc/0]). -include_lib("esip/include/esip.hrl"). +-include("translate.hrl"). %%%=================================================================== %%% API @@ -336,20 +338,30 @@ mod_opt_type(routes) -> econf:list(econf:sip_uri()); mod_opt_type(via) -> econf:list( - econf:and_then( - econf:options( - #{type => econf:enum([tcp, tls, udp]), - host => econf:domain(), - port => econf:port()}, - [{required, [type, host]}]), - fun(Opts) -> - Type = proplists:get_value(type, Opts), - Host = proplists:get_value(host, Opts), - Port = proplists:get_value(port, Opts), - {Type, {Host, Port}} - end)). + fun(L) when is_list(L) -> + (econf:and_then( + econf:options( + #{type => econf:enum([tcp, tls, udp]), + host => econf:domain(), + port => econf:port()}, + [{required, [type, host]}]), + fun(Opts) -> + Type = proplists:get_value(type, Opts), + Host = proplists:get_value(host, Opts), + Port = proplists:get_value(port, Opts), + {Type, {Host, Port}} + end))(L); + (U) -> + (econf:and_then( + econf:url([tls, tcp, udp]), + fun(URI) -> + {ok, {Type, _, Host, Port, _, _}} = + http_uri:parse(binary_to_list(URI)), + {Type, {unicode:characters_to_binary(Host), Port}} + end))(U) + end, [unique]). --spec mod_options(binary()) -> [{via, [{tcp | tls | udp, {binary(), 1..65535 | undefined}}]} | +-spec mod_options(binary()) -> [{via, [{tcp | tls | udp, {binary(), 1..65535}}]} | {atom(), term()}]. mod_options(Host) -> Route = #uri{scheme = <<"sip">>, @@ -363,3 +375,79 @@ mod_options(Host) -> {via, []}]. -endif. + +mod_doc() -> + #{desc => + [?T("This module adds SIP proxy/registrar support " + "for the corresponding virtual host."), "", + ?T("NOTE: It is not enough to just load this module. " + "You should also configure listeners and DNS records " + "properly. See section " + "https://docs.ejabberd.im/admin/configuration/#sip[SIP] " + "of the Configuration Guide for details.")], + opts => + [{always_record_route, + #{value => "true | false", + desc => + ?T("Always insert \"Record-Route\" header into " + "SIP messages. This approach allows to bypass " + "NATs/firewalls a bit more easily. " + "The default value is 'true'.")}}, + {flow_timeout_tcp, + #{value => "timeout()", + desc => + ?T("The option sets a keep-alive timer for " + "https://tools.ietf.org/html/rfc5626[SIP outbound] " + "TCP connections. The default value is '2' minutes.")}}, + {flow_timeout_udp, + #{value => "timeout()", + desc => + ?T("The options sets a keep-alive timer for " + "https://tools.ietf.org/html/rfc5626[SIP outbound] " + "UDP connections. The default value is '29' seconds.")}}, + {record_route, + #{value => ?T("URI"), + desc => + ?T("When the option 'always_record_route' is set to " + "'true' or when https://tools.ietf.org/html/rfc5626" + "[SIP outbound] is utilized, ejabberd inserts " + "\"Record-Route\" header field with this 'URI' into " + "a SIP message. The default is a SIP URI constructed " + "from the virtual host on which the module is loaded.")}}, + {routes, + #{value => "[URI, ...]", + desc => + ?T("You can set a list of SIP URIs of routes pointing " + "to this SIP proxy server. The default is a list containing " + "a single SIP URI constructed from the virtual host " + "on which the module is loaded.")}}, + {via, + #{value => "[URI, ...]", + desc => + ?T("A list to construct \"Via\" headers for " + "inserting them into outgoing SIP messages. " + "This is useful if you're running your SIP proxy " + "in a non-standard network topology. Every 'URI' " + "element in the list must be in the form of " + "\"scheme://host:port\", where \"transport\" " + "must be 'tls', 'tcp', or 'udp', \"host\" must " + "be a domain name or an IP address and \"port\" " + "must be an internet port number. Note that all " + "parts of the 'URI' are mandatory (e.g. you " + "cannot omit \"port\" or \"scheme\").")}}], + example => + ["modules:", + " ...", + " mod_sip:", + " always_record_route: false", + " record_route: \"sip:example.com;lr\"", + " routes:", + " - \"sip:example.com;lr\"", + " - \"sip:sip.example.com;lr\"", + " flow_timeout_udp: 30 sec", + " flow_timeout_tcp: 1 min", + " via:", + " - tls://sip-tls.example.com:5061", + " - tcp://sip-tcp.example.com:5060", + " - udp://sip-udp.example.com:5060", + " ..."]}. diff --git a/src/mod_stats.erl b/src/mod_stats.erl index 34d1d4c10..27a6140ac 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -32,7 +32,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process_iq/1, - mod_options/1, depends/2]). + mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -235,3 +235,23 @@ search_running_node(SNode, [Node | Nodes]) -> mod_options(_Host) -> []. + +mod_doc() -> + #{desc => + [?T("This module adds support for " + "https://xmpp.org/extensions/xep-0039.html" + "[XEP-0039: Statistics Gathering]. This protocol " + "allows you to retrieve the following statistics " + "from your ejabberd server:"), "", + ?T("- Total number of registered users on the current " + "virtual host (users/total)."), "", + ?T("- Total number of registered users on all virtual " + "hosts (users/all-hosts/total)."), "", + ?T("- Total number of online users on the current " + "virtual host (users/online)."), "", + ?T("- Total number of online users on all virtual " + "hosts (users/all-hosts/online)."), "", + ?T("NOTE: The protocol extension is deferred and seems " + "like even a few clients that were supporting it " + "are now abandoned. So using this module makes " + "very little sense.")]}. diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 92b9e4020..63254bcac 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -27,6 +27,7 @@ %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). +-export([mod_doc/0]). %% hooks -export([c2s_stream_started/2, c2s_stream_features/2, c2s_authenticated_packet/2, c2s_unauthenticated_packet/2, @@ -849,3 +850,82 @@ mod_options(Host) -> {cache_life_time, timer:hours(48)}, {resend_on_timeout, false}, {queue_type, ejabberd_option:queue_type(Host)}]. + +mod_doc() -> + #{desc => + ?T("This module adds support for " + "https://xmpp.org/extensions/xep-0198.html" + "[XEP-0198: Stream Management]. This protocol allows " + "active management of an XML stream between two XMPP " + "entities, including features for stanza acknowledgements " + "and stream resumption."), + opts => + [{max_ack_queue, + #{value => ?T("Size"), + desc => + ?T("This option specifies the maximum number of " + "unacknowledged stanzas queued for possible " + "retransmission. When the limit is exceeded, " + "the client session is terminated. The allowed " + "values are positive integers and 'infinity'. " + "You should be careful when setting this value " + "as it should not be set too low, otherwise, " + "you could kill sessions in a loop, before they " + "get the chance to finish proper session initiation. " + "It should definitely be set higher that the size " + "of the offline queue (for example at least 3 times " + "the value of the max offline queue and never lower " + "than '1000'). The default value is '5000'.")}}, + {resume_timeout, + #{value => "timeout()", + desc => + ?T("This option configures the (default) period of time " + "until a session times out if the connection is lost. " + "During this period of time, a client may resume its " + "session. Note that the client may request a different " + "timeout value, see the 'max_resume_timeout' option. " + "Setting it to '0' effectively disables session resumption. " + "The default value is '5' minutes.")}}, + {max_resume_timeout, + #{value => "timeout()", + desc => + ?T("A client may specify the period of time until a session " + "times out if the connection is lost. During this period " + "of time, the client may resume its session. This option " + "limits the period of time a client is permitted to request. " + "It must be set to a timeout equal to or larger than the " + "default 'resume_timeout'. By default, it is set to the " + "same value as the 'resume_timeout' option.")}}, + {ack_timeout, + #{value => "timeout()", + desc => + ?T("A time to wait for stanza acknowledgements. " + "Setting it to 'infinity' effectively disables the timeout. " + "The default value is '1' minute.")}}, + {resend_on_timeout, + #{value => "true | false | if_offline", + desc => + ?T("If this option is set to 'true', any message stanzas " + "that weren't acknowledged by the client will be resent " + "on session timeout. This behavior might often be desired, " + "but could have unexpected results under certain circumstances. " + "For example, a message that was sent to two resources might " + "get resent to one of them if the other one timed out. " + "Therefore, the default value for this option is 'false', " + "which tells ejabberd to generate an error message instead. " + "As an alternative, the option may be set to 'if_offline'. " + "In this case, unacknowledged messages are resent only if " + "no other resource is online when the session times out. " + "Otherwise, error messages are generated.")}}, + {queue_type, + #{value => "ram | file", + desc => + ?T("Same as top-level 'queue_type' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_time.erl b/src/mod_time.erl index 53f2e5432..a511ef5d8 100644 --- a/src/mod_time.erl +++ b/src/mod_time.erl @@ -33,7 +33,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process_local_iq/1, - mod_options/1, depends/2]). + mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -69,3 +69,10 @@ depends(_Host, _Opts) -> mod_options(_Host) -> []. + +mod_doc() -> + #{desc => + ?T("This module adds support for " + "https://xmpp.org/extensions/xep-0202.html" + "[XEP-0202: Entity Time]. In other words, " + "the module reports server's system time.")}. diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 9adf8f71a..bf58c2e9d 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -33,7 +33,7 @@ -behaviour(gen_server). -behaviour(gen_mod). --export([start/2, stop/1, get_sm_features/5, mod_options/1, +-export([start/2, stop/1, get_sm_features/5, mod_options/1, mod_doc/0, process_local_iq/1, process_sm_iq/1, string2lower/1, remove_user/2, export/1, import_info/0, import/5, import_start/2, depends/2, process_search/1, process_vcard/1, get_vcard/2, @@ -586,3 +586,94 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + ?T("This module allows end users to store and retrieve " + "their vCard, and to retrieve other users vCards, " + "as defined in https://xmpp.org/extensions/xep-0054.html" + "[XEP-0054: vcard-temp]. The module also implements an " + "uncomplicated Jabber User Directory based on the vCards " + "of these users. Moreover, it enables the server to send " + "its vCard when queried."), + opts => + [{allow_return_all, + #{value => "true | false", + desc => + ?T("This option enables you to specify if search " + "operations with empty input fields should return " + "all users who added some information to their vCard. " + "The default value is 'false'.")}}, + {host, + #{desc => ?T("Deprecated. Use 'hosts' instead.")}}, + {hosts, + #{value => ?T("[Host, ...]"), + desc => + ?T("This option defines the Jabber IDs of the service. " + "If the 'hosts' option is not specified, the only Jabber ID will " + "be the hostname of the virtual host with the prefix \"vjud.\". " + "The keyword '@HOST@' is replaced with the real virtual host name.")}}, + {name, + #{value => ?T("Name"), + desc => + ?T("The value of the service name. This name is only visible in some " + "clients that support https://xmpp.org/extensions/xep-0030.html" + "[XEP-0030: Service Discovery]. The default is 'vCard User Search'.")}}, + {matches, + #{value => "pos_integer() | infinity", + desc => + ?T("With this option, the number of reported search results " + "can be limited. If the option's value is set to 'infinity', " + "all search results are reported. The default value is '30'.")}}, + {search, + #{value => "true | false", + desc => + ?T("This option specifies whether the search functionality " + "is enabled or not. If disabled, the options 'hosts', 'name' " + "and 'vcard' will be ignored and the Jabber User Directory " + "service will not appear in the Service Discovery item list. " + "The default value is 'false'.")}}, + {db_type, + #{value => "mnesia | sql | ldap", + desc => + ?T("Same as top-level 'default_db' option, but applied to this module only.")}}, + {use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}, + {vcard, + #{value => ?T("vCard"), + desc => + ?T("A custom vCard of the server that will be displayed " + "by some XMPP clients in Service Discovery. The value of " + "'vCard' is a YAML map constructed from an XML representation " + "of vCard. Since the representation has no attributes, " + "the mapping is straightforward."), + example => + [{?T("For example, the following XML representation of vCard:"), + ["", + " Conferences", + " ", + " ", + " Elm Street", + " ", + ""]}, + {?T("will be translated to:"), + ["vcard:", + " fn: Conferences", + " adr:", + " -", + " work: true", + " street: Elm Street"]}]}}]}. diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index c5a9fc617..b4c8edf53 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -31,7 +31,7 @@ -export([start_link/2]). -export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4, remove_user/2, import/3, search_fields/1, search_reported/1, - mod_opt_type/1, mod_options/1]). + mod_opt_type/1, mod_options/1, mod_doc/0]). -export([is_search_supported/1]). %% gen_server callbacks @@ -475,3 +475,100 @@ mod_options(Host) -> {ldap_tls_cacertfile, ejabberd_option:ldap_tls_cacertfile(Host)}, {ldap_tls_depth, ejabberd_option:ldap_tls_depth(Host)}, {ldap_tls_verify, ejabberd_option:ldap_tls_verify(Host)}]. + +mod_doc() -> + #{opts => + [{ldap_search_fields, + #{value => "{Name: Attribute, ...}", + desc => + ?T("This option defines the search form and the LDAP " + "attributes to search within. 'Name' is the name of a " + "search form field which will be automatically " + "translated by using the translation files " + "(see 'msgs/*.msg' for available words). " + "'Attribute' is the LDAP attribute or the pattern '%u'."), + example => + [{?T("The default is:"), + ["User: \"%u\"", + "\"Full Name\": displayName", + "\"Given Name\": givenName", + "\"Middle Name\": initials", + "\"Family Name\": sn", + "Nickname: \"%u\"", + "Birthday: birthDay", + "Country: c", + "City: l", + "Email: mail", + "\"Organization Name\": o", + "\"Organization Unit\": ou"] + }]}}, + {ldap_search_reported, + #{value => "{SearchField: VcardField}, ...}", + desc => + ?T("This option defines which search fields should be " + "reported. 'SearchField' is the name of a search form " + "field which will be automatically translated by using " + "the translation files (see 'msgs/*.msg' for available " + "words). 'VcardField' is the vCard field name defined " + "in the 'ldap_vcard_map' option."), + example => + [{?T("The default is:"), + ["\"Full Name\": FN", + "\"Given Name\": FIRST", + "\"Middle Name\": MIDDLE", + "\"Family Name\": LAST", + "\"Nickname\": NICKNAME", + "\"Birthday\": BDAY", + "\"Country\": CTRY", + "\"City\": LOCALITY", + "\"Email\": EMAIL", + "\"Organization Name\": ORGNAME", + "\"Organization Unit\": ORGUNIT"] + }]}}, + {ldap_vcard_map, + #{value => "{Name: {Pattern, LDAPattributes}, ...}", + desc => + ?T("With this option you can set the table that maps LDAP " + "attributes to vCard fields. 'Name' is the type name of " + "the vCard as defined in " + "http://tools.ietf.org/html/rfc2426[RFC 2426]. " + "'Pattern' is a string which contains " + "pattern variables '%u', '%d' or '%s'. " + "'LDAPattributes' is the list containing LDAP attributes. " + "The pattern variables '%s' will be sequentially replaced " + "with the values of LDAP attributes from " + "'List_of_LDAP_attributes', '%u' will be replaced with " + "the user part of a JID, and '%d' will be replaced with " + "the domain part of a JID."), + example => + [{?T("The default is:"), + ["NICKNAME: {\"%u\": []}", + "FN: {\"%s\": [displayName]}", + "LAST: {\"%s\": [sn]}", + "FIRST: {\"%s\": [givenName]}", + "MIDDLE: {\"%s\": [initials]}", + "ORGNAME: {\"%s\": [o]}", + "ORGUNIT: {\"%s\": [ou]}", + "CTRY: {\"%s\": [c]}", + "LOCALITY: {\"%s\": [l]}", + "STREET: {\"%s\": [street]}", + "REGION: {\"%s\": [st]}", + "PCODE: {\"%s\": [postalCode]}", + "TITLE: {\"%s\": [title]}", + "URL: {\"%s\": [labeleduri]}", + "DESC: {\"%s\": [description]}", + "TEL: {\"%s\": [telephoneNumber]}", + "EMAIL: {\"%s\": [mail]}", + "BDAY: {\"%s\": [birthDay]}", + "ROLE: {\"%s\": [employeeType]}", + "PHOTO: {\"%s\": [jpegPhoto]}"] + }]}}] ++ + [{Opt, + #{desc => + {?T("Same as top-level '~s' option, but " + "applied to this module only."), [Opt]}}} + || Opt <- [ldap_base, ldap_servers, ldap_uids, + ldap_deref_aliases, ldap_encrypt, ldap_password, + ldap_port, ldap_rootdn, ldap_filter, + ldap_tls_certfile, ldap_tls_cacertfile, + ldap_tls_depth, ldap_tls_verify, ldap_backups]]}. diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index d4394b677..a8850f42d 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -31,7 +31,7 @@ search_fields/1, search_reported/1, remove_user/2]). -export([is_search_supported/1]). -export([need_transform/1, transform/1]). --export([mod_opt_type/1, mod_options/1]). +-export([mod_opt_type/1, mod_options/1, mod_doc/0]). -include("xmpp.hrl"). -include("mod_vcard.hrl"). @@ -274,3 +274,12 @@ mod_opt_type(search_all_hosts) -> mod_options(_) -> [{search_all_hosts, true}]. + +mod_doc() -> + #{opts => + [{search_all_hosts, + #{value => "true | false", + desc => + ?T("Whether to perform search on all " + "virtual hosts or not. The default " + "value is 'true'.")}}]}. diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 927ea9594..1c0114fa8 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -31,13 +31,14 @@ %% gen_mod callbacks -export([start/2, stop/1, reload/3]). --export([update_presence/1, vcard_set/1, remove_user/2, +-export([update_presence/1, vcard_set/1, remove_user/2, mod_doc/0, user_send_packet/1, mod_opt_type/1, mod_options/1, depends/2]). %% API -export([compute_hash/1]). -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -define(VCARD_XUPDATE_CACHE, vcard_xupdate_cache). @@ -202,3 +203,51 @@ mod_options(Host) -> {cache_size, ejabberd_option:cache_size(Host)}, {cache_missed, ejabberd_option:cache_missed(Host)}, {cache_life_time, ejabberd_option:cache_life_time(Host)}]. + +mod_doc() -> + #{desc => + [?T("The user's client can store an avatar in the " + "user vCard. The vCard-Based Avatars protocol " + "(https://xmpp.org/extensions/xep-0153.html[XEP-0153]) " + "provides a method for clients to inform the contacts " + "what is the avatar hash value. However, simple or small " + "clients may not implement that protocol."), "", + ?T("If this module is enabled, all the outgoing client presence " + "stanzas get automatically the avatar hash on behalf of the " + "client. So, the contacts receive the presence stanzas with " + "the 'Update Data' described in " + "https://xmpp.org/extensions/xep-0153.html[XEP-0153] as if the " + "client would had inserted it itself. If the client had already " + "included such element in the presence stanza, it is replaced " + "with the element generated by ejabberd."), "", + ?T("By enabling this module, each vCard modification produces " + "a hash recalculation, and each presence sent by a client " + "produces hash retrieval and a presence stanza rewrite. " + "For this reason, enabling this module will introduce a " + "computational overhead in servers with clients that change " + "frequently their presence. However, the overhead is significantly " + "reduced by the use of caching, so you probably don't want " + "to set 'use_cache' to 'false'."), "", + ?T("The module depends on 'mod_vcard'."), "", + ?T("NOTE: Nowadays https://xmpp.org/extensions/xep-0153.html" + "[XEP-0153] is used mostly as \"read-only\", i.e. modern " + "clients don't publish their avatars inside vCards. Thus " + "in the majority of cases the module is only used along " + "with 'mod_avatar' module for providing backward compatibility.")], + opts => + [{use_cache, + #{value => "true | false", + desc => + ?T("Same as top-level 'use_cache' option, but applied to this module only.")}}, + {cache_size, + #{value => "pos_integer() | infinity", + desc => + ?T("Same as top-level 'cache_size' option, but applied to this module only.")}}, + {cache_missed, + #{value => "true | false", + desc => + ?T("Same as top-level 'cache_missed' option, but applied to this module only.")}}, + {cache_life_time, + #{value => "timeout()", + desc => + ?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}. diff --git a/src/mod_version.erl b/src/mod_version.erl index 6def24951..e5b87ab68 100644 --- a/src/mod_version.erl +++ b/src/mod_version.erl @@ -32,7 +32,7 @@ -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process_local_iq/1, - mod_opt_type/1, mod_options/1, depends/2]). + mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -81,3 +81,16 @@ mod_opt_type(show_os) -> mod_options(_Host) -> [{show_os, true}]. + +mod_doc() -> + #{desc => + ?T("This module implements " + "https://xmpp.org/extensions/xep-0092.html" + "[XEP-0092: Software Version]. Consequently, " + "it answers ejabberd's version when queried."), + opts => + [{show_os, + #{value => "true | false", + desc => + ?T("Should the operating system be revealed or not. " + "The default value is 'true'.")}}]}.